mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-15 17:20:54 +03:00
split into input-{event,capture,emulation}
This commit is contained in:
committed by
Ferdinand Schober
parent
7b511bb97d
commit
4db2d37f32
140
src/capture.rs
140
src/capture.rs
@@ -1,140 +0,0 @@
|
||||
use std::{fmt::Display, io};
|
||||
|
||||
use futures_core::Stream;
|
||||
|
||||
use crate::{config::CaptureBackend, event::Event};
|
||||
|
||||
use self::error::CaptureCreationError;
|
||||
|
||||
pub mod error;
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
pub mod libei;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
pub mod wayland;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
pub mod x11;
|
||||
|
||||
/// fallback input capture (does not produce events)
|
||||
pub mod dummy;
|
||||
|
||||
pub type CaptureHandle = u64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
|
||||
pub enum Position {
|
||||
Left,
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Position {
|
||||
pub fn opposite(&self) -> Self {
|
||||
match self {
|
||||
Position::Left => Self::Right,
|
||||
Position::Right => Self::Left,
|
||||
Position::Top => Self::Bottom,
|
||||
Position::Bottom => Self::Top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Position {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let pos = match self {
|
||||
Position::Left => "left",
|
||||
Position::Right => "right",
|
||||
Position::Top => "top",
|
||||
Position::Bottom => "bottom",
|
||||
};
|
||||
write!(f, "{}", pos)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Backend {
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
InputCapturePortal,
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
LayerShell,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
X11,
|
||||
#[cfg(windows)]
|
||||
Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
MacOs,
|
||||
Dummy,
|
||||
}
|
||||
|
||||
pub trait InputCapture: Stream<Item = io::Result<(CaptureHandle, Event)>> + Unpin {
|
||||
/// create a new client with the given id
|
||||
fn create(&mut self, id: CaptureHandle, pos: Position) -> io::Result<()>;
|
||||
|
||||
/// destroy the client with the given id, if it exists
|
||||
fn destroy(&mut self, id: CaptureHandle) -> io::Result<()>;
|
||||
|
||||
/// release mouse
|
||||
fn release(&mut self) -> io::Result<()>;
|
||||
}
|
||||
|
||||
pub async fn create_backend(
|
||||
backend: CaptureBackend,
|
||||
) -> Result<Box<dyn InputCapture<Item = io::Result<(CaptureHandle, Event)>>>, CaptureCreationError>
|
||||
{
|
||||
match backend {
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
CaptureBackend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
CaptureBackend::LayerShell => Ok(Box::new(wayland::WaylandInputCapture::new()?)),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
CaptureBackend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)),
|
||||
#[cfg(windows)]
|
||||
CaptureBackend::Windows => Ok(Box::new(windows::WindowsInputCapture::new())),
|
||||
#[cfg(target_os = "macos")]
|
||||
CaptureBackend::MacOs => Ok(Box::new(macos::MacOSInputCapture::new()?)),
|
||||
CaptureBackend::Dummy => Ok(Box::new(dummy::DummyInputCapture::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
backend: Option<CaptureBackend>,
|
||||
) -> Result<Box<dyn InputCapture<Item = io::Result<(CaptureHandle, Event)>>>, CaptureCreationError>
|
||||
{
|
||||
if let Some(backend) = backend {
|
||||
let b = create_backend(backend).await;
|
||||
if b.is_ok() {
|
||||
log::info!("using capture backend: {backend}");
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
for backend in [
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
CaptureBackend::InputCapturePortal,
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
CaptureBackend::LayerShell,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
CaptureBackend::X11,
|
||||
#[cfg(windows)]
|
||||
CaptureBackend::Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
CaptureBackend::MacOs,
|
||||
CaptureBackend::Dummy,
|
||||
] {
|
||||
match create_backend(backend).await {
|
||||
Ok(b) => {
|
||||
log::info!("using capture backend: {backend}");
|
||||
return Ok(b);
|
||||
}
|
||||
Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"),
|
||||
}
|
||||
}
|
||||
Err(CaptureCreationError::NoAvailableBackend)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_core::Stream;
|
||||
|
||||
use crate::event::Event;
|
||||
|
||||
use super::{CaptureHandle, InputCapture, Position};
|
||||
|
||||
pub struct DummyInputCapture {}
|
||||
|
||||
impl DummyInputCapture {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DummyInputCapture {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCapture for DummyInputCapture {
|
||||
fn create(&mut self, _handle: CaptureHandle, _pos: Position) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&mut self, _handle: CaptureHandle) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for DummyInputCapture {
|
||||
type Item = io::Result<(CaptureHandle, Event)>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
use std::io;
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
use wayland_client::{
|
||||
backend::WaylandError,
|
||||
globals::{BindError, GlobalError},
|
||||
ConnectError, DispatchError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum CaptureCreationError {
|
||||
NoAvailableBackend,
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
Libei(#[from] LibeiCaptureCreationError),
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
LayerShell(#[from] LayerShellCaptureCreationError),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
X11(#[from] X11InputCaptureCreationError),
|
||||
#[cfg(target_os = "macos")]
|
||||
Macos(#[from] MacOSInputCaptureCreationError),
|
||||
#[cfg(windows)]
|
||||
Windows,
|
||||
}
|
||||
|
||||
impl Display for CaptureCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let reason = match self {
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
CaptureCreationError::Libei(reason) => {
|
||||
format!("error creating portal backend: {reason}")
|
||||
}
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
CaptureCreationError::LayerShell(reason) => {
|
||||
format!("error creating layer-shell backend: {reason}")
|
||||
}
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
CaptureCreationError::X11(e) => format!("{e}"),
|
||||
#[cfg(target_os = "macos")]
|
||||
CaptureCreationError::Macos(e) => format!("{e}"),
|
||||
#[cfg(windows)]
|
||||
CaptureCreationError::Windows => String::new(),
|
||||
CaptureCreationError::NoAvailableBackend => "no available backend".to_string(),
|
||||
};
|
||||
write!(f, "could not create input capture: {reason}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LibeiCaptureCreationError {
|
||||
Ashpd(#[from] ashpd::Error),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
impl Display for LibeiCaptureCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LibeiCaptureCreationError::Ashpd(portal_error) => write!(f, "{portal_error}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub struct WaylandBindError {
|
||||
inner: BindError,
|
||||
protocol: &'static str,
|
||||
}
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
impl WaylandBindError {
|
||||
pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self {
|
||||
Self { inner, protocol }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
impl Display for WaylandBindError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} protocol not supported: {}",
|
||||
self.protocol, self.inner
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LayerShellCaptureCreationError {
|
||||
Connect(#[from] ConnectError),
|
||||
Global(#[from] GlobalError),
|
||||
Wayland(#[from] WaylandError),
|
||||
Bind(#[from] WaylandBindError),
|
||||
Dispatch(#[from] DispatchError),
|
||||
Io(#[from] io::Error),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
impl Display for LayerShellCaptureCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LayerShellCaptureCreationError::Bind(e) => write!(f, "{e}"),
|
||||
LayerShellCaptureCreationError::Connect(e) => {
|
||||
write!(f, "could not connect to wayland compositor: {e}")
|
||||
}
|
||||
LayerShellCaptureCreationError::Global(e) => write!(f, "wayland error: {e}"),
|
||||
LayerShellCaptureCreationError::Wayland(e) => write!(f, "wayland error: {e}"),
|
||||
LayerShellCaptureCreationError::Dispatch(e) => {
|
||||
write!(f, "error dispatching wayland events: {e}")
|
||||
}
|
||||
LayerShellCaptureCreationError::Io(e) => write!(f, "io error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum X11InputCaptureCreationError {
|
||||
NotImplemented,
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
impl Display for X11InputCaptureCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "X11 input capture is not yet implemented :(")
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MacOSInputCaptureCreationError {
|
||||
NotImplemented,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl Display for MacOSInputCaptureCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "macos input capture is not yet implemented :(")
|
||||
}
|
||||
}
|
||||
@@ -1,583 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use ashpd::{
|
||||
desktop::{
|
||||
input_capture::{Activated, Barrier, BarrierID, Capabilities, InputCapture, Region, Zones},
|
||||
ResponseError, Session,
|
||||
},
|
||||
enumflags2::BitFlags,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
use reis::{
|
||||
ei::{self, keyboard::KeyState},
|
||||
eis::button::ButtonState,
|
||||
event::{DeviceCapability, EiEvent},
|
||||
tokio::{EiConvertEventStream, EiEventStream},
|
||||
};
|
||||
use std::{
|
||||
cell::Cell,
|
||||
collections::HashMap,
|
||||
io,
|
||||
os::unix::net::UnixStream,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::{
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use futures_core::Stream;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||
|
||||
use super::{
|
||||
error::LibeiCaptureCreationError, CaptureHandle, InputCapture as LanMouseInputCapture, Position,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ProducerEvent {
|
||||
Release,
|
||||
Create(CaptureHandle, Position),
|
||||
Destroy(CaptureHandle),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct LibeiInputCapture<'a> {
|
||||
input_capture: Pin<Box<InputCapture<'a>>>,
|
||||
libei_task: JoinHandle<Result<()>>,
|
||||
event_rx: tokio::sync::mpsc::Receiver<(CaptureHandle, Event)>,
|
||||
notify_tx: tokio::sync::mpsc::Sender<ProducerEvent>,
|
||||
}
|
||||
|
||||
static INTERFACES: Lazy<HashMap<&'static str, u32>> = Lazy::new(|| {
|
||||
let mut m = HashMap::new();
|
||||
m.insert("ei_connection", 1);
|
||||
m.insert("ei_callback", 1);
|
||||
m.insert("ei_pingpong", 1);
|
||||
m.insert("ei_seat", 1);
|
||||
m.insert("ei_device", 2);
|
||||
m.insert("ei_pointer", 1);
|
||||
m.insert("ei_pointer_absolute", 1);
|
||||
m.insert("ei_scroll", 1);
|
||||
m.insert("ei_button", 1);
|
||||
m.insert("ei_keyboard", 1);
|
||||
m.insert("ei_touchscreen", 1);
|
||||
m
|
||||
});
|
||||
|
||||
fn pos_to_barrier(r: &Region, pos: Position) -> (i32, i32, i32, i32) {
|
||||
let (x, y) = (r.x_offset(), r.y_offset());
|
||||
let (width, height) = (r.width() as i32, r.height() as i32);
|
||||
match pos {
|
||||
Position::Left => (x, y, x, y + height - 1), // start pos, end pos, inclusive
|
||||
Position::Right => (x + width, y, x + width, y + height - 1),
|
||||
Position::Top => (x, y, x + width - 1, y),
|
||||
Position::Bottom => (x, y + height, x + width - 1, y + height),
|
||||
}
|
||||
}
|
||||
|
||||
fn select_barriers(
|
||||
zones: &Zones,
|
||||
clients: &Vec<(CaptureHandle, Position)>,
|
||||
next_barrier_id: &mut u32,
|
||||
) -> (Vec<Barrier>, HashMap<BarrierID, CaptureHandle>) {
|
||||
let mut client_for_barrier = HashMap::new();
|
||||
let mut barriers: Vec<Barrier> = vec![];
|
||||
|
||||
for (handle, pos) in clients {
|
||||
let mut client_barriers = zones
|
||||
.regions()
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let id = *next_barrier_id;
|
||||
*next_barrier_id = id + 1;
|
||||
let position = pos_to_barrier(r, *pos);
|
||||
client_for_barrier.insert(id, *handle);
|
||||
Barrier::new(id, position)
|
||||
})
|
||||
.collect();
|
||||
barriers.append(&mut client_barriers);
|
||||
}
|
||||
(barriers, client_for_barrier)
|
||||
}
|
||||
|
||||
async fn update_barriers(
|
||||
input_capture: &InputCapture<'_>,
|
||||
session: &Session<'_>,
|
||||
active_clients: &Vec<(CaptureHandle, Position)>,
|
||||
next_barrier_id: &mut u32,
|
||||
) -> Result<HashMap<BarrierID, CaptureHandle>> {
|
||||
let zones = input_capture.zones(session).await?.response()?;
|
||||
log::debug!("zones: {zones:?}");
|
||||
|
||||
let (barriers, id_map) = select_barriers(&zones, active_clients, next_barrier_id);
|
||||
log::debug!("barriers: {barriers:?}");
|
||||
log::debug!("client for barrier id: {id_map:?}");
|
||||
|
||||
let response = input_capture
|
||||
.set_pointer_barriers(session, &barriers, zones.zone_set())
|
||||
.await?;
|
||||
let response = response.response()?;
|
||||
log::debug!("{response:?}");
|
||||
Ok(id_map)
|
||||
}
|
||||
|
||||
impl<'a> Drop for LibeiInputCapture<'a> {
|
||||
fn drop(&mut self) {
|
||||
self.libei_task.abort();
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_session<'a>(
|
||||
input_capture: &'a InputCapture<'a>,
|
||||
) -> std::result::Result<(Session<'a>, BitFlags<Capabilities>), ashpd::Error> {
|
||||
log::debug!("creating input capture session");
|
||||
let (session, capabilities) = loop {
|
||||
match input_capture
|
||||
.create_session(
|
||||
&ashpd::WindowIdentifier::default(),
|
||||
Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(s) => break s,
|
||||
Err(ashpd::Error::Response(ResponseError::Cancelled)) => continue,
|
||||
o => o?,
|
||||
};
|
||||
};
|
||||
log::debug!("capabilities: {capabilities:?}");
|
||||
Ok((session, capabilities))
|
||||
}
|
||||
|
||||
async fn connect_to_eis(
|
||||
input_capture: &InputCapture<'_>,
|
||||
session: &Session<'_>,
|
||||
) -> Result<(ei::Context, EiConvertEventStream)> {
|
||||
log::debug!("connect_to_eis");
|
||||
let fd = input_capture.connect_to_eis(session).await?;
|
||||
|
||||
// create unix stream from fd
|
||||
let stream = UnixStream::from(fd);
|
||||
stream.set_nonblocking(true)?;
|
||||
|
||||
// create ei context
|
||||
let context = ei::Context::new(stream)?;
|
||||
let mut event_stream = EiEventStream::new(context.clone())?;
|
||||
let response = match reis::tokio::ei_handshake(
|
||||
&mut event_stream,
|
||||
"de.feschber.LanMouse",
|
||||
ei::handshake::ContextType::Receiver,
|
||||
&INTERFACES,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(e) => return Err(anyhow!("ei handshake failed: {e:?}")),
|
||||
};
|
||||
let event_stream = EiConvertEventStream::new(event_stream, response.serial);
|
||||
|
||||
Ok((context, event_stream))
|
||||
}
|
||||
|
||||
async fn libei_event_handler(
|
||||
mut ei_event_stream: EiConvertEventStream,
|
||||
context: ei::Context,
|
||||
event_tx: Sender<(CaptureHandle, Event)>,
|
||||
current_client: Rc<Cell<Option<CaptureHandle>>>,
|
||||
) -> Result<()> {
|
||||
loop {
|
||||
let ei_event = match ei_event_stream.next().await {
|
||||
Some(Ok(event)) => event,
|
||||
Some(Err(e)) => return Err(anyhow!("libei connection closed: {e:?}")),
|
||||
None => return Err(anyhow!("libei connection closed")),
|
||||
};
|
||||
log::trace!("from ei: {ei_event:?}");
|
||||
let client = current_client.get();
|
||||
handle_ei_event(ei_event, client, &context, &event_tx).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_active_client(
|
||||
notify_rx: &mut Receiver<ProducerEvent>,
|
||||
active_clients: &mut Vec<(CaptureHandle, Position)>,
|
||||
) -> Result<()> {
|
||||
// wait for a client update
|
||||
while let Some(producer_event) = notify_rx.recv().await {
|
||||
if let ProducerEvent::Create(c, p) = producer_event {
|
||||
handle_producer_event(ProducerEvent::Create(c, p), active_clients)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a> LibeiInputCapture<'a> {
|
||||
pub async fn new() -> 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<'static>;
|
||||
let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);
|
||||
|
||||
let (event_tx, event_rx) = tokio::sync::mpsc::channel(32);
|
||||
let (notify_tx, mut notify_rx) = tokio::sync::mpsc::channel(32);
|
||||
let libei_task = tokio::task::spawn_local(async move {
|
||||
/* safety: libei_task does not outlive Self */
|
||||
let input_capture = unsafe { &*input_capture_ptr };
|
||||
|
||||
let mut active_clients: Vec<(CaptureHandle, Position)> = vec![];
|
||||
let mut next_barrier_id = 1u32;
|
||||
|
||||
/* there is a bug in xdg-remote-desktop-portal-gnome / mutter that
|
||||
* prevents receiving further events after a session has been disabled once.
|
||||
* Therefore the session needs to recreated when the barriers are updated */
|
||||
|
||||
loop {
|
||||
// otherwise it asks to capture input even with no active clients
|
||||
if active_clients.is_empty() {
|
||||
wait_for_active_client(&mut notify_rx, &mut active_clients).await?;
|
||||
continue;
|
||||
}
|
||||
|
||||
let current_client = Rc::new(Cell::new(None));
|
||||
|
||||
// create session
|
||||
let (session, _) = match first_session.take() {
|
||||
Some(s) => s,
|
||||
_ => create_session(input_capture).await?,
|
||||
};
|
||||
|
||||
// connect to eis server
|
||||
let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?;
|
||||
|
||||
// async event task
|
||||
let mut ei_task: JoinHandle<Result<(), anyhow::Error>> =
|
||||
tokio::task::spawn_local(libei_event_handler(
|
||||
ei_event_stream,
|
||||
context,
|
||||
event_tx.clone(),
|
||||
current_client.clone(),
|
||||
));
|
||||
|
||||
let mut activated = input_capture.receive_activated().await?;
|
||||
let mut zones_changed = input_capture.receive_zones_changed().await?;
|
||||
|
||||
// set barriers
|
||||
let client_for_barrier_id = update_barriers(
|
||||
input_capture,
|
||||
&session,
|
||||
&active_clients,
|
||||
&mut next_barrier_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
log::debug!("enabling session");
|
||||
input_capture.enable(&session).await?;
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
activated = activated.next() => {
|
||||
let activated = activated.ok_or(anyhow!("error receiving activation token"))?;
|
||||
log::debug!("activated: {activated:?}");
|
||||
|
||||
let client = *client_for_barrier_id
|
||||
.get(&activated.barrier_id())
|
||||
.expect("invalid barrier id");
|
||||
current_client.replace(Some(client));
|
||||
|
||||
event_tx.send((client, Event::Enter())).await?;
|
||||
|
||||
tokio::select! {
|
||||
producer_event = notify_rx.recv() => {
|
||||
let producer_event = producer_event.expect("channel closed");
|
||||
if handle_producer_event(producer_event, &mut active_clients)? {
|
||||
break; /* clients updated */
|
||||
}
|
||||
}
|
||||
zones_changed = zones_changed.next() => {
|
||||
log::debug!("zones changed: {zones_changed:?}");
|
||||
break;
|
||||
}
|
||||
res = &mut ei_task => {
|
||||
if let Err(e) = res.expect("ei task paniced") {
|
||||
log::warn!("libei task exited: {e}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
release_capture(
|
||||
input_capture,
|
||||
&session,
|
||||
activated,
|
||||
client,
|
||||
&active_clients,
|
||||
).await?;
|
||||
}
|
||||
producer_event = notify_rx.recv() => {
|
||||
let producer_event = producer_event.expect("channel closed");
|
||||
if handle_producer_event(producer_event, &mut active_clients)? {
|
||||
/* clients updated */
|
||||
break;
|
||||
}
|
||||
},
|
||||
res = &mut ei_task => {
|
||||
if let Err(e) = res.expect("ei task paniced") {
|
||||
log::warn!("libei task exited: {e}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ei_task.abort();
|
||||
input_capture.disable(&session).await?;
|
||||
}
|
||||
});
|
||||
|
||||
let producer = Self {
|
||||
input_capture,
|
||||
event_rx,
|
||||
libei_task,
|
||||
notify_tx,
|
||||
};
|
||||
|
||||
Ok(producer)
|
||||
}
|
||||
}
|
||||
|
||||
async fn release_capture(
|
||||
input_capture: &InputCapture<'_>,
|
||||
session: &Session<'_>,
|
||||
activated: Activated,
|
||||
current_client: CaptureHandle,
|
||||
active_clients: &[(CaptureHandle, Position)],
|
||||
) -> Result<()> {
|
||||
log::debug!("releasing input capture {}", activated.activation_id());
|
||||
let (x, y) = activated.cursor_position();
|
||||
let pos = active_clients
|
||||
.iter()
|
||||
.filter(|(c, _)| *c == current_client)
|
||||
.map(|(_, p)| p)
|
||||
.next()
|
||||
.unwrap(); // FIXME
|
||||
let (dx, dy) = match pos {
|
||||
// offset cursor position to not enter again immediately
|
||||
Position::Left => (1., 0.),
|
||||
Position::Right => (-1., 0.),
|
||||
Position::Top => (0., 1.),
|
||||
Position::Bottom => (0., -1.),
|
||||
};
|
||||
// release 1px to the right of the entered zone
|
||||
let cursor_position = (x as f64 + dx, y as f64 + dy);
|
||||
input_capture
|
||||
.release(session, activated.activation_id(), cursor_position)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_producer_event(
|
||||
producer_event: ProducerEvent,
|
||||
active_clients: &mut Vec<(CaptureHandle, Position)>,
|
||||
) -> Result<bool> {
|
||||
log::debug!("handling event: {producer_event:?}");
|
||||
let updated = match producer_event {
|
||||
ProducerEvent::Release => false,
|
||||
ProducerEvent::Create(c, p) => {
|
||||
active_clients.push((c, p));
|
||||
true
|
||||
}
|
||||
ProducerEvent::Destroy(c) => {
|
||||
active_clients.retain(|(h, _)| *h != c);
|
||||
true
|
||||
}
|
||||
};
|
||||
Ok(updated)
|
||||
}
|
||||
|
||||
async fn handle_ei_event(
|
||||
ei_event: EiEvent,
|
||||
current_client: Option<CaptureHandle>,
|
||||
context: &ei::Context,
|
||||
event_tx: &Sender<(CaptureHandle, Event)>,
|
||||
) {
|
||||
match ei_event {
|
||||
EiEvent::SeatAdded(s) => {
|
||||
s.seat.bind_capabilities(&[
|
||||
DeviceCapability::Pointer,
|
||||
DeviceCapability::PointerAbsolute,
|
||||
DeviceCapability::Keyboard,
|
||||
DeviceCapability::Touch,
|
||||
DeviceCapability::Scroll,
|
||||
DeviceCapability::Button,
|
||||
]);
|
||||
context.flush().unwrap();
|
||||
}
|
||||
EiEvent::SeatRemoved(_) => {}
|
||||
EiEvent::DeviceAdded(_) => {}
|
||||
EiEvent::DeviceRemoved(_) => {}
|
||||
EiEvent::DevicePaused(_) => {}
|
||||
EiEvent::DeviceResumed(_) => {}
|
||||
EiEvent::KeyboardModifiers(mods) => {
|
||||
let modifier_event = KeyboardEvent::Modifiers {
|
||||
mods_depressed: mods.depressed,
|
||||
mods_latched: mods.latched,
|
||||
mods_locked: mods.locked,
|
||||
group: mods.group,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
.send((current_client, Event::Keyboard(modifier_event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
EiEvent::Frame(_) => {}
|
||||
EiEvent::DeviceStartEmulating(_) => {
|
||||
log::debug!("START EMULATING =============>");
|
||||
}
|
||||
EiEvent::DeviceStopEmulating(_) => {
|
||||
log::debug!("==================> STOP EMULATING");
|
||||
}
|
||||
EiEvent::PointerMotion(motion) => {
|
||||
let motion_event = PointerEvent::Motion {
|
||||
time: motion.time as u32,
|
||||
relative_x: motion.dx as f64,
|
||||
relative_y: motion.dy as f64,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
.send((current_client, Event::Pointer(motion_event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
EiEvent::PointerMotionAbsolute(_) => {}
|
||||
EiEvent::Button(button) => {
|
||||
let button_event = PointerEvent::Button {
|
||||
time: button.time as u32,
|
||||
button: button.button,
|
||||
state: match button.state {
|
||||
ButtonState::Released => 0,
|
||||
ButtonState::Press => 1,
|
||||
},
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
.send((current_client, Event::Pointer(button_event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
EiEvent::ScrollDelta(delta) => {
|
||||
if let Some(handle) = current_client {
|
||||
let mut events = vec![];
|
||||
if delta.dy != 0. {
|
||||
events.push(PointerEvent::Axis {
|
||||
time: 0,
|
||||
axis: 0,
|
||||
value: delta.dy as f64,
|
||||
});
|
||||
}
|
||||
if delta.dx != 0. {
|
||||
events.push(PointerEvent::Axis {
|
||||
time: 0,
|
||||
axis: 1,
|
||||
value: delta.dx as f64,
|
||||
});
|
||||
}
|
||||
for event in events {
|
||||
event_tx
|
||||
.send((handle, Event::Pointer(event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
EiEvent::ScrollStop(_) => {}
|
||||
EiEvent::ScrollCancel(_) => {}
|
||||
EiEvent::ScrollDiscrete(scroll) => {
|
||||
if scroll.discrete_dy != 0 {
|
||||
let event = PointerEvent::AxisDiscrete120 {
|
||||
axis: 0,
|
||||
value: scroll.discrete_dy,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
.send((current_client, Event::Pointer(event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
if scroll.discrete_dx != 0 {
|
||||
let event = PointerEvent::AxisDiscrete120 {
|
||||
axis: 1,
|
||||
value: scroll.discrete_dx,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
.send((current_client, Event::Pointer(event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
EiEvent::KeyboardKey(key) => {
|
||||
let key_event = KeyboardEvent::Key {
|
||||
key: key.key,
|
||||
state: match key.state {
|
||||
KeyState::Press => 1,
|
||||
KeyState::Released => 0,
|
||||
},
|
||||
time: key.time as u32,
|
||||
};
|
||||
if let Some(current_client) = current_client {
|
||||
event_tx
|
||||
.send((current_client, Event::Keyboard(key_event)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
EiEvent::TouchDown(_) => {}
|
||||
EiEvent::TouchUp(_) => {}
|
||||
EiEvent::TouchMotion(_) => {}
|
||||
EiEvent::Disconnected(d) => {
|
||||
log::error!("disconnect: {d:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> {
|
||||
fn create(&mut self, handle: super::CaptureHandle, pos: super::Position) -> io::Result<()> {
|
||||
let notify_tx = self.notify_tx.clone();
|
||||
tokio::task::spawn_local(async move {
|
||||
let _ = notify_tx.send(ProducerEvent::Create(handle, pos)).await;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&mut self, handle: super::CaptureHandle) -> io::Result<()> {
|
||||
let notify_tx = self.notify_tx.clone();
|
||||
tokio::task::spawn_local(async move {
|
||||
let _ = notify_tx.send(ProducerEvent::Destroy(handle)).await;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
let notify_tx = self.notify_tx.clone();
|
||||
tokio::task::spawn_local(async move {
|
||||
let _ = notify_tx.send(ProducerEvent::Release).await;
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Stream for LibeiInputCapture<'a> {
|
||||
type Item = io::Result<(CaptureHandle, Event)>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match ready!(self.event_rx.poll_recv(cx)) {
|
||||
None => Poll::Ready(None),
|
||||
Some(e) => Poll::Ready(Some(Ok(e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
use crate::capture::error::MacOSInputCaptureCreationError;
|
||||
use crate::capture::{CaptureHandle, InputCapture, Position};
|
||||
use crate::event::Event;
|
||||
use futures_core::Stream;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{io, pin::Pin};
|
||||
|
||||
pub struct MacOSInputCapture;
|
||||
|
||||
impl MacOSInputCapture {
|
||||
pub fn new() -> std::result::Result<Self, MacOSInputCaptureCreationError> {
|
||||
Err(MacOSInputCaptureCreationError::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for MacOSInputCapture {
|
||||
type Item = io::Result<(CaptureHandle, Event)>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCapture for MacOSInputCapture {
|
||||
fn create(&mut self, _id: CaptureHandle, _pos: Position) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&mut self, _id: CaptureHandle) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,995 +0,0 @@
|
||||
use futures_core::Stream;
|
||||
use memmap::MmapOptions;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
env,
|
||||
io::{self, ErrorKind},
|
||||
os::fd::{AsFd, OwnedFd, RawFd},
|
||||
pin::Pin,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::io::unix::AsyncFd;
|
||||
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufWriter, Write},
|
||||
os::unix::prelude::{AsRawFd, FromRawFd},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use wayland_protocols::{
|
||||
wp::{
|
||||
keyboard_shortcuts_inhibit::zv1::client::{
|
||||
zwp_keyboard_shortcuts_inhibit_manager_v1::ZwpKeyboardShortcutsInhibitManagerV1,
|
||||
zwp_keyboard_shortcuts_inhibitor_v1::ZwpKeyboardShortcutsInhibitorV1,
|
||||
},
|
||||
pointer_constraints::zv1::client::{
|
||||
zwp_locked_pointer_v1::ZwpLockedPointerV1,
|
||||
zwp_pointer_constraints_v1::{Lifetime, ZwpPointerConstraintsV1},
|
||||
},
|
||||
relative_pointer::zv1::client::{
|
||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1,
|
||||
zwp_relative_pointer_v1::{self, ZwpRelativePointerV1},
|
||||
},
|
||||
},
|
||||
xdg::xdg_output::zv1::client::{
|
||||
zxdg_output_manager_v1::ZxdgOutputManagerV1,
|
||||
zxdg_output_v1::{self, ZxdgOutputV1},
|
||||
},
|
||||
};
|
||||
|
||||
use wayland_protocols_wlr::layer_shell::v1::client::{
|
||||
zwlr_layer_shell_v1::{Layer, ZwlrLayerShellV1},
|
||||
zwlr_layer_surface_v1::{self, Anchor, KeyboardInteractivity, ZwlrLayerSurfaceV1},
|
||||
};
|
||||
|
||||
use wayland_client::{
|
||||
backend::{ReadEventsGuard, WaylandError},
|
||||
delegate_noop,
|
||||
globals::{registry_queue_init, GlobalListContents},
|
||||
protocol::{
|
||||
wl_buffer, wl_compositor,
|
||||
wl_keyboard::{self, WlKeyboard},
|
||||
wl_output::{self, WlOutput},
|
||||
wl_pointer::{self, WlPointer},
|
||||
wl_region, wl_registry, wl_seat, wl_shm, wl_shm_pool,
|
||||
wl_surface::WlSurface,
|
||||
},
|
||||
Connection, Dispatch, DispatchError, EventQueue, QueueHandle, WEnum,
|
||||
};
|
||||
|
||||
use tempfile;
|
||||
|
||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||
|
||||
use super::{
|
||||
error::{LayerShellCaptureCreationError, WaylandBindError},
|
||||
CaptureHandle, InputCapture, Position,
|
||||
};
|
||||
|
||||
struct Globals {
|
||||
compositor: wl_compositor::WlCompositor,
|
||||
pointer_constraints: ZwpPointerConstraintsV1,
|
||||
relative_pointer_manager: ZwpRelativePointerManagerV1,
|
||||
shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1,
|
||||
seat: wl_seat::WlSeat,
|
||||
shm: wl_shm::WlShm,
|
||||
layer_shell: ZwlrLayerShellV1,
|
||||
outputs: Vec<WlOutput>,
|
||||
xdg_output_manager: ZxdgOutputManagerV1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct OutputInfo {
|
||||
name: String,
|
||||
position: (i32, i32),
|
||||
size: (i32, i32),
|
||||
}
|
||||
|
||||
impl OutputInfo {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
name: "".to_string(),
|
||||
position: (0, 0),
|
||||
size: (0, 0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct State {
|
||||
pointer: Option<WlPointer>,
|
||||
keyboard: Option<WlKeyboard>,
|
||||
pointer_lock: Option<ZwpLockedPointerV1>,
|
||||
rel_pointer: Option<ZwpRelativePointerV1>,
|
||||
shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
|
||||
client_for_window: Vec<(Rc<Window>, CaptureHandle)>,
|
||||
focused: Option<(Rc<Window>, CaptureHandle)>,
|
||||
g: Globals,
|
||||
wayland_fd: OwnedFd,
|
||||
read_guard: Option<ReadEventsGuard>,
|
||||
qh: QueueHandle<Self>,
|
||||
pending_events: VecDeque<(CaptureHandle, Event)>,
|
||||
output_info: Vec<(WlOutput, OutputInfo)>,
|
||||
scroll_discrete_pending: bool,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
state: State,
|
||||
queue: EventQueue<State>,
|
||||
}
|
||||
|
||||
impl AsRawFd for Inner {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.state.wayland_fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaylandInputCapture(AsyncFd<Inner>);
|
||||
|
||||
struct Window {
|
||||
buffer: wl_buffer::WlBuffer,
|
||||
surface: WlSurface,
|
||||
layer_surface: ZwlrLayerSurfaceV1,
|
||||
pos: Position,
|
||||
}
|
||||
|
||||
impl Window {
|
||||
fn new(
|
||||
state: &State,
|
||||
qh: &QueueHandle<State>,
|
||||
output: &WlOutput,
|
||||
pos: Position,
|
||||
size: (i32, i32),
|
||||
) -> Window {
|
||||
log::debug!("creating window output: {output:?}, size: {size:?}");
|
||||
let g = &state.g;
|
||||
|
||||
let (width, height) = match pos {
|
||||
Position::Left | Position::Right => (1, size.1 as u32),
|
||||
Position::Top | Position::Bottom => (size.0 as u32, 1),
|
||||
};
|
||||
let mut file = tempfile::tempfile().unwrap();
|
||||
draw(&mut file, (width, height));
|
||||
let pool = g
|
||||
.shm
|
||||
.create_pool(file.as_fd(), (width * height * 4) as i32, qh, ());
|
||||
let buffer = pool.create_buffer(
|
||||
0,
|
||||
width as i32,
|
||||
height as i32,
|
||||
(width * 4) as i32,
|
||||
wl_shm::Format::Argb8888,
|
||||
qh,
|
||||
(),
|
||||
);
|
||||
let surface = g.compositor.create_surface(qh, ());
|
||||
|
||||
let layer_surface = g.layer_shell.get_layer_surface(
|
||||
&surface,
|
||||
Some(output),
|
||||
Layer::Overlay,
|
||||
"LAN Mouse Sharing".into(),
|
||||
qh,
|
||||
(),
|
||||
);
|
||||
let anchor = match pos {
|
||||
Position::Left => Anchor::Left,
|
||||
Position::Right => Anchor::Right,
|
||||
Position::Top => Anchor::Top,
|
||||
Position::Bottom => Anchor::Bottom,
|
||||
};
|
||||
|
||||
layer_surface.set_anchor(anchor);
|
||||
layer_surface.set_size(width, height);
|
||||
layer_surface.set_exclusive_zone(-1);
|
||||
layer_surface.set_margin(0, 0, 0, 0);
|
||||
surface.set_input_region(None);
|
||||
surface.commit();
|
||||
Window {
|
||||
pos,
|
||||
buffer,
|
||||
surface,
|
||||
layer_surface,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
fn drop(&mut self) {
|
||||
log::debug!("destroying window!");
|
||||
self.layer_surface.destroy();
|
||||
self.surface.destroy();
|
||||
self.buffer.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
fn get_edges(outputs: &[(WlOutput, OutputInfo)], pos: Position) -> Vec<(WlOutput, i32)> {
|
||||
outputs
|
||||
.iter()
|
||||
.map(|(o, i)| {
|
||||
(
|
||||
o.clone(),
|
||||
match pos {
|
||||
Position::Left => i.position.0,
|
||||
Position::Right => i.position.0 + i.size.0,
|
||||
Position::Top => i.position.1,
|
||||
Position::Bottom => i.position.1 + i.size.1,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, OutputInfo)> {
|
||||
// get all output edges corresponding to the position
|
||||
let edges = get_edges(&state.output_info, pos);
|
||||
log::debug!("edges: {edges:?}");
|
||||
let opposite_edges = get_edges(&state.output_info, pos.opposite());
|
||||
|
||||
// remove those edges that are at the same position
|
||||
// as an opposite edge of a different output
|
||||
let outputs: Vec<WlOutput> = edges
|
||||
.iter()
|
||||
.filter(|(_, edge)| !opposite_edges.iter().map(|(_, e)| *e).any(|e| &e == edge))
|
||||
.map(|(o, _)| o.clone())
|
||||
.collect();
|
||||
state
|
||||
.output_info
|
||||
.iter()
|
||||
.filter(|(o, _)| outputs.contains(o))
|
||||
.map(|(o, i)| (o.clone(), i.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn draw(f: &mut File, (width, height): (u32, u32)) {
|
||||
let mut buf = BufWriter::new(f);
|
||||
for _ in 0..height {
|
||||
for _ in 0..width {
|
||||
if env::var("LM_DEBUG_LAYER_SHELL").ok().is_some() {
|
||||
// AARRGGBB
|
||||
buf.write_all(&0xff11d116u32.to_ne_bytes()).unwrap();
|
||||
} else {
|
||||
// AARRGGBB
|
||||
buf.write_all(&0x00000000u32.to_ne_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandInputCapture {
|
||||
pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (g, mut queue) = registry_queue_init::<State>(&conn)?;
|
||||
|
||||
let qh = queue.handle();
|
||||
|
||||
let compositor: wl_compositor::WlCompositor = g
|
||||
.bind(&qh, 4..=5, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?;
|
||||
let xdg_output_manager: ZxdgOutputManagerV1 = g
|
||||
.bind(&qh, 1..=3, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?;
|
||||
let shm: wl_shm::WlShm = g
|
||||
.bind(&qh, 1..=1, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "wl_shm"))?;
|
||||
let layer_shell: ZwlrLayerShellV1 = g
|
||||
.bind(&qh, 3..=4, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?;
|
||||
let seat: wl_seat::WlSeat = g
|
||||
.bind(&qh, 7..=8, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
|
||||
|
||||
let pointer_constraints: ZwpPointerConstraintsV1 = g
|
||||
.bind(&qh, 1..=1, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "zwp_pointer_constraints_v1"))?;
|
||||
let relative_pointer_manager: ZwpRelativePointerManagerV1 = g
|
||||
.bind(&qh, 1..=1, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "zwp_relative_pointer_manager_v1"))?;
|
||||
let shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1 = g
|
||||
.bind(&qh, 1..=1, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "zwp_keyboard_shortcuts_inhibit_manager_v1"))?;
|
||||
let outputs = vec![];
|
||||
|
||||
let g = Globals {
|
||||
compositor,
|
||||
shm,
|
||||
layer_shell,
|
||||
seat,
|
||||
pointer_constraints,
|
||||
relative_pointer_manager,
|
||||
shortcut_inhibit_manager,
|
||||
outputs,
|
||||
xdg_output_manager,
|
||||
};
|
||||
|
||||
// flush outgoing events
|
||||
queue.flush()?;
|
||||
|
||||
// prepare reading wayland events
|
||||
let read_guard = queue.prepare_read().unwrap(); // there can not yet be events to dispatch
|
||||
let wayland_fd = read_guard.connection_fd().try_clone_to_owned().unwrap();
|
||||
std::mem::drop(read_guard);
|
||||
|
||||
let mut state = State {
|
||||
pointer: None,
|
||||
keyboard: None,
|
||||
g,
|
||||
pointer_lock: None,
|
||||
rel_pointer: None,
|
||||
shortcut_inhibitor: None,
|
||||
client_for_window: Vec::new(),
|
||||
focused: None,
|
||||
qh,
|
||||
wayland_fd,
|
||||
read_guard: None,
|
||||
pending_events: VecDeque::new(),
|
||||
output_info: vec![],
|
||||
scroll_discrete_pending: false,
|
||||
};
|
||||
|
||||
// dispatch registry to () again, in order to read all wl_outputs
|
||||
conn.display().get_registry(&state.qh, ());
|
||||
log::debug!("==============> requested registry");
|
||||
|
||||
// roundtrip to read wl_output globals
|
||||
queue.roundtrip(&mut state)?;
|
||||
log::debug!("==============> roundtrip 1 done");
|
||||
|
||||
// read outputs
|
||||
for output in state.g.outputs.iter() {
|
||||
state
|
||||
.g
|
||||
.xdg_output_manager
|
||||
.get_xdg_output(output, &state.qh, output.clone());
|
||||
}
|
||||
|
||||
// roundtrip to read xdg_output events
|
||||
queue.roundtrip(&mut state)?;
|
||||
|
||||
log::debug!("==============> roundtrip 2 done");
|
||||
for i in &state.output_info {
|
||||
log::debug!("{:#?}", i.1);
|
||||
}
|
||||
|
||||
let read_guard = loop {
|
||||
match queue.prepare_read() {
|
||||
Some(r) => break r,
|
||||
None => {
|
||||
queue.dispatch_pending(&mut state)?;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
state.read_guard = Some(read_guard);
|
||||
|
||||
let inner = AsyncFd::new(Inner { queue, state })?;
|
||||
|
||||
Ok(WaylandInputCapture(inner))
|
||||
}
|
||||
|
||||
fn add_client(&mut self, handle: CaptureHandle, pos: Position) {
|
||||
self.0.get_mut().state.add_client(handle, pos);
|
||||
}
|
||||
|
||||
fn delete_client(&mut self, handle: CaptureHandle) {
|
||||
let inner = self.0.get_mut();
|
||||
// remove all windows corresponding to this client
|
||||
while let Some(i) = inner
|
||||
.state
|
||||
.client_for_window
|
||||
.iter()
|
||||
.position(|(_, c)| *c == handle)
|
||||
{
|
||||
inner.state.client_for_window.remove(i);
|
||||
inner.state.focused = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn grab(
|
||||
&mut self,
|
||||
surface: &WlSurface,
|
||||
pointer: &WlPointer,
|
||||
serial: u32,
|
||||
qh: &QueueHandle<State>,
|
||||
) {
|
||||
let (window, _) = self.focused.as_ref().unwrap();
|
||||
|
||||
// hide the cursor
|
||||
pointer.set_cursor(serial, None, 0, 0);
|
||||
|
||||
// capture input
|
||||
window
|
||||
.layer_surface
|
||||
.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
|
||||
window.surface.commit();
|
||||
|
||||
// lock pointer
|
||||
if self.pointer_lock.is_none() {
|
||||
self.pointer_lock = Some(self.g.pointer_constraints.lock_pointer(
|
||||
surface,
|
||||
pointer,
|
||||
None,
|
||||
Lifetime::Persistent,
|
||||
qh,
|
||||
(),
|
||||
));
|
||||
}
|
||||
|
||||
// request relative input
|
||||
if self.rel_pointer.is_none() {
|
||||
self.rel_pointer = Some(self.g.relative_pointer_manager.get_relative_pointer(
|
||||
pointer,
|
||||
qh,
|
||||
(),
|
||||
));
|
||||
}
|
||||
|
||||
// capture modifier keys
|
||||
if self.shortcut_inhibitor.is_none() {
|
||||
self.shortcut_inhibitor = Some(self.g.shortcut_inhibit_manager.inhibit_shortcuts(
|
||||
surface,
|
||||
&self.g.seat,
|
||||
qh,
|
||||
(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn ungrab(&mut self) {
|
||||
// get focused client
|
||||
let (window, _client) = match self.focused.as_ref() {
|
||||
Some(focused) => focused,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// ungrab surface
|
||||
window
|
||||
.layer_surface
|
||||
.set_keyboard_interactivity(KeyboardInteractivity::None);
|
||||
window.surface.commit();
|
||||
|
||||
// destroy pointer lock
|
||||
if let Some(pointer_lock) = &self.pointer_lock {
|
||||
pointer_lock.destroy();
|
||||
self.pointer_lock = None;
|
||||
}
|
||||
|
||||
// destroy relative input
|
||||
if let Some(rel_pointer) = &self.rel_pointer {
|
||||
rel_pointer.destroy();
|
||||
self.rel_pointer = None;
|
||||
}
|
||||
|
||||
// destroy shortcut inhibitor
|
||||
if let Some(shortcut_inhibitor) = &self.shortcut_inhibitor {
|
||||
shortcut_inhibitor.destroy();
|
||||
self.shortcut_inhibitor = None;
|
||||
}
|
||||
}
|
||||
|
||||
fn add_client(&mut self, client: CaptureHandle, pos: Position) {
|
||||
let outputs = get_output_configuration(self, pos);
|
||||
|
||||
log::debug!("outputs: {outputs:?}");
|
||||
outputs.iter().for_each(|(o, i)| {
|
||||
let window = Window::new(self, &self.qh, o, pos, i.size);
|
||||
let window = Rc::new(window);
|
||||
self.client_for_window.push((window, client));
|
||||
});
|
||||
}
|
||||
|
||||
fn update_windows(&mut self) {
|
||||
log::debug!("updating windows");
|
||||
log::debug!("output info: {:?}", self.output_info);
|
||||
let clients: Vec<_> = self
|
||||
.client_for_window
|
||||
.drain(..)
|
||||
.map(|(w, c)| (c, w.pos))
|
||||
.collect();
|
||||
for (client, pos) in clients {
|
||||
self.add_client(client, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn read(&mut self) -> bool {
|
||||
match self.state.read_guard.take().unwrap().read() {
|
||||
Ok(_) => true,
|
||||
Err(WaylandError::Io(e)) if e.kind() == ErrorKind::WouldBlock => false,
|
||||
Err(WaylandError::Io(e)) => {
|
||||
log::error!("error reading from wayland socket: {e}");
|
||||
false
|
||||
}
|
||||
Err(WaylandError::Protocol(e)) => {
|
||||
panic!("wayland protocol violation: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_read(&mut self) -> io::Result<()> {
|
||||
loop {
|
||||
match self.queue.prepare_read() {
|
||||
None => match self.queue.dispatch_pending(&mut self.state) {
|
||||
Ok(_) => continue,
|
||||
Err(DispatchError::Backend(WaylandError::Io(e))) => return Err(e),
|
||||
Err(e) => panic!("failed to dispatch wayland events: {e}"),
|
||||
},
|
||||
Some(r) => {
|
||||
self.state.read_guard = Some(r);
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_events(&mut self) {
|
||||
match self.queue.dispatch_pending(&mut self.state) {
|
||||
Ok(_) => {}
|
||||
Err(DispatchError::Backend(WaylandError::Io(e))) => {
|
||||
log::error!("Wayland Error: {}", e);
|
||||
}
|
||||
Err(DispatchError::Backend(e)) => {
|
||||
panic!("backend error: {}", e);
|
||||
}
|
||||
Err(DispatchError::BadMessage {
|
||||
sender_id,
|
||||
interface,
|
||||
opcode,
|
||||
}) => {
|
||||
panic!("bad message {}, {} , {}", sender_id, interface, opcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_events(&mut self) -> io::Result<()> {
|
||||
// flush outgoing events
|
||||
match self.queue.flush() {
|
||||
Ok(_) => (),
|
||||
Err(e) => match e {
|
||||
WaylandError::Io(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
WaylandError::Protocol(e) => {
|
||||
panic!("wayland protocol violation: {e}")
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCapture for WaylandInputCapture {
|
||||
fn create(&mut self, handle: CaptureHandle, pos: Position) -> io::Result<()> {
|
||||
self.add_client(handle, pos);
|
||||
let inner = self.0.get_mut();
|
||||
inner.flush_events()
|
||||
}
|
||||
fn destroy(&mut self, handle: CaptureHandle) -> io::Result<()> {
|
||||
self.delete_client(handle);
|
||||
let inner = self.0.get_mut();
|
||||
inner.flush_events()
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
log::debug!("releasing pointer");
|
||||
let inner = self.0.get_mut();
|
||||
inner.state.ungrab();
|
||||
inner.flush_events()
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WaylandInputCapture {
|
||||
type Item = io::Result<(CaptureHandle, Event)>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
if let Some(event) = self.0.get_mut().state.pending_events.pop_front() {
|
||||
return Poll::Ready(Some(Ok(event)));
|
||||
}
|
||||
|
||||
loop {
|
||||
let mut guard = ready!(self.0.poll_read_ready_mut(cx))?;
|
||||
|
||||
{
|
||||
let inner = guard.get_inner_mut();
|
||||
|
||||
// read events
|
||||
while inner.read() {
|
||||
// prepare next read
|
||||
match inner.prepare_read() {
|
||||
Ok(_) => {}
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch the events
|
||||
inner.dispatch_events();
|
||||
|
||||
// flush outgoing events
|
||||
if let Err(e) = inner.flush_events() {
|
||||
if e.kind() != ErrorKind::WouldBlock {
|
||||
return Poll::Ready(Some(Err(e)));
|
||||
}
|
||||
}
|
||||
|
||||
// prepare for the next read
|
||||
match inner.prepare_read() {
|
||||
Ok(_) => {}
|
||||
Err(e) => return Poll::Ready(Some(Err(e))),
|
||||
}
|
||||
}
|
||||
|
||||
// clear read readiness for tokio read guard
|
||||
// guard.clear_ready_matching(Ready::READABLE);
|
||||
guard.clear_ready();
|
||||
|
||||
// if an event has been queued during dispatch_events() we return it
|
||||
match guard.get_inner_mut().state.pending_events.pop_front() {
|
||||
Some(event) => return Poll::Ready(Some(Ok(event))),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_seat::WlSeat, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
seat: &wl_seat::WlSeat,
|
||||
event: <wl_seat::WlSeat as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_seat::Event::Capabilities {
|
||||
capabilities: WEnum::Value(capabilities),
|
||||
} = event
|
||||
{
|
||||
if capabilities.contains(wl_seat::Capability::Pointer) && state.pointer.is_none() {
|
||||
state.pointer.replace(seat.get_pointer(qh, ()));
|
||||
}
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) && state.keyboard.is_none() {
|
||||
seat.get_keyboard(qh, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlPointer, ()> for State {
|
||||
fn event(
|
||||
app: &mut Self,
|
||||
pointer: &WlPointer,
|
||||
event: <WlPointer as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
wl_pointer::Event::Enter {
|
||||
serial,
|
||||
surface,
|
||||
surface_x: _,
|
||||
surface_y: _,
|
||||
} => {
|
||||
// get client corresponding to the focused surface
|
||||
{
|
||||
if let Some((window, client)) = app
|
||||
.client_for_window
|
||||
.iter()
|
||||
.find(|(w, _c)| w.surface == surface)
|
||||
{
|
||||
app.focused = Some((window.clone(), *client));
|
||||
app.grab(&surface, pointer, serial, qh);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let (_, client) = app
|
||||
.client_for_window
|
||||
.iter()
|
||||
.find(|(w, _c)| w.surface == surface)
|
||||
.unwrap();
|
||||
app.pending_events.push_back((*client, Event::Enter()));
|
||||
}
|
||||
wl_pointer::Event::Leave { .. } => {
|
||||
/* There are rare cases, where when a window is opened in
|
||||
* just the wrong moment, the pointer is released, while
|
||||
* still grabbed.
|
||||
* In that case, the pointer must be ungrabbed, otherwise
|
||||
* it is impossible to grab it again (since the pointer
|
||||
* lock, relative pointer,... objects are still in place)
|
||||
*/
|
||||
if app.pointer_lock.is_some() {
|
||||
log::warn!("compositor released mouse");
|
||||
}
|
||||
app.ungrab();
|
||||
}
|
||||
wl_pointer::Event::Button {
|
||||
serial: _,
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::Button {
|
||||
time,
|
||||
button,
|
||||
state: u32::from(state),
|
||||
}),
|
||||
));
|
||||
}
|
||||
wl_pointer::Event::Axis { time, axis, value } => {
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
if app.scroll_discrete_pending {
|
||||
// each axisvalue120 event is coupled with
|
||||
// a corresponding axis event, which needs to
|
||||
// be ignored to not duplicate the scrolling
|
||||
app.scroll_discrete_pending = false;
|
||||
} else {
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::Axis {
|
||||
time,
|
||||
axis: u32::from(axis) as u8,
|
||||
value,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
wl_pointer::Event::AxisValue120 { axis, value120 } => {
|
||||
let (_, client) = app.focused.as_ref().unwrap();
|
||||
app.scroll_discrete_pending = true;
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::AxisDiscrete120 {
|
||||
axis: u32::from(axis) as u8,
|
||||
value: value120,
|
||||
}),
|
||||
));
|
||||
}
|
||||
wl_pointer::Event::Frame {} => {
|
||||
// TODO properly handle frame events
|
||||
// we simply insert a frame event on the client side
|
||||
// after each event for now
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlKeyboard, ()> for State {
|
||||
fn event(
|
||||
app: &mut Self,
|
||||
_: &WlKeyboard,
|
||||
event: wl_keyboard::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
let (_window, client) = match &app.focused {
|
||||
Some(focused) => (Some(&focused.0), Some(&focused.1)),
|
||||
None => (None, None),
|
||||
};
|
||||
match event {
|
||||
wl_keyboard::Event::Key {
|
||||
serial: _,
|
||||
time,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
if let Some(client) = client {
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Keyboard(KeyboardEvent::Key {
|
||||
time,
|
||||
key,
|
||||
state: u32::from(state) as u8,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
wl_keyboard::Event::Modifiers {
|
||||
serial: _,
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
} => {
|
||||
if let Some(client) = client {
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Keyboard(KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
wl_keyboard::Event::Keymap {
|
||||
format: _,
|
||||
fd,
|
||||
size: _,
|
||||
} => {
|
||||
let fd = unsafe { &File::from_raw_fd(fd.as_raw_fd()) };
|
||||
let _mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() };
|
||||
// TODO keymap
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwpRelativePointerV1, ()> for State {
|
||||
fn event(
|
||||
app: &mut Self,
|
||||
_: &ZwpRelativePointerV1,
|
||||
event: <ZwpRelativePointerV1 as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let zwp_relative_pointer_v1::Event::RelativeMotion {
|
||||
utime_hi,
|
||||
utime_lo,
|
||||
dx: _,
|
||||
dy: _,
|
||||
dx_unaccel: surface_x,
|
||||
dy_unaccel: surface_y,
|
||||
} = event
|
||||
{
|
||||
if let Some((_window, client)) = &app.focused {
|
||||
let time = (((utime_hi as u64) << 32 | utime_lo as u64) / 1000) as u32;
|
||||
app.pending_events.push_back((
|
||||
*client,
|
||||
Event::Pointer(PointerEvent::Motion {
|
||||
time,
|
||||
relative_x: surface_x,
|
||||
relative_y: surface_y,
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZwlrLayerSurfaceV1, ()> for State {
|
||||
fn event(
|
||||
app: &mut Self,
|
||||
layer_surface: &ZwlrLayerSurfaceV1,
|
||||
event: <ZwlrLayerSurfaceV1 as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event {
|
||||
if let Some((window, _client)) = app
|
||||
.client_for_window
|
||||
.iter()
|
||||
.find(|(w, _c)| &w.layer_surface == layer_surface)
|
||||
{
|
||||
// client corresponding to the layer_surface
|
||||
let surface = &window.surface;
|
||||
let buffer = &window.buffer;
|
||||
surface.attach(Some(buffer), 0, 0);
|
||||
layer_surface.ack_configure(serial);
|
||||
surface.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delegate wl_registry events to App itself
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
_state: &mut Self,
|
||||
_proxy: &wl_registry::WlRegistry,
|
||||
_event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
|
||||
_data: &GlobalListContents,
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
registry: &wl_registry::WlRegistry,
|
||||
event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
) {
|
||||
match event {
|
||||
wl_registry::Event::Global {
|
||||
name,
|
||||
interface,
|
||||
version: _,
|
||||
} => {
|
||||
if interface.as_str() == "wl_output" {
|
||||
log::debug!("wl_output global");
|
||||
state
|
||||
.g
|
||||
.outputs
|
||||
.push(registry.bind::<WlOutput, _, _>(name, 4, qh, ()))
|
||||
}
|
||||
}
|
||||
wl_registry::Event::GlobalRemove { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<ZxdgOutputV1, WlOutput> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &ZxdgOutputV1,
|
||||
event: <ZxdgOutputV1 as wayland_client::Proxy>::Event,
|
||||
wl_output: &WlOutput,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
log::debug!("xdg-output - {event:?}");
|
||||
let output_info = match state.output_info.iter_mut().find(|(o, _)| o == wl_output) {
|
||||
Some((_, c)) => c,
|
||||
None => {
|
||||
let output_info = OutputInfo::new();
|
||||
state.output_info.push((wl_output.clone(), output_info));
|
||||
&mut state.output_info.last_mut().unwrap().1
|
||||
}
|
||||
};
|
||||
|
||||
match event {
|
||||
zxdg_output_v1::Event::LogicalPosition { x, y } => {
|
||||
output_info.position = (x, y);
|
||||
}
|
||||
zxdg_output_v1::Event::LogicalSize { width, height } => {
|
||||
output_info.size = (width, height);
|
||||
}
|
||||
zxdg_output_v1::Event::Done => {}
|
||||
zxdg_output_v1::Event::Name { name } => {
|
||||
output_info.name = name;
|
||||
}
|
||||
zxdg_output_v1::Event::Description { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlOutput, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_proxy: &WlOutput,
|
||||
event: <WlOutput as wayland_client::Proxy>::Event,
|
||||
_data: &(),
|
||||
_conn: &Connection,
|
||||
_qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_output::Event::Done = event {
|
||||
state.update_windows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// don't emit any events
|
||||
delegate_noop!(State: wl_region::WlRegion);
|
||||
delegate_noop!(State: wl_shm_pool::WlShmPool);
|
||||
delegate_noop!(State: wl_compositor::WlCompositor);
|
||||
delegate_noop!(State: ZwlrLayerShellV1);
|
||||
delegate_noop!(State: ZwpRelativePointerManagerV1);
|
||||
delegate_noop!(State: ZwpKeyboardShortcutsInhibitManagerV1);
|
||||
delegate_noop!(State: ZwpPointerConstraintsV1);
|
||||
|
||||
// ignore events
|
||||
delegate_noop!(State: ignore ZxdgOutputManagerV1);
|
||||
delegate_noop!(State: ignore wl_shm::WlShm);
|
||||
delegate_noop!(State: ignore wl_buffer::WlBuffer);
|
||||
delegate_noop!(State: ignore WlSurface);
|
||||
delegate_noop!(State: ignore ZwpKeyboardShortcutsInhibitorV1);
|
||||
delegate_noop!(State: ignore ZwpLockedPointerV1);
|
||||
@@ -1,630 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use core::task::{Context, Poll};
|
||||
use futures::Stream;
|
||||
use once_cell::unsync::Lazy;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::ptr::{addr_of, addr_of_mut};
|
||||
|
||||
use futures::executor::block_on;
|
||||
use std::default::Default;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::{mpsc, Mutex};
|
||||
use std::task::ready;
|
||||
use std::{io, pin::Pin, thread};
|
||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||
use windows::core::{w, PCWSTR};
|
||||
use windows::Win32::Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM};
|
||||
use windows::Win32::Graphics::Gdi::{
|
||||
EnumDisplayDevicesW, EnumDisplaySettingsW, DEVMODEW, DISPLAY_DEVICEW,
|
||||
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, ENUM_CURRENT_SETTINGS,
|
||||
};
|
||||
use windows::Win32::System::LibraryLoader::GetModuleHandleW;
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW,
|
||||
RegisterClassW, SetWindowsHookExW, TranslateMessage, EDD_GET_DEVICE_INTERFACE_NAME, HHOOK,
|
||||
HMENU, HOOKPROC, KBDLLHOOKSTRUCT, LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL,
|
||||
WH_MOUSE_LL, WINDOW_STYLE, WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN,
|
||||
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN,
|
||||
WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW,
|
||||
WNDPROC,
|
||||
};
|
||||
|
||||
use crate::event::{
|
||||
KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
||||
};
|
||||
use crate::{event::Event, scancode, scancode::Linux};
|
||||
|
||||
use super::{CaptureHandle, InputCapture, Position};
|
||||
|
||||
enum Request {
|
||||
Create(CaptureHandle, Position),
|
||||
Destroy(CaptureHandle),
|
||||
}
|
||||
|
||||
pub struct WindowsInputCapture {
|
||||
event_rx: Receiver<(CaptureHandle, Event)>,
|
||||
msg_thread: Option<std::thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
Request = 0,
|
||||
Release = 1,
|
||||
Exit = 2,
|
||||
}
|
||||
|
||||
unsafe fn signal_message_thread(event_type: EventType) {
|
||||
if let Some(event_tid) = get_event_tid() {
|
||||
PostThreadMessageW(event_tid, WM_USER, WPARAM(event_type as usize), LPARAM(0)).unwrap();
|
||||
} else {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCapture for WindowsInputCapture {
|
||||
fn create(&mut self, handle: CaptureHandle, pos: Position) -> io::Result<()> {
|
||||
unsafe {
|
||||
{
|
||||
let mut requests = REQUEST_BUFFER.lock().unwrap();
|
||||
requests.push(Request::Create(handle, pos));
|
||||
}
|
||||
signal_message_thread(EventType::Request);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn destroy(&mut self, handle: CaptureHandle) -> io::Result<()> {
|
||||
unsafe {
|
||||
{
|
||||
let mut requests = REQUEST_BUFFER.lock().unwrap();
|
||||
requests.push(Request::Destroy(handle));
|
||||
}
|
||||
signal_message_thread(EventType::Request);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
unsafe { signal_message_thread(EventType::Release) };
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
static mut REQUEST_BUFFER: Mutex<Vec<Request>> = Mutex::new(Vec::new());
|
||||
static mut ACTIVE_CLIENT: Option<CaptureHandle> = None;
|
||||
static mut CLIENT_FOR_POS: Lazy<HashMap<Position, CaptureHandle>> = Lazy::new(HashMap::new);
|
||||
static mut EVENT_TX: Option<Sender<(CaptureHandle, Event)>> = None;
|
||||
static mut EVENT_THREAD_ID: AtomicU32 = AtomicU32::new(0);
|
||||
unsafe fn set_event_tid(tid: u32) {
|
||||
EVENT_THREAD_ID.store(tid, Ordering::SeqCst);
|
||||
}
|
||||
unsafe fn get_event_tid() -> Option<u32> {
|
||||
match EVENT_THREAD_ID.load(Ordering::SeqCst) {
|
||||
0 => None,
|
||||
id => Some(id),
|
||||
}
|
||||
}
|
||||
|
||||
static mut ENTRY_POINT: (i32, i32) = (0, 0);
|
||||
|
||||
fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
|
||||
let mouse_low_level: MSLLHOOKSTRUCT = unsafe { *(lparam.0 as *const MSLLHOOKSTRUCT) };
|
||||
match wparam {
|
||||
WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_LEFT,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_MBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_MIDDLE,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_RBUTTONDOWN as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_RIGHT,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_LBUTTONUP as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_LEFT,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_MBUTTONUP as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_MIDDLE,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_RBUTTONUP as usize => Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button: BTN_RIGHT,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_MOUSEMOVE as usize => unsafe {
|
||||
let (x, y) = (mouse_low_level.pt.x, mouse_low_level.pt.y);
|
||||
let (ex, ey) = ENTRY_POINT;
|
||||
let (dx, dy) = (x - ex, y - ey);
|
||||
Some(PointerEvent::Motion {
|
||||
time: 0,
|
||||
relative_x: dx as f64,
|
||||
relative_y: dy as f64,
|
||||
})
|
||||
},
|
||||
WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 {
|
||||
axis: 0,
|
||||
value: -(mouse_low_level.mouseData as i32 >> 16),
|
||||
}),
|
||||
WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => {
|
||||
let hb = mouse_low_level.mouseData >> 16;
|
||||
let button = match hb {
|
||||
1 => BTN_BACK,
|
||||
2 => BTN_FORWARD,
|
||||
_ => {
|
||||
log::warn!("unknown mouse button");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
Some(PointerEvent::Button {
|
||||
time: 0,
|
||||
button,
|
||||
state: if p == WM_XBUTTONDOWN as usize { 1 } else { 0 },
|
||||
})
|
||||
}
|
||||
w => {
|
||||
log::warn!("unknown mouse event: {w:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> {
|
||||
let kybrdllhookstruct: KBDLLHOOKSTRUCT = *(lparam.0 as *const KBDLLHOOKSTRUCT);
|
||||
let mut scan_code = kybrdllhookstruct.scanCode;
|
||||
log::trace!("scan_code: {scan_code}");
|
||||
if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) {
|
||||
scan_code |= 0xE000;
|
||||
}
|
||||
let Ok(win_scan_code) = scancode::Windows::try_from(scan_code) else {
|
||||
log::warn!("failed to translate to windows scancode: {scan_code}");
|
||||
return None;
|
||||
};
|
||||
log::trace!("windows_scan: {win_scan_code:?}");
|
||||
let Ok(linux_scan_code): Result<Linux, ()> = win_scan_code.try_into() else {
|
||||
log::warn!("failed to translate into linux scancode: {win_scan_code:?}");
|
||||
return None;
|
||||
};
|
||||
log::trace!("windows_scan: {linux_scan_code:?}");
|
||||
let scan_code = linux_scan_code as u32;
|
||||
match wparam {
|
||||
WPARAM(p) if p == WM_KEYDOWN as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_KEYUP as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 0,
|
||||
}),
|
||||
WPARAM(p) if p == WM_SYSKEYDOWN as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 1,
|
||||
}),
|
||||
WPARAM(p) if p == WM_SYSKEYUP as usize => Some(KeyboardEvent::Key {
|
||||
time: 0,
|
||||
key: scan_code,
|
||||
state: 1,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// clamp point to display bounds
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `prev_point`: coordinates, the cursor was before entering, within bounds of a display
|
||||
/// * `entry_point`: point to clamp
|
||||
///
|
||||
/// returns: (i32, i32), the corrected entry point
|
||||
///
|
||||
fn clamp_to_display_bounds(prev_point: (i32, i32), point: (i32, i32)) -> (i32, i32) {
|
||||
/* find display where movement came from */
|
||||
let display_regions = unsafe { get_display_regions() };
|
||||
let display = display_regions
|
||||
.iter()
|
||||
.find(|&d| is_within_dp_region(prev_point, d))
|
||||
.unwrap();
|
||||
|
||||
/* clamp to bounds (inclusive) */
|
||||
let (x, y) = point;
|
||||
let (min_x, max_x) = (display.left, display.right - 1);
|
||||
let (min_y, max_y) = (display.top, display.bottom - 1);
|
||||
(x.clamp(min_x, max_x), y.clamp(min_y, max_y))
|
||||
}
|
||||
|
||||
unsafe fn send_blocking(event: Event) {
|
||||
if let Some(active) = ACTIVE_CLIENT {
|
||||
block_on(async move {
|
||||
let _ = EVENT_TX.as_ref().unwrap().send((active, event)).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn check_client_activation(wparam: WPARAM, lparam: LPARAM) -> bool {
|
||||
if wparam.0 != WM_MOUSEMOVE as usize {
|
||||
return ACTIVE_CLIENT.is_some();
|
||||
}
|
||||
let mouse_low_level: MSLLHOOKSTRUCT = *(lparam.0 as *const MSLLHOOKSTRUCT);
|
||||
static mut PREV_POS: Option<(i32, i32)> = None;
|
||||
let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y);
|
||||
let prev_pos = PREV_POS.unwrap_or(curr_pos);
|
||||
PREV_POS.replace(curr_pos);
|
||||
|
||||
/* next event is the first actual event */
|
||||
let ret = ACTIVE_CLIENT.is_some();
|
||||
|
||||
/* client already active, no need to check */
|
||||
if ACTIVE_CLIENT.is_some() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* check if a client was activated */
|
||||
let Some(pos) = entered_barrier(prev_pos, curr_pos, get_display_regions()) else {
|
||||
return ret;
|
||||
};
|
||||
|
||||
/* check if a client is registered for the barrier */
|
||||
let Some(client) = CLIENT_FOR_POS.get(&pos) else {
|
||||
return ret;
|
||||
};
|
||||
|
||||
/* update active client and entry point */
|
||||
ACTIVE_CLIENT.replace(*client);
|
||||
ENTRY_POINT = clamp_to_display_bounds(prev_pos, curr_pos);
|
||||
|
||||
/* notify main thread */
|
||||
log::debug!("ENTERED @ {prev_pos:?} -> {curr_pos:?}");
|
||||
send_blocking(Event::Enter());
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
let active = check_client_activation(wparam, lparam);
|
||||
|
||||
/* no client was active */
|
||||
if !active {
|
||||
return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam);
|
||||
}
|
||||
|
||||
/* get active client if any */
|
||||
let Some(client) = ACTIVE_CLIENT else {
|
||||
return LRESULT(1);
|
||||
};
|
||||
|
||||
/* convert to lan-mouse event */
|
||||
let Some(pointer_event) = to_mouse_event(wparam, lparam) else {
|
||||
return LRESULT(1);
|
||||
};
|
||||
let event = (client, Event::Pointer(pointer_event));
|
||||
|
||||
/* notify mainthread (drop events if sending too fast) */
|
||||
if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) {
|
||||
log::warn!("e: {e}");
|
||||
}
|
||||
|
||||
/* don't pass event to applications */
|
||||
LRESULT(1)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn kybrd_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||
/* get active client if any */
|
||||
let Some(client) = ACTIVE_CLIENT else {
|
||||
return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam);
|
||||
};
|
||||
|
||||
/* convert to key event */
|
||||
let Some(key_event) = to_key_event(wparam, lparam) else {
|
||||
return LRESULT(1);
|
||||
};
|
||||
let event = (client, Event::Keyboard(key_event));
|
||||
|
||||
if let Err(e) = EVENT_TX.as_ref().unwrap().try_send(event) {
|
||||
log::warn!("e: {e}");
|
||||
}
|
||||
|
||||
/* don't pass event to applications */
|
||||
LRESULT(1)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn window_proc(
|
||||
_hwnd: HWND,
|
||||
uint: u32,
|
||||
_wparam: WPARAM,
|
||||
_lparam: LPARAM,
|
||||
) -> LRESULT {
|
||||
match uint {
|
||||
x if x == WM_DISPLAYCHANGE => {
|
||||
log::debug!("display resolution changed");
|
||||
DISPLAY_RESOLUTION_CHANGED = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
LRESULT(1)
|
||||
}
|
||||
|
||||
fn enumerate_displays() -> Vec<RECT> {
|
||||
unsafe {
|
||||
let mut display_rects = vec![];
|
||||
let mut devices = vec![];
|
||||
for i in 0.. {
|
||||
let mut device: DISPLAY_DEVICEW = std::mem::zeroed();
|
||||
device.cb = std::mem::size_of::<DISPLAY_DEVICEW>() as u32;
|
||||
let ret = EnumDisplayDevicesW(None, i, &mut device, EDD_GET_DEVICE_INTERFACE_NAME);
|
||||
if ret == FALSE {
|
||||
break;
|
||||
}
|
||||
if device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP != 0 {
|
||||
devices.push(device.DeviceName);
|
||||
}
|
||||
}
|
||||
for device in devices {
|
||||
let mut dev_mode: DEVMODEW = std::mem::zeroed();
|
||||
dev_mode.dmSize = std::mem::size_of::<DEVMODEW>() as u16;
|
||||
let ret = EnumDisplaySettingsW(
|
||||
PCWSTR::from_raw(&device as *const _),
|
||||
ENUM_CURRENT_SETTINGS,
|
||||
&mut dev_mode,
|
||||
);
|
||||
if ret == FALSE {
|
||||
log::warn!("no display mode");
|
||||
}
|
||||
|
||||
let pos = dev_mode.Anonymous1.Anonymous2.dmPosition;
|
||||
let (x, y) = (pos.x, pos.y);
|
||||
let (width, height) = (dev_mode.dmPelsWidth, dev_mode.dmPelsHeight);
|
||||
|
||||
display_rects.push(RECT {
|
||||
left: x,
|
||||
right: x + width as i32,
|
||||
top: y,
|
||||
bottom: y + height as i32,
|
||||
});
|
||||
}
|
||||
display_rects
|
||||
}
|
||||
}
|
||||
|
||||
static mut DISPLAY_RESOLUTION_CHANGED: bool = true;
|
||||
|
||||
unsafe fn get_display_regions() -> &'static Vec<RECT> {
|
||||
static mut DISPLAYS: Vec<RECT> = vec![];
|
||||
if DISPLAY_RESOLUTION_CHANGED {
|
||||
DISPLAYS = enumerate_displays();
|
||||
DISPLAY_RESOLUTION_CHANGED = false;
|
||||
log::debug!("displays: {DISPLAYS:?}");
|
||||
}
|
||||
&*addr_of!(DISPLAYS)
|
||||
}
|
||||
|
||||
fn is_within_dp_region(point: (i32, i32), display: &RECT) -> bool {
|
||||
[
|
||||
Position::Left,
|
||||
Position::Right,
|
||||
Position::Top,
|
||||
Position::Bottom,
|
||||
]
|
||||
.iter()
|
||||
.all(|&pos| is_within_dp_boundary(point, display, pos))
|
||||
}
|
||||
fn is_within_dp_boundary(point: (i32, i32), display: &RECT, pos: Position) -> bool {
|
||||
let (x, y) = point;
|
||||
match pos {
|
||||
Position::Left => display.left <= x,
|
||||
Position::Right => display.right > x,
|
||||
Position::Top => display.top <= y,
|
||||
Position::Bottom => display.bottom > y,
|
||||
}
|
||||
}
|
||||
|
||||
/// returns whether the given position is within the display bounds with respect to the given
|
||||
/// barrier position
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `x`:
|
||||
/// * `y`:
|
||||
/// * `displays`:
|
||||
/// * `pos`:
|
||||
///
|
||||
/// returns: bool
|
||||
///
|
||||
fn in_bounds(point: (i32, i32), displays: &[RECT], pos: Position) -> bool {
|
||||
displays
|
||||
.iter()
|
||||
.any(|d| is_within_dp_boundary(point, d, pos))
|
||||
}
|
||||
|
||||
fn in_display_region(point: (i32, i32), displays: &[RECT]) -> bool {
|
||||
displays.iter().any(|d| is_within_dp_region(point, d))
|
||||
}
|
||||
|
||||
fn moved_across_boundary(
|
||||
prev_pos: (i32, i32),
|
||||
curr_pos: (i32, i32),
|
||||
displays: &[RECT],
|
||||
pos: Position,
|
||||
) -> bool {
|
||||
/* was within bounds, but is not anymore */
|
||||
in_display_region(prev_pos, displays) && !in_bounds(curr_pos, displays, pos)
|
||||
}
|
||||
|
||||
fn entered_barrier(
|
||||
prev_pos: (i32, i32),
|
||||
curr_pos: (i32, i32),
|
||||
displays: &[RECT],
|
||||
) -> Option<Position> {
|
||||
[
|
||||
Position::Left,
|
||||
Position::Right,
|
||||
Position::Top,
|
||||
Position::Bottom,
|
||||
]
|
||||
.into_iter()
|
||||
.find(|&pos| moved_across_boundary(prev_pos, curr_pos, displays, pos))
|
||||
}
|
||||
|
||||
fn get_msg() -> Option<MSG> {
|
||||
unsafe {
|
||||
let mut msg = std::mem::zeroed();
|
||||
let ret = GetMessageW(addr_of_mut!(msg), HWND::default(), 0, 0);
|
||||
match ret.0 {
|
||||
0 => None,
|
||||
x if x > 0 => Some(msg),
|
||||
_ => panic!("error in GetMessageW"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn message_thread(ready_tx: mpsc::Sender<()>) {
|
||||
unsafe {
|
||||
set_event_tid(GetCurrentThreadId());
|
||||
ready_tx.send(()).expect("channel closed");
|
||||
let mouse_proc: HOOKPROC = Some(mouse_proc);
|
||||
let kybrd_proc: HOOKPROC = Some(kybrd_proc);
|
||||
let window_proc: WNDPROC = Some(window_proc);
|
||||
|
||||
/* register hooks */
|
||||
let _ = SetWindowsHookExW(WH_MOUSE_LL, mouse_proc, HINSTANCE::default(), 0).unwrap();
|
||||
let _ = SetWindowsHookExW(WH_KEYBOARD_LL, kybrd_proc, HINSTANCE::default(), 0).unwrap();
|
||||
|
||||
let instance = GetModuleHandleW(None).unwrap();
|
||||
let window_class: WNDCLASSW = WNDCLASSW {
|
||||
lpfnWndProc: window_proc,
|
||||
hInstance: instance.into(),
|
||||
lpszClassName: w!("lan-mouse-message-window-class"),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let ret = RegisterClassW(&window_class);
|
||||
if ret == 0 {
|
||||
panic!("RegisterClassW");
|
||||
}
|
||||
|
||||
/* window is used ro receive WM_DISPLAYCHANGE messages */
|
||||
let ret = CreateWindowExW(
|
||||
Default::default(),
|
||||
w!("lan-mouse-message-window-class"),
|
||||
w!("lan-mouse-msg-window"),
|
||||
WINDOW_STYLE::default(),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
HWND::default(),
|
||||
HMENU::default(),
|
||||
instance,
|
||||
None,
|
||||
);
|
||||
if ret.0 == 0 {
|
||||
panic!("CreateWindowExW");
|
||||
}
|
||||
|
||||
/* run message loop */
|
||||
loop {
|
||||
// mouse / keybrd proc do not actually return a message
|
||||
let Some(msg) = get_msg() else {
|
||||
break;
|
||||
};
|
||||
if msg.hwnd.0 == 0 {
|
||||
/* messages sent via PostThreadMessage */
|
||||
match msg.wParam.0 {
|
||||
x if x == EventType::Exit as usize => break,
|
||||
x if x == EventType::Release as usize => {
|
||||
ACTIVE_CLIENT.take();
|
||||
}
|
||||
x if x == EventType::Request as usize => {
|
||||
let requests = {
|
||||
let mut res = vec![];
|
||||
let mut requests = REQUEST_BUFFER.lock().unwrap();
|
||||
for request in requests.drain(..) {
|
||||
res.push(request);
|
||||
}
|
||||
res
|
||||
};
|
||||
|
||||
for request in requests {
|
||||
update_clients(request)
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
/* other messages for window_procs */
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_clients(request: Request) {
|
||||
match request {
|
||||
Request::Create(handle, pos) => {
|
||||
unsafe { CLIENT_FOR_POS.insert(pos, handle) };
|
||||
}
|
||||
Request::Destroy(handle) => unsafe {
|
||||
for pos in [
|
||||
Position::Left,
|
||||
Position::Right,
|
||||
Position::Top,
|
||||
Position::Bottom,
|
||||
] {
|
||||
if ACTIVE_CLIENT == Some(handle) {
|
||||
ACTIVE_CLIENT.take();
|
||||
}
|
||||
if CLIENT_FOR_POS.get(&pos).copied() == Some(handle) {
|
||||
CLIENT_FOR_POS.remove(&pos);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsInputCapture {
|
||||
pub(crate) fn new() -> Self {
|
||||
unsafe {
|
||||
let (tx, rx) = channel(10);
|
||||
EVENT_TX.replace(tx);
|
||||
let (ready_tx, ready_rx) = mpsc::channel();
|
||||
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
|
||||
/* wait for thread to set its id */
|
||||
ready_rx.recv().expect("channel closed");
|
||||
Self {
|
||||
msg_thread,
|
||||
event_rx: rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for WindowsInputCapture {
|
||||
type Item = io::Result<(CaptureHandle, Event)>;
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
match ready!(self.event_rx.poll_recv(cx)) {
|
||||
None => Poll::Ready(None),
|
||||
Some(e) => Poll::Ready(Some(Ok(e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for WindowsInputCapture {
|
||||
fn drop(&mut self) {
|
||||
unsafe { signal_message_thread(EventType::Exit) };
|
||||
let _ = self.msg_thread.take().unwrap().join();
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use std::io;
|
||||
use std::task::Poll;
|
||||
|
||||
use futures_core::Stream;
|
||||
|
||||
use super::InputCapture;
|
||||
use crate::event::Event;
|
||||
|
||||
use super::error::X11InputCaptureCreationError;
|
||||
use super::{CaptureHandle, Position};
|
||||
|
||||
pub struct X11InputCapture {}
|
||||
|
||||
impl X11InputCapture {
|
||||
pub fn new() -> std::result::Result<Self, X11InputCaptureCreationError> {
|
||||
Err(X11InputCaptureCreationError::NotImplemented)
|
||||
}
|
||||
}
|
||||
|
||||
impl InputCapture for X11InputCapture {
|
||||
fn create(&mut self, _id: CaptureHandle, _pos: Position) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn destroy(&mut self, _id: CaptureHandle) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn release(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for X11InputCapture {
|
||||
type Item = io::Result<(CaptureHandle, Event)>;
|
||||
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
_cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Self::Item>> {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::capture::{self, Position};
|
||||
use crate::config::Config;
|
||||
use crate::event::{Event, KeyboardEvent};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::StreamExt;
|
||||
use input_capture::{self, Position};
|
||||
use input_event::{Event, KeyboardEvent};
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
pub fn run() -> Result<()> {
|
||||
@@ -19,7 +19,8 @@ pub fn run() -> Result<()> {
|
||||
|
||||
async fn input_capture_test(config: Config) -> Result<()> {
|
||||
log::info!("creating input capture");
|
||||
let mut input_capture = capture::create(config.capture_backend).await?;
|
||||
let backend = config.capture_backend.map(|b| b.into());
|
||||
let mut input_capture = input_capture::create(backend).await?;
|
||||
log::info!("creating clients");
|
||||
input_capture.create(0, Position::Left)?;
|
||||
input_capture.create(1, Position::Right)?;
|
||||
|
||||
@@ -9,7 +9,8 @@ use std::{
|
||||
use serde::{Deserialize, Serialize};
|
||||
use slab::Slab;
|
||||
|
||||
use crate::{capture, config::DEFAULT_PORT};
|
||||
use crate::config::DEFAULT_PORT;
|
||||
use input_capture;
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Position {
|
||||
@@ -25,13 +26,13 @@ impl Default for Position {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Position> for capture::Position {
|
||||
fn from(position: Position) -> capture::Position {
|
||||
impl From<Position> for input_capture::Position {
|
||||
fn from(position: Position) -> input_capture::Position {
|
||||
match position {
|
||||
Position::Left => capture::Position::Left,
|
||||
Position::Right => capture::Position::Right,
|
||||
Position::Top => capture::Position::Top,
|
||||
Position::Bottom => capture::Position::Bottom,
|
||||
Position::Left => input_capture::Position::Left,
|
||||
Position::Right => input_capture::Position::Right,
|
||||
Position::Top => input_capture::Position::Top,
|
||||
Position::Bottom => input_capture::Position::Bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@ use std::{error::Error, fs};
|
||||
use toml;
|
||||
|
||||
use crate::client::Position;
|
||||
use crate::scancode;
|
||||
use crate::scancode::Linux::{KeyLeftAlt, KeyLeftCtrl, KeyLeftMeta, KeyLeftShift};
|
||||
|
||||
use input_event::scancode::{
|
||||
self,
|
||||
Linux::{KeyLeftAlt, KeyLeftCtrl, KeyLeftMeta, KeyLeftShift},
|
||||
};
|
||||
|
||||
pub const DEFAULT_PORT: u16 = 4242;
|
||||
|
||||
@@ -115,6 +118,24 @@ impl Display for CaptureBackend {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CaptureBackend> for input_capture::Backend {
|
||||
fn from(backend: CaptureBackend) -> Self {
|
||||
match backend {
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
CaptureBackend::InputCapturePortal => Self::InputCapturePortal,
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
CaptureBackend::LayerShell => Self::LayerShell,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
CaptureBackend::X11 => Self::X11,
|
||||
#[cfg(windows)]
|
||||
CaptureBackend::Windows => Self::Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
CaptureBackend::MacOs => Self::MacOs,
|
||||
CaptureBackend::Dummy => Self::Dummy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
|
||||
pub enum EmulationBackend {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
@@ -132,6 +153,26 @@ pub enum EmulationBackend {
|
||||
Dummy,
|
||||
}
|
||||
|
||||
impl From<EmulationBackend> for input_emulation::Backend {
|
||||
fn from(backend: EmulationBackend) -> Self {
|
||||
match backend {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
EmulationBackend::Wlroots => Self::Wlroots,
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
EmulationBackend::Libei => Self::Libei,
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
EmulationBackend::Xdp => Self::Xdp,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
EmulationBackend::X11 => Self::X11,
|
||||
#[cfg(windows)]
|
||||
EmulationBackend::Windows => Self::Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
EmulationBackend::MacOs => Self::MacOs,
|
||||
EmulationBackend::Dummy => Self::Dummy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EmulationBackend {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
||||
101
src/emulate.rs
101
src/emulate.rs
@@ -1,101 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use std::future;
|
||||
|
||||
use crate::{config::EmulationBackend, event::Event};
|
||||
use anyhow::Result;
|
||||
|
||||
use self::error::EmulationCreationError;
|
||||
|
||||
#[cfg(windows)]
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
pub mod x11;
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
pub mod wlroots;
|
||||
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
pub mod xdg_desktop_portal;
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
pub mod libei;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod macos;
|
||||
|
||||
/// fallback input emulation (logs events)
|
||||
pub mod dummy;
|
||||
pub mod error;
|
||||
|
||||
pub type EmulationHandle = u64;
|
||||
|
||||
#[async_trait]
|
||||
pub trait InputEmulation: Send {
|
||||
async fn consume(&mut self, event: Event, handle: EmulationHandle);
|
||||
async fn create(&mut self, handle: EmulationHandle);
|
||||
async fn destroy(&mut self, handle: EmulationHandle);
|
||||
/// this function is waited on continuously and can be used to handle events
|
||||
async fn dispatch(&mut self) -> Result<()> {
|
||||
let _: () = future::pending().await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_backend(
|
||||
backend: EmulationBackend,
|
||||
) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
|
||||
match backend {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
EmulationBackend::Wlroots => Ok(Box::new(wlroots::WlrootsEmulation::new()?)),
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
EmulationBackend::Libei => Ok(Box::new(libei::LibeiEmulation::new().await?)),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
EmulationBackend::X11 => Ok(Box::new(x11::X11Emulation::new()?)),
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
EmulationBackend::Xdp => Ok(Box::new(
|
||||
xdg_desktop_portal::DesktopPortalEmulation::new().await?,
|
||||
)),
|
||||
#[cfg(windows)]
|
||||
EmulationBackend::Windows => Ok(Box::new(windows::WindowsEmulation::new()?)),
|
||||
#[cfg(target_os = "macos")]
|
||||
EmulationBackend::MacOs => Ok(Box::new(macos::MacOSEmulation::new()?)),
|
||||
EmulationBackend::Dummy => Ok(Box::new(dummy::DummyEmulation::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
backend: Option<EmulationBackend>,
|
||||
) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
|
||||
if let Some(backend) = backend {
|
||||
let b = create_backend(backend).await;
|
||||
if b.is_ok() {
|
||||
log::info!("using emulation backend: {backend}");
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
for backend in [
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
EmulationBackend::Wlroots,
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
EmulationBackend::Libei,
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
EmulationBackend::X11,
|
||||
#[cfg(windows)]
|
||||
EmulationBackend::Windows,
|
||||
#[cfg(target_os = "macos")]
|
||||
EmulationBackend::MacOs,
|
||||
EmulationBackend::Dummy,
|
||||
] {
|
||||
match create_backend(backend).await {
|
||||
Ok(b) => {
|
||||
log::info!("using emulation backend: {backend}");
|
||||
return Ok(b);
|
||||
}
|
||||
Err(e) => log::warn!("{e}"),
|
||||
}
|
||||
}
|
||||
|
||||
Err(EmulationCreationError::NoAvailableBackend)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
use crate::event::Event;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::{EmulationHandle, InputEmulation};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DummyEmulation;
|
||||
|
||||
impl DummyEmulation {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl InputEmulation for DummyEmulation {
|
||||
async fn consume(&mut self, event: Event, client_handle: EmulationHandle) {
|
||||
log::info!("received event: ({client_handle}) {event}");
|
||||
}
|
||||
async fn create(&mut self, _: EmulationHandle) {}
|
||||
async fn destroy(&mut self, _: EmulationHandle) {}
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use thiserror::Error;
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
use wayland_client::{
|
||||
backend::WaylandError,
|
||||
globals::{BindError, GlobalError},
|
||||
ConnectError, DispatchError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum EmulationCreationError {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
Wlroots(#[from] WlrootsEmulationCreationError),
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
Libei(#[from] LibeiEmulationCreationError),
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
Xdp(#[from] XdpEmulationCreationError),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
X11(#[from] X11EmulationCreationError),
|
||||
#[cfg(target_os = "macos")]
|
||||
MacOs(#[from] MacOSEmulationCreationError),
|
||||
#[cfg(windows)]
|
||||
Windows(#[from] WindowsEmulationCreationError),
|
||||
NoAvailableBackend,
|
||||
}
|
||||
|
||||
impl Display for EmulationCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let reason = match self {
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
EmulationCreationError::Wlroots(e) => format!("wlroots backend: {e}"),
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
EmulationCreationError::Libei(e) => format!("libei backend: {e}"),
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
EmulationCreationError::Xdp(e) => format!("desktop portal backend: {e}"),
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
EmulationCreationError::X11(e) => format!("x11 backend: {e}"),
|
||||
#[cfg(target_os = "macos")]
|
||||
EmulationCreationError::MacOs(e) => format!("macos backend: {e}"),
|
||||
#[cfg(windows)]
|
||||
EmulationCreationError::Windows(e) => format!("windows backend: {e}"),
|
||||
EmulationCreationError::NoAvailableBackend => "no backend available".to_string(),
|
||||
};
|
||||
write!(f, "could not create input emulation backend: {reason}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WlrootsEmulationCreationError {
|
||||
Connect(#[from] ConnectError),
|
||||
Global(#[from] GlobalError),
|
||||
Wayland(#[from] WaylandError),
|
||||
Bind(#[from] WaylandBindError),
|
||||
Dispatch(#[from] DispatchError),
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub struct WaylandBindError {
|
||||
inner: BindError,
|
||||
protocol: &'static str,
|
||||
}
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
impl WaylandBindError {
|
||||
pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self {
|
||||
Self { inner, protocol }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
impl Display for WaylandBindError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} protocol not supported: {}",
|
||||
self.protocol, self.inner
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
|
||||
impl Display for WlrootsEmulationCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
WlrootsEmulationCreationError::Bind(e) => write!(f, "{e}"),
|
||||
WlrootsEmulationCreationError::Connect(e) => {
|
||||
write!(f, "could not connect to wayland compositor: {e}")
|
||||
}
|
||||
WlrootsEmulationCreationError::Global(e) => write!(f, "wayland error: {e}"),
|
||||
WlrootsEmulationCreationError::Wayland(e) => write!(f, "wayland error: {e}"),
|
||||
WlrootsEmulationCreationError::Dispatch(e) => {
|
||||
write!(f, "error dispatching wayland events: {e}")
|
||||
}
|
||||
WlrootsEmulationCreationError::Io(e) => write!(f, "io error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum LibeiEmulationCreationError {
|
||||
Ashpd(#[from] ashpd::Error),
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
|
||||
impl Display for LibeiEmulationCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
LibeiEmulationCreationError::Ashpd(e) => write!(f, "xdg-desktop-portal: {e}"),
|
||||
LibeiEmulationCreationError::Io(e) => write!(f, "io error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum XdpEmulationCreationError {
|
||||
Ashpd(#[from] ashpd::Error),
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))]
|
||||
impl Display for XdpEmulationCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
XdpEmulationCreationError::Ashpd(e) => write!(f, "portal error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum X11EmulationCreationError {
|
||||
OpenDisplay,
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
|
||||
impl Display for X11EmulationCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
X11EmulationCreationError::OpenDisplay => write!(f, "could not open display!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MacOSEmulationCreationError {
|
||||
EventSourceCreation,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
impl Display for MacOSEmulationCreationError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MacOSEmulationCreationError::EventSourceCreation => {
|
||||
write!(f, "could not create event source")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WindowsEmulationCreationError {}
|
||||
@@ -1,397 +0,0 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
os::{fd::OwnedFd, unix::net::UnixStream},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use ashpd::{
|
||||
desktop::{
|
||||
remote_desktop::{DeviceType, RemoteDesktop},
|
||||
ResponseError,
|
||||
},
|
||||
WindowIdentifier,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use futures::StreamExt;
|
||||
|
||||
use reis::{
|
||||
ei::{self, button::ButtonState, handshake::ContextType, keyboard::KeyState},
|
||||
tokio::EiEventStream,
|
||||
PendingRequestResult,
|
||||
};
|
||||
|
||||
use crate::event::Event;
|
||||
|
||||
use super::{error::LibeiEmulationCreationError, EmulationHandle, InputEmulation};
|
||||
|
||||
pub struct LibeiEmulation {
|
||||
handshake: bool,
|
||||
context: ei::Context,
|
||||
events: EiEventStream,
|
||||
pointer: Option<(ei::Device, ei::Pointer)>,
|
||||
has_pointer: bool,
|
||||
scroll: Option<(ei::Device, ei::Scroll)>,
|
||||
has_scroll: bool,
|
||||
button: Option<(ei::Device, ei::Button)>,
|
||||
has_button: bool,
|
||||
keyboard: Option<(ei::Device, ei::Keyboard)>,
|
||||
has_keyboard: bool,
|
||||
capabilities: HashMap<String, u64>,
|
||||
capability_mask: u64,
|
||||
sequence: u32,
|
||||
serial: u32,
|
||||
}
|
||||
|
||||
async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> {
|
||||
let proxy = RemoteDesktop::new().await?;
|
||||
|
||||
// retry when user presses the cancel button
|
||||
let (session, _) = loop {
|
||||
log::debug!("creating session ...");
|
||||
let session = proxy.create_session().await?;
|
||||
|
||||
log::debug!("selecting devices ...");
|
||||
proxy
|
||||
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
||||
.await?;
|
||||
|
||||
log::info!("requesting permission for input emulation");
|
||||
match proxy
|
||||
.start(&session, &WindowIdentifier::default())
|
||||
.await?
|
||||
.response()
|
||||
{
|
||||
Ok(d) => break (session, d),
|
||||
Err(ashpd::Error::Response(ResponseError::Cancelled)) => {
|
||||
log::warn!("request cancelled!");
|
||||
continue;
|
||||
}
|
||||
e => e?,
|
||||
};
|
||||
};
|
||||
|
||||
proxy.connect_to_eis(&session).await
|
||||
}
|
||||
|
||||
impl LibeiEmulation {
|
||||
pub async fn new() -> Result<Self, LibeiEmulationCreationError> {
|
||||
// fd is owned by the message, so we need to dup it
|
||||
let eifd = get_ei_fd().await?;
|
||||
let stream = UnixStream::from(eifd);
|
||||
// let stream = UnixStream::connect("/run/user/1000/eis-0")?;
|
||||
stream.set_nonblocking(true)?;
|
||||
let context = ei::Context::new(stream)?;
|
||||
context.flush().map_err(|e| io::Error::new(e.kind(), e))?;
|
||||
let events = EiEventStream::new(context.clone())?;
|
||||
Ok(Self {
|
||||
handshake: false,
|
||||
context,
|
||||
events,
|
||||
pointer: None,
|
||||
button: None,
|
||||
scroll: None,
|
||||
keyboard: None,
|
||||
has_pointer: false,
|
||||
has_button: false,
|
||||
has_scroll: false,
|
||||
has_keyboard: false,
|
||||
capabilities: HashMap::new(),
|
||||
capability_mask: 0,
|
||||
sequence: 0,
|
||||
serial: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl InputEmulation for LibeiEmulation {
|
||||
async fn consume(&mut self, event: Event, _client_handle: EmulationHandle) {
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_micros() as u64;
|
||||
match event {
|
||||
Event::Pointer(p) => match p {
|
||||
crate::event::PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
if !self.has_pointer {
|
||||
return;
|
||||
}
|
||||
if let Some((d, p)) = self.pointer.as_mut() {
|
||||
p.motion_relative(relative_x as f32, relative_y as f32);
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
if !self.has_button {
|
||||
return;
|
||||
}
|
||||
if let Some((d, b)) = self.button.as_mut() {
|
||||
b.button(
|
||||
button,
|
||||
match state {
|
||||
0 => ButtonState::Released,
|
||||
_ => ButtonState::Press,
|
||||
},
|
||||
);
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
if !self.has_scroll {
|
||||
return;
|
||||
}
|
||||
if let Some((d, s)) = self.scroll.as_mut() {
|
||||
match axis {
|
||||
0 => s.scroll(0., value as f32),
|
||||
_ => s.scroll(value as f32, 0.),
|
||||
}
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
if !self.has_scroll {
|
||||
return;
|
||||
}
|
||||
if let Some((d, s)) = self.scroll.as_mut() {
|
||||
match axis {
|
||||
0 => s.scroll_discrete(0, value),
|
||||
_ => s.scroll_discrete(value, 0),
|
||||
}
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(k) => match k {
|
||||
crate::event::KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
if !self.has_keyboard {
|
||||
return;
|
||||
}
|
||||
if let Some((d, k)) = &mut self.keyboard {
|
||||
k.key(
|
||||
key,
|
||||
match state {
|
||||
0 => KeyState::Released,
|
||||
_ => KeyState::Press,
|
||||
},
|
||||
);
|
||||
d.frame(self.serial, now);
|
||||
}
|
||||
}
|
||||
crate::event::KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
self.context.flush().unwrap();
|
||||
}
|
||||
|
||||
async fn dispatch(&mut self) -> Result<()> {
|
||||
let event = match self.events.next().await {
|
||||
Some(e) => e?,
|
||||
None => return Err(anyhow!("libei connection lost")),
|
||||
};
|
||||
let event = match event {
|
||||
PendingRequestResult::Request(result) => result,
|
||||
PendingRequestResult::ParseError(e) => {
|
||||
return Err(anyhow!("libei protocol violation: {e}"))
|
||||
}
|
||||
PendingRequestResult::InvalidObject(e) => return Err(anyhow!("invalid object {e}")),
|
||||
};
|
||||
match event {
|
||||
ei::Event::Handshake(handshake, request) => match request {
|
||||
ei::handshake::Event::HandshakeVersion { version } => {
|
||||
if self.handshake {
|
||||
return Ok(());
|
||||
}
|
||||
log::info!("libei version {}", version);
|
||||
// sender means we are sending events _to_ the eis server
|
||||
handshake.handshake_version(version); // FIXME
|
||||
handshake.context_type(ContextType::Sender);
|
||||
handshake.name("ei-demo-client");
|
||||
handshake.interface_version("ei_connection", 1);
|
||||
handshake.interface_version("ei_callback", 1);
|
||||
handshake.interface_version("ei_pingpong", 1);
|
||||
handshake.interface_version("ei_seat", 1);
|
||||
handshake.interface_version("ei_device", 2);
|
||||
handshake.interface_version("ei_pointer", 1);
|
||||
handshake.interface_version("ei_pointer_absolute", 1);
|
||||
handshake.interface_version("ei_scroll", 1);
|
||||
handshake.interface_version("ei_button", 1);
|
||||
handshake.interface_version("ei_keyboard", 1);
|
||||
handshake.interface_version("ei_touchscreen", 1);
|
||||
handshake.finish();
|
||||
self.handshake = true;
|
||||
}
|
||||
ei::handshake::Event::InterfaceVersion { name, version } => {
|
||||
log::debug!("handshake: Interface {name} @ {version}");
|
||||
}
|
||||
ei::handshake::Event::Connection { serial, connection } => {
|
||||
connection.sync(1);
|
||||
self.serial = serial;
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
ei::Event::Connection(_connection, request) => match request {
|
||||
ei::connection::Event::Seat { seat } => {
|
||||
log::debug!("connected to seat: {seat:?}");
|
||||
}
|
||||
ei::connection::Event::Ping { ping } => {
|
||||
ping.done(0);
|
||||
}
|
||||
ei::connection::Event::Disconnected {
|
||||
last_serial: _,
|
||||
reason,
|
||||
explanation,
|
||||
} => {
|
||||
log::debug!("ei - disconnected: reason: {reason:?}: {explanation}")
|
||||
}
|
||||
ei::connection::Event::InvalidObject {
|
||||
last_serial,
|
||||
invalid_id,
|
||||
} => {
|
||||
return Err(anyhow!(
|
||||
"invalid object: id: {invalid_id}, serial: {last_serial}"
|
||||
));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
ei::Event::Device(device, request) => match request {
|
||||
ei::device::Event::Destroyed { serial } => {
|
||||
log::debug!("device destroyed: {device:?} - serial: {serial}")
|
||||
}
|
||||
ei::device::Event::Name { name } => {
|
||||
log::debug!("device name: {name}")
|
||||
}
|
||||
ei::device::Event::DeviceType { device_type } => {
|
||||
log::debug!("device type: {device_type:?}")
|
||||
}
|
||||
ei::device::Event::Dimensions { width, height } => {
|
||||
log::debug!("device dimensions: {width}x{height}")
|
||||
}
|
||||
ei::device::Event::Region {
|
||||
offset_x,
|
||||
offset_y,
|
||||
width,
|
||||
hight,
|
||||
scale,
|
||||
} => log::debug!(
|
||||
"device region: {width}x{hight} @ ({offset_x},{offset_y}), scale: {scale}"
|
||||
),
|
||||
ei::device::Event::Interface { object } => {
|
||||
log::debug!("device interface: {object:?}");
|
||||
if object.interface().eq("ei_pointer") {
|
||||
log::debug!("GOT POINTER DEVICE");
|
||||
self.pointer.replace((device, object.downcast().unwrap()));
|
||||
} else if object.interface().eq("ei_button") {
|
||||
log::debug!("GOT BUTTON DEVICE");
|
||||
self.button.replace((device, object.downcast().unwrap()));
|
||||
} else if object.interface().eq("ei_scroll") {
|
||||
log::debug!("GOT SCROLL DEVICE");
|
||||
self.scroll.replace((device, object.downcast().unwrap()));
|
||||
} else if object.interface().eq("ei_keyboard") {
|
||||
log::debug!("GOT KEYBOARD DEVICE");
|
||||
self.keyboard.replace((device, object.downcast().unwrap()));
|
||||
}
|
||||
}
|
||||
ei::device::Event::Done => {
|
||||
log::debug!("device: done {device:?}");
|
||||
}
|
||||
ei::device::Event::Resumed { serial } => {
|
||||
self.serial = serial;
|
||||
device.start_emulating(serial, self.sequence);
|
||||
self.sequence += 1;
|
||||
log::debug!("resumed: {device:?}");
|
||||
if let Some((d, _)) = &mut self.pointer {
|
||||
if d == &device {
|
||||
log::debug!("pointer resumed {serial}");
|
||||
self.has_pointer = true;
|
||||
}
|
||||
}
|
||||
if let Some((d, _)) = &mut self.button {
|
||||
if d == &device {
|
||||
log::debug!("button resumed {serial}");
|
||||
self.has_button = true;
|
||||
}
|
||||
}
|
||||
if let Some((d, _)) = &mut self.scroll {
|
||||
if d == &device {
|
||||
log::debug!("scroll resumed {serial}");
|
||||
self.has_scroll = true;
|
||||
}
|
||||
}
|
||||
if let Some((d, _)) = &mut self.keyboard {
|
||||
if d == &device {
|
||||
log::debug!("keyboard resumed {serial}");
|
||||
self.has_keyboard = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ei::device::Event::Paused { serial } => {
|
||||
self.has_pointer = false;
|
||||
self.has_button = false;
|
||||
self.serial = serial;
|
||||
}
|
||||
ei::device::Event::StartEmulating { serial, sequence } => {
|
||||
log::debug!("start emulating {serial}, {sequence}")
|
||||
}
|
||||
ei::device::Event::StopEmulating { serial } => {
|
||||
log::debug!("stop emulating {serial}")
|
||||
}
|
||||
ei::device::Event::Frame { serial, timestamp } => {
|
||||
log::debug!("frame: {serial}, {timestamp}");
|
||||
}
|
||||
ei::device::Event::RegionMappingId { mapping_id } => {
|
||||
log::debug!("RegionMappingId {mapping_id}")
|
||||
}
|
||||
e => log::debug!("invalid event: {e:?}"),
|
||||
},
|
||||
ei::Event::Seat(seat, request) => match request {
|
||||
ei::seat::Event::Destroyed { serial } => {
|
||||
self.serial = serial;
|
||||
log::debug!("seat destroyed: {seat:?}");
|
||||
}
|
||||
ei::seat::Event::Name { name } => {
|
||||
log::debug!("seat name: {name}");
|
||||
}
|
||||
ei::seat::Event::Capability { mask, interface } => {
|
||||
log::debug!("seat capabilities: {mask}, interface: {interface:?}");
|
||||
self.capabilities.insert(interface, mask);
|
||||
self.capability_mask |= mask;
|
||||
}
|
||||
ei::seat::Event::Done => {
|
||||
log::debug!("seat done");
|
||||
log::debug!("binding capabilities: {}", self.capability_mask);
|
||||
seat.bind(self.capability_mask);
|
||||
}
|
||||
ei::seat::Event::Device { device } => {
|
||||
log::debug!("seat: new device - {device:?}");
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
e => log::debug!("unhandled event: {e:?}"),
|
||||
}
|
||||
self.context.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create(&mut self, _: EmulationHandle) {}
|
||||
async fn destroy(&mut self, _: EmulationHandle) {}
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
use super::{EmulationHandle, InputEmulation};
|
||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||
use async_trait::async_trait;
|
||||
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
|
||||
use core_graphics::event::{
|
||||
CGEvent, CGEventTapLocation, CGEventType, CGKeyCode, CGMouseButton, EventField, ScrollEventUnit,
|
||||
};
|
||||
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
||||
use keycode::{KeyMap, KeyMapping};
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::time::Duration;
|
||||
use tokio::task::AbortHandle;
|
||||
|
||||
use super::error::MacOSEmulationCreationError;
|
||||
|
||||
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
||||
|
||||
pub struct MacOSEmulation {
|
||||
pub event_source: CGEventSource,
|
||||
repeat_task: Option<AbortHandle>,
|
||||
button_state: ButtonState,
|
||||
}
|
||||
|
||||
struct ButtonState {
|
||||
left: bool,
|
||||
right: bool,
|
||||
center: bool,
|
||||
}
|
||||
|
||||
impl Index<CGMouseButton> for ButtonState {
|
||||
type Output = bool;
|
||||
|
||||
fn index(&self, index: CGMouseButton) -> &Self::Output {
|
||||
match index {
|
||||
CGMouseButton::Left => &self.left,
|
||||
CGMouseButton::Right => &self.right,
|
||||
CGMouseButton::Center => &self.center,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<CGMouseButton> for ButtonState {
|
||||
fn index_mut(&mut self, index: CGMouseButton) -> &mut Self::Output {
|
||||
match index {
|
||||
CGMouseButton::Left => &mut self.left,
|
||||
CGMouseButton::Right => &mut self.right,
|
||||
CGMouseButton::Center => &mut self.center,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MacOSEmulation {}
|
||||
|
||||
impl MacOSEmulation {
|
||||
pub fn new() -> Result<Self, MacOSEmulationCreationError> {
|
||||
let event_source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState)
|
||||
.map_err(|_| MacOSEmulationCreationError::EventSourceCreation)?;
|
||||
let button_state = ButtonState {
|
||||
left: false,
|
||||
right: false,
|
||||
center: false,
|
||||
};
|
||||
Ok(Self {
|
||||
event_source,
|
||||
button_state,
|
||||
repeat_task: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_mouse_location(&self) -> Option<CGPoint> {
|
||||
let event: CGEvent = CGEvent::new(self.event_source.clone()).ok()?;
|
||||
Some(event.location())
|
||||
}
|
||||
|
||||
async fn spawn_repeat_task(&mut self, key: u16) {
|
||||
// there can only be one repeating key and it's
|
||||
// always the last to be pressed
|
||||
self.kill_repeat_task();
|
||||
let event_source = self.event_source.clone();
|
||||
let repeat_task = tokio::task::spawn_local(async move {
|
||||
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await;
|
||||
loop {
|
||||
key_event(event_source.clone(), key, 1);
|
||||
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
|
||||
}
|
||||
});
|
||||
self.repeat_task = Some(repeat_task.abort_handle());
|
||||
}
|
||||
fn kill_repeat_task(&mut self) {
|
||||
if let Some(task) = self.repeat_task.take() {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn key_event(event_source: CGEventSource, key: u16, state: u8) {
|
||||
let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) {
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
log::warn!("unable to create key event");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl InputEmulation for MacOSEmulation {
|
||||
async fn consume(&mut self, event: Event, _handle: EmulationHandle) {
|
||||
match event {
|
||||
Event::Pointer(pointer_event) => match pointer_event {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
// FIXME secondary displays?
|
||||
let (min_x, min_y, max_x, max_y) = unsafe {
|
||||
let display = CGMainDisplayID();
|
||||
let bounds = CGDisplayBounds(display);
|
||||
let min_x = bounds.origin.x;
|
||||
let max_x = bounds.origin.x + bounds.size.width;
|
||||
let min_y = bounds.origin.y;
|
||||
let max_y = bounds.origin.y + bounds.size.height;
|
||||
(min_x as f64, min_y as f64, max_x as f64, max_y as f64)
|
||||
};
|
||||
let mut mouse_location = match self.get_mouse_location() {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
log::warn!("could not get mouse location!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
mouse_location.x = (mouse_location.x + relative_x).clamp(min_x, max_x - 1.);
|
||||
mouse_location.y = (mouse_location.y + relative_y).clamp(min_y, max_y - 1.);
|
||||
|
||||
let mut event_type = CGEventType::MouseMoved;
|
||||
if self.button_state.left {
|
||||
event_type = CGEventType::LeftMouseDragged
|
||||
} else if self.button_state.right {
|
||||
event_type = CGEventType::RightMouseDragged
|
||||
} else if self.button_state.center {
|
||||
event_type = CGEventType::OtherMouseDragged
|
||||
};
|
||||
let event = match CGEvent::new_mouse_event(
|
||||
self.event_source.clone(),
|
||||
event_type,
|
||||
mouse_location,
|
||||
CGMouseButton::Left,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
Err(_) => {
|
||||
log::warn!("mouse event creation failed!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.set_integer_value_field(
|
||||
EventField::MOUSE_EVENT_DELTA_X,
|
||||
relative_x as i64,
|
||||
);
|
||||
event.set_integer_value_field(
|
||||
EventField::MOUSE_EVENT_DELTA_Y,
|
||||
relative_y as i64,
|
||||
);
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let (event_type, mouse_button) = match (button, state) {
|
||||
(b, 1) if b == crate::event::BTN_LEFT => {
|
||||
(CGEventType::LeftMouseDown, CGMouseButton::Left)
|
||||
}
|
||||
(b, 0) if b == crate::event::BTN_LEFT => {
|
||||
(CGEventType::LeftMouseUp, CGMouseButton::Left)
|
||||
}
|
||||
(b, 1) if b == crate::event::BTN_RIGHT => {
|
||||
(CGEventType::RightMouseDown, CGMouseButton::Right)
|
||||
}
|
||||
(b, 0) if b == crate::event::BTN_RIGHT => {
|
||||
(CGEventType::RightMouseUp, CGMouseButton::Right)
|
||||
}
|
||||
(b, 1) if b == crate::event::BTN_MIDDLE => {
|
||||
(CGEventType::OtherMouseDown, CGMouseButton::Center)
|
||||
}
|
||||
(b, 0) if b == crate::event::BTN_MIDDLE => {
|
||||
(CGEventType::OtherMouseUp, CGMouseButton::Center)
|
||||
}
|
||||
_ => {
|
||||
log::warn!("invalid button event: {button},{state}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
// store button state
|
||||
self.button_state[mouse_button] = state == 1;
|
||||
|
||||
let location = self.get_mouse_location().unwrap();
|
||||
let event = match CGEvent::new_mouse_event(
|
||||
self.event_source.clone(),
|
||||
event_type,
|
||||
location,
|
||||
mouse_button,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
Err(()) => {
|
||||
log::warn!("mouse event creation failed!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
let value = value as i32;
|
||||
let (count, wheel1, wheel2, wheel3) = match axis {
|
||||
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
||||
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
||||
_ => {
|
||||
log::warn!("invalid scroll event: {axis}, {value}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let event = match CGEvent::new_scroll_event(
|
||||
self.event_source.clone(),
|
||||
ScrollEventUnit::PIXEL,
|
||||
count,
|
||||
wheel1,
|
||||
wheel2,
|
||||
wheel3,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
Err(()) => {
|
||||
log::warn!("scroll event creation failed!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let (count, wheel1, wheel2, wheel3) = match axis {
|
||||
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
||||
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
||||
_ => {
|
||||
log::warn!("invalid scroll event: {axis}, {value}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let event = match CGEvent::new_scroll_event(
|
||||
self.event_source.clone(),
|
||||
ScrollEventUnit::PIXEL,
|
||||
count,
|
||||
wheel1,
|
||||
wheel2,
|
||||
wheel3,
|
||||
) {
|
||||
Ok(e) => e,
|
||||
Err(()) => {
|
||||
log::warn!("scroll event creation failed!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
event.post(CGEventTapLocation::HID);
|
||||
}
|
||||
PointerEvent::Frame { .. } => {}
|
||||
},
|
||||
Event::Keyboard(keyboard_event) => match keyboard_event {
|
||||
KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
let code = match KeyMap::from_key_mapping(KeyMapping::Evdev(key as u16)) {
|
||||
Ok(k) => k.mac as CGKeyCode,
|
||||
Err(_) => {
|
||||
log::warn!("unable to map key event");
|
||||
return;
|
||||
}
|
||||
};
|
||||
match state {
|
||||
// pressed
|
||||
1 => self.spawn_repeat_task(code).await,
|
||||
_ => self.kill_repeat_task(),
|
||||
}
|
||||
key_event(self.event_source.clone(), code, state)
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create(&mut self, _handle: EmulationHandle) {}
|
||||
|
||||
async fn destroy(&mut self, _handle: EmulationHandle) {}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
use super::error::WindowsEmulationCreationError;
|
||||
use crate::{
|
||||
event::{
|
||||
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
||||
},
|
||||
scancode,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::ops::BitOrAssign;
|
||||
use std::time::Duration;
|
||||
use tokio::task::AbortHandle;
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
SendInput, INPUT_0, KEYEVENTF_EXTENDEDKEY, MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP,
|
||||
};
|
||||
use windows::Win32::UI::Input::KeyboardAndMouse::{
|
||||
INPUT, INPUT_KEYBOARD, INPUT_MOUSE, KEYBDINPUT, KEYEVENTF_KEYUP, KEYEVENTF_SCANCODE,
|
||||
MOUSEEVENTF_HWHEEL, MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEDOWN,
|
||||
MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_MOVE, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP,
|
||||
MOUSEEVENTF_WHEEL, MOUSEINPUT,
|
||||
};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{XBUTTON1, XBUTTON2};
|
||||
|
||||
use super::{EmulationHandle, InputEmulation};
|
||||
|
||||
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
|
||||
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
|
||||
|
||||
pub struct WindowsEmulation {
|
||||
repeat_task: Option<AbortHandle>,
|
||||
}
|
||||
|
||||
impl WindowsEmulation {
|
||||
pub fn new() -> Result<Self, WindowsEmulationCreationError> {
|
||||
Ok(Self { repeat_task: None })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl InputEmulation for WindowsEmulation {
|
||||
async fn consume(&mut self, event: Event, _: EmulationHandle) {
|
||||
match event {
|
||||
Event::Pointer(pointer_event) => match pointer_event {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
rel_mouse(relative_x as i32, relative_y as i32);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => mouse_button(button, state),
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => scroll(axis, value as i32),
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => scroll(axis, value),
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(keyboard_event) => match keyboard_event {
|
||||
KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
match state {
|
||||
// pressed
|
||||
0 => self.kill_repeat_task(),
|
||||
1 => self.spawn_repeat_task(key).await,
|
||||
_ => {}
|
||||
}
|
||||
key_event(key, state)
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create(&mut self, _handle: EmulationHandle) {}
|
||||
|
||||
async fn destroy(&mut self, _handle: EmulationHandle) {}
|
||||
}
|
||||
|
||||
impl WindowsEmulation {
|
||||
async fn spawn_repeat_task(&mut self, key: u32) {
|
||||
// there can only be one repeating key and it's
|
||||
// always the last to be pressed
|
||||
self.kill_repeat_task();
|
||||
let repeat_task = tokio::task::spawn_local(async move {
|
||||
tokio::time::sleep(DEFAULT_REPEAT_DELAY).await;
|
||||
loop {
|
||||
key_event(key, 1);
|
||||
tokio::time::sleep(DEFAULT_REPEAT_INTERVAL).await;
|
||||
}
|
||||
});
|
||||
self.repeat_task = Some(repeat_task.abort_handle());
|
||||
}
|
||||
fn kill_repeat_task(&mut self) {
|
||||
if let Some(task) = self.repeat_task.take() {
|
||||
task.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_input_safe(input: INPUT) {
|
||||
unsafe {
|
||||
loop {
|
||||
/* retval = number of successfully submitted events */
|
||||
if SendInput(&[input], std::mem::size_of::<INPUT>() as i32) > 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn send_mouse_input(mi: MOUSEINPUT) {
|
||||
send_input_safe(INPUT {
|
||||
r#type: INPUT_MOUSE,
|
||||
Anonymous: INPUT_0 { mi },
|
||||
});
|
||||
}
|
||||
|
||||
fn send_keyboard_input(ki: KEYBDINPUT) {
|
||||
send_input_safe(INPUT {
|
||||
r#type: INPUT_KEYBOARD,
|
||||
Anonymous: INPUT_0 { ki },
|
||||
});
|
||||
}
|
||||
fn rel_mouse(dx: i32, dy: i32) {
|
||||
let mi = MOUSEINPUT {
|
||||
dx,
|
||||
dy,
|
||||
mouseData: 0,
|
||||
dwFlags: MOUSEEVENTF_MOVE,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
};
|
||||
send_mouse_input(mi);
|
||||
}
|
||||
|
||||
fn mouse_button(button: u32, state: u32) {
|
||||
let dw_flags = match state {
|
||||
0 => match button {
|
||||
BTN_LEFT => MOUSEEVENTF_LEFTUP,
|
||||
BTN_RIGHT => MOUSEEVENTF_RIGHTUP,
|
||||
BTN_MIDDLE => MOUSEEVENTF_MIDDLEUP,
|
||||
BTN_BACK => MOUSEEVENTF_XUP,
|
||||
BTN_FORWARD => MOUSEEVENTF_XUP,
|
||||
_ => return,
|
||||
},
|
||||
1 => match button {
|
||||
BTN_LEFT => MOUSEEVENTF_LEFTDOWN,
|
||||
BTN_RIGHT => MOUSEEVENTF_RIGHTDOWN,
|
||||
BTN_MIDDLE => MOUSEEVENTF_MIDDLEDOWN,
|
||||
BTN_BACK => MOUSEEVENTF_XDOWN,
|
||||
BTN_FORWARD => MOUSEEVENTF_XDOWN,
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
let mouse_data = match button {
|
||||
BTN_BACK => XBUTTON1 as u32,
|
||||
BTN_FORWARD => XBUTTON2 as u32,
|
||||
_ => 0,
|
||||
};
|
||||
let mi = MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0, // no movement
|
||||
mouseData: mouse_data,
|
||||
dwFlags: dw_flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
};
|
||||
send_mouse_input(mi);
|
||||
}
|
||||
|
||||
fn scroll(axis: u8, value: i32) {
|
||||
let event_type = match axis {
|
||||
0 => MOUSEEVENTF_WHEEL,
|
||||
1 => MOUSEEVENTF_HWHEEL,
|
||||
_ => return,
|
||||
};
|
||||
let mi = MOUSEINPUT {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
mouseData: -value as u32,
|
||||
dwFlags: event_type,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
};
|
||||
send_mouse_input(mi);
|
||||
}
|
||||
|
||||
fn key_event(key: u32, state: u8) {
|
||||
let scancode = match linux_keycode_to_windows_scancode(key) {
|
||||
Some(code) => code,
|
||||
None => return,
|
||||
};
|
||||
let extended = scancode > 0xff;
|
||||
let scancode = scancode & 0xff;
|
||||
let mut flags = KEYEVENTF_SCANCODE;
|
||||
if extended {
|
||||
flags.bitor_assign(KEYEVENTF_EXTENDEDKEY);
|
||||
}
|
||||
if state == 0 {
|
||||
flags.bitor_assign(KEYEVENTF_KEYUP);
|
||||
}
|
||||
let ki = KEYBDINPUT {
|
||||
wVk: Default::default(),
|
||||
wScan: scancode,
|
||||
dwFlags: flags,
|
||||
time: 0,
|
||||
dwExtraInfo: 0,
|
||||
};
|
||||
send_keyboard_input(ki);
|
||||
}
|
||||
|
||||
fn linux_keycode_to_windows_scancode(linux_keycode: u32) -> Option<u16> {
|
||||
let linux_scancode = match scancode::Linux::try_from(linux_keycode) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
log::warn!("unknown keycode: {linux_keycode}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
log::trace!("linux code: {linux_scancode:?}");
|
||||
let windows_scancode = match scancode::Windows::try_from(linux_scancode) {
|
||||
Ok(s) => s,
|
||||
Err(_) => {
|
||||
log::warn!("failed to translate linux code into windows scancode: {linux_scancode:?}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
log::trace!("windows code: {windows_scancode:?}");
|
||||
Some(windows_scancode as u16)
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
use crate::emulate::{error::WlrootsEmulationCreationError, InputEmulation};
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::os::fd::{AsFd, OwnedFd};
|
||||
use wayland_client::backend::WaylandError;
|
||||
use wayland_client::WEnum;
|
||||
|
||||
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
||||
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_protocols_wlr::virtual_pointer::v1::client::{
|
||||
zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1 as VpManager,
|
||||
zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1 as Vp,
|
||||
};
|
||||
|
||||
use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{
|
||||
zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1 as VkManager,
|
||||
zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1 as Vk,
|
||||
};
|
||||
|
||||
use wayland_client::{
|
||||
delegate_noop,
|
||||
globals::{registry_queue_init, GlobalListContents},
|
||||
protocol::{wl_registry, wl_seat},
|
||||
Connection, Dispatch, EventQueue, QueueHandle,
|
||||
};
|
||||
|
||||
use crate::event::{Event, KeyboardEvent, PointerEvent};
|
||||
|
||||
use super::error::WaylandBindError;
|
||||
use super::EmulationHandle;
|
||||
|
||||
struct State {
|
||||
keymap: Option<(u32, OwnedFd, u32)>,
|
||||
input_for_client: HashMap<EmulationHandle, VirtualInput>,
|
||||
seat: wl_seat::WlSeat,
|
||||
qh: QueueHandle<Self>,
|
||||
vpm: VpManager,
|
||||
vkm: VkManager,
|
||||
}
|
||||
|
||||
// App State, implements Dispatch event handlers
|
||||
pub(crate) struct WlrootsEmulation {
|
||||
last_flush_failed: bool,
|
||||
state: State,
|
||||
queue: EventQueue<State>,
|
||||
}
|
||||
|
||||
impl WlrootsEmulation {
|
||||
pub fn new() -> Result<Self, WlrootsEmulationCreationError> {
|
||||
let conn = Connection::connect_to_env()?;
|
||||
let (globals, queue) = registry_queue_init::<State>(&conn)?;
|
||||
let qh = queue.handle();
|
||||
|
||||
let seat: wl_seat::WlSeat = globals
|
||||
.bind(&qh, 7..=8, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
|
||||
|
||||
let vpm: VpManager = globals
|
||||
.bind(&qh, 1..=1, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "wlr-virtual-pointer-unstable-v1"))?;
|
||||
let vkm: VkManager = globals
|
||||
.bind(&qh, 1..=1, ())
|
||||
.map_err(|e| WaylandBindError::new(e, "virtual-keyboard-unstable-v1"))?;
|
||||
|
||||
let input_for_client: HashMap<EmulationHandle, VirtualInput> = HashMap::new();
|
||||
|
||||
let mut emulate = WlrootsEmulation {
|
||||
last_flush_failed: false,
|
||||
state: State {
|
||||
keymap: None,
|
||||
input_for_client,
|
||||
seat,
|
||||
vpm,
|
||||
vkm,
|
||||
qh,
|
||||
},
|
||||
queue,
|
||||
};
|
||||
while emulate.state.keymap.is_none() {
|
||||
emulate.queue.blocking_dispatch(&mut emulate.state)?;
|
||||
}
|
||||
// let fd = unsafe { &File::from_raw_fd(emulate.state.keymap.unwrap().1.as_raw_fd()) };
|
||||
// let mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() };
|
||||
// log::debug!("{:?}", &mmap[..100]);
|
||||
Ok(emulate)
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn add_client(&mut self, client: EmulationHandle) {
|
||||
let pointer: Vp = self.vpm.create_virtual_pointer(None, &self.qh, ());
|
||||
let keyboard: Vk = self.vkm.create_virtual_keyboard(&self.seat, &self.qh, ());
|
||||
|
||||
// TODO: use server side keymap
|
||||
if let Some((format, fd, size)) = self.keymap.as_ref() {
|
||||
keyboard.keymap(*format, fd.as_fd(), *size);
|
||||
} else {
|
||||
panic!("no keymap");
|
||||
}
|
||||
|
||||
let vinput = VirtualInput { pointer, keyboard };
|
||||
|
||||
self.input_for_client.insert(client, vinput);
|
||||
}
|
||||
|
||||
fn destroy_client(&mut self, handle: EmulationHandle) {
|
||||
if let Some(input) = self.input_for_client.remove(&handle) {
|
||||
input.pointer.destroy();
|
||||
input.keyboard.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl InputEmulation for WlrootsEmulation {
|
||||
async fn consume(&mut self, event: Event, handle: EmulationHandle) {
|
||||
if let Some(virtual_input) = self.state.input_for_client.get(&handle) {
|
||||
if self.last_flush_failed {
|
||||
if let Err(WaylandError::Io(e)) = self.queue.flush() {
|
||||
if e.kind() == io::ErrorKind::WouldBlock {
|
||||
/*
|
||||
* outgoing buffer is full - sending more events
|
||||
* will overwhelm the output buffer and leave the
|
||||
* wayland connection in a broken state
|
||||
*/
|
||||
log::warn!("can't keep up, discarding event: ({handle}) - {event:?}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual_input.consume_event(event).unwrap();
|
||||
match self.queue.flush() {
|
||||
Err(WaylandError::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
self.last_flush_failed = true;
|
||||
log::warn!("can't keep up, retrying ...");
|
||||
}
|
||||
Err(WaylandError::Io(e)) => {
|
||||
log::error!("{e}")
|
||||
}
|
||||
Err(WaylandError::Protocol(e)) => {
|
||||
panic!("wayland protocol violation: {e}")
|
||||
}
|
||||
Ok(()) => {
|
||||
self.last_flush_failed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create(&mut self, handle: EmulationHandle) {
|
||||
self.state.add_client(handle);
|
||||
if let Err(e) = self.queue.flush() {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
}
|
||||
async fn destroy(&mut self, handle: EmulationHandle) {
|
||||
self.state.destroy_client(handle);
|
||||
if let Err(e) = self.queue.flush() {
|
||||
log::error!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VirtualInput {
|
||||
pointer: Vp,
|
||||
keyboard: Vk,
|
||||
}
|
||||
|
||||
impl VirtualInput {
|
||||
fn consume_event(&self, event: Event) -> Result<(), ()> {
|
||||
match event {
|
||||
Event::Pointer(e) => {
|
||||
match e {
|
||||
PointerEvent::Motion {
|
||||
time,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => self.pointer.motion(time, relative_x, relative_y),
|
||||
PointerEvent::Button {
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let state: ButtonState = state.try_into()?;
|
||||
self.pointer.button(time, button, state);
|
||||
}
|
||||
PointerEvent::Axis { time, axis, value } => {
|
||||
let axis: Axis = (axis as u32).try_into()?;
|
||||
self.pointer.axis(time, axis, value);
|
||||
self.pointer.frame();
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let axis: Axis = (axis as u32).try_into()?;
|
||||
self.pointer
|
||||
.axis_discrete(0, axis, value as f64 / 6., value / 120);
|
||||
self.pointer.frame();
|
||||
}
|
||||
PointerEvent::Frame {} => self.pointer.frame(),
|
||||
}
|
||||
self.pointer.frame();
|
||||
}
|
||||
Event::Keyboard(e) => match e {
|
||||
KeyboardEvent::Key { time, key, state } => {
|
||||
self.keyboard.key(time, key, state as u32);
|
||||
}
|
||||
KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
} => {
|
||||
self.keyboard
|
||||
.modifiers(mods_depressed, mods_latched, mods_locked, group);
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
delegate_noop!(State: Vp);
|
||||
delegate_noop!(State: Vk);
|
||||
delegate_noop!(State: VpManager);
|
||||
delegate_noop!(State: VkManager);
|
||||
|
||||
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
|
||||
fn event(
|
||||
_: &mut State,
|
||||
_: &wl_registry::WlRegistry,
|
||||
_: wl_registry::Event,
|
||||
_: &GlobalListContents,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<State>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlKeyboard, ()> for State {
|
||||
fn event(
|
||||
state: &mut Self,
|
||||
_: &WlKeyboard,
|
||||
event: <WlKeyboard as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_keyboard::Event::Keymap { format, fd, size } = event {
|
||||
state.keymap = Some((u32::from(format), fd, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dispatch<WlSeat, ()> for State {
|
||||
fn event(
|
||||
_: &mut Self,
|
||||
seat: &WlSeat,
|
||||
event: <WlSeat as wayland_client::Proxy>::Event,
|
||||
_: &(),
|
||||
_: &Connection,
|
||||
qhandle: &QueueHandle<Self>,
|
||||
) {
|
||||
if let wl_seat::Event::Capabilities {
|
||||
capabilities: WEnum::Value(capabilities),
|
||||
} = event
|
||||
{
|
||||
if capabilities.contains(wl_seat::Capability::Keyboard) {
|
||||
seat.get_keyboard(qhandle, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use std::ptr;
|
||||
use x11::{
|
||||
xlib::{self, XCloseDisplay},
|
||||
xtest,
|
||||
};
|
||||
|
||||
use crate::event::{
|
||||
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
|
||||
};
|
||||
|
||||
use super::{error::X11EmulationCreationError, EmulationHandle, InputEmulation};
|
||||
|
||||
pub struct X11Emulation {
|
||||
display: *mut xlib::Display,
|
||||
}
|
||||
|
||||
unsafe impl Send for X11Emulation {}
|
||||
|
||||
impl X11Emulation {
|
||||
pub fn new() -> Result<Self, X11EmulationCreationError> {
|
||||
let display = unsafe {
|
||||
match xlib::XOpenDisplay(ptr::null()) {
|
||||
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => {
|
||||
Err(X11EmulationCreationError::OpenDisplay)
|
||||
}
|
||||
display => Ok(display),
|
||||
}
|
||||
}?;
|
||||
Ok(Self { display })
|
||||
}
|
||||
|
||||
fn relative_motion(&self, dx: i32, dy: i32) {
|
||||
unsafe {
|
||||
xtest::XTestFakeRelativeMotionEvent(self.display, dx, dy, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn emulate_mouse_button(&self, button: u32, state: u32) {
|
||||
unsafe {
|
||||
let x11_button = match button {
|
||||
BTN_RIGHT => 3,
|
||||
BTN_MIDDLE => 2,
|
||||
BTN_BACK => 8,
|
||||
BTN_FORWARD => 9,
|
||||
BTN_LEFT => 1,
|
||||
_ => 1,
|
||||
};
|
||||
xtest::XTestFakeButtonEvent(self.display, x11_button, state as i32, 0);
|
||||
};
|
||||
}
|
||||
|
||||
const SCROLL_UP: u32 = 4;
|
||||
const SCROLL_DOWN: u32 = 5;
|
||||
const SCROLL_LEFT: u32 = 6;
|
||||
const SCROLL_RIGHT: u32 = 7;
|
||||
|
||||
fn emulate_scroll(&self, axis: u8, value: f64) {
|
||||
let direction = match axis {
|
||||
1 => {
|
||||
if value < 0.0 {
|
||||
Self::SCROLL_LEFT
|
||||
} else {
|
||||
Self::SCROLL_RIGHT
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if value < 0.0 {
|
||||
Self::SCROLL_UP
|
||||
} else {
|
||||
Self::SCROLL_DOWN
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
xtest::XTestFakeButtonEvent(self.display, direction, 1, 0);
|
||||
xtest::XTestFakeButtonEvent(self.display, direction, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn emulate_key(&self, key: u32, state: u8) {
|
||||
let key = key + 8; // xorg keycodes are shifted by 8
|
||||
unsafe {
|
||||
xtest::XTestFakeKeyEvent(self.display, key, state as i32, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11Emulation {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
XCloseDisplay(self.display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl InputEmulation for X11Emulation {
|
||||
async fn consume(&mut self, event: Event, _: EmulationHandle) {
|
||||
match event {
|
||||
Event::Pointer(pointer_event) => match pointer_event {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
self.relative_motion(relative_x as i32, relative_y as i32);
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
self.emulate_mouse_button(button, state);
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
self.emulate_scroll(axis, value);
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
self.emulate_scroll(axis, value as f64);
|
||||
}
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Event::Keyboard(KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
}) => {
|
||||
self.emulate_key(key, state);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
unsafe {
|
||||
xlib::XFlush(self.display);
|
||||
}
|
||||
}
|
||||
|
||||
async fn create(&mut self, _: EmulationHandle) {
|
||||
// for our purposes it does not matter what client sent the event
|
||||
}
|
||||
|
||||
async fn destroy(&mut self, _: EmulationHandle) {
|
||||
// for our purposes it does not matter what client sent the event
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use ashpd::{
|
||||
desktop::{
|
||||
remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop},
|
||||
ResponseError, Session,
|
||||
},
|
||||
WindowIdentifier,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::event::{
|
||||
Event::{Keyboard, Pointer},
|
||||
KeyboardEvent, PointerEvent,
|
||||
};
|
||||
|
||||
use super::{error::XdpEmulationCreationError, EmulationHandle, InputEmulation};
|
||||
|
||||
pub struct DesktopPortalEmulation<'a> {
|
||||
proxy: RemoteDesktop<'a>,
|
||||
session: Option<Session<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> DesktopPortalEmulation<'a> {
|
||||
pub async fn new() -> Result<DesktopPortalEmulation<'a>, XdpEmulationCreationError> {
|
||||
log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ...");
|
||||
let proxy = RemoteDesktop::new().await?;
|
||||
|
||||
// retry when user presses the cancel button
|
||||
let (session, _) = loop {
|
||||
log::debug!("creating session ...");
|
||||
let session = proxy.create_session().await?;
|
||||
|
||||
log::debug!("selecting devices ...");
|
||||
proxy
|
||||
.select_devices(&session, DeviceType::Keyboard | DeviceType::Pointer)
|
||||
.await?;
|
||||
|
||||
log::info!("requesting permission for input emulation");
|
||||
match proxy
|
||||
.start(&session, &WindowIdentifier::default())
|
||||
.await?
|
||||
.response()
|
||||
{
|
||||
Ok(d) => break (session, d),
|
||||
Err(ashpd::Error::Response(ResponseError::Cancelled)) => {
|
||||
log::warn!("request cancelled!");
|
||||
continue;
|
||||
}
|
||||
e => e?,
|
||||
};
|
||||
};
|
||||
|
||||
log::debug!("started session");
|
||||
let session = Some(session);
|
||||
|
||||
Ok(Self { proxy, session })
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
|
||||
async fn consume(&mut self, event: crate::event::Event, _client: crate::client::ClientHandle) {
|
||||
match event {
|
||||
Pointer(p) => match p {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_motion(
|
||||
self.session.as_ref().expect("no session"),
|
||||
relative_x,
|
||||
relative_y,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
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.as_ref().expect("no session"),
|
||||
button as i32,
|
||||
state,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let axis = match axis {
|
||||
0 => Axis::Vertical,
|
||||
_ => Axis::Horizontal,
|
||||
};
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_axis_discrete(
|
||||
self.session.as_ref().expect("no session"),
|
||||
axis,
|
||||
value,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => {
|
||||
let axis = match axis {
|
||||
0 => Axis::Vertical,
|
||||
_ => Axis::Horizontal,
|
||||
};
|
||||
let (dx, dy) = match axis {
|
||||
Axis::Vertical => (0., value),
|
||||
Axis::Horizontal => (value, 0.),
|
||||
};
|
||||
if let Err(e) = self
|
||||
.proxy
|
||||
.notify_pointer_axis(
|
||||
self.session.as_ref().expect("no session"),
|
||||
dx,
|
||||
dy,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
PointerEvent::Frame {} => {}
|
||||
},
|
||||
Keyboard(k) => {
|
||||
match k {
|
||||
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.as_ref().expect("no session"),
|
||||
key as i32,
|
||||
state,
|
||||
)
|
||||
.await
|
||||
{
|
||||
log::warn!("{e}");
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Modifiers { .. } => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
async fn create(&mut self, _client: EmulationHandle) {}
|
||||
async fn destroy(&mut self, _client: EmulationHandle) {}
|
||||
}
|
||||
|
||||
impl<'a> Drop for DesktopPortalEmulation<'a> {
|
||||
fn drop(&mut self) {
|
||||
let session = self.session.take().expect("no session");
|
||||
tokio::runtime::Handle::try_current()
|
||||
.expect("no runtime")
|
||||
.block_on(async move {
|
||||
log::debug!("closing remote desktop session");
|
||||
if let Err(e) = session.close().await {
|
||||
log::error!("failed to close remote desktop session: {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::config::Config;
|
||||
use crate::emulate;
|
||||
use crate::event::{Event, PointerEvent};
|
||||
use anyhow::Result;
|
||||
use input_event::{Event, PointerEvent};
|
||||
use std::f64::consts::PI;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::task::LocalSet;
|
||||
@@ -22,7 +21,8 @@ const FREQUENCY_HZ: f64 = 1.0;
|
||||
const RADIUS: f64 = 100.0;
|
||||
|
||||
async fn input_emulation_test(config: Config) -> Result<()> {
|
||||
let mut emulation = emulate::create(config.emulation_backend).await?;
|
||||
let backend = config.emulation_backend.map(|b| b.into());
|
||||
let mut emulation = input_emulation::create(backend).await?;
|
||||
emulation.create(0).await;
|
||||
let start = Instant::now();
|
||||
let mut offset = (0, 0);
|
||||
|
||||
586
src/event.rs
586
src/event.rs
@@ -1,586 +0,0 @@
|
||||
use crate::scancode;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt::{self, Display},
|
||||
};
|
||||
|
||||
// FIXME
|
||||
pub const BTN_LEFT: u32 = 0x110;
|
||||
pub const BTN_RIGHT: u32 = 0x111;
|
||||
pub const BTN_MIDDLE: u32 = 0x112;
|
||||
pub const BTN_BACK: u32 = 0x113;
|
||||
pub const BTN_FORWARD: u32 = 0x114;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum PointerEvent {
|
||||
Motion {
|
||||
time: u32,
|
||||
relative_x: f64,
|
||||
relative_y: f64,
|
||||
},
|
||||
Button {
|
||||
time: u32,
|
||||
button: u32,
|
||||
state: u32,
|
||||
},
|
||||
Axis {
|
||||
time: u32,
|
||||
axis: u8,
|
||||
value: f64,
|
||||
},
|
||||
AxisDiscrete120 {
|
||||
axis: u8,
|
||||
value: i32,
|
||||
},
|
||||
Frame {},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum KeyboardEvent {
|
||||
Key {
|
||||
time: u32,
|
||||
key: u32,
|
||||
state: u8,
|
||||
},
|
||||
Modifiers {
|
||||
mods_depressed: u32,
|
||||
mods_latched: u32,
|
||||
mods_locked: u32,
|
||||
group: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub enum Event {
|
||||
/// pointer event (motion / button / axis)
|
||||
Pointer(PointerEvent),
|
||||
/// keyboard events (key / modifiers)
|
||||
Keyboard(KeyboardEvent),
|
||||
/// enter event: request to enter a client.
|
||||
/// The client must release the pointer if it is grabbed
|
||||
/// and reply with a leave event, as soon as its ready to
|
||||
/// receive events
|
||||
Enter(),
|
||||
/// leave event: this client is now ready to receive events and will
|
||||
/// not send any events after until it sends an enter event
|
||||
Leave(),
|
||||
/// ping a client, to see if it is still alive. A client that does
|
||||
/// not respond with a pong event will be assumed to be offline.
|
||||
Ping(),
|
||||
/// response to a ping event: this event signals that a client
|
||||
/// is still alive but must otherwise be ignored
|
||||
Pong(),
|
||||
/// explicit disconnect request. The client will no longer
|
||||
/// send events until the next Enter event. All of its keys should be released.
|
||||
Disconnect(),
|
||||
}
|
||||
|
||||
impl Display for PointerEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
PointerEvent::Motion {
|
||||
time: _,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => write!(f, "motion({relative_x},{relative_y})"),
|
||||
PointerEvent::Button {
|
||||
time: _,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let str = match *button {
|
||||
BTN_LEFT => Some("left"),
|
||||
BTN_RIGHT => Some("right"),
|
||||
BTN_MIDDLE => Some("middle"),
|
||||
BTN_FORWARD => Some("forward"),
|
||||
BTN_BACK => Some("back"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(button) = str {
|
||||
write!(f, "button({button}, {state})")
|
||||
} else {
|
||||
write!(f, "button({button}, {state}")
|
||||
}
|
||||
}
|
||||
PointerEvent::Axis {
|
||||
time: _,
|
||||
axis,
|
||||
value,
|
||||
} => write!(f, "scroll({axis}, {value})"),
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
write!(f, "scroll-120 ({axis}, {value})")
|
||||
}
|
||||
PointerEvent::Frame {} => write!(f, "frame()"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for KeyboardEvent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
KeyboardEvent::Key {
|
||||
time: _,
|
||||
key,
|
||||
state,
|
||||
} => {
|
||||
let scan = scancode::Linux::try_from(*key);
|
||||
if let Ok(scan) = scan {
|
||||
write!(f, "key({scan:?}, {state})")
|
||||
} else {
|
||||
write!(f, "key({key}, {state})")
|
||||
}
|
||||
}
|
||||
KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
} => write!(
|
||||
f,
|
||||
"modifiers({mods_depressed},{mods_latched},{mods_locked},{group})"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Event {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Event::Pointer(p) => write!(f, "{}", p),
|
||||
Event::Keyboard(k) => write!(f, "{}", k),
|
||||
Event::Enter() => write!(f, "enter"),
|
||||
Event::Leave() => write!(f, "leave"),
|
||||
Event::Ping() => write!(f, "ping"),
|
||||
Event::Pong() => write!(f, "pong"),
|
||||
Event::Disconnect() => write!(f, "disconnect"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn event_type(&self) -> EventType {
|
||||
match self {
|
||||
Self::Pointer(_) => EventType::Pointer,
|
||||
Self::Keyboard(_) => EventType::Keyboard,
|
||||
Self::Enter() => EventType::Enter,
|
||||
Self::Leave() => EventType::Leave,
|
||||
Self::Ping() => EventType::Ping,
|
||||
Self::Pong() => EventType::Pong,
|
||||
Self::Disconnect() => EventType::Disconnect,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerEvent {
|
||||
fn event_type(&self) -> PointerEventType {
|
||||
match self {
|
||||
Self::Motion { .. } => PointerEventType::Motion,
|
||||
Self::Button { .. } => PointerEventType::Button,
|
||||
Self::Axis { .. } => PointerEventType::Axis,
|
||||
Self::AxisDiscrete120 { .. } => PointerEventType::AxisDiscrete120,
|
||||
Self::Frame { .. } => PointerEventType::Frame,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardEvent {
|
||||
fn event_type(&self) -> KeyboardEventType {
|
||||
match self {
|
||||
KeyboardEvent::Key { .. } => KeyboardEventType::Key,
|
||||
KeyboardEvent::Modifiers { .. } => KeyboardEventType::Modifiers,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PointerEventType {
|
||||
Motion,
|
||||
Button,
|
||||
Axis,
|
||||
AxisDiscrete120,
|
||||
Frame,
|
||||
}
|
||||
enum KeyboardEventType {
|
||||
Key,
|
||||
Modifiers,
|
||||
}
|
||||
enum EventType {
|
||||
Pointer,
|
||||
Keyboard,
|
||||
Enter,
|
||||
Leave,
|
||||
Ping,
|
||||
Pong,
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for PointerEventType {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
x if x == Self::Motion as u8 => Ok(Self::Motion),
|
||||
x if x == Self::Button as u8 => Ok(Self::Button),
|
||||
x if x == Self::Axis as u8 => Ok(Self::Axis),
|
||||
x if x == Self::AxisDiscrete120 as u8 => Ok(Self::AxisDiscrete120),
|
||||
x if x == Self::Frame as u8 => Ok(Self::Frame),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid pointer event type {}", value),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for KeyboardEventType {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: u8) -> Result<Self> {
|
||||
match value {
|
||||
x if x == Self::Key as u8 => Ok(Self::Key),
|
||||
x if x == Self::Modifiers as u8 => Ok(Self::Modifiers),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid keyboard event type {}", value),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Event> for Vec<u8> {
|
||||
fn from(event: &Event) -> Self {
|
||||
let event_id = vec![event.event_type() as u8];
|
||||
let event_data = match event {
|
||||
Event::Pointer(p) => p.into(),
|
||||
Event::Keyboard(k) => k.into(),
|
||||
Event::Enter() => vec![],
|
||||
Event::Leave() => vec![],
|
||||
Event::Ping() => vec![],
|
||||
Event::Pong() => vec![],
|
||||
Event::Disconnect() => vec![],
|
||||
};
|
||||
[event_id, event_data].concat()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ProtocolError {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for ProtocolError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Protocol violation: {}", self.msg)
|
||||
}
|
||||
}
|
||||
impl Error for ProtocolError {}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Event {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: Vec<u8>) -> Result<Self> {
|
||||
let event_id = u8::from_be_bytes(value[..1].try_into()?);
|
||||
match event_id {
|
||||
i if i == (EventType::Pointer as u8) => Ok(Event::Pointer(value.try_into()?)),
|
||||
i if i == (EventType::Keyboard as u8) => Ok(Event::Keyboard(value.try_into()?)),
|
||||
i if i == (EventType::Enter as u8) => Ok(Event::Enter()),
|
||||
i if i == (EventType::Leave as u8) => Ok(Event::Leave()),
|
||||
i if i == (EventType::Ping as u8) => Ok(Event::Ping()),
|
||||
i if i == (EventType::Pong as u8) => Ok(Event::Pong()),
|
||||
i if i == (EventType::Disconnect as u8) => Ok(Event::Disconnect()),
|
||||
_ => Err(anyhow!(ProtocolError {
|
||||
msg: format!("invalid event_id {}", event_id),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PointerEvent> for Vec<u8> {
|
||||
fn from(event: &PointerEvent) -> Self {
|
||||
let id = vec![event.event_type() as u8];
|
||||
let data = match event {
|
||||
PointerEvent::Motion {
|
||||
time,
|
||||
relative_x,
|
||||
relative_y,
|
||||
} => {
|
||||
let time = time.to_be_bytes();
|
||||
let relative_x = relative_x.to_be_bytes();
|
||||
let relative_y = relative_y.to_be_bytes();
|
||||
[&time[..], &relative_x[..], &relative_y[..]].concat()
|
||||
}
|
||||
PointerEvent::Button {
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
} => {
|
||||
let time = time.to_be_bytes();
|
||||
let button = button.to_be_bytes();
|
||||
let state = state.to_be_bytes();
|
||||
[&time[..], &button[..], &state[..]].concat()
|
||||
}
|
||||
PointerEvent::Axis { time, axis, value } => {
|
||||
let time = time.to_be_bytes();
|
||||
let axis = axis.to_be_bytes();
|
||||
let value = value.to_be_bytes();
|
||||
[&time[..], &axis[..], &value[..]].concat()
|
||||
}
|
||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||
let axis = axis.to_be_bytes();
|
||||
let value = value.to_be_bytes();
|
||||
[&axis[..], &value[..]].concat()
|
||||
}
|
||||
PointerEvent::Frame {} => {
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
[id, data].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for PointerEvent {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self> {
|
||||
match data.get(1) {
|
||||
Some(id) => {
|
||||
let event_type = match id.to_owned().try_into() {
|
||||
Ok(event_type) => event_type,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
match event_type {
|
||||
PointerEventType::Motion => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let relative_x = match data.get(6..14) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let relative_y = match data.get(14..22) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(Self::Motion {
|
||||
time,
|
||||
relative_x,
|
||||
relative_y,
|
||||
})
|
||||
}
|
||||
PointerEventType::Button => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let button = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let state = match data.get(10..14) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(Self::Button {
|
||||
time,
|
||||
button,
|
||||
state,
|
||||
})
|
||||
}
|
||||
PointerEventType::Axis => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 2".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let axis = match data.get(6) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Byte at index 6".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
let value = match data.get(7..15) {
|
||||
Some(d) => f64::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 8 Bytes at index 7".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
Ok(Self::Axis { time, axis, value })
|
||||
}
|
||||
PointerEventType::AxisDiscrete120 => {
|
||||
let axis = match data.get(2) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Byte at index 2".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
let value = match data.get(3..7) {
|
||||
Some(d) => i32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 3".into(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
Ok(Self::AxisDiscrete120 { axis, value })
|
||||
}
|
||||
PointerEventType::Frame => Ok(Self::Frame {}),
|
||||
}
|
||||
}
|
||||
None => Err(anyhow!(ProtocolError {
|
||||
msg: "Expected an element at index 0".into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KeyboardEvent> for Vec<u8> {
|
||||
fn from(event: &KeyboardEvent) -> Self {
|
||||
let id = vec![event.event_type() as u8];
|
||||
let data = match event {
|
||||
KeyboardEvent::Key { time, key, state } => {
|
||||
let time = time.to_be_bytes();
|
||||
let key = key.to_be_bytes();
|
||||
let state = state.to_be_bytes();
|
||||
[&time[..], &key[..], &state[..]].concat()
|
||||
}
|
||||
KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
} => {
|
||||
let mods_depressed = mods_depressed.to_be_bytes();
|
||||
let mods_latched = mods_latched.to_be_bytes();
|
||||
let mods_locked = mods_locked.to_be_bytes();
|
||||
let group = group.to_be_bytes();
|
||||
[
|
||||
&mods_depressed[..],
|
||||
&mods_latched[..],
|
||||
&mods_locked[..],
|
||||
&group[..],
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
};
|
||||
[id, data].concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for KeyboardEvent {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(data: Vec<u8>) -> Result<Self> {
|
||||
match data.get(1) {
|
||||
Some(id) => {
|
||||
let event_type = match id.to_owned().try_into() {
|
||||
Ok(event_type) => event_type,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
match event_type {
|
||||
KeyboardEventType::Key => {
|
||||
let time = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let key = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let state = match data.get(10) {
|
||||
Some(d) => *d,
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 1 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(KeyboardEvent::Key { time, key, state })
|
||||
}
|
||||
KeyboardEventType::Modifiers => {
|
||||
let mods_depressed = match data.get(2..6) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 6".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let mods_latched = match data.get(6..10) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 10".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let mods_locked = match data.get(10..14) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 14".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
let group = match data.get(14..18) {
|
||||
Some(d) => u32::from_be_bytes(d.try_into()?),
|
||||
None => {
|
||||
return Err(anyhow!(ProtocolError {
|
||||
msg: "Expected 4 Bytes at index 18".into(),
|
||||
}))
|
||||
}
|
||||
};
|
||||
Ok(KeyboardEvent::Modifiers {
|
||||
mods_depressed,
|
||||
mods_latched,
|
||||
mods_locked,
|
||||
group,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
None => Err(anyhow!(ProtocolError {
|
||||
msg: "Expected an element at index 0".into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,8 @@
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
pub mod dns;
|
||||
pub mod event;
|
||||
pub mod server;
|
||||
|
||||
pub mod capture;
|
||||
pub mod emulate;
|
||||
|
||||
pub mod capture_test;
|
||||
pub mod emulation_test;
|
||||
pub mod frontend;
|
||||
pub mod scancode;
|
||||
|
||||
851
src/scancode.rs
851
src/scancode.rs
@@ -1,851 +0,0 @@
|
||||
use num_enum::TryFromPrimitive;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/*
|
||||
* https://learn.microsoft.com/en-us/windows/win32/inputdev/about-keyboard-input
|
||||
* https://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/translate.pdf
|
||||
* https://kbd-project.org/docs/scancodes/scancodes-1.html
|
||||
*/
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
pub enum Windows {
|
||||
Shutdown = 0xE05E,
|
||||
SystemSleep = 0xE05F,
|
||||
SystemWakeUp = 0xE063,
|
||||
ErrorRollOver = 0x00FF,
|
||||
KeyA = 0x001E,
|
||||
KeyB = 0x0030,
|
||||
KeyC = 0x002E,
|
||||
KeyD = 0x0020,
|
||||
KeyE = 0x0012,
|
||||
KeyF = 0x0021,
|
||||
KeyG = 0x0022,
|
||||
KeyH = 0x0023,
|
||||
KeyI = 0x0017,
|
||||
KeyJ = 0x0024,
|
||||
KeyK = 0x0025,
|
||||
KeyL = 0x0026,
|
||||
KeyM = 0x0032,
|
||||
KeyN = 0x0031,
|
||||
KeyO = 0x0018,
|
||||
KeyP = 0x0019,
|
||||
KeyQ = 0x0010,
|
||||
KeyR = 0x0013,
|
||||
KeyS = 0x001F,
|
||||
KeyT = 0x0014,
|
||||
KeyU = 0x0016,
|
||||
KeyV = 0x002F,
|
||||
KeyW = 0x0011,
|
||||
KeyX = 0x002D,
|
||||
KeyY = 0x0015,
|
||||
KeyZ = 0x002C,
|
||||
Key1 = 0x0002,
|
||||
Key2 = 0x0003,
|
||||
Key3 = 0x0004,
|
||||
Key4 = 0x0005,
|
||||
Key5 = 0x0006,
|
||||
Key6 = 0x0007,
|
||||
Key7 = 0x0008,
|
||||
Key8 = 0x0009,
|
||||
Key9 = 0x000A,
|
||||
Key0 = 0x000B,
|
||||
KeyEnter = 0x001C,
|
||||
KeyEsc = 0x0001,
|
||||
KeyDelete = 0x000E,
|
||||
KeyTab = 0x000F,
|
||||
KeySpace = 0x0039,
|
||||
KeyMinus = 0x000C,
|
||||
KeyEqual = 0x000D,
|
||||
KeyLeftBrace = 0x001A,
|
||||
KeyRightBrace = 0x001B,
|
||||
KeyBackslash = 0x002B,
|
||||
KeySemiColon = 0x0027,
|
||||
KeyApostrophe = 0x0028,
|
||||
KeyGrave = 0x0029,
|
||||
KeyComma = 0x0033,
|
||||
KeyDot = 0x0034,
|
||||
KeySlash = 0x0035,
|
||||
KeyCapsLock = 0x003A,
|
||||
KeyF1 = 0x003B,
|
||||
KeyF2 = 0x003C,
|
||||
KeyF3 = 0x003D,
|
||||
KeyF4 = 0x003E,
|
||||
KeyF5 = 0x003F,
|
||||
KeyF6 = 0x0040,
|
||||
KeyF7 = 0x0041,
|
||||
KeyF8 = 0x0042,
|
||||
KeyF9 = 0x0043,
|
||||
KeyF10 = 0x0044,
|
||||
KeyF11 = 0x0057,
|
||||
KeyF12 = 0x0058,
|
||||
KeyPrintScreen = 0xE037,
|
||||
KeyScrollLock = 0x0046,
|
||||
KeyPause = 0xE11D45,
|
||||
KeyInsert = 0xE052,
|
||||
KeyHome = 0xE047,
|
||||
KeyPageUp = 0xE049,
|
||||
KeyDeleteForward = 0xE053,
|
||||
KeyEnd = 0xE04F,
|
||||
KeyPageDown = 0xE051,
|
||||
KeyRight = 0xE04D,
|
||||
KeyLeft = 0xE04B,
|
||||
KeyDown = 0xE050,
|
||||
KeyUp = 0xE048,
|
||||
KeypadNumLock = 0x0045,
|
||||
KeypadSlash = 0xE035,
|
||||
KeypadStar = 0x0037,
|
||||
KeypadDash = 0x004A,
|
||||
KeypadPlus = 0x004E,
|
||||
KeypadEnter = 0xE01C,
|
||||
Keypad1End = 0x004F,
|
||||
Keypad2DownArrow = 0x0050,
|
||||
Keypad3PageDn = 0x0051,
|
||||
Keypad4LeftArrow = 0x004B,
|
||||
Keypad5 = 0x004C,
|
||||
Keypad6RightArrow = 0x004D,
|
||||
Keypad7Home = 0x0047,
|
||||
Keypad8UpArrow = 0x0048,
|
||||
Keypad9PageUp = 0x0049,
|
||||
Keypad0Insert = 0x0052,
|
||||
KeypadDot = 0x0053,
|
||||
KeyNonUSSlashBar = 0x0056,
|
||||
KeyApplication = 0xE05D,
|
||||
KeypadEquals = 0x0059,
|
||||
KeyF13 = 0x0064,
|
||||
KeyF14 = 0x0065,
|
||||
KeyF15 = 0x0066,
|
||||
KeyF16 = 0x0067,
|
||||
KeyF17 = 0x0068,
|
||||
KeyF18 = 0x0069,
|
||||
KeyF19 = 0x006A,
|
||||
KeyF20 = 0x006B,
|
||||
KeyF21 = 0x006C,
|
||||
KeyF22 = 0x006D,
|
||||
KeyF23 = 0x006E,
|
||||
KeyF24 = 0x0076, // KeyLANG5
|
||||
KeypadComma = 0x007E,
|
||||
KeyInternational1 = 0x0073,
|
||||
KeyInternational2 = 0x0070,
|
||||
KeyInternational3 = 0x007D, // typo in doc -> its Int'l 3 not Int'l 2
|
||||
#[allow(dead_code)]
|
||||
KeyInternational4 = 0x0079,
|
||||
#[allow(dead_code)]
|
||||
KeyInternational5 = 0x007B,
|
||||
// KeyInternational6 = 0x005C,
|
||||
KeyLANG1 = 0x0072,
|
||||
KeyLANG2 = 0x0071,
|
||||
KeyLANG3 = 0x0078,
|
||||
KeyLANG4 = 0x0077,
|
||||
// KeyLANG5 = 0x0076,
|
||||
KeyLeftCtrl = 0x001D,
|
||||
KeyLeftShift = 0x002A,
|
||||
KeyLeftAlt = 0x0038,
|
||||
KeyLeftGUI = 0xE05B,
|
||||
KeyRightCtrl = 0xE01D,
|
||||
KeyRightShift = 0x0036,
|
||||
KeyFakeRightShift = 0xE036,
|
||||
KeyRightAlt = 0xE038,
|
||||
KeyRightGUI = 0xE05C,
|
||||
KeyScanNextTrack = 0xE019,
|
||||
KeyScanPreviousTrack = 0xE010,
|
||||
KeyStop = 0xE024,
|
||||
KeyPlayPause = 0xE022,
|
||||
KeyMute = 0xE020,
|
||||
KeyVolumeUp = 0xE030,
|
||||
KeyVolumeDown = 0xE02E,
|
||||
#[allow(dead_code)]
|
||||
ALConsumerControlConfiguration = 0xE06D, // TODO Unused
|
||||
ALEmailReader = 0xE06C,
|
||||
ALCalculator = 0xE021,
|
||||
ALLocalMachineBrowser = 0xE06B,
|
||||
ACSearch = 0xE065,
|
||||
ACHome = 0xE032,
|
||||
ACBack = 0xE06A,
|
||||
ACForward = 0xE069,
|
||||
ACStop = 0xE068,
|
||||
ACRefresh = 0xE067,
|
||||
ACBookmarks = 0xE066,
|
||||
}
|
||||
|
||||
/*
|
||||
* https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h
|
||||
*/
|
||||
#[repr(u32)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, Hash, PartialEq, TryFromPrimitive)]
|
||||
#[allow(dead_code)]
|
||||
pub enum Linux {
|
||||
KeyReserved = 0,
|
||||
KeyEsc = 1,
|
||||
Key1 = 2,
|
||||
Key2 = 3,
|
||||
Key3 = 4,
|
||||
Key4 = 5,
|
||||
Key5 = 6,
|
||||
Key6 = 7,
|
||||
Key7 = 8,
|
||||
Key8 = 9,
|
||||
Key9 = 10,
|
||||
Key0 = 11,
|
||||
KeyMinus = 12,
|
||||
KeyEqual = 13,
|
||||
KeyBackspace = 14,
|
||||
KeyTab = 15,
|
||||
KeyQ = 16,
|
||||
KeyW = 17,
|
||||
KeyE = 18,
|
||||
KeyR = 19,
|
||||
KeyT = 20,
|
||||
KeyY = 21,
|
||||
KeyU = 22,
|
||||
KeyI = 23,
|
||||
KeyO = 24,
|
||||
KeyP = 25,
|
||||
KeyLeftbrace = 26,
|
||||
KeyRightbrace = 27,
|
||||
KeyEnter = 28,
|
||||
KeyLeftCtrl = 29,
|
||||
KeyA = 30,
|
||||
KeyS = 31,
|
||||
KeyD = 32,
|
||||
KeyF = 33,
|
||||
KeyG = 34,
|
||||
KeyH = 35,
|
||||
KeyJ = 36,
|
||||
KeyK = 37,
|
||||
KeyL = 38,
|
||||
KeySemicolon = 39,
|
||||
KeyApostrophe = 40,
|
||||
KeyGrave = 41,
|
||||
KeyLeftShift = 42,
|
||||
KeyBackslash = 43,
|
||||
KeyZ = 44,
|
||||
KeyX = 45,
|
||||
KeyC = 46,
|
||||
KeyV = 47,
|
||||
KeyB = 48,
|
||||
KeyN = 49,
|
||||
KeyM = 50,
|
||||
KeyComma = 51,
|
||||
KeyDot = 52,
|
||||
KeySlash = 53,
|
||||
KeyRightShift = 54,
|
||||
KeyKpAsterisk = 55,
|
||||
KeyLeftAlt = 56,
|
||||
KeySpace = 57,
|
||||
KeyCapsLock = 58,
|
||||
KeyF1 = 59,
|
||||
KeyF2 = 60,
|
||||
KeyF3 = 61,
|
||||
KeyF4 = 62,
|
||||
KeyF5 = 63,
|
||||
KeyF6 = 64,
|
||||
KeyF7 = 65,
|
||||
KeyF8 = 66,
|
||||
KeyF9 = 67,
|
||||
KeyF10 = 68,
|
||||
KeyNumlock = 69,
|
||||
KeyScrollLock = 70,
|
||||
KeyKp7 = 71,
|
||||
KeyKp8 = 72,
|
||||
KeyKp9 = 73,
|
||||
KeyKpMinus = 74,
|
||||
KeyKp4 = 75,
|
||||
KeyKp5 = 76,
|
||||
KeyKp6 = 77,
|
||||
KeyKpplus = 78,
|
||||
KeyKp1 = 79,
|
||||
KeyKp2 = 80,
|
||||
KeyKp3 = 81,
|
||||
KeyKp0 = 82,
|
||||
KeyKpDot = 83,
|
||||
Invalid = 84,
|
||||
KeyZenkakuhankaku = 85,
|
||||
Key102nd = 86,
|
||||
KeyF11 = 87,
|
||||
KeyF12 = 88,
|
||||
KeyRo = 89,
|
||||
KeyKatakana = 90,
|
||||
KeyHiragana = 91,
|
||||
KeyHenkan = 92,
|
||||
KeyKatakanahiragana = 93,
|
||||
KeyMuhenkan = 94,
|
||||
KeyKpJpComma = 95,
|
||||
KeyKpEnter = 96,
|
||||
KeyRightCtrl = 97,
|
||||
KeyKpslash = 98,
|
||||
KeySysrq = 99,
|
||||
KeyRightalt = 100,
|
||||
KeyLinefeed = 101,
|
||||
KeyHome = 102,
|
||||
KeyUp = 103,
|
||||
KeyPageup = 104,
|
||||
KeyLeft = 105,
|
||||
KeyRight = 106,
|
||||
KeyEnd = 107,
|
||||
KeyDown = 108,
|
||||
KeyPagedown = 109,
|
||||
KeyInsert = 110,
|
||||
KeyDelete = 111,
|
||||
KeyMacro = 112,
|
||||
KeyMute = 113,
|
||||
KeyVolumeDown = 114,
|
||||
KeyVolumeUp = 115,
|
||||
KeyPower = 116, /* SC System Power Down */
|
||||
KeyKpequal = 117,
|
||||
KeyKpplusminus = 118,
|
||||
KeyPause = 119,
|
||||
KeyScale = 120, /* AL Compiz Scale (Expose) */
|
||||
KeyKpcomma = 121,
|
||||
KeyHanguel = 122,
|
||||
// KEY_HANGUEL = KeyHangeul,
|
||||
KeyHanja = 123,
|
||||
KeyYen = 124,
|
||||
KeyLeftMeta = 125,
|
||||
KeyRightmeta = 126,
|
||||
KeyCompose = 127,
|
||||
KeyStop = 128, /* AC Stop */
|
||||
KeyAgain = 129,
|
||||
KeyProps = 130, /* AC Properties */
|
||||
KeyUndo = 131, /* AC Undo */
|
||||
KeyFront = 132,
|
||||
KeyCopy = 133, /* AC Copy */
|
||||
KeyOpen = 134, /* AC Open */
|
||||
KeyPaste = 135, /* AC Paste */
|
||||
KeyFind = 136, /* AC Search */
|
||||
KeyCut = 137, /* AC Cut */
|
||||
KeyHelp = 138, /* AL Integrated Help Center */
|
||||
KeyMenu = 139, /* Menu (show menu) */
|
||||
KeyCalc = 140, /* AL Calculator */
|
||||
KeySetup = 141,
|
||||
KeySleep = 142, /* SC System Sleep */
|
||||
KeyWakeup = 143, /* System Wake Up */
|
||||
KeyFile = 144, /* AL Local Machine Browser */
|
||||
KeySendfile = 145,
|
||||
KeyDeletefile = 146,
|
||||
KeyXfer = 147,
|
||||
KeyProg1 = 148,
|
||||
KeyProg2 = 149,
|
||||
KeyWww = 150, /* AL Internet Browser */
|
||||
KeyMsdos = 151,
|
||||
KeyCoffee = 152, /* AL Terminal Lock/Screensaver */
|
||||
// KEY_SCREENLOCK = KeyCoffee,
|
||||
KeyRotateDisplay = 153, /* Display orientation for e.g. tablets */
|
||||
// KEY_DIRECTION = KeyRotateDisplay,
|
||||
KeyCyclewindows = 154,
|
||||
KeyMail = 155,
|
||||
KeyBookmarks = 156, /* AC Bookmarks */
|
||||
KeyComputer = 157,
|
||||
KeyBack = 158, /* AC Back */
|
||||
KeyForward = 159, /* AC Forward */
|
||||
KeyClosecd = 160,
|
||||
KeyEjectcd = 161,
|
||||
KeyEjectclosecd = 162,
|
||||
KeyNextsong = 163,
|
||||
KeyPlaypause = 164,
|
||||
KeyPrevioussong = 165,
|
||||
KeyStopcd = 166,
|
||||
KeyRecord = 167,
|
||||
KeyRewind = 168,
|
||||
KeyPhone = 169, /* Media Select Telephone */
|
||||
KeyIso = 170,
|
||||
KeyConfig = 171, /* AL Consumer Control Configuration */
|
||||
KeyHomepage = 172, /* AC Home */
|
||||
KeyRefresh = 173, /* AC Refresh */
|
||||
KeyExit = 174, /* AC Exit */
|
||||
KeyMove = 175,
|
||||
KeyEdit = 176,
|
||||
KeyScrollup = 177,
|
||||
KeyScrolldown = 178,
|
||||
KeyKpleftparen = 179,
|
||||
KeyKprightparen = 180,
|
||||
KeyNew = 181, /* AC New */
|
||||
KeyRedo = 182, /* AC Redo/Repeat */
|
||||
KeyF13 = 183,
|
||||
KeyF14 = 184,
|
||||
KeyF15 = 185,
|
||||
KeyF16 = 186,
|
||||
KeyF17 = 187,
|
||||
KeyF18 = 188,
|
||||
KeyF19 = 189,
|
||||
KeyF20 = 190,
|
||||
KeyF21 = 191,
|
||||
KeyF22 = 192,
|
||||
KeyF23 = 193,
|
||||
KeyF24 = 194,
|
||||
Invalid1 = 195,
|
||||
Invalid2 = 196,
|
||||
Invalid3 = 197,
|
||||
Invalid4 = 198,
|
||||
Invalid5 = 199,
|
||||
KeyPlaycd = 200,
|
||||
KeyPausecd = 201,
|
||||
KeyProg3 = 202,
|
||||
KeyProg4 = 203,
|
||||
KeyAllApplications = 204, /* AC Desktop Show All Applications */
|
||||
// KEY_DASHBOARD = KeyAllApplications,
|
||||
KeySuspend = 205,
|
||||
KeyClose = 206, /* AC Close */
|
||||
KeyPlay = 207,
|
||||
KeyFastforward = 208,
|
||||
KeyBassboost = 209,
|
||||
KeyPrint = 210, /* AC Print */
|
||||
KeyHp = 211,
|
||||
KeyCamera = 212,
|
||||
KeySound = 213,
|
||||
KeyQuestion = 214,
|
||||
KeyEmail = 215,
|
||||
KeyChat = 216,
|
||||
KeySearch = 217,
|
||||
KeyConnect = 218,
|
||||
KeyFinance = 219, /* AL Checkbook/Finance */
|
||||
KeySport = 220,
|
||||
KeyShop = 221,
|
||||
KeyAlterase = 222,
|
||||
KeyCancel = 223, /* AC Cancel */
|
||||
KeyBrightnessdown = 224,
|
||||
KeyBrightnessup = 225,
|
||||
KeyMedia = 226,
|
||||
KeySwitchvideomode = 227, /* Cycle between available video, outputs (Monitor/LCD/TV-out/etc) */
|
||||
KeyKbdillumtoggle = 228,
|
||||
KeyKbdillumdown = 229,
|
||||
KeyKbdillumup = 230,
|
||||
KeySend = 231, /* AC Send */
|
||||
KeyReply = 232, /* AC Reply */
|
||||
KeyForwardmail = 233, /* AC Forward Msg */
|
||||
KeySave = 234, /* AC Save */
|
||||
KeyDocuments = 235,
|
||||
KeyBattery = 236,
|
||||
KeyBluetooth = 237,
|
||||
KeyWlan = 238,
|
||||
KeyUwb = 239,
|
||||
KeyUnknown = 240,
|
||||
KeyVideoNext = 241, /* drive next video source */
|
||||
KeyVideoPrev = 242, /* drive previous video source */
|
||||
KeyBrightnessCycle = 243, /* brightness up, after max is min */
|
||||
KeyBrightnessAuto = 244, /* Set Auto Brightness: manual, brightness control is off, rely on ambient */
|
||||
// KEY_BRIGHTNESS_ZERO=KeyBrightnessAuto,
|
||||
KeyDisplayOff = 245, /* display device to off state */
|
||||
KeyWwan = 246, /* Wireless WAN (LTE, UMTS, GSM, etc.) */
|
||||
// KEY_WIMAX = KeyWwan,
|
||||
KeyRfkill = 247, /* Key that controls all radios */
|
||||
KeyMicmute = 248, /* Mute / unmute the microphone */
|
||||
KeyCount = 249,
|
||||
}
|
||||
|
||||
impl TryFrom<Linux> for Windows {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Linux) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Linux::KeyReserved => Err(()),
|
||||
Linux::KeyEsc => Ok(Self::KeyEsc),
|
||||
Linux::Key1 => Ok(Self::Key1),
|
||||
Linux::Key2 => Ok(Self::Key2),
|
||||
Linux::Key3 => Ok(Self::Key3),
|
||||
Linux::Key4 => Ok(Self::Key4),
|
||||
Linux::Key5 => Ok(Self::Key5),
|
||||
Linux::Key6 => Ok(Self::Key6),
|
||||
Linux::Key7 => Ok(Self::Key7),
|
||||
Linux::Key8 => Ok(Self::Key8),
|
||||
Linux::Key9 => Ok(Self::Key9),
|
||||
Linux::Key0 => Ok(Self::Key0),
|
||||
Linux::KeyMinus => Ok(Self::KeyMinus),
|
||||
Linux::KeyEqual => Ok(Self::KeyEqual),
|
||||
Linux::KeyBackspace => Ok(Self::KeyDelete),
|
||||
Linux::KeyTab => Ok(Self::KeyTab),
|
||||
Linux::KeyQ => Ok(Self::KeyQ),
|
||||
Linux::KeyW => Ok(Self::KeyW),
|
||||
Linux::KeyE => Ok(Self::KeyE),
|
||||
Linux::KeyR => Ok(Self::KeyR),
|
||||
Linux::KeyT => Ok(Self::KeyT),
|
||||
Linux::KeyY => Ok(Self::KeyY),
|
||||
Linux::KeyU => Ok(Self::KeyU),
|
||||
Linux::KeyI => Ok(Self::KeyI),
|
||||
Linux::KeyO => Ok(Self::KeyO),
|
||||
Linux::KeyP => Ok(Self::KeyP),
|
||||
Linux::KeyLeftbrace => Ok(Self::KeyLeftBrace),
|
||||
Linux::KeyRightbrace => Ok(Self::KeyRightBrace),
|
||||
Linux::KeyEnter => Ok(Self::KeyEnter),
|
||||
Linux::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
|
||||
Linux::KeyA => Ok(Self::KeyA),
|
||||
Linux::KeyS => Ok(Self::KeyS),
|
||||
Linux::KeyD => Ok(Self::KeyD),
|
||||
Linux::KeyF => Ok(Self::KeyF),
|
||||
Linux::KeyG => Ok(Self::KeyG),
|
||||
Linux::KeyH => Ok(Self::KeyH),
|
||||
Linux::KeyJ => Ok(Self::KeyJ),
|
||||
Linux::KeyK => Ok(Self::KeyK),
|
||||
Linux::KeyL => Ok(Self::KeyL),
|
||||
Linux::KeySemicolon => Ok(Self::KeySemiColon),
|
||||
Linux::KeyApostrophe => Ok(Self::KeyApostrophe),
|
||||
Linux::KeyGrave => Ok(Self::KeyGrave),
|
||||
Linux::KeyLeftShift => Ok(Self::KeyLeftShift),
|
||||
Linux::KeyBackslash => Ok(Self::KeyBackslash),
|
||||
Linux::KeyZ => Ok(Self::KeyZ),
|
||||
Linux::KeyX => Ok(Self::KeyX),
|
||||
Linux::KeyC => Ok(Self::KeyC),
|
||||
Linux::KeyV => Ok(Self::KeyV),
|
||||
Linux::KeyB => Ok(Self::KeyB),
|
||||
Linux::KeyN => Ok(Self::KeyN),
|
||||
Linux::KeyM => Ok(Self::KeyM),
|
||||
Linux::KeyComma => Ok(Self::KeyComma),
|
||||
Linux::KeyDot => Ok(Self::KeyDot),
|
||||
Linux::KeySlash => Ok(Self::KeySlash),
|
||||
Linux::KeyRightShift => Ok(Self::KeyRightShift),
|
||||
Linux::KeyKpAsterisk => Ok(Self::KeypadStar),
|
||||
Linux::KeyLeftAlt => Ok(Self::KeyLeftAlt),
|
||||
Linux::KeySpace => Ok(Self::KeySpace),
|
||||
Linux::KeyCapsLock => Ok(Self::KeyCapsLock),
|
||||
Linux::KeyF1 => Ok(Self::KeyF1),
|
||||
Linux::KeyF2 => Ok(Self::KeyF2),
|
||||
Linux::KeyF3 => Ok(Self::KeyF3),
|
||||
Linux::KeyF4 => Ok(Self::KeyF4),
|
||||
Linux::KeyF5 => Ok(Self::KeyF5),
|
||||
Linux::KeyF6 => Ok(Self::KeyF6),
|
||||
Linux::KeyF7 => Ok(Self::KeyF7),
|
||||
Linux::KeyF8 => Ok(Self::KeyF8),
|
||||
Linux::KeyF9 => Ok(Self::KeyF9),
|
||||
Linux::KeyF10 => Ok(Self::KeyF10),
|
||||
Linux::KeyNumlock => Ok(Self::KeypadNumLock),
|
||||
Linux::KeyScrollLock => Ok(Self::KeyScrollLock),
|
||||
Linux::KeyKp7 => Ok(Self::Keypad7Home),
|
||||
Linux::KeyKp8 => Ok(Self::Keypad8UpArrow),
|
||||
Linux::KeyKp9 => Ok(Self::Keypad9PageUp),
|
||||
Linux::KeyKpMinus => Ok(Self::KeypadDash),
|
||||
Linux::KeyKp4 => Ok(Self::Keypad4LeftArrow),
|
||||
Linux::KeyKp5 => Ok(Self::Keypad5),
|
||||
Linux::KeyKp6 => Ok(Self::Keypad6RightArrow),
|
||||
Linux::KeyKpplus => Ok(Self::KeypadPlus),
|
||||
Linux::KeyKp1 => Ok(Self::Keypad1End),
|
||||
Linux::KeyKp2 => Ok(Self::Keypad2DownArrow),
|
||||
Linux::KeyKp3 => Ok(Self::Keypad3PageDn),
|
||||
Linux::KeyKp0 => Ok(Self::Keypad0Insert),
|
||||
Linux::KeyKpDot => Ok(Self::KeypadDot),
|
||||
Linux::KeyZenkakuhankaku => Ok(Self::KeyF24), // KeyLANG5
|
||||
Linux::Key102nd => Ok(Self::KeyNonUSSlashBar), // TODO unsure
|
||||
Linux::KeyF11 => Ok(Self::KeyF11),
|
||||
Linux::KeyF12 => Ok(Self::KeyF12),
|
||||
Linux::KeyRo => Ok(Self::KeyInternational1),
|
||||
Linux::KeyKatakana => Ok(Self::KeyLANG3),
|
||||
Linux::KeyHiragana => Ok(Self::KeyLANG4),
|
||||
Linux::KeyHenkan => Ok(Self::KeyInternational4),
|
||||
Linux::KeyKatakanahiragana => Ok(Self::KeyInternational2),
|
||||
Linux::KeyMuhenkan => Ok(Self::KeyInternational5),
|
||||
Linux::KeyKpJpComma => Ok(Self::KeypadComma),
|
||||
Linux::KeyKpEnter => Ok(Self::KeypadEnter),
|
||||
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||
Linux::KeyKpslash => Ok(Self::KeypadSlash),
|
||||
Linux::KeySysrq => Ok(Self::KeyPrintScreen), // TODO Windows does not have Sysrq, right?
|
||||
Linux::KeyRightalt => Ok(Self::KeyRightAlt),
|
||||
Linux::KeyLinefeed => Ok(Self::KeyEnter), // TODO unsure
|
||||
Linux::KeyHome => Ok(Self::KeyHome),
|
||||
Linux::KeyUp => Ok(Self::KeyUp),
|
||||
Linux::KeyPageup => Ok(Self::KeyPageUp),
|
||||
Linux::KeyLeft => Ok(Self::KeyLeft),
|
||||
Linux::KeyRight => Ok(Self::KeyRight),
|
||||
Linux::KeyEnd => Ok(Self::KeyEnd),
|
||||
Linux::KeyDown => Ok(Self::KeyDown),
|
||||
Linux::KeyPagedown => Ok(Self::KeyPageDown),
|
||||
Linux::KeyInsert => Ok(Self::KeyInsert),
|
||||
Linux::KeyDelete => Ok(Self::KeyDeleteForward),
|
||||
Linux::KeyMacro => Err(()), // TODO
|
||||
Linux::KeyMute => Ok(Self::KeyMute),
|
||||
Linux::KeyVolumeDown => Ok(Self::KeyVolumeDown),
|
||||
Linux::KeyVolumeUp => Ok(Self::KeyVolumeUp),
|
||||
Linux::KeyPower => Ok(Self::Shutdown),
|
||||
Linux::KeyKpequal => Ok(Self::KeypadEquals),
|
||||
Linux::KeyKpplusminus => Ok(Self::KeypadPlus),
|
||||
Linux::KeyPause => Ok(Self::KeyPause),
|
||||
Linux::KeyScale => Err(()), // TODO
|
||||
Linux::KeyKpcomma => Ok(Self::KeypadComma),
|
||||
Linux::KeyHanguel => Ok(Self::KeyLANG1), // FIXME should be 00F2?
|
||||
Linux::KeyHanja => Ok(Self::KeyLANG2), // FIXME should be 00F1?
|
||||
Linux::KeyYen => Ok(Self::KeyInternational3),
|
||||
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
|
||||
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
|
||||
Linux::KeyCompose => Ok(Self::KeyApplication),
|
||||
Linux::KeyStop => Ok(Self::ACStop),
|
||||
Linux::KeyAgain => Err(()),
|
||||
Linux::KeyProps => Err(()),
|
||||
Linux::KeyUndo => Err(()),
|
||||
Linux::KeyFront => Err(()),
|
||||
Linux::KeyCopy => Err(()),
|
||||
Linux::KeyOpen => Err(()),
|
||||
Linux::KeyPaste => Err(()),
|
||||
Linux::KeyFind => Ok(Self::ACSearch),
|
||||
Linux::KeyCut => Err(()),
|
||||
Linux::KeyHelp => Ok(Self::KeyF1), // AL Integrated Help Center?
|
||||
Linux::KeyMenu => Ok(Self::KeyApplication),
|
||||
Linux::KeyCalc => Ok(Self::ALCalculator),
|
||||
Linux::KeySetup => Err(()),
|
||||
Linux::KeySleep => Ok(Self::SystemSleep),
|
||||
Linux::KeyWakeup => Ok(Self::SystemWakeUp),
|
||||
Linux::KeyFile => Ok(Self::ALLocalMachineBrowser),
|
||||
Linux::KeySendfile => Err(()),
|
||||
Linux::KeyDeletefile => Err(()),
|
||||
Linux::KeyXfer => Err(()),
|
||||
Linux::KeyProg1 => Err(()),
|
||||
Linux::KeyProg2 => Err(()),
|
||||
Linux::KeyWww => Ok(Self::ACSearch), // TODO unsure
|
||||
Linux::KeyMsdos => Err(()),
|
||||
Linux::KeyCoffee => Err(()),
|
||||
Linux::KeyRotateDisplay => Err(()),
|
||||
Linux::KeyCyclewindows => Err(()),
|
||||
Linux::KeyMail => Ok(Self::ALEmailReader),
|
||||
Linux::KeyBookmarks => Ok(Self::ACBookmarks),
|
||||
Linux::KeyComputer => Ok(Self::ACHome),
|
||||
Linux::KeyBack => Ok(Self::ACBack),
|
||||
Linux::KeyForward => Ok(Self::ACForward),
|
||||
Linux::KeyClosecd => Err(()),
|
||||
Linux::KeyEjectcd => Err(()),
|
||||
Linux::KeyEjectclosecd => Err(()),
|
||||
Linux::KeyNextsong => Ok(Self::KeyScanNextTrack),
|
||||
Linux::KeyPlaypause => Ok(Self::KeyPlayPause),
|
||||
Linux::KeyPrevioussong => Ok(Self::KeyScanPreviousTrack),
|
||||
Linux::KeyStopcd => Ok(Self::KeyStop),
|
||||
Linux::KeyRecord => Err(()),
|
||||
Linux::KeyRewind => Err(()),
|
||||
Linux::KeyPhone => Err(()),
|
||||
Linux::KeyIso => Err(()),
|
||||
Linux::KeyConfig => Err(()),
|
||||
Linux::KeyHomepage => Ok(Self::ACHome),
|
||||
Linux::KeyRefresh => Ok(Self::ACRefresh),
|
||||
Linux::KeyExit => Err(()),
|
||||
Linux::KeyMove => Err(()),
|
||||
Linux::KeyEdit => Err(()),
|
||||
Linux::KeyScrollup => Err(()),
|
||||
Linux::KeyScrolldown => Err(()),
|
||||
Linux::KeyKpleftparen => Err(()),
|
||||
Linux::KeyKprightparen => Err(()),
|
||||
Linux::KeyNew => Err(()),
|
||||
Linux::KeyRedo => Err(()),
|
||||
Linux::KeyF13 => Ok(Self::KeyF13),
|
||||
Linux::KeyF14 => Ok(Self::KeyF14),
|
||||
Linux::KeyF15 => Ok(Self::KeyF15),
|
||||
Linux::KeyF16 => Ok(Self::KeyF16),
|
||||
Linux::KeyF17 => Ok(Self::KeyF17),
|
||||
Linux::KeyF18 => Ok(Self::KeyF18),
|
||||
Linux::KeyF19 => Ok(Self::KeyF19),
|
||||
Linux::KeyF20 => Ok(Self::KeyF20),
|
||||
Linux::KeyF21 => Ok(Self::KeyF21),
|
||||
Linux::KeyF22 => Ok(Self::KeyF22),
|
||||
Linux::KeyF23 => Ok(Self::KeyF23),
|
||||
Linux::KeyF24 => Ok(Self::KeyF24),
|
||||
Linux::KeyPlaycd => Err(()),
|
||||
Linux::KeyPausecd => Err(()),
|
||||
Linux::KeyProg3 => Err(()),
|
||||
Linux::KeyProg4 => Err(()),
|
||||
Linux::KeyAllApplications => Err(()),
|
||||
Linux::KeySuspend => Err(()),
|
||||
Linux::KeyClose => Err(()),
|
||||
Linux::KeyPlay => Err(()),
|
||||
Linux::KeyFastforward => Err(()),
|
||||
Linux::KeyBassboost => Err(()),
|
||||
Linux::KeyPrint => Err(()),
|
||||
Linux::KeyHp => Err(()),
|
||||
Linux::KeyCamera => Err(()),
|
||||
Linux::KeySound => Err(()),
|
||||
Linux::KeyQuestion => Err(()),
|
||||
Linux::KeyEmail => Err(()),
|
||||
Linux::KeyChat => Err(()),
|
||||
Linux::KeySearch => Err(()),
|
||||
Linux::KeyConnect => Err(()),
|
||||
Linux::KeyFinance => Err(()),
|
||||
Linux::KeySport => Err(()),
|
||||
Linux::KeyShop => Err(()),
|
||||
Linux::KeyAlterase => Err(()),
|
||||
Linux::KeyCancel => Err(()),
|
||||
Linux::KeyBrightnessdown => Err(()),
|
||||
Linux::KeyBrightnessup => Err(()),
|
||||
Linux::KeyMedia => Err(()),
|
||||
Linux::KeySwitchvideomode => Err(()),
|
||||
Linux::KeyKbdillumtoggle => Err(()),
|
||||
Linux::KeyKbdillumdown => Err(()),
|
||||
Linux::KeyKbdillumup => Err(()),
|
||||
Linux::KeySend => Err(()),
|
||||
Linux::KeyReply => Err(()),
|
||||
Linux::KeyForwardmail => Err(()),
|
||||
Linux::KeySave => Err(()),
|
||||
Linux::KeyDocuments => Err(()),
|
||||
Linux::KeyBattery => Err(()),
|
||||
Linux::KeyBluetooth => Err(()),
|
||||
Linux::KeyWlan => Err(()),
|
||||
Linux::KeyUwb => Err(()),
|
||||
Linux::KeyUnknown => Err(()),
|
||||
Linux::KeyVideoNext => Err(()),
|
||||
Linux::KeyVideoPrev => Err(()),
|
||||
Linux::KeyBrightnessCycle => Err(()),
|
||||
Linux::KeyBrightnessAuto => Err(()),
|
||||
Linux::KeyDisplayOff => Err(()),
|
||||
Linux::KeyWwan => Err(()),
|
||||
Linux::KeyRfkill => Err(()),
|
||||
Linux::KeyMicmute => Err(()),
|
||||
Linux::KeyCount => Err(()),
|
||||
Linux::Invalid => Err(()),
|
||||
Linux::Invalid1 => Err(()),
|
||||
Linux::Invalid2 => Err(()),
|
||||
Linux::Invalid3 => Err(()),
|
||||
Linux::Invalid4 => Err(()),
|
||||
Linux::Invalid5 => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<Windows> for Linux {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: Windows) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Windows::Shutdown => Ok(Self::KeyPower),
|
||||
Windows::SystemSleep => Ok(Self::KeySleep),
|
||||
Windows::SystemWakeUp => Ok(Self::KeyWakeup),
|
||||
Windows::ErrorRollOver => Ok(Self::KeyRo),
|
||||
Windows::KeyA => Ok(Self::KeyA),
|
||||
Windows::KeyB => Ok(Self::KeyB),
|
||||
Windows::KeyC => Ok(Self::KeyC),
|
||||
Windows::KeyD => Ok(Self::KeyD),
|
||||
Windows::KeyE => Ok(Self::KeyE),
|
||||
Windows::KeyF => Ok(Self::KeyF),
|
||||
Windows::KeyG => Ok(Self::KeyG),
|
||||
Windows::KeyH => Ok(Self::KeyH),
|
||||
Windows::KeyI => Ok(Self::KeyI),
|
||||
Windows::KeyJ => Ok(Self::KeyJ),
|
||||
Windows::KeyK => Ok(Self::KeyK),
|
||||
Windows::KeyL => Ok(Self::KeyL),
|
||||
Windows::KeyM => Ok(Self::KeyM),
|
||||
Windows::KeyN => Ok(Self::KeyN),
|
||||
Windows::KeyO => Ok(Self::KeyO),
|
||||
Windows::KeyP => Ok(Self::KeyP),
|
||||
Windows::KeyQ => Ok(Self::KeyQ),
|
||||
Windows::KeyR => Ok(Self::KeyR),
|
||||
Windows::KeyS => Ok(Self::KeyS),
|
||||
Windows::KeyT => Ok(Self::KeyT),
|
||||
Windows::KeyU => Ok(Self::KeyU),
|
||||
Windows::KeyV => Ok(Self::KeyV),
|
||||
Windows::KeyW => Ok(Self::KeyW),
|
||||
Windows::KeyX => Ok(Self::KeyX),
|
||||
Windows::KeyY => Ok(Self::KeyY),
|
||||
Windows::KeyZ => Ok(Self::KeyZ),
|
||||
Windows::Key1 => Ok(Self::Key1),
|
||||
Windows::Key2 => Ok(Self::Key2),
|
||||
Windows::Key3 => Ok(Self::Key3),
|
||||
Windows::Key4 => Ok(Self::Key4),
|
||||
Windows::Key5 => Ok(Self::Key5),
|
||||
Windows::Key6 => Ok(Self::Key6),
|
||||
Windows::Key7 => Ok(Self::Key7),
|
||||
Windows::Key8 => Ok(Self::Key8),
|
||||
Windows::Key9 => Ok(Self::Key9),
|
||||
Windows::Key0 => Ok(Self::Key0),
|
||||
Windows::KeyEnter => Ok(Self::KeyEnter),
|
||||
Windows::KeyEsc => Ok(Self::KeyEsc),
|
||||
Windows::KeyDelete => Ok(Self::KeyBackspace),
|
||||
Windows::KeyTab => Ok(Self::KeyTab),
|
||||
Windows::KeySpace => Ok(Self::KeySpace),
|
||||
Windows::KeyMinus => Ok(Self::KeyMinus),
|
||||
Windows::KeyEqual => Ok(Self::KeyEqual),
|
||||
Windows::KeyLeftBrace => Ok(Self::KeyLeftbrace),
|
||||
Windows::KeyRightBrace => Ok(Self::KeyRightbrace),
|
||||
Windows::KeyBackslash => Ok(Self::KeyBackslash),
|
||||
Windows::KeySemiColon => Ok(Self::KeySemicolon),
|
||||
Windows::KeyApostrophe => Ok(Self::KeyApostrophe),
|
||||
Windows::KeyGrave => Ok(Self::KeyGrave),
|
||||
Windows::KeyComma => Ok(Self::KeyComma),
|
||||
Windows::KeyDot => Ok(Self::KeyDot),
|
||||
Windows::KeySlash => Ok(Self::KeySlash),
|
||||
Windows::KeyCapsLock => Ok(Self::KeyCapsLock),
|
||||
Windows::KeyF1 => Ok(Self::KeyF1),
|
||||
Windows::KeyF2 => Ok(Self::KeyF2),
|
||||
Windows::KeyF3 => Ok(Self::KeyF3),
|
||||
Windows::KeyF4 => Ok(Self::KeyF4),
|
||||
Windows::KeyF5 => Ok(Self::KeyF5),
|
||||
Windows::KeyF6 => Ok(Self::KeyF6),
|
||||
Windows::KeyF7 => Ok(Self::KeyF7),
|
||||
Windows::KeyF8 => Ok(Self::KeyF8),
|
||||
Windows::KeyF9 => Ok(Self::KeyF9),
|
||||
Windows::KeyF10 => Ok(Self::KeyF10),
|
||||
Windows::KeyF11 => Ok(Self::KeyF11),
|
||||
Windows::KeyF12 => Ok(Self::KeyF12),
|
||||
Windows::KeyPrintScreen => Ok(Self::KeySysrq),
|
||||
Windows::KeyScrollLock => Ok(Self::KeyScrollLock),
|
||||
Windows::KeyPause => Ok(Self::KeyPause),
|
||||
Windows::KeyInsert => Ok(Self::KeyInsert),
|
||||
Windows::KeyHome => Ok(Self::KeyHome),
|
||||
Windows::KeyPageUp => Ok(Self::KeyPageup),
|
||||
Windows::KeyDeleteForward => Ok(Self::KeyDelete),
|
||||
Windows::KeyEnd => Ok(Self::KeyEnd),
|
||||
Windows::KeyPageDown => Ok(Self::KeyPagedown),
|
||||
Windows::KeyRight => Ok(Self::KeyRight),
|
||||
Windows::KeyLeft => Ok(Self::KeyLeft),
|
||||
Windows::KeyDown => Ok(Self::KeyDown),
|
||||
Windows::KeyUp => Ok(Self::KeyUp),
|
||||
Windows::KeypadNumLock => Ok(Self::KeyNumlock),
|
||||
Windows::KeypadSlash => Ok(Self::KeyKpslash),
|
||||
Windows::KeypadStar => Ok(Self::KeyKpAsterisk),
|
||||
Windows::KeypadDash => Ok(Self::KeyKpMinus),
|
||||
Windows::KeypadPlus => Ok(Self::KeyKpplus),
|
||||
Windows::KeypadEnter => Ok(Self::KeyKpEnter),
|
||||
Windows::Keypad1End => Ok(Self::KeyKp1),
|
||||
Windows::Keypad2DownArrow => Ok(Self::KeyKp2),
|
||||
Windows::Keypad3PageDn => Ok(Self::KeyKp3),
|
||||
Windows::Keypad4LeftArrow => Ok(Self::KeyKp4),
|
||||
Windows::Keypad5 => Ok(Self::KeyKp5),
|
||||
Windows::Keypad6RightArrow => Ok(Self::KeyKp6),
|
||||
Windows::Keypad7Home => Ok(Self::KeyKp7),
|
||||
Windows::Keypad8UpArrow => Ok(Self::KeyKp8),
|
||||
Windows::Keypad9PageUp => Ok(Self::KeyKp9),
|
||||
Windows::Keypad0Insert => Ok(Self::KeyKp0),
|
||||
Windows::KeypadDot => Ok(Self::KeyKpDot),
|
||||
Windows::KeyNonUSSlashBar => Ok(Self::Key102nd),
|
||||
Windows::KeyApplication => Ok(Self::KeyMenu),
|
||||
Windows::KeypadEquals => Ok(Self::KeyKpequal),
|
||||
Windows::KeyF13 => Ok(Self::KeyF13),
|
||||
Windows::KeyF14 => Ok(Self::KeyF14),
|
||||
Windows::KeyF15 => Ok(Self::KeyF15),
|
||||
Windows::KeyF16 => Ok(Self::KeyF16),
|
||||
Windows::KeyF17 => Ok(Self::KeyF17),
|
||||
Windows::KeyF18 => Ok(Self::KeyF18),
|
||||
Windows::KeyF19 => Ok(Self::KeyF19),
|
||||
Windows::KeyF20 => Ok(Self::KeyF20),
|
||||
Windows::KeyF21 => Ok(Self::KeyF21),
|
||||
Windows::KeyF22 => Ok(Self::KeyF22),
|
||||
Windows::KeyF23 => Ok(Self::KeyF23),
|
||||
Windows::KeyF24 => Ok(Self::KeyF24),
|
||||
Windows::KeypadComma => Ok(Self::KeyKpcomma),
|
||||
Windows::KeyInternational1 => Ok(Self::KeyRo),
|
||||
Windows::KeyInternational2 => Ok(Self::KeyKatakanahiragana),
|
||||
Windows::KeyInternational3 => Ok(Self::KeyYen),
|
||||
Windows::KeyInternational4 => Ok(Self::KeyHenkan),
|
||||
Windows::KeyInternational5 => Ok(Self::KeyMuhenkan),
|
||||
Windows::KeyLANG1 => Ok(Self::KeyHanguel),
|
||||
Windows::KeyLANG2 => Ok(Self::KeyHanja),
|
||||
Windows::KeyLANG3 => Ok(Self::KeyKatakana),
|
||||
Windows::KeyLANG4 => Ok(Self::KeyHiragana),
|
||||
Windows::KeyLeftCtrl => Ok(Self::KeyLeftCtrl),
|
||||
Windows::KeyLeftShift => Ok(Self::KeyLeftShift),
|
||||
Windows::KeyLeftAlt => Ok(Self::KeyLeftAlt),
|
||||
Windows::KeyLeftGUI => Ok(Self::KeyLeftMeta),
|
||||
Windows::KeyRightCtrl => Ok(Self::KeyRightCtrl),
|
||||
Windows::KeyRightShift => Ok(Self::KeyRightShift),
|
||||
Windows::KeyFakeRightShift => Ok(Self::KeyRightShift),
|
||||
Windows::KeyRightAlt => Ok(Self::KeyRightalt),
|
||||
Windows::KeyRightGUI => Ok(Self::KeyRightmeta),
|
||||
Windows::KeyScanNextTrack => Ok(Self::KeyNextsong),
|
||||
Windows::KeyScanPreviousTrack => Ok(Self::KeyPrevioussong),
|
||||
Windows::KeyStop => Ok(Self::KeyStopcd),
|
||||
Windows::KeyPlayPause => Ok(Self::KeyPlaypause),
|
||||
Windows::KeyMute => Ok(Self::KeyMute),
|
||||
Windows::KeyVolumeUp => Ok(Self::KeyVolumeUp),
|
||||
Windows::KeyVolumeDown => Ok(Self::KeyVolumeDown),
|
||||
Windows::ALConsumerControlConfiguration => Err(()),
|
||||
Windows::ALEmailReader => Ok(Self::KeyMail),
|
||||
Windows::ALCalculator => Ok(Self::KeyCalc),
|
||||
Windows::ALLocalMachineBrowser => Ok(Self::KeyFile),
|
||||
Windows::ACSearch => Ok(Self::KeyWww),
|
||||
Windows::ACHome => Ok(Self::KeyHomepage),
|
||||
Windows::ACBack => Ok(Self::KeyBack),
|
||||
Windows::ACForward => Ok(Self::KeyForward),
|
||||
Windows::ACStop => Ok(Self::KeyStop),
|
||||
Windows::ACRefresh => Ok(Self::KeyRefresh),
|
||||
Windows::ACBookmarks => Ok(Self::KeyBookmarks),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ pub struct Server {
|
||||
client_manager: Rc<RefCell<ClientManager>>,
|
||||
port: Rc<Cell<u16>>,
|
||||
state: Rc<Cell<State>>,
|
||||
release_bind: Vec<crate::scancode::Linux>,
|
||||
release_bind: Vec<input_event::scancode::Linux>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
|
||||
@@ -4,14 +4,11 @@ use std::{collections::HashSet, net::SocketAddr};
|
||||
|
||||
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
|
||||
|
||||
use crate::{
|
||||
capture::{self, error::CaptureCreationError, CaptureHandle, InputCapture, Position},
|
||||
client::ClientHandle,
|
||||
config::CaptureBackend,
|
||||
event::{Event, KeyboardEvent},
|
||||
scancode,
|
||||
server::State,
|
||||
};
|
||||
use input_capture::{self, error::CaptureCreationError, CaptureHandle, InputCapture, Position};
|
||||
|
||||
use input_event::{scancode, Event, KeyboardEvent};
|
||||
|
||||
use crate::{client::ClientHandle, config::CaptureBackend, server::State};
|
||||
|
||||
use super::Server;
|
||||
|
||||
@@ -35,8 +32,9 @@ pub fn new(
|
||||
release_bind: Vec<scancode::Linux>,
|
||||
) -> Result<(JoinHandle<Result<()>>, Sender<CaptureEvent>), CaptureCreationError> {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||
let backend = backend.map(|b| b.into());
|
||||
let task = tokio::task::spawn_local(async move {
|
||||
let mut capture = capture::create(backend).await?;
|
||||
let mut capture = input_capture::create(backend).await?;
|
||||
let mut pressed_keys = HashSet::new();
|
||||
loop {
|
||||
tokio::select! {
|
||||
|
||||
@@ -6,14 +6,9 @@ use tokio::{
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
client::ClientHandle,
|
||||
config::EmulationBackend,
|
||||
emulate::{self, error::EmulationCreationError, EmulationHandle, InputEmulation},
|
||||
event::{Event, KeyboardEvent},
|
||||
scancode,
|
||||
server::State,
|
||||
};
|
||||
use crate::{client::ClientHandle, config::EmulationBackend, server::State};
|
||||
use input_emulation::{self, error::EmulationCreationError, EmulationHandle, InputEmulation};
|
||||
use input_event::{Event, KeyboardEvent};
|
||||
|
||||
use super::{CaptureEvent, Server};
|
||||
|
||||
@@ -39,7 +34,8 @@ pub fn new(
|
||||
) -> Result<(JoinHandle<Result<()>>, Sender<EmulationEvent>), EmulationCreationError> {
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
|
||||
let emulate_task = tokio::task::spawn_local(async move {
|
||||
let mut emulate = emulate::create(backend).await?;
|
||||
let backend = backend.map(|b| b.into());
|
||||
let mut emulate = input_emulation::create(backend).await?;
|
||||
let mut last_ignored = None;
|
||||
|
||||
loop {
|
||||
@@ -225,7 +221,7 @@ async fn release_keys(
|
||||
state: 0,
|
||||
});
|
||||
emulate.consume(event, client).await;
|
||||
if let Ok(key) = scancode::Linux::try_from(key) {
|
||||
if let Ok(key) = input_event::scancode::Linux::try_from(key) {
|
||||
log::warn!("releasing stuck key: {key:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use tokio::{
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{event::Event, frontend::FrontendEvent};
|
||||
use crate::frontend::FrontendEvent;
|
||||
use input_event::Event;
|
||||
|
||||
use super::Server;
|
||||
|
||||
|
||||
@@ -5,7 +5,9 @@ use tokio::{
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::{client::ClientHandle, event::Event};
|
||||
use input_event::Event;
|
||||
|
||||
use crate::client::ClientHandle;
|
||||
|
||||
use super::{capture_task::CaptureEvent, emulation_task::EmulationEvent, Server, State};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user