@desklink/host 0.1.1 → 0.3.0
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 +23 -0
- package/README.md +107 -55
- package/bin/desklink-host.mjs +2 -2
- 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.map +1 -1
- package/dist/engineProcess.js +1 -0
- package/dist/engineProcess.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keeper.d.ts +52 -0
- package/dist/keeper.d.ts.map +1 -0
- package/dist/keeper.js +101 -0
- package/dist/keeper.js.map +1 -0
- package/dist/protocol.d.ts +26 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/resolveEngine.d.ts +3 -3
- package/dist/resolveEngine.d.ts.map +1 -1
- package/dist/resolveEngine.js +24 -11
- package/dist/resolveEngine.js.map +1 -1
- package/docs/BOUNDARY.md +20 -8
- package/docs/PROTOCOL.md +347 -24
- package/engine/Cargo.lock +47 -1
- package/engine/Cargo.toml +13 -2
- package/engine/build.rs +28 -2
- 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 +75 -55
- package/engine/src/capture_mac.rs +24 -14
- package/engine/src/convert.rs +286 -61
- 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/keeper.rs +622 -0
- package/engine/src/mac.rs +9 -6
- package/engine/src/main.rs +64 -4
- package/engine/src/peer.rs +887 -42
- package/engine/src/protocol.rs +153 -4
- package/engine/src/session.rs +1547 -137
- package/engine/src/x11.rs +210 -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 +3 -2
package/engine/src/x11.rs
CHANGED
|
@@ -13,9 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
use crate::convert::{fit, to_i420, I420};
|
|
15
15
|
use anyhow::{Context, Result};
|
|
16
|
-
use
|
|
16
|
+
use std::os::unix::io::{AsFd, AsRawFd};
|
|
17
|
+
use x11rb::connection::{Connection, RequestConnection};
|
|
18
|
+
use x11rb::protocol::damage::ConnectionExt as _;
|
|
19
|
+
use x11rb::protocol::shm::ConnectionExt as _;
|
|
17
20
|
use x11rb::protocol::xproto::{ConnectionExt as _, ImageFormat, Screen};
|
|
18
21
|
use x11rb::protocol::xtest::ConnectionExt as _;
|
|
22
|
+
use x11rb::protocol::Event;
|
|
19
23
|
use x11rb::rust_connection::RustConnection;
|
|
20
24
|
|
|
21
25
|
/// X key codes are evdev codes offset by 8 on every evdev-backed server,
|
|
@@ -41,6 +45,35 @@ pub struct X11Desktop {
|
|
|
41
45
|
/// Bytes per pixel the server reports; 4 for the 24/32-bit TrueColor that
|
|
42
46
|
/// every normal server uses, and the only depth this backend reads.
|
|
43
47
|
depth: u8,
|
|
48
|
+
/// A reused server-allocated MIT-SHM segment. Absent on a server without
|
|
49
|
+
/// MIT-SHM (remote X), where grabs fall back to socket GetImage.
|
|
50
|
+
shm: Option<ShmSegment>,
|
|
51
|
+
/// An XDamage object watching the root. Absent where the server lacks the
|
|
52
|
+
/// extension, where the capture loop falls back to pixel hashing.
|
|
53
|
+
damage: Option<u32>,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// A server-allocated MIT-SHM segment, mmapped into this process and reused
|
|
57
|
+
/// for every grab. The server frees its side when this connection closes, so
|
|
58
|
+
/// dropping the mapping and the fd here is the whole cleanup.
|
|
59
|
+
struct ShmSegment {
|
|
60
|
+
seg: u32,
|
|
61
|
+
// Held open for the mapping's lifetime and closed on drop; never read.
|
|
62
|
+
#[allow(dead_code)]
|
|
63
|
+
fd: std::os::unix::io::OwnedFd,
|
|
64
|
+
ptr: *mut u8,
|
|
65
|
+
len: usize,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Only touched through `&mut X11Desktop`, itself always behind a Mutex.
|
|
69
|
+
unsafe impl Send for ShmSegment {}
|
|
70
|
+
|
|
71
|
+
impl Drop for ShmSegment {
|
|
72
|
+
fn drop(&mut self) {
|
|
73
|
+
unsafe {
|
|
74
|
+
libc::munmap(self.ptr as *mut libc::c_void, self.len);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
44
77
|
}
|
|
45
78
|
|
|
46
79
|
impl X11Desktop {
|
|
@@ -69,10 +102,117 @@ impl X11Desktop {
|
|
|
69
102
|
width: screen.width_in_pixels as usize,
|
|
70
103
|
height: screen.height_in_pixels as usize,
|
|
71
104
|
depth: screen.root_depth,
|
|
105
|
+
shm: Self::setup_shm(
|
|
106
|
+
&connection,
|
|
107
|
+
screen.width_in_pixels as usize,
|
|
108
|
+
screen.height_in_pixels as usize,
|
|
109
|
+
),
|
|
110
|
+
damage: Self::setup_damage(&connection, screen.root),
|
|
72
111
|
connection,
|
|
73
112
|
})
|
|
74
113
|
}
|
|
75
114
|
|
|
115
|
+
/// Allocate one MIT-SHM segment for the full root and map it here. Any
|
|
116
|
+
/// failure means "no SHM on this server", never an error: the caller
|
|
117
|
+
/// falls back to socket GetImage and reports it via [`Self::capture_path`].
|
|
118
|
+
fn setup_shm(connection: &RustConnection, width: usize, height: usize) -> Option<ShmSegment> {
|
|
119
|
+
let len = width.checked_mul(height)?.checked_mul(4)?;
|
|
120
|
+
let size: u32 = len.try_into().ok()?;
|
|
121
|
+
if size == 0 {
|
|
122
|
+
return None;
|
|
123
|
+
}
|
|
124
|
+
connection
|
|
125
|
+
.extension_information(x11rb::protocol::shm::X11_EXTENSION_NAME)
|
|
126
|
+
.ok()??;
|
|
127
|
+
let reply = connection.shm_query_version().ok()?.reply().ok()?;
|
|
128
|
+
let _ = (reply.major_version, reply.minor_version);
|
|
129
|
+
let seg = connection.generate_id().ok()?;
|
|
130
|
+
let reply = connection
|
|
131
|
+
.shm_create_segment(seg, size, false)
|
|
132
|
+
.ok()?
|
|
133
|
+
.reply()
|
|
134
|
+
.ok()?;
|
|
135
|
+
let ptr = unsafe {
|
|
136
|
+
libc::mmap(
|
|
137
|
+
std::ptr::null_mut(),
|
|
138
|
+
len,
|
|
139
|
+
libc::PROT_READ | libc::PROT_WRITE,
|
|
140
|
+
libc::MAP_SHARED,
|
|
141
|
+
reply.shm_fd.as_fd().as_raw_fd(),
|
|
142
|
+
0,
|
|
143
|
+
)
|
|
144
|
+
};
|
|
145
|
+
if ptr == libc::MAP_FAILED {
|
|
146
|
+
return None;
|
|
147
|
+
}
|
|
148
|
+
Some(ShmSegment {
|
|
149
|
+
seg,
|
|
150
|
+
fd: reply.shm_fd,
|
|
151
|
+
ptr: ptr as *mut u8,
|
|
152
|
+
len,
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Watch the root for changes at NON_EMPTY level: one event per change
|
|
157
|
+
/// batch, re-armed by [`Self::take_damage`]. Absent where unsupported.
|
|
158
|
+
fn setup_damage(connection: &RustConnection, root: u32) -> Option<u32> {
|
|
159
|
+
use x11rb::protocol::damage::ReportLevel;
|
|
160
|
+
let ext = connection
|
|
161
|
+
.extension_information(x11rb::protocol::damage::X11_EXTENSION_NAME)
|
|
162
|
+
.ok()?;
|
|
163
|
+
ext?;
|
|
164
|
+
connection.damage_query_version(1, 1).ok()?.reply().ok()?;
|
|
165
|
+
let damage = connection.generate_id().ok()?;
|
|
166
|
+
connection
|
|
167
|
+
.damage_create(damage, root, ReportLevel::NON_EMPTY)
|
|
168
|
+
.ok()?
|
|
169
|
+
.check()
|
|
170
|
+
.ok()?;
|
|
171
|
+
connection.flush().ok()?;
|
|
172
|
+
Some(damage)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Which grab path is in use: `"shm"`, or `"get_image"` on a server
|
|
176
|
+
/// without MIT-SHM. Labs cite this so a number is never ambiguous.
|
|
177
|
+
pub fn capture_path(&self) -> &'static str {
|
|
178
|
+
if self.shm.is_some() {
|
|
179
|
+
"shm"
|
|
180
|
+
} else {
|
|
181
|
+
"get_image"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Whether XDamage watches the root. When false the capture loop keeps
|
|
186
|
+
/// the old pixel-hash change detection.
|
|
187
|
+
pub fn damage_armed(&self) -> bool {
|
|
188
|
+
self.damage.is_some()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Drain queued events and report whether the root changed since the last
|
|
192
|
+
/// call. Without Damage this always returns true and the caller hashes.
|
|
193
|
+
pub fn take_damage(&self) -> bool {
|
|
194
|
+
let damage = match self.damage {
|
|
195
|
+
Some(damage) => damage,
|
|
196
|
+
None => return true,
|
|
197
|
+
};
|
|
198
|
+
let mut changed = false;
|
|
199
|
+
loop {
|
|
200
|
+
match self.connection.poll_for_event() {
|
|
201
|
+
Ok(Some(Event::DamageNotify(_))) => changed = true,
|
|
202
|
+
Ok(Some(_)) => {}
|
|
203
|
+
Ok(None) => break,
|
|
204
|
+
// Let the next grab surface connection trouble as an error.
|
|
205
|
+
Err(_) => return true,
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if changed {
|
|
209
|
+
// Clear the pending region so the next change notifies again.
|
|
210
|
+
let _ = self.connection.damage_subtract(damage, 0u32, 0u32);
|
|
211
|
+
let _ = self.connection.flush();
|
|
212
|
+
}
|
|
213
|
+
changed
|
|
214
|
+
}
|
|
215
|
+
|
|
76
216
|
pub fn screen_size(&self) -> (i32, i32) {
|
|
77
217
|
(self.width as i32, self.height as i32)
|
|
78
218
|
}
|
|
@@ -87,11 +227,25 @@ impl X11Desktop {
|
|
|
87
227
|
.map(|(frame, _)| frame)
|
|
88
228
|
}
|
|
89
229
|
|
|
230
|
+
/// The frame and the root image it came from: `width * 4`-byte BGRX rows at
|
|
231
|
+
/// the screen's own size, which a caller that keeps pre-encode pixels
|
|
232
|
+
/// samples into the frame's geometry with `convert::to_bgrx`.
|
|
90
233
|
pub fn capture_with_pixels(
|
|
91
234
|
&mut self,
|
|
92
235
|
max_width: usize,
|
|
93
236
|
max_height: usize,
|
|
94
237
|
) -> Result<(I420, Vec<u8>)> {
|
|
238
|
+
// The server's depth is the only field that can tell us how the pixel is
|
|
239
|
+
// packed; the engine assumes the byte order every TrueColor server uses.
|
|
240
|
+
let _ = self.depth;
|
|
241
|
+
let stride = self.width * 4;
|
|
242
|
+
// Prefer MIT-SHM. A segment that worked at connect can still break
|
|
243
|
+
// later (revoked by the server); that degrades to the socket path,
|
|
244
|
+
// which keeps capturing, instead of stalling on failing grabs.
|
|
245
|
+
if let Ok(output) = self.capture_shm(stride, max_width, max_height) {
|
|
246
|
+
return Ok(output);
|
|
247
|
+
}
|
|
248
|
+
self.shm = None;
|
|
95
249
|
let image = self
|
|
96
250
|
.connection
|
|
97
251
|
.get_image(
|
|
@@ -106,7 +260,6 @@ impl X11Desktop {
|
|
|
106
260
|
.context("X11 GetImage failed")?
|
|
107
261
|
.reply()
|
|
108
262
|
.context("X11 GetImage returned no reply")?;
|
|
109
|
-
let stride = self.width * 4;
|
|
110
263
|
if image.data.len() < stride * self.height {
|
|
111
264
|
anyhow::bail!(
|
|
112
265
|
"X11 returned {} bytes for a {}x{} screen",
|
|
@@ -116,9 +269,6 @@ impl X11Desktop {
|
|
|
116
269
|
);
|
|
117
270
|
}
|
|
118
271
|
let (width, height) = fit(self.width, self.height, max_width, max_height);
|
|
119
|
-
// The server's depth is the only field that can tell us how the pixel is
|
|
120
|
-
// packed; the engine assumes the byte order every TrueColor server uses.
|
|
121
|
-
let _ = self.depth;
|
|
122
272
|
let frame = to_i420(
|
|
123
273
|
&image.data,
|
|
124
274
|
self.width,
|
|
@@ -129,8 +279,59 @@ impl X11Desktop {
|
|
|
129
279
|
height,
|
|
130
280
|
)
|
|
131
281
|
.context("the captured X11 pixels are not a format this engine can read")?;
|
|
132
|
-
|
|
133
|
-
|
|
282
|
+
Ok((frame, image.data))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// One MIT-SHM grab into the reused segment. The caller tears the
|
|
286
|
+
/// segment down on any error and falls back to the socket path.
|
|
287
|
+
fn capture_shm(
|
|
288
|
+
&self,
|
|
289
|
+
stride: usize,
|
|
290
|
+
max_width: usize,
|
|
291
|
+
max_height: usize,
|
|
292
|
+
) -> Result<(I420, Vec<u8>)> {
|
|
293
|
+
let (seg, ptr, len) = match &self.shm {
|
|
294
|
+
Some(shm) => (shm.seg, shm.ptr, shm.len),
|
|
295
|
+
None => anyhow::bail!("no MIT-SHM segment"),
|
|
296
|
+
};
|
|
297
|
+
// MIT-SHM: the server writes the root straight into our mapping;
|
|
298
|
+
// the reply only signals completion. The segment is reused, so
|
|
299
|
+
// copy out through the conversion below before the next grab.
|
|
300
|
+
self.connection
|
|
301
|
+
.shm_get_image(
|
|
302
|
+
self.root,
|
|
303
|
+
0,
|
|
304
|
+
0,
|
|
305
|
+
self.width as u16,
|
|
306
|
+
self.height as u16,
|
|
307
|
+
u32::MAX,
|
|
308
|
+
u8::from(ImageFormat::Z_PIXMAP),
|
|
309
|
+
seg,
|
|
310
|
+
0,
|
|
311
|
+
)
|
|
312
|
+
.context("X11 ShmGetImage failed")?
|
|
313
|
+
.reply()
|
|
314
|
+
.context("X11 ShmGetImage returned no reply")?;
|
|
315
|
+
let pixels = unsafe { std::slice::from_raw_parts(ptr as *const u8, len) };
|
|
316
|
+
// Snapshot the length: the caller drops the mapping on error, so no
|
|
317
|
+
// slice may be alive past this return.
|
|
318
|
+
let byte_len = pixels.len();
|
|
319
|
+
if byte_len < stride * self.height {
|
|
320
|
+
anyhow::bail!(
|
|
321
|
+
"X11 shared memory holds {} bytes for a {}x{} screen",
|
|
322
|
+
byte_len,
|
|
323
|
+
self.width,
|
|
324
|
+
self.height
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
// Copy out of the reused segment before the next grab overwrites
|
|
328
|
+
// it; the caller samples these full-screen rows into the frame's
|
|
329
|
+
// geometry. This copy is inherent to SHM; the socket arm below
|
|
330
|
+
// moves its reply instead.
|
|
331
|
+
let owned = pixels.to_vec();
|
|
332
|
+
let (width, height) = fit(self.width, self.height, max_width, max_height);
|
|
333
|
+
let frame = to_i420(
|
|
334
|
+
&owned,
|
|
134
335
|
self.width,
|
|
135
336
|
self.height,
|
|
136
337
|
stride,
|
|
@@ -138,8 +339,8 @@ impl X11Desktop {
|
|
|
138
339
|
width,
|
|
139
340
|
height,
|
|
140
341
|
)
|
|
141
|
-
.context("the captured X11 pixels
|
|
142
|
-
Ok((frame,
|
|
342
|
+
.context("the captured X11 pixels are not a format this engine can read")?;
|
|
343
|
+
Ok((frame, owned))
|
|
143
344
|
}
|
|
144
345
|
|
|
145
346
|
fn flush(&self) -> Result<()> {
|
package/engine/src/x11_mac.rs
CHANGED
|
@@ -22,6 +22,18 @@ impl X11Desktop {
|
|
|
22
22
|
bail!("the X11 backend is only available on Linux")
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
pub fn capture_path(&self) -> &'static str {
|
|
26
|
+
"get_image"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
pub fn damage_armed(&self) -> bool {
|
|
30
|
+
false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
pub fn take_damage(&self) -> bool {
|
|
34
|
+
true
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
pub fn move_pointer(&mut self, _: i64, _: i64) -> Result<()> {
|
|
26
38
|
bail!("the X11 backend is only available on Linux")
|
|
27
39
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
2
|
+
copy of this software and associated documentation files (the
|
|
3
|
+
"Software"), to deal in the Software without restriction, including
|
|
4
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
5
|
+
distribute, sub license, and/or sell copies of the Software, and to
|
|
6
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
7
|
+
the following conditions:
|
|
8
|
+
|
|
9
|
+
The above copyright notice and this permission notice (including the
|
|
10
|
+
next paragraph) shall be included in all copies or substantial portions
|
|
11
|
+
of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
14
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
15
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
|
|
16
|
+
IN NO EVENT SHALL PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR
|
|
17
|
+
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
18
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
19
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|