@desklink/host 0.2.0 → 0.3.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.
Files changed (82) hide show
  1. package/NOTICE +26 -0
  2. package/README.md +134 -56
  3. package/bin/desklink-host.mjs +13 -5
  4. package/dist/bridge.d.ts +1 -0
  5. package/dist/bridge.d.ts.map +1 -1
  6. package/dist/bridge.js +12 -4
  7. package/dist/bridge.js.map +1 -1
  8. package/dist/engineProcess.d.ts +6 -1
  9. package/dist/engineProcess.d.ts.map +1 -1
  10. package/dist/engineProcess.js +35 -4
  11. package/dist/engineProcess.js.map +1 -1
  12. package/dist/protocol.d.ts +58 -0
  13. package/dist/protocol.d.ts.map +1 -1
  14. package/dist/protocol.js.map +1 -1
  15. package/dist/resolveEngine.d.ts +7 -1
  16. package/dist/resolveEngine.d.ts.map +1 -1
  17. package/dist/resolveEngine.js +66 -14
  18. package/dist/resolveEngine.js.map +1 -1
  19. package/docs/BOUNDARY.md +15 -7
  20. package/docs/PROTOCOL.md +267 -27
  21. package/engine/Cargo.lock +47 -2
  22. package/engine/Cargo.toml +18 -13
  23. package/engine/build.rs +86 -4
  24. package/engine/native/agent_overlay_mac.m +51 -4
  25. package/engine/native/agent_overlay_wayland.c +145 -12
  26. package/engine/native/agent_overlay_x11.c +72 -0
  27. package/engine/native/nvenc_shim.c +453 -0
  28. package/engine/native/vaapi_shim.c +727 -0
  29. package/engine/native/vpx_shim.c +54 -4
  30. package/engine/native/vt_shim.c +448 -0
  31. package/engine/src/capture.rs +167 -58
  32. package/engine/src/capture_mac.rs +24 -14
  33. package/engine/src/capture_win.rs +23 -0
  34. package/engine/src/clipboard_win.rs +8 -0
  35. package/engine/src/convert.rs +286 -61
  36. package/engine/src/cursor.rs +118 -0
  37. package/engine/src/encoder.rs +122 -13
  38. package/engine/src/h264.rs +188 -0
  39. package/engine/src/h264_nvenc.rs +308 -0
  40. package/engine/src/h264_openh264.rs +735 -0
  41. package/engine/src/h264_vaapi.rs +263 -0
  42. package/engine/src/h264_vt.rs +368 -0
  43. package/engine/src/indicator.rs +104 -3
  44. package/engine/src/input_win.rs +91 -0
  45. package/engine/src/keymap_win.rs +39 -0
  46. package/engine/src/mac.rs +34 -9
  47. package/engine/src/main.rs +186 -26
  48. package/engine/src/peer.rs +466 -28
  49. package/engine/src/portal.rs +42 -4
  50. package/engine/src/portal_win.rs +37 -0
  51. package/engine/src/protocol.rs +64 -2
  52. package/engine/src/session.rs +1252 -105
  53. package/engine/src/win.rs +110 -0
  54. package/engine/src/x11.rs +225 -9
  55. package/engine/src/x11_mac.rs +12 -0
  56. package/engine/vendor/libva/COPYING +19 -0
  57. package/engine/vendor/libva/va/va.h +5450 -0
  58. package/engine/vendor/libva/va/va_dec_av1.h +695 -0
  59. package/engine/vendor/libva/va/va_dec_hevc.h +616 -0
  60. package/engine/vendor/libva/va/va_dec_jpeg.h +157 -0
  61. package/engine/vendor/libva/va/va_dec_vp8.h +249 -0
  62. package/engine/vendor/libva/va/va_dec_vp9.h +312 -0
  63. package/engine/vendor/libva/va/va_dec_vvc.h +688 -0
  64. package/engine/vendor/libva/va/va_drm.h +61 -0
  65. package/engine/vendor/libva/va/va_enc_av1.h +1010 -0
  66. package/engine/vendor/libva/va/va_enc_h264.h +659 -0
  67. package/engine/vendor/libva/va/va_enc_hevc.h +958 -0
  68. package/engine/vendor/libva/va/va_enc_jpeg.h +162 -0
  69. package/engine/vendor/libva/va/va_enc_mpeg2.h +308 -0
  70. package/engine/vendor/libva/va/va_enc_vp8.h +350 -0
  71. package/engine/vendor/libva/va/va_enc_vp9.h +639 -0
  72. package/engine/vendor/libva/va/va_fei.h +170 -0
  73. package/engine/vendor/libva/va/va_fei_h264.h +505 -0
  74. package/engine/vendor/libva/va/va_fei_hevc.h +254 -0
  75. package/engine/vendor/libva/va/va_prot.h +525 -0
  76. package/engine/vendor/libva/va/va_version.h +87 -0
  77. package/engine/vendor/libva/va/va_vpp.h +1590 -0
  78. package/engine/vendor/nv-codec-headers/LICENSE +24 -0
  79. package/engine/vendor/nv-codec-headers/README +7 -0
  80. package/engine/vendor/nv-codec-headers/include/ffnvcodec/nvEncodeAPI.h +3825 -0
  81. package/examples/reference-client.html +1 -1
  82. package/package.json +4 -3
