@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.
- package/NOTICE +26 -0
- package/README.md +134 -56
- package/bin/desklink-host.mjs +13 -5
- package/dist/bridge.d.ts +1 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +12 -4
- package/dist/bridge.js.map +1 -1
- package/dist/engineProcess.d.ts +6 -1
- package/dist/engineProcess.d.ts.map +1 -1
- package/dist/engineProcess.js +35 -4
- package/dist/engineProcess.js.map +1 -1
- package/dist/protocol.d.ts +58 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/resolveEngine.d.ts +7 -1
- package/dist/resolveEngine.d.ts.map +1 -1
- package/dist/resolveEngine.js +66 -14
- package/dist/resolveEngine.js.map +1 -1
- package/docs/BOUNDARY.md +15 -7
- package/docs/PROTOCOL.md +267 -27
- package/engine/Cargo.lock +47 -2
- package/engine/Cargo.toml +18 -13
- package/engine/build.rs +86 -4
- package/engine/native/agent_overlay_mac.m +51 -4
- package/engine/native/agent_overlay_wayland.c +145 -12
- package/engine/native/agent_overlay_x11.c +72 -0
- package/engine/native/nvenc_shim.c +453 -0
- package/engine/native/vaapi_shim.c +727 -0
- package/engine/native/vpx_shim.c +54 -4
- package/engine/native/vt_shim.c +448 -0
- package/engine/src/capture.rs +167 -58
- package/engine/src/capture_mac.rs +24 -14
- package/engine/src/capture_win.rs +23 -0
- package/engine/src/clipboard_win.rs +8 -0
- package/engine/src/convert.rs +286 -61
- package/engine/src/cursor.rs +118 -0
- package/engine/src/encoder.rs +122 -13
- package/engine/src/h264.rs +188 -0
- package/engine/src/h264_nvenc.rs +308 -0
- package/engine/src/h264_openh264.rs +735 -0
- package/engine/src/h264_vaapi.rs +263 -0
- package/engine/src/h264_vt.rs +368 -0
- package/engine/src/indicator.rs +104 -3
- package/engine/src/input_win.rs +91 -0
- package/engine/src/keymap_win.rs +39 -0
- package/engine/src/mac.rs +34 -9
- package/engine/src/main.rs +186 -26
- package/engine/src/peer.rs +466 -28
- package/engine/src/portal.rs +42 -4
- package/engine/src/portal_win.rs +37 -0
- package/engine/src/protocol.rs +64 -2
- package/engine/src/session.rs +1252 -105
- package/engine/src/win.rs +110 -0
- package/engine/src/x11.rs +225 -9
- package/engine/src/x11_mac.rs +12 -0
- package/engine/vendor/libva/COPYING +19 -0
- package/engine/vendor/libva/va/va.h +5450 -0
- package/engine/vendor/libva/va/va_dec_av1.h +695 -0
- package/engine/vendor/libva/va/va_dec_hevc.h +616 -0
- package/engine/vendor/libva/va/va_dec_jpeg.h +157 -0
- package/engine/vendor/libva/va/va_dec_vp8.h +249 -0
- package/engine/vendor/libva/va/va_dec_vp9.h +312 -0
- package/engine/vendor/libva/va/va_dec_vvc.h +688 -0
- package/engine/vendor/libva/va/va_drm.h +61 -0
- package/engine/vendor/libva/va/va_enc_av1.h +1010 -0
- package/engine/vendor/libva/va/va_enc_h264.h +659 -0
- package/engine/vendor/libva/va/va_enc_hevc.h +958 -0
- package/engine/vendor/libva/va/va_enc_jpeg.h +162 -0
- package/engine/vendor/libva/va/va_enc_mpeg2.h +308 -0
- package/engine/vendor/libva/va/va_enc_vp8.h +350 -0
- package/engine/vendor/libva/va/va_enc_vp9.h +639 -0
- package/engine/vendor/libva/va/va_fei.h +170 -0
- package/engine/vendor/libva/va/va_fei_h264.h +505 -0
- package/engine/vendor/libva/va/va_fei_hevc.h +254 -0
- package/engine/vendor/libva/va/va_prot.h +525 -0
- package/engine/vendor/libva/va/va_version.h +87 -0
- package/engine/vendor/libva/va/va_vpp.h +1590 -0
- package/engine/vendor/nv-codec-headers/LICENSE +24 -0
- package/engine/vendor/nv-codec-headers/README +7 -0
- package/engine/vendor/nv-codec-headers/include/ffnvcodec/nvEncodeAPI.h +3825 -0
- package/examples/reference-client.html +1 -1
- package/package.json +4 -3
package/engine/src/portal.rs
CHANGED
|
@@ -33,6 +33,7 @@ pub struct SelectedSource {
|
|
|
33
33
|
|
|
34
34
|
pub struct PortalSession {
|
|
35
35
|
pub fd: OwnedFd,
|
|
36
|
+
pub cursor_mode: &'static str,
|
|
36
37
|
pub source: SelectedSource,
|
|
37
38
|
pub restore_token: Option<String>,
|
|
38
39
|
}
|
|
@@ -46,7 +47,10 @@ pub async fn open_display(_: Option<u32>) -> Result<PortalSession> {
|
|
|
46
47
|
anyhow::bail!("display selection is only supported on macOS")
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
pub async fn open(
|
|
50
|
+
pub async fn open(
|
|
51
|
+
restore_token: Option<&str>,
|
|
52
|
+
cursor: crate::protocol::CursorMode,
|
|
53
|
+
) -> Result<PortalSession> {
|
|
50
54
|
let proxy = Screencast::new()
|
|
51
55
|
.await
|
|
52
56
|
.context("compositor has no ScreenCast portal")?;
|
|
@@ -59,6 +63,15 @@ pub async fn open(restore_token: Option<&str>) -> Result<PortalSession> {
|
|
|
59
63
|
anyhow::bail!("ScreenCast portal advertises no monitor source");
|
|
60
64
|
}
|
|
61
65
|
|
|
66
|
+
let cursor_mode = if cursor == crate::protocol::CursorMode::Hidden {
|
|
67
|
+
let available = proxy
|
|
68
|
+
.available_cursor_modes()
|
|
69
|
+
.await
|
|
70
|
+
.context("portal did not advertise cursor modes")?;
|
|
71
|
+
choose_cursor_mode(available)?
|
|
72
|
+
} else {
|
|
73
|
+
CursorMode::Embedded
|
|
74
|
+
};
|
|
62
75
|
let session = proxy
|
|
63
76
|
.create_session()
|
|
64
77
|
.await
|
|
@@ -67,9 +80,7 @@ pub async fn open(restore_token: Option<&str>) -> Result<PortalSession> {
|
|
|
67
80
|
proxy
|
|
68
81
|
.select_sources(
|
|
69
82
|
&session,
|
|
70
|
-
|
|
71
|
-
// make precise placement impossible.
|
|
72
|
-
CursorMode::Embedded,
|
|
83
|
+
cursor_mode,
|
|
73
84
|
BitFlags::from(SourceType::Monitor),
|
|
74
85
|
false,
|
|
75
86
|
restore_token,
|
|
@@ -104,6 +115,11 @@ pub async fn open(restore_token: Option<&str>) -> Result<PortalSession> {
|
|
|
104
115
|
.context("OpenPipeWireRemote failed")?;
|
|
105
116
|
|
|
106
117
|
Ok(PortalSession {
|
|
118
|
+
cursor_mode: match cursor_mode {
|
|
119
|
+
CursorMode::Metadata => "metadata",
|
|
120
|
+
CursorMode::Hidden => "hidden",
|
|
121
|
+
_ => "embedded",
|
|
122
|
+
},
|
|
107
123
|
fd,
|
|
108
124
|
source: SelectedSource {
|
|
109
125
|
node_id: stream.pipe_wire_node_id(),
|
|
@@ -117,3 +133,25 @@ pub async fn open(restore_token: Option<&str>) -> Result<PortalSession> {
|
|
|
117
133
|
restore_token: streams.restore_token().map(str::to_owned),
|
|
118
134
|
})
|
|
119
135
|
}
|
|
136
|
+
|
|
137
|
+
fn choose_cursor_mode(available: BitFlags<CursorMode>) -> Result<CursorMode> {
|
|
138
|
+
if available.contains(CursorMode::Metadata) {
|
|
139
|
+
return Ok(CursorMode::Metadata);
|
|
140
|
+
}
|
|
141
|
+
anyhow::bail!("cursor_positions_unavailable: portal does not provide cursor metadata")
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[cfg(test)]
|
|
145
|
+
mod tests {
|
|
146
|
+
use super::*;
|
|
147
|
+
#[test]
|
|
148
|
+
fn hidden_requires_metadata_and_refuses_hidden_only_portals() {
|
|
149
|
+
assert_eq!(
|
|
150
|
+
choose_cursor_mode(CursorMode::Metadata | CursorMode::Hidden).unwrap(),
|
|
151
|
+
CursorMode::Metadata
|
|
152
|
+
);
|
|
153
|
+
let error = choose_cursor_mode(CursorMode::Hidden.into()).unwrap_err();
|
|
154
|
+
assert!(error.to_string().contains("cursor_positions_unavailable:"));
|
|
155
|
+
assert!(choose_cursor_mode(CursorMode::Embedded.into()).is_err());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
use anyhow::{bail, Result};
|
|
2
|
+
use serde_json::Value;
|
|
3
|
+
|
|
4
|
+
/// A selected display; Windows enumeration is deferred to the capture lane.
|
|
5
|
+
#[derive(Debug, Clone, serde::Serialize)]
|
|
6
|
+
pub struct SelectedSource {
|
|
7
|
+
pub node_id: u32,
|
|
8
|
+
pub width: i32,
|
|
9
|
+
pub height: i32,
|
|
10
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
11
|
+
pub position: Option<(i32, i32)>,
|
|
12
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
13
|
+
pub source_type: Option<String>,
|
|
14
|
+
pub origin_x: i32,
|
|
15
|
+
pub origin_y: i32,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
pub struct PortalSession {
|
|
19
|
+
pub source: SelectedSource,
|
|
20
|
+
pub restore_token: Option<String>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
pub async fn open(_restore_token: Option<&str>) -> Result<PortalSession> {
|
|
24
|
+
open_display_inner(None)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub async fn open_display(display_id: Option<u32>) -> Result<PortalSession> {
|
|
28
|
+
open_display_inner(display_id)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fn open_display_inner(_display_id: Option<u32>) -> Result<PortalSession> {
|
|
32
|
+
bail!("{}", crate::capture::unavailable_reason())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn displays() -> Vec<Value> {
|
|
36
|
+
Vec::new()
|
|
37
|
+
}
|
package/engine/src/protocol.rs
CHANGED
|
@@ -111,8 +111,18 @@ pub enum EncodedCodec {
|
|
|
111
111
|
H264,
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
115
|
+
#[serde(rename_all = "snake_case")]
|
|
116
|
+
pub enum CursorMode {
|
|
117
|
+
#[default]
|
|
118
|
+
Embedded,
|
|
119
|
+
Hidden,
|
|
120
|
+
}
|
|
121
|
+
|
|
114
122
|
#[derive(Debug, Deserialize)]
|
|
115
123
|
pub struct OpenParams {
|
|
124
|
+
#[serde(default)]
|
|
125
|
+
pub cursor: CursorMode,
|
|
116
126
|
/// Absent means "the portal, with the user's consent".
|
|
117
127
|
#[serde(default)]
|
|
118
128
|
pub source: Option<SourceRequest>,
|
|
@@ -139,6 +149,15 @@ pub struct OpenParams {
|
|
|
139
149
|
/// Show local, capture-excluded feedback for an agent controlling this session.
|
|
140
150
|
#[serde(default)]
|
|
141
151
|
pub agent_indicator: bool,
|
|
152
|
+
/// Keep the latest pre-encode frame for `session.frame` and emit
|
|
153
|
+
/// `session.frame.changed`. A consumer that only streams turns it off, and
|
|
154
|
+
/// the engine then spends nothing per frame on either.
|
|
155
|
+
#[serde(default = "default_local_frames")]
|
|
156
|
+
pub local_frames: bool,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
fn default_local_frames() -> bool {
|
|
160
|
+
true
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
/// The encode box when the consumer names none: a desktop's own pixels up to
|
|
@@ -153,8 +172,24 @@ fn default_max_height() -> usize {
|
|
|
153
172
|
fn default_bitrate() -> u32 {
|
|
154
173
|
0
|
|
155
174
|
}
|
|
175
|
+
/// A still desktop costs nothing at 60: frames are only produced on change,
|
|
176
|
+
/// so consumers get the smooth path without naming a rate.
|
|
156
177
|
fn default_fps() -> u32 {
|
|
157
|
-
|
|
178
|
+
60
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// A host-only visual cue; never an input message.
|
|
182
|
+
#[derive(Debug, Deserialize)]
|
|
183
|
+
#[serde(deny_unknown_fields)]
|
|
184
|
+
pub struct PointParams {
|
|
185
|
+
pub session_id: String,
|
|
186
|
+
pub x: Option<i64>,
|
|
187
|
+
pub y: Option<i64>,
|
|
188
|
+
#[serde(default)]
|
|
189
|
+
pub label: String,
|
|
190
|
+
pub timeout_ms: Option<u64>,
|
|
191
|
+
#[serde(default)]
|
|
192
|
+
pub clear: bool,
|
|
158
193
|
}
|
|
159
194
|
|
|
160
195
|
#[derive(Debug, Deserialize)]
|
|
@@ -366,6 +401,7 @@ pub enum ControlReply<'a> {
|
|
|
366
401
|
},
|
|
367
402
|
Revoked {
|
|
368
403
|
reason: &'a str,
|
|
404
|
+
code: &'a str,
|
|
369
405
|
},
|
|
370
406
|
}
|
|
371
407
|
|
|
@@ -373,6 +409,16 @@ pub enum ControlReply<'a> {
|
|
|
373
409
|
mod tests {
|
|
374
410
|
use super::*;
|
|
375
411
|
|
|
412
|
+
#[test]
|
|
413
|
+
fn cursor_option_defaults_and_refuses_unknown_modes() {
|
|
414
|
+
let default: OpenParams = serde_json::from_str("{}").unwrap();
|
|
415
|
+
assert_eq!(default.cursor, CursorMode::Embedded);
|
|
416
|
+
let hidden: OpenParams = serde_json::from_str(r#"{"cursor":"hidden"}"#).unwrap();
|
|
417
|
+
assert_eq!(hidden.cursor, CursorMode::Hidden);
|
|
418
|
+
assert!(serde_json::from_str::<OpenParams>(r#"{"cursor":"metadata"}"#).is_err());
|
|
419
|
+
assert!(serde_json::from_str::<OpenParams>(r#"{"cursor":false}"#).is_err());
|
|
420
|
+
}
|
|
421
|
+
|
|
376
422
|
#[test]
|
|
377
423
|
fn a_pointer_message_round_trips_with_its_sequence() {
|
|
378
424
|
let parsed: ControlMessage =
|
|
@@ -501,7 +547,8 @@ mod tests {
|
|
|
501
547
|
params.bitrate_kbps, 0,
|
|
502
548
|
"the engine sizes the rate to the surface"
|
|
503
549
|
);
|
|
504
|
-
assert_eq!(params.max_fps,
|
|
550
|
+
assert_eq!(params.max_fps, 60);
|
|
551
|
+
assert!(params.local_frames, "session.frame works unless turned off");
|
|
505
552
|
assert!(
|
|
506
553
|
!params.agent_indicator,
|
|
507
554
|
"human control has no overlay by default"
|
|
@@ -512,3 +559,18 @@ mod tests {
|
|
|
512
559
|
assert!(agent.agent_indicator);
|
|
513
560
|
}
|
|
514
561
|
}
|
|
562
|
+
|
|
563
|
+
/// Monotonic engine-process clock shared by local frame and cursor notices.
|
|
564
|
+
pub fn timestamp_us(at: std::time::Instant) -> u64 {
|
|
565
|
+
static EPOCH: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
|
|
566
|
+
at.saturating_duration_since(*EPOCH.get_or_init(|| at))
|
|
567
|
+
.as_micros() as u64
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
#[derive(Debug, Clone, Copy)]
|
|
571
|
+
pub struct CursorPosition {
|
|
572
|
+
pub x: i32,
|
|
573
|
+
pub y: i32,
|
|
574
|
+
pub visible: bool,
|
|
575
|
+
pub timestamp_us: u64,
|
|
576
|
+
}
|