Skip to content

Commit bcbb574

Browse files
committed
Support function key commands
1 parent 6a75842 commit bcbb574

5 files changed

Lines changed: 74 additions & 10 deletions

File tree

src/main/input/command-executor.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ describe('DesktopCommandExecutor', () => {
121121
const { adapter, executor, overlay } = createExecutor();
122122

123123
await executor.execute(command('keyboard.key', { key: 'Enter' }));
124+
await executor.execute(command('keyboard.key', { key: 'F12' }));
124125
await executor.execute(command('keyboard.shortcut', { keys: ['Ctrl', 'C'] }));
126+
await executor.execute(command('keyboard.shortcut', { keys: ['Ctrl', 'F5'] }));
125127
await executor.execute(command('keyboard.typeText', { text: 'Hello' }));
126128
await executor.execute(command('media.control', { action: 'playPause' }));
127129
await executor.execute(command('window.control', { action: 'switchNext' }));
@@ -130,28 +132,30 @@ describe('DesktopCommandExecutor', () => {
130132
expect(pingResult).toEqual({ ok: true });
131133
expect(adapter.calls).toEqual([
132134
{ method: 'pressKey', args: ['Enter'] },
135+
{ method: 'pressKey', args: ['F12'] },
133136
{ method: 'pressShortcut', args: [['Ctrl', 'C']] },
137+
{ method: 'pressShortcut', args: [['Ctrl', 'F5']] },
134138
{ method: 'typeText', args: ['Hello'] },
135139
{ method: 'mediaControl', args: ['playPause'] },
136140
{ method: 'controlWindow', args: ['switchNext'] }
137141
]);
138142
expect(overlay.events).toHaveLength(0);
139143
expect(overlay.activeCount).toBe(0);
140-
expect(overlay.hideCount).toBe(6);
144+
expect(overlay.hideCount).toBe(8);
141145
});
142146

143147
it('executes non-movement no-response commands without coalescing', async () => {
144148
const { adapter, executor, overlay } = createExecutor();
145149

146150
await executor.execute(command('mouse.click', { button: 'left' }, { responseMode: 'none' }));
147151
await executor.execute(command('mouse.scroll', { dx: 0, dy: -3 }, { responseMode: 'none' }));
148-
await executor.execute(command('keyboard.key', { key: 'Enter' }, { responseMode: 'none' }));
152+
await executor.execute(command('keyboard.key', { key: 'F12' }, { responseMode: 'none' }));
149153
await executor.execute(command('window.control', { action: 'switchNext' }, { responseMode: 'none' }));
150154

151155
expect(adapter.calls).toEqual([
152156
{ method: 'clickMouse', args: ['left'] },
153157
{ method: 'scrollMouse', args: [{ dx: 0, dy: -3 }] },
154-
{ method: 'pressKey', args: ['Enter'] },
158+
{ method: 'pressKey', args: ['F12'] },
155159
{ method: 'controlWindow', args: ['switchNext'] }
156160
]);
157161
expect(overlay.events).toEqual(['click']);

src/main/input/libnut-win32-adapter.test.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { describe, expect, it } from 'vitest';
2-
import { calculateNativeScrollDelta, calculateScaledMouseTarget, toLibnutMouseToggle } from './libnut-win32-adapter';
2+
import {
3+
calculateNativeScrollDelta,
4+
calculateScaledMouseTarget,
5+
toLibnutKeyboardKey,
6+
toLibnutMouseToggle
7+
} from './libnut-win32-adapter';
38
import {
49
createWindowControlScript,
510
toWindowsWindowControlStrategy
@@ -90,6 +95,17 @@ describe('toLibnutMouseToggle', () => {
9095
});
9196
});
9297

98+
describe('toLibnutKeyboardKey', () => {
99+
it('maps navigation keys to libnut key values', () => {
100+
expect(toLibnutKeyboardKey('PageUp')).toBe('pageup');
101+
});
102+
103+
it('maps function keys to libnut key values', () => {
104+
expect(toLibnutKeyboardKey('F1')).toBe('f1');
105+
expect(toLibnutKeyboardKey('F12')).toBe('f12');
106+
});
107+
});
108+
93109
describe('toWindowsWindowControlStrategy', () => {
94110
it('leaves app switching on the known-good libnut path', () => {
95111
expect(toWindowsWindowControlStrategy('switchNext')).toBeNull();

src/main/input/libnut-win32-adapter.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,17 @@ export class LibnutWin32InputAdapter implements DesktopInputAdapter {
5353
}
5454

5555
async pressKey(key: KeyboardKey): Promise<void> {
56-
keyTap(toLibnutKey(key));
56+
keyTap(toLibnutKeyboardKey(key));
5757
}
5858

5959
async pressShortcut(keys: ShortcutKey[]): Promise<void> {
6060
if (keys.length === 1) {
61-
keyTap(toLibnutKey(keys[0]));
61+
keyTap(toLibnutKeyboardKey(keys[0]));
6262
return;
6363
}
6464

6565
const [key, ...modifiers] = [...keys].reverse();
66-
keyTap(toLibnutKey(key), modifiers.map(toLibnutKey));
66+
keyTap(toLibnutKeyboardKey(key), modifiers.map(toLibnutKeyboardKey));
6767
}
6868

6969
async typeText(text: string): Promise<void> {
@@ -150,7 +150,7 @@ function toLibnutMouseButton(button: MouseButton): string {
150150
}
151151
}
152152

153-
function toLibnutKey(key: KeyboardKey | ShortcutKey): string {
153+
export function toLibnutKeyboardKey(key: KeyboardKey | ShortcutKey): string {
154154
switch (key) {
155155
case 'Backspace':
156156
return 'backspace';
@@ -180,6 +180,19 @@ function toLibnutKey(key: KeyboardKey | ShortcutKey): string {
180180
return 'pageup';
181181
case 'PageDown':
182182
return 'pagedown';
183+
case 'F1':
184+
case 'F2':
185+
case 'F3':
186+
case 'F4':
187+
case 'F5':
188+
case 'F6':
189+
case 'F7':
190+
case 'F8':
191+
case 'F9':
192+
case 'F10':
193+
case 'F11':
194+
case 'F12':
195+
return key.toLowerCase();
183196
case 'Ctrl':
184197
return 'control';
185198
case 'Alt':

src/shared/protocol.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ describe('protocol request validation', () => {
3333
{ type: 'mouse.dragEnd', payload: { button: 'left' } },
3434
{ type: 'keyboard.key', payload: { key: 'Enter' } },
3535
{ type: 'keyboard.shortcut', payload: { keys: ['Ctrl', 'C'] } },
36+
{ type: 'keyboard.key', payload: { key: 'F1' } },
37+
{ type: 'keyboard.key', payload: { key: 'F12' } },
38+
{ type: 'keyboard.shortcut', payload: { keys: ['Ctrl', 'F5'] } },
3639
{ type: 'keyboard.typeText', payload: { text: 'Hello' } },
3740
{ type: 'media.control', payload: { action: 'playPause' } },
3841
{ type: 'window.control', payload: { action: 'switchNext' } },
@@ -196,6 +199,10 @@ describe('protocol request validation', () => {
196199
ok: false,
197200
error: 'invalid_payload'
198201
});
202+
expect(validateProtocolRequest({ ...baseCommand, type: 'keyboard.key', payload: { key: 'F13' } })).toMatchObject({
203+
ok: false,
204+
error: 'invalid_payload'
205+
});
199206
expect(validateProtocolRequest({ ...baseCommand, type: 'keyboard.typeText', payload: { text: 'x'.repeat(2_001) } })).toMatchObject({
200207
ok: false,
201208
error: 'invalid_payload'

src/shared/protocol.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ export type KeyboardKey =
3131
| 'Home'
3232
| 'End'
3333
| 'PageUp'
34-
| 'PageDown';
34+
| 'PageDown'
35+
| 'F1'
36+
| 'F2'
37+
| 'F3'
38+
| 'F4'
39+
| 'F5'
40+
| 'F6'
41+
| 'F7'
42+
| 'F8'
43+
| 'F9'
44+
| 'F10'
45+
| 'F11'
46+
| 'F12';
3547

3648
export type ShortcutKey =
3749
| KeyboardKey
@@ -241,7 +253,19 @@ const keyboardKeys = new Set<KeyboardKey>([
241253
'Home',
242254
'End',
243255
'PageUp',
244-
'PageDown'
256+
'PageDown',
257+
'F1',
258+
'F2',
259+
'F3',
260+
'F4',
261+
'F5',
262+
'F6',
263+
'F7',
264+
'F8',
265+
'F9',
266+
'F10',
267+
'F11',
268+
'F12'
245269
]);
246270
const shortcutKeys = new Set<ShortcutKey>([
247271
...keyboardKeys,

0 commit comments

Comments
 (0)