feat(overlay): forward mpv mouse button and wheel bindings (#279)

This commit is contained in:
sudacode authored and GitHub committed 2026-10-01 00:44:51 -07:00
1 parent 9fadbc8cdd
commit 9e79fa93c2
18 files changed
+500 -76

No files matched your search

+27 -3
View File
@@ -4,16 +4,19 @@ import {
keyboardEventToMpvKey,
normalizeMpvInputKey,
parseMpvInputBindingKeys,
wheelEventToMpvWheel,
} from './mpv-input-bindings';
test('mpv discovery validates entries and excludes inactive, mouse, sequence, and SubMiner keys', () => {
test('mpv discovery validates entries and excludes inactive, sequence, and SubMiner keys', () => {
assert.deepEqual(
parseMpvInputBindingKeys([
{ key: 'r', cmd: 'script-binding replay/run', priority: 1, owner: 'replay' },
{ key: 'r', cmd: 'show-text duplicate', priority: 0 },
{ key: 'Ctrl+A', cmd: 'show-text shifted', priority: 1 },
{ key: 'g-g', cmd: 'seek 0', priority: 1 },
{ key: 'MBTN_LEFT', cmd: 'cycle pause', priority: 1 },
{ key: 'MBTN_LEFT_DBL', cmd: 'cycle fullscreen', priority: 1 },
{ key: 'MOUSE_MOVE', cmd: 'script-binding osc/move', priority: 1 },
{ key: 'Shift+WHEEL_UP', cmd: 'add volume 2', priority: 1 },
{ key: 'q', cmd: 'quit', priority: -1 },
{ key: 's', cmd: 'screenshot', priority: 1 },
{ key: 's', cmd: 'script-binding subminer/session', priority: 5, owner: 'subminer' },
@@ -22,7 +25,7 @@ test('mpv discovery validates entries and excludes inactive, mouse, sequence, an
{ key: 'z', cmd: 5, priority: 1 },
null,
]),
['r', 'ctrl+A'],
['r', 'ctrl+A', 'MBTN_LEFT_DBL', 'shift+WHEEL_UP'],
);
assert.deepEqual(parseMpvInputBindingKeys({ key: 'r' }), []);
});
@@ -33,6 +36,27 @@ test('mpv keys retain printable characters and normalize modifiers', () => {
assert.equal(normalizeMpvInputKey('Shift+LEFT'), 'shift+LEFT');
assert.equal(normalizeMpvInputKey('F12'), 'F12');
assert.equal(normalizeMpvInputKey('UNMAPPED'), null);
assert.equal(normalizeMpvInputKey('Ctrl+WHEEL_DOWN'), 'ctrl+WHEEL_DOWN');
});
test('wheel conversion picks the dominant axis and reports mpv notch scale', () => {
assert.deepEqual(wheelEventToMpvWheel({ deltaX: 0, deltaY: -240, deltaMode: 0 }), {
key: 'WHEEL_UP',
notches: 2,
});
assert.deepEqual(wheelEventToMpvWheel({ deltaX: 3, deltaY: 30, deltaMode: 0 }), {
key: 'WHEEL_DOWN',
notches: 0.25,
});
assert.deepEqual(wheelEventToMpvWheel({ deltaX: 120, deltaY: 0, deltaMode: 0 }), {
key: 'WHEEL_RIGHT',
notches: 1,
});
assert.deepEqual(wheelEventToMpvWheel({ deltaX: -3, deltaY: 0, deltaMode: 1 }), {
key: 'WHEEL_LEFT',
notches: 1,
});
assert.equal(wheelEventToMpvWheel({ deltaX: 0, deltaY: 0, deltaMode: 0 }), null);
});
test('keyboard conversion respects layout characters and skips composition and AltGr', () => {
+44 -3
View File
@@ -16,13 +16,33 @@ const SPECIAL_KEYS: Record<string, string> = {
ArrowUp: 'UP',
ArrowDown: 'DOWN',
};
const MPV_SPECIAL_KEYS = new Set(Object.values(SPECIAL_KEYS));
const MPV_WHEEL_KEYS = ['WHEEL_UP', 'WHEEL_DOWN', 'WHEEL_LEFT', 'WHEEL_RIGHT'] as const;
export type MpvWheelKey = (typeof MPV_WHEEL_KEYS)[number];
// DOM MouseEvent.button to mpv's mouse button names.
export const MPV_MOUSE_BUTTON_BY_BUTTON: Readonly<Record<number, string>> = {
0: 'MBTN_LEFT',
1: 'MBTN_MID',
2: 'MBTN_RIGHT',
3: 'MBTN_BACK',
4: 'MBTN_FORWARD',
};
// mpv synthesizes these from two quick presses of the base button.
const MPV_DOUBLE_CLICK_KEYS = ['MBTN_LEFT_DBL', 'MBTN_MID_DBL', 'MBTN_RIGHT_DBL'];
const MPV_SPECIAL_KEYS = new Set<string>([
...Object.values(SPECIAL_KEYS),
...MPV_WHEEL_KEYS,
...Object.values(MPV_MOUSE_BUTTON_BY_BUTTON),
...MPV_DOUBLE_CLICK_KEYS,
]);
// Chromium reports 120 px per wheel notch on both Wayland and X11.
const WHEEL_NOTCH_PIXELS = 120;
const WHEEL_NOTCH_LINES = 3;
// Leading command flags accepted by mpv's input/cmd.c, before the command name.
const MPV_COMMAND_PREFIXES =
/^(?:(?:no-osd|osd-bar|osd-msg|osd-msg-bar|osd-auto|expand-properties|raw|repeatable|nonrepeatable|nonscalable|async|sync)\s+)+/;
// Only single keyboard strokes are imported. Mouse input and sequences need
// their own focus and conflict rules before they can be forwarded safely.
// Single keyboard strokes, mouse buttons, and wheel scrolls are imported. Key sequences
// and pointer motion (MOUSE_MOVE) are not.
export function normalizeMpvInputKey(value: string): string | null {
const modifiers = new Set<string>();
let key = value;
@@ -59,6 +79,27 @@ export function keyboardEventToMpvKey(
return normalizeMpvInputKey([...modifiers, key].join('+'));
}
// Maps a DOM wheel event to mpv's wheel key and its notch count, which mpv uses as the
// precise-scroll scale for `keypress <key> <scale>`. Trackpads yield fractional notches.
export function wheelEventToMpvWheel(
event: Pick<WheelEvent, 'deltaX' | 'deltaY' | 'deltaMode'>,
): { key: MpvWheelKey; notches: number } | null {
const vertical = Math.abs(event.deltaY) >= Math.abs(event.deltaX);
const delta = vertical ? event.deltaY : event.deltaX;
if (!Number.isFinite(delta) || delta === 0) return null;
const key: MpvWheelKey = vertical
? delta < 0
? 'WHEEL_UP'
: 'WHEEL_DOWN'
: delta < 0
? 'WHEEL_LEFT'
: 'WHEEL_RIGHT';
// deltaMode: 0 = pixels, 1 = lines, 2 = pages.
const unit =
event.deltaMode === 1 ? WHEEL_NOTCH_LINES : event.deltaMode === 2 ? 1 : WHEEL_NOTCH_PIXELS;
return { key, notches: Math.abs(delta) / unit };
}
export function parseMpvInputBindingKeys(
value: unknown,
{ includeIgnored = true }: { includeIgnored?: boolean } = {},