ashpd/desktop/input_capture.rs
1//! # Examples
2//!
3//! ## A Note of Warning Regarding the GNOME Portal Implementation
4//!
5//! `xdg-desktop-portal-gnome` in version 46.0 has a
6//! [bug](https://gitlab.gnome.org/GNOME/xdg-desktop-portal-gnome/-/issues/126)
7//! that prevents reenabling a disabled session.
8//!
9//! Since changing barrier locations requires a session to be disabled,
10//! it is currently (as of GNOME 46) not possible to change barriers
11//! after a session has been enabled!
12//!
13//! (the [official documentation](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-setpointerbarriers)
14//! states that a
15//! [`InputCapture::set_pointer_barriers()`][set_pointer_barriers]
16//! request suspends the capture session but in reality the GNOME
17//! desktop portal enforces a
18//! [`InputCapture::disable()`][disable]
19//! request
20//! in order to use
21//! [`InputCapture::set_pointer_barriers()`][set_pointer_barriers]
22//! )
23//!
24//! [set_pointer_barriers]: crate::desktop::input_capture::InputCapture::set_pointer_barriers
25//! [disable]: crate::desktop::input_capture::InputCapture::disable
26//!
27//! ## Retrieving an Ei File Descriptor
28//!
29//! The input capture portal is used to negotiate the input capture
30//! triggers and enable input capturing.
31//!
32//! Actual input capture events are then communicated over a unix
33//! stream using the [libei protocol](https://gitlab.freedesktop.org/libinput/libei).
34//!
35//! The lifetime of an ei file descriptor is bound by a capture session.
36//!
37//! ```rust,no_run
38//! use std::os::fd::AsRawFd;
39//!
40//! use ashpd::desktop::input_capture::{Capabilities, InputCapture};
41//!
42//! async fn run() -> ashpd::Result<()> {
43//! let input_capture = InputCapture::new().await?;
44//! let (session, capabilities) = input_capture
45//! .create_session(
46//! None,
47//! Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen,
48//! )
49//! .await?;
50//! eprintln!("capabilities: {capabilities}");
51//!
52//! let eifd = input_capture.connect_to_eis(&session).await?;
53//! eprintln!("eifd: {}", eifd.as_raw_fd());
54//! Ok(())
55//! }
56//! ```
57//!
58//!
59//! ## Selecting Pointer Barriers.
60//!
61//! Input capture is triggered through pointer barriers that are provided
62//! by the client.
63//!
64//! The provided barriers need to be positioned at the edges of outputs
65//! (monitors) and can be denied by the compositor for various reasons, such as
66//! wrong placement.
67//!
68//! For debugging why a barrier placement failed, the logs of the
69//! active portal implementation can be useful, e.g.:
70//!
71//! ```sh
72//! journalctl --user -xeu xdg-desktop-portal-gnome.service
73//! ```
74//!
75//! The following example sets up barriers according to `pos`
76//! (either `Left`, `Right`, `Top` or `Bottom`).
77//!
78//! Note that barriers positioned between two monitors will be denied
79//! and returned in the `failed_barrier_ids` vector.
80//!
81//! ```rust,no_run
82//! use ashpd::desktop::input_capture::{Barrier, BarrierID, Capabilities, InputCapture};
83//!
84//! #[allow(unused)]
85//! enum Position {
86//! Left,
87//! Right,
88//! Top,
89//! Bottom,
90//! }
91//!
92//! async fn run() -> ashpd::Result<()> {
93//! let input_capture = InputCapture::new().await?;
94//! let (session, _capabilities) = input_capture
95//! .create_session(
96//! None,
97//! Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen,
98//! )
99//! .await?;
100//!
101//! let pos = Position::Left;
102//! let zones = input_capture.zones(&session).await?.response()?;
103//! eprintln!("zones: {zones:?}");
104//! let barriers = zones
105//! .regions()
106//! .iter()
107//! .enumerate()
108//! .map(|(n, r)| {
109//! let id = BarrierID::new((n + 1) as u32).expect("barrier-id must be non-zero");
110//! let (x, y) = (r.x_offset(), r.y_offset());
111//! let (width, height) = (r.width() as i32, r.height() as i32);
112//! let barrier_pos = match pos {
113//! Position::Left => (x, y, x, y + height - 1), // start pos, end pos, inclusive
114//! Position::Right => (x + width, y, x + width, y + height - 1),
115//! Position::Top => (x, y, x + width - 1, y),
116//! Position::Bottom => (x, y + height, x + width - 1, y + height),
117//! };
118//! Barrier::new(id, barrier_pos)
119//! })
120//! .collect::<Vec<_>>();
121//!
122//! eprintln!("requested barriers: {barriers:?}");
123//!
124//! let request = input_capture
125//! .set_pointer_barriers(&session, &barriers, zones.zone_set())
126//! .await?;
127//! let response = request.response()?;
128//! let failed_barrier_ids = response.failed_barriers();
129//!
130//! eprintln!("failed barrier ids: {:?}", failed_barrier_ids);
131//!
132//! Ok(())
133//! }
134//! ```
135//!
136//! ## Enabling Input Capture and Retrieving Captured Input Events.
137//!
138//! The following full example uses the [reis crate](https://docs.rs/reis/0.2.0/reis/)
139//! for libei communication.
140//!
141//! Input Capture can be released using ESC.
142//!
143//! ```rust,no_run
144//! use std::{collections::HashMap, os::unix::net::UnixStream, sync::OnceLock, time::Duration};
145//!
146//! use ashpd::desktop::input_capture::{Barrier, BarrierID, Capabilities, InputCapture};
147//! use futures_util::StreamExt;
148//! use reis::{
149//! ei::{self, keyboard::KeyState},
150//! event::{DeviceCapability, EiEvent, KeyboardKey},
151//! };
152//!
153//! #[allow(unused)]
154//! enum Position {
155//! Left,
156//! Right,
157//! Top,
158//! Bottom,
159//! }
160//!
161//! static INTERFACES: OnceLock<HashMap<&'static str, u32>> = OnceLock::new();
162//!
163//! async fn run() -> ashpd::Result<()> {
164//! let input_capture = InputCapture::new().await?;
165//!
166//! let (session, _cap) = input_capture
167//! .create_session(
168//! None,
169//! Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen,
170//! )
171//! .await?;
172//!
173//! // connect to eis server
174//! let fd = input_capture.connect_to_eis(&session).await?;
175//!
176//! // create unix stream from fd
177//! let stream = UnixStream::from(fd);
178//! stream.set_nonblocking(true)?;
179//!
180//! // create ei context
181//! let context = ei::Context::new(stream)?;
182//! context.flush().unwrap();
183//!
184//! let (_connection, mut event_stream) = context
185//! .handshake_tokio("ashpd-mre", ei::handshake::ContextType::Receiver)
186//! .await
187//! .expect("ei handshake failed");
188//!
189//! let pos = Position::Left;
190//! let zones = input_capture.zones(&session).await?.response()?;
191//! eprintln!("zones: {zones:?}");
192//! let barriers = zones
193//! .regions()
194//! .iter()
195//! .enumerate()
196//! .map(|(n, r)| {
197//! let id = BarrierID::new((n + 1) as u32).expect("barrier-id must be non-zero");
198//! let (x, y) = (r.x_offset(), r.y_offset());
199//! let (width, height) = (r.width() as i32, r.height() as i32);
200//! let barrier_pos = match pos {
201//! Position::Left => (x, y, x, y + height - 1), // start pos, end pos, inclusive
202//! Position::Right => (x + width, y, x + width, y + height - 1),
203//! Position::Top => (x, y, x + width - 1, y),
204//! Position::Bottom => (x, y + height, x + width - 1, y + height),
205//! };
206//! Barrier::new(id, barrier_pos)
207//! })
208//! .collect::<Vec<_>>();
209//!
210//! eprintln!("requested barriers: {barriers:?}");
211//!
212//! let request = input_capture
213//! .set_pointer_barriers(&session, &barriers, zones.zone_set())
214//! .await?;
215//! let response = request.response()?;
216//! let failed_barrier_ids = response.failed_barriers();
217//!
218//! eprintln!("failed barrier ids: {:?}", failed_barrier_ids);
219//!
220//! input_capture.enable(&session).await?;
221//!
222//! let mut activate_stream = input_capture.receive_activated().await?;
223//!
224//! loop {
225//! let activated = activate_stream.next().await.unwrap();
226//!
227//! eprintln!("activated: {activated:?}");
228//! loop {
229//! let ei_event = event_stream.next().await.unwrap().unwrap();
230//! eprintln!("ei event: {ei_event:?}");
231//! if let EiEvent::SeatAdded(seat_event) = &ei_event {
232//! seat_event.seat.bind_capabilities(&[
233//! DeviceCapability::Pointer,
234//! DeviceCapability::PointerAbsolute,
235//! DeviceCapability::Keyboard,
236//! DeviceCapability::Touch,
237//! DeviceCapability::Scroll,
238//! DeviceCapability::Button,
239//! ]);
240//! context.flush().unwrap();
241//! }
242//! if let EiEvent::DeviceAdded(_) = ei_event {
243//! // new device added -> restart capture
244//! break;
245//! };
246//! if let EiEvent::KeyboardKey(KeyboardKey { key, state, .. }) = ei_event {
247//! if key == 1 && state == KeyState::Press {
248//! // esc pressed
249//! break;
250//! }
251//! }
252//! }
253//!
254//! eprintln!("releasing input capture");
255//! let (x, y) = activated.cursor_position().unwrap();
256//! let (x, y) = (x as f64, y as f64);
257//! let cursor_pos = match pos {
258//! Position::Left => (x + 1., y),
259//! Position::Right => (x - 1., y),
260//! Position::Top => (x, y - 1.),
261//! Position::Bottom => (x, y + 1.),
262//! };
263//! input_capture
264//! .release(&session, activated.activation_id(), Some(cursor_pos))
265//! .await?;
266//! }
267//! }
268//! ```
269
270use std::{collections::HashMap, num::NonZeroU32, os::fd::OwnedFd};
271
272use enumflags2::{bitflags, BitFlags};
273use futures_util::{Stream, TryFutureExt};
274use serde::{de::Visitor, Deserialize};
275use serde_repr::{Deserialize_repr, Serialize_repr};
276use zbus::zvariant::{
277 self, DeserializeDict, ObjectPath, OwnedObjectPath, OwnedValue, SerializeDict, Type, Value,
278};
279
280use super::{session::SessionPortal, HandleToken, Request, Session};
281use crate::{proxy::Proxy, Error, WindowIdentifier};
282
283#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Debug, Copy, Clone, Type)]
284#[bitflags]
285#[repr(u32)]
286/// Supported capabilities
287pub enum Capabilities {
288 /// Keyboard
289 Keyboard,
290 /// Pointer
291 Pointer,
292 /// Touchscreen
293 Touchscreen,
294}
295
296#[derive(Debug, SerializeDict, Type)]
297#[zvariant(signature = "dict")]
298struct CreateSessionOptions {
299 handle_token: HandleToken,
300 session_handle_token: HandleToken,
301 capabilities: BitFlags<Capabilities>,
302}
303
304#[derive(Debug, DeserializeDict, Type)]
305#[zvariant(signature = "dict")]
306struct CreateSessionResponse {
307 session_handle: OwnedObjectPath,
308 capabilities: BitFlags<Capabilities>,
309}
310
311#[derive(Default, Debug, SerializeDict, Type)]
312#[zvariant(signature = "dict")]
313struct GetZonesOptions {
314 handle_token: HandleToken,
315}
316
317#[derive(Default, Debug, SerializeDict, Type)]
318#[zvariant(signature = "dict")]
319struct SetPointerBarriersOptions {
320 handle_token: HandleToken,
321}
322
323#[derive(Default, Debug, SerializeDict, Type)]
324#[zvariant(signature = "dict")]
325struct EnableOptions {}
326
327#[derive(Default, Debug, SerializeDict, Type)]
328#[zvariant(signature = "dict")]
329struct DisableOptions {}
330
331#[derive(Default, Debug, SerializeDict, Type)]
332#[zvariant(signature = "dict")]
333struct ReleaseOptions {
334 activation_id: Option<u32>,
335 cursor_position: Option<(f64, f64)>,
336}
337
338/// Indicates that an input capturing session was disabled.
339#[derive(Debug, Deserialize, Type)]
340#[zvariant(signature = "(oa{sv})")]
341pub struct Disabled(OwnedObjectPath, HashMap<String, OwnedValue>);
342
343impl Disabled {
344 /// Session that was disabled.
345 pub fn session_handle(&self) -> ObjectPath<'_> {
346 self.0.as_ref()
347 }
348
349 /// Optional information
350 pub fn options(&self) -> &HashMap<String, OwnedValue> {
351 &self.1
352 }
353}
354
355#[derive(Debug, DeserializeDict, Type)]
356#[zvariant(signature = "dict")]
357struct DeactivatedOptions {
358 activation_id: Option<u32>,
359}
360
361/// Indicates that an input capturing session was deactivated.
362#[derive(Debug, Deserialize, Type)]
363#[zvariant(signature = "(oa{sv})")]
364pub struct Deactivated(OwnedObjectPath, DeactivatedOptions);
365
366impl Deactivated {
367 /// Session that was deactivated.
368 pub fn session_handle(&self) -> ObjectPath<'_> {
369 self.0.as_ref()
370 }
371
372 /// The same activation_id number as in the corresponding "Activated"
373 /// signal.
374 pub fn activation_id(&self) -> Option<u32> {
375 self.1.activation_id
376 }
377}
378
379#[derive(Debug, DeserializeDict, Type)]
380#[zvariant(signature = "dict")]
381struct ActivatedOptions {
382 activation_id: Option<u32>,
383 cursor_position: Option<(f32, f32)>,
384 barrier_id: Option<ActivatedBarrier>,
385}
386
387/// Indicates that an input capturing session was activated.
388#[derive(Debug, Deserialize, Type)]
389#[zvariant(signature = "(oa{sv})")]
390pub struct Activated(OwnedObjectPath, ActivatedOptions);
391
392impl Activated {
393 /// Session that was activated.
394 pub fn session_handle(&self) -> ObjectPath<'_> {
395 self.0.as_ref()
396 }
397
398 /// A number that can be used to synchronize with the transport-layer.
399 pub fn activation_id(&self) -> Option<u32> {
400 self.1.activation_id
401 }
402
403 /// The current cursor position in the same coordinate space as the zones.
404 pub fn cursor_position(&self) -> Option<(f32, f32)> {
405 self.1.cursor_position
406 }
407
408 /// The barrier that was triggered or None,
409 /// if the input-capture was not triggered by a barrier
410 pub fn barrier_id(&self) -> Option<ActivatedBarrier> {
411 self.1.barrier_id
412 }
413}
414
415#[derive(Clone, Copy, Debug, Type)]
416#[zvariant(signature = "u")]
417/// information about an activation barrier
418pub enum ActivatedBarrier {
419 /// [`BarrierID`] of the triggered barrier
420 Barrier(BarrierID),
421 /// The id of the triggered barrier could not be determined,
422 /// e.g. because of multiple barriers at the same location.
423 UnknownBarrier,
424}
425
426impl<'de> Deserialize<'de> for ActivatedBarrier {
427 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
428 where
429 D: serde::Deserializer<'de>,
430 {
431 let visitor = ActivatedBarrierVisitor {};
432 deserializer.deserialize_u32(visitor)
433 }
434}
435
436struct ActivatedBarrierVisitor {}
437
438impl Visitor<'_> for ActivatedBarrierVisitor {
439 type Value = ActivatedBarrier;
440
441 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
442 write!(formatter, "an unsigned 32bit integer (u32)")
443 }
444
445 fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
446 where
447 E: serde::de::Error,
448 {
449 match BarrierID::new(v) {
450 Some(v) => Ok(ActivatedBarrier::Barrier(v)),
451 None => Ok(ActivatedBarrier::UnknownBarrier),
452 }
453 }
454}
455
456#[derive(Debug, DeserializeDict, Type)]
457#[zvariant(signature = "dict")]
458struct ZonesChangedOptions {
459 zone_set: Option<u32>,
460}
461
462/// Indicates that zones available to this session changed.
463#[derive(Debug, Deserialize, Type)]
464#[zvariant(signature = "(oa{sv})")]
465pub struct ZonesChanged(OwnedObjectPath, ZonesChangedOptions);
466
467impl ZonesChanged {
468 /// Session that was deactivated.
469 pub fn session_handle(&self) -> ObjectPath<'_> {
470 self.0.as_ref()
471 }
472
473 /// The zone_set ID of the invalidated zone.
474 pub fn zone_set(&self) -> Option<u32> {
475 self.1.zone_set
476 }
477}
478
479/// A region of a [`Zones`].
480#[derive(Debug, Clone, Copy, Deserialize, Type)]
481#[zvariant(signature = "(uuii)")]
482pub struct Region(u32, u32, i32, i32);
483
484impl Region {
485 /// The width.
486 pub fn width(self) -> u32 {
487 self.0
488 }
489
490 /// The height
491 pub fn height(self) -> u32 {
492 self.1
493 }
494
495 /// The x offset.
496 pub fn x_offset(self) -> i32 {
497 self.2
498 }
499
500 /// The y offset.
501 pub fn y_offset(self) -> i32 {
502 self.3
503 }
504}
505
506/// A response of [`InputCapture::zones`].
507#[derive(Debug, Type, DeserializeDict)]
508#[zvariant(signature = "dict")]
509pub struct Zones {
510 zones: Vec<Region>,
511 zone_set: u32,
512}
513
514impl Zones {
515 /// A list of regions.
516 pub fn regions(&self) -> &[Region] {
517 &self.zones
518 }
519
520 /// A unique ID to be used in [`InputCapture::set_pointer_barriers`].
521 pub fn zone_set(&self) -> u32 {
522 self.zone_set
523 }
524}
525
526/// A barrier ID.
527pub type BarrierID = NonZeroU32;
528
529#[derive(Debug, SerializeDict, Type)]
530#[zvariant(signature = "dict")]
531/// Input Barrier.
532pub struct Barrier {
533 barrier_id: BarrierID,
534 position: (i32, i32, i32, i32),
535}
536
537impl Barrier {
538 /// Create a new barrier.
539 pub fn new(barrier_id: BarrierID, position: (i32, i32, i32, i32)) -> Self {
540 Self {
541 barrier_id,
542 position,
543 }
544 }
545}
546
547/// A response to [`InputCapture::set_pointer_barriers`]
548#[derive(Debug, DeserializeDict, Type)]
549#[zvariant(signature = "dict")]
550pub struct SetPointerBarriersResponse {
551 failed_barriers: Vec<BarrierID>,
552}
553
554impl SetPointerBarriersResponse {
555 /// List of pointer barriers that have been denied
556 pub fn failed_barriers(&self) -> &[BarrierID] {
557 &self.failed_barriers
558 }
559}
560
561/// Wrapper of the DBus interface: [`org.freedesktop.portal.InputCapture`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html).
562#[doc(alias = "org.freedesktop.portal.InputCapture")]
563pub struct InputCapture<'a>(Proxy<'a>);
564
565impl<'a> InputCapture<'a> {
566 /// Create a new instance of [`InputCapture`].
567 pub async fn new() -> Result<InputCapture<'a>, Error> {
568 let proxy = Proxy::new_desktop("org.freedesktop.portal.InputCapture").await?;
569 Ok(Self(proxy))
570 }
571
572 /// Create an input capture session.
573 ///
574 /// # Specifications
575 ///
576 /// See also [`CreateSession`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-createsession).
577 pub async fn create_session(
578 &self,
579 identifier: Option<&WindowIdentifier>,
580 capabilities: BitFlags<Capabilities>,
581 ) -> Result<(Session<'_, Self>, BitFlags<Capabilities>), Error> {
582 let options = CreateSessionOptions {
583 handle_token: Default::default(),
584 session_handle_token: Default::default(),
585 capabilities,
586 };
587 let identifier = identifier.map(|i| i.to_string()).unwrap_or_default();
588 let (request, proxy) = futures_util::try_join!(
589 self.0
590 .request::<CreateSessionResponse>(
591 &options.handle_token,
592 "CreateSession",
593 (identifier, &options)
594 )
595 .into_future(),
596 Session::from_unique_name(&options.session_handle_token).into_future(),
597 )?;
598 let response = request.response()?;
599 assert_eq!(proxy.path(), &response.session_handle.as_ref());
600 Ok((proxy, response.capabilities))
601 }
602
603 /// A set of currently available input zones for this session.
604 ///
605 /// # Specifications
606 ///
607 /// See also [`GetZones`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-getzones).
608 #[doc(alias = "GetZones")]
609 pub async fn zones(&self, session: &Session<'_, Self>) -> Result<Request<Zones>, Error> {
610 let options = GetZonesOptions::default();
611 self.0
612 .request(&options.handle_token, "GetZones", (session, &options))
613 .await
614 }
615
616 /// Set up zero or more pointer barriers.
617 ///
618 /// # Specifications
619 ///
620 /// See also [`SetPointerBarriers`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-setpointerbarriers).
621 #[doc(alias = "SetPointerBarriers")]
622 pub async fn set_pointer_barriers(
623 &self,
624 session: &Session<'_, Self>,
625 barriers: &[Barrier],
626 zone_set: u32,
627 ) -> Result<Request<SetPointerBarriersResponse>, Error> {
628 let options = SetPointerBarriersOptions::default();
629 self.0
630 .request(
631 &options.handle_token,
632 "SetPointerBarriers",
633 &(session, &options, barriers, zone_set),
634 )
635 .await
636 }
637
638 /// Enable input capturing.
639 ///
640 /// # Specifications
641 ///
642 /// See also [`Enable`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-enable).
643 pub async fn enable(&self, session: &Session<'_, Self>) -> Result<(), Error> {
644 let options = EnableOptions::default();
645 self.0.call("Enable", &(session, &options)).await
646 }
647
648 /// Disable input capturing.
649 ///
650 /// # Specifications
651 ///
652 /// See also [`Disable`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-disable).
653 pub async fn disable(&self, session: &Session<'_, Self>) -> Result<(), Error> {
654 let options = DisableOptions::default();
655 self.0.call("Disable", &(session, &options)).await
656 }
657
658 /// Release any ongoing input capture.
659 ///
660 /// # Specifications
661 ///
662 /// See also [`Release`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-release).
663 pub async fn release(
664 &self,
665 session: &Session<'_, Self>,
666 activation_id: Option<u32>,
667 cursor_position: Option<(f64, f64)>,
668 ) -> Result<(), Error> {
669 let options = ReleaseOptions {
670 activation_id,
671 cursor_position,
672 };
673 self.0.call("Release", &(session, &options)).await
674 }
675
676 /// Connect to EIS.
677 ///
678 /// # Specifications
679 ///
680 /// See also [`ConnectToEIS`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-connecttoeis).
681 #[doc(alias = "ConnectToEIS")]
682 pub async fn connect_to_eis(&self, session: &Session<'_, Self>) -> Result<OwnedFd, Error> {
683 // `ConnectToEIS` doesn't take any options for now
684 let options: HashMap<&str, Value<'_>> = HashMap::new();
685 let fd = self
686 .0
687 .call::<zvariant::OwnedFd>("ConnectToEIS", &(session, options))
688 .await?;
689 Ok(fd.into())
690 }
691
692 /// Signal emitted when the application will no longer receive captured
693 /// events.
694 ///
695 /// # Specifications
696 ///
697 /// See also [`Disabled`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-disabled).
698 #[doc(alias = "Disabled")]
699 pub async fn receive_disabled(&self) -> Result<impl Stream<Item = Disabled>, Error> {
700 self.0.signal("Disabled").await
701 }
702
703 /// Signal emitted when input capture starts and
704 /// input events are about to be sent to the application.
705 ///
706 /// # Specifications
707 ///
708 /// See also [`Activated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-activated).
709 #[doc(alias = "Activated")]
710 pub async fn receive_activated(&self) -> Result<impl Stream<Item = Activated>, Error> {
711 self.0.signal("Activated").await
712 }
713
714 /// Signal emitted when input capture stopped and input events
715 /// are no longer sent to the application.
716 ///
717 /// # Specifications
718 ///
719 /// See also [`Deactivated`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-deactivated).
720 #[doc(alias = "Deactivated")]
721 pub async fn receive_deactivated(&self) -> Result<impl Stream<Item = Deactivated>, Error> {
722 self.0.signal("Deactivated").await
723 }
724
725 /// Signal emitted when the set of zones available to this session change.
726 ///
727 /// # Specifications
728 ///
729 /// See also [`ZonesChanged`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-zoneschanged).
730 #[doc(alias = "ZonesChanged")]
731 pub async fn receive_zones_changed(&self) -> Result<impl Stream<Item = ZonesChanged>, Error> {
732 self.0.signal("ZonesChanged").await
733 }
734
735 /// Supported capabilities.
736 ///
737 /// # Specifications
738 ///
739 /// See also [`SupportedCapabilities`](https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.InputCapture.html#org-freedesktop-portal-inputcapture-supportedcapabilities).
740 #[doc(alias = "SupportedCapabilities")]
741 pub async fn supported_capabilities(&self) -> Result<BitFlags<Capabilities>, Error> {
742 self.0.property("SupportedCapabilities").await
743 }
744}
745
746impl<'a> std::ops::Deref for InputCapture<'a> {
747 type Target = zbus::Proxy<'a>;
748
749 fn deref(&self) -> &Self::Target {
750 &self.0
751 }
752}
753
754impl crate::Sealed for InputCapture<'_> {}
755impl SessionPortal for InputCapture<'_> {}