@desklink/host 0.1.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/LICENSE +202 -0
- package/NOTICE +44 -0
- package/README.md +266 -0
- package/bin/desklink-host.mjs +113 -0
- package/dist/bridge.d.ts +57 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +208 -0
- package/dist/bridge.js.map +1 -0
- package/dist/engineProcess.d.ts +82 -0
- package/dist/engineProcess.d.ts.map +1 -0
- package/dist/engineProcess.js +243 -0
- package/dist/engineProcess.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +169 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +18 -0
- package/dist/protocol.js.map +1 -0
- package/dist/resolveEngine.d.ts +34 -0
- package/dist/resolveEngine.d.ts.map +1 -0
- package/dist/resolveEngine.js +103 -0
- package/dist/resolveEngine.js.map +1 -0
- package/docs/BOUNDARY.md +145 -0
- package/docs/PROTOCOL.md +363 -0
- package/engine/Cargo.lock +3077 -0
- package/engine/Cargo.toml +37 -0
- package/engine/build.rs +107 -0
- package/engine/native/inputtino_shim.cpp +11 -0
- package/engine/native/vpx_shim.c +184 -0
- package/engine/native/wrapper.h +7 -0
- package/engine/src/capture.rs +510 -0
- package/engine/src/clipboard.rs +132 -0
- package/engine/src/convert.rs +299 -0
- package/engine/src/encoder.rs +204 -0
- package/engine/src/input.rs +355 -0
- package/engine/src/keymap.rs +338 -0
- package/engine/src/main.rs +614 -0
- package/engine/src/peer.rs +775 -0
- package/engine/src/portal.rs +115 -0
- package/engine/src/protocol.rs +331 -0
- package/engine/src/session.rs +2159 -0
- package/engine/src/x11.rs +244 -0
- package/engine/vendor/inputtino/.clang-format +166 -0
- package/engine/vendor/inputtino/.clang-tidy +30 -0
- package/engine/vendor/inputtino/.dockerignore +11 -0
- package/engine/vendor/inputtino/.github/workflows/mirror-repo.yml +14 -0
- package/engine/vendor/inputtino/.github/workflows/python-lint-format-test-typecheck.yml +124 -0
- package/engine/vendor/inputtino/.github/workflows/python-test-install.yaml +49 -0
- package/engine/vendor/inputtino/.github/workflows/rust.yml +40 -0
- package/engine/vendor/inputtino/CMakeLists.txt +187 -0
- package/engine/vendor/inputtino/LICENSE +21 -0
- package/engine/vendor/inputtino/README.md +110 -0
- package/engine/vendor/inputtino/bindings/python/.devcontainer/Dockerfile +23 -0
- package/engine/vendor/inputtino/bindings/python/.devcontainer/devcontainer.json +28 -0
- package/engine/vendor/inputtino/bindings/python/.python-version +1 -0
- package/engine/vendor/inputtino/bindings/python/CMakeLists.txt +23 -0
- package/engine/vendor/inputtino/bindings/python/CPPLINT.cfg +2 -0
- package/engine/vendor/inputtino/bindings/python/Makefile +49 -0
- package/engine/vendor/inputtino/bindings/python/README.md +215 -0
- package/engine/vendor/inputtino/bindings/python/pyproject.toml +178 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/__init__.py +43 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/_core.pyi +533 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/base.py +63 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/joypad.py +248 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/keyboard.py +256 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/mouse.py +106 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/pentablet.py +73 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/py.typed +0 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/touchscreen.py +61 -0
- package/engine/vendor/inputtino/bindings/python/src/inputtino/trackpad.py +74 -0
- package/engine/vendor/inputtino/bindings/python/src/main.cpp +217 -0
- package/engine/vendor/inputtino/bindings/python/tests/__init__.py +0 -0
- package/engine/vendor/inputtino/bindings/python/tests/helpers.py +3 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_base.py +60 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_inputtino.py +5 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_joypad.py +332 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_keyboard.py +164 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_mouse.py +185 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_pentablet.py +198 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_touchscreen.py +144 -0
- package/engine/vendor/inputtino/bindings/python/tests/test_trackpad.py +146 -0
- package/engine/vendor/inputtino/bindings/python/uv.lock +390 -0
- package/engine/vendor/inputtino/bindings/rust/Cargo.toml +6 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/.dockerignore +1 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/Cargo.toml +18 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/examples/keyboard.rs +33 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/examples/mouse.rs +17 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/common.rs +116 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/error.rs +17 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad.rs +74 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_nintendo.rs +135 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_ps5.rs +280 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/joypad_xbox.rs +134 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/keyboard.rs +76 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/lib.rs +32 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/src/mouse.rs +178 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/common.rs +38 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/joypads.rs +150 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/keyboard.rs +73 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino/tests/mouse.rs +139 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/.dockerignore +1 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/Cargo.toml +12 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/build.rs +48 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/src/lib.rs +5 -0
- package/engine/vendor/inputtino/bindings/rust/inputtino-sys/wrapper.h +1 -0
- package/engine/vendor/inputtino/cmake/utils.cmake +9 -0
- package/engine/vendor/inputtino/docker/Dockerfile +70 -0
- package/engine/vendor/inputtino/include/inputtino/input.h +324 -0
- package/engine/vendor/inputtino/include/inputtino/input.hpp +466 -0
- package/engine/vendor/inputtino/include/inputtino/result.hpp +37 -0
- package/engine/vendor/inputtino/share/pkgconfig/libinputtino.pc.in +17 -0
- package/engine/vendor/inputtino/src/c-bindings/helpers.hpp +19 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_ps5.cpp +115 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_switch.cpp +63 -0
- package/engine/vendor/inputtino/src/c-bindings/joypad_xone.cpp +62 -0
- package/engine/vendor/inputtino/src/c-bindings/keyboard.cpp +42 -0
- package/engine/vendor/inputtino/src/c-bindings/mouse.cpp +67 -0
- package/engine/vendor/inputtino/src/c-bindings/pen_tablet.cpp +50 -0
- package/engine/vendor/inputtino/src/c-bindings/touchscreen.cpp +43 -0
- package/engine/vendor/inputtino/src/c-bindings/trackpad.cpp +49 -0
- package/engine/vendor/inputtino/src/uhid/README.adoc +63 -0
- package/engine/vendor/inputtino/src/uhid/include/crc32.hpp +38 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/protected_types.hpp +36 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/ps5.hpp +550 -0
- package/engine/vendor/inputtino/src/uhid/include/uhid/uhid.hpp +168 -0
- package/engine/vendor/inputtino/src/uhid/joypad_ps5.cpp +580 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/keyboard.hpp +84 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_ps5_types.hpp +7 -0
- package/engine/vendor/inputtino/src/uinput/include/inputtino/protected_types.hpp +110 -0
- package/engine/vendor/inputtino/src/uinput/joypad_nintendo.cpp +190 -0
- package/engine/vendor/inputtino/src/uinput/joypad_ps.cpp +203 -0
- package/engine/vendor/inputtino/src/uinput/joypad_utils.hpp +246 -0
- package/engine/vendor/inputtino/src/uinput/joypad_xbox.cpp +190 -0
- package/engine/vendor/inputtino/src/uinput/keyboard.cpp +120 -0
- package/engine/vendor/inputtino/src/uinput/mouse.cpp +199 -0
- package/engine/vendor/inputtino/src/uinput/pentablet.cpp +162 -0
- package/engine/vendor/inputtino/src/uinput/touchscreen.cpp +140 -0
- package/engine/vendor/inputtino/src/uinput/trackpad.cpp +192 -0
- package/examples/reference-client.html +428 -0
- package/package.json +38 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
//! Portal (XDG Desktop Portal ScreenCast) session negotiation.
|
|
2
|
+
//!
|
|
3
|
+
//! The portal is the only capture path this engine asks the compositor for: it
|
|
4
|
+
//! is what carries the user's consent, works across Wayland compositors, and
|
|
5
|
+
//! hands back a PipeWire remote that [`crate::capture`] consumes. No
|
|
6
|
+
//! compositor-private protocol is used.
|
|
7
|
+
|
|
8
|
+
use anyhow::{Context, Result};
|
|
9
|
+
use ashpd::desktop::screencast::{CursorMode, Screencast, SourceType};
|
|
10
|
+
use ashpd::desktop::PersistMode;
|
|
11
|
+
use ashpd::enumflags2::BitFlags;
|
|
12
|
+
use std::os::fd::OwnedFd;
|
|
13
|
+
|
|
14
|
+
/// What the portal gave us: a PipeWire remote plus the identity of the selected
|
|
15
|
+
/// source. `width`/`height` are the source's logical size in the compositor's
|
|
16
|
+
/// own scale; `position` is its origin in the desktop layout.
|
|
17
|
+
#[derive(Debug, Clone, serde::Serialize)]
|
|
18
|
+
pub struct SelectedSource {
|
|
19
|
+
pub node_id: u32,
|
|
20
|
+
pub width: i32,
|
|
21
|
+
pub height: i32,
|
|
22
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
23
|
+
pub position: Option<(i32, i32)>,
|
|
24
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
25
|
+
pub source_type: Option<String>,
|
|
26
|
+
/// Absolute origin of the selected source in the desktop layout. Reported
|
|
27
|
+
/// for diagnostics and for geometry consumers only: input is applied in the
|
|
28
|
+
/// source's own pixels, and nothing here is added to a converted
|
|
29
|
+
/// coordinate.
|
|
30
|
+
pub origin_x: i32,
|
|
31
|
+
pub origin_y: i32,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub struct PortalSession {
|
|
35
|
+
pub fd: OwnedFd,
|
|
36
|
+
pub source: SelectedSource,
|
|
37
|
+
pub restore_token: Option<String>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Ask the portal for a screen cast session.
|
|
41
|
+
///
|
|
42
|
+
/// `restore_token` is a token from an earlier session; supplying it lets a
|
|
43
|
+
/// backend skip the picker when its policy allows it. The returned token must be
|
|
44
|
+
/// persisted by the consumer and is single-use.
|
|
45
|
+
pub async fn open(restore_token: Option<&str>) -> Result<PortalSession> {
|
|
46
|
+
let proxy = Screencast::new()
|
|
47
|
+
.await
|
|
48
|
+
.context("compositor has no ScreenCast portal")?;
|
|
49
|
+
|
|
50
|
+
if proxy
|
|
51
|
+
.available_source_types()
|
|
52
|
+
.await
|
|
53
|
+
.is_ok_and(|types| !types.contains(SourceType::Monitor))
|
|
54
|
+
{
|
|
55
|
+
anyhow::bail!("ScreenCast portal advertises no monitor source");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let session = proxy
|
|
59
|
+
.create_session()
|
|
60
|
+
.await
|
|
61
|
+
.context("ScreenCast.CreateSession failed")?;
|
|
62
|
+
|
|
63
|
+
proxy
|
|
64
|
+
.select_sources(
|
|
65
|
+
&session,
|
|
66
|
+
// The cursor is part of the picture the user drives; hiding it would
|
|
67
|
+
// make precise placement impossible.
|
|
68
|
+
CursorMode::Embedded,
|
|
69
|
+
BitFlags::from(SourceType::Monitor),
|
|
70
|
+
false,
|
|
71
|
+
restore_token,
|
|
72
|
+
// Request a durable grant, not one tied to this engine process.
|
|
73
|
+
// The portal still owns consent and may decline persistence.
|
|
74
|
+
PersistMode::ExplicitlyRevoked,
|
|
75
|
+
)
|
|
76
|
+
.await
|
|
77
|
+
.context("ScreenCast.SelectSources failed")?
|
|
78
|
+
.response()
|
|
79
|
+
.context("ScreenCast.SelectSources was cancelled")?;
|
|
80
|
+
|
|
81
|
+
// `Start` is what the compositor uses to present its consent UI.
|
|
82
|
+
let streams = proxy
|
|
83
|
+
.start(&session, None)
|
|
84
|
+
.await
|
|
85
|
+
.context("ScreenCast.Start failed")?
|
|
86
|
+
.response()
|
|
87
|
+
.context("screen capture was not granted")?;
|
|
88
|
+
|
|
89
|
+
let stream = streams
|
|
90
|
+
.streams()
|
|
91
|
+
.first()
|
|
92
|
+
.context("portal returned no capture stream")?;
|
|
93
|
+
|
|
94
|
+
let (width, height) = stream.size().context("portal stream has no logical size")?;
|
|
95
|
+
let position = stream.position();
|
|
96
|
+
|
|
97
|
+
let fd = proxy
|
|
98
|
+
.open_pipe_wire_remote(&session)
|
|
99
|
+
.await
|
|
100
|
+
.context("OpenPipeWireRemote failed")?;
|
|
101
|
+
|
|
102
|
+
Ok(PortalSession {
|
|
103
|
+
fd,
|
|
104
|
+
source: SelectedSource {
|
|
105
|
+
node_id: stream.pipe_wire_node_id(),
|
|
106
|
+
width,
|
|
107
|
+
height,
|
|
108
|
+
position,
|
|
109
|
+
source_type: stream.source_type().map(|t| format!("{t:?}")),
|
|
110
|
+
origin_x: position.map(|(x, _)| x).unwrap_or(0),
|
|
111
|
+
origin_y: position.map(|(_, y)| y).unwrap_or(0),
|
|
112
|
+
},
|
|
113
|
+
restore_token: streams.restore_token().map(str::to_owned),
|
|
114
|
+
})
|
|
115
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
//! Wire types for the local control protocol and for the session's control
|
|
2
|
+
//! channel. Documented in `docs/PROTOCOL.md`; this module is the definition.
|
|
3
|
+
|
|
4
|
+
use serde::{Deserialize, Serialize};
|
|
5
|
+
|
|
6
|
+
pub const PROTOCOL_VERSION: u32 = 2;
|
|
7
|
+
|
|
8
|
+
/// A request from the consumer to the engine.
|
|
9
|
+
#[derive(Debug, Deserialize)]
|
|
10
|
+
pub struct Request {
|
|
11
|
+
#[serde(default)]
|
|
12
|
+
pub id: Option<u64>,
|
|
13
|
+
pub method: String,
|
|
14
|
+
#[serde(default)]
|
|
15
|
+
pub params: serde_json::Value,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/// A reply to one request. Exactly one of `result`/`error` is set.
|
|
19
|
+
#[derive(Debug, Serialize)]
|
|
20
|
+
pub struct Response {
|
|
21
|
+
pub id: u64,
|
|
22
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
23
|
+
pub result: Option<serde_json::Value>,
|
|
24
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
25
|
+
pub error: Option<ErrorBody>,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[derive(Debug, Serialize)]
|
|
29
|
+
pub struct ErrorBody {
|
|
30
|
+
pub code: String,
|
|
31
|
+
pub message: String,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl ErrorBody {
|
|
35
|
+
pub fn new(code: &str, message: impl Into<String>) -> Self {
|
|
36
|
+
Self {
|
|
37
|
+
code: code.to_owned(),
|
|
38
|
+
message: message.into(),
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// A notification the engine sends on its own.
|
|
44
|
+
#[derive(Debug, Serialize)]
|
|
45
|
+
pub struct Event {
|
|
46
|
+
pub event: String,
|
|
47
|
+
pub params: serde_json::Value,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
#[derive(Debug, Default, Deserialize)]
|
|
51
|
+
pub struct HelloParams {
|
|
52
|
+
#[serde(default)]
|
|
53
|
+
pub protocol: u32,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
57
|
+
#[serde(rename_all = "snake_case")]
|
|
58
|
+
pub enum Permission {
|
|
59
|
+
View,
|
|
60
|
+
Control,
|
|
61
|
+
Clipboard,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
65
|
+
pub struct IceServerParam {
|
|
66
|
+
#[serde(default)]
|
|
67
|
+
pub urls: Vec<String>,
|
|
68
|
+
#[serde(default)]
|
|
69
|
+
pub username: Option<String>,
|
|
70
|
+
#[serde(default)]
|
|
71
|
+
pub credential: Option<String>,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Which desktop to capture.
|
|
75
|
+
///
|
|
76
|
+
/// The portal is the default because on a Wayland desktop it is what carries the
|
|
77
|
+
/// user's consent. An explicit X display is the other supported backend: a
|
|
78
|
+
/// machine with no working screen-cast portal, a headless X server, or a remote
|
|
79
|
+
/// X session. Asking for one is a deployment choice, not a test switch.
|
|
80
|
+
#[derive(Debug, Clone, Deserialize)]
|
|
81
|
+
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
82
|
+
pub enum SourceRequest {
|
|
83
|
+
Portal,
|
|
84
|
+
X11 {
|
|
85
|
+
#[serde(default)]
|
|
86
|
+
display: Option<String>,
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#[derive(Debug, Deserialize)]
|
|
91
|
+
pub struct OpenParams {
|
|
92
|
+
/// Absent means "the portal, with the user's consent".
|
|
93
|
+
#[serde(default)]
|
|
94
|
+
pub source: Option<SourceRequest>,
|
|
95
|
+
#[serde(default)]
|
|
96
|
+
pub permissions: Vec<Permission>,
|
|
97
|
+
#[serde(default = "default_max_width")]
|
|
98
|
+
pub max_width: usize,
|
|
99
|
+
#[serde(default = "default_max_height")]
|
|
100
|
+
pub max_height: usize,
|
|
101
|
+
#[serde(default = "default_bitrate")]
|
|
102
|
+
pub bitrate_kbps: u32,
|
|
103
|
+
#[serde(default = "default_fps")]
|
|
104
|
+
pub max_fps: u32,
|
|
105
|
+
#[serde(default)]
|
|
106
|
+
pub ice_servers: Vec<IceServerParam>,
|
|
107
|
+
#[serde(default)]
|
|
108
|
+
pub restore_token: Option<String>,
|
|
109
|
+
#[serde(default)]
|
|
110
|
+
pub ttl_seconds: Option<u64>,
|
|
111
|
+
/// Also listen for ICE over TCP on this computer's loopback, for a client
|
|
112
|
+
/// whose only way here is a local forward (an SSH tunnel).
|
|
113
|
+
#[serde(default)]
|
|
114
|
+
pub loopback_tcp: bool,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// The encode box when the consumer names none: a desktop's own pixels up to
|
|
118
|
+
/// 4K, because a phone zooms into the picture and text has to survive that.
|
|
119
|
+
fn default_max_width() -> usize {
|
|
120
|
+
3840
|
|
121
|
+
}
|
|
122
|
+
fn default_max_height() -> usize {
|
|
123
|
+
2160
|
|
124
|
+
}
|
|
125
|
+
/// Zero asks the engine to size the rate to the encoded surface.
|
|
126
|
+
fn default_bitrate() -> u32 {
|
|
127
|
+
0
|
|
128
|
+
}
|
|
129
|
+
fn default_fps() -> u32 {
|
|
130
|
+
30
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#[derive(Debug, Deserialize)]
|
|
134
|
+
pub struct SessionRef {
|
|
135
|
+
pub session_id: String,
|
|
136
|
+
#[serde(default)]
|
|
137
|
+
pub generation: Option<u64>,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[derive(Debug, Deserialize)]
|
|
141
|
+
pub struct DescriptionParams {
|
|
142
|
+
pub session_id: String,
|
|
143
|
+
#[serde(default)]
|
|
144
|
+
pub generation: Option<u64>,
|
|
145
|
+
pub description: RtcDescription,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
#[derive(Debug, Deserialize)]
|
|
149
|
+
pub struct RtcDescription {
|
|
150
|
+
#[serde(rename = "type")]
|
|
151
|
+
pub kind: String,
|
|
152
|
+
pub sdp: String,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#[derive(Debug, Deserialize)]
|
|
156
|
+
pub struct CandidateParams {
|
|
157
|
+
pub session_id: String,
|
|
158
|
+
#[serde(default)]
|
|
159
|
+
pub generation: Option<u64>,
|
|
160
|
+
pub candidate: String,
|
|
161
|
+
#[serde(default)]
|
|
162
|
+
pub sdp_mid: Option<String>,
|
|
163
|
+
#[serde(default, rename = "sdpMLineIndex", alias = "sdp_m_line_index")]
|
|
164
|
+
pub sdp_m_line_index: Option<u16>,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[derive(Debug, Deserialize)]
|
|
168
|
+
pub struct ClipboardParams {
|
|
169
|
+
pub session_id: String,
|
|
170
|
+
#[serde(default)]
|
|
171
|
+
pub mime: Option<String>,
|
|
172
|
+
#[serde(default)]
|
|
173
|
+
pub text: Option<String>,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/// Messages the client sends on the session's control channel.
|
|
177
|
+
///
|
|
178
|
+
/// `seq` is the client's own ordering. The engine refuses a repeat or a lower
|
|
179
|
+
/// value rather than re-applying an action, so a replayed frame cannot move the
|
|
180
|
+
/// pointer twice.
|
|
181
|
+
#[derive(Debug, Deserialize)]
|
|
182
|
+
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
183
|
+
pub enum ControlMessage {
|
|
184
|
+
Pointer {
|
|
185
|
+
phase: PointerPhase,
|
|
186
|
+
x: i64,
|
|
187
|
+
y: i64,
|
|
188
|
+
#[serde(default = "left_button")]
|
|
189
|
+
button: i64,
|
|
190
|
+
#[serde(default)]
|
|
191
|
+
seq: u64,
|
|
192
|
+
},
|
|
193
|
+
/// Detents; fractions scroll smoothly where the desktop supports it.
|
|
194
|
+
Wheel {
|
|
195
|
+
#[serde(default)]
|
|
196
|
+
dx: f64,
|
|
197
|
+
#[serde(default)]
|
|
198
|
+
dy: f64,
|
|
199
|
+
#[serde(default)]
|
|
200
|
+
seq: u64,
|
|
201
|
+
},
|
|
202
|
+
Key {
|
|
203
|
+
#[serde(default)]
|
|
204
|
+
name: Option<String>,
|
|
205
|
+
#[serde(default)]
|
|
206
|
+
character: Option<String>,
|
|
207
|
+
down: bool,
|
|
208
|
+
#[serde(default)]
|
|
209
|
+
modifiers: Vec<String>,
|
|
210
|
+
#[serde(default)]
|
|
211
|
+
seq: u64,
|
|
212
|
+
},
|
|
213
|
+
Text {
|
|
214
|
+
text: String,
|
|
215
|
+
#[serde(default)]
|
|
216
|
+
seq: u64,
|
|
217
|
+
},
|
|
218
|
+
ReleaseAll {
|
|
219
|
+
#[serde(default)]
|
|
220
|
+
seq: u64,
|
|
221
|
+
},
|
|
222
|
+
ClipboardRead {
|
|
223
|
+
request: String,
|
|
224
|
+
#[serde(default)]
|
|
225
|
+
seq: u64,
|
|
226
|
+
},
|
|
227
|
+
ClipboardWrite {
|
|
228
|
+
request: String,
|
|
229
|
+
text: String,
|
|
230
|
+
#[serde(default)]
|
|
231
|
+
seq: u64,
|
|
232
|
+
},
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
fn left_button() -> i64 {
|
|
236
|
+
1
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
impl ControlMessage {
|
|
240
|
+
pub fn seq(&self) -> u64 {
|
|
241
|
+
match self {
|
|
242
|
+
Self::Pointer { seq, .. }
|
|
243
|
+
| Self::Wheel { seq, .. }
|
|
244
|
+
| Self::Key { seq, .. }
|
|
245
|
+
| Self::Text { seq, .. }
|
|
246
|
+
| Self::ReleaseAll { seq }
|
|
247
|
+
| Self::ClipboardRead { seq, .. }
|
|
248
|
+
| Self::ClipboardWrite { seq, .. } => *seq,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[derive(Debug, Clone, Copy, Deserialize)]
|
|
254
|
+
#[serde(rename_all = "snake_case")]
|
|
255
|
+
pub enum PointerPhase {
|
|
256
|
+
Move,
|
|
257
|
+
Down,
|
|
258
|
+
Up,
|
|
259
|
+
Cancel,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// Messages the engine sends back on the control channel.
|
|
263
|
+
#[derive(Debug, Serialize)]
|
|
264
|
+
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
265
|
+
pub enum ControlReply<'a> {
|
|
266
|
+
Hello {
|
|
267
|
+
protocol: u32,
|
|
268
|
+
geometry: serde_json::Value,
|
|
269
|
+
},
|
|
270
|
+
Ack {
|
|
271
|
+
seq: u64,
|
|
272
|
+
},
|
|
273
|
+
Rejected {
|
|
274
|
+
seq: u64,
|
|
275
|
+
code: &'a str,
|
|
276
|
+
message: &'a str,
|
|
277
|
+
},
|
|
278
|
+
Clipboard {
|
|
279
|
+
request: &'a str,
|
|
280
|
+
text: String,
|
|
281
|
+
truncated: bool,
|
|
282
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
283
|
+
error: Option<&'a str>,
|
|
284
|
+
},
|
|
285
|
+
Revoked {
|
|
286
|
+
reason: &'a str,
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[cfg(test)]
|
|
291
|
+
mod tests {
|
|
292
|
+
use super::*;
|
|
293
|
+
|
|
294
|
+
#[test]
|
|
295
|
+
fn a_pointer_message_round_trips_with_its_sequence() {
|
|
296
|
+
let parsed: ControlMessage =
|
|
297
|
+
serde_json::from_str(r#"{"kind":"pointer","phase":"down","x":10,"y":20,"seq":3}"#)
|
|
298
|
+
.unwrap();
|
|
299
|
+
match parsed {
|
|
300
|
+
ControlMessage::Pointer {
|
|
301
|
+
phase,
|
|
302
|
+
x,
|
|
303
|
+
y,
|
|
304
|
+
seq,
|
|
305
|
+
button,
|
|
306
|
+
} => {
|
|
307
|
+
assert!(matches!(phase, PointerPhase::Down));
|
|
308
|
+
assert_eq!((x, y, seq, button), (10, 20, 3, 1));
|
|
309
|
+
}
|
|
310
|
+
other => panic!("wrong variant: {other:?}"),
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#[test]
|
|
315
|
+
fn an_unknown_action_is_refused_rather_than_ignored() {
|
|
316
|
+
assert!(serde_json::from_str::<ControlMessage>(r#"{"kind":"teleport"}"#).is_err());
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
#[test]
|
|
320
|
+
fn open_defaults_keep_the_desktops_own_pixels_up_to_4k() {
|
|
321
|
+
let params: OpenParams =
|
|
322
|
+
serde_json::from_str(r#"{"permissions":["view","control"]}"#).unwrap();
|
|
323
|
+
assert_eq!(params.max_width, 3840);
|
|
324
|
+
assert_eq!(params.max_height, 2160);
|
|
325
|
+
assert_eq!(
|
|
326
|
+
params.bitrate_kbps, 0,
|
|
327
|
+
"the engine sizes the rate to the surface"
|
|
328
|
+
);
|
|
329
|
+
assert_eq!(params.max_fps, 30);
|
|
330
|
+
}
|
|
331
|
+
}
|