@desklink/host 0.5.8 → 0.5.9
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/dist/protocol.d.ts +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/docs/PROTOCOL.md +36 -5
- package/engine/Cargo.lock +2 -1
- package/engine/Cargo.toml +5 -2
- package/engine/native/agent_overlay_wayland.c +7 -4
- package/engine/src/hyprland.rs +25 -0
- package/engine/src/indicator.rs +29 -12
- package/engine/src/main.rs +99 -12
- package/engine/src/peer.rs +168 -17
- package/engine/src/session.rs +214 -14
- package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +2 -2
- package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +1 -1
- package/engine/vendor/rtc-ice/src/agent/mod.rs +1 -1
- package/engine/vendor/rtc-ice/src/dl_trace.rs +1 -1
- package/engine/vendor/rtc-ice/src/lib.rs +1 -1
- package/package.json +3 -3
package/dist/protocol.d.ts
CHANGED
package/dist/protocol.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
package/docs/PROTOCOL.md
CHANGED
|
@@ -265,6 +265,20 @@ rate and X11 capture loop; portal capture offers it as the preferred PipeWire
|
|
|
265
265
|
frame rate, and macOS sets its ScreenCaptureKit stream minimum frame interval.
|
|
266
266
|
A source may still supply fewer frames.
|
|
267
267
|
|
|
268
|
+
`ice_servers` carries the STUN/TURN servers the user brought, one object per
|
|
269
|
+
server with every URL that server lists. Each URL an entry lists reaches the
|
|
270
|
+
WebRTC stack with that entry's `username` and `credential` — listing
|
|
271
|
+
`stun:a` and `stun:b` in one entry uses both, so a fallback behind an
|
|
272
|
+
unreachable primary still gathers candidates. A TURN URL (`turn:`/`turns:`)
|
|
273
|
+
whose transport is not UDP — `?transport=tcp`, or a bare `turns:` URL, which
|
|
274
|
+
is TLS over TCP by default — is one the WebRTC stack cannot dial: the engine
|
|
275
|
+
leaves it out of the configuration and reports it once with a
|
|
276
|
+
`session.iceServerSkipped` warning event naming the URL, so a skipped URL is
|
|
277
|
+
never a silent drop. Everything else in the entry — including a URL the
|
|
278
|
+
parser cannot read — is handed to the WebRTC stack unchanged, whose own
|
|
279
|
+
verdict then applies: for example a `turn:` URL on an entry without both a
|
|
280
|
+
`username` and a `credential` refuses the open in the stack's own words.
|
|
281
|
+
|
|
268
282
|
`agent_indicator` is opt-in local feedback for an agent controller; desklink-axi
|
|
269
283
|
sets it for sessions it drives (except Windows); human phone control omits it. On
|
|
270
284
|
Linux it adds an eased arrow inside a halo that brightens on clicks and keys,
|
|
@@ -275,9 +289,11 @@ masked before capture/diff (pixels behind the effect can remain briefly stale).
|
|
|
275
289
|
On a located Hyprland window share each pointer cue goes to the desktop point
|
|
276
290
|
the pointer landed on — the window's place plus the tapped point (see Input and
|
|
277
291
|
clipboard) — and the window's picture holds no layer surface, so nothing is
|
|
278
|
-
masked; a refused tap draws no cue.
|
|
279
|
-
|
|
280
|
-
|
|
292
|
+
masked; a refused tap draws no cue. When a tap lands on another output than
|
|
293
|
+
the cue helper is on, the engine moves the helper there — respawning it with
|
|
294
|
+
the landed layout point, which selects that output exactly like a point
|
|
295
|
+
marker — and the cue draws at the landed point with its edge glow; a tap on
|
|
296
|
+
the helper's own output reuses it with no new process. On bare X11 it is an override-redirect, click-through window that follows the
|
|
281
297
|
pointer, so it stays
|
|
282
298
|
visible over windows that set their own cursor and never replaces the cursor.
|
|
283
299
|
Its size follows `Xft.dpi` from 96–384 DPI, defaulting to 1x otherwise;
|
|
@@ -316,6 +332,17 @@ the SDP and candidates to the client and brings back the answer.
|
|
|
316
332
|
"description":{"type":"offer","sdp":"v=0\r\n…"}}}
|
|
317
333
|
```
|
|
318
334
|
|
|
335
|
+
A renegotiation offer may include `geometry` when the captured source changes
|
|
336
|
+
size. It uses the same shape as the `session.open` result; consumers should
|
|
337
|
+
apply its encoded dimensions to the presented surface. Older hosts omit it.
|
|
338
|
+
|
|
339
|
+
```jsonc
|
|
340
|
+
{"event":"session.description","params":{"sessionId":"…","generation":1,
|
|
341
|
+
"description":{"type":"offer","sdp":"v=0\r\n…"},
|
|
342
|
+
"geometry":{"source":{"width":1200,"height":600},
|
|
343
|
+
"encoded":{"width":1200,"height":600},"origin":{"x":0,"y":0}}}}
|
|
344
|
+
```
|
|
345
|
+
|
|
319
346
|
```jsonc
|
|
320
347
|
{"id":5,"method":"session.description","params":{"session_id":"…","generation":1,
|
|
321
348
|
"description":{"type":"answer","sdp":"v=0\r\n…"}}}
|
|
@@ -500,7 +527,9 @@ FIR), and when the coded size changes that way, so a receiver never has to scale
|
|
|
500
527
|
references across a size change. `target_kbps` is the current rate target after
|
|
501
528
|
any back-off: for a standing queue on the path, measured from the receiver's
|
|
502
529
|
transport-wide congestion feedback (RTP `transport-cc`), and for the loss
|
|
503
|
-
its receiver reports carry.
|
|
530
|
+
its receiver reports carry. A path that runs over ICE-TCP backs off only on a
|
|
531
|
+
queue above 100 ms, not 25 ms, so a relay's delivery jitter does not pull the
|
|
532
|
+
target to its floor. It starts at the requested ceiling capped at
|
|
504
533
|
3000 kbps on an unknown link, then grows toward the ceiling on clean
|
|
505
534
|
loss/delay feedback. `codec` is what frames go out in
|
|
506
535
|
(`vp9` or `h264`) and `encoder` what codes them (`libvpx`, `videotoolbox`,
|
|
@@ -611,7 +640,9 @@ origin, before video scaling (`geometry.source`, not `geometry.encoded`). On X11
|
|
|
611
640
|
this is the root screen; on macOS it is the selected display, with native pixels
|
|
612
641
|
converted to display points. On a located Hyprland window share (see Input and
|
|
613
642
|
clipboard) they are the window's own pixels, and the ring shows on the desktop
|
|
614
|
-
point a press there would land, on the output holding it; a point
|
|
643
|
+
point a press there would land, on the output holding it; when a later point
|
|
644
|
+
lands on another output the helper moves there, and a point on its own output
|
|
645
|
+
reuses it with no new process. A point the window
|
|
615
646
|
cannot take there is refused with `indicator-unavailable` and the reason. Both
|
|
616
647
|
coordinates must be within its bounds. The
|
|
617
648
|
ring may clip at an edge; its label stays inside the display. `timeout_ms`
|
package/engine/Cargo.lock
CHANGED
|
@@ -536,7 +536,7 @@ checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
|
|
536
536
|
|
|
537
537
|
[[package]]
|
|
538
538
|
name = "desklink-host"
|
|
539
|
-
version = "0.5.
|
|
539
|
+
version = "0.5.9"
|
|
540
540
|
dependencies = [
|
|
541
541
|
"anyhow",
|
|
542
542
|
"ashpd",
|
|
@@ -557,6 +557,7 @@ dependencies = [
|
|
|
557
557
|
"pipewire",
|
|
558
558
|
"rand 0.8.8",
|
|
559
559
|
"rtc",
|
|
560
|
+
"rtc-ice",
|
|
560
561
|
"serde",
|
|
561
562
|
"serde_json",
|
|
562
563
|
"sha2",
|
package/engine/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "desklink-host"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.9"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
rust-version = "1.97.1"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -18,7 +18,7 @@ base64 = "0.22"
|
|
|
18
18
|
|
|
19
19
|
[target.'cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))'.dependencies]
|
|
20
20
|
anyhow = "1"
|
|
21
|
-
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "io-std", "sync", "time", "signal"] }
|
|
21
|
+
tokio = { version = "1", features = ["rt-multi-thread", "macros", "io-util", "io-std", "sync", "time", "signal", "net"] }
|
|
22
22
|
libc = "0.2"
|
|
23
23
|
bytes = "1"
|
|
24
24
|
# rtc-sctp 0.21.1 is required through this stack: 0.21.0 replaces an
|
|
@@ -27,6 +27,9 @@ bytes = "1"
|
|
|
27
27
|
# session. Cargo.lock pins 0.21.1; drop the pin when a later release carries it.
|
|
28
28
|
webrtc = { version = "0.21", default-features = false, features = ["runtime-tokio", "crypto-ring"] }
|
|
29
29
|
rtc = { version = "0.21", default-features = false, features = ["crypto-ring"] }
|
|
30
|
+
# Parses ice server URLs when the engine sorts out which ones the WebRTC
|
|
31
|
+
# stack can dial; the [patch.crates-io] entry below keeps this the vendored copy.
|
|
32
|
+
rtc-ice = { version = "0.21", default-features = false }
|
|
30
33
|
log = "0.4"
|
|
31
34
|
async-trait = "0.1"
|
|
32
35
|
rand = "0.8"
|
|
@@ -252,7 +252,9 @@ static int overlay_main(int source_w,int source_h,int point_mode,int origin_know
|
|
|
252
252
|
}
|
|
253
253
|
if(wl_display_roundtrip(o.display)<0)return 2;
|
|
254
254
|
}
|
|
255
|
-
|
|
255
|
+
// Point markers always name their output; a desktop agent cue does once
|
|
256
|
+
// the engine has seen a press land, so the cue opens on that output.
|
|
257
|
+
if(point_mode || (desktop && origin_known)) {
|
|
256
258
|
for(int i=0;i<o.output_count;i++) {
|
|
257
259
|
struct Output *out=&o.outputs[i];
|
|
258
260
|
if(origin_known && out->valid && origin_x>=out->x && origin_y>=out->y && origin_x<out->x+out->w && origin_y<out->y+out->h){selected=out->handle;o.mine=out;matches++;}
|
|
@@ -269,8 +271,9 @@ static int overlay_main(int source_w,int source_h,int point_mode,int origin_know
|
|
|
269
271
|
FT_Library library=NULL;FT_Face font=point_mode?point_font(&library):NULL;
|
|
270
272
|
if(point_mode&&!font){wl_display_disconnect(o.display);return 2;}
|
|
271
273
|
o.surface=wl_compositor_create_surface(o.compositor);
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
+
// A desktop surface learns which output it is on, whether the compositor
|
|
275
|
+
// placed it or a layout origin selected it.
|
|
276
|
+
if(desktop)wl_surface_add_listener(o.surface,&surface_listener,&o);
|
|
274
277
|
o.layer=wl_proxy_marshal_flags(o.shell,0,&layer_surface_interface,1,0,NULL,o.surface,selected,3,point_mode?"desklink-point":"desklink-agent");
|
|
275
278
|
wl_proxy_add_listener(o.layer,layer_listener,&o);
|
|
276
279
|
uint32_t anchor,keyboard_focus,empty_input;
|
|
@@ -356,7 +359,7 @@ static int overlay_main(int source_w,int source_h,int point_mode,int origin_know
|
|
|
356
359
|
return 0;
|
|
357
360
|
}
|
|
358
361
|
|
|
359
|
-
int desklink_agent_overlay_wayland(int w,int h,int desktop){return overlay_main(w,h,0,
|
|
362
|
+
int desklink_agent_overlay_wayland(int w,int h,int desktop,int known,int x,int y){return overlay_main(w,h,0,known,x,y,desktop);}
|
|
360
363
|
int desklink_point_overlay_wayland(int w,int h,int known,int x,int y,int desktop){return overlay_main(w,h,1,known,x,y,desktop);}
|
|
361
364
|
|
|
362
365
|
// Read-only capability probe for idempotent clear before a helper exists.
|
package/engine/src/hyprland.rs
CHANGED
|
@@ -303,6 +303,31 @@ impl WindowLocator {
|
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
+
/// The name of the monitor holding desktop point `(px, py)`, if any.
|
|
307
|
+
/// Geometry math matches `WindowLocator::locate`'s on-screen check, so a
|
|
308
|
+
/// point a press landed on maps to the output whose overlay must draw it.
|
|
309
|
+
pub fn monitor_containing(px: f64, py: f64) -> Result<Option<String>, String> {
|
|
310
|
+
let monitors = query("j/monitors")?;
|
|
311
|
+
for monitor in monitors.as_array().into_iter().flatten() {
|
|
312
|
+
if monitor["disabled"].as_bool() == Some(true) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
let scale = monitor["scale"].as_f64().filter(|scale| *scale > 0.0);
|
|
316
|
+
if let (Some(mx), Some(my), Some(mw), Some(mh), Some(scale)) = (
|
|
317
|
+
monitor["x"].as_f64(),
|
|
318
|
+
monitor["y"].as_f64(),
|
|
319
|
+
monitor["width"].as_f64(),
|
|
320
|
+
monitor["height"].as_f64(),
|
|
321
|
+
scale,
|
|
322
|
+
) {
|
|
323
|
+
if (mx..mx + mw / scale).contains(&px) && (my..my + mh / scale).contains(&py) {
|
|
324
|
+
return Ok(monitor["name"].as_str().map(str::to_owned));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
Ok(None)
|
|
329
|
+
}
|
|
330
|
+
|
|
306
331
|
/// A window's box in the layout: left, top, width, height.
|
|
307
332
|
fn bounds(window: &Value) -> Option<(f64, f64, f64, f64)> {
|
|
308
333
|
let pair = |key: &str| Some((window[key][0].as_f64()?, window[key][1].as_f64()?));
|
package/engine/src/indicator.rs
CHANGED
|
@@ -222,19 +222,29 @@ impl Indicator {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
/// `desktop`: cues are compositor layout coordinates, drawn only where
|
|
225
|
-
/// they fall on the output the overlay opened on.
|
|
225
|
+
/// they fall on the output the overlay opened on. `origin` selects that
|
|
226
|
+
/// output by layout point, reusing the point helper's xdg-output
|
|
227
|
+
/// selection; without one the compositor places the surface.
|
|
226
228
|
#[cfg(target_os = "linux")]
|
|
227
|
-
pub fn start_wayland(
|
|
229
|
+
pub fn start_wayland(
|
|
230
|
+
width: usize,
|
|
231
|
+
height: usize,
|
|
232
|
+
desktop: bool,
|
|
233
|
+
origin: Option<(i32, i32)>,
|
|
234
|
+
) -> Result<Self> {
|
|
228
235
|
let executable = std::env::current_exe()?;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
let mut command = Command::new(executable);
|
|
237
|
+
command
|
|
238
|
+
.arg("agent-overlay-wayland")
|
|
239
|
+
.arg(width.to_string())
|
|
240
|
+
.arg(height.to_string());
|
|
241
|
+
if desktop {
|
|
242
|
+
command.arg("desktop");
|
|
243
|
+
if let Some((x, y)) = origin {
|
|
244
|
+
command.arg(x.to_string()).arg(y.to_string());
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
Self::spawn(&mut command, 1.0, 1.0)
|
|
238
248
|
}
|
|
239
249
|
|
|
240
250
|
#[cfg(target_os = "linux")]
|
|
@@ -369,7 +379,14 @@ mod tests {
|
|
|
369
379
|
};
|
|
370
380
|
let mut drained = vec![];
|
|
371
381
|
for _ in 0..200 {
|
|
372
|
-
|
|
382
|
+
// Each drain re-reports the last two remembered states, so the
|
|
383
|
+
// same state can arrive in several drains when the helper's lines
|
|
384
|
+
// are split across reads. Keep each state once, in pipe order.
|
|
385
|
+
for cue in feed.drain() {
|
|
386
|
+
if !drained.contains(&cue.x) {
|
|
387
|
+
drained.push(cue.x);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
373
390
|
if drained.len() >= 3 {
|
|
374
391
|
break;
|
|
375
392
|
}
|
package/engine/src/main.rs
CHANGED
|
@@ -136,7 +136,14 @@ use tokio::sync::mpsc as tokio_mpsc;
|
|
|
136
136
|
unsafe extern "C" {
|
|
137
137
|
fn desklink_point_overlay_main(display: *const std::ffi::c_char) -> i32;
|
|
138
138
|
fn desklink_agent_overlay_main(display: *const std::ffi::c_char) -> i32;
|
|
139
|
-
fn desklink_agent_overlay_wayland(
|
|
139
|
+
fn desklink_agent_overlay_wayland(
|
|
140
|
+
source_w: i32,
|
|
141
|
+
source_h: i32,
|
|
142
|
+
desktop: i32,
|
|
143
|
+
origin_known: i32,
|
|
144
|
+
origin_x: i32,
|
|
145
|
+
origin_y: i32,
|
|
146
|
+
) -> i32;
|
|
140
147
|
fn desklink_wayland_layer_shell_available() -> i32;
|
|
141
148
|
fn desklink_point_overlay_wayland(
|
|
142
149
|
w: i32,
|
|
@@ -212,7 +219,21 @@ fn main() {
|
|
|
212
219
|
2
|
|
213
220
|
} else {
|
|
214
221
|
let desktop = i32::from(args.get(3).map(String::as_str) == Some("desktop"));
|
|
215
|
-
|
|
222
|
+
// A desktop cue respawned after a press names the layout
|
|
223
|
+
// point that chose its output: `w h desktop ox oy`.
|
|
224
|
+
let (known, ox, oy) = match (args.get(4), args.get(5)) {
|
|
225
|
+
(Some(x), Some(y)) => match (x.parse(), y.parse()) {
|
|
226
|
+
(Ok(x), Ok(y)) => (1, x, y),
|
|
227
|
+
_ => (0, 0, 0),
|
|
228
|
+
},
|
|
229
|
+
_ => (0, 0, 0),
|
|
230
|
+
};
|
|
231
|
+
let (known, ox, oy) = if desktop == 1 {
|
|
232
|
+
(known, ox, oy)
|
|
233
|
+
} else {
|
|
234
|
+
(0, 0, 0)
|
|
235
|
+
};
|
|
236
|
+
unsafe { desklink_agent_overlay_wayland(width, height, desktop, known, ox, oy) }
|
|
216
237
|
}
|
|
217
238
|
}
|
|
218
239
|
"capabilities" => {
|
|
@@ -542,6 +563,47 @@ async fn serve() -> Result<()> {
|
|
|
542
563
|
Ok(())
|
|
543
564
|
}
|
|
544
565
|
|
|
566
|
+
/// Splits the requested ICE servers into the configuration the peer gets and
|
|
567
|
+
/// the URLs this engine leaves out. Every URL the WebRTC stack can dial goes
|
|
568
|
+
/// through with its entry's username and credential — all of an entry's URLs,
|
|
569
|
+
/// so a fallback behind an unreachable primary still gathers candidates. A
|
|
570
|
+
/// TURN URL whose transport is not UDP is one the stack cannot dial, so it is
|
|
571
|
+
/// skipped here and reported once by the caller, never dropped silently. A
|
|
572
|
+
/// URL the parser cannot read is forwarded unchanged: the peer connection
|
|
573
|
+
/// owns that verdict and refuses the open in its own words.
|
|
574
|
+
#[cfg(any(
|
|
575
|
+
target_os = "linux",
|
|
576
|
+
all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
|
|
577
|
+
))]
|
|
578
|
+
fn usable_ice_servers(
|
|
579
|
+
servers: Vec<protocol::IceServerParam>,
|
|
580
|
+
) -> (
|
|
581
|
+
Vec<(Vec<String>, Option<String>, Option<String>)>,
|
|
582
|
+
Vec<String>,
|
|
583
|
+
) {
|
|
584
|
+
use rtc_ice::url::{ProtoType, SchemeType, Url};
|
|
585
|
+
let mut usable = Vec::new();
|
|
586
|
+
let mut skipped = Vec::new();
|
|
587
|
+
for server in servers {
|
|
588
|
+
let mut urls = Vec::with_capacity(server.urls.len());
|
|
589
|
+
for url in server.urls {
|
|
590
|
+
let undialable = Url::parse_url(&url).is_ok_and(|parsed| {
|
|
591
|
+
matches!(parsed.scheme, SchemeType::Turn | SchemeType::Turns)
|
|
592
|
+
&& parsed.proto != ProtoType::Udp
|
|
593
|
+
});
|
|
594
|
+
if undialable {
|
|
595
|
+
skipped.push(url);
|
|
596
|
+
} else {
|
|
597
|
+
urls.push(url);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if !urls.is_empty() {
|
|
601
|
+
usable.push((urls, server.username, server.credential));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
(usable, skipped)
|
|
605
|
+
}
|
|
606
|
+
|
|
545
607
|
#[cfg(any(
|
|
546
608
|
target_os = "linux",
|
|
547
609
|
all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
|
|
@@ -616,6 +678,10 @@ fn render_event(notice: session::Notice) -> Option<String> {
|
|
|
616
678
|
"session.capture.stopped",
|
|
617
679
|
serde_json::json!({ "sessionId": session_id, "reason": reason }),
|
|
618
680
|
),
|
|
681
|
+
session::SessionEvent::IceServerSkipped { url } => (
|
|
682
|
+
"session.iceServerSkipped",
|
|
683
|
+
serde_json::json!({ "sessionId": session_id, "url": url }),
|
|
684
|
+
),
|
|
619
685
|
session::SessionEvent::Revoked { reason, code } => (
|
|
620
686
|
"session.revoked",
|
|
621
687
|
serde_json::json!({ "sessionId": session_id, "reason": reason, "code": code }),
|
|
@@ -672,6 +738,7 @@ async fn dispatch(
|
|
|
672
738
|
}
|
|
673
739
|
let params: protocol::OpenParams = serde_json::from_value(request.params.clone())
|
|
674
740
|
.map_err(|error| ErrorBody::new("malformed", error.to_string()))?;
|
|
741
|
+
let usable = usable_ice_servers(params.ice_servers);
|
|
675
742
|
let open = session::OpenRequest {
|
|
676
743
|
source: params.source,
|
|
677
744
|
cursor: params.cursor,
|
|
@@ -685,16 +752,7 @@ async fn dispatch(
|
|
|
685
752
|
agent_indicator: params.agent_indicator,
|
|
686
753
|
local_frames: params.local_frames,
|
|
687
754
|
audio: params.audio,
|
|
688
|
-
ice_servers:
|
|
689
|
-
.ice_servers
|
|
690
|
-
.into_iter()
|
|
691
|
-
.filter_map(|server| {
|
|
692
|
-
server
|
|
693
|
-
.urls
|
|
694
|
-
.first()
|
|
695
|
-
.map(|url| (url.clone(), server.username, server.credential))
|
|
696
|
-
})
|
|
697
|
-
.collect(),
|
|
755
|
+
ice_servers: usable.0,
|
|
698
756
|
};
|
|
699
757
|
let session = session::Session::open(open, events.clone())
|
|
700
758
|
.await
|
|
@@ -713,6 +771,15 @@ async fn dispatch(
|
|
|
713
771
|
"agentIndicator": session.agent_indicator(),
|
|
714
772
|
"audio": session.audio_info(),
|
|
715
773
|
});
|
|
774
|
+
// URLs the WebRTC stack cannot dial were left out of the peer's
|
|
775
|
+
// configuration; each skipped one is named once instead of
|
|
776
|
+
// vanishing from a multi-URL entry without a trace.
|
|
777
|
+
for url in usable.1 {
|
|
778
|
+
let _ = events.send(session::Notice {
|
|
779
|
+
session_id: session.id().to_owned(),
|
|
780
|
+
event: session::SessionEvent::IceServerSkipped { url },
|
|
781
|
+
});
|
|
782
|
+
}
|
|
716
783
|
*current = Some(session);
|
|
717
784
|
Ok(result)
|
|
718
785
|
}
|
|
@@ -947,6 +1014,26 @@ mod tests {
|
|
|
947
1014
|
);
|
|
948
1015
|
}
|
|
949
1016
|
|
|
1017
|
+
#[test]
|
|
1018
|
+
fn the_skipped_ice_server_warning_names_the_url_once() {
|
|
1019
|
+
let line = render_event(session::Notice {
|
|
1020
|
+
session_id: String::from("session-1"),
|
|
1021
|
+
event: session::SessionEvent::IceServerSkipped {
|
|
1022
|
+
url: String::from("turn:turn.example.com:3478?transport=tcp"),
|
|
1023
|
+
},
|
|
1024
|
+
})
|
|
1025
|
+
.expect("a skipped-URL warning is renderable");
|
|
1026
|
+
let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
|
|
1027
|
+
assert_eq!(parsed["event"], "session.iceServerSkipped");
|
|
1028
|
+
assert_eq!(
|
|
1029
|
+
parsed["params"],
|
|
1030
|
+
serde_json::json!({
|
|
1031
|
+
"sessionId": "session-1",
|
|
1032
|
+
"url": "turn:turn.example.com:3478?transport=tcp"
|
|
1033
|
+
})
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
950
1037
|
#[tokio::test]
|
|
951
1038
|
async fn the_state_notification_carries_exactly_what_the_document_promises() {
|
|
952
1039
|
let line = render_event(session::Notice {
|
package/engine/src/peer.rs
CHANGED
|
@@ -23,7 +23,7 @@ use rtc::peer_connection::configuration::media_engine::{
|
|
|
23
23
|
use rtc::peer_connection::configuration::RTCConfigurationBuilder;
|
|
24
24
|
use rtc::peer_connection::event::RTCPeerConnectionIceEvent;
|
|
25
25
|
use rtc::peer_connection::sdp::RTCSessionDescription;
|
|
26
|
-
use rtc::peer_connection::transport::{RTCIceCandidateInit, RTCIceServer};
|
|
26
|
+
use rtc::peer_connection::transport::{RTCIceCandidateInit, RTCIceProtocol, RTCIceServer};
|
|
27
27
|
use rtc::rtcp::payload_feedbacks::full_intra_request::FullIntraRequest;
|
|
28
28
|
use rtc::rtcp::payload_feedbacks::picture_loss_indication::PictureLossIndication;
|
|
29
29
|
use rtc::rtcp::receiver_report::ReceiverReport;
|
|
@@ -47,7 +47,7 @@ use webrtc::peer_connection::{
|
|
|
47
47
|
};
|
|
48
48
|
use webrtc::runtime::default_runtime;
|
|
49
49
|
|
|
50
|
-
// desklink scout trace
|
|
50
|
+
// desklink scout trace: engine-side restart timeline.
|
|
51
51
|
// Enabled with `DESKLINK_ICE_TRACE=1`; stamps microseconds since the Unix epoch
|
|
52
52
|
// so these lines interleave with the vendored ICE agent's `[ice ...]` lines in
|
|
53
53
|
// one order. Droppable: grep `desklink scout trace`.
|
|
@@ -258,7 +258,10 @@ pub enum PeerEvent {
|
|
|
258
258
|
/// Transport settings the consumer chose for this session.
|
|
259
259
|
#[derive(Debug, Clone)]
|
|
260
260
|
pub struct TransportOptions {
|
|
261
|
-
|
|
261
|
+
/// One entry per ICE server the consumer asked for, carrying every URL
|
|
262
|
+
/// that entry listed; the engine has already left out the ones the
|
|
263
|
+
/// WebRTC stack cannot dial and reported those to the consumer.
|
|
264
|
+
pub ice_servers: Vec<(Vec<String>, Option<String>, Option<String>)>,
|
|
262
265
|
/// Listen for ICE over TCP on the loopback too. A phone that reaches this
|
|
263
266
|
/// computer only through SSH has no UDP path; it forwards this port over
|
|
264
267
|
/// the SSH connection it already has, and the picture rides that.
|
|
@@ -474,7 +477,7 @@ impl PeerConnectionEventHandler for Handler {
|
|
|
474
477
|
}
|
|
475
478
|
|
|
476
479
|
async fn on_connection_state_change(&self, state: RTCPeerConnectionState) {
|
|
477
|
-
// desklink scout trace
|
|
480
|
+
// desklink scout trace: the engine's rtc-level
|
|
478
481
|
// transport state event, on the same microsecond clock as the vendored
|
|
479
482
|
// agent's `[ice ...]` lines, so the rtc Connected gate is separable.
|
|
480
483
|
if dl_trace_on() {
|
|
@@ -898,8 +901,8 @@ impl VideoPeer {
|
|
|
898
901
|
options
|
|
899
902
|
.ice_servers
|
|
900
903
|
.iter()
|
|
901
|
-
.map(|(
|
|
902
|
-
urls:
|
|
904
|
+
.map(|(urls, username, credential)| RTCIceServer {
|
|
905
|
+
urls: urls.clone(),
|
|
903
906
|
username: username.clone().unwrap_or_default(),
|
|
904
907
|
credential: credential.clone().unwrap_or_default(),
|
|
905
908
|
})
|
|
@@ -1130,6 +1133,21 @@ impl VideoPeer {
|
|
|
1130
1133
|
self.connected.load(Ordering::SeqCst)
|
|
1131
1134
|
}
|
|
1132
1135
|
|
|
1136
|
+
/// Whether ICE selected a TCP candidate pair (the relay's ICE-TCP path).
|
|
1137
|
+
/// False until a pair is selected.
|
|
1138
|
+
pub async fn selected_pair_is_tcp(&self) -> bool {
|
|
1139
|
+
let Some(sender) = self.peer.get_senders().await.into_iter().next() else {
|
|
1140
|
+
return false;
|
|
1141
|
+
};
|
|
1142
|
+
let Ok(Some(dtls)) = sender.transport().await else {
|
|
1143
|
+
return false;
|
|
1144
|
+
};
|
|
1145
|
+
matches!(
|
|
1146
|
+
dtls.ice_transport().get_selected_candidate_pair().await,
|
|
1147
|
+
Ok(Some(pair)) if pair.local().protocol == RTCIceProtocol::Tcp
|
|
1148
|
+
)
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1133
1151
|
/// Stand in for a connected transport, for a pipeline under test.
|
|
1134
1152
|
#[cfg(test)]
|
|
1135
1153
|
pub fn assume_connected(&self) {
|
|
@@ -1172,6 +1190,12 @@ impl VideoPeer {
|
|
|
1172
1190
|
lock(&self.path).take()
|
|
1173
1191
|
}
|
|
1174
1192
|
|
|
1193
|
+
/// Stand in for transport-wide feedback that reports a path, for a pipeline under test.
|
|
1194
|
+
#[cfg(test)]
|
|
1195
|
+
pub fn report_path(&self, report: PathReport) {
|
|
1196
|
+
*lock(&self.path) = Some(report);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1175
1199
|
/// Take the pending "the far end needs a reference frame" request, if any.
|
|
1176
1200
|
pub fn take_keyframe_request(&self) -> bool {
|
|
1177
1201
|
self.wants_keyframe.swap(false, Ordering::SeqCst)
|
|
@@ -1697,6 +1721,7 @@ pub(crate) mod tests {
|
|
|
1697
1721
|
async fn media_receiver(
|
|
1698
1722
|
receiver: Arc<MediaReceiver>,
|
|
1699
1723
|
udp: Vec<std::net::SocketAddr>,
|
|
1724
|
+
tcp: Vec<std::net::SocketAddr>,
|
|
1700
1725
|
) -> Arc<dyn PeerConnection> {
|
|
1701
1726
|
let mut media_engine = MediaEngine::default();
|
|
1702
1727
|
media_engine
|
|
@@ -1710,6 +1735,7 @@ pub(crate) mod tests {
|
|
|
1710
1735
|
.with_runtime(default_runtime().expect("a runtime"))
|
|
1711
1736
|
.with_handler(receiver)
|
|
1712
1737
|
.with_udp_addrs(udp)
|
|
1738
|
+
.with_tcp_addrs(tcp)
|
|
1713
1739
|
.build()
|
|
1714
1740
|
.await
|
|
1715
1741
|
.expect("a receiving peer");
|
|
@@ -1719,17 +1745,27 @@ pub(crate) mod tests {
|
|
|
1719
1745
|
/// An engine and a client receiving on `client_udp`, offered, answered and
|
|
1720
1746
|
/// connected over ICE, with the client's RTP arrival times and the
|
|
1721
1747
|
/// candidates the engine offered.
|
|
1722
|
-
struct Connected {
|
|
1723
|
-
engine: Arc<VideoPeer>,
|
|
1748
|
+
pub(crate) struct Connected {
|
|
1749
|
+
pub(crate) engine: Arc<VideoPeer>,
|
|
1724
1750
|
offer: String,
|
|
1725
|
-
client: Arc<dyn PeerConnection>,
|
|
1751
|
+
pub(crate) client: Arc<dyn PeerConnection>,
|
|
1726
1752
|
arrivals: Arc<Mutex<Vec<Instant>>>,
|
|
1727
1753
|
offered: Arc<Mutex<Vec<String>>>,
|
|
1728
1754
|
}
|
|
1729
1755
|
|
|
1730
|
-
|
|
1756
|
+
/// `client_tcp` and `loopback_tcp` give a pair that runs over ICE-TCP: a
|
|
1757
|
+
/// client with no UDP candidates can only reach the engine over TCP.
|
|
1758
|
+
pub(crate) async fn connected(
|
|
1759
|
+
client_udp: Vec<std::net::SocketAddr>,
|
|
1760
|
+
client_tcp: Vec<std::net::SocketAddr>,
|
|
1761
|
+
loopback_tcp: bool,
|
|
1762
|
+
) -> Connected {
|
|
1731
1763
|
let (events, mut events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
1732
|
-
let
|
|
1764
|
+
let options = TransportOptions {
|
|
1765
|
+
loopback_tcp,
|
|
1766
|
+
..transport()
|
|
1767
|
+
};
|
|
1768
|
+
let (engine, offer) = VideoPeer::offer(options, events, VideoCodec::Vp9)
|
|
1733
1769
|
.await
|
|
1734
1770
|
.expect("an offering peer");
|
|
1735
1771
|
let engine = Arc::new(engine);
|
|
@@ -1743,7 +1779,7 @@ pub(crate) mod tests {
|
|
|
1743
1779
|
arrivals: Arc::clone(&arrivals),
|
|
1744
1780
|
connected: Arc::clone(&connected),
|
|
1745
1781
|
});
|
|
1746
|
-
let client = media_receiver(Arc::clone(&receiver), client_udp).await;
|
|
1782
|
+
let client = media_receiver(Arc::clone(&receiver), client_udp, client_tcp).await;
|
|
1747
1783
|
|
|
1748
1784
|
client
|
|
1749
1785
|
.set_remote_description(RTCSessionDescription::offer(offer.clone()).expect("an offer"))
|
|
@@ -1824,7 +1860,12 @@ pub(crate) mod tests {
|
|
|
1824
1860
|
client,
|
|
1825
1861
|
arrivals,
|
|
1826
1862
|
offered,
|
|
1827
|
-
} = connected(
|
|
1863
|
+
} = connected(
|
|
1864
|
+
vec![std::net::SocketAddr::from(([0, 0, 0, 0], 0))],
|
|
1865
|
+
Vec::new(),
|
|
1866
|
+
false,
|
|
1867
|
+
)
|
|
1868
|
+
.await;
|
|
1828
1869
|
eprintln!("restart: the engine offered {:?}", offered.lock().unwrap());
|
|
1829
1870
|
|
|
1830
1871
|
// Any bytes the VP9 packetizer turns into one access unit.
|
|
@@ -1968,10 +2009,14 @@ pub(crate) mod tests {
|
|
|
1968
2009
|
arrivals,
|
|
1969
2010
|
offered,
|
|
1970
2011
|
..
|
|
1971
|
-
} = connected(
|
|
1972
|
-
std::net::
|
|
1973
|
-
|
|
1974
|
-
|
|
2012
|
+
} = connected(
|
|
2013
|
+
vec![std::net::SocketAddr::from((
|
|
2014
|
+
std::net::Ipv6Addr::LOCALHOST,
|
|
2015
|
+
0,
|
|
2016
|
+
))],
|
|
2017
|
+
Vec::new(),
|
|
2018
|
+
false,
|
|
2019
|
+
)
|
|
1975
2020
|
.await;
|
|
1976
2021
|
let offered = offered.lock().unwrap().clone();
|
|
1977
2022
|
eprintln!("ipv6: the engine offered {offered:?}");
|
|
@@ -2047,4 +2092,110 @@ pub(crate) mod tests {
|
|
|
2047
2092
|
"no one else is offered a port on the loopback"
|
|
2048
2093
|
);
|
|
2049
2094
|
}
|
|
2095
|
+
|
|
2096
|
+
/// A STUN responder bound on the wildcard that answers every Binding
|
|
2097
|
+
/// request with the requester's own address, the way a real STUN server
|
|
2098
|
+
/// would. Reach it at the address `primary_ip` names, not at its bind
|
|
2099
|
+
/// address: the wildcard receives for every interface.
|
|
2100
|
+
async fn stun_responder() -> std::net::SocketAddr {
|
|
2101
|
+
use rtc::stun::message::Setter;
|
|
2102
|
+
let socket = tokio::net::UdpSocket::bind("0.0.0.0:0")
|
|
2103
|
+
.await
|
|
2104
|
+
.expect("a socket");
|
|
2105
|
+
let addr = socket.local_addr().expect("a bound port");
|
|
2106
|
+
tokio::spawn(async move {
|
|
2107
|
+
let mut buffer = vec![0u8; 1500];
|
|
2108
|
+
while let Ok((size, peer)) = socket.recv_from(&mut buffer).await {
|
|
2109
|
+
let mut request = rtc::stun::message::Message::new();
|
|
2110
|
+
request.raw = buffer[..size].to_vec();
|
|
2111
|
+
if request.decode().is_err() {
|
|
2112
|
+
continue;
|
|
2113
|
+
}
|
|
2114
|
+
let mut response = rtc::stun::message::Message::new();
|
|
2115
|
+
response.set_type(rtc::stun::message::BINDING_SUCCESS);
|
|
2116
|
+
response.transaction_id = request.transaction_id;
|
|
2117
|
+
let mapped = rtc::stun::xoraddr::XorMappedAddress {
|
|
2118
|
+
ip: peer.ip(),
|
|
2119
|
+
port: peer.port(),
|
|
2120
|
+
};
|
|
2121
|
+
if mapped.add_to(&mut response).is_err() {
|
|
2122
|
+
continue;
|
|
2123
|
+
}
|
|
2124
|
+
response.write_header();
|
|
2125
|
+
let _ = socket.send_to(&response.raw, peer).await;
|
|
2126
|
+
}
|
|
2127
|
+
});
|
|
2128
|
+
addr
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
/// The interface address this machine sources its own traffic from — the
|
|
2132
|
+
/// address a socket bound to this machine's interfaces can reach. The
|
|
2133
|
+
/// peer binds one socket per non-loopback interface (loopback is skipped
|
|
2134
|
+
/// when host candidates are gathered), and Windows' strong-host model
|
|
2135
|
+
/// refuses to send an interface-bound datagram to 127.0.0.1, where the
|
|
2136
|
+
/// Linux weak-host model allows it — so a loopback STUN fixture gathers
|
|
2137
|
+
/// nothing on Windows. A real STUN server sits on the subnet, and the
|
|
2138
|
+
/// fixture does the same. The probe is a connected UDP socket: the OS
|
|
2139
|
+
/// resolves the route and its source address without sending a packet.
|
|
2140
|
+
fn primary_ip() -> std::net::IpAddr {
|
|
2141
|
+
let probe = std::net::UdpSocket::bind("0.0.0.0:0").and_then(|probe| {
|
|
2142
|
+
probe.connect("192.0.2.1:9")?; // TEST-NET-1: connected UDP sends nothing
|
|
2143
|
+
probe.local_addr()
|
|
2144
|
+
});
|
|
2145
|
+
match probe {
|
|
2146
|
+
Ok(addr) if !addr.ip().is_unspecified() && !addr.ip().is_loopback() => addr.ip(),
|
|
2147
|
+
_ => std::net::IpAddr::from([127, 0, 0, 1]),
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
|
|
2151
|
+
/// One ice_servers entry listing two URLs must put both in front of the
|
|
2152
|
+
/// peer connection: the first responder here never answers, so any
|
|
2153
|
+
/// reflexive candidate can only have come from the second URL. The engine
|
|
2154
|
+
/// used to forward only the first URL of each entry, which lost the rest.
|
|
2155
|
+
#[tokio::test]
|
|
2156
|
+
async fn the_second_url_of_an_ice_server_entry_gathers_a_reflexive_candidate() {
|
|
2157
|
+
let ip = primary_ip();
|
|
2158
|
+
// A server that never answers: bound, so its port cannot be reused,
|
|
2159
|
+
// and silent, so it is as good as unreachable.
|
|
2160
|
+
let silent = tokio::net::UdpSocket::bind("0.0.0.0:0")
|
|
2161
|
+
.await
|
|
2162
|
+
.expect("a socket");
|
|
2163
|
+
let unreachable = silent.local_addr().expect("a port");
|
|
2164
|
+
let answering = stun_responder().await;
|
|
2165
|
+
|
|
2166
|
+
let (events, mut events_rx) = tokio::sync::mpsc::unbounded_channel();
|
|
2167
|
+
let (_peer, _offer) = VideoPeer::offer(
|
|
2168
|
+
TransportOptions {
|
|
2169
|
+
ice_servers: vec![(
|
|
2170
|
+
vec![
|
|
2171
|
+
format!("stun:{ip}:{}", unreachable.port()),
|
|
2172
|
+
format!("stun:{ip}:{}", answering.port()),
|
|
2173
|
+
],
|
|
2174
|
+
None,
|
|
2175
|
+
None,
|
|
2176
|
+
)],
|
|
2177
|
+
loopback_tcp: false,
|
|
2178
|
+
pace_bps: 20_000_000.0,
|
|
2179
|
+
},
|
|
2180
|
+
events,
|
|
2181
|
+
VideoCodec::Vp9,
|
|
2182
|
+
)
|
|
2183
|
+
.await
|
|
2184
|
+
.expect("a peer connection");
|
|
2185
|
+
|
|
2186
|
+
// Host candidates arrive without the network; the reflexive one needs
|
|
2187
|
+
// the binding round trip through the second URL of the entry.
|
|
2188
|
+
let deadline = tokio::time::Instant::now() + Duration::from_secs(10);
|
|
2189
|
+
loop {
|
|
2190
|
+
let event = tokio::time::timeout_at(deadline, events_rx.recv())
|
|
2191
|
+
.await
|
|
2192
|
+
.expect("no reflexive candidate was ever gathered")
|
|
2193
|
+
.expect("the peer event stream ended");
|
|
2194
|
+
if let PeerEvent::Candidate { candidate, .. } = event {
|
|
2195
|
+
if candidate.contains(" typ srflx ") {
|
|
2196
|
+
break;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2050
2201
|
}
|
package/engine/src/session.rs
CHANGED
|
@@ -703,7 +703,7 @@ pub struct OpenRequest {
|
|
|
703
703
|
pub max_height: usize,
|
|
704
704
|
pub bitrate_kbps: u32,
|
|
705
705
|
pub max_fps: u32,
|
|
706
|
-
pub ice_servers: Vec<(String
|
|
706
|
+
pub ice_servers: Vec<(Vec<String>, Option<String>, Option<String>)>,
|
|
707
707
|
pub restore_token: Option<String>,
|
|
708
708
|
pub ttl: Option<Duration>,
|
|
709
709
|
/// Offer a passive ICE-TCP candidate on the loopback, for a forwarded client.
|
|
@@ -762,6 +762,13 @@ pub enum SessionEvent {
|
|
|
762
762
|
KeyframeRequest {
|
|
763
763
|
generation: u64,
|
|
764
764
|
},
|
|
765
|
+
/// An ICE server URL the request asked for that the peer's configuration
|
|
766
|
+
/// does not carry: one the WebRTC stack cannot dial (TURN over anything
|
|
767
|
+
/// but UDP). Reported once per open, naming the URL, so a skipped URL is
|
|
768
|
+
/// a warning the consumer sees rather than a silent drop.
|
|
769
|
+
IceServerSkipped {
|
|
770
|
+
url: String,
|
|
771
|
+
},
|
|
765
772
|
RestoreToken(String),
|
|
766
773
|
CaptureStopped {
|
|
767
774
|
reason: String,
|
|
@@ -821,6 +828,13 @@ struct Inner {
|
|
|
821
828
|
/// closes its Hyprland event socket.
|
|
822
829
|
#[cfg(target_os = "linux")]
|
|
823
830
|
window: Mutex<Option<crate::hyprland::WindowLocator>>,
|
|
831
|
+
/// Which Hyprland output the agent cue helper draws on (a located window
|
|
832
|
+
/// share's desktop mode): the helper respawns only when a press lands
|
|
833
|
+
/// elsewhere. Same for the point helper.
|
|
834
|
+
#[cfg(target_os = "linux")]
|
|
835
|
+
cue_output: Mutex<Option<String>>,
|
|
836
|
+
#[cfg(target_os = "linux")]
|
|
837
|
+
point_output: Mutex<Option<String>>,
|
|
824
838
|
capture: Mutex<Option<FrameSource>>,
|
|
825
839
|
/// Absent for an encoded source: there is no local keyboard to translate a
|
|
826
840
|
/// character through, because the client's keys are forwarded instead.
|
|
@@ -1274,8 +1288,11 @@ impl Session {
|
|
|
1274
1288
|
share_watch.is_some() && source.source_type.as_deref() == Some("Window");
|
|
1275
1289
|
#[cfg(target_os = "linux")]
|
|
1276
1290
|
if request.agent_indicator {
|
|
1291
|
+
// No press has landed yet, so no output is chosen: the
|
|
1292
|
+
// compositor places this first helper, and the first
|
|
1293
|
+
// located press moves it onto the landed output.
|
|
1277
1294
|
indicator =
|
|
1278
|
-
crate::indicator::Indicator::start_wayland(source_w, source_h, desktop)
|
|
1295
|
+
crate::indicator::Indicator::start_wayland(source_w, source_h, desktop, None)
|
|
1279
1296
|
.ok();
|
|
1280
1297
|
}
|
|
1281
1298
|
#[cfg(target_os = "macos")]
|
|
@@ -1493,6 +1510,10 @@ impl Session {
|
|
|
1493
1510
|
},
|
|
1494
1511
|
#[cfg(target_os = "linux")]
|
|
1495
1512
|
window: Mutex::new(window),
|
|
1513
|
+
#[cfg(target_os = "linux")]
|
|
1514
|
+
cue_output: Mutex::new(None),
|
|
1515
|
+
#[cfg(target_os = "linux")]
|
|
1516
|
+
point_output: Mutex::new(None),
|
|
1496
1517
|
capture: Mutex::new(Some(capture)),
|
|
1497
1518
|
layout: Mutex::new(Some(layout)),
|
|
1498
1519
|
#[cfg(target_os = "linux")]
|
|
@@ -1593,6 +1614,10 @@ impl Session {
|
|
|
1593
1614
|
point_desktop: PointDesktop::Unavailable("layer_shell_unavailable"),
|
|
1594
1615
|
#[cfg(target_os = "linux")]
|
|
1595
1616
|
window: Mutex::new(None),
|
|
1617
|
+
#[cfg(target_os = "linux")]
|
|
1618
|
+
cue_output: Mutex::new(None),
|
|
1619
|
+
#[cfg(target_os = "linux")]
|
|
1620
|
+
point_output: Mutex::new(None),
|
|
1596
1621
|
capture: Mutex::new(None),
|
|
1597
1622
|
layout: Mutex::new(None),
|
|
1598
1623
|
#[cfg(target_os = "linux")]
|
|
@@ -1739,6 +1764,10 @@ impl Session {
|
|
|
1739
1764
|
if let Some(helper) = overlay.take() {
|
|
1740
1765
|
helper.stop();
|
|
1741
1766
|
}
|
|
1767
|
+
#[cfg(target_os = "linux")]
|
|
1768
|
+
{
|
|
1769
|
+
*lock(&self.inner.point_output) = None;
|
|
1770
|
+
}
|
|
1742
1771
|
return Ok(());
|
|
1743
1772
|
}
|
|
1744
1773
|
let (x, y) = params
|
|
@@ -1763,21 +1792,35 @@ impl Session {
|
|
|
1763
1792
|
));
|
|
1764
1793
|
}
|
|
1765
1794
|
// A located window's point shows where a press there would land, on
|
|
1766
|
-
// the output holding it; the
|
|
1767
|
-
//
|
|
1795
|
+
// the output holding it; the helper moves only when that output
|
|
1796
|
+
// changes, so repeats on one output reuse it with no new process.
|
|
1768
1797
|
#[cfg(target_os = "linux")]
|
|
1769
1798
|
let landed = match lock(&self.inner.window).as_ref() {
|
|
1770
1799
|
Some(window) => {
|
|
1771
1800
|
let (px, py) = window
|
|
1772
1801
|
.locate(x, y)
|
|
1773
1802
|
.map_err(|message| SessionError::new("indicator-unavailable", message))?;
|
|
1774
|
-
if let Some(helper) = overlay.take() {
|
|
1775
|
-
helper.stop();
|
|
1776
|
-
}
|
|
1777
1803
|
Some((px.floor() as i64, py.floor() as i64))
|
|
1778
1804
|
}
|
|
1779
1805
|
None => None,
|
|
1780
1806
|
};
|
|
1807
|
+
#[cfg(target_os = "linux")]
|
|
1808
|
+
let landed_output: Option<String> = match landed {
|
|
1809
|
+
Some((px, py)) => {
|
|
1810
|
+
crate::hyprland::monitor_containing(px as f64, py as f64).unwrap_or(None)
|
|
1811
|
+
}
|
|
1812
|
+
None => None,
|
|
1813
|
+
};
|
|
1814
|
+
#[cfg(target_os = "linux")]
|
|
1815
|
+
if match (&landed, &landed_output) {
|
|
1816
|
+
(Some(_), output) => lock(&self.inner.point_output).clone() != *output,
|
|
1817
|
+
(None, _) => overlay.is_none(),
|
|
1818
|
+
} {
|
|
1819
|
+
if let Some(helper) = overlay.take() {
|
|
1820
|
+
helper.stop();
|
|
1821
|
+
}
|
|
1822
|
+
*lock(&self.inner.point_output) = landed_output.clone();
|
|
1823
|
+
}
|
|
1781
1824
|
#[cfg(not(target_os = "windows"))]
|
|
1782
1825
|
if overlay.is_none() {
|
|
1783
1826
|
#[cfg(target_os = "linux")]
|
|
@@ -2139,6 +2182,11 @@ impl Inner {
|
|
|
2139
2182
|
if let Some(helper) = lock(&self.point_overlay).take() {
|
|
2140
2183
|
helper.stop();
|
|
2141
2184
|
}
|
|
2185
|
+
#[cfg(target_os = "linux")]
|
|
2186
|
+
{
|
|
2187
|
+
*lock(&self.cue_output) = None;
|
|
2188
|
+
*lock(&self.point_output) = None;
|
|
2189
|
+
}
|
|
2142
2190
|
// Release first, then tear down: a stuck modifier is the one failure the
|
|
2143
2191
|
// user cannot undo by reconnecting.
|
|
2144
2192
|
if let Ok(mut input) = self.input.lock() {
|
|
@@ -2318,6 +2366,10 @@ impl Inner {
|
|
|
2318
2366
|
self.reject(seq, "input-unavailable", "no input backend");
|
|
2319
2367
|
return;
|
|
2320
2368
|
}
|
|
2369
|
+
// Only a located window-share press can move the cue helper: other
|
|
2370
|
+
// paths have no desktop point to choose an output from.
|
|
2371
|
+
#[cfg(target_os = "linux")]
|
|
2372
|
+
let located_press = located && matches!(&message, ControlMessage::Pointer { .. });
|
|
2321
2373
|
let outcome = match message {
|
|
2322
2374
|
ControlMessage::Pointer {
|
|
2323
2375
|
phase,
|
|
@@ -2361,6 +2413,14 @@ impl Inner {
|
|
|
2361
2413
|
};
|
|
2362
2414
|
match outcome {
|
|
2363
2415
|
Ok(()) => {
|
|
2416
|
+
#[cfg(target_os = "linux")]
|
|
2417
|
+
if located_press {
|
|
2418
|
+
// Onto the landed output before the cue is told where to
|
|
2419
|
+
// draw, so a cross-output tap draws there, not nowhere.
|
|
2420
|
+
if let Some((_, x, y)) = feedback {
|
|
2421
|
+
self.move_cue_to(x, y);
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2364
2424
|
if let Some((kind, x, y)) = feedback {
|
|
2365
2425
|
if let Ok(mut indicator) = self.indicator.lock() {
|
|
2366
2426
|
if let Some(indicator) = indicator.as_mut() {
|
|
@@ -2426,6 +2486,49 @@ impl Inner {
|
|
|
2426
2486
|
}
|
|
2427
2487
|
}
|
|
2428
2488
|
|
|
2489
|
+
/// Move the agent cue helper onto the output holding desktop point
|
|
2490
|
+
/// `(x, y)`, where a located press just landed. A press on the helper's
|
|
2491
|
+
/// own output reuses it with no new process; anything else respawns it
|
|
2492
|
+
/// with the landed origin, which selects that output through the point
|
|
2493
|
+
/// helper's xdg-output selection. Best-effort: when the output or the
|
|
2494
|
+
/// respawn is unavailable, the standing helper keeps the cue.
|
|
2495
|
+
#[cfg(target_os = "linux")]
|
|
2496
|
+
fn move_cue_to(&self, x: i64, y: i64) {
|
|
2497
|
+
let output = match crate::hyprland::monitor_containing(x as f64, y as f64) {
|
|
2498
|
+
Ok(output) => output,
|
|
2499
|
+
Err(error) => {
|
|
2500
|
+
log::warn!("agent cue stays on its output: {error}");
|
|
2501
|
+
return;
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
if lock(&self.cue_output).clone() == output {
|
|
2505
|
+
return;
|
|
2506
|
+
}
|
|
2507
|
+
let Some(name) = output else { return };
|
|
2508
|
+
let mut indicator = lock(&self.indicator);
|
|
2509
|
+
if let Some(helper) = indicator.take() {
|
|
2510
|
+
helper.stop();
|
|
2511
|
+
}
|
|
2512
|
+
let (width, height) = (
|
|
2513
|
+
self.source.width.max(1) as usize,
|
|
2514
|
+
self.source.height.max(1) as usize,
|
|
2515
|
+
);
|
|
2516
|
+
match crate::indicator::Indicator::start_wayland(
|
|
2517
|
+
width,
|
|
2518
|
+
height,
|
|
2519
|
+
true,
|
|
2520
|
+
Some((x as i32, y as i32)),
|
|
2521
|
+
) {
|
|
2522
|
+
Ok(helper) => {
|
|
2523
|
+
*indicator = Some(helper);
|
|
2524
|
+
*lock(&self.cue_output) = Some(name);
|
|
2525
|
+
}
|
|
2526
|
+
Err(error) => {
|
|
2527
|
+
log::warn!("agent cue stays on its output: the {name} helper did not start: {error:#}");
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2429
2532
|
fn pointer(
|
|
2430
2533
|
&self,
|
|
2431
2534
|
phase: PointerPhase,
|
|
@@ -2804,6 +2907,12 @@ fn auto_bitrate(width: usize, height: usize) -> u32 {
|
|
|
2804
2907
|
const QUEUE_HIGH: Duration = Duration::from_millis(25);
|
|
2805
2908
|
/// Below this the path has room again and the target may creep back up.
|
|
2806
2909
|
const QUEUE_LOW: Duration = Duration::from_millis(10);
|
|
2910
|
+
/// The same thresholds for a TCP path. TCP never drops, so its queue is read
|
|
2911
|
+
/// as delay alone, and a relay's bridge adds 20-30 ms of delivery jitter that is
|
|
2912
|
+
/// not queueing: at 25/10 ms that jitter backs the target off to its floor
|
|
2913
|
+
/// with the link idle. Only a queue that stands over 100 ms is a backlog here.
|
|
2914
|
+
const QUEUE_HIGH_TCP: Duration = Duration::from_millis(100);
|
|
2915
|
+
const QUEUE_LOW_TCP: Duration = Duration::from_millis(60);
|
|
2807
2916
|
/// How often queueing delay may back the target off: long enough for the last
|
|
2808
2917
|
/// back-off to reach the far end and show in its feedback, short enough that a
|
|
2809
2918
|
/// queue is drained in a few steps rather than a few seconds.
|
|
@@ -2825,6 +2934,8 @@ struct RateControl {
|
|
|
2825
2934
|
last_change: Option<Instant>,
|
|
2826
2935
|
/// The last queueing delay reported, while the path reports one.
|
|
2827
2936
|
queue: Option<Duration>,
|
|
2937
|
+
queue_high: Duration,
|
|
2938
|
+
queue_low: Duration,
|
|
2828
2939
|
}
|
|
2829
2940
|
|
|
2830
2941
|
impl RateControl {
|
|
@@ -2843,9 +2954,21 @@ impl RateControl {
|
|
|
2843
2954
|
target: ceiling,
|
|
2844
2955
|
last_change: None,
|
|
2845
2956
|
queue: None,
|
|
2957
|
+
queue_high: QUEUE_HIGH,
|
|
2958
|
+
queue_low: QUEUE_LOW,
|
|
2846
2959
|
}
|
|
2847
2960
|
}
|
|
2848
2961
|
|
|
2962
|
+
/// Read queueing delay against the thresholds for the transport the selected
|
|
2963
|
+
/// pair runs over.
|
|
2964
|
+
fn set_tcp(&mut self, tcp: bool) {
|
|
2965
|
+
(self.queue_high, self.queue_low) = if tcp {
|
|
2966
|
+
(QUEUE_HIGH_TCP, QUEUE_LOW_TCP)
|
|
2967
|
+
} else {
|
|
2968
|
+
(QUEUE_HIGH, QUEUE_LOW)
|
|
2969
|
+
};
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2849
2972
|
/// The new target when these loss reports (RFC 3550 fractions, /256) or
|
|
2850
2973
|
/// this path report move it: down on a standing queue at most every
|
|
2851
2974
|
/// `DELAY_BACKOFF_EVERY`, otherwise at most once a second.
|
|
@@ -2856,7 +2979,7 @@ impl RateControl {
|
|
|
2856
2979
|
let since = self.last_change.map(|at| now.duration_since(at));
|
|
2857
2980
|
let waited = |gap: Duration| since.is_none_or(|since| since >= gap);
|
|
2858
2981
|
let floor = (self.ceiling / 8).max(500);
|
|
2859
|
-
let next = if let Some(path) = path.filter(|path| path.queue_delay >
|
|
2982
|
+
let next = if let Some(path) = path.filter(|path| path.queue_delay > self.queue_high) {
|
|
2860
2983
|
if !waited(DELAY_BACKOFF_EVERY) {
|
|
2861
2984
|
return None;
|
|
2862
2985
|
}
|
|
@@ -2868,7 +2991,7 @@ impl RateControl {
|
|
|
2868
2991
|
return None;
|
|
2869
2992
|
}
|
|
2870
2993
|
let loss = worst as f64 / 256.0;
|
|
2871
|
-
let roomy = self.queue.is_none_or(|queue| queue <
|
|
2994
|
+
let roomy = self.queue.is_none_or(|queue| queue < self.queue_low);
|
|
2872
2995
|
if loss > 0.10 {
|
|
2873
2996
|
((self.target as f64 * 0.7) as u32).max(floor)
|
|
2874
2997
|
} else if loss < 0.02 && roomy {
|
|
@@ -3117,6 +3240,9 @@ fn spawn_pipeline(
|
|
|
3117
3240
|
}
|
|
3118
3241
|
let reports = peer.take_loss_reports();
|
|
3119
3242
|
let path = peer.take_path_report();
|
|
3243
|
+
if path.is_some() {
|
|
3244
|
+
rate.set_tcp(handle.block_on(peer.selected_pair_is_tcp()));
|
|
3245
|
+
}
|
|
3120
3246
|
if let Some(kbps) = rate.update(&reports, path, Instant::now()) {
|
|
3121
3247
|
let applied = inner.encoder.lock().map(|mut encoder| {
|
|
3122
3248
|
encoder
|
|
@@ -3678,6 +3804,10 @@ mod tests {
|
|
|
3678
3804
|
point_desktop: PointDesktop::Unavailable("layer_shell_unavailable"),
|
|
3679
3805
|
#[cfg(target_os = "linux")]
|
|
3680
3806
|
window: Mutex::new(None),
|
|
3807
|
+
#[cfg(target_os = "linux")]
|
|
3808
|
+
cue_output: Mutex::new(None),
|
|
3809
|
+
#[cfg(target_os = "linux")]
|
|
3810
|
+
point_output: Mutex::new(None),
|
|
3681
3811
|
capture: Mutex::new(None),
|
|
3682
3812
|
layout: Mutex::new(None),
|
|
3683
3813
|
#[cfg(target_os = "linux")]
|
|
@@ -3757,7 +3887,7 @@ mod tests {
|
|
|
3757
3887
|
)
|
|
3758
3888
|
.await
|
|
3759
3889
|
.expect("a peer connection builds without a network");
|
|
3760
|
-
test_inner_with(events, peer, 64, 64)
|
|
3890
|
+
test_inner_with(events, Arc::new(peer), 64, 64)
|
|
3761
3891
|
}
|
|
3762
3892
|
|
|
3763
3893
|
#[cfg(target_os = "linux")]
|
|
@@ -3892,7 +4022,7 @@ mod tests {
|
|
|
3892
4022
|
|
|
3893
4023
|
fn test_inner_with(
|
|
3894
4024
|
events: tokio_mpsc::UnboundedSender<Notice>,
|
|
3895
|
-
peer: VideoPeer
|
|
4025
|
+
peer: Arc<VideoPeer>,
|
|
3896
4026
|
width: usize,
|
|
3897
4027
|
height: usize,
|
|
3898
4028
|
) -> (Arc<Inner>, Arc<Mutex<Vec<(i16, bool)>>>) {
|
|
@@ -3920,7 +4050,7 @@ mod tests {
|
|
|
3920
4050
|
latest: Arc::new(Mutex::new(None)),
|
|
3921
4051
|
capture_error: Arc::new(Mutex::new(None)),
|
|
3922
4052
|
frame_changes: tokio::sync::watch::channel(0).1,
|
|
3923
|
-
peer: Mutex::new(Some(
|
|
4053
|
+
peer: Mutex::new(Some(peer)),
|
|
3924
4054
|
encoder: Mutex::new(Some(VideoEncoder::Vp9(
|
|
3925
4055
|
Encoder::new(width, height, 1000, 30, 1).expect("an encoder"),
|
|
3926
4056
|
))),
|
|
@@ -3939,6 +4069,10 @@ mod tests {
|
|
|
3939
4069
|
point_desktop: PointDesktop::Unavailable("layer_shell_unavailable"),
|
|
3940
4070
|
#[cfg(target_os = "linux")]
|
|
3941
4071
|
window: Mutex::new(None),
|
|
4072
|
+
#[cfg(target_os = "linux")]
|
|
4073
|
+
cue_output: Mutex::new(None),
|
|
4074
|
+
#[cfg(target_os = "linux")]
|
|
4075
|
+
point_output: Mutex::new(None),
|
|
3942
4076
|
layout: Mutex::new(Some(Layout::from_environment().expect("a keymap"))),
|
|
3943
4077
|
#[cfg(target_os = "linux")]
|
|
3944
4078
|
audio: Mutex::new(None),
|
|
@@ -4518,7 +4652,7 @@ mod tests {
|
|
|
4518
4652
|
let codec = crate::peer::tests::h264_receiver_codec();
|
|
4519
4653
|
let (peer, _) = crate::peer::tests::answered(vec![codec.clone()]).await;
|
|
4520
4654
|
let (events, mut received) = tokio_mpsc::unbounded_channel();
|
|
4521
|
-
let (inner, _) = test_inner_with(events, peer, 64, 64);
|
|
4655
|
+
let (inner, _) = test_inner_with(events, Arc::new(peer), 64, 64);
|
|
4522
4656
|
let session = Session { inner };
|
|
4523
4657
|
let mut offers = Vec::new();
|
|
4524
4658
|
for _ in 0..3 {
|
|
@@ -4690,6 +4824,72 @@ mod tests {
|
|
|
4690
4824
|
assert_eq!((later.codec, later.encoder), ("vp9", "libvpx"));
|
|
4691
4825
|
}
|
|
4692
4826
|
|
|
4827
|
+
/// The rate target after 3 s of delivery jitter on a connected pair: a queue
|
|
4828
|
+
/// that alternates between 5 and 30 ms with nothing lost, and 800 kbps
|
|
4829
|
+
/// delivered. The pipeline runs for real; only the path reports are fed.
|
|
4830
|
+
async fn target_under_bridge_jitter(tcp: bool) -> u32 {
|
|
4831
|
+
let peers = crate::peer::tests::connected(
|
|
4832
|
+
if tcp {
|
|
4833
|
+
Vec::new()
|
|
4834
|
+
} else {
|
|
4835
|
+
vec![std::net::SocketAddr::from(([0, 0, 0, 0], 0))]
|
|
4836
|
+
},
|
|
4837
|
+
if tcp {
|
|
4838
|
+
vec![std::net::SocketAddr::from(([127, 0, 0, 1], 0))]
|
|
4839
|
+
} else {
|
|
4840
|
+
Vec::new()
|
|
4841
|
+
},
|
|
4842
|
+
tcp,
|
|
4843
|
+
)
|
|
4844
|
+
.await;
|
|
4845
|
+
let engine = Arc::clone(&peers.engine);
|
|
4846
|
+
assert_eq!(
|
|
4847
|
+
engine.selected_pair_is_tcp().await,
|
|
4848
|
+
tcp,
|
|
4849
|
+
"the selected pair must run over {}",
|
|
4850
|
+
if tcp { "TCP" } else { "UDP" }
|
|
4851
|
+
);
|
|
4852
|
+
let (events, _events_rx) = tokio_mpsc::unbounded_channel();
|
|
4853
|
+
let (inner, _recorded) = test_inner_with(events, Arc::clone(&engine), 64, 64);
|
|
4854
|
+
let (_frame_tx, frame_rx) = latest_frame();
|
|
4855
|
+
spawn_pipeline(
|
|
4856
|
+
&inner,
|
|
4857
|
+
Arc::clone(&engine),
|
|
4858
|
+
frame_rx,
|
|
4859
|
+
Arc::new(AtomicBool::new(true)),
|
|
4860
|
+
30,
|
|
4861
|
+
1000,
|
|
4862
|
+
);
|
|
4863
|
+
for step in 0..300 {
|
|
4864
|
+
let queue_delay = Duration::from_millis(if step % 2 == 0 { 5 } else { 30 });
|
|
4865
|
+
engine.report_path(PathReport {
|
|
4866
|
+
queue_delay,
|
|
4867
|
+
delivered_kbps: 800,
|
|
4868
|
+
});
|
|
4869
|
+
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
4870
|
+
}
|
|
4871
|
+
let target = inner.metrics.lock().unwrap().target_kbps;
|
|
4872
|
+
drop(peers);
|
|
4873
|
+
target
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
#[tokio::test(flavor = "multi_thread")]
|
|
4877
|
+
async fn a_tcp_path_with_bridge_jitter_holds_its_rate_ceiling() {
|
|
4878
|
+
assert_eq!(
|
|
4879
|
+
target_under_bridge_jitter(true).await,
|
|
4880
|
+
1000,
|
|
4881
|
+
"a 5-30 ms jitter that is not a queue must not back a TCP path off"
|
|
4882
|
+
);
|
|
4883
|
+
}
|
|
4884
|
+
|
|
4885
|
+
#[tokio::test(flavor = "multi_thread")]
|
|
4886
|
+
async fn a_udp_path_with_the_same_jitter_still_backs_off() {
|
|
4887
|
+
assert!(
|
|
4888
|
+
target_under_bridge_jitter(false).await < 1000,
|
|
4889
|
+
"a UDP path reads the same 30 ms as a standing queue and must back off"
|
|
4890
|
+
);
|
|
4891
|
+
}
|
|
4892
|
+
|
|
4693
4893
|
#[tokio::test]
|
|
4694
4894
|
async fn a_receiver_that_asks_for_h264_is_sent_h264_from_the_first_frame() {
|
|
4695
4895
|
let Some(info) = tokio::task::spawn_blocking(crate::h264::probe)
|
|
@@ -4702,7 +4902,7 @@ mod tests {
|
|
|
4702
4902
|
let (peer, _answer) =
|
|
4703
4903
|
crate::peer::tests::answered(vec![crate::peer::tests::h264_receiver_codec()]).await;
|
|
4704
4904
|
let (events, _events_rx) = tokio_mpsc::unbounded_channel();
|
|
4705
|
-
let (inner, _recorded) = test_inner_with(events, peer, 320, 240);
|
|
4905
|
+
let (inner, _recorded) = test_inner_with(events, Arc::new(peer), 320, 240);
|
|
4706
4906
|
// The answer is real, so ICE runs and, with no candidates, never
|
|
4707
4907
|
// connects; each state it reports would take back `assume_connected`.
|
|
4708
4908
|
let pinned = current_peer(&inner).expect("the test's peer");
|
|
@@ -97,8 +97,8 @@ No public API changed; no signalling or wire change.
|
|
|
97
97
|
|
|
98
98
|
Do this once upstream rtc-ice keeps the previous selected pair across a restart
|
|
99
99
|
on its own and nominates a freshly validated pair without waiting for the next
|
|
100
|
-
check tick (an upstream PR is a follow-up;
|
|
101
|
-
since
|
|
100
|
+
check tick (an upstream PR is a follow-up; that needs a maintainer decision first,
|
|
101
|
+
since the contribution would carry its author's name).
|
|
102
102
|
|
|
103
103
|
## Dev-only measurement instrumentation (not part of the patch)
|
|
104
104
|
|
|
@@ -10,7 +10,7 @@ use stun::textattrs::*;
|
|
|
10
10
|
|
|
11
11
|
use crate::attributes::{control::*, priority::*, use_candidate::*};
|
|
12
12
|
use crate::candidate::{candidate_pair::*, *};
|
|
13
|
-
// desklink scout trace
|
|
13
|
+
// desklink scout trace: dev-only ICE timeline instrumentation.
|
|
14
14
|
use crate::dl_trace;
|
|
15
15
|
use crate::dl_trace::dl_ice;
|
|
16
16
|
|
|
@@ -38,7 +38,7 @@ use stun::xoraddr::*;
|
|
|
38
38
|
|
|
39
39
|
use crate::candidate::candidate_peer_reflexive::CandidatePeerReflexiveConfig;
|
|
40
40
|
use crate::candidate::{candidate_pair::*, *};
|
|
41
|
-
// desklink scout trace
|
|
41
|
+
// desklink scout trace: dev-only ICE timeline instrumentation.
|
|
42
42
|
use crate::dl_trace;
|
|
43
43
|
use crate::dl_trace::dl_ice;
|
|
44
44
|
use crate::mdns::{MulticastDnsMode, create_multicast_dns, generate_multicast_dns_name};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! desklink scout instrumentation (dev-only, `DESKLINK_ICE_TRACE=1`).
|
|
2
2
|
//!
|
|
3
3
|
//! Timestamped stderr trace of the ICE agent's restart/check/nomination timeline
|
|
4
|
-
//! for
|
|
4
|
+
//! for ICE restart diagnosis. Not part of the shipped patch; see
|
|
5
5
|
//! DESKLINK-PATCH.md. Every call site is marked `// desklink scout trace:` so the
|
|
6
6
|
//! whole diff is trivially droppable.
|
|
7
7
|
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
/// without adding — and version-matching — a direct `rtc-crypto` dependency.
|
|
59
59
|
pub use crypto;
|
|
60
60
|
|
|
61
|
-
// desklink scout trace: dev-only ICE timeline instrumentation
|
|
61
|
+
// desklink scout trace: dev-only ICE timeline instrumentation.
|
|
62
62
|
pub(crate) mod dl_trace;
|
|
63
63
|
|
|
64
64
|
pub mod agent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desklink/host",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"ws": "8.21.3"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"@desklink/host-linux-x64-gnu": "0.5.
|
|
42
|
-
"@desklink/host-darwin-arm64": "0.5.
|
|
41
|
+
"@desklink/host-linux-x64-gnu": "0.5.9",
|
|
42
|
+
"@desklink/host-darwin-arm64": "0.5.9"
|
|
43
43
|
}
|
|
44
44
|
}
|