-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for keyboard shortcuts, xkeys, streamdeck and space…
…mouse
- Loading branch information
Showing
16 changed files
with
1,231 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
packages/apps/client/src/components/ScriptEditor/plugins/updateModel.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
packages/apps/client/src/lib/triggerActions/TriggerActionHandler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { assertNever } from '@sofie-prompter-editor/shared-lib' | ||
import { RootAppStore } from '../../stores/RootAppStore.ts' | ||
import { AnyTriggerAction } from './triggerActions.ts' | ||
|
||
/** | ||
* The TriggerActionHandler is responsible for listening to some action events and executing the actions accordingly. | ||
*/ | ||
export class TriggerActionHandler { | ||
private prompterSpeed = 0 | ||
|
||
constructor(private store: typeof RootAppStore) { | ||
this.onAction = this.onAction.bind(this) | ||
|
||
this.store.triggerStore.on('action', this.onAction) | ||
} | ||
|
||
private onAction(action: AnyTriggerAction) { | ||
console.log('action', JSON.stringify(action)) | ||
|
||
if (action.type === 'prompterMove') { | ||
this.prompterSpeed = action.payload.speed | ||
this.sendPrompterSpeed() | ||
} else if (action.type === 'prompterAccelerate') { | ||
this.prompterSpeed += action.payload.accelerate | ||
this.sendPrompterSpeed() | ||
} else if (action.type === 'prompterJump') { | ||
// TODO | ||
// this.store.connection.controller.sendMessage({ | ||
// speed: 0, | ||
// offset: action.payload.offset, | ||
// }) | ||
} else if (action.type === 'movePrompterToHere') { | ||
// Not handled here | ||
} else { | ||
assertNever(action) | ||
} | ||
} | ||
|
||
private sendPrompterSpeed() { | ||
// Send message with speed: | ||
|
||
// Modify the value so that | ||
const speed = this.attackCurve(this.prompterSpeed, 10, 0.7) | ||
|
||
this.store.connection.controller.sendMessage({ | ||
speed: speed, | ||
offset: null, | ||
}) | ||
} | ||
destroy(): void {} | ||
|
||
/** | ||
* Scales a value which follows a curve which at low values (0-normalValue) is pretty much linear, | ||
* but at higher values (normalValue+) grows much faster | ||
* @param x The value to scale | ||
* @param power The power of the curve | ||
* @param normalValue The value up to which at which the curve is mostly linear | ||
*/ | ||
private attackCurve(x: number, power = 1, normalValue = 1): number { | ||
const attack = Math.sign(x) * Math.abs(Math.pow(x, power) * Math.pow(1 / normalValue, power)) | ||
const linear = x | ||
return linear + attack | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
packages/apps/client/src/lib/triggerActions/triggerActions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
/* eslint-disable no-mixed-spaces-and-tabs */ | ||
|
||
import { ControllerMessage } from '@sofie-prompter-editor/shared-model' | ||
|
||
export type AnyTriggerAction = | ||
| TriggerAction< | ||
'prompterMove', | ||
{ | ||
/** The speed to move the prompter */ | ||
speed: number | ||
} | ||
> | ||
| TriggerAction< | ||
'prompterAccelerate', | ||
{ | ||
/** The acceleration move the prompter */ | ||
accelerate: number | ||
} | ||
> | ||
| TriggerAction< | ||
'prompterJump', | ||
{ | ||
offset: ControllerMessage['offset'] | ||
} | ||
> | ||
| TriggerAction< | ||
'movePrompterToHere', | ||
{ | ||
// nothing | ||
} | ||
> | ||
|
||
type TriggerAction<Type extends string, Payload extends Record<string, unknown>> = { | ||
type: Type | ||
payload: Payload | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { BindOptions } from '@sofie-automation/sorensen' | ||
import { AnyTriggerAction } from '../triggerActions/triggerActions.ts' | ||
import { DeviceModelId } from '@elgato-stream-deck/webhid' | ||
|
||
export type TriggerConfig = | ||
| TriggerConfigKeyboard | ||
| TriggerConfigXkeys | ||
| TriggerConfigStreamdeck | ||
| TriggerConfigSpacemouse | ||
|
||
export enum TriggerConfigType { | ||
KEYBOARD = 'keyboard', | ||
XKEYS = 'xkeys', | ||
STREAMDECK = 'streamdeck', | ||
SPACEMOUSE = 'spacemouse', | ||
} | ||
export interface TriggerConfigBase { | ||
type: TriggerConfigType | ||
} | ||
export interface TriggerConfigKeyboard extends TriggerConfigBase { | ||
type: TriggerConfigType.KEYBOARD | ||
|
||
action: AnyTriggerAction | ||
/** | ||
* a "+" and space concatenated list of KeyboardEvent.key key values (see: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values), | ||
* in order (order not significant for modifier keys), f.g. "Control+Shift+KeyA", "Control+Shift+KeyB KeyU". | ||
* "Control" means "either ControlLeft or ControlRight", same for "Shift" and "Alt" | ||
* Spaces indicate chord sequences. | ||
*/ | ||
keys: string | ||
|
||
/** | ||
* If enabled, actions will happen on keyUp, as opposed to keyDown | ||
* | ||
* @type {boolean} | ||
* @memberof IBlueprintHotkeyTrigger | ||
*/ | ||
up?: boolean | ||
|
||
global?: BindOptions['global'] | ||
} | ||
export interface TriggerConfigXkeys extends TriggerConfigBase { | ||
type: TriggerConfigType.XKEYS | ||
|
||
/** userId of the xkeys panel, or null to match any */ | ||
productId: number | null | ||
/** userId of the xkeys, or null to match any */ | ||
unitId: number | null | ||
|
||
eventType: 'down' | 'up' | 'jog' | 'joystick' | 'rotary' | 'shuttle' | 'tbar' | 'trackball' | ||
/** Index of the key, joystick, etc */ | ||
index: number | ||
|
||
/** If action.payload is not set, use value from the xkeys */ | ||
action: | ||
| AnyTriggerAction | ||
| { | ||
type: AnyTriggerAction['type'] | ||
// no payload, use value from the xkeys | ||
} | ||
} | ||
export interface TriggerConfigStreamdeck extends TriggerConfigBase { | ||
type: TriggerConfigType.STREAMDECK | ||
|
||
/** userId of the Streamdeck, or null to match any */ | ||
modelId: DeviceModelId | null | ||
/** userId of the Streamdeck, or null to match any */ | ||
serialNumber: string | null | ||
|
||
eventType: 'down' | 'up' | 'rotate' | 'encoderDown' | 'encoderUp' | ||
/** Index of the key, knob, etc */ | ||
index: number | ||
|
||
/** If action.payload is not set, use value from the xkeys */ | ||
action: | ||
| AnyTriggerAction | ||
| { | ||
type: AnyTriggerAction['type'] | ||
// no payload, use value from the streamdeck | ||
} | ||
} | ||
|
||
export interface TriggerConfigSpacemouse extends TriggerConfigBase { | ||
type: TriggerConfigType.SPACEMOUSE | ||
|
||
/** userId of the xkeys panel, or null to match any */ | ||
productId: number | null | ||
/** userId of the xkeys, or null to match any */ | ||
unitId: number | null | ||
|
||
eventType: 'down' | 'up' | 'rotate' | 'translate' | ||
/** Index of the key, if needed, 0 otherwise */ | ||
index: number | ||
|
||
/** If action.payload is not set, use value from the xkeys */ | ||
action: | ||
| AnyTriggerAction | ||
| { | ||
type: AnyTriggerAction['type'] | ||
// no payload, use value from the xkeys | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
packages/apps/client/src/lib/triggers/triggerHandlers/TriggerHandler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { EventEmitter } from 'eventemitter3' | ||
import { AnyTriggerAction } from '../../triggerActions/triggerActions.ts' | ||
import { TriggerConfig } from '../triggerConfig.ts' | ||
|
||
export interface TriggerHandlerEvents { | ||
action: [action: AnyTriggerAction] | ||
|
||
/** A message indicating that we want to ask the user if we should request access to the HIDDevice */ | ||
requestHIDDeviceAccess: [deviceName: string, callback: (access: boolean) => void] | ||
} | ||
|
||
export abstract class TriggerHandler extends EventEmitter<TriggerHandlerEvents> { | ||
protected triggers: TriggerConfig[] = [] | ||
abstract initialize(triggers?: TriggerConfig[]): Promise<void> | ||
abstract destroy(): Promise<void> | ||
} |
43 changes: 43 additions & 0 deletions
43
packages/apps/client/src/lib/triggers/triggerHandlers/TriggerHandlerKeyboard.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import Sorensen from '@sofie-automation/sorensen' | ||
import { TriggerHandler } from './TriggerHandler' | ||
import { TriggerConfig, TriggerConfigType } from '../triggerConfig' | ||
|
||
export class TriggerHandlerKeyboard extends TriggerHandler { | ||
async initialize(triggers?: TriggerConfig[]): Promise<void> { | ||
if (triggers) this.triggers = triggers | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const theWindow = window as any | ||
// hot-module-reload fix: | ||
if (!theWindow.sorensenInitialized) { | ||
theWindow.sorensenInitialized = true | ||
await Sorensen.init() | ||
} else { | ||
await Sorensen.destroy() | ||
await Sorensen.init() | ||
} | ||
|
||
for (const trigger of this.triggers) { | ||
if (trigger.type !== TriggerConfigType.KEYBOARD) continue | ||
|
||
Sorensen.bind( | ||
trigger.keys, | ||
() => { | ||
this.emit('action', trigger.action) | ||
}, | ||
{ | ||
up: trigger.up, | ||
global: trigger.global, | ||
|
||
exclusive: true, | ||
ordered: 'modifiersFirst', | ||
preventDefaultPartials: false, | ||
preventDefaultDown: true, | ||
} | ||
) | ||
} | ||
} | ||
async destroy(): Promise<void> { | ||
Sorensen.destroy().catch((e: Error) => console.error('Sorensen.destroy', e)) | ||
} | ||
} |
Oops, something went wrong.