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<'_> {}