Compare commits

...

11 Commits

Author SHA1 Message Date
Ferdinand Schober
ce10eb6c3e fix macos, the 2nd 2024-06-28 23:54:34 +02:00
Ferdinand Schober
89c5067e0d fix macos build 2024-06-28 23:48:56 +02:00
Ferdinand Schober
c2fde88900 add option to config 2024-06-28 23:44:55 +02:00
Ferdinand Schober
1db43c4751 adapt windows + macos error types 2024-06-28 23:31:42 +02:00
Ferdinand Schober
16df8e9693 fix Display 2024-06-28 23:20:38 +02:00
Ferdinand Schober
769597af58 adapt windows 2024-06-28 23:16:34 +02:00
Ferdinand Schober
b874898c11 configurable capture backend 2024-06-28 22:56:49 +02:00
Ferdinand Schober
232c048c19 fix transmuting to pointer types UB (#147)
closes #134
2024-06-25 14:13:05 +02:00
Orhun Parmaksız
1c37579ae5 Update Arch Linux instructions (#145) 2024-06-19 20:28:30 +02:00
Ferdinand Schober
460bacade5 fix sizeof usize assumed to be 8 (#143)
closes #141
2024-06-09 00:49:00 +02:00
虢豳
5fd3b719d6 Extract package name and version from Cargo.toml (#136)
* chore: nix flake update

* feat: Extract package name and version from Cargo.toml
2024-05-22 08:06:44 +02:00
20 changed files with 313 additions and 118 deletions

10
Cargo.lock generated
View File

@@ -1292,6 +1292,7 @@ dependencies = [
"async-trait", "async-trait",
"clap", "clap",
"core-graphics", "core-graphics",
"endi",
"env_logger", "env_logger",
"futures", "futures",
"futures-core", "futures-core",
@@ -1311,6 +1312,7 @@ dependencies = [
"serde_json", "serde_json",
"slab", "slab",
"tempfile", "tempfile",
"thiserror",
"tokio", "tokio",
"toml", "toml",
"wayland-client", "wayland-client",
@@ -2010,18 +2012,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.58" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.58" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -35,6 +35,8 @@ once_cell = "1.19.0"
num_enum = "0.7.2" num_enum = "0.7.2"
hostname = "0.4.0" hostname = "0.4.0"
slab = "0.4.9" slab = "0.4.9"
endi = "1.1.0"
thiserror = "1.0.61"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2.148" libc = "0.2.148"

View File

@@ -69,7 +69,15 @@ Precompiled release binaries for Windows, MacOS and Linux are available in the [
For Windows, the depenedencies are included in the .zip file, for other operating systems see [Installing Dependencies](#installing-dependencies). For Windows, the depenedencies are included in the .zip file, for other operating systems see [Installing Dependencies](#installing-dependencies).
### Arch Linux ### Arch Linux
Lan Mouse is available on the AUR:
Lan Mouse can be installed from the [official repositories](https://archlinux.org/packages/extra/x86_64/lan-mouse/):
```sh
pacman -S lan-mouse
```
It is also available on the AUR:
```sh ```sh
# git version (includes latest changes) # git version (includes latest changes)
paru -S lan-mouse-git paru -S lan-mouse-git

View File

@@ -1,5 +1,7 @@
# example configuration # example configuration
# capture_backend = "LayerShell"
# release bind # release bind
release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ] release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ]

12
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1710806803, "lastModified": 1716293225,
"narHash": "sha256-qrxvLS888pNJFwJdK+hf1wpRCSQcqA6W5+Ox202NDa0=", "narHash": "sha256-pU9ViBVE3XYb70xZx+jK6SEVphvt7xMTbm6yDIF4xPs=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b06025f1533a1e07b6db3e75151caa155d1c7eb3", "rev": "3eaeaeb6b1e08a016380c279f8846e0bd8808916",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -48,11 +48,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1710987136, "lastModified": 1716257780,
"narHash": "sha256-Q8GRdlAIKZ8tJUXrbcRO1pA33AdoPfTUirsSnmGQnOU=", "narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "97596b54ac34ad8184ca1eef44b1ec2e5c2b5f9e", "rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -2,10 +2,14 @@
rustPlatform, rustPlatform,
lib, lib,
pkgs, pkgs,
}: }: let
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
pname = cargoToml.package.name;
version = cargoToml.package.version;
in
rustPlatform.buildRustPackage { rustPlatform.buildRustPackage {
pname = "lan-mouse"; pname = pname;
version = "0.7.0"; version = version;
nativeBuildInputs = with pkgs; [ nativeBuildInputs = with pkgs; [
pkg-config pkg-config
@@ -23,7 +27,7 @@ rustPlatform.buildRustPackage {
]; ];
src = builtins.path { src = builtins.path {
name = "lan-mouse"; name = pname;
path = lib.cleanSource ../.; path = lib.cleanSource ../.;
}; };
@@ -38,7 +42,7 @@ rustPlatform.buildRustPackage {
Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch. Lan Mouse is a mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple pcs with a single set of mouse and keyboard. This is also known as a Software KVM switch.
The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details). The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg have partial support as well (see below for more details).
''; '';
mainProgram = "lan-mouse"; mainProgram = pname;
platforms = platforms.all; platforms = platforms.all;
}; };
} }

View File

@@ -4,9 +4,14 @@ use futures_core::Stream;
use crate::{ use crate::{
client::{ClientEvent, ClientHandle}, client::{ClientEvent, ClientHandle},
config::CaptureBackend,
event::Event, event::Event,
}; };
use self::error::CaptureCreationError;
pub mod error;
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
pub mod libei; pub mod libei;
@@ -25,24 +30,42 @@ pub mod x11;
/// fallback input capture (does not produce events) /// fallback input capture (does not produce events)
pub mod dummy; pub mod dummy;
pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>> { #[allow(unreachable_code)]
pub async fn create(
backend: Option<CaptureBackend>,
) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, CaptureCreationError> {
if let Some(backend) = backend {
return 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())),
};
}
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
match macos::MacOSInputCapture::new() { match macos::MacOSInputCapture::new() {
Ok(p) => return Box::new(p), Ok(p) => return Ok(Box::new(p)),
Err(e) => log::info!("macos input capture not available: {e}"), Err(e) => log::info!("macos input capture not available: {e}"),
} }
#[cfg(windows)] #[cfg(windows)]
match windows::WindowsInputCapture::new() { return Ok(Box::new(windows::WindowsInputCapture::new()));
Ok(p) => return Box::new(p),
Err(e) => log::info!("windows input capture not available: {e}"),
}
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
match libei::LibeiInputCapture::new().await { match libei::LibeiInputCapture::new().await {
Ok(p) => { Ok(p) => {
log::info!("using libei input capture"); log::info!("using libei input capture");
return Box::new(p); return Ok(Box::new(p));
} }
Err(e) => log::info!("libei input capture not available: {e}"), Err(e) => log::info!("libei input capture not available: {e}"),
} }
@@ -51,7 +74,7 @@ pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, E
match wayland::WaylandInputCapture::new() { match wayland::WaylandInputCapture::new() {
Ok(p) => { Ok(p) => {
log::info!("using layer-shell input capture"); log::info!("using layer-shell input capture");
return Box::new(p); return Ok(Box::new(p));
} }
Err(e) => log::info!("layer_shell input capture not available: {e}"), Err(e) => log::info!("layer_shell input capture not available: {e}"),
} }
@@ -60,13 +83,13 @@ pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, E
match x11::X11InputCapture::new() { match x11::X11InputCapture::new() {
Ok(p) => { Ok(p) => {
log::info!("using x11 input capture"); log::info!("using x11 input capture");
return Box::new(p); return Ok(Box::new(p));
} }
Err(e) => log::info!("x11 input capture not available: {e}"), Err(e) => log::info!("x11 input capture not available: {e}"),
} }
log::error!("falling back to dummy input capture"); log::error!("falling back to dummy input capture");
Box::new(dummy::DummyInputCapture::new()) Ok(Box::new(dummy::DummyInputCapture::new()))
} }
pub trait InputCapture: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin { pub trait InputCapture: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {

140
src/capture/error.rs Normal file
View File

@@ -0,0 +1,140 @@
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 {
#[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::from(""),
};
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 :(")
}
}

View File

@@ -36,6 +36,8 @@ use crate::{
event::{Event, KeyboardEvent, PointerEvent}, event::{Event, KeyboardEvent, PointerEvent},
}; };
use super::error::LibeiCaptureCreationError;
#[derive(Debug)] #[derive(Debug)]
enum ProducerEvent { enum ProducerEvent {
Release, Release,
@@ -131,7 +133,7 @@ impl<'a> Drop for LibeiInputCapture<'a> {
async fn create_session<'a>( async fn create_session<'a>(
input_capture: &'a InputCapture<'a>, input_capture: &'a InputCapture<'a>,
) -> Result<(Session<'a>, BitFlags<Capabilities>)> { ) -> std::result::Result<(Session<'a>, BitFlags<Capabilities>), ashpd::Error> {
log::debug!("creating input capture session"); log::debug!("creating input capture session");
let (session, capabilities) = loop { let (session, capabilities) = loop {
match input_capture match input_capture
@@ -213,7 +215,7 @@ async fn wait_for_active_client(
} }
impl<'a> LibeiInputCapture<'a> { impl<'a> LibeiInputCapture<'a> {
pub async fn new() -> Result<Self> { pub async fn new() -> std::result::Result<Self, LibeiCaptureCreationError> {
let input_capture = Box::pin(InputCapture::new().await?); let input_capture = Box::pin(InputCapture::new().await?);
let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>; 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 mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);

View File

@@ -1,7 +1,7 @@
use crate::capture::error::MacOSInputCaptureCreationError;
use crate::capture::InputCapture; use crate::capture::InputCapture;
use crate::client::{ClientEvent, ClientHandle}; use crate::client::{ClientEvent, ClientHandle};
use crate::event::Event; use crate::event::Event;
use anyhow::{anyhow, Result};
use futures_core::Stream; use futures_core::Stream;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{io, pin::Pin}; use std::{io, pin::Pin};
@@ -9,8 +9,8 @@ use std::{io, pin::Pin};
pub struct MacOSInputCapture; pub struct MacOSInputCapture;
impl MacOSInputCapture { impl MacOSInputCapture {
pub fn new() -> Result<Self> { pub fn new() -> std::result::Result<Self, MacOSInputCaptureCreationError> {
Err(anyhow!("not yet implemented")) Err(MacOSInputCaptureCreationError::NotImplemented)
} }
} }

View File

@@ -1,9 +1,8 @@
use crate::{ use crate::{
capture::InputCapture, capture::{error::WaylandBindError, InputCapture},
client::{ClientEvent, ClientHandle, Position}, client::{ClientEvent, ClientHandle, Position},
}; };
use anyhow::{anyhow, Result};
use futures_core::Stream; use futures_core::Stream;
use memmap::MmapOptions; use memmap::MmapOptions;
use std::{ use std::{
@@ -68,6 +67,8 @@ use tempfile;
use crate::event::{Event, KeyboardEvent, PointerEvent}; use crate::event::{Event, KeyboardEvent, PointerEvent};
use super::error::LayerShellCaptureCreationError;
struct Globals { struct Globals {
compositor: wl_compositor::WlCompositor, compositor: wl_compositor::WlCompositor,
pointer_constraints: ZwpPointerConstraintsV1, pointer_constraints: ZwpPointerConstraintsV1,
@@ -258,64 +259,37 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
} }
impl WaylandInputCapture { impl WaylandInputCapture {
pub fn new() -> Result<Self> { pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
let conn = match Connection::connect_to_env() { let conn = Connection::connect_to_env()?;
Ok(c) => c, let (g, mut queue) = registry_queue_init::<State>(&conn)?;
Err(e) => return Err(anyhow!("could not connect to wayland compositor: {e:?}")),
};
let (g, mut queue) = match registry_queue_init::<State>(&conn) {
Ok(q) => q,
Err(e) => return Err(anyhow!("failed to initialize wl_registry: {e:?}")),
};
let qh = queue.handle(); let qh = queue.handle();
let compositor: wl_compositor::WlCompositor = match g.bind(&qh, 4..=5, ()) { let compositor: wl_compositor::WlCompositor = g
Ok(compositor) => compositor, .bind(&qh, 4..=5, ())
Err(_) => return Err(anyhow!("wl_compositor >= v4 not supported")), .map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?;
}; let xdg_output_manager: ZxdgOutputManagerV1 = g
.bind(&qh, 1..=3, ())
let xdg_output_manager: ZxdgOutputManagerV1 = match g.bind(&qh, 1..=3, ()) { .map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?;
Ok(xdg_output_manager) => xdg_output_manager, let shm: wl_shm::WlShm = g
Err(_) => return Err(anyhow!("xdg_output not supported!")), .bind(&qh, 1..=1, ())
}; .map_err(|e| WaylandBindError::new(e, "wl_shm"))?;
let layer_shell: ZwlrLayerShellV1 = g
let shm: wl_shm::WlShm = match g.bind(&qh, 1..=1, ()) { .bind(&qh, 3..=4, ())
Ok(wl_shm) => wl_shm, .map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?;
Err(_) => return Err(anyhow!("wl_shm v1 not supported")), let seat: wl_seat::WlSeat = g
}; .bind(&qh, 7..=8, ())
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
let layer_shell: ZwlrLayerShellV1 = match g.bind(&qh, 3..=4, ()) {
Ok(layer_shell) => layer_shell,
Err(_) => return Err(anyhow!("zwlr_layer_shell_v1 >= v3 not supported - required to display a surface at the edge of the screen")),
};
let seat: wl_seat::WlSeat = match g.bind(&qh, 7..=8, ()) {
Ok(wl_seat) => wl_seat,
Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")),
};
let pointer_constraints: ZwpPointerConstraintsV1 = match g.bind(&qh, 1..=1, ()) {
Ok(pointer_constraints) => pointer_constraints,
Err(_) => return Err(anyhow!("zwp_pointer_constraints_v1 not supported")),
};
let relative_pointer_manager: ZwpRelativePointerManagerV1 = match g.bind(&qh, 1..=1, ()) {
Ok(relative_pointer_manager) => relative_pointer_manager,
Err(_) => return Err(anyhow!("zwp_relative_pointer_manager_v1 not supported")),
};
let shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1 =
match g.bind(&qh, 1..=1, ()) {
Ok(shortcut_inhibit_manager) => shortcut_inhibit_manager,
Err(_) => {
return Err(anyhow!(
"zwp_keyboard_shortcuts_inhibit_manager_v1 not supported"
))
}
};
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 outputs = vec![];
let g = Globals { let g = Globals {

View File

@@ -96,8 +96,7 @@ unsafe fn get_event_tid() -> Option<u32> {
static mut ENTRY_POINT: (i32, i32) = (0, 0); static mut ENTRY_POINT: (i32, i32) = (0, 0);
fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> { fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
let mouse_low_level: MSLLHOOKSTRUCT = let mouse_low_level: MSLLHOOKSTRUCT = unsafe { *(lparam.0 as *const MSLLHOOKSTRUCT) };
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
match wparam { match wparam {
WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button { WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button {
time: 0, time: 0,
@@ -167,8 +166,7 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
} }
unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> { unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> {
let kybrdllhookstruct: KBDLLHOOKSTRUCT = let kybrdllhookstruct: KBDLLHOOKSTRUCT = *(lparam.0 as *const KBDLLHOOKSTRUCT);
*std::mem::transmute::<LPARAM, *const KBDLLHOOKSTRUCT>(lparam);
let mut scan_code = kybrdllhookstruct.scanCode; let mut scan_code = kybrdllhookstruct.scanCode;
log::trace!("scan_code: {scan_code}"); log::trace!("scan_code: {scan_code}");
if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) { if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) {
@@ -247,8 +245,7 @@ unsafe fn check_client_activation(wparam: WPARAM, lparam: LPARAM) -> bool {
if wparam.0 != WM_MOUSEMOVE as usize { if wparam.0 != WM_MOUSEMOVE as usize {
return ACTIVE_CLIENT.is_some(); return ACTIVE_CLIENT.is_some();
} }
let mouse_low_level: MSLLHOOKSTRUCT = let mouse_low_level: MSLLHOOKSTRUCT = *(lparam.0 as *const MSLLHOOKSTRUCT);
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
static mut PREV_POS: Option<(i32, i32)> = None; static mut PREV_POS: Option<(i32, i32)> = None;
let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y); let curr_pos = (mouse_low_level.pt.x, mouse_low_level.pt.y);
let prev_pos = PREV_POS.unwrap_or(curr_pos); let prev_pos = PREV_POS.unwrap_or(curr_pos);
@@ -578,7 +575,7 @@ fn update_clients(client_event: ClientEvent) {
} }
impl WindowsInputCapture { impl WindowsInputCapture {
pub(crate) fn new() -> Result<Self> { pub(crate) fn new() -> Self {
unsafe { unsafe {
let (tx, rx) = channel(10); let (tx, rx) = channel(10);
EVENT_TX.replace(tx); EVENT_TX.replace(tx);
@@ -586,10 +583,10 @@ impl WindowsInputCapture {
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx))); let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
/* wait for thread to set its id */ /* wait for thread to set its id */
ready_rx.recv().expect("channel closed"); ready_rx.recv().expect("channel closed");
Ok(Self { Self {
msg_thread, msg_thread,
event_rx: rx, event_rx: rx,
}) }
} }
} }
} }

View File

@@ -1,4 +1,3 @@
use anyhow::{anyhow, Result};
use std::io; use std::io;
use std::task::Poll; use std::task::Poll;
@@ -9,11 +8,13 @@ use crate::event::Event;
use crate::client::{ClientEvent, ClientHandle}; use crate::client::{ClientEvent, ClientHandle};
use super::error::X11InputCaptureCreationError;
pub struct X11InputCapture {} pub struct X11InputCapture {}
impl X11InputCapture { impl X11InputCapture {
pub fn new() -> Result<Self> { pub fn new() -> std::result::Result<Self, X11InputCaptureCreationError> {
Err(anyhow!("not implemented")) Err(X11InputCaptureCreationError::NotImplemented)
} }
} }

View File

@@ -1,5 +1,6 @@
use crate::capture; use crate::capture;
use crate::client::{ClientEvent, Position}; use crate::client::{ClientEvent, Position};
use crate::config::Config;
use crate::event::{Event, KeyboardEvent}; use crate::event::{Event, KeyboardEvent};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use futures::StreamExt; use futures::StreamExt;
@@ -12,12 +13,14 @@ pub fn run() -> Result<()> {
.enable_time() .enable_time()
.build()?; .build()?;
runtime.block_on(LocalSet::new().run_until(input_capture_test())) let config = Config::new()?;
runtime.block_on(LocalSet::new().run_until(input_capture_test(config)))
} }
async fn input_capture_test() -> Result<()> { async fn input_capture_test(config: Config) -> Result<()> {
log::info!("creating input capture"); log::info!("creating input capture");
let mut input_capture = capture::create().await; let mut input_capture = capture::create(config.capture_backend).await?;
log::info!("creating clients"); log::info!("creating clients");
input_capture.notify(ClientEvent::Create(0, Position::Left))?; input_capture.notify(ClientEvent::Create(0, Position::Left))?;
input_capture.notify(ClientEvent::Create(1, Position::Right))?; input_capture.notify(ClientEvent::Create(1, Position::Right))?;

View File

@@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use clap::Parser; use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
@@ -15,6 +15,7 @@ pub const DEFAULT_PORT: u16 = 4242;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct ConfigToml { pub struct ConfigToml {
pub capture_backend: Option<CaptureBackend>,
pub port: Option<u16>, pub port: Option<u16>,
pub frontend: Option<String>, pub frontend: Option<String>,
pub release_bind: Option<Vec<scancode::Linux>>, pub release_bind: Option<Vec<scancode::Linux>>,
@@ -26,6 +27,7 @@ pub struct ConfigToml {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct TomlClient { pub struct TomlClient {
pub capture_backend: Option<CaptureBackend>,
pub hostname: Option<String>, pub hostname: Option<String>,
pub host_name: Option<String>, pub host_name: Option<String>,
pub ips: Option<Vec<IpAddr>>, pub ips: Option<Vec<IpAddr>>,
@@ -68,8 +70,34 @@ struct CliArgs {
/// test input emulation /// test input emulation
#[arg(long)] #[arg(long)]
test_emulation: bool, test_emulation: bool,
/// capture backend override
#[arg(long)]
capture_backend: Option<CaptureBackend>,
/// emulation backend override
#[arg(long)]
emulation_backend: Option<EmulationBackend>,
} }
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)]
pub enum CaptureBackend {
#[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,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum EmulationBackend {}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum Frontend { pub enum Frontend {
Gtk, Gtk,
@@ -78,6 +106,7 @@ pub enum Frontend {
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub capture_backend: Option<CaptureBackend>,
pub frontend: Frontend, pub frontend: Frontend,
pub port: u16, pub port: u16,
pub clients: Vec<(TomlClient, Position)>, pub clients: Vec<(TomlClient, Position)>,
@@ -163,6 +192,11 @@ impl Config {
.and_then(|c| c.release_bind.clone()) .and_then(|c| c.release_bind.clone())
.unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned())); .unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned()));
let capture_backend = match args.capture_backend {
Some(b) => Some(b),
None => config_toml.as_ref().and_then(|c| c.capture_backend),
};
let mut clients: Vec<(TomlClient, Position)> = vec![]; let mut clients: Vec<(TomlClient, Position)> = vec![];
if let Some(config_toml) = config_toml { if let Some(config_toml) = config_toml {
@@ -185,6 +219,7 @@ impl Config {
let test_emulation = args.test_emulation; let test_emulation = args.test_emulation;
Ok(Config { Ok(Config {
capture_backend,
daemon, daemon,
frontend, frontend,
clients, clients,

View File

@@ -11,6 +11,7 @@ use std::{
use crate::frontend::{gtk::window::Window, FrontendRequest}; use crate::frontend::{gtk::window::Window, FrontendRequest};
use adw::Application; use adw::Application;
use endi::{Endian, ReadBytes};
use gtk::{ use gtk::{
gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme, gdk::Display, glib::clone, prelude::*, subclass::prelude::ObjectSubclassIsExt, IconTheme,
}; };
@@ -85,16 +86,14 @@ fn build_ui(app: &Application) {
gio::spawn_blocking(move || { gio::spawn_blocking(move || {
match loop { match loop {
// read length // read length
let mut len = [0u8; 8]; let len = match rx.read_u64(Endian::Big) {
match rx.read_exact(&mut len) { Ok(l) => l,
Ok(_) => (),
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()), Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),
Err(e) => break Err(e), Err(e) => break Err(e),
}; };
let len = usize::from_be_bytes(len);
// read payload // read payload
let mut buf = vec![0u8; len]; let mut buf = vec![0u8; len as usize];
match rx.read_exact(&mut buf) { match rx.read_exact(&mut buf) {
Ok(_) => (), Ok(_) => (),
Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()), Err(e) if e.kind() == ErrorKind::UnexpectedEof => break Ok(()),

View File

@@ -10,6 +10,7 @@ use std::net::TcpStream;
use adw::prelude::*; use adw::prelude::*;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use endi::{Endian, WriteBytes};
use glib::{clone, Object}; use glib::{clone, Object};
use gtk::{ use gtk::{
gio, gio,
@@ -265,8 +266,7 @@ impl Window {
let mut stream = self.imp().stream.borrow_mut(); let mut stream = self.imp().stream.borrow_mut();
let stream = stream.as_mut().unwrap(); let stream = stream.as_mut().unwrap();
let bytes = json.as_bytes(); let bytes = json.as_bytes();
let len = bytes.len().to_be_bytes(); if let Err(e) = stream.write_u64(Endian::Big, bytes.len() as u64) {
if let Err(e) = stream.write(&len) {
log::error!("error sending message: {e}"); log::error!("error sending message: {e}");
}; };
if let Err(e) = stream.write(bytes) { if let Err(e) = stream.write(bytes) {

View File

@@ -71,7 +71,7 @@ fn run_service(config: &Config) -> Result<()> {
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse"); log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
let server = Server::new(config); let server = Server::new(config);
server.run().await?; server.run(config.capture_backend).await?;
log::debug!("service exiting"); log::debug!("service exiting");
anyhow::Ok(()) anyhow::Ok(())

View File

@@ -8,7 +8,7 @@ use tokio::signal;
use crate::{ use crate::{
client::{ClientConfig, ClientHandle, ClientManager, ClientState}, client::{ClientConfig, ClientHandle, ClientManager, ClientState},
config::Config, config::{CaptureBackend, Config},
dns, dns,
frontend::{FrontendListener, FrontendRequest}, frontend::{FrontendListener, FrontendRequest},
server::capture_task::CaptureEvent, server::capture_task::CaptureEvent,
@@ -77,7 +77,7 @@ impl Server {
} }
} }
pub async fn run(&self) -> anyhow::Result<()> { pub async fn run(&self, backend: Option<CaptureBackend>) -> anyhow::Result<()> {
// create frontend communication adapter // create frontend communication adapter
let frontend = match FrontendListener::new().await { let frontend = match FrontendListener::new().await {
Some(f) => f?, Some(f) => f?,
@@ -97,11 +97,12 @@ impl Server {
// input capture // input capture
let (mut capture_task, capture_channel) = capture_task::new( let (mut capture_task, capture_channel) = capture_task::new(
backend,
self.clone(), self.clone(),
sender_tx.clone(), sender_tx.clone(),
timer_tx.clone(), timer_tx.clone(),
self.release_bind.clone(), self.release_bind.clone(),
); )?;
// input emulation // input emulation
let (mut emulation_task, emulate_channel) = emulation_task::new( let (mut emulation_task, emulate_channel) = emulation_task::new(

View File

@@ -5,8 +5,9 @@ use std::{collections::HashSet, net::SocketAddr};
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle}; use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
use crate::{ use crate::{
capture::{self, InputCapture}, capture::{self, error::CaptureCreationError, InputCapture},
client::{ClientEvent, ClientHandle}, client::{ClientEvent, ClientHandle},
config::CaptureBackend,
event::{Event, KeyboardEvent}, event::{Event, KeyboardEvent},
scancode, scancode,
server::State, server::State,
@@ -25,14 +26,15 @@ pub enum CaptureEvent {
} }
pub fn new( pub fn new(
backend: Option<CaptureBackend>,
server: Server, server: Server,
sender_tx: Sender<(Event, SocketAddr)>, sender_tx: Sender<(Event, SocketAddr)>,
timer_tx: Sender<()>, timer_tx: Sender<()>,
release_bind: Vec<scancode::Linux>, release_bind: Vec<scancode::Linux>,
) -> (JoinHandle<Result<()>>, Sender<CaptureEvent>) { ) -> Result<(JoinHandle<Result<()>>, Sender<CaptureEvent>), CaptureCreationError> {
let (tx, mut rx) = tokio::sync::mpsc::channel(32); let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let task = tokio::task::spawn_local(async move { let task = tokio::task::spawn_local(async move {
let mut capture = capture::create().await; let mut capture = capture::create(backend).await?;
let mut pressed_keys = HashSet::new(); let mut pressed_keys = HashSet::new();
loop { loop {
tokio::select! { tokio::select! {
@@ -62,7 +64,7 @@ pub fn new(
} }
anyhow::Ok(()) anyhow::Ok(())
}); });
(task, tx) Ok((task, tx))
} }
fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) { fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) {