Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/main/input/command-executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ describe('DesktopCommandExecutor', () => {

await executor.execute(command('keyboard.key', { key: 'Enter' }));
await executor.execute(command('keyboard.key', { key: 'F12' }));
await executor.execute(command('keyboard.key', { key: 'Meta' }));
await executor.execute(command('keyboard.shortcut', { keys: ['Ctrl', 'C'] }));
await executor.execute(command('keyboard.shortcut', { keys: ['Ctrl', 'F5'] }));
await executor.execute(command('keyboard.typeText', { text: 'Hello' }));
Expand All @@ -142,6 +143,7 @@ describe('DesktopCommandExecutor', () => {
expect(adapter.calls).toEqual([
{ method: 'pressKey', args: ['Enter'] },
{ method: 'pressKey', args: ['F12'] },
{ method: 'pressKey', args: ['Meta'] },
{ method: 'pressShortcut', args: [['Ctrl', 'C']] },
{ method: 'pressShortcut', args: [['Ctrl', 'F5']] },
{ method: 'typeText', args: ['Hello'] },
Expand All @@ -150,7 +152,7 @@ describe('DesktopCommandExecutor', () => {
]);
expect(overlay.events).toHaveLength(0);
expect(overlay.activeCount).toBe(0);
expect(overlay.hideCount).toBe(8);
expect(overlay.hideCount).toBe(9);
});

it('executes non-movement no-response commands without coalescing', async () => {
Expand Down Expand Up @@ -202,6 +204,22 @@ describe('DesktopCommandExecutor', () => {
]);
});

it('streams Meta as a key before closing successfully', async () => {
const { adapter, executor } = createExecutor();

await expect(executor.execute(command('keyboard.textStream.open', { streamId: 'stream-1' }))).resolves.toEqual({
ok: true
});
await expect(
executor.execute(command('keyboard.textStream.key', { streamId: 'stream-1', seq: 0, key: 'Meta' }, { responseMode: 'none' }))
).resolves.toEqual({ ok: true });
await expect(executor.execute(command('keyboard.textStream.close', { streamId: 'stream-1', expectedCount: 1 }))).resolves.toEqual({
ok: true
});

expect(adapter.calls).toEqual([{ method: 'pressKey', args: ['Meta'] }]);
});

it('streams ACKed text chunks before closing successfully', async () => {
const { adapter, executor } = createExecutor();

Expand Down
4 changes: 4 additions & 0 deletions src/main/input/libnut-win32-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ describe('toLibnutKeyboardKey', () => {
expect(toLibnutKeyboardKey('F1')).toBe('f1');
expect(toLibnutKeyboardKey('F12')).toBe('f12');
});

it('maps Meta to the libnut command key', () => {
expect(toLibnutKeyboardKey('Meta')).toBe('command');
});
});

describe('toWindowsWindowControlStrategy', () => {
Expand Down
20 changes: 18 additions & 2 deletions src/shared/protocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('protocol request validation', () => {
{ type: 'mouse.dragStart', payload: { button: 'left' } },
{ type: 'mouse.dragEnd', payload: { button: 'left' } },
{ type: 'keyboard.key', payload: { key: 'Enter' } },
{ type: 'keyboard.key', payload: { key: 'Meta' } },
{ type: 'keyboard.shortcut', payload: { keys: ['Ctrl', 'C'] } },
{ type: 'keyboard.key', payload: { key: 'F1' } },
{ type: 'keyboard.key', payload: { key: 'F12' } },
Expand All @@ -41,6 +42,7 @@ describe('protocol request validation', () => {
{ type: 'keyboard.textStream.char', payload: { streamId: 'android-stream-1', seq: 0, text: 'H' } },
{ type: 'keyboard.textStream.chunk', payload: { streamId: 'android-stream-1', seq: 0, text: 'Hello' } },
{ type: 'keyboard.textStream.key', payload: { streamId: 'android-stream-1', seq: 1, key: 'Enter' } },
{ type: 'keyboard.textStream.key', payload: { streamId: 'android-stream-1', seq: 2, key: 'Meta' } },
{ type: 'keyboard.textStream.close', payload: { streamId: 'android-stream-1', expectedCount: 2 } },
{ type: 'media.control', payload: { action: 'playPause' } },
{ type: 'window.control', payload: { action: 'switchNext' } },
Expand All @@ -63,11 +65,11 @@ describe('protocol request validation', () => {
'mouse.scroll': { dx: 0, dy: -3 },
'mouse.dragStart': { button: 'left' },
'mouse.dragEnd': { button: 'left' },
'keyboard.key': { key: 'Enter' },
'keyboard.key': { key: 'Meta' },
'keyboard.shortcut': { keys: ['Ctrl', 'C'] },
'keyboard.typeText': { text: 'Hello' },
'keyboard.textStream.char': { streamId: 'android-stream-1', seq: 0, text: 'H' },
'keyboard.textStream.key': { streamId: 'android-stream-1', seq: 1, key: 'Enter' },
'keyboard.textStream.key': { streamId: 'android-stream-1', seq: 1, key: 'Meta' },
'media.control': { action: 'playPause' },
'window.control': { action: 'switchNext' }
};
Expand Down Expand Up @@ -114,6 +116,20 @@ describe('protocol request validation', () => {
).toMatchObject({ ok: false, error: 'invalid_payload' });
});

it('accepts Meta as a keyboard key', () => {
expect(validateProtocolRequest({ ...baseCommand, type: 'keyboard.key', payload: { key: 'Meta' } })).toMatchObject({
ok: true
});

expect(
validateProtocolRequest({
...baseCommand,
type: 'keyboard.textStream.key',
payload: { streamId: 'stream-1', seq: 0, key: 'Meta' }
})
).toMatchObject({ ok: true });
});

it('accepts pairing approval requests without command auth fields', () => {
expect(
validateProtocolRequest({
Expand Down
2 changes: 2 additions & 0 deletions src/shared/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type KeyboardKey =
| 'Escape'
| 'Space'
| 'Tab'
| 'Meta'
| 'ArrowUp'
| 'ArrowDown'
| 'ArrowLeft'
Expand Down Expand Up @@ -279,6 +280,7 @@ const keyboardKeys = new Set<KeyboardKey>([
'Escape',
'Space',
'Tab',
'Meta',
'ArrowUp',
'ArrowDown',
'ArrowLeft',
Expand Down