mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
Add overlay gamepad support for keyboard-only mode (#17)
This commit is contained in:
@@ -8,6 +8,7 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
| 'annotationWebsocket'
|
||||
| 'logging'
|
||||
| 'texthooker'
|
||||
| 'controller'
|
||||
| 'shortcuts'
|
||||
| 'secondarySub'
|
||||
| 'subsync'
|
||||
@@ -31,6 +32,47 @@ export const CORE_DEFAULT_CONFIG: Pick<
|
||||
launchAtStartup: true,
|
||||
openBrowser: true,
|
||||
},
|
||||
controller: {
|
||||
enabled: true,
|
||||
preferredGamepadId: '',
|
||||
preferredGamepadLabel: '',
|
||||
smoothScroll: true,
|
||||
scrollPixelsPerSecond: 900,
|
||||
horizontalJumpPixels: 160,
|
||||
stickDeadzone: 0.2,
|
||||
triggerInputMode: 'auto',
|
||||
triggerDeadzone: 0.5,
|
||||
repeatDelayMs: 320,
|
||||
repeatIntervalMs: 120,
|
||||
buttonIndices: {
|
||||
select: 6,
|
||||
buttonSouth: 0,
|
||||
buttonEast: 1,
|
||||
buttonWest: 2,
|
||||
buttonNorth: 3,
|
||||
leftShoulder: 4,
|
||||
rightShoulder: 5,
|
||||
leftStickPress: 9,
|
||||
rightStickPress: 10,
|
||||
leftTrigger: 6,
|
||||
rightTrigger: 7,
|
||||
},
|
||||
bindings: {
|
||||
toggleLookup: 'buttonSouth',
|
||||
closeLookup: 'buttonEast',
|
||||
toggleKeyboardOnlyMode: 'buttonNorth',
|
||||
mineCard: 'buttonWest',
|
||||
quitMpv: 'select',
|
||||
previousAudio: 'none',
|
||||
nextAudio: 'rightShoulder',
|
||||
playCurrentAudio: 'leftShoulder',
|
||||
toggleMpvPause: 'leftStickPress',
|
||||
leftStickHorizontal: 'leftStickX',
|
||||
leftStickVertical: 'leftStickY',
|
||||
rightStickHorizontal: 'rightStickX',
|
||||
rightStickVertical: 'rightStickY',
|
||||
},
|
||||
},
|
||||
shortcuts: {
|
||||
toggleVisibleOverlayGlobal: 'Alt+Shift+O',
|
||||
copySubtitle: 'CommandOrControl+C',
|
||||
|
||||
@@ -19,6 +19,8 @@ test('config option registry includes critical paths and has unique entries', ()
|
||||
for (const requiredPath of [
|
||||
'logging.level',
|
||||
'annotationWebsocket.enabled',
|
||||
'controller.enabled',
|
||||
'controller.scrollPixelsPerSecond',
|
||||
'startupWarmups.lowPowerMode',
|
||||
'subtitleStyle.enableJlpt',
|
||||
'subtitleStyle.autoPauseVideoOnYomitanPopup',
|
||||
@@ -38,6 +40,7 @@ test('config template sections include expected domains and unique keys', () =>
|
||||
const requiredKeys: (typeof keys)[number][] = [
|
||||
'websocket',
|
||||
'annotationWebsocket',
|
||||
'controller',
|
||||
'startupWarmups',
|
||||
'subtitleStyle',
|
||||
'ankiConnect',
|
||||
|
||||
@@ -4,6 +4,21 @@ import { ConfigOptionRegistryEntry } from './shared';
|
||||
export function buildCoreConfigOptionRegistry(
|
||||
defaultConfig: ResolvedConfig,
|
||||
): ConfigOptionRegistryEntry[] {
|
||||
const controllerButtonEnumValues = [
|
||||
'none',
|
||||
'select',
|
||||
'buttonSouth',
|
||||
'buttonEast',
|
||||
'buttonNorth',
|
||||
'buttonWest',
|
||||
'leftShoulder',
|
||||
'rightShoulder',
|
||||
'leftStickPress',
|
||||
'rightStickPress',
|
||||
'leftTrigger',
|
||||
'rightTrigger',
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
path: 'logging.level',
|
||||
@@ -12,6 +27,230 @@ export function buildCoreConfigOptionRegistry(
|
||||
defaultValue: defaultConfig.logging.level,
|
||||
description: 'Minimum log level for runtime logging.',
|
||||
},
|
||||
{
|
||||
path: 'controller.enabled',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.controller.enabled,
|
||||
description: 'Enable overlay controller support through the Chrome Gamepad API.',
|
||||
},
|
||||
{
|
||||
path: 'controller.preferredGamepadId',
|
||||
kind: 'string',
|
||||
defaultValue: defaultConfig.controller.preferredGamepadId,
|
||||
description: 'Preferred controller id saved from the controller selection modal.',
|
||||
},
|
||||
{
|
||||
path: 'controller.preferredGamepadLabel',
|
||||
kind: 'string',
|
||||
defaultValue: defaultConfig.controller.preferredGamepadLabel,
|
||||
description: 'Preferred controller display label saved for diagnostics.',
|
||||
},
|
||||
{
|
||||
path: 'controller.smoothScroll',
|
||||
kind: 'boolean',
|
||||
defaultValue: defaultConfig.controller.smoothScroll,
|
||||
description: 'Use smooth scrolling for controller-driven popup scroll input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.scrollPixelsPerSecond',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.scrollPixelsPerSecond,
|
||||
description: 'Base popup scroll speed for controller stick input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.horizontalJumpPixels',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.horizontalJumpPixels,
|
||||
description: 'Popup page-jump distance for controller jump input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.stickDeadzone',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.stickDeadzone,
|
||||
description: 'Deadzone applied to controller stick axes.',
|
||||
},
|
||||
{
|
||||
path: 'controller.triggerInputMode',
|
||||
kind: 'enum',
|
||||
enumValues: ['auto', 'digital', 'analog'],
|
||||
defaultValue: defaultConfig.controller.triggerInputMode,
|
||||
description: 'How controller triggers are interpreted: auto, pressed-only, or thresholded analog.',
|
||||
},
|
||||
{
|
||||
path: 'controller.triggerDeadzone',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.triggerDeadzone,
|
||||
description: 'Minimum analog trigger value required when trigger input uses auto or analog mode.',
|
||||
},
|
||||
{
|
||||
path: 'controller.repeatDelayMs',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.repeatDelayMs,
|
||||
description: 'Delay before repeating held controller actions.',
|
||||
},
|
||||
{
|
||||
path: 'controller.repeatIntervalMs',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.repeatIntervalMs,
|
||||
description: 'Repeat interval for held controller actions.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.select',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.select,
|
||||
description: 'Raw button index used for the controller select/minus/back button.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.buttonSouth',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.buttonSouth,
|
||||
description: 'Raw button index used for controller south/A button input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.buttonEast',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.buttonEast,
|
||||
description: 'Raw button index used for controller east/B button input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.buttonNorth',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.buttonNorth,
|
||||
description: 'Raw button index used for controller north/Y button input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.buttonWest',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.buttonWest,
|
||||
description: 'Raw button index used for controller west/X button input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.leftShoulder',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.leftShoulder,
|
||||
description: 'Raw button index used for controller left shoulder input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.rightShoulder',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.rightShoulder,
|
||||
description: 'Raw button index used for controller right shoulder input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.leftStickPress',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.leftStickPress,
|
||||
description: 'Raw button index used for controller L3 input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.rightStickPress',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.rightStickPress,
|
||||
description: 'Raw button index used for controller R3 input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.leftTrigger',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.leftTrigger,
|
||||
description: 'Raw button index used for controller L2 input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.buttonIndices.rightTrigger',
|
||||
kind: 'number',
|
||||
defaultValue: defaultConfig.controller.buttonIndices.rightTrigger,
|
||||
description: 'Raw button index used for controller R2 input.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.toggleLookup',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.toggleLookup,
|
||||
description: 'Controller binding for toggling lookup.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.closeLookup',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.closeLookup,
|
||||
description: 'Controller binding for closing lookup.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.toggleKeyboardOnlyMode',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.toggleKeyboardOnlyMode,
|
||||
description: 'Controller binding for toggling keyboard-only mode.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.mineCard',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.mineCard,
|
||||
description: 'Controller binding for mining the active card.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.quitMpv',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.quitMpv,
|
||||
description: 'Controller binding for quitting mpv.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.previousAudio',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.previousAudio,
|
||||
description: 'Controller binding for previous Yomitan audio.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.nextAudio',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.nextAudio,
|
||||
description: 'Controller binding for next Yomitan audio.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.playCurrentAudio',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.playCurrentAudio,
|
||||
description: 'Controller binding for playing the current Yomitan audio.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.toggleMpvPause',
|
||||
kind: 'enum',
|
||||
enumValues: controllerButtonEnumValues,
|
||||
defaultValue: defaultConfig.controller.bindings.toggleMpvPause,
|
||||
description: 'Controller binding for toggling mpv play/pause.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.leftStickHorizontal',
|
||||
kind: 'enum',
|
||||
enumValues: ['leftStickX', 'leftStickY', 'rightStickX', 'rightStickY'],
|
||||
defaultValue: defaultConfig.controller.bindings.leftStickHorizontal,
|
||||
description: 'Axis binding used for left/right token selection.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.leftStickVertical',
|
||||
kind: 'enum',
|
||||
enumValues: ['leftStickX', 'leftStickY', 'rightStickX', 'rightStickY'],
|
||||
defaultValue: defaultConfig.controller.bindings.leftStickVertical,
|
||||
description: 'Axis binding used for primary popup scrolling.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.rightStickHorizontal',
|
||||
kind: 'enum',
|
||||
enumValues: ['leftStickX', 'leftStickY', 'rightStickX', 'rightStickY'],
|
||||
defaultValue: defaultConfig.controller.bindings.rightStickHorizontal,
|
||||
description: 'Axis binding reserved for alternate right-stick mappings.',
|
||||
},
|
||||
{
|
||||
path: 'controller.bindings.rightStickVertical',
|
||||
kind: 'enum',
|
||||
enumValues: ['leftStickX', 'leftStickY', 'rightStickX', 'rightStickY'],
|
||||
defaultValue: defaultConfig.controller.bindings.rightStickVertical,
|
||||
description: 'Axis binding used for popup page jumps.',
|
||||
},
|
||||
{
|
||||
path: 'texthooker.launchAtStartup',
|
||||
kind: 'boolean',
|
||||
|
||||
@@ -34,6 +34,16 @@ const CORE_TEMPLATE_SECTIONS: ConfigTemplateSection[] = [
|
||||
description: ['Controls logging verbosity.', 'Set to debug for full runtime diagnostics.'],
|
||||
key: 'logging',
|
||||
},
|
||||
{
|
||||
title: 'Controller Support',
|
||||
description: [
|
||||
'Gamepad support for the visible overlay while keyboard-only mode is active.',
|
||||
'Use the selection modal to save a preferred controller by id for future launches.',
|
||||
'Trigger input mode can be auto, digital-only, or analog-thresholded depending on the controller.',
|
||||
'Override controller.buttonIndices when your pad reports non-standard raw button numbers.',
|
||||
],
|
||||
key: 'controller',
|
||||
},
|
||||
{
|
||||
title: 'Startup Warmups',
|
||||
description: [
|
||||
|
||||
Reference in New Issue
Block a user