mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-10 06:40:56 +03:00
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:
committed by
GitHub
parent
4cdc5ea49c
commit
be0fe9f2d9
@@ -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!()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user