Compare commits

..

1 Commits

Author SHA1 Message Date
Ferdinand Schober
bc6d35c457 windows: fix resolution with scaling enabled 2024-05-06 20:42:24 +02:00
31 changed files with 324 additions and 534 deletions

View File

@@ -7,7 +7,7 @@ jobs:
matrix:
os:
- ubuntu-latest
- macos-13
- macos-latest
- macos-14
name: "Build"
runs-on: ${{ matrix.os }}
@@ -31,7 +31,7 @@ jobs:
run: nix build --print-build-logs --show-trace .#packages.x86_64-linux.lan-mouse
- name: Build lan-mouse (x86_64-darwin)
if: matrix.os == 'macos-13'
if: matrix.os == 'macos-latest'
run: nix build --print-build-logs --show-trace .#packages.x86_64-darwin.lan-mouse
- name: Build lan-mouse (aarch64-darwin)

View File

@@ -78,7 +78,7 @@ jobs:
path: lan-mouse-windows.zip
macos-release-build:
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: install dependencies

View File

@@ -92,7 +92,7 @@ jobs:
target/debug/*.dll
build-macos:
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: install dependencies

View File

@@ -74,7 +74,7 @@ jobs:
path: lan-mouse-windows.zip
macos-release-build:
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: install dependencies

12
Cargo.lock generated
View File

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

View File

@@ -1,7 +1,7 @@
[package]
name = "lan-mouse"
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
version = "0.8.0"
version = "0.7.3"
edition = "2021"
license = "GPL-3.0-or-later"
repository = "https://github.com/ferdinandschober/lan-mouse"
@@ -22,7 +22,7 @@ anyhow = "1.0.71"
log = "0.4.20"
env_logger = "0.11.3"
serde_json = "1.0.107"
tokio = {version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "process", "rt", "sync", "signal"] }
tokio = {version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "rt", "sync", "signal"] }
async-trait = "0.1.73"
futures-core = "0.3.28"
futures = "0.3.28"
@@ -35,8 +35,6 @@ once_cell = "1.19.0"
num_enum = "0.7.2"
hostname = "0.4.0"
slab = "0.4.9"
endi = "1.1.0"
thiserror = "1.0.61"
[target.'cfg(unix)'.dependencies]
libc = "0.2.148"

View File

@@ -69,15 +69,7 @@ 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).
### Arch Linux
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:
Lan Mouse is available on the AUR:
```sh
# git version (includes latest changes)
paru -S lan-mouse-git

View File

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

12
flake.lock generated
View File

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

View File

@@ -2,14 +2,10 @@
rustPlatform,
lib,
pkgs,
}: let
cargoToml = builtins.fromTOML (builtins.readFile ../Cargo.toml);
pname = cargoToml.package.name;
version = cargoToml.package.version;
in
}:
rustPlatform.buildRustPackage {
pname = pname;
version = version;
pname = "lan-mouse";
version = "0.7.0";
nativeBuildInputs = with pkgs; [
pkg-config
@@ -27,7 +23,7 @@ rustPlatform.buildRustPackage {
];
src = builtins.path {
name = pname;
name = "lan-mouse";
path = lib.cleanSource ../.;
};
@@ -42,7 +38,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.
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 = pname;
mainProgram = "lan-mouse";
platforms = platforms.all;
};
}

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
use crate::{
capture::{error::WaylandBindError, InputCapture},
capture::InputCapture,
client::{ClientEvent, ClientHandle, Position},
};
use anyhow::{anyhow, Result};
use futures_core::Stream;
use memmap::MmapOptions;
use std::{
@@ -67,8 +68,6 @@ use tempfile;
use crate::event::{Event, KeyboardEvent, PointerEvent};
use super::error::LayerShellCaptureCreationError;
struct Globals {
compositor: wl_compositor::WlCompositor,
pointer_constraints: ZwpPointerConstraintsV1,
@@ -112,7 +111,6 @@ struct State {
qh: QueueHandle<Self>,
pending_events: VecDeque<(ClientHandle, Event)>,
output_info: Vec<(WlOutput, OutputInfo)>,
scroll_discrete_pending: bool,
}
struct Inner {
@@ -259,37 +257,64 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
}
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)?;
pub fn new() -> Result<Self> {
let conn = match Connection::connect_to_env() {
Ok(c) => c,
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 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 compositor: wl_compositor::WlCompositor = match g.bind(&qh, 4..=5, ()) {
Ok(compositor) => compositor,
Err(_) => return Err(anyhow!("wl_compositor >= v4 not supported")),
};
let xdg_output_manager: ZxdgOutputManagerV1 = match g.bind(&qh, 1..=3, ()) {
Ok(xdg_output_manager) => xdg_output_manager,
Err(_) => return Err(anyhow!("xdg_output not supported!")),
};
let shm: wl_shm::WlShm = match g.bind(&qh, 1..=1, ()) {
Ok(wl_shm) => wl_shm,
Err(_) => return Err(anyhow!("wl_shm v1 not supported")),
};
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 g = Globals {
@@ -326,7 +351,6 @@ impl WaylandInputCapture {
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
@@ -728,25 +752,17 @@ impl Dispatch<WlPointer, ()> for 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,
}),
));
}
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 {

View File

@@ -96,7 +96,8 @@ unsafe fn get_event_tid() -> Option<u32> {
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) };
let mouse_low_level: MSLLHOOKSTRUCT =
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
match wparam {
WPARAM(p) if p == WM_LBUTTONDOWN as usize => Some(PointerEvent::Button {
time: 0,
@@ -140,7 +141,7 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
},
WPARAM(p) if p == WM_MOUSEWHEEL as usize => Some(PointerEvent::AxisDiscrete120 {
axis: 0,
value: -(mouse_low_level.mouseData as i32 >> 16),
value: -(mouse_low_level.mouseData as i32),
}),
WPARAM(p) if p == WM_XBUTTONDOWN as usize || p == WM_XBUTTONUP as usize => {
let hb = mouse_low_level.mouseData >> 16;
@@ -166,7 +167,8 @@ fn to_mouse_event(wparam: WPARAM, lparam: LPARAM) -> Option<PointerEvent> {
}
unsafe fn to_key_event(wparam: WPARAM, lparam: LPARAM) -> Option<KeyboardEvent> {
let kybrdllhookstruct: KBDLLHOOKSTRUCT = *(lparam.0 as *const KBDLLHOOKSTRUCT);
let kybrdllhookstruct: KBDLLHOOKSTRUCT =
*std::mem::transmute::<LPARAM, *const KBDLLHOOKSTRUCT>(lparam);
let mut scan_code = kybrdllhookstruct.scanCode;
log::trace!("scan_code: {scan_code}");
if kybrdllhookstruct.flags.contains(LLKHF_EXTENDED) {
@@ -245,7 +247,8 @@ 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);
let mouse_low_level: MSLLHOOKSTRUCT =
unsafe { *std::mem::transmute::<LPARAM, *const MSLLHOOKSTRUCT>(lparam) };
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);
@@ -575,7 +578,7 @@ fn update_clients(client_event: ClientEvent) {
}
impl WindowsInputCapture {
pub(crate) fn new() -> Self {
pub(crate) fn new() -> Result<Self> {
unsafe {
let (tx, rx) = channel(10);
EVENT_TX.replace(tx);
@@ -583,10 +586,10 @@ impl WindowsInputCapture {
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
/* wait for thread to set its id */
ready_rx.recv().expect("channel closed");
Self {
Ok(Self {
msg_thread,
event_rx: rx,
}
})
}
}
}

