@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.
@@ -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("compositor has no ScreenCast portal")?;
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
+ }
@@ -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")]