@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
@@ -13,7 +13,30 @@ pub struct Indicator {
13
13
  position: std::sync::Arc<std::sync::Mutex<Option<(i64, i64)>>>,
14
14
  }
15
15
 
16
+ #[cfg(target_os = "linux")]
17
+ fn point_wayland_executable() -> Result<std::path::PathBuf> {
18
+ #[cfg(test)]
19
+ if let Some(path) = std::env::var_os("DESKLINK_POINT_TEST_ENGINE") {
20
+ return Ok(path.into());
21
+ }
22
+ Ok(std::env::current_exe()?)
23
+ }
24
+
16
25
  impl Indicator {
26
+ #[cfg(target_os = "linux")]
27
+ pub fn wayland_layer_shell_available() -> Result<bool> {
28
+ let mut command = Command::new(point_wayland_executable()?);
29
+ command.arg("point-layer-shell-probe");
30
+ match Self::spawn(&mut command, 1.0, 1.0) {
31
+ Ok(helper) => {
32
+ helper.stop();
33
+ Ok(true)
34
+ }
35
+ Err(error) if error.to_string().contains("layer_shell_unavailable") => Ok(false),
36
+ Err(error) => Err(error),
37
+ }
38
+ }
39
+
17
40
  #[cfg(target_os = "macos")]
18
41
  pub fn start(display_id: u32, width: usize, height: usize) -> Result<Self> {
19
42
  let executable = std::env::current_exe()?;
@@ -39,6 +62,77 @@ impl Indicator {
39
62
  )
40
63
  }
41
64
 
65
+ #[cfg(target_os = "linux")]
66
+ pub fn start_point(display: &str) -> Result<Self> {
67
+ Self::spawn(
68
+ Command::new(std::env::current_exe()?)
69
+ .arg("point-overlay")
70
+ .arg(display),
71
+ 1.0,
72
+ 1.0,
73
+ )
74
+ }
75
+
76
+ #[cfg(target_os = "linux")]
77
+ pub fn start_point_wayland(
78
+ width: i32,
79
+ height: i32,
80
+ position: Option<(i32, i32)>,
81
+ ) -> Result<Self> {
82
+ let (x, y) = position.unwrap_or_default();
83
+ Self::spawn(
84
+ Command::new(point_wayland_executable()?)
85
+ .arg("point-overlay-wayland")
86
+ .args([
87
+ width.to_string(),
88
+ height.to_string(),
89
+ i32::from(position.is_some()).to_string(),
90
+ x.to_string(),
91
+ y.to_string(),
92
+ ]),
93
+ 1.0,
94
+ 1.0,
95
+ )
96
+ }
97
+
98
+ #[cfg(target_os = "macos")]
99
+ pub fn start_point(display_id: u32, width: usize, height: usize) -> Result<Self> {
100
+ let (_, _, points_w, points_h) = crate::mac::display_geometry(display_id)
101
+ .context("the selected display is unavailable")?;
102
+ Self::spawn(
103
+ Command::new(std::env::current_exe()?)
104
+ .arg("point-overlay")
105
+ .arg(display_id.to_string()),
106
+ points_w / width as f64,
107
+ points_h / height as f64,
108
+ )
109
+ }
110
+
111
+ #[cfg(all(test, target_os = "linux"))]
112
+ pub fn test_pid(&self) -> u32 {
113
+ self.child.as_ref().unwrap().id()
114
+ }
115
+
116
+ pub fn point(&mut self, x: i64, y: i64, timeout_ms: u64, label: &str) -> Result<()> {
117
+ let input = self.input.as_mut().context("point overlay exited")?;
118
+ writeln!(
119
+ input,
120
+ "P {:.2} {:.2} {timeout_ms} {label}",
121
+ x as f64 * self.scale_x,
122
+ y as f64 * self.scale_y
123
+ )?;
124
+ Ok(())
125
+ }
126
+
127
+ /// Point markers have no fade: close/clear must remove them and reap the helper.
128
+ pub fn stop(mut self) {
129
+ self.input.take();
130
+ if let Some(mut child) = self.child.take() {
131
+ let _ = child.kill();
132
+ let _ = child.wait();
133
+ }
134
+ }
135
+
42
136
  #[cfg(target_os = "linux")]
43
137
  pub fn start_wayland(width: usize, height: usize) -> Result<Self> {
44
138
  let executable = std::env::current_exe()?;
@@ -69,13 +163,20 @@ impl Indicator {
69
163
  let reader = std::thread::spawn(move || {
70
164
  let mut ready = String::new();
71
165
  let result = BufReader::new(stdout).read_line(&mut ready);
72
- let _ = ready_tx.send(result.is_ok() && ready.trim() == "READY");
166
+ let _ = ready_tx.send(if result.is_ok() {
167
+ ready.trim().to_owned()
168
+ } else {
169
+ String::new()
170
+ });
73
171
  });
74
- if ready_rx.recv_timeout(std::time::Duration::from_secs(5)) != Ok(true) {
172
+ let ready = ready_rx
173
+ .recv_timeout(std::time::Duration::from_secs(5))
174
+ .unwrap_or_default();
175
+ if ready != "READY" {
75
176
  let _ = child.kill();
76
177
  let _ = child.wait();
77
178
  let _ = reader.join();
78
- anyhow::bail!("agent indicator did not open its overlay within 5s");
179
+ anyhow::bail!("agent indicator did not open its overlay within 5s: {ready}");
79
180
  }
80
181
  let _ = reader.join();
81
182
  Ok(Self {
@@ -0,0 +1,91 @@
1
+ use anyhow::{bail, Result};
2
+
3
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
4
+ pub enum Button {
5
+ Left,
6
+ Middle,
7
+ Right,
8
+ }
9
+
10
+ impl Button {
11
+ pub fn from_number(number: i64) -> Self {
12
+ match number {
13
+ 2 => Self::Middle,
14
+ 3 => Self::Right,
15
+ _ => Self::Left,
16
+ }
17
+ }
18
+ }
19
+
20
+ #[derive(Debug, Default, Clone, PartialEq, Eq)]
21
+ pub struct HeldState {
22
+ keys: Vec<i16>,
23
+ buttons: Vec<Button>,
24
+ }
25
+
26
+ impl HeldState {
27
+ pub fn key(&mut self, code: i16, down: bool) {
28
+ if down {
29
+ if !self.keys.contains(&code) {
30
+ self.keys.push(code);
31
+ }
32
+ } else {
33
+ self.keys.retain(|held| *held != code);
34
+ }
35
+ }
36
+
37
+ pub fn button(&mut self, button: Button, down: bool) {
38
+ if down {
39
+ if !self.buttons.contains(&button) {
40
+ self.buttons.push(button);
41
+ }
42
+ } else {
43
+ self.buttons.retain(|held| *held != button);
44
+ }
45
+ }
46
+
47
+ pub fn release_plan(&mut self) -> (Vec<Button>, Vec<i16>) {
48
+ (
49
+ std::mem::take(&mut self.buttons),
50
+ std::mem::take(&mut self.keys),
51
+ )
52
+ }
53
+ }
54
+
55
+ #[derive(Debug, Clone, serde::Serialize)]
56
+ pub struct InputUnavailable {
57
+ pub reason: String,
58
+ pub remedy: String,
59
+ }
60
+
61
+ pub fn unavailable_reason() -> &'static str {
62
+ "Windows input is not implemented in this build."
63
+ }
64
+ pub fn probe() -> Result<(), InputUnavailable> {
65
+ Err(InputUnavailable {
66
+ reason: unavailable_reason().into(),
67
+ remedy: "Use a future Windows input-enabled build.".into(),
68
+ })
69
+ }
70
+ // This private, unconstructible adapter cannot inject native input.
71
+ pub struct InputDevices {
72
+ _private: (),
73
+ }
74
+ impl InputDevices {
75
+ pub fn create_for_display(_width: i32, _height: i32, _display: u32) -> Result<Self> {
76
+ bail!("{}", unavailable_reason())
77
+ }
78
+ pub fn move_absolute(&mut self, _x: i64, _y: i64) {}
79
+ pub fn button(&mut self, _button: Button, _down: bool) {}
80
+ pub fn scroll(&mut self, _dx: f64, _dy: f64) {}
81
+ pub fn key(&mut self, _code: i16, _down: bool) -> Result<()> {
82
+ bail!("{}", unavailable_reason())
83
+ }
84
+ pub fn unicode_text(&mut self, _text: &str) -> Result<()> {
85
+ bail!("{}", unavailable_reason())
86
+ }
87
+ }
88
+
89
+ pub fn native_keycode(_code: i16) -> Result<i16> {
90
+ bail!("{}", unavailable_reason())
91
+ }
@@ -0,0 +1,39 @@
1
+ use anyhow::Result;
2
+
3
+ // No key mapping is advertised until the native input lane is implemented.
4
+ pub struct Keystroke {
5
+ pub code: i16,
6
+ }
7
+ impl Keystroke {
8
+ pub fn modifiers(&self) -> Vec<i16> {
9
+ Vec::new()
10
+ }
11
+ }
12
+
13
+ pub fn named_key(_name: &str) -> Option<Keystroke> {
14
+ None
15
+ }
16
+ pub fn modifier_key(_name: &str) -> Option<i16> {
17
+ None
18
+ }
19
+ pub fn keyboard_layout() -> &'static str {
20
+ "unknown"
21
+ }
22
+ pub struct LayoutNames;
23
+ impl LayoutNames {
24
+ pub fn from_environment() -> Self {
25
+ Self
26
+ }
27
+ pub fn identity(&self) -> String {
28
+ keyboard_layout().into()
29
+ }
30
+ }
31
+ pub struct Layout;
32
+ impl Layout {
33
+ pub fn from_environment() -> Result<Self> {
34
+ Ok(Self)
35
+ }
36
+ pub fn keystroke_for_char(&self, _character: char) -> Option<Keystroke> {
37
+ None
38
+ }
39
+ }
package/engine/src/mac.rs CHANGED
@@ -26,6 +26,7 @@ struct CGRect {
26
26
  type DisplayId = u32;
27
27
 
28
28
  unsafe extern "C" {
29
+ fn desklink_point_overlay_main(display_id: i32) -> i32;
29
30
  fn desklink_agent_overlay_main(display_id: i32) -> i32;
30
31
  fn desklink_image_bgra(image: *const c_void, length: *mut usize, stride: *mut usize)
31
32
  -> *mut u8;
@@ -182,6 +183,7 @@ fn capabilities() -> Value {
182
183
  "backends": ["screencapturekit"],
183
184
  "formats": ["bgra"],
184
185
  "cursor": "embedded",
186
+ "cursor_modes": ["embedded"],
185
187
  "audio": false,
186
188
  "displays": displays(),
187
189
  "grant": if capture_granted { "granted" } else { "missing-screen-recording" },
@@ -327,7 +329,7 @@ fn encode_probe() -> Result<(), String> {
327
329
  let mut encoder = crate::encoder::Encoder::new(640, 360, 1000, 30, 1)
328
330
  .map_err(|error| format!("VP9 encoder initialization failed: {error:#}"))?;
329
331
  let packet = encoder
330
- .encode(&frame, true)
332
+ .encode(&frame, true, false)
331
333
  .map_err(|error| format!("VP9 encoding failed: {error:#}"))?;
332
334
  println!(
333
335
  "{}",
@@ -463,14 +465,25 @@ fn indicator_demo(display_id: u32, path: &str) -> Result<(), String> {
463
465
  fn record_agent_overlay(path: &str) -> Result<(), String> {
464
466
  use std::io::Write;
465
467
  let display = unsafe { CGMainDisplayID() };
466
- let (w, h) = crate::convert::fit(unsafe { CGDisplayPixelsWide(display) },
467
- unsafe { CGDisplayPixelsHigh(display) }, 960, 640);
468
+ let (w, h) = crate::convert::fit(
469
+ unsafe { CGDisplayPixelsWide(display) },
470
+ unsafe { CGDisplayPixelsHigh(display) },
471
+ 960,
472
+ 640,
473
+ );
468
474
  let mut file = std::fs::File::create(path).map_err(|error| error.to_string())?;
469
475
  for _ in 0..30 {
470
476
  let (pixels, sw, sh, stride) = capture_display(display)?;
471
- let raw = crate::convert::to_bgrx(&pixels, sw, sh, stride,
472
- crate::convert::PixelFormat::Bgra, w, h)
473
- .ok_or_else(|| String::from("could not scale recording frame"))?;
477
+ let raw = crate::convert::to_bgrx(
478
+ &pixels,
479
+ sw,
480
+ sh,
481
+ stride,
482
+ crate::convert::PixelFormat::Bgra,
483
+ w,
484
+ h,
485
+ )
486
+ .ok_or_else(|| String::from("could not scale recording frame"))?;
474
487
  file.write_all(&raw).map_err(|error| error.to_string())?;
475
488
  std::thread::sleep(Duration::from_millis(100));
476
489
  }
@@ -581,7 +594,12 @@ pub fn run() -> i32 {
581
594
  let command = args.next().unwrap_or_else(|| "help".into());
582
595
  if matches!(
583
596
  command.as_str(),
584
- "serve" | "capabilities" | "capture-probe" | "indicator-demo" | "axi-record" | "setup-input"
597
+ "serve"
598
+ | "capabilities"
599
+ | "capture-probe"
600
+ | "indicator-demo"
601
+ | "axi-record"
602
+ | "setup-input"
585
603
  ) {
586
604
  #[cfg(not(desklink_macos_cli))]
587
605
  if let Err(error) = ensure_disclaimed() {
@@ -595,10 +613,14 @@ pub fn run() -> i32 {
595
613
  let engine = match std::env::current_exe() { Ok(path) => path, Err(error) => { eprintln!("engine path: {error}"); return 1; } };
596
614
  let result = std::process::Command::new("/opt/homebrew/bin/node")
597
615
  .arg(cli).arg("--bridge").args(args)
598
- .env("DESKLINK_AXI_ENGINE", engine).env("DESKLINK_MACOS", "1")
616
+ .env("DESKLINK_AXI_ENGINE", engine)
599
617
  .status();
600
618
  return match result { Ok(status) => status.code().unwrap_or(1), Err(error) => { eprintln!("axi bridge: {error}"); 1 } };
601
619
  }
620
+ "point-overlay" => {
621
+ let display = args.next().and_then(|value| value.parse().ok()).unwrap_or(0);
622
+ return unsafe { desklink_point_overlay_main(display) };
623
+ }
602
624
  "agent-overlay" => {
603
625
  let display = args.next().and_then(|value| value.parse().ok()).unwrap_or(0);
604
626
  return unsafe { desklink_agent_overlay_main(display) };
@@ -707,7 +729,10 @@ mod tests {
707
729
  assert_eq!(value["protocol"], crate::protocol::PROTOCOL_VERSION);
708
730
  assert_eq!(value["platform"], "macos");
709
731
  assert_eq!(value["capture"]["mechanism"], "screencapturekit");
710
- assert_eq!(value["encode"]["codecs"], json!(["vp9"]));
732
+ assert_eq!(value["encode"]["codecs"][0], "vp9", "VP9 is always offered");
733
+ // Every Mac this engine runs on has a hardware H.264 encoder.
734
+ assert_eq!(value["encode"]["codecs"][1], "h264");
735
+ assert_eq!(value["encode"]["h264"]["encoder"], "videotoolbox");
711
736
  assert!(value["capture"]["displays"].is_array());
712
737
  assert!(value["clipboard"]["read"].as_bool().unwrap());
713
738
  assert!(matches!(