View File

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

View File

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

View File

@@ -102,8 +102,6 @@ pub struct ClientConfig {
pub port: u16,
/// position of a client on screen
pub pos: Position,
/// enter hook
pub cmd: Option<String>,
}
impl Default for ClientConfig {
@@ -113,7 +111,6 @@ impl Default for ClientConfig {
hostname: Default::default(),
fix_ips: Default::default(),
pos: Default::default(),
cmd: None,
}
}
}

View File

@@ -1,5 +1,5 @@
use anyhow::Result;
use clap::{Parser, ValueEnum};
use clap::Parser;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::env;
@@ -15,7 +15,6 @@ pub const DEFAULT_PORT: u16 = 4242;
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigToml {
pub capture_backend: Option<CaptureBackend>,
pub port: Option<u16>,
pub frontend: Option<String>,
pub release_bind: Option<Vec<scancode::Linux>>,
@@ -27,13 +26,11 @@ pub struct ConfigToml {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct TomlClient {
pub capture_backend: Option<CaptureBackend>,
pub hostname: Option<String>,
pub host_name: Option<String>,
pub ips: Option<Vec<IpAddr>>,
pub port: Option<u16>,
pub activate_on_startup: Option<bool>,
pub enter_hook: Option<String>,
}
impl ConfigToml {
@@ -70,34 +67,8 @@ struct CliArgs {
/// test input emulation
#[arg(long)]
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)]
pub enum Frontend {
Gtk,
@@ -106,7 +77,6 @@ pub enum Frontend {
#[derive(Debug)]
pub struct Config {
pub capture_backend: Option<CaptureBackend>,
pub frontend: Frontend,
pub port: u16,
pub clients: Vec<(TomlClient, Position)>,
@@ -122,7 +92,6 @@ pub struct ConfigClient {
pub port: u16,
pub pos: Position,
pub active: bool,
pub enter_hook: Option<String>,
}
const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] =
@@ -192,11 +161,6 @@ impl Config {
.and_then(|c| c.release_bind.clone())
.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![];
if let Some(config_toml) = config_toml {
@@ -219,7 +183,6 @@ impl Config {
let test_emulation = args.test_emulation;
Ok(Config {
capture_backend,
daemon,
frontend,
clients,
@@ -245,14 +208,12 @@ impl Config {
None => c.host_name.clone(),
};
let active = c.activate_on_startup.unwrap_or(false);
let enter_hook = c.enter_hook.clone();
ConfigClient {
ips,
hostname,
port,
pos: *pos,
active,
enter_hook,
}
})
.collect()

View File

@@ -182,8 +182,7 @@ impl VirtualInput {
}
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.axis(0, axis, (value / 15) as f64);
self.pointer.frame();
}
PointerEvent::Frame {} => self.pointer.frame(),

View File

@@ -107,18 +107,16 @@ pub enum FrontendRequest {
UpdatePosition(ClientHandle, Position),
/// update fix-ips
UpdateFixIps(ClientHandle, Vec<IpAddr>),
/// request the state of the given client
GetState(ClientHandle),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FrontendEvent {
/// a client was created
Created(ClientHandle, ClientConfig, ClientState),
/// no such client
NoSuchClient(ClientHandle),
/// a client was updated
Updated(ClientHandle, ClientConfig),
/// state changed
State(ClientHandle, ClientConfig, ClientState),
StateChange(ClientHandle, ClientState),
/// the client was deleted
Deleted(ClientHandle),
/// new port, reason of failure (if failed)
@@ -237,7 +235,9 @@ impl FrontendListener {
let json = serde_json::to_string(&notify).unwrap();
let payload = json.as_bytes();
let len = payload.len().to_be_bytes();
log::debug!("broadcasting event to streams: {json}");
log::debug!("json: {json}, len: {}", payload.len());
log::debug!("broadcasting event to streams: {:?}", self.tx_streams);
let mut keep = vec![];
// TODO do simultaneously
for tx in self.tx_streams.iter_mut() {

View File

@@ -100,18 +100,6 @@ impl<'a> Cli<'a> {
}
}
async fn update_client(&mut self, handle: ClientHandle) -> Result<()> {
self.send_request(FrontendRequest::GetState(handle)).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::State(_, _, _) | FrontendEvent::NoSuchClient(_) = event {
break;
}
}
Ok(())
}
async fn execute(&mut self, cmd: Command) -> Result<()> {
match cmd {
Command::None => {}
@@ -137,8 +125,14 @@ impl<'a> Cli<'a> {
FrontendRequest::UpdatePosition(handle, pos),
] {
self.send_request(request).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::Updated(_, _) = event {
break;
}
}
}
self.update_client(handle).await?;
}
Command::Disconnect(id) => {
self.send_request(FrontendRequest::Delete(id)).await?;
@@ -154,12 +148,26 @@ impl<'a> Cli<'a> {
Command::Activate(id) => {
self.send_request(FrontendRequest::Activate(id, true))
.await?;
self.update_client(id).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::StateChange(_, _) = event {
self.handle_event(event);
break;
}
}
}
Command::Deactivate(id) => {
self.send_request(FrontendRequest::Activate(id, false))
.await?;
self.update_client(id).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::StateChange(_, _) = event {
self.handle_event(event);
break;
}
}
}
Command::List => {
self.send_request(FrontendRequest::Enumerate()).await?;
@@ -174,12 +182,25 @@ impl<'a> Cli<'a> {
Command::SetHost(handle, host) => {
let request = FrontendRequest::UpdateHostname(handle, Some(host.clone()));
self.send_request(request).await?;
self.update_client(handle).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::Updated(_, _) = event {
self.handle_event(event);
break;
}
}
}
Command::SetPort(handle, port) => {
let request = FrontendRequest::UpdatePort(handle, port.unwrap_or(DEFAULT_PORT));
self.send_request(request).await?;
self.update_client(handle).await?;
loop {
let event = self.await_event().await?;
self.handle_event(event.clone());
if let FrontendEvent::Updated(_, _) = event {
break;
}
}
}
Command::Help => {
for cmd_type in [
@@ -223,11 +244,8 @@ impl<'a> Cli<'a> {
eprintln!();
self.clients.push((h, c, s));
}
FrontendEvent::NoSuchClient(h) => {
eprintln!("no such client: {h}");
}
FrontendEvent::State(h, c, s) => {
if let Some((_, config, state)) = self.find_mut(h) {
FrontendEvent::Updated(h, c) => {
if let Some((_, config, _)) = self.find_mut(h) {
let old_host = config.hostname.clone().unwrap_or("\"\"".into());
let new_host = c.hostname.clone().unwrap_or("\"\"".into());
if old_host != new_host {
@@ -243,6 +261,10 @@ impl<'a> Cli<'a> {
eprintln!("client {h} ips updated: {:?}", c.fix_ips)
}
*config = c;
}
}
FrontendEvent::StateChange(h, s) => {
if let Some((_, _, state)) = self.find_mut(h) {
if state.active ^ s.active {
eprintln!(
"client {h} {}",

View File

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

View File

@@ -10,7 +10,6 @@ use std::net::TcpStream;
use adw::prelude::*;
use adw::subclass::prelude::*;
use endi::{Endian, WriteBytes};
use glib::{clone, Object};
use gtk::{
gio,
@@ -52,10 +51,6 @@ impl Window {
.expect("Could not get clients")
}
fn client_by_idx(&self, idx: u32) -> Option<ClientObject> {
self.clients().item(idx).map(|o| o.downcast().unwrap())
}
fn setup_clients(&self) {
let model = gio::ListStore::new::<ClientObject>();
self.imp().clients.replace(Some(model));
@@ -67,24 +62,27 @@ impl Window {
let client_object = obj.downcast_ref().expect("Expected object of type `ClientObject`.");
let row = window.create_client_row(client_object);
row.connect_closure("request-update", false, closure_local!(@strong window => move |row: ClientRow, active: bool| {
if let Some(client) = window.client_by_idx(row.index() as u32) {
window.request_client_activate(&client, active);
window.request_client_update(&client);
window.request_client_state(&client);
}
let index = row.index() as u32;
let Some(client) = window.clients().item(index) else {
return;
};
let client = client.downcast_ref::<ClientObject>().unwrap();
window.request_client_update(client);
window.request_client_activate(client, active)
}));
row.connect_closure("request-delete", false, closure_local!(@strong window => move |row: ClientRow| {
if let Some(client) = window.client_by_idx(row.index() as u32) {
window.request_client_delete(&client);
}
let index = row.index() as u32;
window.request_client_delete(index);
}));
row.connect_closure("request-dns", false, closure_local!(@strong window => move
|row: ClientRow| {
if let Some(client) = window.client_by_idx(row.index() as u32) {
window.request_client_update(&client);
window.request_dns(&client);
window.request_client_state(&client);
}
let index = row.index() as u32;
let Some(client) = window.clients().item(index) else {
return;
};
let client = client.downcast_ref::<ClientObject>().unwrap();
window.request_client_update(client);
window.request_dns(index);
}));
row.upcast()
})
@@ -179,7 +177,7 @@ impl Window {
if state.resolving != data.resolving {
client_object.set_resolving(state.resolving);
log::debug!("resolving {}: {}", data.handle, state.resolving);
log::debug!("resolving {}: {}", data.handle, state.active);
}
self.update_dns_state(handle, !state.ips.is_empty());
@@ -206,6 +204,20 @@ impl Window {
}
}
pub fn request_dns(&self, idx: u32) {
let client_object = self.clients().item(idx).unwrap();
let client_object: &ClientObject = client_object.downcast_ref().unwrap();
let data = client_object.get_data();
let event = FrontendRequest::ResolveDns(data.handle);
self.request(event);
}
pub fn request_client_create(&self) {
let event = FrontendRequest::Create;
self.imp().set_port(DEFAULT_PORT);
self.request(event);
}
pub fn request_port_change(&self) {
let port = self.imp().port_entry.get().text().to_string();
if let Ok(port) = port.as_str().parse::<u16>() {
@@ -215,23 +227,6 @@ impl Window {
}
}
pub fn request_client_state(&self, client: &ClientObject) {
let handle = client.handle();
let event = FrontendRequest::GetState(handle);
self.request(event);
}
pub fn request_client_create(&self) {
let event = FrontendRequest::Create;
self.request(event);
}
pub fn request_dns(&self, client: &ClientObject) {
let data = client.get_data();
let event = FrontendRequest::ResolveDns(data.handle);
self.request(event);
}
pub fn request_client_update(&self, client: &ClientObject) {
let handle = client.handle();
let data = client.get_data();
@@ -244,29 +239,38 @@ impl Window {
FrontendRequest::UpdatePosition(handle, position),
FrontendRequest::UpdatePort(handle, port),
] {
log::debug!("requesting: {event:?}");
self.request(event);
}
}
pub fn request_client_activate(&self, client: &ClientObject, active: bool) {
let handle = client.handle();
let event = FrontendRequest::Activate(handle, active);
log::debug!("requesting: {event:?}");
self.request(event);
}
pub fn request_client_delete(&self, client: &ClientObject) {
let handle = client.handle();
let event = FrontendRequest::Delete(handle);
self.request(event);
pub fn request_client_delete(&self, idx: u32) {
if let Some(obj) = self.clients().item(idx) {
let client_object: &ClientObject = obj
.downcast_ref()
.expect("Expected object of type `ClientObject`.");
let handle = client_object.handle();
let event = FrontendRequest::Delete(handle);
self.request(event);
}
}
pub fn request(&self, event: FrontendRequest) {
let json = serde_json::to_string(&event).unwrap();
log::debug!("requesting: {json}");
log::debug!("requesting {json}");
let mut stream = self.imp().stream.borrow_mut();
let stream = stream.as_mut().unwrap();
let bytes = json.as_bytes();
if let Err(e) = stream.write_u64(Endian::Big, bytes.len() as u64) {
let len = bytes.len().to_be_bytes();
if let Err(e) = stream.write(&len) {
log::error!("error sending message: {e}");
};
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");
let server = Server::new(config);
server.run(config.capture_backend).await?;
server.run().await?;
log::debug!("service exiting");
anyhow::Ok(())

View File

@@ -3,8 +3,6 @@ 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)]
@@ -122,15 +120,15 @@ pub enum Windows {
KeyF21 = 0x006C,
KeyF22 = 0x006D,
KeyF23 = 0x006E,
KeyF24 = 0x0076, // KeyLANG5
KeyF24 = 0x0076,
KeypadComma = 0x007E,
KeyInternational1 = 0x0073,
KeyInternational2 = 0x0070,
KeyInternational3 = 0x007D, // typo in doc -> its Int'l 3 not Int'l 2
KeyInternational3 = 0x007D,
#[allow(dead_code)]
KeyInternational4 = 0x0079,
KeyInternational4 = 0x0079, // FIXME unused
#[allow(dead_code)]
KeyInternational5 = 0x007B,
KeyInternational5 = 0x007B, // FIXME unused
// KeyInternational6 = 0x005C,
KeyLANG1 = 0x0072,
KeyLANG2 = 0x0071,
@@ -143,7 +141,6 @@ pub enum Windows {
KeyLeftGUI = 0xE05B,
KeyRightCtrl = 0xE01D,
KeyRightShift = 0x0036,
KeyFakeRightShift = 0xE036,
KeyRightAlt = 0xE038,
KeyRightGUI = 0xE05C,
KeyScanNextTrack = 0xE019,
@@ -296,7 +293,7 @@ pub enum Linux {
KeyPause = 119,
KeyScale = 120, /* AL Compiz Scale (Expose) */
KeyKpcomma = 121,
KeyHanguel = 122,
KeyHangeul = 122,
// KEY_HANGUEL = KeyHangeul,
KeyHanja = 123,
KeyYen = 124,
@@ -521,16 +518,16 @@ impl TryFrom<Linux> for Windows {
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::KeyZenkakuhankaku => Ok(Self::KeyLANG1), // TODO unsure
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::KeyRo => Ok(Self::ErrorRollOver), // TODO unsure
Linux::KeyKatakana => Ok(Self::KeyLANG1), // TODO unsure
Linux::KeyHiragana => Ok(Self::KeyLANG2), // TODO unsure
Linux::KeyHenkan => Ok(Self::KeyLANG3), // TODO unsure
Linux::KeyKatakanahiragana => Ok(Self::KeyLANG4), // TODO unsure
Linux::KeyMuhenkan => Ok(Self::KeyLANG4), // TODO unsure
Linux::KeyKpJpComma => Ok(Self::KeypadComma),
Linux::KeyKpEnter => Ok(Self::KeypadEnter),
Linux::KeyRightCtrl => Ok(Self::KeyRightCtrl),
@@ -558,9 +555,9 @@ impl TryFrom<Linux> for Windows {
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::KeyHangeul => Ok(Self::KeyInternational1), // TODO unsure
Linux::KeyHanja => Ok(Self::KeyInternational2), // TODO unsure
Linux::KeyYen => Ok(Self::KeyInternational3), // TODO unsure
Linux::KeyLeftMeta => Ok(Self::KeyLeftGUI),
Linux::KeyRightmeta => Ok(Self::KeyRightGUI),
Linux::KeyCompose => Ok(Self::KeyApplication),
@@ -810,22 +807,21 @@ impl TryFrom<Windows> for Linux {
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::KeyInternational1 => Ok(Self::KeyHangeul),
Windows::KeyInternational2 => Ok(Self::KeyHanja),
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::KeyInternational4 => Err(()),
Windows::KeyInternational5 => Err(()),
Windows::KeyLANG1 => Ok(Self::KeyKatakana),
Windows::KeyLANG2 => Ok(Self::KeyHiragana),
Windows::KeyLANG3 => Ok(Self::KeyHenkan),
Windows::KeyLANG4 => Ok(Self::KeyKatakanahiragana),
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),

View File

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

View File

@@ -2,12 +2,11 @@ use anyhow::{anyhow, Result};
use futures::StreamExt;
use std::{collections::HashSet, net::SocketAddr};
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
use tokio::{sync::mpsc::Sender, task::JoinHandle};
use crate::{
capture::{self, error::CaptureCreationError, InputCapture},
capture::{self, InputCapture},
client::{ClientEvent, ClientHandle},
config::CaptureBackend,
event::{Event, KeyboardEvent},
scancode,
server::State,
@@ -26,15 +25,14 @@ pub enum CaptureEvent {
}
pub fn new(
backend: Option<CaptureBackend>,
server: Server,
sender_tx: Sender<(Event, SocketAddr)>,
timer_tx: Sender<()>,
release_bind: Vec<scancode::Linux>,
) -> Result<(JoinHandle<Result<()>>, Sender<CaptureEvent>), CaptureCreationError> {
) -> (JoinHandle<Result<()>>, Sender<CaptureEvent>) {
let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let task = tokio::task::spawn_local(async move {
let mut capture = capture::create(backend).await?;
let mut capture = capture::create().await;
let mut pressed_keys = HashSet::new();
loop {
tokio::select! {
@@ -64,7 +62,7 @@ pub fn new(
}
anyhow::Ok(())
});
Ok((task, tx))
(task, tx)
}
fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) {
@@ -142,9 +140,6 @@ async fn handle_capture_event(
if start_timer {
let _ = timer_tx.try_send(());
}
if enter {
spawn_hook_command(server, handle);
}
if let Some(addr) = addr {
if enter {
let _ = sender_tx.send((Event::Enter(), addr)).await;
@@ -153,34 +148,3 @@ async fn handle_capture_event(
}
Ok(())
}
fn spawn_hook_command(server: &Server, handle: ClientHandle) {
let Some(cmd) = server
.client_manager
.borrow()
.get(handle)
.and_then(|(c, _)| c.cmd.clone())
else {
return;
};
tokio::task::spawn_local(async move {
log::info!("spawning command!");
let mut child = match Command::new("sh").arg("-c").arg(cmd.as_str()).spawn() {
Ok(c) => c,
Err(e) => {
log::warn!("could not execute cmd: {e}");
return;
}
};
match child.wait().await {
Ok(s) => {
if s.success() {
log::info!("{cmd} exited successfully");
} else {
log::warn!("{cmd} exited with {s}");
}
}
Err(e) => log::warn!("{cmd}: {e}"),
}
});
}

View File

@@ -107,22 +107,20 @@ async fn handle_frontend_event(
log::debug!("frontend: {event:?}");
match event {
FrontendRequest::Create => {
let handle = add_client(server, frontend).await;
resolve_dns(server, resolve_tx, handle).await;
add_client(server, frontend).await;
}
FrontendRequest::Activate(handle, active) => {
if active {
activate_client(server, capture, emulate, handle).await;
activate_client(server, frontend, capture, emulate, handle).await;
} else {
deactivate_client(server, capture, emulate, handle).await;
deactivate_client(server, frontend, capture, emulate, handle).await;
}
}
FrontendRequest::ChangePort(port) => {
let _ = port_tx.send(port).await;
}
FrontendRequest::Delete(handle) => {
remove_client(server, capture, emulate, handle).await;
broadcast(frontend, FrontendEvent::Deleted(handle)).await;
remove_client(server, frontend, capture, emulate, handle).await;
}
FrontendRequest::Enumerate() => {
let clients = server
@@ -133,74 +131,65 @@ async fn handle_frontend_event(
.collect();
broadcast(frontend, FrontendEvent::Enumerate(clients)).await;
}
FrontendRequest::GetState(handle) => {
broadcast_client(server, frontend, handle).await;
}
FrontendRequest::Terminate() => {
log::info!("terminating gracefully...");
return true;
}
FrontendRequest::UpdateFixIps(handle, fix_ips) => {
update_fix_ips(server, handle, fix_ips).await;
resolve_dns(server, resolve_tx, handle).await;
update_fix_ips(server, resolve_tx, handle, fix_ips).await;
broadcast_client_update(server, frontend, handle).await;
}
FrontendRequest::UpdateHostname(handle, hostname) => {
update_hostname(server, resolve_tx, handle, hostname).await;
resolve_dns(server, resolve_tx, handle).await;
broadcast_client_update(server, frontend, handle).await;
}
FrontendRequest::UpdatePort(handle, port) => {
update_port(server, handle, port).await;
broadcast_client_update(server, frontend, handle).await;
}
FrontendRequest::UpdatePosition(handle, pos) => {
update_pos(server, handle, capture, emulate, pos).await;
broadcast_client_update(server, frontend, handle).await;
}
FrontendRequest::ResolveDns(handle) => {
resolve_dns(server, resolve_tx, handle).await;
let hostname = server
.client_manager
.borrow()
.get(handle)
.and_then(|(c, _)| c.hostname.clone());
if let Some(hostname) = hostname {
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
}
}
};
false
}
async fn resolve_dns(server: &Server, resolve_tx: &Sender<DnsRequest>, handle: ClientHandle) {
let hostname = server
.client_manager
.borrow()
.get(handle)
.and_then(|(c, _)| c.hostname.clone());
if let Some(hostname) = hostname {
let _ = resolve_tx
.send(DnsRequest {
hostname: hostname.clone(),
handle,
})
.await;
}
}
async fn broadcast(frontend: &mut FrontendListener, event: FrontendEvent) {
if let Err(e) = frontend.broadcast_event(event).await {
log::error!("error notifying frontend: {e}");
}
}
pub async fn add_client(server: &Server, frontend: &mut FrontendListener) -> ClientHandle {
pub async fn add_client(server: &Server, frontend: &mut FrontendListener) {
let handle = server.client_manager.borrow_mut().add_client();
log::info!("added client {handle}");
let (c, s) = server.client_manager.borrow().get(handle).unwrap().clone();
broadcast(frontend, FrontendEvent::Created(handle, c, s)).await;
handle
}
pub async fn deactivate_client(
server: &Server,
frontend: &mut FrontendListener,
capture: &Sender<CaptureEvent>,
emulate: &Sender<EmulationEvent>,
handle: ClientHandle,
) {
match server.client_manager.borrow_mut().get_mut(handle) {
let state = match server.client_manager.borrow_mut().get_mut(handle) {
Some((_, s)) => {
s.active = false;
s.clone()
}
None => return,
};
@@ -208,10 +197,13 @@ pub async fn deactivate_client(
let event = ClientEvent::Destroy(handle);
let _ = capture.send(CaptureEvent::ClientEvent(event)).await;
let _ = emulate.send(EmulationEvent::ClientEvent(event)).await;
let event = FrontendEvent::StateChange(handle, state);
broadcast(frontend, event).await;
}
pub async fn activate_client(
server: &Server,
frontend: &mut FrontendListener,
capture: &Sender<CaptureEvent>,
emulate: &Sender<EmulationEvent>,
handle: ClientHandle,
@@ -225,13 +217,14 @@ pub async fn activate_client(
let other = server.client_manager.borrow_mut().find_client(pos);
if let Some(other) = other {
if other != handle {
deactivate_client(server, capture, emulate, other).await;
deactivate_client(server, frontend, capture, emulate, other).await;
}
}
/* activate the client */
if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) {
let state = if let Some((_, s)) = server.client_manager.borrow_mut().get_mut(handle) {
s.active = true;
s.clone()
} else {
return;
};
@@ -240,10 +233,13 @@ pub async fn activate_client(
let event = ClientEvent::Create(handle, pos);
let _ = capture.send(CaptureEvent::ClientEvent(event)).await;
let _ = emulate.send(EmulationEvent::ClientEvent(event)).await;
let event = FrontendEvent::StateChange(handle, state);
broadcast(frontend, event).await;
}
pub async fn remove_client(
server: &Server,
frontend: &mut FrontendListener,
capture: &Sender<CaptureEvent>,
emulate: &Sender<EmulationEvent>,
handle: ClientHandle,
@@ -262,15 +258,30 @@ pub async fn remove_client(
let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await;
let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await;
}
let event = FrontendEvent::Deleted(handle);
broadcast(frontend, event).await;
}
async fn update_fix_ips(server: &Server, handle: ClientHandle, fix_ips: Vec<IpAddr>) {
let mut client_manager = server.client_manager.borrow_mut();
let Some((c, _)) = client_manager.get_mut(handle) else {
return;
async fn update_fix_ips(
server: &Server,
resolve_tx: &Sender<DnsRequest>,
handle: ClientHandle,
fix_ips: Vec<IpAddr>,
) {
let hostname = {
let mut client_manager = server.client_manager.borrow_mut();
let Some((c, _)) = client_manager.get_mut(handle) else {
return;
};
c.fix_ips = fix_ips;
c.hostname.clone()
};
c.fix_ips = fix_ips;
if let Some(hostname) = hostname {
let _ = resolve_tx.send(DnsRequest { hostname, handle }).await;
}
}
async fn update_hostname(
@@ -345,11 +356,11 @@ async fn update_pos(
}
}
async fn broadcast_client(server: &Server, frontend: &mut FrontendListener, handle: ClientHandle) {
let client = server.client_manager.borrow().get(handle).cloned();
if let Some((config, state)) = client {
broadcast(frontend, FrontendEvent::State(handle, config, state)).await;
} else {
broadcast(frontend, FrontendEvent::NoSuchClient(handle)).await;
}
async fn broadcast_client_update(
server: &Server,
frontend: &mut FrontendListener,
handle: ClientHandle,
) {
let (client, _) = server.client_manager.borrow().get(handle).unwrap().clone();
broadcast(frontend, FrontendEvent::Updated(handle, client)).await;
}

View File

@@ -35,7 +35,7 @@ pub fn new(
Ok(ips) => ips,
Err(e) => {
log::warn!("could not resolve host '{host}': {e}");
vec![]
continue;
}
};
@@ -59,10 +59,14 @@ async fn notify_state_change(
server: &mut Server,
handle: ClientHandle,
) {
let state = server.client_manager.borrow_mut().get_mut(handle).cloned();
if let Some((config, state)) = state {
let state = server
.client_manager
.borrow_mut()
.get_mut(handle)
.map(|(_, s)| s.clone());
if let Some(state) = state {
let _ = frontend
.send(FrontendEvent::State(handle, config, state))
.send(FrontendEvent::StateChange(handle, state))
.await;
}
}