@desklink/host 0.5.8 → 0.5.10
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/README.md +5 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +2 -1
- 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/protocol.d.ts +59 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/docs/PROTOCOL.md +92 -5
- package/engine/Cargo.lock +3 -1
- package/engine/Cargo.toml +7 -2
- package/engine/native/agent_overlay_wayland.c +7 -4
- package/engine/src/hyprland.rs +25 -0
- package/engine/src/indicator.rs +29 -12
- package/engine/src/input_mac.rs +433 -0
- package/engine/src/keymap_mac.rs +257 -0
- package/engine/src/mac.rs +8 -0
- package/engine/src/main.rs +111 -13
- package/engine/src/observe.rs +94 -0
- package/engine/src/peer.rs +168 -17
- package/engine/src/portal.rs +83 -1
- package/engine/src/protocol.rs +30 -1
- package/engine/src/session.rs +273 -14
- package/engine/vendor/rtc-ice/DESKLINK-PATCH.md +2 -2
- package/engine/vendor/rtc-ice/src/agent/agent_selector.rs +1 -1
- package/engine/vendor/rtc-ice/src/agent/mod.rs +1 -1
- package/engine/vendor/rtc-ice/src/dl_trace.rs +1 -1
- package/engine/vendor/rtc-ice/src/lib.rs +1 -1
- package/package.json +3 -3
package/engine/src/portal.rs
CHANGED
|
@@ -47,13 +47,20 @@ pub async fn open_display(_: Option<u32>) -> Result<PortalSession> {
|
|
|
47
47
|
anyhow::bail!("display selection is only supported on macOS")
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/// The portal service answered without a ScreenCast interface. That happens
|
|
51
|
+
/// when xdg-desktop-portal started before the compositor's portal and cached an
|
|
52
|
+
/// interface list without it; only a restart makes it look again.
|
|
53
|
+
const NO_SCREENCAST: &str = "compositor has no ScreenCast portal: xdg-desktop-portal does not export \
|
|
54
|
+
org.freedesktop.portal.ScreenCast. If it started before the compositor's portal, run \
|
|
55
|
+
`systemctl --user restart xdg-desktop-portal` once the compositor portal is running";
|
|
56
|
+
|
|
50
57
|
pub async fn open(
|
|
51
58
|
restore_token: Option<&str>,
|
|
52
59
|
cursor: crate::protocol::CursorMode,
|
|
53
60
|
) -> Result<PortalSession> {
|
|
54
61
|
let proxy = Screencast::new()
|
|
55
62
|
.await
|
|
56
|
-
.context(
|
|
63
|
+
.context(NO_SCREENCAST)?;
|
|
57
64
|
|
|
58
65
|
if proxy
|
|
59
66
|
.available_source_types()
|
|
@@ -142,3 +149,78 @@ fn choose_cursor_mode(available: BitFlags<CursorMode>) -> Result<CursorMode> {
|
|
|
142
149
|
.find(|mode| available.contains(*mode))
|
|
143
150
|
.context("cursor-unavailable: portal cannot leave the cursor out of capture")
|
|
144
151
|
}
|
|
152
|
+
|
|
153
|
+
#[cfg(all(test, target_os = "linux"))]
|
|
154
|
+
mod tests {
|
|
155
|
+
use super::*;
|
|
156
|
+
use std::process::{Child, Command, Stdio};
|
|
157
|
+
use zbus::{connection::Builder, message::Type, MessageStream};
|
|
158
|
+
|
|
159
|
+
struct Bus(Child);
|
|
160
|
+
|
|
161
|
+
impl Drop for Bus {
|
|
162
|
+
fn drop(&mut self) {
|
|
163
|
+
let _ = self.0.kill();
|
|
164
|
+
let _ = self.0.wait();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// A private session bus whose `org.freedesktop.portal.Desktop` owner
|
|
169
|
+
/// answers every property read the way a stale xdg-desktop-portal answers
|
|
170
|
+
/// for ScreenCast: no such interface.
|
|
171
|
+
#[tokio::test]
|
|
172
|
+
async fn open_names_the_fix_when_the_portal_exports_no_screencast() {
|
|
173
|
+
use zbus::export::futures_core::Stream;
|
|
174
|
+
|
|
175
|
+
let mut daemon = Command::new("dbus-daemon")
|
|
176
|
+
.args(["--session", "--nofork", "--print-address=1"])
|
|
177
|
+
.stdout(Stdio::piped())
|
|
178
|
+
.spawn()
|
|
179
|
+
.expect("dbus-daemon");
|
|
180
|
+
let mut address = String::new();
|
|
181
|
+
std::io::BufRead::read_line(
|
|
182
|
+
&mut std::io::BufReader::new(daemon.stdout.take().unwrap()),
|
|
183
|
+
&mut address,
|
|
184
|
+
)
|
|
185
|
+
.unwrap();
|
|
186
|
+
let _bus = Bus(daemon);
|
|
187
|
+
let address = address.trim().to_owned();
|
|
188
|
+
|
|
189
|
+
let fake = Builder::address(address.as_str())
|
|
190
|
+
.unwrap()
|
|
191
|
+
.name("org.freedesktop.portal.Desktop")
|
|
192
|
+
.unwrap()
|
|
193
|
+
.build()
|
|
194
|
+
.await
|
|
195
|
+
.unwrap();
|
|
196
|
+
let service = fake.clone();
|
|
197
|
+
let answering = tokio::spawn(async move {
|
|
198
|
+
let mut messages = MessageStream::from(&service);
|
|
199
|
+
while let Some(Ok(message)) =
|
|
200
|
+
std::future::poll_fn(|cx| std::pin::Pin::new(&mut messages).poll_next(cx)).await
|
|
201
|
+
{
|
|
202
|
+
if message.message_type() != Type::MethodCall {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
let _ = service
|
|
206
|
+
.reply_dbus_error(
|
|
207
|
+
&message.header(),
|
|
208
|
+
zbus::fdo::Error::InvalidArgs(
|
|
209
|
+
"No such interface \u{201c}org.freedesktop.portal.ScreenCast\u{201d}".into(),
|
|
210
|
+
),
|
|
211
|
+
)
|
|
212
|
+
.await;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
// Screencast::new reads the session bus from the environment.
|
|
216
|
+
std::env::set_var("DBUS_SESSION_BUS_ADDRESS", &address);
|
|
217
|
+
|
|
218
|
+
let error = match open(None, crate::protocol::CursorMode::Embedded).await {
|
|
219
|
+
Ok(_) => panic!("a portal without ScreenCast must not open a session"),
|
|
220
|
+
Err(error) => format!("{error:#}"),
|
|
221
|
+
};
|
|
222
|
+
answering.abort();
|
|
223
|
+
assert!(error.starts_with("compositor has no ScreenCast portal"), "{error}");
|
|
224
|
+
assert!(error.contains("systemctl --user restart xdg-desktop-portal"), "{error}");
|
|
225
|
+
}
|
|
226
|
+
}
|
package/engine/src/protocol.rs
CHANGED
|
@@ -149,6 +149,11 @@ pub struct OpenParams {
|
|
|
149
149
|
/// cannot be answered is refused.
|
|
150
150
|
#[serde(default)]
|
|
151
151
|
pub audio: bool,
|
|
152
|
+
/// Observe this computer's own pointer buttons, cursor and keys and raise
|
|
153
|
+
/// them as `session.input.observed` and `session.cursor`. Off unless asked:
|
|
154
|
+
/// it reports every key typed on the desktop, in every application.
|
|
155
|
+
#[serde(default)]
|
|
156
|
+
pub observe_input: bool,
|
|
152
157
|
}
|
|
153
158
|
|
|
154
159
|
fn default_local_frames() -> bool {
|
|
@@ -379,7 +384,7 @@ impl ControlMessage {
|
|
|
379
384
|
}
|
|
380
385
|
}
|
|
381
386
|
|
|
382
|
-
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
|
387
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
383
388
|
#[serde(rename_all = "snake_case")]
|
|
384
389
|
pub enum PointerPhase {
|
|
385
390
|
Move,
|
|
@@ -388,6 +393,30 @@ pub enum PointerPhase {
|
|
|
388
393
|
Cancel,
|
|
389
394
|
}
|
|
390
395
|
|
|
396
|
+
/// A pointer button or key the person pressed on the observed desktop. The
|
|
397
|
+
/// vocabulary is the control channel's own (`pointer` and `key`), so a consumer
|
|
398
|
+
/// reads what it sent and what it saw the same way. `x`/`y` are in the source's
|
|
399
|
+
/// own pixels (`geometry.source`); `modifiers` is the modifier state after the
|
|
400
|
+
/// event.
|
|
401
|
+
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
|
402
|
+
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
403
|
+
pub enum ObservedInput {
|
|
404
|
+
Pointer {
|
|
405
|
+
phase: PointerPhase,
|
|
406
|
+
x: i64,
|
|
407
|
+
y: i64,
|
|
408
|
+
button: i64,
|
|
409
|
+
},
|
|
410
|
+
Key {
|
|
411
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
412
|
+
name: Option<String>,
|
|
413
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
414
|
+
character: Option<String>,
|
|
415
|
+
down: bool,
|
|
416
|
+
modifiers: Vec<String>,
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
|
|
391
420
|
/// Messages the engine sends back on the control channel.
|
|
392
421
|
#[derive(Debug, Serialize)]
|
|
393
422
|
#[serde(tag = "kind", rename_all = "snake_case")]
|