@desklink/host 0.1.0 → 0.1.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/NOTICE +3 -0
- package/README.md +50 -16
- package/bin/desklink-host.mjs +13 -2
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +5 -1
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +17 -5
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +42 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/resolveEngine.d.ts +1 -0
- package/dist/resolveEngine.d.ts.map +1 -1
- package/dist/resolveEngine.js +16 -1
- package/dist/resolveEngine.js.map +1 -1
- package/docs/BOUNDARY.md +4 -3
- package/docs/PROTOCOL.md +95 -27
- package/engine/Cargo.lock +195 -1
- package/engine/Cargo.toml +25 -5
- package/engine/build.rs +98 -23
- package/engine/native/agent_overlay_mac.m +133 -0
- package/engine/native/agent_overlay_wayland.c +192 -0
- package/engine/native/agent_overlay_x11.c +103 -0
- package/engine/native/mac_stream.mm +188 -0
- package/engine/src/capture.rs +169 -19
- package/engine/src/capture_mac.rs +145 -0
- package/engine/src/clipboard_mac.rs +182 -0
- package/engine/src/convert.rs +57 -1
- package/engine/src/indicator.rs +162 -0
- package/engine/src/input.rs +12 -0
- package/engine/src/input_mac.rs +312 -0
- package/engine/src/keymap.rs +25 -0
- package/engine/src/keymap_mac.rs +209 -0
- package/engine/src/mac.rs +722 -0
- package/engine/src/main.rs +116 -2
- package/engine/src/portal.rs +4 -0
- package/engine/src/portal_mac.rs +68 -0
- package/engine/src/protocol.rs +48 -1
- package/engine/src/session.rs +556 -62
- package/engine/src/x11.rs +23 -3
- package/engine/src/x11_mac.rs +40 -0
- package/examples/reference-client.html +33 -4
- package/package.json +2 -2
package/docs/PROTOCOL.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
# desklink local control protocol (
|
|
1
|
+
# desklink local control protocol (v3)
|
|
2
2
|
|
|
3
3
|
The host engine is a per-user, on-demand process. A consumer that already owns
|
|
4
4
|
the user's session starts it and talks to it over an inherited private channel —
|
|
5
5
|
the engine never opens a public listener, never runs as root, and never installs
|
|
6
6
|
a service.
|
|
7
7
|
|
|
8
|
-
This document
|
|
9
|
-
|
|
8
|
+
This document describes the shared Linux and macOS engine protocol. The macOS
|
|
9
|
+
engine uses the same v3 stdio/WebRTC session path, with native display capture,
|
|
10
|
+
input and clipboard adapters. The protocol contains no application concepts:
|
|
11
|
+
no accounts, no chat, no machine ids, no pane ids.
|
|
10
12
|
|
|
11
13
|
## Starting the engine
|
|
12
14
|
|
|
@@ -55,13 +57,13 @@ Request parameters use the engine's own snake_case names (`session_id`,
|
|
|
55
57
|
## Handshake
|
|
56
58
|
|
|
57
59
|
```jsonc
|
|
58
|
-
{"id":1,"method":"hello","params":{"protocol":
|
|
60
|
+
{"id":1,"method":"hello","params":{"protocol":3}}
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
Result:
|
|
62
64
|
|
|
63
65
|
```jsonc
|
|
64
|
-
{"protocol":
|
|
66
|
+
{"protocol":3,"engine":"desklink-host/0.1.0","platform":"linux", /* the rest of Capabilities */ }
|
|
65
67
|
```
|
|
66
68
|
|
|
67
69
|
The consumer must send `hello` first, and a version the engine does not speak is
|
|
@@ -69,6 +71,31 @@ refused with `error.code = "unsupported-protocol"`. Until a supported `hello`
|
|
|
69
71
|
arrives, every other request is refused the same way; `capabilities` and
|
|
70
72
|
`shutdown` are the two that answer without a handshake.
|
|
71
73
|
|
|
74
|
+
## macOS
|
|
75
|
+
|
|
76
|
+
macOS speaks protocol 3 and shares the session lifecycle below. It captures the
|
|
77
|
+
selected display with ScreenCaptureKit, encodes VP9, and uses the same WebRTC
|
|
78
|
+
offer, ICE, control-channel, lease, and metrics flow as Linux. Capture currently
|
|
79
|
+
uses one bounded ScreenCaptureKit stream (three-frame queue); the backend may
|
|
80
|
+
supply fewer frames. Audio is not captured.
|
|
81
|
+
|
|
82
|
+
`capabilities` is non-prompting and reports active displays (pixel and point
|
|
83
|
+
dimensions, origin and scale), the current keyboard layout, console state, and
|
|
84
|
+
Screen Recording/Accessibility grant states. A `display` source accepts an
|
|
85
|
+
optional `display_id` from `capture.displays`; an absent ID selects the main
|
|
86
|
+
display. Screen Recording consent is requested by `session.open` when capture
|
|
87
|
+
is needed. Control may request Accessibility consent; if it remains unavailable,
|
|
88
|
+
open fails with `input-unavailable`. TCC permissions attach to the responsible
|
|
89
|
+
launching app (for example Terminal, iTerm, or the Node.js host), not to a
|
|
90
|
+
separate DesklinkHost.app bundle. Enable that app in **System Settings → Privacy
|
|
91
|
+
& Security → Screen & System Audio Recording** and **Accessibility**, then
|
|
92
|
+
reconnect. No TCC state is pre-seeded or bypassed.
|
|
93
|
+
|
|
94
|
+
macOS clipboard read/write is plain text and requires the session's `clipboard`
|
|
95
|
+
permission. `restore_token` is not supported by this native backend; a new
|
|
96
|
+
session may require fresh consent. The macOS engine requires a VP9-enabled build
|
|
97
|
+
(`DESKLINK_VPX_STATIC_DIR`) for `serve`.
|
|
98
|
+
|
|
72
99
|
## Capabilities
|
|
73
100
|
|
|
74
101
|
```jsonc
|
|
@@ -77,7 +104,7 @@ arrives, every other request is refused the same way; `capabilities` and
|
|
|
77
104
|
|
|
78
105
|
```jsonc
|
|
79
106
|
{
|
|
80
|
-
"protocol":
|
|
107
|
+
"protocol": 3,
|
|
81
108
|
"engine": "desklink-host/0.1.0",
|
|
82
109
|
"platform": "linux",
|
|
83
110
|
"session": {"kind": "wayland"},
|
|
@@ -117,7 +144,8 @@ without kernel input access a portal session may request `view`, but an open
|
|
|
117
144
|
requesting `control` is refused. X display capture uses XTest instead and does
|
|
118
145
|
not require that grant. The muxr host and WebSocket bridge report clipboard
|
|
119
146
|
unavailable for an X display, regardless of the engine's Wayland clipboard
|
|
120
|
-
probe.
|
|
147
|
+
probe. On Linux the engine never asks for privileges on its own; macOS may
|
|
148
|
+
request Screen Recording or Accessibility consent at `session.open`.
|
|
121
149
|
|
|
122
150
|
`capabilities` is safe to call before any consent has been given and must not
|
|
123
151
|
trigger a capture request.
|
|
@@ -127,6 +155,7 @@ trigger a capture request.
|
|
|
127
155
|
```jsonc
|
|
128
156
|
{"id":4,"method":"session.open","params":{
|
|
129
157
|
"source": {"kind":"portal"}, // or {"kind":"x11","display":":99"}
|
|
158
|
+
// macOS: {"kind":"display","display_id":123}
|
|
130
159
|
"permissions": ["view","control","clipboard"],
|
|
131
160
|
"max_width": 3840, "max_height": 2160, // encode box (default); never upscales
|
|
132
161
|
"bitrate_kbps": 0, // 0 (default): sized to the encoded surface
|
|
@@ -134,7 +163,8 @@ trigger a capture request.
|
|
|
134
163
|
"ice_servers": [{"urls":["stun:..."],"username":null,"credential":null}],
|
|
135
164
|
"restore_token": null, // from a previous session.restoreToken
|
|
136
165
|
"ttl_seconds": 3600, // session lease; default 3600
|
|
137
|
-
"loopback_tcp": false
|
|
166
|
+
"loopback_tcp": false, // also offer ICE over TCP on 127.0.0.1; default false
|
|
167
|
+
"agent_indicator": false // local input feedback; default false (phone control)
|
|
138
168
|
}}
|
|
139
169
|
```
|
|
140
170
|
|
|
@@ -150,20 +180,31 @@ Result:
|
|
|
150
180
|
|
|
151
181
|
`source` selects the desktop. `portal` (or absent) asks the compositor for a
|
|
152
182
|
screen cast, which is the only path that carries a Wayland user's consent;
|
|
153
|
-
`x11` reads a named X display's root window and applies input through XTest
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
a client is not in a position to know which
|
|
157
|
-
|
|
158
|
-
`permissions` are the engine's authority for this session.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
`
|
|
162
|
-
for `view` explicitly instead.
|
|
163
|
-
|
|
164
|
-
`max_fps` defaults to 30. It caps the encoded frame
|
|
165
|
-
loop; portal capture offers it as the preferred PipeWire
|
|
166
|
-
|
|
183
|
+
`x11` reads a named X display's root window and applies input through XTest;
|
|
184
|
+
`display` selects a macOS display by the ID reported in capabilities (or the
|
|
185
|
+
main display when omitted). The choice is normally the *consumer's* decision,
|
|
186
|
+
because a client is not in a position to know which backend a machine can offer.
|
|
187
|
+
|
|
188
|
+
`permissions` are the engine's authority for this session. `view` is required
|
|
189
|
+
to open a session. The engine enforces the remaining scopes on every input and
|
|
190
|
+
clipboard action and never infers them from a source, a peer or an SDP.
|
|
191
|
+
`control` without a working input backend is refused at `session.open` with
|
|
192
|
+
`error.code = "input-unavailable"` — the consumer must ask for `view` explicitly instead.
|
|
193
|
+
|
|
194
|
+
`max_fps` defaults to 30 and is bounded to 1–60. It caps the encoded frame
|
|
195
|
+
rate and X11 capture loop; portal capture offers it as the preferred PipeWire
|
|
196
|
+
frame rate, and macOS sets its ScreenCaptureKit stream minimum frame interval.
|
|
197
|
+
A source may still supply fewer frames.
|
|
198
|
+
|
|
199
|
+
`agent_indicator` is opt-in local feedback for an agent controller; desklink-axi
|
|
200
|
+
sets it for control sessions, never for view-only or human phone control. It
|
|
201
|
+
adds an eased pointer halo, click ripple, and typing pulse. On Wayland it uses
|
|
202
|
+
a click-through layer-shell surface with an edge glow; its bounded area is
|
|
203
|
+
masked before capture/diff (pixels behind the effect can remain briefly stale).
|
|
204
|
+
On bare X11 it replaces the pointer sprite instead, which XGetImage excludes:
|
|
205
|
+
there is no edge glow on that path. macOS uses a nonactivating, click-through
|
|
206
|
+
window excluded by the ScreenCaptureKit content filter. Closing the session
|
|
207
|
+
fades and removes the indicator.
|
|
167
208
|
|
|
168
209
|
`session.open` is where the capture request happens, which is where the user's
|
|
169
210
|
consent appears. It is not implicit and it is not retried silently.
|
|
@@ -209,7 +250,7 @@ always does.
|
|
|
209
250
|
"firstFrame":true}} // false until a frame has been encoded
|
|
210
251
|
```
|
|
211
252
|
|
|
212
|
-
|
|
253
|
+
A stopped capture emits `{"event":"session.capture.stopped","params":{"sessionId":"…","reason":"SCStream stopped (SCStreamErrorDomain -3821): …"}}`. Frame requests then return `stream-stopped` rather than stale pixels. On macOS the engine retries opening the stream up to three times and emits `session.state` with `capture=streaming` on recovery; Linux capture errors also surface instead of serving an old frame.\n\nThe `geometry` a client maps touch against is the one `session.open` returned,
|
|
213
254
|
and the one the control channel's `hello` repeats. A session keeps the one
|
|
214
255
|
`generation` it was opened with: a different source or scale is a new
|
|
215
256
|
`session.open`, not a generation change within a session.
|
|
@@ -289,7 +330,7 @@ are JSON, one per message:
|
|
|
289
330
|
The engine answers on the same channel:
|
|
290
331
|
|
|
291
332
|
```jsonc
|
|
292
|
-
{"kind":"hello","protocol":
|
|
333
|
+
{"kind":"hello","protocol":3,"geometry":{…}} // once, when the channel opens
|
|
293
334
|
{"kind":"ack","seq":44}
|
|
294
335
|
{"kind":"rejected","seq":44,"code":"coordinates","message":"(9000,4) is outside the 1280x720 surface"}
|
|
295
336
|
{"kind":"clipboard","request":"…","text":"…","truncated":false} // an empty "text" with "error" when the read failed
|
|
@@ -307,9 +348,36 @@ physical effect.
|
|
|
307
348
|
The engine holds its own pressed-key/button state. `close`, `release_all`,
|
|
308
349
|
control-channel loss and process exit all synthesize releases for exactly the
|
|
309
350
|
keys and buttons this session pressed, so a dropped connection cannot leave a
|
|
310
|
-
modifier stuck down on the desktop. `input.text` in `capabilities` states the
|
|
311
|
-
|
|
312
|
-
|
|
351
|
+
modifier stuck down on the desktop. On Linux, `input.text` in `capabilities` states the characters the compiled
|
|
352
|
+
layout can produce. macOS reports Unicode text support and currently maps
|
|
353
|
+
`key.character` chords through a US ANSI virtual-key map; the reported keyboard
|
|
354
|
+
layout is diagnostic, not proof that this map matches it. Anything outside the
|
|
355
|
+
map is refused with `text-unsupported`.
|
|
356
|
+
|
|
357
|
+
## Latest frame over the local protocol
|
|
358
|
+
|
|
359
|
+
`session.frame` requires `view` permission. It writes the latest pre-encode frame as
|
|
360
|
+
packed BGRX bytes to the caller's path, with optional `[x,y,w,h]` `region`.
|
|
361
|
+
The directory must already exist and be private to the caller; pixels never
|
|
362
|
+
travel through JSON. `seq` advances only when capture changes, `still_ms` is
|
|
363
|
+
elapsed time since that capture, and `damage` bounds the changed 32-pixel
|
|
364
|
+
tiles since `since` (full frame when the requested sequence is older than the
|
|
365
|
+
previous capture). A request with the current sequence reports empty damage;
|
|
366
|
+
omitting `path` requests metadata only (`written: false`). `after_seq` waits for
|
|
367
|
+
`seq` to exceed that value; `still_ms` waits for that many milliseconds
|
|
368
|
+
of stillness. When both are supplied, both conditions must hold.
|
|
369
|
+
`timeout_ms` alone waits for 150 ms of stillness. The default deadline is 5 s;
|
|
370
|
+
`timeout_ms` can extend it to at most 120 s. An unmet condition returns
|
|
371
|
+
`frame-timeout`. Damage is grouped by connected 32-pixel tiles.
|
|
372
|
+
Each changed capture emits a local `session.frame.changed` event with `sessionId`,
|
|
373
|
+
`seq` and `damage`, allowing a co-located consumer to track animation across
|
|
374
|
+
observations. Neither the event nor this file-writing method is forwarded by
|
|
375
|
+
the remote WebSocket bridge.
|
|
376
|
+
|
|
377
|
+
```json
|
|
378
|
+
{"id":20,"method":"session.frame","params":{"session_id":"…","since":7,"path":"/run/user/1000/frame.raw"}}
|
|
379
|
+
{"id":20,"result":{"seq":8,"still_ms":210,"width":1280,"height":720,"format":"bgrx","damage":[[32,32,32,32]],"written":true}}
|
|
380
|
+
```
|
|
313
381
|
|
|
314
382
|
## Clipboard over the local protocol
|
|
315
383
|
|
package/engine/Cargo.lock
CHANGED
|
@@ -257,6 +257,15 @@ dependencies = [
|
|
|
257
257
|
"generic-array",
|
|
258
258
|
]
|
|
259
259
|
|
|
260
|
+
[[package]]
|
|
261
|
+
name = "block2"
|
|
262
|
+
version = "0.6.2"
|
|
263
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
264
|
+
checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
|
|
265
|
+
dependencies = [
|
|
266
|
+
"objc2",
|
|
267
|
+
]
|
|
268
|
+
|
|
260
269
|
[[package]]
|
|
261
270
|
name = "bumpalo"
|
|
262
271
|
version = "3.20.3"
|
|
@@ -508,16 +517,22 @@ checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
|
|
508
517
|
|
|
509
518
|
[[package]]
|
|
510
519
|
name = "desklink-host"
|
|
511
|
-
version = "0.1.
|
|
520
|
+
version = "0.1.1"
|
|
512
521
|
dependencies = [
|
|
513
522
|
"anyhow",
|
|
514
523
|
"ashpd",
|
|
515
524
|
"async-trait",
|
|
525
|
+
"block2",
|
|
516
526
|
"bytes",
|
|
517
527
|
"cc",
|
|
518
528
|
"futures-util",
|
|
519
529
|
"libc",
|
|
520
530
|
"log",
|
|
531
|
+
"objc2-core-foundation",
|
|
532
|
+
"objc2-core-graphics",
|
|
533
|
+
"objc2-core-media",
|
|
534
|
+
"objc2-foundation",
|
|
535
|
+
"objc2-screen-capture-kit",
|
|
521
536
|
"pipewire",
|
|
522
537
|
"rand 0.8.8",
|
|
523
538
|
"rtc",
|
|
@@ -541,6 +556,16 @@ dependencies = [
|
|
|
541
556
|
"subtle",
|
|
542
557
|
]
|
|
543
558
|
|
|
559
|
+
[[package]]
|
|
560
|
+
name = "dispatch2"
|
|
561
|
+
version = "0.3.1"
|
|
562
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
563
|
+
checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
|
|
564
|
+
dependencies = [
|
|
565
|
+
"bitflags 2.13.2",
|
|
566
|
+
"objc2",
|
|
567
|
+
]
|
|
568
|
+
|
|
544
569
|
[[package]]
|
|
545
570
|
name = "displaydoc"
|
|
546
571
|
version = "0.2.7"
|
|
@@ -1211,6 +1236,175 @@ dependencies = [
|
|
|
1211
1236
|
"autocfg",
|
|
1212
1237
|
]
|
|
1213
1238
|
|
|
1239
|
+
[[package]]
|
|
1240
|
+
name = "objc2"
|
|
1241
|
+
version = "0.6.4"
|
|
1242
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1243
|
+
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
|
1244
|
+
dependencies = [
|
|
1245
|
+
"objc2-encode",
|
|
1246
|
+
]
|
|
1247
|
+
|
|
1248
|
+
[[package]]
|
|
1249
|
+
name = "objc2-av-foundation"
|
|
1250
|
+
version = "0.3.2"
|
|
1251
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1252
|
+
checksum = "478ae33fcac9df0a18db8302387c666b8ef08a3e2d62b510ca4fc278a384b6c0"
|
|
1253
|
+
dependencies = [
|
|
1254
|
+
"objc2",
|
|
1255
|
+
"objc2-foundation",
|
|
1256
|
+
]
|
|
1257
|
+
|
|
1258
|
+
[[package]]
|
|
1259
|
+
name = "objc2-core-audio"
|
|
1260
|
+
version = "0.3.2"
|
|
1261
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1262
|
+
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
|
1263
|
+
dependencies = [
|
|
1264
|
+
"dispatch2",
|
|
1265
|
+
"objc2",
|
|
1266
|
+
"objc2-core-audio-types",
|
|
1267
|
+
"objc2-core-foundation",
|
|
1268
|
+
]
|
|
1269
|
+
|
|
1270
|
+
[[package]]
|
|
1271
|
+
name = "objc2-core-audio-types"
|
|
1272
|
+
version = "0.3.2"
|
|
1273
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1274
|
+
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
|
1275
|
+
dependencies = [
|
|
1276
|
+
"bitflags 2.13.2",
|
|
1277
|
+
"objc2",
|
|
1278
|
+
]
|
|
1279
|
+
|
|
1280
|
+
[[package]]
|
|
1281
|
+
name = "objc2-core-foundation"
|
|
1282
|
+
version = "0.3.2"
|
|
1283
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1284
|
+
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
|
1285
|
+
dependencies = [
|
|
1286
|
+
"bitflags 2.13.2",
|
|
1287
|
+
"block2",
|
|
1288
|
+
"dispatch2",
|
|
1289
|
+
"libc",
|
|
1290
|
+
"objc2",
|
|
1291
|
+
]
|
|
1292
|
+
|
|
1293
|
+
[[package]]
|
|
1294
|
+
name = "objc2-core-graphics"
|
|
1295
|
+
version = "0.3.2"
|
|
1296
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1297
|
+
checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
|
|
1298
|
+
dependencies = [
|
|
1299
|
+
"bitflags 2.13.2",
|
|
1300
|
+
"block2",
|
|
1301
|
+
"dispatch2",
|
|
1302
|
+
"libc",
|
|
1303
|
+
"objc2",
|
|
1304
|
+
"objc2-core-foundation",
|
|
1305
|
+
"objc2-io-surface",
|
|
1306
|
+
"objc2-metal",
|
|
1307
|
+
]
|
|
1308
|
+
|
|
1309
|
+
[[package]]
|
|
1310
|
+
name = "objc2-core-media"
|
|
1311
|
+
version = "0.3.2"
|
|
1312
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1313
|
+
checksum = "05ec576860167a15dd9fce7fbee7512beb4e31f532159d3482d1f9c6caedf31d"
|
|
1314
|
+
dependencies = [
|
|
1315
|
+
"bitflags 2.13.2",
|
|
1316
|
+
"block2",
|
|
1317
|
+
"dispatch2",
|
|
1318
|
+
"objc2",
|
|
1319
|
+
"objc2-core-audio",
|
|
1320
|
+
"objc2-core-audio-types",
|
|
1321
|
+
"objc2-core-foundation",
|
|
1322
|
+
"objc2-core-video",
|
|
1323
|
+
]
|
|
1324
|
+
|
|
1325
|
+
[[package]]
|
|
1326
|
+
name = "objc2-core-video"
|
|
1327
|
+
version = "0.3.2"
|
|
1328
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1329
|
+
checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6"
|
|
1330
|
+
dependencies = [
|
|
1331
|
+
"bitflags 2.13.2",
|
|
1332
|
+
"objc2",
|
|
1333
|
+
"objc2-core-foundation",
|
|
1334
|
+
"objc2-core-graphics",
|
|
1335
|
+
"objc2-io-surface",
|
|
1336
|
+
]
|
|
1337
|
+
|
|
1338
|
+
[[package]]
|
|
1339
|
+
name = "objc2-encode"
|
|
1340
|
+
version = "4.1.0"
|
|
1341
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1342
|
+
checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
|
|
1343
|
+
|
|
1344
|
+
[[package]]
|
|
1345
|
+
name = "objc2-foundation"
|
|
1346
|
+
version = "0.3.2"
|
|
1347
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1348
|
+
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
|
1349
|
+
dependencies = [
|
|
1350
|
+
"bitflags 2.13.2",
|
|
1351
|
+
"block2",
|
|
1352
|
+
"libc",
|
|
1353
|
+
"objc2",
|
|
1354
|
+
"objc2-core-foundation",
|
|
1355
|
+
]
|
|
1356
|
+
|
|
1357
|
+
[[package]]
|
|
1358
|
+
name = "objc2-io-surface"
|
|
1359
|
+
version = "0.3.2"
|
|
1360
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1361
|
+
checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
|
|
1362
|
+
dependencies = [
|
|
1363
|
+
"bitflags 2.13.2",
|
|
1364
|
+
"objc2",
|
|
1365
|
+
"objc2-core-foundation",
|
|
1366
|
+
]
|
|
1367
|
+
|
|
1368
|
+
[[package]]
|
|
1369
|
+
name = "objc2-metal"
|
|
1370
|
+
version = "0.3.2"
|
|
1371
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1372
|
+
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
|
|
1373
|
+
dependencies = [
|
|
1374
|
+
"bitflags 2.13.2",
|
|
1375
|
+
"objc2",
|
|
1376
|
+
"objc2-foundation",
|
|
1377
|
+
]
|
|
1378
|
+
|
|
1379
|
+
[[package]]
|
|
1380
|
+
name = "objc2-screen-capture-kit"
|
|
1381
|
+
version = "0.3.2"
|
|
1382
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1383
|
+
checksum = "74b7c5390f477482f001bc354d6571a70db7e4f8d5288e860c45521fbce11394"
|
|
1384
|
+
dependencies = [
|
|
1385
|
+
"bitflags 2.13.2",
|
|
1386
|
+
"block2",
|
|
1387
|
+
"dispatch2",
|
|
1388
|
+
"libc",
|
|
1389
|
+
"objc2",
|
|
1390
|
+
"objc2-av-foundation",
|
|
1391
|
+
"objc2-core-foundation",
|
|
1392
|
+
"objc2-core-graphics",
|
|
1393
|
+
"objc2-core-media",
|
|
1394
|
+
"objc2-foundation",
|
|
1395
|
+
"objc2-uniform-type-identifiers",
|
|
1396
|
+
]
|
|
1397
|
+
|
|
1398
|
+
[[package]]
|
|
1399
|
+
name = "objc2-uniform-type-identifiers"
|
|
1400
|
+
version = "0.3.2"
|
|
1401
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1402
|
+
checksum = "7902ac02859fc1f7045f8b598c63f1ae0cc7efeaa06a9bc9f3d9a3c955974fa4"
|
|
1403
|
+
dependencies = [
|
|
1404
|
+
"objc2",
|
|
1405
|
+
"objc2-foundation",
|
|
1406
|
+
]
|
|
1407
|
+
|
|
1214
1408
|
[[package]]
|
|
1215
1409
|
name = "oid-registry"
|
|
1216
1410
|
version = "0.7.1"
|
package/engine/Cargo.toml
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "desklink-host"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.1"
|
|
4
4
|
edition = "2021"
|
|
5
|
+
rust-version = "1.97.1"
|
|
5
6
|
license = "Apache-2.0"
|
|
6
7
|
description = "Per-user on-demand desktop capture, encode and input engine speaking a versioned local control protocol"
|
|
7
8
|
publish = false
|
|
@@ -11,23 +12,42 @@ name = "desklink-host"
|
|
|
11
12
|
path = "src/main.rs"
|
|
12
13
|
|
|
13
14
|
[dependencies]
|
|
14
|
-
anyhow = "1"
|
|
15
15
|
serde = { version = "1", features = ["derive"] }
|
|
16
16
|
serde_json = "1"
|
|
17
|
+
|
|
18
|
+
[target.'cfg(target_os = "linux")'.dependencies]
|
|
19
|
+
anyhow = "1"
|
|
17
20
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "io-std", "sync", "time", "signal"] }
|
|
18
21
|
futures-util = "0.3"
|
|
19
|
-
ashpd = { version = "0.11", default-features = false, features = ["tokio"] }
|
|
20
|
-
pipewire = { version = "0.10", features = ["v0_3_44"] }
|
|
21
22
|
libc = "0.2"
|
|
22
23
|
bytes = "1"
|
|
23
24
|
webrtc = { version = "0.21", default-features = false, features = ["runtime-tokio", "crypto-ring"] }
|
|
24
25
|
rtc = { version = "0.21", default-features = false, features = ["crypto-ring"] }
|
|
26
|
+
log = "0.4"
|
|
27
|
+
async-trait = "0.1"
|
|
28
|
+
rand = "0.8"
|
|
29
|
+
ashpd = { version = "0.11", default-features = false, features = ["tokio"] }
|
|
30
|
+
pipewire = { version = "0.10", features = ["v0_3_44"] }
|
|
25
31
|
xkbcommon = "0.8"
|
|
26
32
|
x11rb = { version = "0.14", default-features = false, features = ["allow-unsafe-code", "xtest"] }
|
|
27
33
|
wl-clipboard-rs = "0.9"
|
|
28
|
-
|
|
34
|
+
|
|
35
|
+
[target.'cfg(target_os = "macos")'.dependencies]
|
|
36
|
+
anyhow = "1"
|
|
29
37
|
async-trait = "0.1"
|
|
38
|
+
bytes = "1"
|
|
39
|
+
libc = "0.2"
|
|
40
|
+
log = "0.4"
|
|
30
41
|
rand = "0.8"
|
|
42
|
+
rtc = { version = "0.21", default-features = false, features = ["crypto-ring"] }
|
|
43
|
+
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "io-std", "sync", "time", "signal"] }
|
|
44
|
+
webrtc = { version = "0.21", default-features = false, features = ["runtime-tokio", "crypto-ring"] }
|
|
45
|
+
objc2-screen-capture-kit = { version = "0.3.2", features = ["SCStream", "SCShareableContent", "SCError", "SCScreenshotManager", "block2", "dispatch2", "objc2-core-media", "objc2-core-graphics"] }
|
|
46
|
+
objc2-foundation = { version = "0.3.2", features = ["NSArray", "NSError", "NSString"] }
|
|
47
|
+
objc2-core-media = "0.3.2"
|
|
48
|
+
objc2-core-foundation = "0.3.2"
|
|
49
|
+
objc2-core-graphics = { version = "0.3.2", features = ["CGImage", "CGDataProvider"] }
|
|
50
|
+
block2 = "0.6"
|
|
31
51
|
|
|
32
52
|
[build-dependencies]
|
|
33
53
|
cc = "1"
|
package/engine/build.rs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
//! Native dependencies of the engine.
|
|
2
2
|
//!
|
|
3
|
-
//!
|
|
4
|
-
//! sources — no prebuilt binary is fetched, and nothing is
|
|
3
|
+
//! The Linux build uses two permissive C libraries, both built or linked from
|
|
4
|
+
//! this machine's own sources — no prebuilt binary is fetched, and nothing is
|
|
5
|
+
//! installed:
|
|
5
6
|
//!
|
|
6
7
|
//! * libvpx (BSD-3-Clause) — VP9 encode, through `native/vpx_shim.c` so the
|
|
7
8
|
//! versioned encoder config struct is laid out by a C compiler.
|
|
@@ -9,9 +10,9 @@
|
|
|
9
10
|
//! over `uinput`/`libevdev`, built by its own CMake project into a static
|
|
10
11
|
//! library and called through its documented C API.
|
|
11
12
|
//!
|
|
12
|
-
//!
|
|
13
|
-
//!
|
|
14
|
-
//!
|
|
13
|
+
//! macOS builds only the libvpx shim; ScreenCaptureKit, Quartz input and
|
|
14
|
+
//! AppKit clipboard use native frameworks. Linux runtime input access is still
|
|
15
|
+
//! checked through `/dev/uinput`.
|
|
15
16
|
//!
|
|
16
17
|
//! `DESKLINK_VPX_STATIC_DIR` names a libvpx install prefix (`include/`, `lib/`)
|
|
17
18
|
//! to link statically instead. The prebuilt engine uses it, because libvpx's
|
|
@@ -21,40 +22,114 @@
|
|
|
21
22
|
use std::path::{Path, PathBuf};
|
|
22
23
|
|
|
23
24
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
25
|
+
println!("cargo:rustc-check-cfg=cfg(desklink_vpx)");
|
|
26
|
+
println!("cargo:rustc-check-cfg=cfg(desklink_macos_cli)");
|
|
24
27
|
println!("cargo:rerun-if-changed=native/vpx_shim.c");
|
|
25
28
|
println!("cargo:rerun-if-changed=native/inputtino_shim.cpp");
|
|
29
|
+
println!("cargo:rerun-if-changed=native/mac_stream.mm");
|
|
30
|
+
println!("cargo:rerun-if-changed=native/agent_overlay_mac.m");
|
|
31
|
+
println!("cargo:rerun-if-changed=native/agent_overlay_x11.c");
|
|
32
|
+
println!("cargo:rerun-if-changed=native/agent_overlay_wayland.c");
|
|
33
|
+
println!("cargo:rerun-if-changed=Info.plist");
|
|
26
34
|
println!("cargo:rerun-if-changed=vendor/inputtino/src/uinput/include/inputtino/keyboard.hpp");
|
|
27
35
|
println!("cargo:rerun-if-changed=vendor/inputtino/include/inputtino/input.h");
|
|
28
36
|
println!("cargo:rerun-if-changed=vendor/inputtino/CMakeLists.txt");
|
|
29
37
|
|
|
30
38
|
println!("cargo:rerun-if-env-changed=DESKLINK_VPX_STATIC_DIR");
|
|
39
|
+
println!("cargo:rerun-if-env-changed=DESKLINK_MACOS_CLI");
|
|
31
40
|
|
|
32
|
-
let
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
|
|
42
|
+
if let Some(prefix) = std::env::var_os("DESKLINK_VPX_STATIC_DIR").map(PathBuf::from) {
|
|
43
|
+
if !prefix.join("include/vpx/vp8cx.h").exists() || !prefix.join("lib/libvpx.a").exists() {
|
|
44
|
+
return Err(format!("DESKLINK_VPX_STATIC_DIR must be the libvpx install prefix containing include/vpx/vp8cx.h and lib/libvpx.a, not its lib directory: {}", prefix.display()).into());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if target_os == "macos" && std::env::var_os("DESKLINK_MACOS_CLI").is_some() {
|
|
48
|
+
println!("cargo:rustc-cfg=desklink_macos_cli");
|
|
49
|
+
}
|
|
50
|
+
if target_os == "linux" {
|
|
51
|
+
let static_vpx = std::env::var_os("DESKLINK_VPX_STATIC_DIR").map(PathBuf::from);
|
|
52
|
+
let mut shim = cc::Build::new();
|
|
53
|
+
shim.file("native/vpx_shim.c")
|
|
54
|
+
.flag_if_supported("-Wno-unused-parameter");
|
|
55
|
+
if let Some(prefix) = &static_vpx {
|
|
56
|
+
shim.include(prefix.join("include"));
|
|
57
|
+
}
|
|
58
|
+
shim.compile("dlvpx");
|
|
59
|
+
match &static_vpx {
|
|
60
|
+
Some(prefix) => {
|
|
61
|
+
println!(
|
|
62
|
+
"cargo:rustc-link-search=native={}",
|
|
63
|
+
prefix.join("lib").display()
|
|
64
|
+
);
|
|
65
|
+
println!("cargo:rustc-link-lib=static=vpx");
|
|
66
|
+
}
|
|
67
|
+
None => println!("cargo:rustc-link-lib=vpx"),
|
|
68
|
+
}
|
|
38
69
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
70
|
+
|
|
71
|
+
if target_os == "linux" {
|
|
72
|
+
cc::Build::new()
|
|
73
|
+
.file("native/agent_overlay_x11.c")
|
|
74
|
+
.file("native/agent_overlay_wayland.c")
|
|
75
|
+
.compile("dlagentoverlay");
|
|
76
|
+
for lib in ["X11", "Xcursor", "wayland-client", "m"] {
|
|
77
|
+
println!("cargo:rustc-link-lib={lib}");
|
|
78
|
+
}
|
|
79
|
+
cc::Build::new()
|
|
80
|
+
.cpp(true)
|
|
81
|
+
.std("c++17")
|
|
82
|
+
.include("vendor/inputtino/src/uinput/include")
|
|
83
|
+
.file("native/inputtino_shim.cpp")
|
|
84
|
+
.compile("dlinputkey");
|
|
85
|
+
build_inputtino(&PathBuf::from(std::env::var("OUT_DIR")?))?;
|
|
86
|
+
} else if target_os == "macos" {
|
|
87
|
+
cc::Build::new()
|
|
88
|
+
.cpp(true)
|
|
89
|
+
.file("native/mac_stream.mm")
|
|
90
|
+
.flag("-fobjc-arc")
|
|
91
|
+
.compile("dlmacstream");
|
|
92
|
+
cc::Build::new()
|
|
93
|
+
.file("native/agent_overlay_mac.m")
|
|
94
|
+
.flag("-fobjc-arc")
|
|
95
|
+
.compile("dlagentoverlay");
|
|
96
|
+
for framework in [
|
|
97
|
+
"ScreenCaptureKit",
|
|
98
|
+
"CoreVideo",
|
|
99
|
+
"CoreMedia",
|
|
100
|
+
"Foundation",
|
|
101
|
+
"AppKit",
|
|
102
|
+
"QuartzCore",
|
|
103
|
+
] {
|
|
104
|
+
println!("cargo:rustc-link-lib=framework={framework}");
|
|
105
|
+
}
|
|
106
|
+
if let Some(prefix) = std::env::var_os("DESKLINK_VPX_STATIC_DIR").map(PathBuf::from) {
|
|
107
|
+
cc::Build::new()
|
|
108
|
+
.file("native/vpx_shim.c")
|
|
109
|
+
.include(prefix.join("include"))
|
|
110
|
+
.compile("dlvpx");
|
|
42
111
|
println!(
|
|
43
112
|
"cargo:rustc-link-search=native={}",
|
|
44
113
|
prefix.join("lib").display()
|
|
45
114
|
);
|
|
46
115
|
println!("cargo:rustc-link-lib=static=vpx");
|
|
116
|
+
println!("cargo:rustc-cfg=desklink_vpx");
|
|
117
|
+
}
|
|
118
|
+
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
|
119
|
+
println!("cargo:rustc-link-lib=framework=ApplicationServices");
|
|
120
|
+
println!("cargo:rustc-link-lib=framework=Carbon");
|
|
121
|
+
// The dev harness embeds its bundle identity for local TCC qualification.
|
|
122
|
+
// The published CLI must inherit the responsible app's TCC identity instead.
|
|
123
|
+
if std::env::var_os("DESKLINK_MACOS_CLI").is_none() {
|
|
124
|
+
println!("cargo:rustc-link-arg-bins=-sectcreate");
|
|
125
|
+
println!("cargo:rustc-link-arg-bins=__TEXT");
|
|
126
|
+
println!("cargo:rustc-link-arg-bins=__info_plist");
|
|
127
|
+
println!(
|
|
128
|
+
"cargo:rustc-link-arg-bins={}",
|
|
129
|
+
Path::new("Info.plist").canonicalize()?.display()
|
|
130
|
+
);
|
|
47
131
|
}
|
|
48
|
-
None => println!("cargo:rustc-link-lib=vpx"),
|
|
49
132
|
}
|
|
50
|
-
|
|
51
|
-
cc::Build::new()
|
|
52
|
-
.cpp(true)
|
|
53
|
-
.std("c++17")
|
|
54
|
-
.include("vendor/inputtino/src/uinput/include")
|
|
55
|
-
.file("native/inputtino_shim.cpp")
|
|
56
|
-
.compile("dlinputkey");
|
|
57
|
-
build_inputtino(&PathBuf::from(std::env::var("OUT_DIR")?))?;
|
|
58
133
|
Ok(())
|
|
59
134
|
}
|
|
60
135
|
|