diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
index 0641461..3818701 100644
--- a/.github/workflows/pre-release.yml
+++ b/.github/workflows/pre-release.yml
@@ -18,6 +18,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install libx11-dev libxtst-dev
+ sudo apt-get install libadwaita-1-dev libgtk-4-dev
- name: Release Build
run: cargo build --release
- name: Upload build artifact
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 3164a45..16a0fbb 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -20,6 +20,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install libx11-dev libxtst-dev
+ sudo apt-get install libadwaita-1-dev libgtk-4-dev
- name: Build
run: cargo build --verbose
- name: Run tests
diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml
index 5f50f5d..6d33472 100644
--- a/.github/workflows/tagged-release.yml
+++ b/.github/workflows/tagged-release.yml
@@ -14,6 +14,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install libx11-dev libxtst-dev
+ sudo apt-get install libadwaita-1-dev libgtk-4-dev
- name: Release Build
run: cargo build --release
- name: Upload build artifact
diff --git a/Cargo.lock b/Cargo.lock
index cc993e5..7e7be32 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -155,14 +155,14 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "enum-as-inner"
-version = "0.5.1"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116"
+checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn 1.0.109",
+ "syn 2.0.37",
]
[[package]]
@@ -423,6 +423,12 @@ dependencies = [
"thiserror",
]
+[[package]]
+name = "glib-build-tools"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3431c56f463443cba9bc3600248bc6d680cb614c2ee1cdd39dab5415bd12ac5c"
+
[[package]]
name = "glib-macros"
version = "0.18.2"
@@ -601,17 +607,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
-[[package]]
-name = "idna"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
-dependencies = [
- "matches",
- "unicode-bidi",
- "unicode-normalization",
-]
-
[[package]]
name = "idna"
version = "0.4.0"
@@ -684,6 +679,7 @@ version = "0.2.1"
dependencies = [
"anyhow",
"env_logger",
+ "glib-build-tools",
"gtk4",
"libadwaita",
"libc",
@@ -705,12 +701,6 @@ dependencies = [
"x11",
]
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
[[package]]
name = "libadwaita"
version = "0.5.2"
@@ -802,12 +792,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
-[[package]]
-name = "matches"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
-
[[package]]
name = "memchr"
version = "2.6.3"
@@ -1433,9 +1417,9 @@ dependencies = [
[[package]]
name = "trust-dns-proto"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26"
+checksum = "0dc775440033cb114085f6f2437682b194fa7546466024b1037e82a48a052a69"
dependencies = [
"async-trait",
"cfg-if",
@@ -1444,9 +1428,9 @@ dependencies = [
"futures-channel",
"futures-io",
"futures-util",
- "idna 0.2.3",
+ "idna",
"ipnet",
- "lazy_static",
+ "once_cell",
"rand",
"smallvec",
"thiserror",
@@ -1458,16 +1442,17 @@ dependencies = [
[[package]]
name = "trust-dns-resolver"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aff21aa4dcefb0a1afbfac26deb0adc93888c7d295fb63ab273ef276ba2b7cfe"
+checksum = "2dff7aed33ef3e8bf2c9966fccdfed93f93d46f432282ea875cd66faabc6ef2f"
dependencies = [
"cfg-if",
"futures-util",
"ipconfig",
- "lazy_static",
"lru-cache",
+ "once_cell",
"parking_lot",
+ "rand",
"resolv-conf",
"smallvec",
"thiserror",
@@ -1504,7 +1489,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
dependencies = [
"form_urlencoded",
- "idna 0.4.0",
+ "idna",
"percent-encoding",
]
diff --git a/Cargo.toml b/Cargo.toml
index 0a5c4da..b9f9aae 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,8 +13,8 @@ strip = true
lto = "fat"
[dependencies]
-tempfile = "3.6"
-trust-dns-resolver = "0.22"
+tempfile = "3.8"
+trust-dns-resolver = "0.23"
memmap = "0.7"
toml = "0.7"
serde = { version = "1.0", features = ["derive"] }
@@ -33,16 +33,30 @@ wayland-protocols-misc = { version="0.1.0", features=["client"], optional = true
wayland-protocols-plasma = { version="0.1.0", features=["client"], optional = true }
mio-signals = "0.2.0"
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
-gtk = { package = "gtk4", version = "0.7.2", features = ["v4_8"], optional = true }
-adw = { package = "libadwaita", version = "0.5.2", features = ["v1_3"], optional = true }
+gtk = { package = "gtk4", version = "0.7.2", features = ["v4_6"], optional = true }
+adw = { package = "libadwaita", version = "0.5.2", features = ["v1_1"], optional = true }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["winuser"] }
+[target.'cfg(unix)'.build-dependencies]
+glib-build-tools = "0.18.0"
+
[features]
-default = ["wayland", "x11", "xdg_desktop_portal", "libei"]
-wayland = ["dep:wayland-client", "dep:wayland-protocols", "dep:wayland-protocols-wlr", "dep:wayland-protocols-misc", "dep:wayland-protocols-plasma"]
-x11 = ["dep:x11"]
+default = [
+ "wayland",
+ "x11",
+ "xdg_desktop_portal",
+ "libei",
+ "gtk",
+]
+wayland = [
+ "dep:wayland-client",
+ "dep:wayland-protocols",
+ "dep:wayland-protocols-wlr",
+ "dep:wayland-protocols-misc",
+ "dep:wayland-protocols-plasma" ]
+x11 = [ "dep:x11" ]
xdg_desktop_portal = []
libei = []
gtk = ["dep:gtk", "dep:adw"]
diff --git a/README.md b/README.md
index 7eb3e6e..dafda15 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,7 @@
# Lan Mouse Share
+
+
+
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/).
Focus lies on performance and a clean, manageable implementation that can easily be expanded to support additional backends like e.g. Android, iOS, ... .
@@ -7,6 +10,8 @@ Of course ***blazingly fastâ„¢*** and stable, because it's written in rust.
For an alternative (with slightly different goals) you may check out [Input Leap](https://github.com/input-leap).
+_Now with a gtk frontend_
+
## Configuration
Configuration is done through the file `config.toml`,
which must be located in the current working directory when
@@ -16,32 +21,43 @@ executing lan-mouse.
A minimal config file could look like this:
```toml
+# example configuration
+
+# optional port (defaults to 4242)
+port = 4242
+# # optional frontend -> defaults to gtk if available
+# # possible values are "cli" and "gtk"
+# frontend = "gtk"
+
+# define a client on the right side with host name "iridium"
+[right]
+# hostname
+host_name = "iridium"
+# optional list of (known) ip addresses
+ips = ["192.168.178.156"]
+
+# define a client on the left side with IP address 192.168.178.189
[left]
-host_name = "my-laptop"
+# The hostname is optional: When no hostname is specified,
+# at least one ip address needs to be specified.
+host_name = "thorium"
+# ips for ethernet and wifi
+ips = ["192.168.178.189"]
```
Where `left` can be either `left`, `right`, `top` or `bottom`.
+> :warning: Note, that with the gtk frontend, the clients from the config
+> file are currently ignored.
-### Additional options
-Additionally
-- a preferred backend
-- a port override for the default port (4242)
-
-can be specified.
-
-Supported backends currently include "wlroots", "x11" and "windows".
-
-These two options can also be specified via the commandline
-options `--backend` and `--port` respectively.
## Build and Run
-Build only
+Build in release mode:
```sh
cargo build --release
```
-Run
+Run directly:
```sh
cargo run --release
```
@@ -133,12 +149,8 @@ This is to be looked into in the future.
(this works natively on sway versions >= 1.8)
## Windows support
-Currently windows can receive mouse and keyboard events, however unlike
-with the wlroots back-end,
-
-the scancodes are not translated between keyboard layouts.
-
-Event emitting is WIP.
+Currently windows can receive mouse and keyboard events,
+event producing on windows is WIP.
## TODOS
@@ -151,11 +163,11 @@ Event emitting is WIP.
- [x] Button support
- [ ] Latency measurement + logging
- [ ] Bandwidth usage approximation + logging
-- [ ] Multiple IP addresses -> check which one is reachable
+- [x] Multiple IP addresses -> check which one is reachable
- [x] Merge server and client -> Both client and server can send and receive events depending on what mouse is used where
-- [ ] Liveness tracking (automatically ungrab mouse when client unreachable)
+- [x] Liveness tracking (automatically ungrab mouse when client unreachable)
- [ ] Clipboard support
-- [ ] Graphical frontend (gtk?)
+- [x] Graphical frontend (gtk?)
- [ ] *Encrytion*
- [ ] Gnome Shell Extension (layer shell is not supported)
- [ ] respect xdg-config-home for config file location.
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..03bb139
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,9 @@
+fn main() {
+ // composite_templates
+ #[cfg(unix)]
+ glib_build_tools::compile_resources(
+ &["resources"],
+ "resources/resources.gresource.xml",
+ "lan-mouse.gresource",
+ );
+}
diff --git a/config.toml b/config.toml
index b327b82..82b19fd 100644
--- a/config.toml
+++ b/config.toml
@@ -2,19 +2,20 @@
# optional port (defaults to 4242)
port = 4242
+# optional frontend -> defaults to gtk if available
+# frontend = "gtk"
# define a client on the right side with host name "iridium"
[right]
# hostname
host_name = "iridium"
# optional list of (known) ip addresses
-ips = ["192.168.178.141"]
-# optional port (defaults to 4242)
-port = 4242
+ips = ["192.168.178.156"]
# define a client on the left side with IP address 192.168.178.189
[left]
# The hostname is optional: When no hostname is specified,
# at least one ip address needs to be specified.
host_name = "thorium"
-ips = ["192.168.178.189"]
+# ips for ethernet and wifi
+ips = ["192.168.178.189", "192.168.178.172"]
diff --git a/resources/client_row.ui b/resources/client_row.ui
new file mode 100644
index 0000000..226fbbe
--- /dev/null
+++ b/resources/client_row.ui
@@ -0,0 +1,74 @@
+
+
+
+ hostname
+
+
+
+
+
+
+
+
+
+
+
+ position
+
+
+
+ - Left
+ - Right
+ - Top
+ - Bottom
+
+
+
+
+
+
+
+
+ delete this client
+
+
+
+ user-trash-symbolic
+ center
+ center
+ delete-button
+
+
+
+
+
+
diff --git a/resources/mouse-icon.svg b/resources/mouse-icon.svg
new file mode 100644
index 0000000..ff8e237
--- /dev/null
+++ b/resources/mouse-icon.svg
@@ -0,0 +1,171 @@
+
+
+
+
diff --git a/resources/resources.gresource.xml b/resources/resources.gresource.xml
new file mode 100644
index 0000000..ae6696b
--- /dev/null
+++ b/resources/resources.gresource.xml
@@ -0,0 +1,12 @@
+
+
+
+ window.ui
+ client_row.ui
+ style.css
+ style-dark.css
+
+
+ mouse-icon.svg
+
+
diff --git a/resources/style-dark.css b/resources/style-dark.css
new file mode 100644
index 0000000..df5c5c3
--- /dev/null
+++ b/resources/style-dark.css
@@ -0,0 +1,3 @@
+#delete-button {
+ color: @red_1;
+}
diff --git a/resources/style.css b/resources/style.css
new file mode 100644
index 0000000..0d2d462
--- /dev/null
+++ b/resources/style.css
@@ -0,0 +1,3 @@
+#delete-button {
+ color: @red_3;
+}
diff --git a/resources/window.ui b/resources/window.ui
new file mode 100644
index 0000000..84198bd
--- /dev/null
+++ b/resources/window.ui
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+ 800
+ Lan Mouse
+ True
+
+
+
+
+
+ Lan Mouse
+ easily use your mouse and keyboard on multiple computers
+ mouse-icon
+
+
+ 600
+ 300
+
+
+ vertical
+ 12
+
+
+ General
+
+
+ enable
+
+
+ center
+ enable
+
+
+
+
+
+
+ port
+
+
+
+ 4242
+ 5
+ 0.5
+ center
+
+ GTK_INPUT_PURPOSE_DIGITS
+
+
+
+
+
+
+
+
+ Connections
+
+
+ none
+
+
+ No connections!
+ add a new client via the button below
+
+
+
+
+
+
+
+
+
+
+
+ center
+ center
+ connect a new computer
+
+
+ list-add-symbolic
+ Add
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/backend/producer/wayland.rs b/src/backend/producer/wayland.rs
index d76a474..3768850 100644
--- a/src/backend/producer/wayland.rs
+++ b/src/backend/producer/wayland.rs
@@ -129,6 +129,15 @@ impl Window {
}
}
+impl Drop for Window {
+ fn drop(&mut self) {
+ log::debug!("destroying window!");
+ self.layer_surface.destroy();
+ self.surface.destroy();
+ self.buffer.destroy();
+ }
+}
+
fn draw(f: &mut File, (width, height): (u32, u32)) {
let mut buf = BufWriter::new(f);
for _ in 0..height {
@@ -301,6 +310,7 @@ impl State {
fn add_client(&mut self, client: ClientHandle, pos: Position) {
let window = Rc::new(Window::new(&self.g, &self.qh, pos));
+ assert!(Rc::strong_count(&window) == 1);
self.client_for_window.push((window, client));
}
}
@@ -414,8 +424,15 @@ impl EventProducer for WaylandEventProducer {
fn notify(&mut self, client_event: ClientEvent) {
if let ClientEvent::Create(handle, pos) = client_event {
self.state.add_client(handle, pos);
- self.flush_events();
}
+ if let ClientEvent::Destroy(handle) = client_event {
+ if let Some(i) = self.state.client_for_window.iter().position(|(_,c)| *c == handle) {
+ let w = self.state.client_for_window.remove(i);
+ self.state.focused = None;
+ assert!(Rc::strong_count(&w.0) == 1);
+ }
+ }
+ self.flush_events();
}
fn release(&mut self) {
@@ -466,15 +483,16 @@ impl Dispatch for State {
} => {
// get client corresponding to the focused surface
log::trace!("produce: enter()");
-
{
- let (window, client) = app
+ if let Some((window, client)) = app
.client_for_window
.iter()
- .find(|(w, _c)| w.surface == surface)
- .unwrap();
- app.focused = Some((window.clone(), *client));
- app.grab(&surface, pointer, serial.clone(), qh);
+ .find(|(w, _c)| w.surface == surface) {
+ app.focused = Some((window.clone(), *client));
+ app.grab(&surface, pointer, serial.clone(), qh);
+ } else {
+ return;
+ }
}
let (_, client) = app
.client_for_window
@@ -641,17 +659,17 @@ impl Dispatch for State {
_: &QueueHandle,
) {
if let zwlr_layer_surface_v1::Event::Configure { serial, .. } = event {
- let (window, _client) = app
+ if let Some((window, _client)) = app
.client_for_window
.iter()
- .find(|(w, _c)| &w.layer_surface == layer_surface)
- .unwrap();
- // client corresponding to the layer_surface
- let surface = &window.surface;
- let buffer = &window.buffer;
- surface.attach(Some(&buffer), 0, 0);
- layer_surface.ack_configure(serial);
- surface.commit();
+ .find(|(w, _c)| &w.layer_surface == layer_surface) {
+ // client corresponding to the layer_surface
+ let surface = &window.surface;
+ let buffer = &window.buffer;
+ surface.attach(Some(&buffer), 0, 0);
+ layer_surface.ack_configure(serial);
+ surface.commit();
+ }
}
}
}
diff --git a/src/client.rs b/src/client.rs
index ef619e9..b0c9738 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -121,46 +121,46 @@ impl ClientManager {
pub fn last_ping(&self, client: ClientHandle) -> Option {
let last_ping = self.last_ping
.iter()
- .find(|(c,_)| *c == client)
- .unwrap().1;
+ .find(|(c,_)| *c == client)?.1;
last_ping.map(|p| p.elapsed())
}
pub fn last_seen(&self, client: ClientHandle) -> Option {
let last_seen = self.last_seen
.iter()
- .find(|(c, _)| *c == client)
- .unwrap().1;
+ .find(|(c, _)| *c == client)?.1;
last_seen.map(|t| t.elapsed())
}
pub fn last_replied(&self, client: ClientHandle) -> Option {
let last_replied = self.last_replied
.iter()
- .find(|(c, _)| *c == client)
- .unwrap().1;
+ .find(|(c, _)| *c == client)?.1;
last_replied.map(|t| t.elapsed())
}
pub fn reset_last_ping(&mut self, client: ClientHandle) {
- self.last_ping
+ if let Some(c) = self.last_ping
.iter_mut()
- .find(|(c, _)| *c == client)
- .unwrap().1 = Some(Instant::now());
+ .find(|(c, _)| *c == client) {
+ c.1 = Some(Instant::now());
+ }
}
pub fn reset_last_seen(&mut self, client: ClientHandle) {
- self.last_seen
+ if let Some(c) = self.last_seen
.iter_mut()
- .find(|(c, _)| *c == client)
- .unwrap().1 = Some(Instant::now());
+ .find(|(c, _)| *c == client) {
+ c.1 = Some(Instant::now());
+ }
}
pub fn reset_last_replied(&mut self, client: ClientHandle) {
- self.last_replied
+ if let Some(c) = self.last_replied
.iter_mut()
- .find(|(c, _)| *c == client)
- .unwrap().1 = Some(Instant::now());
+ .find(|(c, _)| *c == client) {
+ c.1 = Some(Instant::now());
+ }
}
pub fn get_client(&self, addr: SocketAddr) -> Option {
@@ -170,6 +170,15 @@ impl ClientManager {
.map(|c| c.handle)
}
+ pub fn remove_client(&mut self, client: ClientHandle) {
+ if let Some(i) = self.clients.iter().position(|c| c.handle == client) {
+ self.clients.remove(i);
+ self.last_ping.remove(i);
+ self.last_seen.remove(i);
+ self.last_replied.remove(i);
+ }
+ }
+
fn next_id(&mut self) -> ClientHandle {
let handle = self.next_client_id;
self.next_client_id += 1;
diff --git a/src/config.rs b/src/config.rs
index 219e060..fef7966 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -64,6 +64,7 @@ fn find_arg(key: &'static str) -> Result