@bun-native/protocol 0.0.0-stage → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/bun-native-source.json +75 -0
- package/package.json +26 -4
- package/src/app-menu.ts +217 -0
- package/src/dialogs.ts +146 -0
- package/src/drops.ts +49 -0
- package/src/elements.ts +473 -0
- package/src/files.ts +295 -0
- package/src/framing.ts +109 -0
- package/src/index.ts +136 -0
- package/src/message-dialogs.ts +131 -0
- package/src/messages.ts +1490 -0
- package/src/processes.ts +263 -0
- package/src/services.ts +208 -0
- package/src/values.ts +61 -0
package/src/messages.ts
ADDED
|
@@ -0,0 +1,1490 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseAppMenuItemId,
|
|
3
|
+
parseAppMenus,
|
|
4
|
+
parseContextMenuItems,
|
|
5
|
+
type AppMenu,
|
|
6
|
+
type AppMenuEntry,
|
|
7
|
+
} from './app-menu.ts'
|
|
8
|
+
import {
|
|
9
|
+
parseFileDialogOptions,
|
|
10
|
+
parseFileDialogResult,
|
|
11
|
+
type FileDialogOptions,
|
|
12
|
+
type FileDialogResult,
|
|
13
|
+
} from './dialogs.ts'
|
|
14
|
+
import { parseElement, type Element } from './elements.ts'
|
|
15
|
+
import {
|
|
16
|
+
parseMessageDialogOptions,
|
|
17
|
+
parseMessageDialogResult,
|
|
18
|
+
type MessageDialogOptions,
|
|
19
|
+
type MessageDialogResult,
|
|
20
|
+
} from './message-dialogs.ts'
|
|
21
|
+
import { MAX_FRAME_BYTES, ProtocolError } from './framing.ts'
|
|
22
|
+
import {
|
|
23
|
+
parseDisplaySnapshot,
|
|
24
|
+
parseClipboardText,
|
|
25
|
+
parseServicePolicy,
|
|
26
|
+
parseShortcutKey,
|
|
27
|
+
parseShortcutModifiers,
|
|
28
|
+
parseNotificationContent,
|
|
29
|
+
parseNotificationDismissal,
|
|
30
|
+
MAX_CLIPBOARD_BYTES,
|
|
31
|
+
MAX_GLOBAL_SHORTCUTS,
|
|
32
|
+
MAX_NOTIFICATIONS,
|
|
33
|
+
MAX_SERVICE_REQUESTS,
|
|
34
|
+
type ClipboardText,
|
|
35
|
+
type DisplaySnapshot,
|
|
36
|
+
type NotificationDismissal,
|
|
37
|
+
type ServicePolicy,
|
|
38
|
+
type ShortcutModifiers,
|
|
39
|
+
} from './services.ts'
|
|
40
|
+
import {
|
|
41
|
+
MAX_FILE_CHUNK_BYTES,
|
|
42
|
+
MAX_FILE_GRANTS,
|
|
43
|
+
parseFileAccess,
|
|
44
|
+
parseFileOperation,
|
|
45
|
+
parseFilePath,
|
|
46
|
+
parseFileResult,
|
|
47
|
+
parseRootId,
|
|
48
|
+
type FileAccess,
|
|
49
|
+
type FileOperation,
|
|
50
|
+
type FileResult,
|
|
51
|
+
} from './files.ts'
|
|
52
|
+
import {
|
|
53
|
+
MAX_PROCESSES,
|
|
54
|
+
parseProcessExit,
|
|
55
|
+
parseProcessLaunch,
|
|
56
|
+
parseProcessOutput,
|
|
57
|
+
parseProcessSignal,
|
|
58
|
+
parseProcessStream,
|
|
59
|
+
parseTerminalSize,
|
|
60
|
+
type ProcessExit,
|
|
61
|
+
type ProcessLaunch,
|
|
62
|
+
type ProcessSignal,
|
|
63
|
+
type ProcessStream,
|
|
64
|
+
type TerminalSize,
|
|
65
|
+
} from './processes.ts'
|
|
66
|
+
import {
|
|
67
|
+
MAX_DROP_ITEMS,
|
|
68
|
+
MAX_DROP_TARGETS,
|
|
69
|
+
parseDragState,
|
|
70
|
+
parseDropItemIndex,
|
|
71
|
+
parseDroppedItems,
|
|
72
|
+
parseDropRejection,
|
|
73
|
+
type DragState,
|
|
74
|
+
type DroppedItem,
|
|
75
|
+
type DropRejection,
|
|
76
|
+
} from './drops.ts'
|
|
77
|
+
import {
|
|
78
|
+
fields,
|
|
79
|
+
id,
|
|
80
|
+
integer,
|
|
81
|
+
keys,
|
|
82
|
+
MAX_TEXT_BYTES,
|
|
83
|
+
MAX_TITLE_BYTES,
|
|
84
|
+
number,
|
|
85
|
+
object,
|
|
86
|
+
string,
|
|
87
|
+
} from './values.ts'
|
|
88
|
+
|
|
89
|
+
export const PROTOCOL_VERSION = 12
|
|
90
|
+
/** Four operations per node of a full window: a whole-window replacement fits one batch. */
|
|
91
|
+
export const MAX_OPERATIONS = 16_384
|
|
92
|
+
const MAX_NODES_PER_WINDOW = 4_096
|
|
93
|
+
const MAX_WINDOWS = 16
|
|
94
|
+
const MAX_DEPTH = 64
|
|
95
|
+
const MAX_TEXT_BYTES_PER_WINDOW = 4_194_304
|
|
96
|
+
|
|
97
|
+
export interface Limits {
|
|
98
|
+
max_frame_bytes: number
|
|
99
|
+
max_operations: number
|
|
100
|
+
max_nodes_per_window: number
|
|
101
|
+
max_windows: number
|
|
102
|
+
max_depth: number
|
|
103
|
+
max_text_bytes: number
|
|
104
|
+
max_text_bytes_per_window: number
|
|
105
|
+
max_clipboard_bytes: number
|
|
106
|
+
max_service_requests: number
|
|
107
|
+
max_global_shortcuts: number
|
|
108
|
+
max_notifications: number
|
|
109
|
+
max_file_grants: number
|
|
110
|
+
max_file_chunk_bytes: number
|
|
111
|
+
max_processes: number
|
|
112
|
+
max_drop_targets: number
|
|
113
|
+
max_drop_items: number
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const DEFAULT_LIMITS: Readonly<Limits> = Object.freeze({
|
|
117
|
+
max_frame_bytes: MAX_FRAME_BYTES,
|
|
118
|
+
max_operations: MAX_OPERATIONS,
|
|
119
|
+
max_nodes_per_window: MAX_NODES_PER_WINDOW,
|
|
120
|
+
max_windows: MAX_WINDOWS,
|
|
121
|
+
max_depth: MAX_DEPTH,
|
|
122
|
+
max_text_bytes: MAX_TEXT_BYTES,
|
|
123
|
+
max_text_bytes_per_window: MAX_TEXT_BYTES_PER_WINDOW,
|
|
124
|
+
max_clipboard_bytes: MAX_CLIPBOARD_BYTES,
|
|
125
|
+
max_service_requests: MAX_SERVICE_REQUESTS,
|
|
126
|
+
max_global_shortcuts: MAX_GLOBAL_SHORTCUTS,
|
|
127
|
+
max_notifications: MAX_NOTIFICATIONS,
|
|
128
|
+
max_file_grants: MAX_FILE_GRANTS,
|
|
129
|
+
max_file_chunk_bytes: MAX_FILE_CHUNK_BYTES,
|
|
130
|
+
max_processes: MAX_PROCESSES,
|
|
131
|
+
max_drop_targets: MAX_DROP_TARGETS,
|
|
132
|
+
max_drop_items: MAX_DROP_ITEMS,
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
export type Operation =
|
|
136
|
+
| { type: 'create'; node_id: number; element: Element }
|
|
137
|
+
| {
|
|
138
|
+
type: 'set'
|
|
139
|
+
node_id: number
|
|
140
|
+
element: Exclude<Element, { kind: 'input' }>
|
|
141
|
+
}
|
|
142
|
+
| {
|
|
143
|
+
type: 'set'
|
|
144
|
+
node_id: number
|
|
145
|
+
element: Extract<Element, { kind: 'input' }>
|
|
146
|
+
expected_input_revision: number
|
|
147
|
+
}
|
|
148
|
+
| { type: 'insert'; parent_id: number; child_id: number; index: number }
|
|
149
|
+
| { type: 'delete'; node_id: number }
|
|
150
|
+
/** Requires `focus_v1`; applied after the batch's tree changes. */
|
|
151
|
+
| { type: 'focus'; node_id: number }
|
|
152
|
+
/** Requires `scroll_control_v1`; offsets from the content start, clamped natively. */
|
|
153
|
+
| { type: 'scroll_to'; node_id: number; x?: number; y?: number }
|
|
154
|
+
/** Requires `scroll_control_v1`. */
|
|
155
|
+
| { type: 'scroll_into_view'; node_id: number }
|
|
156
|
+
|
|
157
|
+
/** Largest programmatic scroll offset, in logical pixels. */
|
|
158
|
+
export const MAX_SCROLL_OFFSET = 1_000_000
|
|
159
|
+
|
|
160
|
+
export type Command =
|
|
161
|
+
| { id: number; type: 'hello'; protocol: typeof PROTOCOL_VERSION }
|
|
162
|
+
| {
|
|
163
|
+
id: number
|
|
164
|
+
type: 'open'
|
|
165
|
+
window_id: number
|
|
166
|
+
root_id: number
|
|
167
|
+
title: string
|
|
168
|
+
width: number
|
|
169
|
+
height: number
|
|
170
|
+
}
|
|
171
|
+
| {
|
|
172
|
+
id: number
|
|
173
|
+
type: 'batch'
|
|
174
|
+
window_id: number
|
|
175
|
+
base_revision: number
|
|
176
|
+
operations: readonly Operation[]
|
|
177
|
+
}
|
|
178
|
+
| { id: number; type: 'close'; window_id: number }
|
|
179
|
+
| { id: number; type: 'display_snapshot'; window_id: number }
|
|
180
|
+
| { id: number; type: 'clipboard_read'; window_id: number }
|
|
181
|
+
| { id: number; type: 'clipboard_write'; window_id: number; text: string }
|
|
182
|
+
| {
|
|
183
|
+
id: number
|
|
184
|
+
type: 'register_global_shortcut'
|
|
185
|
+
window_id: number
|
|
186
|
+
shortcut_id: number
|
|
187
|
+
key: string
|
|
188
|
+
modifiers: ShortcutModifiers
|
|
189
|
+
}
|
|
190
|
+
| {
|
|
191
|
+
id: number
|
|
192
|
+
type: 'unregister_global_shortcut'
|
|
193
|
+
window_id: number
|
|
194
|
+
shortcut_id: number
|
|
195
|
+
}
|
|
196
|
+
| {
|
|
197
|
+
id: number
|
|
198
|
+
type: 'post_notification'
|
|
199
|
+
window_id: number
|
|
200
|
+
notification_id: number
|
|
201
|
+
title: string
|
|
202
|
+
body: string
|
|
203
|
+
}
|
|
204
|
+
| {
|
|
205
|
+
id: number
|
|
206
|
+
type: 'withdraw_notification'
|
|
207
|
+
window_id: number
|
|
208
|
+
notification_id: number
|
|
209
|
+
}
|
|
210
|
+
| {
|
|
211
|
+
id: number
|
|
212
|
+
type: 'open_file_grant'
|
|
213
|
+
window_id: number
|
|
214
|
+
grant_id: number
|
|
215
|
+
root: string
|
|
216
|
+
access: FileAccess
|
|
217
|
+
}
|
|
218
|
+
| {
|
|
219
|
+
id: number
|
|
220
|
+
type: 'close_file_grant'
|
|
221
|
+
window_id: number
|
|
222
|
+
grant_id: number
|
|
223
|
+
}
|
|
224
|
+
| {
|
|
225
|
+
id: number
|
|
226
|
+
type: 'file_request'
|
|
227
|
+
window_id: number
|
|
228
|
+
grant_id: number
|
|
229
|
+
path: string
|
|
230
|
+
operation: FileOperation
|
|
231
|
+
}
|
|
232
|
+
| {
|
|
233
|
+
id: number
|
|
234
|
+
type: 'spawn_process'
|
|
235
|
+
window_id: number
|
|
236
|
+
process_id: number
|
|
237
|
+
launch: ProcessLaunch
|
|
238
|
+
}
|
|
239
|
+
| { id: number; type: 'write_process'; window_id: number; process_id: number; data: string }
|
|
240
|
+
| { id: number; type: 'close_process_input'; window_id: number; process_id: number }
|
|
241
|
+
| {
|
|
242
|
+
id: number
|
|
243
|
+
type: 'resize_process'
|
|
244
|
+
window_id: number
|
|
245
|
+
process_id: number
|
|
246
|
+
size: TerminalSize
|
|
247
|
+
}
|
|
248
|
+
| {
|
|
249
|
+
id: number
|
|
250
|
+
type: 'signal_process'
|
|
251
|
+
window_id: number
|
|
252
|
+
process_id: number
|
|
253
|
+
signal: ProcessSignal
|
|
254
|
+
}
|
|
255
|
+
| {
|
|
256
|
+
id: number
|
|
257
|
+
type: 'acknowledge_process_output'
|
|
258
|
+
window_id: number
|
|
259
|
+
process_id: number
|
|
260
|
+
chunk: number
|
|
261
|
+
}
|
|
262
|
+
| { id: number; type: 'release_process'; window_id: number; process_id: number }
|
|
263
|
+
| {
|
|
264
|
+
id: number
|
|
265
|
+
type: 'show_file_dialog'
|
|
266
|
+
window_id: number
|
|
267
|
+
dialog_id: number
|
|
268
|
+
options: FileDialogOptions
|
|
269
|
+
}
|
|
270
|
+
| {
|
|
271
|
+
id: number
|
|
272
|
+
type: 'close_file_dialog'
|
|
273
|
+
window_id: number
|
|
274
|
+
dialog_id: number
|
|
275
|
+
}
|
|
276
|
+
| {
|
|
277
|
+
id: number
|
|
278
|
+
type: 'show_message_dialog'
|
|
279
|
+
window_id: number
|
|
280
|
+
dialog_id: number
|
|
281
|
+
options: MessageDialogOptions
|
|
282
|
+
}
|
|
283
|
+
| {
|
|
284
|
+
id: number
|
|
285
|
+
type: 'close_message_dialog'
|
|
286
|
+
window_id: number
|
|
287
|
+
dialog_id: number
|
|
288
|
+
}
|
|
289
|
+
/** Shows an OS context menu at a logical point of the window's content. */
|
|
290
|
+
| {
|
|
291
|
+
id: number
|
|
292
|
+
type: 'show_context_menu'
|
|
293
|
+
window_id: number
|
|
294
|
+
menu_id: number
|
|
295
|
+
x: number
|
|
296
|
+
y: number
|
|
297
|
+
items: AppMenuEntry[]
|
|
298
|
+
}
|
|
299
|
+
| { id: number; type: 'close_context_menu'; window_id: number; menu_id: number }
|
|
300
|
+
| {
|
|
301
|
+
id: number
|
|
302
|
+
type: 'register_drop_target'
|
|
303
|
+
window_id: number
|
|
304
|
+
target_id: number
|
|
305
|
+
node_id: number
|
|
306
|
+
}
|
|
307
|
+
| { id: number; type: 'unregister_drop_target'; window_id: number; target_id: number }
|
|
308
|
+
| {
|
|
309
|
+
id: number
|
|
310
|
+
type: 'resolve_dropped_item'
|
|
311
|
+
window_id: number
|
|
312
|
+
target_id: number
|
|
313
|
+
drop_id: number
|
|
314
|
+
item: number
|
|
315
|
+
root: string
|
|
316
|
+
}
|
|
317
|
+
/** Replaces the application menu bar; an empty `menus` removes it. */
|
|
318
|
+
| { id: number; type: 'set_app_menu'; menu_id: number; menus: AppMenu[] }
|
|
319
|
+
| { id: number; type: 'shutdown' }
|
|
320
|
+
|
|
321
|
+
export const ERROR_CODES = [
|
|
322
|
+
'invalid_command',
|
|
323
|
+
'version_mismatch',
|
|
324
|
+
'invalid_state',
|
|
325
|
+
'invalid_handle',
|
|
326
|
+
'stale_revision',
|
|
327
|
+
'limit_exceeded',
|
|
328
|
+
'native_failure',
|
|
329
|
+
'permission_denied',
|
|
330
|
+
'service_unavailable',
|
|
331
|
+
'cancelled',
|
|
332
|
+
'operation_timed_out',
|
|
333
|
+
'payload_too_large',
|
|
334
|
+
'conflict',
|
|
335
|
+
'not_found',
|
|
336
|
+
'already_exists',
|
|
337
|
+
'path_denied',
|
|
338
|
+
'wrong_kind',
|
|
339
|
+
'not_empty',
|
|
340
|
+
'access_denied',
|
|
341
|
+
] as const
|
|
342
|
+
export type ErrorCode = (typeof ERROR_CODES)[number]
|
|
343
|
+
export const REQUIRED_CAPABILITIES = Object.freeze([
|
|
344
|
+
'tree',
|
|
345
|
+
'input_revisions',
|
|
346
|
+
'layout_v1',
|
|
347
|
+
'interactions_v1',
|
|
348
|
+
'window_display_v1',
|
|
349
|
+
] as const)
|
|
350
|
+
const CAPABILITIES = [
|
|
351
|
+
...REQUIRED_CAPABILITIES,
|
|
352
|
+
'clipboard_text_v1',
|
|
353
|
+
'global_shortcuts_v1',
|
|
354
|
+
'notifications_v1',
|
|
355
|
+
'filesystem_grants_v1',
|
|
356
|
+
'processes_v1',
|
|
357
|
+
'split_v1',
|
|
358
|
+
'style_decoration_v1',
|
|
359
|
+
'display_details_v1',
|
|
360
|
+
'display_changes_v1',
|
|
361
|
+
'file_dialogs_v1',
|
|
362
|
+
'overlays_v1',
|
|
363
|
+
'scroll_anchor_v1',
|
|
364
|
+
'focus_v1',
|
|
365
|
+
'scroll_control_v1',
|
|
366
|
+
'overlay_keyboard_v1',
|
|
367
|
+
'popup_windows_v1',
|
|
368
|
+
'drag_drop_files_v1',
|
|
369
|
+
'input_states_v1',
|
|
370
|
+
'app_menu_v1',
|
|
371
|
+
'task_dialogs_v1',
|
|
372
|
+
'context_menus_v1',
|
|
373
|
+
'split_resize_v1',
|
|
374
|
+
'accessibility_v1',
|
|
375
|
+
'accessibility_lists_v1',
|
|
376
|
+
'display_identity_v1',
|
|
377
|
+
] as const
|
|
378
|
+
export type Capability = (typeof CAPABILITIES)[number]
|
|
379
|
+
|
|
380
|
+
export interface KeyModifiers {
|
|
381
|
+
control: boolean
|
|
382
|
+
alt: boolean
|
|
383
|
+
shift: boolean
|
|
384
|
+
platform: boolean
|
|
385
|
+
function: boolean
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export type PointerButton = 'left' | 'right' | 'middle' | 'back' | 'forward'
|
|
389
|
+
/** Why the host asks the application to close a popup window overlay. */
|
|
390
|
+
export type DismissReason = 'window_blur'
|
|
391
|
+
/** What moved a split divider (`split_resize_v1`). */
|
|
392
|
+
export type ResizeSource = 'pointer' | 'keyboard'
|
|
393
|
+
|
|
394
|
+
interface NodeEventRoute {
|
|
395
|
+
sequence: number
|
|
396
|
+
window_id: number
|
|
397
|
+
node_id: number
|
|
398
|
+
tree_revision: number
|
|
399
|
+
/** Native committed ancestry, root first and target last. MAX_DEPTH counts edges. */
|
|
400
|
+
path: readonly number[]
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
type RoutedEvent = NodeEventRoute &
|
|
404
|
+
(
|
|
405
|
+
| { type: 'click' }
|
|
406
|
+
| { type: 'focus' }
|
|
407
|
+
| { type: 'blur' }
|
|
408
|
+
| { type: 'input'; value: string; input_revision: number }
|
|
409
|
+
| {
|
|
410
|
+
type: 'key_down'
|
|
411
|
+
key: string
|
|
412
|
+
key_char?: string
|
|
413
|
+
modifiers: KeyModifiers
|
|
414
|
+
}
|
|
415
|
+
| {
|
|
416
|
+
type: 'key_up'
|
|
417
|
+
key: string
|
|
418
|
+
key_char?: string
|
|
419
|
+
modifiers: KeyModifiers
|
|
420
|
+
}
|
|
421
|
+
| {
|
|
422
|
+
type: 'pointer_down'
|
|
423
|
+
button: PointerButton
|
|
424
|
+
x: number
|
|
425
|
+
y: number
|
|
426
|
+
modifiers: KeyModifiers
|
|
427
|
+
}
|
|
428
|
+
| {
|
|
429
|
+
type: 'pointer_up'
|
|
430
|
+
button: PointerButton
|
|
431
|
+
x: number
|
|
432
|
+
y: number
|
|
433
|
+
modifiers: KeyModifiers
|
|
434
|
+
}
|
|
435
|
+
| { type: 'dismiss'; reason: DismissReason }
|
|
436
|
+
| { type: 'split_resize'; ratio: number; source: ResizeSource }
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
export type NativeEvent =
|
|
440
|
+
| {
|
|
441
|
+
type: 'ready'
|
|
442
|
+
protocol: typeof PROTOCOL_VERSION
|
|
443
|
+
capabilities: Capability[]
|
|
444
|
+
limits: Limits
|
|
445
|
+
service_policy: ServicePolicy
|
|
446
|
+
}
|
|
447
|
+
| { type: 'ack'; id: number }
|
|
448
|
+
| {
|
|
449
|
+
type: 'opened'
|
|
450
|
+
id: number
|
|
451
|
+
window_id: number
|
|
452
|
+
root_id: number
|
|
453
|
+
tree_revision: number
|
|
454
|
+
}
|
|
455
|
+
| { type: 'applied'; id: number; window_id: number; tree_revision: number }
|
|
456
|
+
| {
|
|
457
|
+
type: 'display_snapshot'
|
|
458
|
+
id: number
|
|
459
|
+
window_id: number
|
|
460
|
+
snapshot: DisplaySnapshot
|
|
461
|
+
}
|
|
462
|
+
| {
|
|
463
|
+
type: 'clipboard_text'
|
|
464
|
+
id: number
|
|
465
|
+
window_id: number
|
|
466
|
+
result: ClipboardText
|
|
467
|
+
}
|
|
468
|
+
| { type: 'clipboard_written'; id: number; window_id: number }
|
|
469
|
+
| {
|
|
470
|
+
type: 'global_shortcut_registered'
|
|
471
|
+
id: number
|
|
472
|
+
window_id: number
|
|
473
|
+
shortcut_id: number
|
|
474
|
+
}
|
|
475
|
+
| {
|
|
476
|
+
type: 'global_shortcut_unregistered'
|
|
477
|
+
id: number
|
|
478
|
+
window_id: number
|
|
479
|
+
shortcut_id: number
|
|
480
|
+
}
|
|
481
|
+
| RoutedEvent
|
|
482
|
+
| { type: 'display_changed'; sequence: number; window_id: number; snapshot: DisplaySnapshot }
|
|
483
|
+
| { type: 'global_shortcut'; sequence: number; window_id: number; shortcut_id: number }
|
|
484
|
+
| {
|
|
485
|
+
type: 'notification_posted'
|
|
486
|
+
id: number
|
|
487
|
+
window_id: number
|
|
488
|
+
notification_id: number
|
|
489
|
+
activatable: boolean
|
|
490
|
+
}
|
|
491
|
+
| {
|
|
492
|
+
type: 'notification_withdrawn'
|
|
493
|
+
id: number
|
|
494
|
+
window_id: number
|
|
495
|
+
notification_id: number
|
|
496
|
+
}
|
|
497
|
+
| {
|
|
498
|
+
type: 'notification_activated'
|
|
499
|
+
sequence: number
|
|
500
|
+
window_id: number
|
|
501
|
+
notification_id: number
|
|
502
|
+
}
|
|
503
|
+
| {
|
|
504
|
+
type: 'notification_dismissed'
|
|
505
|
+
sequence: number
|
|
506
|
+
window_id: number
|
|
507
|
+
notification_id: number
|
|
508
|
+
reason: NotificationDismissal
|
|
509
|
+
}
|
|
510
|
+
| { type: 'file_grant_opened'; id: number; window_id: number; grant_id: number }
|
|
511
|
+
| { type: 'file_grant_closed'; id: number; window_id: number; grant_id: number }
|
|
512
|
+
| { type: 'file_result'; id: number; window_id: number; grant_id: number; result: FileResult }
|
|
513
|
+
| { type: 'process_spawned'; id: number; window_id: number; process_id: number }
|
|
514
|
+
| { type: 'process_updated'; id: number; window_id: number; process_id: number }
|
|
515
|
+
| { type: 'process_released'; id: number; window_id: number; process_id: number }
|
|
516
|
+
| {
|
|
517
|
+
type: 'process_output'
|
|
518
|
+
sequence: number
|
|
519
|
+
window_id: number
|
|
520
|
+
process_id: number
|
|
521
|
+
chunk: number
|
|
522
|
+
stream: ProcessStream
|
|
523
|
+
data: string
|
|
524
|
+
}
|
|
525
|
+
| {
|
|
526
|
+
type: 'process_exited'
|
|
527
|
+
sequence: number
|
|
528
|
+
window_id: number
|
|
529
|
+
process_id: number
|
|
530
|
+
exit: ProcessExit
|
|
531
|
+
}
|
|
532
|
+
| {
|
|
533
|
+
type: 'file_dialog_opened'
|
|
534
|
+
id: number
|
|
535
|
+
window_id: number
|
|
536
|
+
dialog_id: number
|
|
537
|
+
}
|
|
538
|
+
| {
|
|
539
|
+
type: 'file_dialog_closed'
|
|
540
|
+
id: number
|
|
541
|
+
window_id: number
|
|
542
|
+
dialog_id: number
|
|
543
|
+
}
|
|
544
|
+
| {
|
|
545
|
+
type: 'file_dialog_completed'
|
|
546
|
+
sequence: number
|
|
547
|
+
window_id: number
|
|
548
|
+
dialog_id: number
|
|
549
|
+
result: FileDialogResult
|
|
550
|
+
}
|
|
551
|
+
| {
|
|
552
|
+
type: 'message_dialog_opened'
|
|
553
|
+
id: number
|
|
554
|
+
window_id: number
|
|
555
|
+
dialog_id: number
|
|
556
|
+
}
|
|
557
|
+
| {
|
|
558
|
+
type: 'message_dialog_closed'
|
|
559
|
+
id: number
|
|
560
|
+
window_id: number
|
|
561
|
+
dialog_id: number
|
|
562
|
+
}
|
|
563
|
+
| {
|
|
564
|
+
type: 'message_dialog_completed'
|
|
565
|
+
sequence: number
|
|
566
|
+
window_id: number
|
|
567
|
+
dialog_id: number
|
|
568
|
+
result: MessageDialogResult
|
|
569
|
+
}
|
|
570
|
+
| { type: 'context_menu_opened'; id: number; window_id: number; menu_id: number }
|
|
571
|
+
| { type: 'context_menu_closed'; id: number; window_id: number; menu_id: number }
|
|
572
|
+
/** `item_id` is absent when the menu was dismissed. */
|
|
573
|
+
| {
|
|
574
|
+
type: 'context_menu_completed'
|
|
575
|
+
sequence: number
|
|
576
|
+
window_id: number
|
|
577
|
+
menu_id: number
|
|
578
|
+
item_id?: string
|
|
579
|
+
}
|
|
580
|
+
| { type: 'drop_target_registered'; id: number; window_id: number; target_id: number }
|
|
581
|
+
| { type: 'drop_target_unregistered'; id: number; window_id: number; target_id: number }
|
|
582
|
+
| {
|
|
583
|
+
type: 'dropped_item_resolved'
|
|
584
|
+
id: number
|
|
585
|
+
window_id: number
|
|
586
|
+
target_id: number
|
|
587
|
+
drop_id: number
|
|
588
|
+
item: number
|
|
589
|
+
root: string
|
|
590
|
+
path: string
|
|
591
|
+
}
|
|
592
|
+
| {
|
|
593
|
+
type: 'drop_target_drag'
|
|
594
|
+
sequence: number
|
|
595
|
+
window_id: number
|
|
596
|
+
target_id: number
|
|
597
|
+
state: DragState
|
|
598
|
+
}
|
|
599
|
+
| {
|
|
600
|
+
type: 'drop_received'
|
|
601
|
+
sequence: number
|
|
602
|
+
window_id: number
|
|
603
|
+
target_id: number
|
|
604
|
+
drop_id: number
|
|
605
|
+
items: DroppedItem[]
|
|
606
|
+
}
|
|
607
|
+
| {
|
|
608
|
+
type: 'drop_rejected'
|
|
609
|
+
sequence: number
|
|
610
|
+
window_id: number
|
|
611
|
+
target_id: number
|
|
612
|
+
reason: DropRejection
|
|
613
|
+
}
|
|
614
|
+
| { type: 'closed'; sequence: number; window_id: number }
|
|
615
|
+
| { type: 'app_menu_set'; id: number; menu_id: number }
|
|
616
|
+
/** Application-scoped: a menu bar belongs to the application, not a window. */
|
|
617
|
+
| { type: 'app_menu_item'; sequence: number; menu_id: number; item_id: string }
|
|
618
|
+
| { type: 'error'; id?: number; code: ErrorCode; message: string }
|
|
619
|
+
/** Sequenced events owned by one window. */
|
|
620
|
+
export type WindowEvent = Extract<NativeEvent, { sequence: number; window_id: number }>
|
|
621
|
+
/** Sequenced events owned by the application rather than one window. */
|
|
622
|
+
export type ApplicationEvent = Extract<NativeEvent, { type: 'app_menu_item' }>
|
|
623
|
+
/** Unsolicited events, as opposed to responses that carry a request id. */
|
|
624
|
+
export type HostEvent = WindowEvent | ApplicationEvent
|
|
625
|
+
|
|
626
|
+
function version(value: unknown): typeof PROTOCOL_VERSION {
|
|
627
|
+
if (value !== PROTOCOL_VERSION) throw new ProtocolError('Native protocol version mismatch')
|
|
628
|
+
return PROTOCOL_VERSION
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** Property names of a window-scoped command, reply or unsolicited event. */
|
|
632
|
+
const windowCommand = (...names: string[]) => fields('id', 'type', 'window_id', ...names)
|
|
633
|
+
const windowReply = (...names: string[]) => fields('type', 'id', 'window_id', ...names)
|
|
634
|
+
const windowNotice = (...names: string[]) => fields('type', 'sequence', 'window_id', ...names)
|
|
635
|
+
|
|
636
|
+
const OPERATION_CREATE_KEYS = fields('type', 'node_id', 'element')
|
|
637
|
+
const OPERATION_SET_KEYS = fields('type', 'node_id', 'element', 'expected_input_revision')
|
|
638
|
+
const OPERATION_INSERT_KEYS = fields('type', 'parent_id', 'child_id', 'index')
|
|
639
|
+
const OPERATION_DELETE_KEYS = fields('type', 'node_id')
|
|
640
|
+
const OPERATION_SCROLL_TO_KEYS = fields('type', 'node_id', 'x', 'y')
|
|
641
|
+
|
|
642
|
+
function operation(value: unknown): Operation {
|
|
643
|
+
const data = object(value)
|
|
644
|
+
switch (data['type']) {
|
|
645
|
+
case 'create':
|
|
646
|
+
keys(data, OPERATION_CREATE_KEYS)
|
|
647
|
+
return {
|
|
648
|
+
type: 'create',
|
|
649
|
+
node_id: id(data['node_id']),
|
|
650
|
+
element: parseElement(data['element']),
|
|
651
|
+
}
|
|
652
|
+
case 'set': {
|
|
653
|
+
keys(data, OPERATION_SET_KEYS)
|
|
654
|
+
const node_id = id(data['node_id'])
|
|
655
|
+
const element = parseElement(data['element'])
|
|
656
|
+
if (element.kind === 'input')
|
|
657
|
+
return {
|
|
658
|
+
type: 'set',
|
|
659
|
+
node_id,
|
|
660
|
+
element,
|
|
661
|
+
expected_input_revision: integer(data['expected_input_revision'], 'input revision'),
|
|
662
|
+
}
|
|
663
|
+
if ('expected_input_revision' in data)
|
|
664
|
+
throw new ProtocolError('Only input elements accept an input revision')
|
|
665
|
+
return { type: 'set', node_id, element }
|
|
666
|
+
}
|
|
667
|
+
case 'insert':
|
|
668
|
+
keys(data, OPERATION_INSERT_KEYS)
|
|
669
|
+
return {
|
|
670
|
+
type: 'insert',
|
|
671
|
+
parent_id: id(data['parent_id']),
|
|
672
|
+
child_id: id(data['child_id']),
|
|
673
|
+
index: integer(data['index'], 'index', 0, MAX_NODES_PER_WINDOW),
|
|
674
|
+
}
|
|
675
|
+
case 'delete':
|
|
676
|
+
case 'focus':
|
|
677
|
+
case 'scroll_into_view':
|
|
678
|
+
keys(data, OPERATION_DELETE_KEYS)
|
|
679
|
+
return { type: data['type'], node_id: id(data['node_id']) }
|
|
680
|
+
case 'scroll_to': {
|
|
681
|
+
keys(data, OPERATION_SCROLL_TO_KEYS)
|
|
682
|
+
if (!('x' in data) && !('y' in data)) throw new ProtocolError('scroll_to requires x or y')
|
|
683
|
+
const offset = (axis: 'x' | 'y') =>
|
|
684
|
+
axis in data ? { [axis]: number(data[axis], 'scroll ' + axis, 0, MAX_SCROLL_OFFSET) } : {}
|
|
685
|
+
return { type: 'scroll_to', node_id: id(data['node_id']), ...offset('x'), ...offset('y') }
|
|
686
|
+
}
|
|
687
|
+
default:
|
|
688
|
+
throw new ProtocolError('Unknown native operation')
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const COMMAND_HELLO_KEYS = fields('id', 'type', 'protocol')
|
|
693
|
+
const COMMAND_OPEN_KEYS = windowCommand('root_id', 'title', 'width', 'height')
|
|
694
|
+
const COMMAND_BATCH_KEYS = windowCommand('base_revision', 'operations')
|
|
695
|
+
const COMMAND_CLOSE_KEYS = windowCommand()
|
|
696
|
+
const COMMAND_CLIPBOARD_WRITE_KEYS = windowCommand('text')
|
|
697
|
+
const COMMAND_REGISTER_GLOBAL_SHORTCUT_KEYS = windowCommand('shortcut_id', 'key', 'modifiers')
|
|
698
|
+
const COMMAND_UNREGISTER_GLOBAL_SHORTCUT_KEYS = windowCommand('shortcut_id')
|
|
699
|
+
const COMMAND_POST_NOTIFICATION_KEYS = windowCommand('notification_id', 'title', 'body')
|
|
700
|
+
const COMMAND_WITHDRAW_NOTIFICATION_KEYS = windowCommand('notification_id')
|
|
701
|
+
const COMMAND_OPEN_FILE_GRANT_KEYS = windowCommand('grant_id', 'root', 'access')
|
|
702
|
+
const COMMAND_CLOSE_FILE_GRANT_KEYS = windowCommand('grant_id')
|
|
703
|
+
const COMMAND_FILE_REQUEST_KEYS = windowCommand('grant_id', 'path', 'operation')
|
|
704
|
+
const COMMAND_SPAWN_PROCESS_KEYS = windowCommand('process_id', 'launch')
|
|
705
|
+
const COMMAND_WRITE_PROCESS_KEYS = windowCommand('process_id', 'data')
|
|
706
|
+
const COMMAND_CLOSE_PROCESS_INPUT_KEYS = windowCommand('process_id')
|
|
707
|
+
const COMMAND_RESIZE_PROCESS_KEYS = windowCommand('process_id', 'size')
|
|
708
|
+
const COMMAND_SIGNAL_PROCESS_KEYS = windowCommand('process_id', 'signal')
|
|
709
|
+
const COMMAND_ACKNOWLEDGE_PROCESS_OUTPUT_KEYS = windowCommand('process_id', 'chunk')
|
|
710
|
+
const COMMAND_SHOW_FILE_DIALOG_KEYS = windowCommand('dialog_id', 'options')
|
|
711
|
+
const COMMAND_CLOSE_FILE_DIALOG_KEYS = windowCommand('dialog_id')
|
|
712
|
+
const COMMAND_SHOW_MESSAGE_DIALOG_KEYS = windowCommand('dialog_id', 'options')
|
|
713
|
+
const COMMAND_CLOSE_MESSAGE_DIALOG_KEYS = windowCommand('dialog_id')
|
|
714
|
+
const COMMAND_SHOW_CONTEXT_MENU_KEYS = windowCommand('menu_id', 'x', 'y', 'items')
|
|
715
|
+
const COMMAND_CLOSE_CONTEXT_MENU_KEYS = windowCommand('menu_id')
|
|
716
|
+
const COMMAND_REGISTER_DROP_TARGET_KEYS = windowCommand('target_id', 'node_id')
|
|
717
|
+
const COMMAND_UNREGISTER_DROP_TARGET_KEYS = windowCommand('target_id')
|
|
718
|
+
const COMMAND_RESOLVE_DROPPED_ITEM_KEYS = windowCommand('target_id', 'drop_id', 'item', 'root')
|
|
719
|
+
const COMMAND_SET_APP_MENU_KEYS = fields('id', 'type', 'menu_id', 'menus')
|
|
720
|
+
const COMMAND_SHUTDOWN_KEYS = fields('id', 'type')
|
|
721
|
+
|
|
722
|
+
/** Validate before crossing the native boundary, including JavaScript callers. */
|
|
723
|
+
export function parseCommand(value: unknown): Command {
|
|
724
|
+
const message = object(value)
|
|
725
|
+
const request = id(message['id'])
|
|
726
|
+
switch (message['type']) {
|
|
727
|
+
case 'hello':
|
|
728
|
+
keys(message, COMMAND_HELLO_KEYS)
|
|
729
|
+
return {
|
|
730
|
+
id: request,
|
|
731
|
+
type: 'hello',
|
|
732
|
+
protocol: version(message['protocol']),
|
|
733
|
+
}
|
|
734
|
+
case 'open': {
|
|
735
|
+
keys(message, COMMAND_OPEN_KEYS)
|
|
736
|
+
const title = string(message['title'], 'title', MAX_TITLE_BYTES)
|
|
737
|
+
if (!title || /\p{Cc}/u.test(title))
|
|
738
|
+
throw new ProtocolError('Window title must be nonempty and contain no control characters')
|
|
739
|
+
return {
|
|
740
|
+
id: request,
|
|
741
|
+
type: 'open',
|
|
742
|
+
window_id: id(message['window_id']),
|
|
743
|
+
root_id: id(message['root_id']),
|
|
744
|
+
title,
|
|
745
|
+
width: number(message['width'], 'width', 64, 16_384),
|
|
746
|
+
height: number(message['height'], 'height', 64, 16_384),
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
case 'batch': {
|
|
750
|
+
keys(message, COMMAND_BATCH_KEYS)
|
|
751
|
+
const operations = message['operations']
|
|
752
|
+
if (!Array.isArray(operations) || operations.length < 1 || operations.length > MAX_OPERATIONS)
|
|
753
|
+
throw new ProtocolError(
|
|
754
|
+
'Batch must contain between 1 and ' + MAX_OPERATIONS + ' operations'
|
|
755
|
+
)
|
|
756
|
+
return {
|
|
757
|
+
id: request,
|
|
758
|
+
type: 'batch',
|
|
759
|
+
window_id: id(message['window_id']),
|
|
760
|
+
base_revision: integer(message['base_revision'], 'tree revision'),
|
|
761
|
+
operations: operations.map(operation),
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
case 'close':
|
|
765
|
+
case 'display_snapshot':
|
|
766
|
+
case 'clipboard_read':
|
|
767
|
+
keys(message, COMMAND_CLOSE_KEYS)
|
|
768
|
+
return {
|
|
769
|
+
id: request,
|
|
770
|
+
type: message['type'],
|
|
771
|
+
window_id: id(message['window_id']),
|
|
772
|
+
}
|
|
773
|
+
case 'clipboard_write':
|
|
774
|
+
keys(message, COMMAND_CLIPBOARD_WRITE_KEYS)
|
|
775
|
+
return {
|
|
776
|
+
id: request,
|
|
777
|
+
type: 'clipboard_write',
|
|
778
|
+
window_id: id(message['window_id']),
|
|
779
|
+
text: string(message['text'], 'clipboard text', MAX_CLIPBOARD_BYTES),
|
|
780
|
+
}
|
|
781
|
+
case 'register_global_shortcut':
|
|
782
|
+
keys(message, COMMAND_REGISTER_GLOBAL_SHORTCUT_KEYS)
|
|
783
|
+
return {
|
|
784
|
+
id: request,
|
|
785
|
+
type: 'register_global_shortcut',
|
|
786
|
+
window_id: id(message['window_id']),
|
|
787
|
+
shortcut_id: id(message['shortcut_id']),
|
|
788
|
+
key: parseShortcutKey(message['key']),
|
|
789
|
+
modifiers: parseShortcutModifiers(message['modifiers']),
|
|
790
|
+
}
|
|
791
|
+
case 'unregister_global_shortcut':
|
|
792
|
+
keys(message, COMMAND_UNREGISTER_GLOBAL_SHORTCUT_KEYS)
|
|
793
|
+
return {
|
|
794
|
+
id: request,
|
|
795
|
+
type: 'unregister_global_shortcut',
|
|
796
|
+
window_id: id(message['window_id']),
|
|
797
|
+
shortcut_id: id(message['shortcut_id']),
|
|
798
|
+
}
|
|
799
|
+
case 'post_notification': {
|
|
800
|
+
keys(message, COMMAND_POST_NOTIFICATION_KEYS)
|
|
801
|
+
const { title, body } = parseNotificationContent(message['title'], message['body'])
|
|
802
|
+
return {
|
|
803
|
+
id: request,
|
|
804
|
+
type: 'post_notification',
|
|
805
|
+
window_id: id(message['window_id']),
|
|
806
|
+
notification_id: id(message['notification_id']),
|
|
807
|
+
title,
|
|
808
|
+
body,
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
case 'withdraw_notification':
|
|
812
|
+
keys(message, COMMAND_WITHDRAW_NOTIFICATION_KEYS)
|
|
813
|
+
return {
|
|
814
|
+
id: request,
|
|
815
|
+
type: 'withdraw_notification',
|
|
816
|
+
window_id: id(message['window_id']),
|
|
817
|
+
notification_id: id(message['notification_id']),
|
|
818
|
+
}
|
|
819
|
+
case 'open_file_grant':
|
|
820
|
+
keys(message, COMMAND_OPEN_FILE_GRANT_KEYS)
|
|
821
|
+
return {
|
|
822
|
+
id: request,
|
|
823
|
+
type: 'open_file_grant',
|
|
824
|
+
window_id: id(message['window_id']),
|
|
825
|
+
grant_id: id(message['grant_id']),
|
|
826
|
+
root: parseRootId(message['root']),
|
|
827
|
+
access: parseFileAccess(message['access']),
|
|
828
|
+
}
|
|
829
|
+
case 'close_file_grant':
|
|
830
|
+
keys(message, COMMAND_CLOSE_FILE_GRANT_KEYS)
|
|
831
|
+
return {
|
|
832
|
+
id: request,
|
|
833
|
+
type: 'close_file_grant',
|
|
834
|
+
window_id: id(message['window_id']),
|
|
835
|
+
grant_id: id(message['grant_id']),
|
|
836
|
+
}
|
|
837
|
+
case 'file_request': {
|
|
838
|
+
keys(message, COMMAND_FILE_REQUEST_KEYS)
|
|
839
|
+
const path = parseFilePath(message['path'])
|
|
840
|
+
return {
|
|
841
|
+
id: request,
|
|
842
|
+
type: 'file_request',
|
|
843
|
+
window_id: id(message['window_id']),
|
|
844
|
+
grant_id: id(message['grant_id']),
|
|
845
|
+
path,
|
|
846
|
+
operation: parseFileOperation(message['operation'], path),
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
case 'spawn_process':
|
|
850
|
+
keys(message, COMMAND_SPAWN_PROCESS_KEYS)
|
|
851
|
+
return {
|
|
852
|
+
id: request,
|
|
853
|
+
type: 'spawn_process',
|
|
854
|
+
window_id: id(message['window_id']),
|
|
855
|
+
process_id: id(message['process_id']),
|
|
856
|
+
launch: parseProcessLaunch(message['launch']),
|
|
857
|
+
}
|
|
858
|
+
case 'write_process':
|
|
859
|
+
keys(message, COMMAND_WRITE_PROCESS_KEYS)
|
|
860
|
+
return {
|
|
861
|
+
id: request,
|
|
862
|
+
type: 'write_process',
|
|
863
|
+
window_id: id(message['window_id']),
|
|
864
|
+
process_id: id(message['process_id']),
|
|
865
|
+
data: parseProcessOutput(message['data']),
|
|
866
|
+
}
|
|
867
|
+
case 'close_process_input':
|
|
868
|
+
case 'release_process':
|
|
869
|
+
keys(message, COMMAND_CLOSE_PROCESS_INPUT_KEYS)
|
|
870
|
+
return {
|
|
871
|
+
id: request,
|
|
872
|
+
type: message['type'],
|
|
873
|
+
window_id: id(message['window_id']),
|
|
874
|
+
process_id: id(message['process_id']),
|
|
875
|
+
}
|
|
876
|
+
case 'resize_process':
|
|
877
|
+
keys(message, COMMAND_RESIZE_PROCESS_KEYS)
|
|
878
|
+
return {
|
|
879
|
+
id: request,
|
|
880
|
+
type: 'resize_process',
|
|
881
|
+
window_id: id(message['window_id']),
|
|
882
|
+
process_id: id(message['process_id']),
|
|
883
|
+
size: parseTerminalSize(message['size']),
|
|
884
|
+
}
|
|
885
|
+
case 'signal_process':
|
|
886
|
+
keys(message, COMMAND_SIGNAL_PROCESS_KEYS)
|
|
887
|
+
return {
|
|
888
|
+
id: request,
|
|
889
|
+
type: 'signal_process',
|
|
890
|
+
window_id: id(message['window_id']),
|
|
891
|
+
process_id: id(message['process_id']),
|
|
892
|
+
signal: parseProcessSignal(message['signal']),
|
|
893
|
+
}
|
|
894
|
+
case 'acknowledge_process_output':
|
|
895
|
+
keys(message, COMMAND_ACKNOWLEDGE_PROCESS_OUTPUT_KEYS)
|
|
896
|
+
return {
|
|
897
|
+
id: request,
|
|
898
|
+
type: 'acknowledge_process_output',
|
|
899
|
+
window_id: id(message['window_id']),
|
|
900
|
+
process_id: id(message['process_id']),
|
|
901
|
+
chunk: id(message['chunk']),
|
|
902
|
+
}
|
|
903
|
+
case 'show_file_dialog':
|
|
904
|
+
keys(message, COMMAND_SHOW_FILE_DIALOG_KEYS)
|
|
905
|
+
return {
|
|
906
|
+
id: request,
|
|
907
|
+
type: 'show_file_dialog',
|
|
908
|
+
window_id: id(message['window_id']),
|
|
909
|
+
dialog_id: id(message['dialog_id']),
|
|
910
|
+
options: parseFileDialogOptions(message['options']),
|
|
911
|
+
}
|
|
912
|
+
case 'close_file_dialog':
|
|
913
|
+
keys(message, COMMAND_CLOSE_FILE_DIALOG_KEYS)
|
|
914
|
+
return {
|
|
915
|
+
id: request,
|
|
916
|
+
type: 'close_file_dialog',
|
|
917
|
+
window_id: id(message['window_id']),
|
|
918
|
+
dialog_id: id(message['dialog_id']),
|
|
919
|
+
}
|
|
920
|
+
case 'show_message_dialog':
|
|
921
|
+
keys(message, COMMAND_SHOW_MESSAGE_DIALOG_KEYS)
|
|
922
|
+
return {
|
|
923
|
+
id: request,
|
|
924
|
+
type: 'show_message_dialog',
|
|
925
|
+
window_id: id(message['window_id']),
|
|
926
|
+
dialog_id: id(message['dialog_id']),
|
|
927
|
+
options: parseMessageDialogOptions(message['options']),
|
|
928
|
+
}
|
|
929
|
+
case 'close_message_dialog':
|
|
930
|
+
keys(message, COMMAND_CLOSE_MESSAGE_DIALOG_KEYS)
|
|
931
|
+
return {
|
|
932
|
+
id: request,
|
|
933
|
+
type: 'close_message_dialog',
|
|
934
|
+
window_id: id(message['window_id']),
|
|
935
|
+
dialog_id: id(message['dialog_id']),
|
|
936
|
+
}
|
|
937
|
+
case 'show_context_menu':
|
|
938
|
+
keys(message, COMMAND_SHOW_CONTEXT_MENU_KEYS)
|
|
939
|
+
return {
|
|
940
|
+
id: request,
|
|
941
|
+
type: 'show_context_menu',
|
|
942
|
+
window_id: id(message['window_id']),
|
|
943
|
+
menu_id: id(message['menu_id']),
|
|
944
|
+
x: number(message['x'], 'context menu x', 0, 1_048_576),
|
|
945
|
+
y: number(message['y'], 'context menu y', 0, 1_048_576),
|
|
946
|
+
items: parseContextMenuItems(message['items']),
|
|
947
|
+
}
|
|
948
|
+
case 'close_context_menu':
|
|
949
|
+
keys(message, COMMAND_CLOSE_CONTEXT_MENU_KEYS)
|
|
950
|
+
return {
|
|
951
|
+
id: request,
|
|
952
|
+
type: 'close_context_menu',
|
|
953
|
+
window_id: id(message['window_id']),
|
|
954
|
+
menu_id: id(message['menu_id']),
|
|
955
|
+
}
|
|
956
|
+
case 'register_drop_target':
|
|
957
|
+
keys(message, COMMAND_REGISTER_DROP_TARGET_KEYS)
|
|
958
|
+
return {
|
|
959
|
+
id: request,
|
|
960
|
+
type: 'register_drop_target',
|
|
961
|
+
window_id: id(message['window_id']),
|
|
962
|
+
target_id: id(message['target_id']),
|
|
963
|
+
node_id: id(message['node_id']),
|
|
964
|
+
}
|
|
965
|
+
case 'unregister_drop_target':
|
|
966
|
+
keys(message, COMMAND_UNREGISTER_DROP_TARGET_KEYS)
|
|
967
|
+
return {
|
|
968
|
+
id: request,
|
|
969
|
+
type: 'unregister_drop_target',
|
|
970
|
+
window_id: id(message['window_id']),
|
|
971
|
+
target_id: id(message['target_id']),
|
|
972
|
+
}
|
|
973
|
+
case 'resolve_dropped_item':
|
|
974
|
+
keys(message, COMMAND_RESOLVE_DROPPED_ITEM_KEYS)
|
|
975
|
+
return {
|
|
976
|
+
id: request,
|
|
977
|
+
type: 'resolve_dropped_item',
|
|
978
|
+
window_id: id(message['window_id']),
|
|
979
|
+
target_id: id(message['target_id']),
|
|
980
|
+
drop_id: id(message['drop_id']),
|
|
981
|
+
item: parseDropItemIndex(message['item']),
|
|
982
|
+
root: parseRootId(message['root']),
|
|
983
|
+
}
|
|
984
|
+
case 'set_app_menu':
|
|
985
|
+
keys(message, COMMAND_SET_APP_MENU_KEYS)
|
|
986
|
+
return {
|
|
987
|
+
id: request,
|
|
988
|
+
type: 'set_app_menu',
|
|
989
|
+
menu_id: id(message['menu_id']),
|
|
990
|
+
menus: parseAppMenus(message['menus']),
|
|
991
|
+
}
|
|
992
|
+
case 'shutdown':
|
|
993
|
+
keys(message, COMMAND_SHUTDOWN_KEYS)
|
|
994
|
+
return { id: request, type: 'shutdown' }
|
|
995
|
+
default:
|
|
996
|
+
throw new ProtocolError('Unknown native command')
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const LIMITS_KEYS = fields(...Object.keys(DEFAULT_LIMITS))
|
|
1001
|
+
|
|
1002
|
+
function limits(value: unknown): Limits {
|
|
1003
|
+
const data = object(value)
|
|
1004
|
+
keys(data, LIMITS_KEYS)
|
|
1005
|
+
const parse = (key: keyof Limits) => integer(data[key], key, 1, DEFAULT_LIMITS[key])
|
|
1006
|
+
return {
|
|
1007
|
+
max_frame_bytes: parse('max_frame_bytes'),
|
|
1008
|
+
max_operations: parse('max_operations'),
|
|
1009
|
+
max_nodes_per_window: parse('max_nodes_per_window'),
|
|
1010
|
+
max_windows: parse('max_windows'),
|
|
1011
|
+
max_depth: parse('max_depth'),
|
|
1012
|
+
max_text_bytes: parse('max_text_bytes'),
|
|
1013
|
+
max_text_bytes_per_window: parse('max_text_bytes_per_window'),
|
|
1014
|
+
max_clipboard_bytes: parse('max_clipboard_bytes'),
|
|
1015
|
+
max_service_requests: parse('max_service_requests'),
|
|
1016
|
+
max_global_shortcuts: parse('max_global_shortcuts'),
|
|
1017
|
+
max_notifications: parse('max_notifications'),
|
|
1018
|
+
max_file_grants: parse('max_file_grants'),
|
|
1019
|
+
max_file_chunk_bytes: parse('max_file_chunk_bytes'),
|
|
1020
|
+
max_processes: parse('max_processes'),
|
|
1021
|
+
max_drop_targets: parse('max_drop_targets'),
|
|
1022
|
+
max_drop_items: parse('max_drop_items'),
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
const ROUTE_KEYS = ['type', 'sequence', 'window_id', 'node_id', 'tree_revision', 'path'] as const
|
|
1027
|
+
|
|
1028
|
+
/** Short paths, the common case, compare pairwise instead of building a set. */
|
|
1029
|
+
function unique(path: readonly number[]): boolean {
|
|
1030
|
+
if (path.length > 16) return new Set(path).size === path.length
|
|
1031
|
+
for (let index = 1; index < path.length; index++)
|
|
1032
|
+
for (let other = 0; other < index; other++) if (path[index] === path[other]) return false
|
|
1033
|
+
return true
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function route(message: Record<string, unknown>): NodeEventRoute {
|
|
1037
|
+
const node_id = id(message['node_id'])
|
|
1038
|
+
const value = message['path']
|
|
1039
|
+
if (!Array.isArray(value) || !value.length || value.length > MAX_DEPTH + 1)
|
|
1040
|
+
throw new ProtocolError('Native event path must contain between 1 and 65 node identities')
|
|
1041
|
+
const path = value.map(id)
|
|
1042
|
+
if (!unique(path) || path.at(-1) !== node_id)
|
|
1043
|
+
throw new ProtocolError('Native event path must be unique and end at its target')
|
|
1044
|
+
return {
|
|
1045
|
+
sequence: id(message['sequence']),
|
|
1046
|
+
window_id: id(message['window_id']),
|
|
1047
|
+
node_id,
|
|
1048
|
+
tree_revision: integer(message['tree_revision'], 'tree revision'),
|
|
1049
|
+
path,
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const MODIFIERS_KEYS = fields('control', 'alt', 'shift', 'platform', 'function')
|
|
1054
|
+
|
|
1055
|
+
function modifiers(value: unknown): KeyModifiers {
|
|
1056
|
+
const data = object(value)
|
|
1057
|
+
keys(data, MODIFIERS_KEYS)
|
|
1058
|
+
const modifier = (name: keyof KeyModifiers) => {
|
|
1059
|
+
if (typeof data[name] !== 'boolean') throw new ProtocolError('Invalid native key modifier')
|
|
1060
|
+
return data[name]
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
control: modifier('control'),
|
|
1064
|
+
alt: modifier('alt'),
|
|
1065
|
+
shift: modifier('shift'),
|
|
1066
|
+
platform: modifier('platform'),
|
|
1067
|
+
function: modifier('function'),
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const EVENT_READY_KEYS = fields('type', 'protocol', 'capabilities', 'limits', 'service_policy')
|
|
1072
|
+
const EVENT_ACK_KEYS = fields('type', 'id')
|
|
1073
|
+
const EVENT_OPENED_KEYS = windowReply('root_id', 'tree_revision')
|
|
1074
|
+
const EVENT_APPLIED_KEYS = windowReply('tree_revision')
|
|
1075
|
+
const EVENT_DISPLAY_SNAPSHOT_KEYS = windowReply('snapshot')
|
|
1076
|
+
const EVENT_CLIPBOARD_TEXT_KEYS = windowReply('result')
|
|
1077
|
+
const EVENT_CLIPBOARD_WRITTEN_KEYS = windowReply()
|
|
1078
|
+
const EVENT_GLOBAL_SHORTCUT_REGISTERED_KEYS = windowReply('shortcut_id')
|
|
1079
|
+
const EVENT_DISPLAY_CHANGED_KEYS = windowNotice('snapshot')
|
|
1080
|
+
const EVENT_GLOBAL_SHORTCUT_KEYS = windowNotice('shortcut_id')
|
|
1081
|
+
const EVENT_NOTIFICATION_POSTED_KEYS = windowReply('notification_id', 'activatable')
|
|
1082
|
+
const EVENT_NOTIFICATION_WITHDRAWN_KEYS = windowReply('notification_id')
|
|
1083
|
+
const EVENT_NOTIFICATION_ACTIVATED_KEYS = windowNotice('notification_id')
|
|
1084
|
+
const EVENT_NOTIFICATION_DISMISSED_KEYS = windowNotice('notification_id', 'reason')
|
|
1085
|
+
const EVENT_FILE_GRANT_OPENED_KEYS = windowReply('grant_id')
|
|
1086
|
+
const EVENT_FILE_RESULT_KEYS = windowReply('grant_id', 'result')
|
|
1087
|
+
const EVENT_PROCESS_SPAWNED_KEYS = windowReply('process_id')
|
|
1088
|
+
const EVENT_PROCESS_OUTPUT_KEYS = windowNotice('process_id', 'chunk', 'stream', 'data')
|
|
1089
|
+
const EVENT_PROCESS_EXITED_KEYS = windowNotice('process_id', 'exit')
|
|
1090
|
+
const EVENT_FILE_DIALOG_OPENED_KEYS = windowReply('dialog_id')
|
|
1091
|
+
const EVENT_FILE_DIALOG_COMPLETED_KEYS = windowNotice('dialog_id', 'result')
|
|
1092
|
+
const EVENT_MESSAGE_DIALOG_OPENED_KEYS = windowReply('dialog_id')
|
|
1093
|
+
const EVENT_MESSAGE_DIALOG_COMPLETED_KEYS = windowNotice('dialog_id', 'result')
|
|
1094
|
+
const EVENT_CONTEXT_MENU_OPENED_KEYS = windowReply('menu_id')
|
|
1095
|
+
const EVENT_CONTEXT_MENU_COMPLETED_KEYS = windowNotice('menu_id', 'item_id')
|
|
1096
|
+
const EVENT_DROP_TARGET_REGISTERED_KEYS = windowReply('target_id')
|
|
1097
|
+
const EVENT_DROPPED_ITEM_RESOLVED_KEYS = windowReply('target_id', 'drop_id', 'item', 'root', 'path')
|
|
1098
|
+
const EVENT_DROP_TARGET_DRAG_KEYS = windowNotice('target_id', 'state')
|
|
1099
|
+
const EVENT_DROP_RECEIVED_KEYS = windowNotice('target_id', 'drop_id', 'items')
|
|
1100
|
+
const EVENT_DROP_REJECTED_KEYS = windowNotice('target_id', 'reason')
|
|
1101
|
+
const EVENT_ROUTE_KEYS = fields(...ROUTE_KEYS)
|
|
1102
|
+
const EVENT_KEY_KEYS = fields(...ROUTE_KEYS, 'key', 'key_char', 'modifiers')
|
|
1103
|
+
const EVENT_POINTER_KEYS = fields(...ROUTE_KEYS, 'button', 'x', 'y', 'modifiers')
|
|
1104
|
+
const EVENT_DISMISS_KEYS = fields(...ROUTE_KEYS, 'reason')
|
|
1105
|
+
const EVENT_SPLIT_RESIZE_KEYS = fields(...ROUTE_KEYS, 'ratio', 'source')
|
|
1106
|
+
const EVENT_CLOSED_KEYS = windowNotice()
|
|
1107
|
+
const EVENT_INPUT_KEYS = fields(...ROUTE_KEYS, 'value', 'input_revision')
|
|
1108
|
+
const EVENT_APP_MENU_SET_KEYS = fields('type', 'id', 'menu_id')
|
|
1109
|
+
const EVENT_APP_MENU_ITEM_KEYS = fields('type', 'sequence', 'menu_id', 'item_id')
|
|
1110
|
+
const EVENT_ERROR_KEYS = fields('type', 'id', 'code', 'message')
|
|
1111
|
+
|
|
1112
|
+
/** Native stdout is untrusted until its message shape and limits are checked. */
|
|
1113
|
+
export function parseEvent(value: unknown): NativeEvent {
|
|
1114
|
+
const message = object(value)
|
|
1115
|
+
switch (message['type']) {
|
|
1116
|
+
case 'ready': {
|
|
1117
|
+
keys(message, EVENT_READY_KEYS)
|
|
1118
|
+
const capabilities = message['capabilities']
|
|
1119
|
+
if (!Array.isArray(capabilities) || capabilities.length > CAPABILITIES.length)
|
|
1120
|
+
throw new ProtocolError('Invalid native capabilities')
|
|
1121
|
+
const parsed: Capability[] = []
|
|
1122
|
+
for (const value of capabilities) {
|
|
1123
|
+
const capability = CAPABILITIES.find((candidate) => candidate === value)
|
|
1124
|
+
if (!capability || parsed.includes(capability))
|
|
1125
|
+
throw new ProtocolError('Unknown or duplicate native capability')
|
|
1126
|
+
parsed.push(capability)
|
|
1127
|
+
}
|
|
1128
|
+
return {
|
|
1129
|
+
type: 'ready',
|
|
1130
|
+
protocol: version(message['protocol']),
|
|
1131
|
+
capabilities: parsed,
|
|
1132
|
+
limits: limits(message['limits']),
|
|
1133
|
+
service_policy: parseServicePolicy(message['service_policy']),
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
case 'ack':
|
|
1137
|
+
keys(message, EVENT_ACK_KEYS)
|
|
1138
|
+
return { type: 'ack', id: id(message['id']) }
|
|
1139
|
+
case 'opened':
|
|
1140
|
+
keys(message, EVENT_OPENED_KEYS)
|
|
1141
|
+
return {
|
|
1142
|
+
type: 'opened',
|
|
1143
|
+
id: id(message['id']),
|
|
1144
|
+
window_id: id(message['window_id']),
|
|
1145
|
+
root_id: id(message['root_id']),
|
|
1146
|
+
tree_revision: integer(message['tree_revision'], 'tree revision'),
|
|
1147
|
+
}
|
|
1148
|
+
case 'applied':
|
|
1149
|
+
keys(message, EVENT_APPLIED_KEYS)
|
|
1150
|
+
return {
|
|
1151
|
+
type: 'applied',
|
|
1152
|
+
id: id(message['id']),
|
|
1153
|
+
window_id: id(message['window_id']),
|
|
1154
|
+
tree_revision: integer(message['tree_revision'], 'tree revision'),
|
|
1155
|
+
}
|
|
1156
|
+
case 'display_snapshot':
|
|
1157
|
+
keys(message, EVENT_DISPLAY_SNAPSHOT_KEYS)
|
|
1158
|
+
return {
|
|
1159
|
+
type: 'display_snapshot',
|
|
1160
|
+
id: id(message['id']),
|
|
1161
|
+
window_id: id(message['window_id']),
|
|
1162
|
+
snapshot: parseDisplaySnapshot(message['snapshot']),
|
|
1163
|
+
}
|
|
1164
|
+
case 'clipboard_text':
|
|
1165
|
+
keys(message, EVENT_CLIPBOARD_TEXT_KEYS)
|
|
1166
|
+
return {
|
|
1167
|
+
type: 'clipboard_text',
|
|
1168
|
+
id: id(message['id']),
|
|
1169
|
+
window_id: id(message['window_id']),
|
|
1170
|
+
result: parseClipboardText(message['result']),
|
|
1171
|
+
}
|
|
1172
|
+
case 'clipboard_written':
|
|
1173
|
+
keys(message, EVENT_CLIPBOARD_WRITTEN_KEYS)
|
|
1174
|
+
return {
|
|
1175
|
+
type: 'clipboard_written',
|
|
1176
|
+
id: id(message['id']),
|
|
1177
|
+
window_id: id(message['window_id']),
|
|
1178
|
+
}
|
|
1179
|
+
case 'global_shortcut_registered':
|
|
1180
|
+
case 'global_shortcut_unregistered':
|
|
1181
|
+
keys(message, EVENT_GLOBAL_SHORTCUT_REGISTERED_KEYS)
|
|
1182
|
+
return {
|
|
1183
|
+
type: message['type'],
|
|
1184
|
+
id: id(message['id']),
|
|
1185
|
+
window_id: id(message['window_id']),
|
|
1186
|
+
shortcut_id: id(message['shortcut_id']),
|
|
1187
|
+
}
|
|
1188
|
+
case 'display_changed':
|
|
1189
|
+
keys(message, EVENT_DISPLAY_CHANGED_KEYS)
|
|
1190
|
+
return {
|
|
1191
|
+
type: 'display_changed',
|
|
1192
|
+
sequence: id(message['sequence']),
|
|
1193
|
+
window_id: id(message['window_id']),
|
|
1194
|
+
snapshot: parseDisplaySnapshot(message['snapshot']),
|
|
1195
|
+
}
|
|
1196
|
+
case 'global_shortcut':
|
|
1197
|
+
keys(message, EVENT_GLOBAL_SHORTCUT_KEYS)
|
|
1198
|
+
return {
|
|
1199
|
+
type: 'global_shortcut',
|
|
1200
|
+
sequence: id(message['sequence']),
|
|
1201
|
+
window_id: id(message['window_id']),
|
|
1202
|
+
shortcut_id: id(message['shortcut_id']),
|
|
1203
|
+
}
|
|
1204
|
+
case 'notification_posted':
|
|
1205
|
+
keys(message, EVENT_NOTIFICATION_POSTED_KEYS)
|
|
1206
|
+
if (typeof message['activatable'] !== 'boolean')
|
|
1207
|
+
throw new ProtocolError('Notification activatable must be a boolean')
|
|
1208
|
+
return {
|
|
1209
|
+
type: 'notification_posted',
|
|
1210
|
+
id: id(message['id']),
|
|
1211
|
+
window_id: id(message['window_id']),
|
|
1212
|
+
notification_id: id(message['notification_id']),
|
|
1213
|
+
activatable: message['activatable'],
|
|
1214
|
+
}
|
|
1215
|
+
case 'notification_withdrawn':
|
|
1216
|
+
keys(message, EVENT_NOTIFICATION_WITHDRAWN_KEYS)
|
|
1217
|
+
return {
|
|
1218
|
+
type: 'notification_withdrawn',
|
|
1219
|
+
id: id(message['id']),
|
|
1220
|
+
window_id: id(message['window_id']),
|
|
1221
|
+
notification_id: id(message['notification_id']),
|
|
1222
|
+
}
|
|
1223
|
+
case 'notification_activated':
|
|
1224
|
+
keys(message, EVENT_NOTIFICATION_ACTIVATED_KEYS)
|
|
1225
|
+
return {
|
|
1226
|
+
type: 'notification_activated',
|
|
1227
|
+
sequence: id(message['sequence']),
|
|
1228
|
+
window_id: id(message['window_id']),
|
|
1229
|
+
notification_id: id(message['notification_id']),
|
|
1230
|
+
}
|
|
1231
|
+
case 'notification_dismissed':
|
|
1232
|
+
keys(message, EVENT_NOTIFICATION_DISMISSED_KEYS)
|
|
1233
|
+
return {
|
|
1234
|
+
type: 'notification_dismissed',
|
|
1235
|
+
sequence: id(message['sequence']),
|
|
1236
|
+
window_id: id(message['window_id']),
|
|
1237
|
+
notification_id: id(message['notification_id']),
|
|
1238
|
+
reason: parseNotificationDismissal(message['reason']),
|
|
1239
|
+
}
|
|
1240
|
+
case 'file_grant_opened':
|
|
1241
|
+
case 'file_grant_closed':
|
|
1242
|
+
keys(message, EVENT_FILE_GRANT_OPENED_KEYS)
|
|
1243
|
+
return {
|
|
1244
|
+
type: message['type'],
|
|
1245
|
+
id: id(message['id']),
|
|
1246
|
+
window_id: id(message['window_id']),
|
|
1247
|
+
grant_id: id(message['grant_id']),
|
|
1248
|
+
}
|
|
1249
|
+
case 'file_result':
|
|
1250
|
+
keys(message, EVENT_FILE_RESULT_KEYS)
|
|
1251
|
+
return {
|
|
1252
|
+
type: 'file_result',
|
|
1253
|
+
id: id(message['id']),
|
|
1254
|
+
window_id: id(message['window_id']),
|
|
1255
|
+
grant_id: id(message['grant_id']),
|
|
1256
|
+
result: parseFileResult(message['result']),
|
|
1257
|
+
}
|
|
1258
|
+
case 'process_spawned':
|
|
1259
|
+
case 'process_updated':
|
|
1260
|
+
case 'process_released':
|
|
1261
|
+
keys(message, EVENT_PROCESS_SPAWNED_KEYS)
|
|
1262
|
+
return {
|
|
1263
|
+
type: message['type'],
|
|
1264
|
+
id: id(message['id']),
|
|
1265
|
+
window_id: id(message['window_id']),
|
|
1266
|
+
process_id: id(message['process_id']),
|
|
1267
|
+
}
|
|
1268
|
+
case 'process_output':
|
|
1269
|
+
keys(message, EVENT_PROCESS_OUTPUT_KEYS)
|
|
1270
|
+
return {
|
|
1271
|
+
type: 'process_output',
|
|
1272
|
+
sequence: id(message['sequence']),
|
|
1273
|
+
window_id: id(message['window_id']),
|
|
1274
|
+
process_id: id(message['process_id']),
|
|
1275
|
+
chunk: id(message['chunk']),
|
|
1276
|
+
stream: parseProcessStream(message['stream']),
|
|
1277
|
+
data: parseProcessOutput(message['data']),
|
|
1278
|
+
}
|
|
1279
|
+
case 'process_exited':
|
|
1280
|
+
keys(message, EVENT_PROCESS_EXITED_KEYS)
|
|
1281
|
+
return {
|
|
1282
|
+
type: 'process_exited',
|
|
1283
|
+
sequence: id(message['sequence']),
|
|
1284
|
+
window_id: id(message['window_id']),
|
|
1285
|
+
process_id: id(message['process_id']),
|
|
1286
|
+
exit: parseProcessExit(message['exit']),
|
|
1287
|
+
}
|
|
1288
|
+
case 'file_dialog_opened':
|
|
1289
|
+
case 'file_dialog_closed':
|
|
1290
|
+
keys(message, EVENT_FILE_DIALOG_OPENED_KEYS)
|
|
1291
|
+
return {
|
|
1292
|
+
type: message['type'],
|
|
1293
|
+
id: id(message['id']),
|
|
1294
|
+
window_id: id(message['window_id']),
|
|
1295
|
+
dialog_id: id(message['dialog_id']),
|
|
1296
|
+
}
|
|
1297
|
+
case 'file_dialog_completed':
|
|
1298
|
+
keys(message, EVENT_FILE_DIALOG_COMPLETED_KEYS)
|
|
1299
|
+
return {
|
|
1300
|
+
type: 'file_dialog_completed',
|
|
1301
|
+
sequence: id(message['sequence']),
|
|
1302
|
+
window_id: id(message['window_id']),
|
|
1303
|
+
dialog_id: id(message['dialog_id']),
|
|
1304
|
+
result: parseFileDialogResult(message['result']),
|
|
1305
|
+
}
|
|
1306
|
+
case 'message_dialog_opened':
|
|
1307
|
+
case 'message_dialog_closed':
|
|
1308
|
+
keys(message, EVENT_MESSAGE_DIALOG_OPENED_KEYS)
|
|
1309
|
+
return {
|
|
1310
|
+
type: message['type'],
|
|
1311
|
+
id: id(message['id']),
|
|
1312
|
+
window_id: id(message['window_id']),
|
|
1313
|
+
dialog_id: id(message['dialog_id']),
|
|
1314
|
+
}
|
|
1315
|
+
case 'message_dialog_completed':
|
|
1316
|
+
keys(message, EVENT_MESSAGE_DIALOG_COMPLETED_KEYS)
|
|
1317
|
+
return {
|
|
1318
|
+
type: 'message_dialog_completed',
|
|
1319
|
+
sequence: id(message['sequence']),
|
|
1320
|
+
window_id: id(message['window_id']),
|
|
1321
|
+
dialog_id: id(message['dialog_id']),
|
|
1322
|
+
result: parseMessageDialogResult(message['result']),
|
|
1323
|
+
}
|
|
1324
|
+
case 'context_menu_opened':
|
|
1325
|
+
case 'context_menu_closed':
|
|
1326
|
+
keys(message, EVENT_CONTEXT_MENU_OPENED_KEYS)
|
|
1327
|
+
return {
|
|
1328
|
+
type: message['type'],
|
|
1329
|
+
id: id(message['id']),
|
|
1330
|
+
window_id: id(message['window_id']),
|
|
1331
|
+
menu_id: id(message['menu_id']),
|
|
1332
|
+
}
|
|
1333
|
+
case 'context_menu_completed': {
|
|
1334
|
+
keys(message, EVENT_CONTEXT_MENU_COMPLETED_KEYS)
|
|
1335
|
+
const event: Extract<NativeEvent, { type: 'context_menu_completed' }> = {
|
|
1336
|
+
type: 'context_menu_completed',
|
|
1337
|
+
sequence: id(message['sequence']),
|
|
1338
|
+
window_id: id(message['window_id']),
|
|
1339
|
+
menu_id: id(message['menu_id']),
|
|
1340
|
+
}
|
|
1341
|
+
if ('item_id' in message) event.item_id = parseAppMenuItemId(message['item_id'])
|
|
1342
|
+
return event
|
|
1343
|
+
}
|
|
1344
|
+
case 'drop_target_registered':
|
|
1345
|
+
case 'drop_target_unregistered':
|
|
1346
|
+
keys(message, EVENT_DROP_TARGET_REGISTERED_KEYS)
|
|
1347
|
+
return {
|
|
1348
|
+
type: message['type'],
|
|
1349
|
+
id: id(message['id']),
|
|
1350
|
+
window_id: id(message['window_id']),
|
|
1351
|
+
target_id: id(message['target_id']),
|
|
1352
|
+
}
|
|
1353
|
+
case 'dropped_item_resolved':
|
|
1354
|
+
keys(message, EVENT_DROPPED_ITEM_RESOLVED_KEYS)
|
|
1355
|
+
return {
|
|
1356
|
+
type: 'dropped_item_resolved',
|
|
1357
|
+
id: id(message['id']),
|
|
1358
|
+
window_id: id(message['window_id']),
|
|
1359
|
+
target_id: id(message['target_id']),
|
|
1360
|
+
drop_id: id(message['drop_id']),
|
|
1361
|
+
item: parseDropItemIndex(message['item']),
|
|
1362
|
+
root: parseRootId(message['root']),
|
|
1363
|
+
path: parseFilePath(message['path']),
|
|
1364
|
+
}
|
|
1365
|
+
case 'drop_target_drag':
|
|
1366
|
+
keys(message, EVENT_DROP_TARGET_DRAG_KEYS)
|
|
1367
|
+
return {
|
|
1368
|
+
type: 'drop_target_drag',
|
|
1369
|
+
sequence: id(message['sequence']),
|
|
1370
|
+
window_id: id(message['window_id']),
|
|
1371
|
+
target_id: id(message['target_id']),
|
|
1372
|
+
state: parseDragState(message['state']),
|
|
1373
|
+
}
|
|
1374
|
+
case 'drop_received':
|
|
1375
|
+
keys(message, EVENT_DROP_RECEIVED_KEYS)
|
|
1376
|
+
return {
|
|
1377
|
+
type: 'drop_received',
|
|
1378
|
+
sequence: id(message['sequence']),
|
|
1379
|
+
window_id: id(message['window_id']),
|
|
1380
|
+
target_id: id(message['target_id']),
|
|
1381
|
+
drop_id: id(message['drop_id']),
|
|
1382
|
+
items: parseDroppedItems(message['items']),
|
|
1383
|
+
}
|
|
1384
|
+
case 'drop_rejected':
|
|
1385
|
+
keys(message, EVENT_DROP_REJECTED_KEYS)
|
|
1386
|
+
return {
|
|
1387
|
+
type: 'drop_rejected',
|
|
1388
|
+
sequence: id(message['sequence']),
|
|
1389
|
+
window_id: id(message['window_id']),
|
|
1390
|
+
target_id: id(message['target_id']),
|
|
1391
|
+
reason: parseDropRejection(message['reason']),
|
|
1392
|
+
}
|
|
1393
|
+
case 'click':
|
|
1394
|
+
case 'focus':
|
|
1395
|
+
case 'blur':
|
|
1396
|
+
keys(message, EVENT_ROUTE_KEYS)
|
|
1397
|
+
return {
|
|
1398
|
+
type: message['type'],
|
|
1399
|
+
...route(message),
|
|
1400
|
+
}
|
|
1401
|
+
case 'key_down':
|
|
1402
|
+
case 'key_up': {
|
|
1403
|
+
keys(message, EVENT_KEY_KEYS)
|
|
1404
|
+
const key = string(message['key'], 'key', 256)
|
|
1405
|
+
if (!key) throw new ProtocolError('Native key must be nonempty')
|
|
1406
|
+
return {
|
|
1407
|
+
type: message['type'],
|
|
1408
|
+
...route(message),
|
|
1409
|
+
key,
|
|
1410
|
+
...('key_char' in message
|
|
1411
|
+
? { key_char: string(message['key_char'], 'key_char', 256) }
|
|
1412
|
+
: {}),
|
|
1413
|
+
modifiers: modifiers(message['modifiers']),
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
case 'pointer_down':
|
|
1417
|
+
case 'pointer_up': {
|
|
1418
|
+
keys(message, EVENT_POINTER_KEYS)
|
|
1419
|
+
const button = (['left', 'right', 'middle', 'back', 'forward'] as const).find(
|
|
1420
|
+
(button) => button === message['button']
|
|
1421
|
+
)
|
|
1422
|
+
if (!button) throw new ProtocolError('Unknown native pointer button')
|
|
1423
|
+
return {
|
|
1424
|
+
type: message['type'],
|
|
1425
|
+
...route(message),
|
|
1426
|
+
button,
|
|
1427
|
+
x: number(message['x'], 'pointer x', -1_048_576, 1_048_576),
|
|
1428
|
+
y: number(message['y'], 'pointer y', -1_048_576, 1_048_576),
|
|
1429
|
+
modifiers: modifiers(message['modifiers']),
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
case 'dismiss':
|
|
1433
|
+
keys(message, EVENT_DISMISS_KEYS)
|
|
1434
|
+
if (message['reason'] !== 'window_blur')
|
|
1435
|
+
throw new ProtocolError('Unknown native dismissal reason')
|
|
1436
|
+
return { type: 'dismiss', ...route(message), reason: 'window_blur' }
|
|
1437
|
+
case 'split_resize': {
|
|
1438
|
+
keys(message, EVENT_SPLIT_RESIZE_KEYS)
|
|
1439
|
+
const source = (['pointer', 'keyboard'] as const).find(
|
|
1440
|
+
(source) => source === message['source']
|
|
1441
|
+
)
|
|
1442
|
+
if (!source) throw new ProtocolError('Unknown split resize source')
|
|
1443
|
+
return {
|
|
1444
|
+
type: 'split_resize',
|
|
1445
|
+
...route(message),
|
|
1446
|
+
ratio: number(message['ratio'], 'split ratio', 0, 1),
|
|
1447
|
+
source,
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
case 'closed':
|
|
1451
|
+
keys(message, EVENT_CLOSED_KEYS)
|
|
1452
|
+
return {
|
|
1453
|
+
type: 'closed',
|
|
1454
|
+
sequence: id(message['sequence']),
|
|
1455
|
+
window_id: id(message['window_id']),
|
|
1456
|
+
}
|
|
1457
|
+
case 'input':
|
|
1458
|
+
keys(message, EVENT_INPUT_KEYS)
|
|
1459
|
+
return {
|
|
1460
|
+
type: 'input',
|
|
1461
|
+
...route(message),
|
|
1462
|
+
value: string(message['value'], 'value'),
|
|
1463
|
+
input_revision: integer(message['input_revision'], 'input revision'),
|
|
1464
|
+
}
|
|
1465
|
+
case 'app_menu_set':
|
|
1466
|
+
keys(message, EVENT_APP_MENU_SET_KEYS)
|
|
1467
|
+
return { type: 'app_menu_set', id: id(message['id']), menu_id: id(message['menu_id']) }
|
|
1468
|
+
case 'app_menu_item':
|
|
1469
|
+
keys(message, EVENT_APP_MENU_ITEM_KEYS)
|
|
1470
|
+
return {
|
|
1471
|
+
type: 'app_menu_item',
|
|
1472
|
+
sequence: id(message['sequence']),
|
|
1473
|
+
menu_id: id(message['menu_id']),
|
|
1474
|
+
item_id: parseAppMenuItemId(message['item_id']),
|
|
1475
|
+
}
|
|
1476
|
+
case 'error': {
|
|
1477
|
+
keys(message, EVENT_ERROR_KEYS)
|
|
1478
|
+
const code = ERROR_CODES.find((code) => code === message['code'])
|
|
1479
|
+
if (!code) throw new ProtocolError('Invalid native error code')
|
|
1480
|
+
return {
|
|
1481
|
+
type: 'error',
|
|
1482
|
+
code,
|
|
1483
|
+
message: string(message['message'], 'message'),
|
|
1484
|
+
...('id' in message ? { id: id(message['id']) } : {}),
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
default:
|
|
1488
|
+
throw new ProtocolError('Unknown native event')
|
|
1489
|
+
}
|
|
1490
|
+
}
|