@@ -11,24 +11,51 @@ mod capture;
11
11
  #[cfg(target_os = "macos")]
12
12
  #[path = "capture_mac.rs"]
13
13
  mod capture;
14
+ #[cfg(target_os = "windows")]
15
+ #[path = "capture_win.rs"]
16
+ mod capture;
14
17
  #[cfg(target_os = "linux")]
15
18
  mod clipboard;
16
19
  #[cfg(target_os = "macos")]
17
20
  #[path = "clipboard_mac.rs"]
18
21
  mod clipboard;
19
- #[cfg(any(target_os = "linux", target_os = "macos"))]
22
+ #[cfg(target_os = "windows")]
23
+ #[path = "clipboard_win.rs"]
24
+ mod clipboard;
25
+ #[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
20
26
  mod convert;
21
27
  #[cfg(target_os = "linux")]
28
+ mod cursor;
29
+ #[cfg(target_os = "linux")]
22
30
  mod encoder;
23
- #[cfg(all(target_os = "macos", desklink_vpx))]
31
+ #[cfg(all(any(target_os = "macos", target_os = "windows"), desklink_vpx))]
24
32
  mod encoder;
25
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
33
+ #[cfg(any(
34
+ target_os = "linux",
35
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
36
+ ))]
37
+ mod h264;
38
+ #[cfg(target_os = "linux")]
39
+ mod h264_nvenc;
40
+ #[cfg(target_os = "linux")]
41
+ mod h264_openh264;
42
+ #[cfg(target_os = "linux")]
43
+ mod h264_vaapi;
44
+ #[cfg(all(target_os = "macos", desklink_vpx))]
45
+ mod h264_vt;
46
+ #[cfg(any(
47
+ target_os = "linux",
48
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
49
+ ))]
26
50
  mod indicator;
27
51
  #[cfg(target_os = "linux")]
28
52
  mod input;
29
53
  #[cfg(target_os = "macos")]
30
54
  #[path = "input_mac.rs"]
31
55
  mod input;
56
+ #[cfg(target_os = "windows")]
57
+ #[path = "input_win.rs"]
58
+ mod input;
32
59
  #[cfg(target_os = "linux")]
33
60
  mod keeper;
34
61
  #[cfg(target_os = "linux")]
@@ -36,44 +63,76 @@ mod keymap;
36
63
  #[cfg(target_os = "macos")]
37
64
  #[path = "keymap_mac.rs"]
38
65
  mod keymap;
66
+ #[cfg(target_os = "windows")]
67
+ #[path = "keymap_win.rs"]
68
+ mod keymap;
39
69
  #[cfg(target_os = "macos")]
40
70
  mod mac;
41
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
71
+ #[cfg(any(
72
+ target_os = "linux",
73
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
74
+ ))]
42
75
  mod peer;
43
76
  #[cfg(target_os = "linux")]
44
77
  mod portal;
45
78
  #[cfg(target_os = "macos")]
46
79
  #[path = "portal_mac.rs"]
47
80
  mod portal;
48
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
81
+ #[cfg(target_os = "windows")]
82
+ #[path = "portal_win.rs"]
83
+ mod portal;
84
+ #[cfg(any(
85
+ target_os = "linux",
86
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
87
+ ))]
49
88
  mod protocol;
50
89
  #[cfg(target_os = "linux")]
51
90
  mod session;
