mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-23 21:20:53 +03:00
Compare commits
9 Commits
x11-event-
...
v0.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c99f9bea3 | ||
|
|
d54b3a08e1 | ||
|
|
767fc8bd6b | ||
|
|
fa15048ad8 | ||
|
|
eb366bcd34 | ||
|
|
91176b1267 | ||
|
|
a6f386ea83 | ||
|
|
40b0cdd52e | ||
|
|
1553ed4212 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1226,7 +1226,7 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
||||
|
||||
[[package]]
|
||||
name = "lan-mouse"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
|
||||
@@ -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"
|
||||
|
||||
23
README.md
23
README.md
@@ -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
|
||||
|
||||
@@ -10,4 +10,4 @@ Name=Lan Mouse
|
||||
StartupNotify=true
|
||||
Terminal=false
|
||||
Type=Application
|
||||
Version=0.5.0
|
||||
Version=0.5.1
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user