@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/engine/src/main.rs
CHANGED
|
@@ -6,25 +6,75 @@
|
|
|
6
6
|
//! process, owns its stdin/stdout, and carries the SDP/ICE the engine produces
|
|
7
7
|
//! over whatever authenticated channel it already has.
|
|
8
8
|
|
|
9
|
+
#[cfg(target_os = "linux")]
|
|
9
10
|
mod capture;
|
|
11
|
+
#[cfg(target_os = "macos")]
|
|
12
|
+
#[path = "capture_mac.rs"]
|
|
13
|
+
mod capture;
|
|
14
|
+
#[cfg(target_os = "linux")]
|
|
15
|
+
mod clipboard;
|
|
16
|
+
#[cfg(target_os = "macos")]
|
|
17
|
+
#[path = "clipboard_mac.rs"]
|
|
10
18
|
mod clipboard;
|
|
19
|
+
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
11
20
|
mod convert;
|
|
21
|
+
#[cfg(target_os = "linux")]
|
|
12
22
|
mod encoder;
|
|
23
|
+
#[cfg(all(target_os = "macos", desklink_vpx))]
|
|
24
|
+
mod encoder;
|
|
25
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
26
|
+
mod indicator;
|
|
27
|
+
#[cfg(target_os = "linux")]
|
|
28
|
+
mod input;
|
|
29
|
+
#[cfg(target_os = "macos")]
|
|
30
|
+
#[path = "input_mac.rs"]
|
|
13
31
|
mod input;
|
|
32
|
+
#[cfg(target_os = "linux")]
|
|
14
33
|
mod keymap;
|
|
34
|
+
#[cfg(target_os = "macos")]
|
|
35
|
+
#[path = "keymap_mac.rs"]
|
|
36
|
+
mod keymap;
|
|
37
|
+
#[cfg(target_os = "macos")]
|
|
38
|
+
mod mac;
|
|
39
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
15
40
|
mod peer;
|
|
41
|
+
#[cfg(target_os = "linux")]
|
|
42
|
+
mod portal;
|
|
43
|
+
#[cfg(target_os = "macos")]
|
|
44
|
+
#[path = "portal_mac.rs"]
|
|
16
45
|
mod portal;
|
|
46
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
17
47
|
mod protocol;
|
|
48
|
+
#[cfg(target_os = "linux")]
|
|
49
|
+
mod session;
|
|
50
|
+
#[cfg(all(target_os = "macos", desklink_vpx))]
|
|
18
51
|
mod session;
|
|
52
|
+
#[cfg(target_os = "linux")]
|
|
53
|
+
mod x11;
|
|
54
|
+
#[cfg(target_os = "macos")]
|
|
55
|
+
#[path = "x11_mac.rs"]
|
|
19
56
|
mod x11;
|
|
20
57
|
|
|
58
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
21
59
|
use anyhow::Result;
|
|
60
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
22
61
|
use protocol::{ErrorBody, Event, Request, Response};
|
|
62
|
+
#[cfg(target_os = "linux")]
|
|
23
63
|
use std::sync::mpsc as std_mpsc;
|
|
64
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
24
65
|
use std::time::Duration;
|
|
66
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
25
67
|
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
|
|
68
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
26
69
|
use tokio::sync::mpsc as tokio_mpsc;
|
|
27
70
|
|
|
71
|
+
#[cfg(target_os = "linux")]
|
|
72
|
+
unsafe extern "C" {
|
|
73
|
+
fn desklink_agent_overlay_main(display: *const std::ffi::c_char) -> i32;
|
|
74
|
+
fn desklink_agent_overlay_wayland(source_w: i32, source_h: i32) -> i32;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[cfg(target_os = "linux")]
|
|
28
78
|
fn main() {
|
|
29
79
|
let args: Vec<String> = std::env::args().skip(1).collect();
|
|
30
80
|
let command = args.first().map(String::as_str).unwrap_or("help");
|
|
@@ -43,6 +93,20 @@ fn main() {
|
|
|
43
93
|
println!("{}", env!("CARGO_PKG_VERSION"));
|
|
44
94
|
0
|
|
45
95
|
}
|
|
96
|
+
"agent-overlay" => {
|
|
97
|
+
let display = args.get(1).map(String::as_str).unwrap_or("");
|
|
98
|
+
let display = std::ffi::CString::new(display).expect("valid X display");
|
|
99
|
+
unsafe { desklink_agent_overlay_main(display.as_ptr()) }
|
|
100
|
+
}
|
|
101
|
+
"agent-overlay-wayland" => {
|
|
102
|
+
let width = args.get(1).and_then(|v| v.parse().ok()).unwrap_or(0);
|
|
103
|
+
let height = args.get(2).and_then(|v| v.parse().ok()).unwrap_or(0);
|
|
104
|
+
if width <= 0 || height <= 0 {
|
|
105
|
+
2
|
|
106
|
+
} else {
|
|
107
|
+
unsafe { desklink_agent_overlay_wayland(width, height) }
|
|
108
|
+
}
|
|
109
|
+
}
|
|
46
110
|
"capabilities" => {
|
|
47
111
|
println!(
|
|
48
112
|
"{}",
|
|
@@ -72,6 +136,7 @@ fn main() {
|
|
|
72
136
|
std::process::exit(code);
|
|
73
137
|
}
|
|
74
138
|
|
|
139
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
75
140
|
fn report(result: Result<()>) -> i32 {
|
|
76
141
|
match result {
|
|
77
142
|
Ok(()) => 0,
|
|
@@ -82,6 +147,7 @@ fn report(result: Result<()>) -> i32 {
|
|
|
82
147
|
}
|
|
83
148
|
}
|
|
84
149
|
|
|
150
|
+
#[cfg(target_os = "linux")]
|
|
85
151
|
fn print_help() {
|
|
86
152
|
println!(
|
|
87
153
|
"desklink-host {}
|
|
@@ -105,6 +171,7 @@ anything. See docs/PROTOCOL.md for the protocol.",
|
|
|
105
171
|
|
|
106
172
|
/// Deliberately does not perform the change. Kernel input access is a privileged
|
|
107
173
|
/// decision, so the engine explains it and stops; the user runs the command.
|
|
174
|
+
#[cfg(target_os = "linux")]
|
|
108
175
|
fn print_input_setup() {
|
|
109
176
|
match input::probe() {
|
|
110
177
|
Ok(()) => {
|
|
@@ -154,6 +221,7 @@ fn print_input_setup() {
|
|
|
154
221
|
|
|
155
222
|
/// Start the portal session, capture a few frames, and print what actually
|
|
156
223
|
/// arrived. This is the diagnostic to run when the picture does not appear.
|
|
224
|
+
#[cfg(target_os = "linux")]
|
|
157
225
|
async fn probe(seconds: Option<u64>, display: Option<&str>) -> Result<()> {
|
|
158
226
|
let seconds = seconds.unwrap_or(3);
|
|
159
227
|
match display {
|
|
@@ -164,6 +232,7 @@ async fn probe(seconds: Option<u64>, display: Option<&str>) -> Result<()> {
|
|
|
164
232
|
|
|
165
233
|
/// Read a named X display's root window for a few seconds. The frame shape is
|
|
166
234
|
/// reported as a coarse checksum so a flat or unreadable screen is visible.
|
|
235
|
+
#[cfg(target_os = "linux")]
|
|
167
236
|
fn probe_x11(display: &str, seconds: u64) -> Result<()> {
|
|
168
237
|
let mut desktop = crate::x11::X11Desktop::connect(Some(display))?;
|
|
169
238
|
let (width, height) = desktop.screen_size();
|
|
@@ -195,6 +264,7 @@ fn probe_x11(display: &str, seconds: u64) -> Result<()> {
|
|
|
195
264
|
|
|
196
265
|
/// A cheap fingerprint of a frame, enough to tell "the screen changed" from
|
|
197
266
|
/// "the screen is one flat colour".
|
|
267
|
+
#[cfg(target_os = "linux")]
|
|
198
268
|
fn frame_shape(frame: &crate::convert::I420) -> u64 {
|
|
199
269
|
frame
|
|
200
270
|
.y_plane()
|
|
@@ -207,6 +277,7 @@ fn frame_shape(frame: &crate::convert::I420) -> u64 {
|
|
|
207
277
|
})
|
|
208
278
|
}
|
|
209
279
|
|
|
280
|
+
#[cfg(target_os = "linux")]
|
|
210
281
|
async fn probe_portal(seconds: u64) -> Result<()> {
|
|
211
282
|
let portal = portal::open(None).await?;
|
|
212
283
|
eprintln!("portal source: {:?}", portal.source);
|
|
@@ -217,9 +288,11 @@ async fn probe_portal(seconds: u64) -> Result<()> {
|
|
|
217
288
|
0,
|
|
218
289
|
0,
|
|
219
290
|
30,
|
|
220
|
-
Box::new(move |frame, seq| {
|
|
291
|
+
Box::new(move |frame, seq, _raw| {
|
|
221
292
|
let _ = tx.send((frame.width, frame.height, seq));
|
|
222
293
|
}),
|
|
294
|
+
None,
|
|
295
|
+
Box::new(|reason| eprintln!("{reason}")),
|
|
223
296
|
)?;
|
|
224
297
|
|
|
225
298
|
let deadline = std::time::Instant::now() + Duration::from_secs(seconds);
|
|
@@ -253,6 +326,7 @@ async fn probe_portal(seconds: u64) -> Result<()> {
|
|
|
253
326
|
}
|
|
254
327
|
|
|
255
328
|
/// The local control protocol loop.
|
|
329
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
256
330
|
async fn serve() -> Result<()> {
|
|
257
331
|
let (out_tx, mut out_rx) = tokio_mpsc::unbounded_channel::<String>();
|
|
258
332
|
let writer = tokio::spawn(async move {
|
|
@@ -343,6 +417,7 @@ async fn serve() -> Result<()> {
|
|
|
343
417
|
Ok(())
|
|
344
418
|
}
|
|
345
419
|
|
|
420
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
346
421
|
fn render_event(notice: session::Notice) -> Option<String> {
|
|
347
422
|
let session::Notice { session_id, event } = notice;
|
|
348
423
|
let (name, params) = match event {
|
|
@@ -373,10 +448,18 @@ fn render_event(notice: session::Notice) -> Option<String> {
|
|
|
373
448
|
"session.state",
|
|
374
449
|
serde_json::json!({ "sessionId": session_id, "capture": capture, "transport": transport, "firstFrame": first_frame }),
|
|
375
450
|
),
|
|
451
|
+
session::SessionEvent::Frame { seq, damage } => (
|
|
452
|
+
"session.frame.changed",
|
|
453
|
+
serde_json::json!({ "sessionId": session_id, "seq": seq, "damage": damage }),
|
|
454
|
+
),
|
|
376
455
|
session::SessionEvent::RestoreToken(token) => (
|
|
377
456
|
"session.restoreToken",
|
|
378
457
|
serde_json::json!({ "sessionId": session_id, "token": token }),
|
|
379
458
|
),
|
|
459
|
+
session::SessionEvent::CaptureStopped { reason } => (
|
|
460
|
+
"session.capture.stopped",
|
|
461
|
+
serde_json::json!({ "sessionId": session_id, "reason": reason }),
|
|
462
|
+
),
|
|
380
463
|
session::SessionEvent::Revoked { reason } => (
|
|
381
464
|
"session.revoked",
|
|
382
465
|
serde_json::json!({ "sessionId": session_id, "reason": reason }),
|
|
@@ -389,6 +472,7 @@ fn render_event(notice: session::Notice) -> Option<String> {
|
|
|
389
472
|
.ok()
|
|
390
473
|
}
|
|
391
474
|
|
|
475
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
392
476
|
async fn dispatch(
|
|
393
477
|
request: &Request,
|
|
394
478
|
hello_seen: &mut bool,
|
|
@@ -439,6 +523,7 @@ async fn dispatch(
|
|
|
439
523
|
restore_token: params.restore_token,
|
|
440
524
|
ttl: params.ttl_seconds.map(Duration::from_secs),
|
|
441
525
|
loopback_tcp: params.loopback_tcp,
|
|
526
|
+
agent_indicator: params.agent_indicator,
|
|
442
527
|
ice_servers: params
|
|
443
528
|
.ice_servers
|
|
444
529
|
.into_iter()
|
|
@@ -518,6 +603,28 @@ async fn dispatch(
|
|
|
518
603
|
.map_err(|reason| ErrorBody::new("clipboard", reason))?;
|
|
519
604
|
Ok(serde_json::json!({ "written": true }))
|
|
520
605
|
}
|
|
606
|
+
"session.frame" => {
|
|
607
|
+
let session = require_session(current)?;
|
|
608
|
+
let params: protocol::FrameParams = serde_json::from_value(request.params.clone())
|
|
609
|
+
.map_err(|error| ErrorBody::new("malformed", error.to_string()))?;
|
|
610
|
+
check_session(session, ¶ms.session_id, None)?;
|
|
611
|
+
if params.after_seq.is_some()
|
|
612
|
+
|| params.still_ms.is_some()
|
|
613
|
+
|| params.timeout_ms.is_some()
|
|
614
|
+
{
|
|
615
|
+
session
|
|
616
|
+
.wait_frame(
|
|
617
|
+
params.after_seq,
|
|
618
|
+
params.still_ms,
|
|
619
|
+
params.timeout_ms.unwrap_or(5000),
|
|
620
|
+
)
|
|
621
|
+
.await
|
|
622
|
+
.map_err(|error| ErrorBody::new(error.code, error.message))?;
|
|
623
|
+
}
|
|
624
|
+
session
|
|
625
|
+
.frame(params.since, ¶ms.path, params.region)
|
|
626
|
+
.map_err(|error| ErrorBody::new(error.code, error.message))
|
|
627
|
+
}
|
|
521
628
|
"session.metrics" => {
|
|
522
629
|
let session = require_session(current)?;
|
|
523
630
|
Ok(serde_json::to_value(session.metrics()).unwrap_or(serde_json::Value::Null))
|
|
@@ -538,6 +645,7 @@ async fn dispatch(
|
|
|
538
645
|
}
|
|
539
646
|
}
|
|
540
647
|
|
|
648
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
541
649
|
fn require_session(
|
|
542
650
|
current: &mut Option<session::Session>,
|
|
543
651
|
) -> std::result::Result<&mut session::Session, ErrorBody> {
|
|
@@ -548,6 +656,7 @@ fn require_session(
|
|
|
548
656
|
|
|
549
657
|
/// A late message for a session that has already ended must not revive it, and a
|
|
550
658
|
/// message for a different session must not reach this one.
|
|
659
|
+
#[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
|
|
551
660
|
fn check_session(
|
|
552
661
|
session: &session::Session,
|
|
553
662
|
id: &str,
|
|
@@ -567,7 +676,12 @@ fn check_session(
|
|
|
567
676
|
Ok(())
|
|
568
677
|
}
|
|
569
678
|
|
|
570
|
-
#[cfg(
|
|
679
|
+
#[cfg(target_os = "macos")]
|
|
680
|
+
fn main() {
|
|
681
|
+
std::process::exit(mac::run());
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
#[cfg(all(test, target_os = "linux"))]
|
|
571
685
|
mod tests {
|
|
572
686
|
use super::*;
|
|
573
687
|
|
package/engine/src/portal.rs
CHANGED
|
@@ -42,6 +42,10 @@ pub struct PortalSession {
|
|
|
42
42
|
/// `restore_token` is a token from an earlier session; supplying it lets a
|
|
43
43
|
/// backend skip the picker when its policy allows it. The returned token must be
|
|
44
44
|
/// persisted by the consumer and is single-use.
|
|
45
|
+
pub async fn open_display(_: Option<u32>) -> Result<PortalSession> {
|
|
46
|
+
anyhow::bail!("display selection is only supported on macOS")
|
|
47
|
+
}
|
|
48
|
+
|
|
45
49
|
pub async fn open(restore_token: Option<&str>) -> Result<PortalSession> {
|
|
46
50
|
let proxy = Screencast::new()
|
|
47
51
|
.await
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
use anyhow::{bail, Result};
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
|
|
4
|
+
/// A selected macOS display. `node_id` is its CoreGraphics display identifier.
|
|
5
|
+
#[derive(Debug, Clone, serde::Serialize)]
|
|
6
|
+
pub struct SelectedSource {
|
|
7
|
+
pub node_id: u32,
|
|
8
|
+
pub width: i32,
|
|
9
|
+
pub height: i32,
|
|
10
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
11
|
+
pub position: Option<(i32, i32)>,
|
|
12
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
13
|
+
pub source_type: Option<String>,
|
|
14
|
+
pub origin_x: i32,
|
|
15
|
+
pub origin_y: i32,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub struct PortalSession {
|
|
19
|
+
pub source: SelectedSource,
|
|
20
|
+
pub restore_token: Option<String>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub async fn open(_restore_token: Option<&str>) -> Result<PortalSession> {
|
|
24
|
+
open_display_inner(None)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub async fn open_display(display_id: Option<u32>) -> Result<PortalSession> {
|
|
28
|
+
open_display_inner(display_id)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn open_display_inner(display_id: Option<u32>) -> Result<PortalSession> {
|
|
32
|
+
if !unsafe { crate::mac::CGPreflightScreenCaptureAccess() } {
|
|
33
|
+
crate::mac::request_screen_capture_content().map_err(anyhow::Error::msg)?;
|
|
34
|
+
if !unsafe { crate::mac::CGPreflightScreenCaptureAccess() } {
|
|
35
|
+
bail!("capture-permission: allow {} in System Settings › Privacy & Security › Screen & System Audio Recording, then reconnect", crate::mac::tcc_responsible_app_name());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let displays = crate::mac::displays();
|
|
39
|
+
let selected = displays
|
|
40
|
+
.iter()
|
|
41
|
+
.find(|display| {
|
|
42
|
+
display_id.is_some_and(|id| display["id"].as_u64() == Some(id as u64))
|
|
43
|
+
|| (display_id.is_none() && display["main"] == true)
|
|
44
|
+
})
|
|
45
|
+
.or_else(|| display_id.is_none().then(|| displays.first()).flatten())
|
|
46
|
+
.ok_or_else(|| anyhow::anyhow!("no active display is available on the Mac"))?;
|
|
47
|
+
let origin_x = selected
|
|
48
|
+
.pointer("/origin/x")
|
|
49
|
+
.and_then(Value::as_f64)
|
|
50
|
+
.unwrap_or(0.0) as i32;
|
|
51
|
+
let origin_y = selected
|
|
52
|
+
.pointer("/origin/y")
|
|
53
|
+
.and_then(Value::as_f64)
|
|
54
|
+
.unwrap_or(0.0) as i32;
|
|
55
|
+
let source = SelectedSource {
|
|
56
|
+
node_id: selected["id"].as_u64().unwrap_or_default() as u32,
|
|
57
|
+
width: selected["width"].as_u64().unwrap_or_default() as i32,
|
|
58
|
+
height: selected["height"].as_u64().unwrap_or_default() as i32,
|
|
59
|
+
position: Some((origin_x, origin_y)),
|
|
60
|
+
source_type: Some(String::from("display")),
|
|
61
|
+
origin_x,
|
|
62
|
+
origin_y,
|
|
63
|
+
};
|
|
64
|
+
Ok(PortalSession {
|
|
65
|
+
source,
|
|
66
|
+
restore_token: None,
|
|
67
|
+
})
|
|
68
|
+
}
|
package/engine/src/protocol.rs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
use serde::{Deserialize, Serialize};
|
|
5
5
|
|
|
6
|
-
pub const PROTOCOL_VERSION: u32 =
|
|
6
|
+
pub const PROTOCOL_VERSION: u32 = 3;
|
|
7
7
|
|
|
8
8
|
/// A request from the consumer to the engine.
|
|
9
9
|
#[derive(Debug, Deserialize)]
|
|
@@ -81,6 +81,10 @@ pub struct IceServerParam {
|
|
|
81
81
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
82
82
|
pub enum SourceRequest {
|
|
83
83
|
Portal,
|
|
84
|
+
Display {
|
|
85
|
+
#[serde(default)]
|
|
86
|
+
display_id: Option<u32>,
|
|
87
|
+
},
|
|
84
88
|
X11 {
|
|
85
89
|
#[serde(default)]
|
|
86
90
|
display: Option<String>,
|
|
@@ -112,6 +116,9 @@ pub struct OpenParams {
|
|
|
112
116
|
/// whose only way here is a local forward (an SSH tunnel).
|
|
113
117
|
#[serde(default)]
|
|
114
118
|
pub loopback_tcp: bool,
|
|
119
|
+
/// Show local, capture-excluded feedback for an agent controlling this session.
|
|
120
|
+
#[serde(default)]
|
|
121
|
+
pub agent_indicator: bool,
|
|
115
122
|
}
|
|
116
123
|
|
|
117
124
|
/// The encode box when the consumer names none: a desktop's own pixels up to
|
|
@@ -130,6 +137,23 @@ fn default_fps() -> u32 {
|
|
|
130
137
|
30
|
|
131
138
|
}
|
|
132
139
|
|
|
140
|
+
#[derive(Debug, Deserialize)]
|
|
141
|
+
pub struct FrameParams {
|
|
142
|
+
pub session_id: String,
|
|
143
|
+
#[serde(default)]
|
|
144
|
+
pub since: Option<u64>,
|
|
145
|
+
#[serde(default)]
|
|
146
|
+
pub after_seq: Option<u64>,
|
|
147
|
+
#[serde(default)]
|
|
148
|
+
pub still_ms: Option<u64>,
|
|
149
|
+
#[serde(default)]
|
|
150
|
+
pub timeout_ms: Option<u64>,
|
|
151
|
+
#[serde(default)]
|
|
152
|
+
pub path: String,
|
|
153
|
+
#[serde(default)]
|
|
154
|
+
pub region: Option<[usize; 4]>,
|
|
155
|
+
}
|
|
156
|
+
|
|
133
157
|
#[derive(Debug, Deserialize)]
|
|
134
158
|
pub struct SessionRef {
|
|
135
159
|
pub session_id: String,
|
|
@@ -316,6 +340,21 @@ mod tests {
|
|
|
316
340
|
assert!(serde_json::from_str::<ControlMessage>(r#"{"kind":"teleport"}"#).is_err());
|
|
317
341
|
}
|
|
318
342
|
|
|
343
|
+
#[test]
|
|
344
|
+
fn display_source_round_trips_its_explicit_id() {
|
|
345
|
+
let request: SourceRequest = serde_json::from_value(serde_json::json!({
|
|
346
|
+
"kind": "display",
|
|
347
|
+
"display_id": 42
|
|
348
|
+
}))
|
|
349
|
+
.unwrap();
|
|
350
|
+
assert!(matches!(
|
|
351
|
+
request,
|
|
352
|
+
SourceRequest::Display {
|
|
353
|
+
display_id: Some(42)
|
|
354
|
+
}
|
|
355
|
+
));
|
|
356
|
+
}
|
|
357
|
+
|
|
319
358
|
#[test]
|
|
320
359
|
fn open_defaults_keep_the_desktops_own_pixels_up_to_4k() {
|
|
321
360
|
let params: OpenParams =
|
|
@@ -327,5 +366,13 @@ mod tests {
|
|
|
327
366
|
"the engine sizes the rate to the surface"
|
|
328
367
|
);
|
|
329
368
|
assert_eq!(params.max_fps, 30);
|
|
369
|
+
assert!(
|
|
370
|
+
!params.agent_indicator,
|
|
371
|
+
"human control has no overlay by default"
|
|
372
|
+
);
|
|
373
|
+
let agent: OpenParams =
|
|
374
|
+
serde_json::from_str(r#"{"permissions":["view","control"],"agent_indicator":true}"#)
|
|
375
|
+
.unwrap();
|
|
376
|
+
assert!(agent.agent_indicator);
|
|
330
377
|
}
|
|
331
378
|
}
|