52
- #[cfg(all(target_os = "macos", desklink_vpx))]
91
+ #[cfg(all(any(target_os = "macos", target_os = "windows"), desklink_vpx))]
53
92
  mod session;
93
+ #[cfg(target_os = "windows")]
94
+ mod win;
54
95
  #[cfg(target_os = "linux")]
55
96
  mod x11;
56
- #[cfg(target_os = "macos")]
97
+ #[cfg(any(target_os = "macos", target_os = "windows"))]
57
98
  #[path = "x11_mac.rs"]
58
99
  mod x11;
59
100
 
60
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
101
+ #[cfg(any(
102
+ target_os = "linux",
103
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
104
+ ))]
61
105
  use anyhow::Result;
62
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
106
+ #[cfg(any(
107
+ target_os = "linux",
108
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
109
+ ))]
63
110
  use protocol::{ErrorBody, Event, Request, Response};
64
111
  #[cfg(target_os = "linux")]
65
112
  use std::sync::mpsc as std_mpsc;
66
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
113
+ #[cfg(any(
114
+ target_os = "linux",
115
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
116
+ ))]
67
117
  use std::time::Duration;
68
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
118
+ #[cfg(any(
119
+ target_os = "linux",
120
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
121
+ ))]
69
122
  use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
70
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
123
+ #[cfg(any(
124
+ target_os = "linux",
125
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
126
+ ))]
71
127
  use tokio::sync::mpsc as tokio_mpsc;
72
128
 
73
129
  #[cfg(target_os = "linux")]
74
130
  unsafe extern "C" {
131
+ fn desklink_point_overlay_main(display: *const std::ffi::c_char) -> i32;
75
132
  fn desklink_agent_overlay_main(display: *const std::ffi::c_char) -> i32;
76
133
  fn desklink_agent_overlay_wayland(source_w: i32, source_h: i32) -> i32;
134
+ fn desklink_wayland_layer_shell_available() -> i32;
135
+ fn desklink_point_overlay_wayland(w: i32, h: i32, known: i32, x: i32, y: i32) -> i32;
77
136
  }
78
137
 
79
138
  #[cfg(target_os = "linux")]
@@ -95,11 +154,39 @@ fn main() {
95
154
  println!("{}", env!("CARGO_PKG_VERSION"));
96
155
  0
97
156
  }
157
+ "point-overlay" => {
158
+ let display = args.get(1).map(String::as_str).unwrap_or("");
159
+ let display = std::ffi::CString::new(display).expect("valid X display");
160
+ unsafe { desklink_point_overlay_main(display.as_ptr()) }
161
+ }
98
162
  "agent-overlay" => {
99
163
  let display = args.get(1).map(String::as_str).unwrap_or("");
100
164
  let display = std::ffi::CString::new(display).expect("valid X display");
101
165
  unsafe { desklink_agent_overlay_main(display.as_ptr()) }
102
166
  }
