Compare commits

...

9 Commits

Author SHA1 Message Date
Ferdinand Schober
6c99f9bea3 chore: Release lan-mouse version 0.5.1 2024-01-12 13:46:56 +01:00
Ferdinand Schober
d54b3a08e1 ignore double press / release events (#71)
This should fix most of the remaining issues with stuck keys in KDE
2024-01-12 13:13:23 +01:00
Ferdinand Schober
767fc8bd6b comment about pointer relase in sending state
Figured out, why it sometimes happened that the pointer is released in
sending state. However nothing we can do about it.

(not a real issue)
2024-01-08 16:54:14 +01:00
Ferdinand Schober
fa15048ad8 ignore every event except Enter in receiving mode (#65)
Modifier or other key events are still sent by some compositors after leaving the window which causes them to be pressed on both devices.
Now an Enter event must be produced before any further events are sent out (except disconnect)
2024-01-05 18:00:30 +01:00
Ferdinand Schober
eb366bcd34 Update README.md
fix typos
2024-01-05 17:23:36 +01:00
Ferdinand Schober
91176b1267 Update README.md
see #64
2024-01-05 17:14:03 +01:00
Ferdinand Schober
a6f386ea83 windows: impl key repeating (#63)
closes #47
2024-01-05 13:19:41 +01:00
Ferdinand Schober
40b0cdd52e Add security disclaimer 2024-01-03 16:52:56 +01:00
Ferdinand Schober
1553ed4212 Update README.md 2024-01-02 22:46:27 +01:00
8 changed files with 89 additions and 18 deletions

2
Cargo.lock generated
View File

@@ -1226,7 +1226,7 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "lan-mouse"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"anyhow",
"ashpd",

View File

@@ -1,7 +1,7 @@
[package]
name = "lan-mouse"
description = "Software KVM Switch / mouse & keyboard sharing software for Local Area Networks"
version = "0.5.0"
version = "0.5.1"
edition = "2021"
license = "GPL-3.0-or-later"
repository = "https://github.com/ferdinandschober/lan-mouse"

View File

@@ -14,7 +14,7 @@ The primary target is Wayland on Linux but Windows and MacOS and Linux on Xorg h
</picture>
Goal of this project is to be an open-source replacement for proprietary tools like [Synergy](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
Goal of this project is to be an open-source replacement for proprietary tools like [Synergy 2/3](https://symless.com/synergy), [Share Mouse](https://www.sharemouse.com/de/).
Focus lies on performance and a clean, manageable implementation that can easily be expanded to support additional backends like e.g. Android, iOS, ... .
@@ -22,6 +22,18 @@ Focus lies on performance and a clean, manageable implementation that can easily
For an alternative (with slightly different goals) you may check out [Input Leap](https://github.com/input-leap).
> [!WARNING]
> Since this tool has gained a bit of popularity over the past couple of days:
>
> All network traffic is currently **unencrypted** and sent in **plaintext**.
>
> A malicious actor with access to the network could read input data or send input events with spoofed IPs to take control over a device.
>
> Therefore you should only use this tool in your local network with trusted devices for now
> and I take no responsibility for any leakage of data!
## OS Support
The following table shows support for input emulation (to emulate events received from other clients) and
@@ -38,6 +50,10 @@ input capture (to send events *to* other clients) on different operating systems
Keycode translation is not yet implemented so on MacOS only mouse emulation works as of right now.
> [!Important]
> If you are using [Wayfire](https://github.com/WayfireWM/wayfire), make sure to use a recent version (must be newer than October 23rd) and **add `shortcuts-inhibit` to the list of plugins in your wayfire config!**
> Otherwise input capture will not work.
## Build and Run
### Install Dependencies
@@ -210,11 +226,12 @@ Where `left` can be either `left`, `right`, `top` or `bottom`.
- [x] respect xdg-config-home for config file location.
- [x] IP Address switching
- [x] Liveness tracking Automatically ungrab mouse when client unreachable
- [ ] Liveness tracking: Automatically release keys, when server offline
- [x] Liveness tracking: Automatically release keys, when server offline
- [ ] Libei Input Capture
- [ ] X11 Input Capture
- [ ] Windows Input Capture
- [ ] MacOS Input Capture
- [ ] MaxOS KeyCode Translation
- [ ] MacOS KeyCode Translation
- [ ] Latency measurement and visualization
- [ ] Bandwidth usage measurement and visualization
- [ ] Clipboard support

View File

@@ -10,4 +10,4 @@ Name=Lan Mouse
StartupNotify=true
Terminal=false
Type=Application
Version=0.5.0
Version=0.5.1

View File

@@ -5,6 +5,8 @@ use crate::{
};
use anyhow::Result;
use async_trait::async_trait;
use std::time::Duration;
use tokio::task::AbortHandle;
use winapi::um::winuser::{SendInput, KEYEVENTF_EXTENDEDKEY};
use winapi::{
self,
@@ -21,11 +23,16 @@ use crate::{
event::Event,
};
pub struct WindowsConsumer {}
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
pub struct WindowsConsumer {
repeat_task: Option<AbortHandle>,
}
impl WindowsConsumer {
pub fn new() -> Result<Self> {
Ok(Self {})
Ok(Self { repeat_task: None })
}
}
@@ -58,7 +65,15 @@ impl EventConsumer for WindowsConsumer {
time: _,
key,
state,
} => key_event(key, state),
} => {
match state {
// pressed
0 => self.kill_repeat_task(),
1 => self.spawn_repeat_task(key).await,
_ => {}
}
key_event(key, state)
}
KeyboardEvent::Modifiers { .. } => {}
},
_ => {}
@@ -72,6 +87,27 @@ impl EventConsumer for WindowsConsumer {
async fn destroy(&mut self) {}
}
impl WindowsConsumer {
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_mouse_input(mi: MOUSEINPUT) {
unsafe {
let mut input = INPUT {

View File

@@ -712,6 +712,13 @@ impl Dispatch<wl_pointer::WlPointer, ()> for State {
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)
*/
app.ungrab();
}
wl_pointer::Event::Button {

View File

@@ -11,7 +11,7 @@ pub const BTN_MIDDLE: u32 = 0x112;
pub const BTN_BACK: u32 = 0x113;
pub const BTN_FORWARD: u32 = 0x114;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PointerEvent {
Motion {
time: u32,
@@ -31,7 +31,7 @@ pub enum PointerEvent {
Frame {},
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum KeyboardEvent {
Key {
time: u32,
@@ -46,7 +46,7 @@ pub enum KeyboardEvent {
},
}
#[derive(Debug, Clone, Copy)]
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum Event {
/// pointer event (motion / button / axis)
Pointer(PointerEvent),

View File

@@ -131,7 +131,6 @@ impl Server {
tokio::select! {
event = producer.next() => {
let event = event.ok_or(anyhow!("event producer closed"))??;
log::debug!("producer event: {event:?}");
server.handle_producer_event(&mut producer, &sender_ch, &timer_ch, event).await?;
}
e = producer_notify_rx.recv() => {
@@ -721,6 +720,7 @@ impl Server {
}
}
State::Receiving => {
let mut ignore_event = false;
if let Event::Keyboard(KeyboardEvent::Key {
time: _,
key,
@@ -736,15 +736,21 @@ impl Server {
return;
};
if state == 0 {
client_state.pressed_keys.remove(&key);
// ignore release event if key not pressed
ignore_event = !client_state.pressed_keys.remove(&key);
} else {
client_state.pressed_keys.insert(key);
// ignore press event if key not released
ignore_event = !client_state.pressed_keys.insert(key);
let _ = timer_tx.try_send(());
}
}
// consume event
consumer.consume(event, handle).await;
log::trace!("{event:?} => consumer");
// ignore double press / release events to
// workaround buggy rdp backend.
if !ignore_event {
// consume event
consumer.consume(event, handle).await;
log::trace!("{event:?} => consumer");
}
}
State::AwaitingLeave => {
// we just entered the deadzone of a client, so
@@ -824,6 +830,11 @@ impl Server {
start_timer = true;
log::trace!("STATE ===> AwaitingLeave");
enter = true;
} else {
// ignore any potential events in receiving mode
if self.state.get() == State::Receiving && e != Event::Disconnect() {
return Ok(());
}
}
(client_state.active_addr, enter, start_timer)