From 5dea801d7475051d3a7fa36bfd9f792809f5c1ae Mon Sep 17 00:00:00 2001 From: Sina Sharafzadeh Date: Wed, 11 Mar 2026 02:11:10 +0100 Subject: [PATCH 1/4] Add gamepad controller support for physical match control Introduces a GamepadController provider that maps PS5 DualSense (and compatible) gamepad buttons to game commands (HALT, STOP, kick-offs, free kicks, timeouts, continue actions, auto-continue toggle). Adds a GamepadStatus toolbar component showing connection state, active button, and a button-to-action reference tooltip. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/App.vue | 2 + frontend/src/components/GamepadStatus.vue | 120 +++++++++ frontend/src/plugins/control/index.ts | 3 + .../src/providers/gamepadController/index.ts | 237 ++++++++++++++++++ 4 files changed, 362 insertions(+) create mode 100644 frontend/src/components/GamepadStatus.vue create mode 100644 frontend/src/providers/gamepadController/index.ts diff --git a/frontend/src/App.vue b/frontend/src/App.vue index cea678d0..9bd228e2 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -9,6 +9,7 @@ import ProtocolList from "@/components/protocol/ProtocolList.vue"; import {useQuasar} from "quasar"; import {useUiStateStore} from "@/store/uiState"; import {useProtocolStore} from "@/store/protocolState"; +import GamepadStatus from "@/components/GamepadStatus.vue"; const uiStore = useUiStateStore() const protocolStore = useProtocolStore() @@ -89,6 +90,7 @@ const dev = computed(() => { Show Shortcuts + diff --git a/frontend/src/components/GamepadStatus.vue b/frontend/src/components/GamepadStatus.vue new file mode 100644 index 00000000..4b7a998a --- /dev/null +++ b/frontend/src/components/GamepadStatus.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/frontend/src/plugins/control/index.ts b/frontend/src/plugins/control/index.ts index 718a9637..5b2fb927 100644 --- a/frontend/src/plugins/control/index.ts +++ b/frontend/src/plugins/control/index.ts @@ -6,15 +6,18 @@ import type {App} from "vue"; import {useProtocolStore} from "@/store/protocolState"; import {ManualActions} from "@/providers/manualActions"; import {Shortcuts} from "@/providers/shortcuts"; +import {GamepadController} from "@/providers/gamepadController"; export const control = { install(app: App) { const controlApi = new ControlApi() const manualActions = new ManualActions(controlApi) const shortcuts = new Shortcuts(manualActions, controlApi) + const gamepadController = new GamepadController(manualActions, controlApi) app.provide('control-api', controlApi) app.provide('command-actions', manualActions) app.provide('shortcuts', shortcuts) + app.provide('gamepad-controller', gamepadController) const matchStateStore = useMatchStateStore() controlApi.RegisterConsumer((output: OutputJson) => { diff --git a/frontend/src/providers/gamepadController/index.ts b/frontend/src/providers/gamepadController/index.ts new file mode 100644 index 00000000..4ceef1c9 --- /dev/null +++ b/frontend/src/providers/gamepadController/index.ts @@ -0,0 +1,237 @@ +import {reactive} from 'vue' +import type {ManualActions} from "@/providers/manualActions"; +import type {ControlApi} from "@/providers/controlApi"; +import {useGcStateStore} from "@/store/gcState"; + +// Standard PS5 DualSense / generic gamepad button labels +export const GAMEPAD_BUTTON_LABELS: Record = { + 0: 'Cross (×)', + 1: 'Circle (○)', + 2: 'Square (□)', + 3: 'Triangle (△)', + 4: 'L1', + 5: 'R1', + 6: 'L2', + 7: 'R2', + 8: 'Create', + 9: 'Options', + 10: 'L3', + 11: 'R3', + 12: 'D-Pad ↑', + 13: 'D-Pad ↓', + 14: 'D-Pad ←', + 15: 'D-Pad →', + 16: 'PS', + 17: 'Touchpad', +} + +// What action each button triggers (for display in the UI) +export const GAMEPAD_BUTTON_ACTIONS: Record = { + 0: 'STOP', + 1: 'HALT', + 2: 'Force Start', + 3: 'Normal Start', + 4: 'Kick-off Yellow', + 5: 'Kick-off Blue', + 6: 'Direct Yellow', + 7: 'Direct Blue', + 8: 'Toggle Auto-Continue', + 9: 'Continue (action 1)', + 10: 'Timeout Yellow', + 11: 'Timeout Blue', + 12: 'Continue (action 2)', + 13: 'Continue (action 3)', + 14: 'Continue (action 4)', + 15: 'Continue (action 5)', +} + +export interface GamepadControllerState { + connected: boolean + gamepadId: string + activeButton: number | null +} + +export class GamepadController { + private readonly manualActions: ManualActions + private readonly controlApi: ControlApi + private readonly gcStateStore = useGcStateStore() + private enabled: boolean = true + private animationFrameId: number | null = null + private previousButtonStates: boolean[] = [] + private currentGamepadIndex: number | null = null + + public readonly state = reactive({ + connected: false, + gamepadId: '', + activeButton: null, + }) + + constructor(manualActions: ManualActions, controlApi: ControlApi) { + this.manualActions = manualActions + this.controlApi = controlApi + this.init() + } + + public enable() { + this.enabled = true + } + + public disable() { + this.enabled = false + } + + public destroy() { + this.stopPolling() + } + + private init() { + window.addEventListener('gamepadconnected', (e: GamepadEvent) => { + this.onGamepadConnected(e.gamepad) + }) + window.addEventListener('gamepaddisconnected', (e: GamepadEvent) => { + this.onGamepadDisconnected(e.gamepad) + }) + // Chrome requires user interaction before dispatching gamepadconnected. + // Poll once at startup to detect gamepads already plugged in. + this.checkExistingGamepads() + } + + private checkExistingGamepads() { + const gamepads = navigator.getGamepads() + for (const gamepad of gamepads) { + if (gamepad) { + this.onGamepadConnected(gamepad) + break + } + } + } + + private onGamepadConnected(gamepad: Gamepad) { + this.currentGamepadIndex = gamepad.index + this.state.connected = true + this.state.gamepadId = gamepad.id + this.previousButtonStates = new Array(gamepad.buttons.length).fill(false) + this.startPolling() + } + + private onGamepadDisconnected(gamepad: Gamepad) { + if (gamepad.index === this.currentGamepadIndex) { + this.currentGamepadIndex = null + this.state.connected = false + this.state.gamepadId = '' + this.state.activeButton = null + this.stopPolling() + } + } + + private startPolling() { + if (this.animationFrameId !== null) return + const poll = () => { + this.pollGamepad() + this.animationFrameId = requestAnimationFrame(poll) + } + this.animationFrameId = requestAnimationFrame(poll) + } + + private stopPolling() { + if (this.animationFrameId !== null) { + cancelAnimationFrame(this.animationFrameId) + this.animationFrameId = null + } + } + + private pollGamepad() { + if (this.currentGamepadIndex === null) return + const gamepads = navigator.getGamepads() + const gamepad = gamepads[this.currentGamepadIndex] + if (!gamepad) return + + gamepad.buttons.forEach((button, index) => { + const wasPressed = this.previousButtonStates[index] ?? false + const isPressed = button.pressed + + if (isPressed && !wasPressed) { + // Rising edge — button just pressed + this.state.activeButton = index + if (this.enabled) { + this.handleButtonPress(index) + } + } else if (!isPressed && wasPressed) { + if (this.state.activeButton === index) { + this.state.activeButton = null + } + } + + this.previousButtonStates[index] = isPressed + }) + } + + private handleButtonPress(buttonIndex: number) { + switch (buttonIndex) { + // Face buttons + case 0: // Cross (×) → STOP + this.manualActions.getCommandAction('STOP').send() + break + case 1: // Circle (○) → HALT + this.manualActions.getCommandAction('HALT').send() + break + case 2: // Square (□) → Force Start + this.manualActions.getCommandAction('FORCE_START').send() + break + case 3: // Triangle (△) → Normal Start + this.manualActions.getCommandAction('NORMAL_START').send() + break + + // Shoulder buttons + case 4: // L1 → Kick-off Yellow + this.manualActions.getCommandAction('KICKOFF', 'YELLOW').send() + break + case 5: // R1 → Kick-off Blue + this.manualActions.getCommandAction('KICKOFF', 'BLUE').send() + break + case 6: // L2 → Direct Free Kick Yellow + this.manualActions.getCommandAction('DIRECT', 'YELLOW').send() + break + case 7: // R2 → Direct Free Kick Blue + this.manualActions.getCommandAction('DIRECT', 'BLUE').send() + break + + // Center buttons + case 8: // Create/Share → Toggle Auto-Continue + this.controlApi.ChangeConfig({autoContinue: !this.gcStateStore.config.autoContinue}) + break + case 9: // Options → Continue with first available action + this.continueWithAction(0) + break + + // Stick clicks + case 10: // L3 → Timeout Yellow + this.manualActions.getCommandAction('TIMEOUT', 'YELLOW').send() + break + case 11: // R3 → Timeout Blue + this.manualActions.getCommandAction('TIMEOUT', 'BLUE').send() + break + + // D-Pad → cycle through continue actions + case 12: // D-Pad Up → Continue action 2 + this.continueWithAction(1) + break + case 13: // D-Pad Down → Continue action 3 + this.continueWithAction(2) + break + case 14: // D-Pad Left → Continue action 4 + this.continueWithAction(3) + break + case 15: // D-Pad Right → Continue action 5 + this.continueWithAction(4) + break + } + } + + private continueWithAction(id: number) { + const actions = this.gcStateStore.gcState.continueActions + if (actions && actions.length > id) { + this.controlApi.Continue(actions[id]) + } + } +} From 4fb5f7e50c277b26eedb33589dcdbe8a3c6197aa Mon Sep 17 00:00:00 2001 From: Sina Sharafzadeh Date: Wed, 11 Mar 2026 02:12:46 +0100 Subject: [PATCH 2/4] Update frontend protobuf generated files Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/proto/api/ssl_gc_api_pb.ts | 2 +- frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts | 2 +- frontend/src/proto/ci/ssl_gc_ci_pb.ts | 2 +- frontend/src/proto/engine/ssl_gc_engine_config_pb.ts | 2 +- frontend/src/proto/engine/ssl_gc_engine_pb.ts | 2 +- frontend/src/proto/geom/ssl_gc_geometry_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_common_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_game_event_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_referee_message_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_state_pb.ts | 2 +- frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts | 2 +- frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts | 2 +- frontend/src/proto/vision/ssl_vision_detection_pb.ts | 2 +- frontend/src/proto/vision/ssl_vision_geometry_pb.ts | 2 +- frontend/src/proto/vision/ssl_vision_wrapper_pb.ts | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/src/proto/api/ssl_gc_api_pb.ts b/frontend/src/proto/api/ssl_gc_api_pb.ts index 9c18325e..280a1621 100644 --- a/frontend/src/proto/api/ssl_gc_api_pb.ts +++ b/frontend/src/proto/api/ssl_gc_api_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file api/ssl_gc_api.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts b/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts index 7e082699..aea1c703 100644 --- a/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts +++ b/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file ci/autoref/ssl_autoref_ci.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/ci/ssl_gc_ci_pb.ts b/frontend/src/proto/ci/ssl_gc_ci_pb.ts index 21ae4a31..fe2945e0 100644 --- a/frontend/src/proto/ci/ssl_gc_ci_pb.ts +++ b/frontend/src/proto/ci/ssl_gc_ci_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file ci/ssl_gc_ci.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts b/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts index a5b5a6cf..910b296b 100644 --- a/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts +++ b/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file engine/ssl_gc_engine_config.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/engine/ssl_gc_engine_pb.ts b/frontend/src/proto/engine/ssl_gc_engine_pb.ts index 864d1a8b..cfa43036 100644 --- a/frontend/src/proto/engine/ssl_gc_engine_pb.ts +++ b/frontend/src/proto/engine/ssl_gc_engine_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file engine/ssl_gc_engine.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/geom/ssl_gc_geometry_pb.ts b/frontend/src/proto/geom/ssl_gc_geometry_pb.ts index b43c97c5..1979b9ba 100644 --- a/frontend/src/proto/geom/ssl_gc_geometry_pb.ts +++ b/frontend/src/proto/geom/ssl_gc_geometry_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file geom/ssl_gc_geometry.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts index 0467c59f..e4f73940 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon_autoref.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts index 0441e245..dad7247d 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts index 570e17ec..0a2dd11c 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon_remotecontrol.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts index 3705d0e3..5fe1e8db 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon_team.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_common_pb.ts b/frontend/src/proto/state/ssl_gc_common_pb.ts index a8bc8abe..4882044a 100644 --- a/frontend/src/proto/state/ssl_gc_common_pb.ts +++ b/frontend/src/proto/state/ssl_gc_common_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_common.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_game_event_pb.ts b/frontend/src/proto/state/ssl_gc_game_event_pb.ts index 71513f74..1ec08a27 100644 --- a/frontend/src/proto/state/ssl_gc_game_event_pb.ts +++ b/frontend/src/proto/state/ssl_gc_game_event_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_game_event.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_referee_message_pb.ts b/frontend/src/proto/state/ssl_gc_referee_message_pb.ts index 72eb5502..4f38aefd 100644 --- a/frontend/src/proto/state/ssl_gc_referee_message_pb.ts +++ b/frontend/src/proto/state/ssl_gc_referee_message_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_referee_message.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_state_pb.ts b/frontend/src/proto/state/ssl_gc_state_pb.ts index dece51ec..adac6da0 100644 --- a/frontend/src/proto/state/ssl_gc_state_pb.ts +++ b/frontend/src/proto/state/ssl_gc_state_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_state.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts b/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts index 7fba94c5..1e20aa0b 100644 --- a/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts +++ b/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file tracker/ssl_vision_detection_tracked.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts b/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts index 2d0539b6..db2a2b08 100644 --- a/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts +++ b/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file tracker/ssl_vision_wrapper_tracked.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/vision/ssl_vision_detection_pb.ts b/frontend/src/proto/vision/ssl_vision_detection_pb.ts index 557da6ec..3df2e327 100644 --- a/frontend/src/proto/vision/ssl_vision_detection_pb.ts +++ b/frontend/src/proto/vision/ssl_vision_detection_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file vision/ssl_vision_detection.proto (syntax proto2) -/* eslint-disable */ + import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/vision/ssl_vision_geometry_pb.ts b/frontend/src/proto/vision/ssl_vision_geometry_pb.ts index 8a3141e3..6ffb7212 100644 --- a/frontend/src/proto/vision/ssl_vision_geometry_pb.ts +++ b/frontend/src/proto/vision/ssl_vision_geometry_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file vision/ssl_vision_geometry.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts b/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts index 695a4b6b..112648ab 100644 --- a/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts +++ b/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file vision/ssl_vision_wrapper.proto (syntax proto2) -/* eslint-disable */ + import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; From 4a09f88ea21bd93b67a7d941ca35812d4ed41728 Mon Sep 17 00:00:00 2001 From: Sina Sharafzadeh Date: Wed, 11 Mar 2026 02:17:14 +0100 Subject: [PATCH 3/4] Restore eslint-disable comments in generated protobuf files Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/proto/api/ssl_gc_api_pb.ts | 2 +- frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts | 2 +- frontend/src/proto/ci/ssl_gc_ci_pb.ts | 2 +- frontend/src/proto/engine/ssl_gc_engine_config_pb.ts | 2 +- frontend/src/proto/engine/ssl_gc_engine_pb.ts | 2 +- frontend/src/proto/geom/ssl_gc_geometry_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts | 2 +- frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_common_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_game_event_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_referee_message_pb.ts | 2 +- frontend/src/proto/state/ssl_gc_state_pb.ts | 2 +- frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts | 2 +- frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts | 2 +- frontend/src/proto/vision/ssl_vision_detection_pb.ts | 2 +- frontend/src/proto/vision/ssl_vision_geometry_pb.ts | 2 +- frontend/src/proto/vision/ssl_vision_wrapper_pb.ts | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frontend/src/proto/api/ssl_gc_api_pb.ts b/frontend/src/proto/api/ssl_gc_api_pb.ts index 280a1621..9c18325e 100644 --- a/frontend/src/proto/api/ssl_gc_api_pb.ts +++ b/frontend/src/proto/api/ssl_gc_api_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file api/ssl_gc_api.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts b/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts index aea1c703..7e082699 100644 --- a/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts +++ b/frontend/src/proto/ci/autoref/ssl_autoref_ci_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file ci/autoref/ssl_autoref_ci.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/ci/ssl_gc_ci_pb.ts b/frontend/src/proto/ci/ssl_gc_ci_pb.ts index fe2945e0..21ae4a31 100644 --- a/frontend/src/proto/ci/ssl_gc_ci_pb.ts +++ b/frontend/src/proto/ci/ssl_gc_ci_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file ci/ssl_gc_ci.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts b/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts index 910b296b..a5b5a6cf 100644 --- a/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts +++ b/frontend/src/proto/engine/ssl_gc_engine_config_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file engine/ssl_gc_engine_config.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/engine/ssl_gc_engine_pb.ts b/frontend/src/proto/engine/ssl_gc_engine_pb.ts index cfa43036..864d1a8b 100644 --- a/frontend/src/proto/engine/ssl_gc_engine_pb.ts +++ b/frontend/src/proto/engine/ssl_gc_engine_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file engine/ssl_gc_engine.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/geom/ssl_gc_geometry_pb.ts b/frontend/src/proto/geom/ssl_gc_geometry_pb.ts index 1979b9ba..b43c97c5 100644 --- a/frontend/src/proto/geom/ssl_gc_geometry_pb.ts +++ b/frontend/src/proto/geom/ssl_gc_geometry_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file geom/ssl_gc_geometry.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts index e4f73940..0467c59f 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_autoref_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon_autoref.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts index dad7247d..0441e245 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts index 0a2dd11c..570e17ec 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_remotecontrol_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon_remotecontrol.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts b/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts index 5fe1e8db..3705d0e3 100644 --- a/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts +++ b/frontend/src/proto/rcon/ssl_gc_rcon_team_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file rcon/ssl_gc_rcon_team.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_common_pb.ts b/frontend/src/proto/state/ssl_gc_common_pb.ts index 4882044a..a8bc8abe 100644 --- a/frontend/src/proto/state/ssl_gc_common_pb.ts +++ b/frontend/src/proto/state/ssl_gc_common_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_common.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_game_event_pb.ts b/frontend/src/proto/state/ssl_gc_game_event_pb.ts index 1ec08a27..71513f74 100644 --- a/frontend/src/proto/state/ssl_gc_game_event_pb.ts +++ b/frontend/src/proto/state/ssl_gc_game_event_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_game_event.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_referee_message_pb.ts b/frontend/src/proto/state/ssl_gc_referee_message_pb.ts index 4f38aefd..72eb5502 100644 --- a/frontend/src/proto/state/ssl_gc_referee_message_pb.ts +++ b/frontend/src/proto/state/ssl_gc_referee_message_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_referee_message.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/state/ssl_gc_state_pb.ts b/frontend/src/proto/state/ssl_gc_state_pb.ts index adac6da0..dece51ec 100644 --- a/frontend/src/proto/state/ssl_gc_state_pb.ts +++ b/frontend/src/proto/state/ssl_gc_state_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file state/ssl_gc_state.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts b/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts index 1e20aa0b..7fba94c5 100644 --- a/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts +++ b/frontend/src/proto/tracker/ssl_vision_detection_tracked_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file tracker/ssl_vision_detection_tracked.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts b/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts index db2a2b08..2d0539b6 100644 --- a/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts +++ b/frontend/src/proto/tracker/ssl_vision_wrapper_tracked_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file tracker/ssl_vision_wrapper_tracked.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/vision/ssl_vision_detection_pb.ts b/frontend/src/proto/vision/ssl_vision_detection_pb.ts index 3df2e327..557da6ec 100644 --- a/frontend/src/proto/vision/ssl_vision_detection_pb.ts +++ b/frontend/src/proto/vision/ssl_vision_detection_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file vision/ssl_vision_detection.proto (syntax proto2) - +/* eslint-disable */ import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/vision/ssl_vision_geometry_pb.ts b/frontend/src/proto/vision/ssl_vision_geometry_pb.ts index 6ffb7212..8a3141e3 100644 --- a/frontend/src/proto/vision/ssl_vision_geometry_pb.ts +++ b/frontend/src/proto/vision/ssl_vision_geometry_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file vision/ssl_vision_geometry.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; diff --git a/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts b/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts index 112648ab..695a4b6b 100644 --- a/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts +++ b/frontend/src/proto/vision/ssl_vision_wrapper_pb.ts @@ -1,6 +1,6 @@ // @generated by protoc-gen-es v2.2.3 with parameter "target=ts,json_types=true" // @generated from file vision/ssl_vision_wrapper.proto (syntax proto2) - +/* eslint-disable */ import type { GenEnum, GenFile, GenMessage } from "@bufbuild/protobuf/codegenv1"; import { enumDesc, fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv1"; From ab61ed4c3c78a72a49c6ec06e9c2ce50af5fdf87 Mon Sep 17 00:00:00 2001 From: Sina Sharafzadeh Date: Wed, 11 Mar 2026 02:24:10 +0100 Subject: [PATCH 4/4] Make gamepad help menu stay open until clicked outside Replaces q-tooltip with q-menu so the button map stays visible until the user dismisses it by clicking elsewhere. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/components/GamepadStatus.vue | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/GamepadStatus.vue b/frontend/src/components/GamepadStatus.vue index 4b7a998a..7b11334f 100644 --- a/frontend/src/components/GamepadStatus.vue +++ b/frontend/src/components/GamepadStatus.vue @@ -43,15 +43,14 @@ const buttonMap = Object.entries(GAMEPAD_BUTTON_LABELS) dense flat round :icon="connected ? 'sports_esports' : 'videogame_asset_off'" :color="connected ? 'white' : 'grey-5'" - :title="connected ? `Connected: ${gamepadId}` : 'No gamepad connected'" > {{ activeButtonLabel }} - - + +
Gamepad Controls @@ -85,7 +84,7 @@ const buttonMap = Object.entries(GAMEPAD_BUTTON_LABELS) - +