167
+ "point-layer-shell-probe" => match unsafe { desklink_wayland_layer_shell_available() } {
168
+ 1 => {
169
+ println!("READY");
170
+ 0
171
+ }
172
+ 0 => {
173
+ println!("layer_shell_unavailable");
174
+ 2
175
+ }
176
+ _ => 2,
177
+ },
178
+ "point-overlay-wayland" => {
179
+ let values: Vec<i32> = args.iter().skip(1).filter_map(|v| v.parse().ok()).collect();
180
+ if values.len() != 5 || values[0] <= 0 || values[1] <= 0 {
181
+ 2
182
+ } else {
183
+ unsafe {
184
+ desklink_point_overlay_wayland(
185
+ values[0], values[1], values[2], values[3], values[4],
186
+ )
187
+ }
188
+ }
189
+ }
103
190
  "agent-overlay-wayland" => {
104
191
  let width = args.get(1).and_then(|v| v.parse().ok()).unwrap_or(0);
105
192
  let height = args.get(2).and_then(|v| v.parse().ok()).unwrap_or(0);
@@ -139,7 +226,10 @@ fn main() {
139
226
  std::process::exit(code);
140
227
  }
141
228
 
142
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
229
+ #[cfg(any(
230
+ target_os = "linux",
231
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
232
+ ))]
143
233
  fn report(result: Result<()>) -> i32 {
144
234
  match result {
145
235
  Ok(()) => 0,
@@ -285,15 +375,23 @@ fn frame_shape(frame: &crate::convert::I420) -> u64 {
285
375
 
286
376
  #[cfg(target_os = "linux")]
287
377
  async fn probe_portal(seconds: u64) -> Result<()> {
288
- let portal = portal::open(None).await?;
378
+ let portal = portal::open(None, protocol::CursorMode::Embedded).await?;
289
379
  eprintln!("portal source: {:?}", portal.source);
380
+ // The source's own size: a zero box converts nothing and reports no frames.
381
+ let (width, height) = crate::convert::fit(
382
+ portal.source.width.max(0) as usize,
383
+ portal.source.height.max(0) as usize,
384
+ usize::MAX,
385
+ usize::MAX,
386
+ );
290
387
 
291
388
  let (tx, rx) = std_mpsc::channel();
292
389
  let capture = capture::start(
293
390
  portal,
294
- 0,
295
- 0,
391
+ width,
392
+ height,
296
393
  30,
394
+ None,
297
395
  Box::new(move |frame, seq, _raw| {
298
396
  let _ = tx.send((frame.width, frame.height, seq));
299
397
  }),
@@ -332,7 +430,10 @@ async fn probe_portal(seconds: u64) -> Result<()> {
332
430
  }
333
431
 
334
432
  /// The local control protocol loop.
335
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
433
+ #[cfg(any(
434
+ target_os = "linux",
435
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
436
+ ))]
336
437
  async fn serve() -> Result<()> {
337
438
  let (out_tx, mut out_rx) = tokio_mpsc::unbounded_channel::<String>();
338
439
  let writer = tokio::spawn(async move {
@@ -417,13 +518,16 @@ async fn serve() -> Result<()> {
417
518
  if let Some(session) = current.take() {
418
519
  session.close("the consumer disconnected").await;
419
520
  }
420
- let _ = out_tx.send(String::new()).ok();
521
+ drop(events_tx);
421
522
  drop(out_tx);
422
523
  let _ = writer.await;
423
524
  Ok(())
424
525
  }
425
526
 
426
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
527
+ #[cfg(any(
528
+ target_os = "linux",
529
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
530
+ ))]
427
531
  fn render_event(notice: session::Notice) -> Option<String> {
428
532
  let session::Notice { session_id, event } = notice;
429
533
  let (name, params) = match event {
@@ -454,9 +558,17 @@ fn render_event(notice: session::Notice) -> Option<String> {
454
558
  "session.state",
455
559
  serde_json::json!({ "sessionId": session_id, "capture": capture, "transport": transport, "firstFrame": first_frame }),
456
560
  ),
457
- session::SessionEvent::Frame { seq, damage } => (
561
+ session::SessionEvent::Cursor { position } => (
562
+ "session.cursor",
563
+ serde_json::json!({ "sessionId": session_id, "x": position.x, "y": position.y, "visible": position.visible, "timestamp_us": position.timestamp_us }),
564
+ ),
565
+ session::SessionEvent::Frame {
566
+ seq,
567
+ damage,
568
+ timestamp_us,
569
+ } => (
458
570
  "session.frame.changed",
459
- serde_json::json!({ "sessionId": session_id, "seq": seq, "damage": damage }),
571
+ serde_json::json!({ "sessionId": session_id, "seq": seq, "damage": damage, "timestamp_us": timestamp_us }),
460
572
  ),
461
573
  session::SessionEvent::Input { input } => (
462
574
  "session.input",
@@ -474,9 +586,9 @@ fn render_event(notice: session::Notice) -> Option<String> {
474
586
  "session.capture.stopped",
475
587
  serde_json::json!({ "sessionId": session_id, "reason": reason }),
476
588
  ),
477
- session::SessionEvent::Revoked { reason } => (
589
+ session::SessionEvent::Revoked { reason, code } => (
478
590
  "session.revoked",
479
- serde_json::json!({ "sessionId": session_id, "reason": reason }),
591
+ serde_json::json!({ "sessionId": session_id, "reason": reason, "code": code }),
480
592
  ),
481
593
  };
482
594
  serde_json::to_string(&Event {
@@ -486,7 +598,10 @@ fn render_event(notice: session::Notice) -> Option<String> {
486
598
  .ok()
487
599
  }
488
600
 
489
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
601
+ #[cfg(any(
602
+ target_os = "linux",
603
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
604
+ ))]
490
605
  async fn dispatch(
491
606
  request: &Request,
492
607
  hello_seen: &mut bool,
@@ -529,6 +644,7 @@ async fn dispatch(
529
644
  .map_err(|error| ErrorBody::new("malformed", error.to_string()))?;
530
645
  let open = session::OpenRequest {
531
646
  source: params.source,
647
+ cursor: params.cursor,
532
648
  permissions: params.permissions,
533
649
  max_width: params.max_width,
534
650
  max_height: params.max_height,
@@ -538,6 +654,7 @@ async fn dispatch(
538
654
  ttl: params.ttl_seconds.map(Duration::from_secs),
539
655
  loopback_tcp: params.loopback_tcp,
540
656
  agent_indicator: params.agent_indicator,
657
+ local_frames: params.local_frames,
541
658
  ice_servers: params
542
659
  .ice_servers
543
660
  .into_iter()
@@ -562,10 +679,21 @@ async fn dispatch(
562
679
  "origin": { "x": session.source().origin_x, "y": session.source().origin_y },
563
680
  },
564
681
  "geometry": session.geometry(),
682
+ "cursor": session.cursor_info(),
565
683
  });
566
684
  *current = Some(session);
567
685
  Ok(result)
568
686
  }
687
+ "session.point" => {
688
+ let session = require_session(current)?;
689
+ let params: protocol::PointParams = serde_json::from_value(request.params.clone())
690
+ .map_err(|error| ErrorBody::new("malformed", error.to_string()))?;
691
+ check_session(session, &params.session_id, None)?;
692
+ session
693
+ .point(&params)
694
+ .map_err(|error| ErrorBody::new(error.code, error.message))?;
695
+ Ok(serde_json::json!({"shown": !params.clear}))
696
+ }
569
697
  "session.description" => {
570
698
  let session = require_session(current)?;
571
699
  let params: protocol::DescriptionParams =
@@ -687,7 +815,10 @@ async fn dispatch(
687
815
  }
688
816
  }
689
817
 
690
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
818
+ #[cfg(any(
819
+ target_os = "linux",
820
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
821
+ ))]
691
822
  fn require_session(
692
823
  current: &mut Option<session::Session>,
693
824
  ) -> std::result::Result<&mut session::Session, ErrorBody> {
@@ -698,7 +829,10 @@ fn require_session(
698
829
 
699
830
  /// A late message for a session that has already ended must not revive it, and a
700
831
  /// message for a different session must not reach this one.
701
- #[cfg(any(target_os = "linux", all(target_os = "macos", desklink_vpx)))]
832
+ #[cfg(any(
833
+ target_os = "linux",
834
+ all(any(target_os = "macos", target_os = "windows"), desklink_vpx)
835
+ ))]
702
836
  fn check_session(
703
837
  session: &session::Session,
704
838
  id: &str,
@@ -723,6 +857,11 @@ fn main() {
723
857
  std::process::exit(mac::run());
724
858
  }
725
859
 
860
+ #[cfg(target_os = "windows")]
861
+ fn main() {
862
+ std::process::exit(win::run());
863
+ }
864
+
726
865
  #[cfg(all(test, target_os = "linux"))]
727
866
  mod tests {
728
867
  use super::*;
@@ -744,6 +883,27 @@ mod tests {
744
883
  assert!(!hello_seen, "a refused handshake must not open the gate");
745
884
  }
746
885
 
886
+ #[test]
887
+ fn cursor_notice_preserves_source_coordinates_visibility_and_clock() {
888
+ let line = render_event(session::Notice {
889
+ session_id: String::from("cursor-session"),
890
+ event: session::SessionEvent::Cursor {
891
+ position: protocol::CursorPosition {
892
+ x: 32,
893
+ y: 64,
894
+ visible: false,
895
+ timestamp_us: 123456,
896
+ },
897
+ },
898
+ })
899
+ .unwrap();
900
+ let parsed: serde_json::Value = serde_json::from_str(&line).unwrap();
901
+ assert_eq!(
902
+ parsed,
903
+ serde_json::json!({"event":"session.cursor","params":{"sessionId":"cursor-session","x":32,"y":64,"visible":false,"timestamp_us":123456}})
904
+ );
905
+ }
906
+
747
907
  #[tokio::test]
748
908
  async fn the_state_notification_carries_exactly_what_the_document_promises() {
749
909
  let line = render_event(session::Notice {