Support event consumer on KDE! (portal backend) (#31)

* Support event consumer on KDE! (portal backend)

Support for KDE event emulation using the remote-desktop xdg-desktop-portal

* fix scrolling (TODO: smooth / kinetic scrolling)

* windows: fix compilation errors

* Update README.md
This commit is contained in:
Ferdinand Schober
2023-10-13 13:57:33 +02:00
committed by GitHub
parent 4cdc5ea49c
commit be0fe9f2d9
11 changed files with 807 additions and 151 deletions

View File

@@ -1,4 +1,4 @@
use crate::consumer::EventConsumer;
use crate::consumer::SyncConsumer;
pub struct LibeiConsumer {}
@@ -6,7 +6,7 @@ impl LibeiConsumer {
pub fn new() -> Self { Self { } }
}
impl EventConsumer for LibeiConsumer {
impl SyncConsumer for LibeiConsumer {
fn consume(&mut self, _: crate::event::Event, _: crate::client::ClientHandle) {
log::error!("libei backend not yet implemented!");
todo!()

View File

@@ -1,4 +1,4 @@
use crate::{event::{KeyboardEvent, PointerEvent}, consumer::EventConsumer};
use crate::{event::{KeyboardEvent, PointerEvent}, consumer::SyncConsumer};
use winapi::{
self,
um::winuser::{INPUT, INPUT_MOUSE, LPINPUT, MOUSEEVENTF_MOVE, MOUSEINPUT,
@@ -25,7 +25,7 @@ impl WindowsConsumer {
pub fn new() -> Self { Self { } }
}
impl EventConsumer for WindowsConsumer {
impl SyncConsumer for WindowsConsumer {
fn consume(&mut self, event: Event, _: ClientHandle) {
match event {
Event::Pointer(pointer_event) => match pointer_event {

View File

@@ -1,7 +1,7 @@
use wayland_client::WEnum;
use wayland_client::backend::WaylandError;
use crate::client::{ClientHandle, ClientEvent};
use crate::consumer::EventConsumer;
use crate::consumer::SyncConsumer;
use std::collections::HashMap;
use std::io;
use std::os::fd::OwnedFd;
@@ -140,7 +140,7 @@ impl State {
}
}
impl EventConsumer for WlrootsConsumer {
impl SyncConsumer for WlrootsConsumer {
fn consume(&mut self, event: Event, client_handle: ClientHandle) {
if let Some(virtual_input) = self.state.input_for_client.get(&client_handle) {
if self.last_flush_failed {

View File

@@ -3,7 +3,7 @@ use x11::{xlib, xtest};
use crate::{
client::ClientHandle,
event::Event, consumer::EventConsumer,
event::Event, consumer::SyncConsumer,
};
pub struct X11Consumer {
@@ -30,7 +30,7 @@ impl X11Consumer {
}
}
impl EventConsumer for X11Consumer {
impl SyncConsumer for X11Consumer {
fn consume(&mut self, event: Event, _: ClientHandle) {
match event {
Event::Pointer(pointer_event) => match pointer_event {

View File

@@ -1,15 +1,91 @@
use crate::consumer::EventConsumer;
use async_trait::async_trait;
use anyhow::Result;
use ashpd::{desktop::{remote_desktop::{RemoteDesktop, DeviceType, KeyState, Axis}, Session}, WindowIdentifier};
pub struct DesktopPortalConsumer {}
use crate::consumer::AsyncConsumer;
impl DesktopPortalConsumer {
pub fn new() -> Self { Self { } }
pub struct DesktopPortalConsumer<'a> {
proxy: RemoteDesktop<'a>,
session: Session<'a>,
}
impl EventConsumer for DesktopPortalConsumer {
fn consume(&mut self, _: crate::event::Event, _: crate::client::ClientHandle) {
log::error!("xdg_desktop_portal backend not yet implemented!");
impl<'a> DesktopPortalConsumer<'a> {
pub async fn new() -> Result<DesktopPortalConsumer<'a>> {
let proxy = RemoteDesktop::new().await?;
let session = proxy.create_session().await?;
proxy
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
.await?;
let _ = proxy
.start(&session, &WindowIdentifier::default())
.await?
.response()?;
Ok(Self { proxy, session })
}
}
#[async_trait]
impl<'a> AsyncConsumer for DesktopPortalConsumer<'a> {
async fn consume(&mut self, event: crate::event::Event, _client: crate::client::ClientHandle) {
match event {
crate::event::Event::Pointer(p) => {
match p {
crate::event::PointerEvent::Motion { time: _, relative_x, relative_y } => {
if let Err(e) = self.proxy.notify_pointer_motion(&self.session, relative_x, relative_y).await {
log::warn!("{e}");
}
},
crate::event::PointerEvent::Button { time: _, button, state } => {
let state = match state {
0 => KeyState::Released,
_ => KeyState::Pressed,
};
if let Err(e) = self.proxy.notify_pointer_button(&self.session, button as i32, state).await {
log::warn!("{e}");
}
},
crate::event::PointerEvent::Axis { time: _, axis, value } => {
let axis = match axis {
0 => Axis::Vertical,
_ => Axis::Horizontal,
};
// TODO smooth scrolling
if let Err(e) = self.proxy.notify_pointer_axis_discrete(&self.session, axis, value as i32).await {
log::warn!("{e}");
}
},
crate::event::PointerEvent::Frame { } => {},
}
},
crate::event::Event::Keyboard(k) => {
match k {
crate::event::KeyboardEvent::Key { time: _, key, state } => {
let state = match state {
0 => KeyState::Released,
_ => KeyState::Pressed,
};
if let Err(e) = self.proxy.notify_keyboard_keycode(&self.session, key as i32, state).await {
log::warn!("{e}");
}
},
crate::event::KeyboardEvent::Modifiers { .. } => {
// ignore
},
}
},
_ => {},
}
}
fn notify(&mut self, _: crate::client::ClientEvent) {}
async fn notify(&mut self, _client: crate::client::ClientEvent) { }
async fn destroy(&mut self) {
log::debug!("closing remote desktop session");
if let Err(e) = self.session.close().await {
log::error!("failed to close remote desktop session: {e}");
}
}
}

View File

@@ -1,3 +1,5 @@
use async_trait::async_trait;
#[cfg(unix)]
use std::env;
@@ -13,7 +15,12 @@ enum Backend {
Libei,
}
pub trait EventConsumer {
pub enum EventConsumer {
Sync(Box<dyn SyncConsumer>),
Async(Box<dyn AsyncConsumer>),
}
pub trait SyncConsumer {
/// Event corresponding to an abstract `client_handle`
fn consume(&mut self, event: Event, client_handle: ClientHandle);
@@ -21,9 +28,16 @@ pub trait EventConsumer {
fn notify(&mut self, client_event: ClientEvent);
}
pub fn create() -> Result<Box<dyn EventConsumer>> {
#[async_trait]
pub trait AsyncConsumer {
async fn consume(&mut self, event: Event, client_handle: ClientHandle);
async fn notify(&mut self, client_event: ClientEvent);
async fn destroy(&mut self);
}
pub async fn create() -> Result<EventConsumer> {
#[cfg(windows)]
return Ok(Box::new(consumer::windows::WindowsConsumer::new()));
return Ok(EventConsumer::Sync(Box::new(consumer::windows::WindowsConsumer::new())));
#[cfg(unix)]
let backend = match env::var("XDG_SESSION_TYPE") {
@@ -75,25 +89,25 @@ pub fn create() -> Result<Box<dyn EventConsumer>> {
#[cfg(not(feature = "libei"))]
panic!("feature libei not enabled");
#[cfg(feature = "libei")]
Ok(Box::new(consumer::libei::LibeiConsumer::new()))
Ok(EventConsumer::Sync(Box::new(consumer::libei::LibeiConsumer::new())))
},
Backend::RemoteDesktopPortal => {
#[cfg(not(feature = "xdg_desktop_portal"))]
panic!("feature xdg_desktop_portal not enabled");
#[cfg(feature = "xdg_desktop_portal")]
Ok(Box::new(consumer::xdg_desktop_portal::DesktopPortalConsumer::new()))
Ok(EventConsumer::Async(Box::new(consumer::xdg_desktop_portal::DesktopPortalConsumer::new().await?)))
},
Backend::Wlroots => {
#[cfg(not(feature = "wayland"))]
panic!("feature wayland not enabled");
#[cfg(feature = "wayland")]
Ok(Box::new(consumer::wlroots::WlrootsConsumer::new()?))
Ok(EventConsumer::Sync(Box::new(consumer::wlroots::WlrootsConsumer::new()?)))
},
Backend::X11 => {
#[cfg(not(feature = "x11"))]
panic!("feature x11 not enabled");
#[cfg(feature = "x11")]
Ok(Box::new(consumer::x11::X11Consumer::new()))
Ok(EventConsumer::Sync(Box::new(consumer::x11::X11Consumer::new())))
},
}
}

View File

@@ -1,6 +1,6 @@
use std::{error::Error, io::Result, collections::HashSet, time::{Duration, Instant}, net::IpAddr};
use log;
use tokio::{net::UdpSocket, io::ReadHalf, sync::mpsc::{Sender, Receiver}};
use tokio::{net::UdpSocket, io::ReadHalf, signal, sync::mpsc::{Sender, Receiver}};
#[cfg(unix)]
use tokio::net::UnixStream;
@@ -26,7 +26,7 @@ pub struct Server {
client_manager: ClientManager,
state: State,
frontend: FrontendListener,
consumer: Box<dyn EventConsumer>,
consumer: EventConsumer,
producer: Box<dyn EventProducer>,
socket: UdpSocket,
frontend_rx: Receiver<FrontendEvent>,
@@ -37,7 +37,7 @@ impl Server {
pub async fn new(
port: u16,
frontend: FrontendListener,
consumer: Box<dyn EventConsumer>,
consumer: EventConsumer,
producer: Box<dyn EventProducer>,
) -> anyhow::Result<Self> {
@@ -102,6 +102,10 @@ impl Server {
}
}
}
_ = signal::ctrl_c() => {
log::info!("terminating gracefully ...");
break;
}
}
}
@@ -135,9 +139,18 @@ impl Server {
}
}
}
_ = signal::ctrl_c() => {
log::info!("terminating gracefully ...");
break;
}
}
}
// destroy consumer
if let EventConsumer::Async(c) = &mut self.consumer {
c.destroy().await;
}
Ok(())
}
@@ -164,22 +177,31 @@ impl Server {
client
}
pub fn activate_client(&mut self, client: ClientHandle, active: bool) {
pub async fn activate_client(&mut self, client: ClientHandle, active: bool) {
if let Some(state) = self.client_manager.get_mut(client) {
state.active = active;
if state.active {
self.producer.notify(ClientEvent::Create(client, state.client.pos));
self.consumer.notify(ClientEvent::Create(client, state.client.pos));
match &mut self.consumer {
EventConsumer::Sync(consumer) => consumer.notify(ClientEvent::Create(client, state.client.pos)),
EventConsumer::Async(consumer) => consumer.notify(ClientEvent::Create(client, state.client.pos)).await,
}
} else {
self.producer.notify(ClientEvent::Destroy(client));
self.consumer.notify(ClientEvent::Destroy(client));
match &mut self.consumer {
EventConsumer::Sync(consumer) => consumer.notify(ClientEvent::Destroy(client)),
EventConsumer::Async(consumer) => consumer.notify(ClientEvent::Destroy(client)).await,
}
}
}
}
pub async fn remove_client(&mut self, client: ClientHandle) -> Option<ClientHandle> {
self.producer.notify(ClientEvent::Destroy(client));
self.consumer.notify(ClientEvent::Destroy(client));
match &mut self.consumer {
EventConsumer::Sync(consumer) => consumer.notify(ClientEvent::Destroy(client)),
EventConsumer::Async(consumer) => consumer.notify(ClientEvent::Destroy(client)).await,
}
if let Some(client) = self.client_manager.remove_client(client).map(|s| s.client.handle) {
let notify = FrontendNotify::NotifyClientDelete(client);
log::debug!("{notify:?}");
@@ -208,9 +230,15 @@ impl Server {
state.client.pos = pos;
if state.active {
self.producer.notify(ClientEvent::Destroy(client));
self.consumer.notify(ClientEvent::Destroy(client));
match &mut self.consumer {
EventConsumer::Sync(consumer) => consumer.notify(ClientEvent::Destroy(client)),
EventConsumer::Async(consumer) => consumer.notify(ClientEvent::Destroy(client)).await,
}
self.producer.notify(ClientEvent::Create(client, pos));
self.consumer.notify(ClientEvent::Create(client, pos));
match &mut self.consumer {
EventConsumer::Sync(consumer) => consumer.notify(ClientEvent::Create(client, pos)),
EventConsumer::Async(consumer) => consumer.notify(ClientEvent::Create(client, pos)).await,
}
}
// update port
@@ -294,7 +322,10 @@ impl Server {
}
State::Receiving => {
// consume event
self.consumer.consume(event, handle);
match &mut self.consumer {
EventConsumer::Sync(consumer) => consumer.consume(event, handle),
EventConsumer::Async(consumer) => consumer.consume(event, handle).await,
}
// let the server know we are still alive once every second
let last_replied = state.last_replied;
@@ -421,7 +452,7 @@ impl Server {
log::debug!("frontend: {event:?}");
match event {
FrontendEvent::AddClient(hostname, port, pos) => { self.add_client(hostname, HashSet::new(), port, pos).await; },
FrontendEvent::ActivateClient(client, active) => self.activate_client(client, active),
FrontendEvent::ActivateClient(client, active) => self.activate_client(client, active).await,
FrontendEvent::DelClient(client) => { self.remove_client(client).await; },
FrontendEvent::UpdateClient(client, hostname, port, pos) => self.update_client(client, hostname, port, pos).await,
FrontendEvent::Enumerate() => self.enumerate().await,

View File

@@ -27,10 +27,6 @@ pub fn run() -> Result<(), Box<dyn Error>> {
// parse config file
let config = Config::new()?;
// start producing and consuming events
let producer = producer::create()?;
let consumer = consumer::create()?;
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_io()
.enable_time()
@@ -38,6 +34,10 @@ pub fn run() -> Result<(), Box<dyn Error>> {
// run async event loop
runtime.block_on(LocalSet::new().run_until(async {
// start producing and consuming events
let producer = producer::create()?;
let consumer = consumer::create().await?;
// create frontend communication adapter
let frontend_adapter = FrontendListener::new().await?;