implement xdg-foreign to put capture dialog on top

This commit is contained in:
Ferdinand Schober
2026-02-11 17:41:15 +01:00
committed by Ferdinand Schober
parent be27e337f4
commit a5bdcd0972
10 changed files with 166 additions and 43 deletions

View File

@@ -2,6 +2,7 @@ use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Display,
mem::swap,
sync::{Arc, Mutex},
task::{Poll, ready},
};
@@ -129,6 +130,23 @@ pub struct InputCapture {
pending: VecDeque<(CaptureHandle, CaptureEvent)>,
}
#[derive(Clone, Debug)]
pub enum WindowIdentifier {
Wayland(String),
X11(u32),
}
impl Into<ashpd::WindowIdentifier> for WindowIdentifier {
fn into(self) -> ashpd::WindowIdentifier {
match self {
WindowIdentifier::Wayland(handle) => {
ashpd::WindowIdentifier::from_xdg_foreign_exported_v2(handle)
}
WindowIdentifier::X11(_) => todo!(),
}
}
}
impl InputCapture {
/// create a new client with the given id
pub async fn create(&mut self, id: CaptureHandle, pos: Position) -> Result<(), CaptureError> {
@@ -177,8 +195,11 @@ impl InputCapture {
}
/// creates a new [`InputCapture`]
pub async fn new(backend: Option<Backend>) -> Result<Self, CaptureCreationError> {
let capture = create(backend).await?;
pub async fn new(
backend: Option<Backend>,
window_identifier: Arc<Mutex<Option<WindowIdentifier>>>,
) -> Result<Self, CaptureCreationError> {
let capture = create(backend, window_identifier).await?;
Ok(Self {
capture,
id_map: Default::default(),
@@ -280,13 +301,16 @@ trait Capture: Stream<Item = Result<(Position, CaptureEvent), CaptureError>> + U
async fn create_backend(
backend: Backend,
window_identifier: Arc<Mutex<Option<WindowIdentifier>>>,
) -> Result<
Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
CaptureCreationError,
> {
match backend {
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
Backend::InputCapturePortal => Ok(Box::new(
libei::LibeiInputCapture::new(window_identifier).await?,
)),
#[cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))]
Backend::LayerShell => Ok(Box::new(layer_shell::LayerShellInputCapture::new()?)),
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
@@ -301,12 +325,13 @@ async fn create_backend(
async fn create(
backend: Option<Backend>,
window_identifier: Arc<Mutex<Option<WindowIdentifier>>>,
) -> Result<
Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
CaptureCreationError,
> {
if let Some(backend) = backend {
let b = create_backend(backend).await;
let b = create_backend(backend, window_identifier).await;
if b.is_ok() {
log::info!("using capture backend: {backend}");
}
@@ -325,7 +350,7 @@ async fn create(
#[cfg(target_os = "macos")]
Backend::MacOs,
] {
match create_backend(backend).await {
match create_backend(backend, window_identifier.clone()).await {
Ok(b) => {
log::info!("using capture backend: {backend}");
return Ok(b);

View File

@@ -23,7 +23,7 @@ use std::{
os::unix::net::UnixStream,
pin::Pin,
rc::Rc,
sync::Arc,
sync::{Arc, Mutex},
task::{Context, Poll},
};
use tokio::{
@@ -39,7 +39,7 @@ use futures_core::Stream;
use input_event::Event;
use crate::CaptureEvent;
use crate::{CaptureEvent, WindowIdentifier};
use super::{
Capture as LanMouseInputCapture, Position,
@@ -153,11 +153,15 @@ async fn update_barriers(
async fn create_session(
input_capture: &InputCapture,
window_identifier: Arc<Mutex<Option<WindowIdentifier>>>,
) -> std::result::Result<(Session<InputCapture>, BitFlags<Capabilities>), ashpd::Error> {
log::debug!("creating input capture session");
let window_identifier = window_identifier.lock().unwrap().clone();
log::debug!("creating input capture session: {window_identifier:?}");
let ashpd_window_identifier: Option<ashpd::WindowIdentifier> =
window_identifier.map(|i| i.into());
input_capture
.create_session(
None,
ashpd_window_identifier.as_ref(),
Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen,
)
.await
@@ -202,10 +206,15 @@ async fn libei_event_handler(
}
impl LibeiInputCapture {
pub async fn new() -> std::result::Result<Self, LibeiCaptureCreationError> {
/// creates a new libei input capture
/// `window_id` is a window identifier for user prompts
pub async fn new(
window_identifier: Arc<Mutex<Option<WindowIdentifier>>>,
) -> std::result::Result<Self, LibeiCaptureCreationError> {
let input_capture = Box::pin(InputCapture::new().await?);
let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture;
let first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);
let first_session =
Some(create_session(unsafe { &*input_capture_ptr }, window_identifier.clone()).await?);
let (event_tx, event_rx) = mpsc::channel(1);
let (notify_capture, notify_rx) = mpsc::channel(1);
@@ -220,6 +229,7 @@ impl LibeiInputCapture {
first_session,
event_tx,
cancellation_token.clone(),
window_identifier,
);
let capture_task = tokio::task::spawn_local(capture);
@@ -244,6 +254,7 @@ async fn do_capture(
session: Option<(Session<InputCapture>, BitFlags<Capabilities>)>,
event_tx: Sender<(Position, CaptureEvent)>,
cancellation_token: CancellationToken,
window_identifier: Arc<Mutex<Option<WindowIdentifier>>>,
) -> Result<(), CaptureError> {
let mut session = session.map(|s| s.0);
@@ -289,7 +300,11 @@ async fn do_capture(
// create session
let mut session = match session.take() {
Some(s) => s,
None => create_session(input_capture).await?.0,
None => {
create_session(input_capture, window_identifier.clone())
.await?
.0
}
};
let capture_session = do_capture_session(