mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-13 00:00:53 +03:00
Compare commits
11 Commits
macos-inpu
...
fix-scroll
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b03c7d8bd5 | ||
|
|
0dd413e989 | ||
|
|
4e5a66340a | ||
|
|
0a0d91b0da | ||
|
|
94e6372218 | ||
|
|
39b79d88a5 | ||
|
|
5ad90ca6a5 | ||
|
|
68df27ab2c | ||
|
|
eb1dcbddb0 | ||
|
|
9f10ebcbd2 | ||
|
|
e29eb7134c |
8
.github/workflows/pre-release.yml
vendored
8
.github/workflows/pre-release.yml
vendored
@@ -94,7 +94,9 @@ jobs:
|
|||||||
- name: Install cargo bundle
|
- name: Install cargo bundle
|
||||||
run: cargo install cargo-bundle
|
run: cargo install cargo-bundle
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: cargo bundle --release
|
run: |
|
||||||
|
cargo bundle --release
|
||||||
|
scripts/copy-macos-dylib.sh "target/release/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse"
|
||||||
- name: Zip bundle
|
- name: Zip bundle
|
||||||
run: |
|
run: |
|
||||||
cd target/release/bundle/osx
|
cd target/release/bundle/osx
|
||||||
@@ -120,7 +122,9 @@ jobs:
|
|||||||
- name: Install cargo bundle
|
- name: Install cargo bundle
|
||||||
run: cargo install cargo-bundle
|
run: cargo install cargo-bundle
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: cargo bundle --release
|
run: |
|
||||||
|
cargo bundle --release
|
||||||
|
scripts/copy-macos-dylib.sh "target/release/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse"
|
||||||
- name: Zip bundle
|
- name: Zip bundle
|
||||||
run: |
|
run: |
|
||||||
cd target/release/bundle/osx
|
cd target/release/bundle/osx
|
||||||
|
|||||||
8
.github/workflows/rust.yml
vendored
8
.github/workflows/rust.yml
vendored
@@ -112,7 +112,9 @@ jobs:
|
|||||||
- name: Install cargo bundle
|
- name: Install cargo bundle
|
||||||
run: cargo install cargo-bundle
|
run: cargo install cargo-bundle
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: cargo bundle
|
run: |
|
||||||
|
cargo bundle
|
||||||
|
scripts/copy-macos-dylib.sh
|
||||||
- name: Zip bundle
|
- name: Zip bundle
|
||||||
run: |
|
run: |
|
||||||
cd target/debug/bundle/osx
|
cd target/debug/bundle/osx
|
||||||
@@ -142,7 +144,9 @@ jobs:
|
|||||||
- name: Install cargo bundle
|
- name: Install cargo bundle
|
||||||
run: cargo install cargo-bundle
|
run: cargo install cargo-bundle
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: cargo bundle
|
run: |
|
||||||
|
cargo bundle
|
||||||
|
scripts/copy-macos-dylib.sh
|
||||||
- name: Zip bundle
|
- name: Zip bundle
|
||||||
run: |
|
run: |
|
||||||
cd target/debug/bundle/osx
|
cd target/debug/bundle/osx
|
||||||
|
|||||||
8
.github/workflows/tagged-release.yml
vendored
8
.github/workflows/tagged-release.yml
vendored
@@ -90,7 +90,9 @@ jobs:
|
|||||||
- name: Install cargo bundle
|
- name: Install cargo bundle
|
||||||
run: cargo install cargo-bundle
|
run: cargo install cargo-bundle
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: cargo bundle --release
|
run: |
|
||||||
|
cargo bundle --release
|
||||||
|
scripts/copy-macos-dylib.sh "target/release/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse"
|
||||||
- name: Zip bundle
|
- name: Zip bundle
|
||||||
run: |
|
run: |
|
||||||
cd target/release/bundle/osx
|
cd target/release/bundle/osx
|
||||||
@@ -116,7 +118,9 @@ jobs:
|
|||||||
- name: Install cargo bundle
|
- name: Install cargo bundle
|
||||||
run: cargo install cargo-bundle
|
run: cargo install cargo-bundle
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: cargo bundle --release
|
run: |
|
||||||
|
cargo bundle --release
|
||||||
|
scripts/copy-macos-dylib.sh "target/release/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse"
|
||||||
- name: Zip bundle
|
- name: Zip bundle
|
||||||
run: |
|
run: |
|
||||||
cd target/release/bundle/osx
|
cd target/release/bundle/osx
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -8,3 +8,7 @@ result
|
|||||||
*.pem
|
*.pem
|
||||||
*.csr
|
*.csr
|
||||||
extfile.conf
|
extfile.conf
|
||||||
|
|
||||||
|
# flatpak files
|
||||||
|
.flatpak-builder
|
||||||
|
repo
|
||||||
|
|||||||
2055
Cargo.lock
generated
2055
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -24,7 +24,7 @@ strip = true
|
|||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
shadow-rs = "0.38.0"
|
shadow-rs = "1.2.0"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
input-event = { path = "input-event", version = "0.3.0" }
|
input-event = { path = "input-event", version = "0.3.0" }
|
||||||
@@ -34,9 +34,9 @@ lan-mouse-cli = { path = "lan-mouse-cli", version = "0.2.0" }
|
|||||||
lan-mouse-gtk = { path = "lan-mouse-gtk", version = "0.2.0", optional = true }
|
lan-mouse-gtk = { path = "lan-mouse-gtk", version = "0.2.0", optional = true }
|
||||||
lan-mouse-ipc = { path = "lan-mouse-ipc", version = "0.2.0" }
|
lan-mouse-ipc = { path = "lan-mouse-ipc", version = "0.2.0" }
|
||||||
lan-mouse-proto = { path = "lan-mouse-proto", version = "0.2.0" }
|
lan-mouse-proto = { path = "lan-mouse-proto", version = "0.2.0" }
|
||||||
shadow-rs = { version = "0.38.0", features = ["metadata"] }
|
shadow-rs = { version = "1.2.0", features = ["metadata"] }
|
||||||
|
|
||||||
hickory-resolver = "0.24.1"
|
hickory-resolver = "0.25.2"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
@@ -58,8 +58,8 @@ slab = "0.4.9"
|
|||||||
thiserror = "2.0.0"
|
thiserror = "2.0.0"
|
||||||
tokio-util = "0.7.11"
|
tokio-util = "0.7.11"
|
||||||
local-channel = "0.1.5"
|
local-channel = "0.1.5"
|
||||||
webrtc-dtls = { version = "0.10.0", features = ["pem"] }
|
webrtc-dtls = { version = "0.12.0", features = ["pem"] }
|
||||||
webrtc-util = "0.9.0"
|
webrtc-util = "0.11.0"
|
||||||
rustls = { version = "0.23.12", default-features = false, features = [
|
rustls = { version = "0.23.12", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"ring",
|
"ring",
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -81,15 +81,37 @@ paru -S lan-mouse-git
|
|||||||
- flake: [README.md](./nix/README.md)
|
- flake: [README.md](./nix/README.md)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Fedora</summary>
|
||||||
|
You can install Lan Mouse from the [Terra Repository](https://terra.fyralabs.com).
|
||||||
|
|
||||||
|
|
||||||
|
After enabling Terra:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dnf install lan-mouse
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>MacOS</summary>
|
||||||
|
|
||||||
|
- Download the package for your Mac (Intel or ARM) from the releases page
|
||||||
|
- Unzip it
|
||||||
|
- Remove the quarantine with `xattr -rd com.apple.quarantine "Lan Mouse.app"`
|
||||||
|
- Launch the app
|
||||||
|
- Grant accessibility permissions in System Preferences
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Manual Installation</summary>
|
<summary>Manual Installation</summary>
|
||||||
|
|
||||||
First make sure to [install the necessary dependencies](#installing-dependencies).
|
First make sure to [install the necessary dependencies](#installing-dependencies-for-development--compiling-from-source).
|
||||||
|
|
||||||
Precompiled release binaries for Windows, MacOS and Linux are available in the [releases section](https://github.com/feschber/lan-mouse/releases).
|
Precompiled release binaries for Windows, MacOS and Linux are available in the [releases section](https://github.com/feschber/lan-mouse/releases).
|
||||||
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-for-development--compiling-from-source).
|
||||||
|
|
||||||
Alternatively, the `lan-mouse` binary can be compiled from source (see below).
|
Alternatively, the `lan-mouse` binary can be compiled from source (see below).
|
||||||
|
|
||||||
@@ -161,7 +183,15 @@ For a detailed list of available features, checkout the [Cargo.toml](./Cargo.tom
|
|||||||
<summary>MacOS</summary>
|
<summary>MacOS</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew install libadwaita pkg-config
|
# Install dependencies
|
||||||
|
brew install libadwaita pkg-config imagemagick
|
||||||
|
cargo install cargo-bundle
|
||||||
|
# Create the macOS icon file
|
||||||
|
scripts/makeicns.sh
|
||||||
|
# Create the .app bundle
|
||||||
|
cargo bundle
|
||||||
|
# Copy all dynamic libraries into the bundle, and update the bundle to find them there
|
||||||
|
scripts/copy-macos-dylib.sh
|
||||||
```
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
50
build-aux/de.feschber.LanMouse.yml
Normal file
50
build-aux/de.feschber.LanMouse.yml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/flatpak/flatpak-builder/refs/heads/main/data/flatpak-manifest.schema.json
|
||||||
|
app-id: de.feschber.LanMouse
|
||||||
|
runtime: org.gnome.Platform
|
||||||
|
runtime-version: "48"
|
||||||
|
sdk: org.gnome.Sdk
|
||||||
|
sdk-extensions:
|
||||||
|
- org.freedesktop.Sdk.Extension.rust-stable
|
||||||
|
- org.freedesktop.Sdk.Extension.llvm20
|
||||||
|
command: /app/bin/lan-mouse
|
||||||
|
build-options:
|
||||||
|
append-path: "/usr/lib/sdk/rust-stable/bin:/usr/lib/sdk/llvm20/bin"
|
||||||
|
env:
|
||||||
|
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER": "clang"
|
||||||
|
"CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold"
|
||||||
|
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER": "clang"
|
||||||
|
"CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUSTFLAGS": "-C link-arg=-fuse-ld=/usr/lib/sdk/rust-stable/bin/mold"
|
||||||
|
build-args:
|
||||||
|
"--share=network"
|
||||||
|
prepend-ld-library-path:
|
||||||
|
"/usr/lib/sdk/llvm19/lib"
|
||||||
|
|
||||||
|
finish-args:
|
||||||
|
- "--socket=wayland"
|
||||||
|
- "--socket=fallback-x11"
|
||||||
|
- "--device=dri"
|
||||||
|
- "--socket=session-bus"
|
||||||
|
- "--share=network"
|
||||||
|
- "--filesystem=xdg-config"
|
||||||
|
- "--env=RUST_BACKTRACE=1"
|
||||||
|
- "--env=RUST_LOG=lan-mouse=debug"
|
||||||
|
- "--env=GTK_PATH=/app/lib/gtk-4.0"
|
||||||
|
|
||||||
|
modules:
|
||||||
|
- name: lan-mouse
|
||||||
|
buildsystem: simple
|
||||||
|
build-options:
|
||||||
|
build-args:
|
||||||
|
- "--share=network"
|
||||||
|
append-path: /usr/lib/sdk/rust-stable/bin
|
||||||
|
env:
|
||||||
|
CARGO_HOME: /run/build/lan-mouse/cargo
|
||||||
|
build-commands:
|
||||||
|
- cargo fetch --manifest-path Cargo.toml --verbose
|
||||||
|
- cargo build
|
||||||
|
- install -Dm0755 target/debug/lan-mouse /app/bin/lan-mouse
|
||||||
|
- install -Dm0644 lan-mouse-gtk/resources/de.feschber.LanMouse.svg ${FLATPAK_DEST}/share/icons/hicolor/scalable/apps/${FLATPAK_ID}.svg
|
||||||
|
- install -Dm0644 de.feschber.LanMouse.desktop ${FLATPAK_DEST}/share/applications/${FLATPAK_ID}.desktop
|
||||||
|
sources:
|
||||||
|
- type: dir
|
||||||
|
path: ..
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
app-id: de.feschber.LanMouse
|
|
||||||
runtime: org.freedesktop.Platform
|
|
||||||
runtime-version: '22.08'
|
|
||||||
sdk: org.freedesktop.Sdk
|
|
||||||
command: target/release/lan-mouse
|
|
||||||
modules:
|
|
||||||
- name: hello
|
|
||||||
buildsystem: simple
|
|
||||||
build-commands:
|
|
||||||
- cargo build --release
|
|
||||||
- install -D lan-mouse /app/bin/lan-mouse
|
|
||||||
sources:
|
|
||||||
- type: file
|
|
||||||
path: target/release/lan-mouse
|
|
||||||
1
dylibs/.gitignore
vendored
Normal file
1
dylibs/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*
|
||||||
12
flake.lock
generated
12
flake.lock
generated
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1740560979,
|
"lastModified": 1752687322,
|
||||||
"narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=",
|
"narHash": "sha256-RKwfXA4OZROjBTQAl9WOZQFm7L8Bo93FQwSJpAiSRvo=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5135c59491985879812717f4c9fea69604e7f26f",
|
"rev": "6e987485eb2c77e5dcc5af4e3c70843711ef9251",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -29,11 +29,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1740623427,
|
"lastModified": 1752806774,
|
||||||
"narHash": "sha256-3SdPQrZoa4odlScFDUHd4CUPQ/R1gtH4Mq9u8CBiK8M=",
|
"narHash": "sha256-4cHeoR2roN7d/3J6gT+l6o7J2hTrBIUiCwVdDNMeXzE=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "d342e8b5fd88421ff982f383c853f0fc78a847ab",
|
"rev": "3c90219b3ba1c9790c45a078eae121de48a39c55",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -40,21 +40,21 @@ wayland-protocols-wlr = { version = "0.3.1", features = [
|
|||||||
"client",
|
"client",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
||||||
ashpd = { version = "0.10", default-features = false, features = [
|
ashpd = { version = "0.11.0", default-features = false, features = [
|
||||||
"tokio",
|
"tokio",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
reis = { version = "0.4", features = ["tokio"], optional = true }
|
reis = { version = "0.5.0", features = ["tokio"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os="macos")'.dependencies]
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
core-graphics = { version = "0.24.0", features = ["highsierra"] }
|
core-graphics = { version = "0.25.0", features = ["highsierra"] }
|
||||||
core-foundation = "0.10.0"
|
core-foundation = "0.10.0"
|
||||||
core-foundation-sys = "0.8.6"
|
core-foundation-sys = "0.8.6"
|
||||||
libc = "0.2.155"
|
libc = "0.2.155"
|
||||||
keycode = "0.4.0"
|
keycode = "1.0.0"
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.58.0", features = [
|
windows = { version = "0.61.2", features = [
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ impl State {
|
|||||||
fn update_windows(&mut self) {
|
fn update_windows(&mut self) {
|
||||||
log::info!("active outputs: ");
|
log::info!("active outputs: ");
|
||||||
for output in self.outputs.iter().filter(|o| o.info.is_some()) {
|
for output in self.outputs.iter().filter(|o| o.info.is_some()) {
|
||||||
log::info!(" * {}", output);
|
log::info!(" * {output}");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.active_windows.clear();
|
self.active_windows.clear();
|
||||||
@@ -582,17 +582,17 @@ impl Inner {
|
|||||||
match self.queue.dispatch_pending(&mut self.state) {
|
match self.queue.dispatch_pending(&mut self.state) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(DispatchError::Backend(WaylandError::Io(e))) => {
|
Err(DispatchError::Backend(WaylandError::Io(e))) => {
|
||||||
log::error!("Wayland Error: {}", e);
|
log::error!("Wayland Error: {e}");
|
||||||
}
|
}
|
||||||
Err(DispatchError::Backend(e)) => {
|
Err(DispatchError::Backend(e)) => {
|
||||||
panic!("backend error: {}", e);
|
panic!("backend error: {e}");
|
||||||
}
|
}
|
||||||
Err(DispatchError::BadMessage {
|
Err(DispatchError::BadMessage {
|
||||||
sender_id,
|
sender_id,
|
||||||
interface,
|
interface,
|
||||||
opcode,
|
opcode,
|
||||||
}) => {
|
}) => {
|
||||||
panic!("bad message {}, {} , {}", sender_id, interface, opcode);
|
panic!("bad message {sender_id}, {interface} , {opcode}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,7 +974,7 @@ impl Dispatch<ZxdgOutputV1, u32> for State {
|
|||||||
.find(|o| o.global.name == *name)
|
.find(|o| o.global.name == *name)
|
||||||
.expect("output");
|
.expect("output");
|
||||||
|
|
||||||
log::debug!("xdg_output {name} - {:?}", event);
|
log::debug!("xdg_output {name} - {event:?}");
|
||||||
match event {
|
match event {
|
||||||
zxdg_output_v1::Event::LogicalPosition { x, y } => {
|
zxdg_output_v1::Event::LogicalPosition { x, y } => {
|
||||||
output.pending_info.position = (x, y);
|
output.pending_info.position = (x, y);
|
||||||
@@ -1010,7 +1010,7 @@ impl Dispatch<WlOutput, u32> for State {
|
|||||||
_conn: &Connection,
|
_conn: &Connection,
|
||||||
_qhandle: &QueueHandle<Self>,
|
_qhandle: &QueueHandle<Self>,
|
||||||
) {
|
) {
|
||||||
log::debug!("wl_output {name} - {:?}", event);
|
log::debug!("wl_output {name} - {event:?}");
|
||||||
if let wl_output::Event::Done = event {
|
if let wl_output::Event::Done = event {
|
||||||
state.update_output_info(*name);
|
state.update_output_info(*name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ impl Display for Position {
|
|||||||
Position::Top => "top",
|
Position::Top => "top",
|
||||||
Position::Bottom => "bottom",
|
Position::Bottom => "bottom",
|
||||||
};
|
};
|
||||||
write!(f, "{}", pos)
|
write!(f, "{pos}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -587,9 +587,13 @@ impl LanMouseInputCapture for LibeiInputCapture<'_> {
|
|||||||
self.cancellation_token.cancel();
|
self.cancellation_token.cancel();
|
||||||
let task = &mut self.capture_task;
|
let task = &mut self.capture_task;
|
||||||
log::debug!("waiting for capture to terminate...");
|
log::debug!("waiting for capture to terminate...");
|
||||||
let res = task.await.expect("libei task panic");
|
let res = if !task.is_finished() {
|
||||||
log::debug!("done!");
|
task.await.expect("libei task panic")
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
self.terminated = true;
|
self.terminated = true;
|
||||||
|
log::debug!("done!");
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use core_graphics::base::{kCGErrorSuccess, CGError};
|
|||||||
use core_graphics::display::{CGDisplay, CGPoint};
|
use core_graphics::display::{CGDisplay, CGPoint};
|
||||||
use core_graphics::event::{
|
use core_graphics::event::{
|
||||||
CGEvent, CGEventFlags, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement,
|
CGEvent, CGEventFlags, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement,
|
||||||
CGEventTapProxy, CGEventType, EventField,
|
CGEventTapProxy, CGEventType, CallbackResult, EventField,
|
||||||
};
|
};
|
||||||
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
use core_graphics::event_source::{CGEventSource, CGEventSourceStateID};
|
||||||
use futures_core::Stream;
|
use futures_core::Stream;
|
||||||
@@ -390,15 +390,15 @@ fn create_event_tap<'a>(
|
|||||||
|
|
||||||
if let Some(pos) = pos {
|
if let Some(pos) = pos {
|
||||||
res_events.iter().for_each(|e| {
|
res_events.iter().for_each(|e| {
|
||||||
event_tx
|
// error must be ignored, since the event channel
|
||||||
.blocking_send((pos, *e))
|
// may already be closed when the InputCapture instance is dropped.
|
||||||
.expect("Failed to send event");
|
let _ = event_tx.blocking_send((pos, *e));
|
||||||
});
|
});
|
||||||
// Returning None should stop the event from being processed
|
// Returning Drop should stop the event from being processed
|
||||||
// but core fundation still returns the event
|
// but core fundation still returns the event
|
||||||
cg_ev.set_type(CGEventType::Null);
|
cg_ev.set_type(CGEventType::Null);
|
||||||
}
|
}
|
||||||
Some(cg_ev.to_owned())
|
CallbackResult::Replace(cg_ev.to_owned())
|
||||||
};
|
};
|
||||||
|
|
||||||
let tap = CGEventTap::new(
|
let tap = CGEventTap::new(
|
||||||
@@ -411,7 +411,7 @@ fn create_event_tap<'a>(
|
|||||||
.map_err(|_| MacosCaptureCreationError::EventTapCreation)?;
|
.map_err(|_| MacosCaptureCreationError::EventTapCreation)?;
|
||||||
|
|
||||||
let tap_source: CFRunLoopSource = tap
|
let tap_source: CFRunLoopSource = tap
|
||||||
.mach_port
|
.mach_port()
|
||||||
.create_runloop_source(0)
|
.create_runloop_source(0)
|
||||||
.expect("Failed creating loop source");
|
.expect("Failed creating loop source");
|
||||||
|
|
||||||
@@ -426,8 +426,8 @@ fn event_tap_thread(
|
|||||||
client_state: Arc<Mutex<InputCaptureState>>,
|
client_state: Arc<Mutex<InputCaptureState>>,
|
||||||
event_tx: Sender<(Position, CaptureEvent)>,
|
event_tx: Sender<(Position, CaptureEvent)>,
|
||||||
notify_tx: Sender<ProducerEvent>,
|
notify_tx: Sender<ProducerEvent>,
|
||||||
ready: std::sync::mpsc::Sender<Result<(), MacosCaptureCreationError>>,
|
ready: std::sync::mpsc::Sender<Result<CFRunLoop, MacosCaptureCreationError>>,
|
||||||
exit: oneshot::Sender<Result<(), &'static str>>,
|
exit: oneshot::Sender<()>,
|
||||||
) {
|
) {
|
||||||
let _tap = match create_event_tap(client_state, notify_tx, event_tx) {
|
let _tap = match create_event_tap(client_state, notify_tx, event_tx) {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -435,18 +435,22 @@ fn event_tap_thread(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(tap) => {
|
Ok(tap) => {
|
||||||
ready.send(Ok(())).expect("channel closed");
|
let run_loop = CFRunLoop::get_current();
|
||||||
|
ready.send(Ok(run_loop)).expect("channel closed");
|
||||||
tap
|
tap
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
log::debug!("running CFRunLoop...");
|
||||||
CFRunLoop::run_current();
|
CFRunLoop::run_current();
|
||||||
|
log::debug!("event tap thread exiting!...");
|
||||||
|
|
||||||
let _ = exit.send(Err("tap thread exited"));
|
let _ = exit.send(());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MacOSInputCapture {
|
pub struct MacOSInputCapture {
|
||||||
event_rx: Receiver<(Position, CaptureEvent)>,
|
event_rx: Receiver<(Position, CaptureEvent)>,
|
||||||
notify_tx: Sender<ProducerEvent>,
|
notify_tx: Sender<ProducerEvent>,
|
||||||
|
run_loop: CFRunLoop,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MacOSInputCapture {
|
impl MacOSInputCapture {
|
||||||
@@ -475,36 +479,44 @@ impl MacOSInputCapture {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// wait for event tap creation result
|
// wait for event tap creation result
|
||||||
ready_rx.recv().expect("channel closed")?;
|
let run_loop = ready_rx.recv().expect("channel closed")?;
|
||||||
|
|
||||||
let _tap_task: tokio::task::JoinHandle<()> = tokio::task::spawn_local(async move {
|
let _tap_task: tokio::task::JoinHandle<()> = tokio::task::spawn_local(async move {
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
producer_event = notify_rx.recv() => {
|
producer_event = notify_rx.recv() => {
|
||||||
let producer_event = producer_event.expect("channel closed");
|
let Some(producer_event) = producer_event else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
let mut state = state.lock().await;
|
let mut state = state.lock().await;
|
||||||
state.handle_producer_event(producer_event).await.unwrap_or_else(|e| {
|
state.handle_producer_event(producer_event).await.unwrap_or_else(|e| {
|
||||||
log::error!("Failed to handle producer event: {e}");
|
log::error!("Failed to handle producer event: {e}");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
res = &mut tap_exit_rx => {
|
_ = &mut tap_exit_rx => {
|
||||||
if let Err(e) = res.expect("channel closed") {
|
break;
|
||||||
log::error!("Tap thread failed: {:?}", e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// show cursor
|
||||||
|
let _ = CGDisplay::show_cursor(&CGDisplay::main());
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
event_rx,
|
event_rx,
|
||||||
notify_tx,
|
notify_tx,
|
||||||
|
run_loop,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for MacOSInputCapture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.run_loop.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Capture for MacOSInputCapture {
|
impl Capture for MacOSInputCapture {
|
||||||
async fn create(&mut self, pos: Position) -> Result<(), CaptureError> {
|
async fn create(&mut self, pos: Position) -> Result<(), CaptureError> {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use std::thread;
|
|||||||
use tokio::sync::mpsc::error::TrySendError;
|
use tokio::sync::mpsc::error::TrySendError;
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
use windows::core::{w, PCWSTR};
|
use windows::core::{w, PCWSTR};
|
||||||
use windows::Win32::Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, RECT, WPARAM};
|
use windows::Win32::Foundation::{FALSE, HWND, LPARAM, LRESULT, RECT, WPARAM};
|
||||||
use windows::Win32::Graphics::Gdi::{
|
use windows::Win32::Graphics::Gdi::{
|
||||||
EnumDisplayDevicesW, EnumDisplaySettingsW, DEVMODEW, DISPLAY_DEVICEW,
|
EnumDisplayDevicesW, EnumDisplaySettingsW, DEVMODEW, DISPLAY_DEVICEW,
|
||||||
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, ENUM_CURRENT_SETTINGS,
|
DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, ENUM_CURRENT_SETTINGS,
|
||||||
@@ -19,12 +19,12 @@ use windows::Win32::System::Threading::GetCurrentThreadId;
|
|||||||
|
|
||||||
use windows::Win32::UI::WindowsAndMessaging::{
|
use windows::Win32::UI::WindowsAndMessaging::{
|
||||||
CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW,
|
CallNextHookEx, CreateWindowExW, DispatchMessageW, GetMessageW, PostThreadMessageW,
|
||||||
RegisterClassW, SetWindowsHookExW, TranslateMessage, EDD_GET_DEVICE_INTERFACE_NAME, HHOOK,
|
RegisterClassW, SetWindowsHookExW, TranslateMessage, EDD_GET_DEVICE_INTERFACE_NAME, HOOKPROC,
|
||||||
HMENU, HOOKPROC, KBDLLHOOKSTRUCT, LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL,
|
KBDLLHOOKSTRUCT, LLKHF_EXTENDED, MSG, MSLLHOOKSTRUCT, WH_KEYBOARD_LL, WH_MOUSE_LL,
|
||||||
WH_MOUSE_LL, WINDOW_STYLE, WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN,
|
WINDOW_STYLE, WM_DISPLAYCHANGE, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP,
|
||||||
WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL,
|
WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_RBUTTONDOWN,
|
||||||
WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN,
|
WM_RBUTTONUP, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_USER, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW,
|
||||||
WM_XBUTTONUP, WNDCLASSW, WNDPROC,
|
WNDPROC,
|
||||||
};
|
};
|
||||||
|
|
||||||
use input_event::{
|
use input_event::{
|
||||||
@@ -128,7 +128,7 @@ thread_local! {
|
|||||||
fn get_msg() -> Option<MSG> {
|
fn get_msg() -> Option<MSG> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut msg = std::mem::zeroed();
|
let mut msg = std::mem::zeroed();
|
||||||
let ret = GetMessageW(addr_of_mut!(msg), HWND::default(), 0, 0);
|
let ret = GetMessageW(addr_of_mut!(msg), None, 0, 0);
|
||||||
match ret.0 {
|
match ret.0 {
|
||||||
0 => None,
|
0 => None,
|
||||||
x if x > 0 => Some(msg),
|
x if x > 0 => Some(msg),
|
||||||
@@ -176,14 +176,15 @@ fn start_routine(
|
|||||||
|
|
||||||
/* register hooks */
|
/* register hooks */
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = SetWindowsHookExW(WH_MOUSE_LL, mouse_proc, HINSTANCE::default(), 0).unwrap();
|
let _ = SetWindowsHookExW(WH_MOUSE_LL, mouse_proc, None, 0).unwrap();
|
||||||
let _ = SetWindowsHookExW(WH_KEYBOARD_LL, kybrd_proc, HINSTANCE::default(), 0).unwrap();
|
let _ = SetWindowsHookExW(WH_KEYBOARD_LL, kybrd_proc, None, 0).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let instance = unsafe { GetModuleHandleW(None).unwrap() };
|
let instance = unsafe { GetModuleHandleW(None).unwrap() };
|
||||||
|
let instance = instance.into();
|
||||||
let window_class: WNDCLASSW = WNDCLASSW {
|
let window_class: WNDCLASSW = WNDCLASSW {
|
||||||
lpfnWndProc: window_proc,
|
lpfnWndProc: window_proc,
|
||||||
hInstance: instance.into(),
|
hInstance: instance,
|
||||||
lpszClassName: w!("lan-mouse-message-window-class"),
|
lpszClassName: w!("lan-mouse-message-window-class"),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@@ -213,9 +214,9 @@ fn start_routine(
|
|||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
HWND::default(),
|
None,
|
||||||
HMENU::default(),
|
None,
|
||||||
instance,
|
Some(instance),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.expect("CreateWindowExW");
|
.expect("CreateWindowExW");
|
||||||
@@ -312,7 +313,7 @@ unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM)
|
|||||||
|
|
||||||
/* no client was active */
|
/* no client was active */
|
||||||
if !active {
|
if !active {
|
||||||
return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam);
|
return CallNextHookEx(None, ncode, wparam, lparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* get active client if any */
|
/* get active client if any */
|
||||||
@@ -337,7 +338,7 @@ unsafe extern "system" fn mouse_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM)
|
|||||||
unsafe extern "system" fn kybrd_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
unsafe extern "system" fn kybrd_proc(ncode: i32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
/* get active client if any */
|
/* get active client if any */
|
||||||
let Some(client) = ACTIVE_CLIENT.get() else {
|
let Some(client) = ACTIVE_CLIENT.get() else {
|
||||||
return CallNextHookEx(HHOOK::default(), ncode, wparam, lparam);
|
return CallNextHookEx(None, ncode, wparam, lparam);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* convert to key event */
|
/* convert to key event */
|
||||||
@@ -388,7 +389,10 @@ fn enumerate_displays(display_rects: &mut Vec<RECT>) {
|
|||||||
if ret == FALSE {
|
if ret == FALSE {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if device.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP != 0 {
|
if device
|
||||||
|
.StateFlags
|
||||||
|
.contains(DISPLAY_DEVICE_ATTACHED_TO_DESKTOP)
|
||||||
|
{
|
||||||
devices.push(device.DeviceName);
|
devices.push(device.DeviceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,18 +39,18 @@ wayland-protocols-misc = { version = "0.3.1", features = [
|
|||||||
"client",
|
"client",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true }
|
||||||
ashpd = { version = "0.10", default-features = false, features = [
|
ashpd = { version = "0.11.0", default-features = false, features = [
|
||||||
"tokio",
|
"tokio",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
reis = { version = "0.4", features = ["tokio"], optional = true }
|
reis = { version = "0.5.0", features = ["tokio"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os="macos")'.dependencies]
|
[target.'cfg(target_os="macos")'.dependencies]
|
||||||
bitflags = "2.6.0"
|
bitflags = "2.6.0"
|
||||||
core-graphics = { version = "0.24.0", features = ["highsierra"] }
|
core-graphics = { version = "0.25.0", features = ["highsierra"] }
|
||||||
keycode = "0.4.0"
|
keycode = "1.0.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.58.0", features = [
|
windows = { version = "0.61.2", features = [
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_Foundation",
|
"Win32_Foundation",
|
||||||
|
|||||||
@@ -161,12 +161,12 @@ fn get_display_at_point(x: CGFloat, y: CGFloat) -> Option<CGDirectDisplayID> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if error != 0 {
|
if error != 0 {
|
||||||
log::warn!("error getting displays at point ({}, {}): {}", x, y, error);
|
log::warn!("error getting displays at point ({x}, {y}): {error}");
|
||||||
return Option::None;
|
return Option::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
if display_count == 0 {
|
if display_count == 0 {
|
||||||
log::debug!("no displays found at point ({}, {})", x, y);
|
log::debug!("no displays found at point ({x}, {y})");
|
||||||
return Option::None;
|
return Option::None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,9 +343,10 @@ impl Emulation for MacOSEmulation {
|
|||||||
event.post(CGEventTapLocation::HID);
|
event.post(CGEventTapLocation::HID);
|
||||||
}
|
}
|
||||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||||
|
const LINES_PER_STEP: i32 = 3;
|
||||||
let (count, wheel1, wheel2, wheel3) = match axis {
|
let (count, wheel1, wheel2, wheel3) = match axis {
|
||||||
0 => (1, value, 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
0 => (1, value / (120 / LINES_PER_STEP), 0, 0), // 0 = vertical => 1 scroll wheel device (y axis)
|
||||||
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
1 => (2, 0, value / (120 / LINES_PER_STEP), 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
|
||||||
_ => {
|
_ => {
|
||||||
log::warn!("invalid scroll event: {axis}, {value}");
|
log::warn!("invalid scroll event: {axis}, {value}");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -353,7 +354,7 @@ impl Emulation for MacOSEmulation {
|
|||||||
};
|
};
|
||||||
let event = match CGEvent::new_scroll_event(
|
let event = match CGEvent::new_scroll_event(
|
||||||
self.event_source.clone(),
|
self.event_source.clone(),
|
||||||
ScrollEventUnit::PIXEL,
|
ScrollEventUnit::LINE,
|
||||||
count,
|
count,
|
||||||
wheel1,
|
wheel1,
|
||||||
wheel2,
|
wheel2,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use wayland_client::backend::WaylandError;
|
|||||||
use wayland_client::WEnum;
|
use wayland_client::WEnum;
|
||||||
|
|
||||||
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
|
||||||
use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
|
use wayland_client::protocol::wl_pointer::{Axis, AxisSource, ButtonState};
|
||||||
use wayland_client::protocol::wl_seat::WlSeat;
|
use wayland_client::protocol::wl_seat::WlSeat;
|
||||||
use wayland_protocols_wlr::virtual_pointer::v1::client::{
|
use wayland_protocols_wlr::virtual_pointer::v1::client::{
|
||||||
zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1 as VpManager,
|
zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1 as VpManager,
|
||||||
@@ -163,13 +163,13 @@ impl Emulation for WlrootsEmulation {
|
|||||||
async fn create(&mut self, handle: EmulationHandle) {
|
async fn create(&mut self, handle: EmulationHandle) {
|
||||||
self.state.add_client(handle);
|
self.state.add_client(handle);
|
||||||
if let Err(e) = self.queue.flush() {
|
if let Err(e) = self.queue.flush() {
|
||||||
log::error!("{}", e);
|
log::error!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn destroy(&mut self, handle: EmulationHandle) {
|
async fn destroy(&mut self, handle: EmulationHandle) {
|
||||||
self.state.destroy_client(handle);
|
self.state.destroy_client(handle);
|
||||||
if let Err(e) = self.queue.flush() {
|
if let Err(e) = self.queue.flush() {
|
||||||
log::error!("{}", e);
|
log::error!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn terminate(&mut self) {
|
async fn terminate(&mut self) {
|
||||||
@@ -210,7 +210,8 @@ impl VirtualInput {
|
|||||||
PointerEvent::AxisDiscrete120 { axis, value } => {
|
PointerEvent::AxisDiscrete120 { axis, value } => {
|
||||||
let axis: Axis = (axis as u32).try_into()?;
|
let axis: Axis = (axis as u32).try_into()?;
|
||||||
self.pointer
|
self.pointer
|
||||||
.axis_discrete(now, axis, value as f64 / 6., value / 120);
|
.axis_discrete(now, axis, value as f64 / 8., value / 120);
|
||||||
|
self.pointer.axis_source(AxisSource::Wheel);
|
||||||
self.pointer.frame();
|
self.pointer.frame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ impl VirtualInput {
|
|||||||
self.keyboard.key(time, key, state as u32);
|
self.keyboard.key(time, key, state as u32);
|
||||||
if let Ok(mut mods) = self.modifiers.lock() {
|
if let Ok(mut mods) = self.modifiers.lock() {
|
||||||
if mods.update_by_key_event(key, state) {
|
if mods.update_by_key_event(key, state) {
|
||||||
log::trace!("Key triggers modifier change: {:?}", mods);
|
log::trace!("Key triggers modifier change: {mods:?}");
|
||||||
self.keyboard.modifiers(
|
self.keyboard.modifiers(
|
||||||
mods.mask_pressed().bits(),
|
mods.mask_pressed().bits(),
|
||||||
0,
|
0,
|
||||||
@@ -330,7 +331,7 @@ impl XMods {
|
|||||||
|
|
||||||
fn update_by_key_event(&mut self, key: u32, state: u8) -> bool {
|
fn update_by_key_event(&mut self, key: u32, state: u8) -> bool {
|
||||||
if let Ok(key) = scancode::Linux::try_from(key) {
|
if let Ok(key) = scancode::Linux::try_from(key) {
|
||||||
log::trace!("Attempting to process modifier from: {:#?}", key);
|
log::trace!("Attempting to process modifier from: {key:#?}");
|
||||||
let pressed_mask = match key {
|
let pressed_mask = match key {
|
||||||
scancode::Linux::KeyLeftShift | scancode::Linux::KeyRightShift => XMods::ShiftMask,
|
scancode::Linux::KeyLeftShift | scancode::Linux::KeyRightShift => XMods::ShiftMask,
|
||||||
scancode::Linux::KeyLeftCtrl | scancode::Linux::KeyRightCtrl => XMods::ControlMask,
|
scancode::Linux::KeyLeftCtrl | scancode::Linux::KeyRightCtrl => XMods::ControlMask,
|
||||||
@@ -348,7 +349,7 @@ impl XMods {
|
|||||||
|
|
||||||
// unchanged
|
// unchanged
|
||||||
if pressed_mask.is_empty() && locked_mask.is_empty() {
|
if pressed_mask.is_empty() && locked_mask.is_empty() {
|
||||||
log::trace!("{:#?} is not a modifier key", key);
|
log::trace!("{key:#?} is not a modifier key");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
match state {
|
match state {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
thiserror = "2.0.0"
|
thiserror = "2.0.0"
|
||||||
|
|
||||||
[target.'cfg(all(unix, not(target_os="macos")))'.dependencies]
|
[target.'cfg(all(unix, not(target_os="macos")))'.dependencies]
|
||||||
reis = { version = "0.4", optional = true }
|
reis = { version = "0.5.0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["libei"]
|
default = ["libei"]
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ impl Display for KeyboardEvent {
|
|||||||
impl Display for Event {
|
impl Display for Event {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Event::Pointer(p) => write!(f, "{}", p),
|
Event::Pointer(p) => write!(f, "{p}"),
|
||||||
Event::Keyboard(k) => write!(f, "{}", k),
|
Event::Keyboard(k) => write!(f, "{k}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ impl Window {
|
|||||||
|
|
||||||
pub(super) fn update_client_config(&self, handle: ClientHandle, client: ClientConfig) {
|
pub(super) fn update_client_config(&self, handle: ClientHandle, client: ClientConfig) {
|
||||||
let Some(row) = self.row_for_handle(handle) else {
|
let Some(row) = self.row_for_handle(handle) else {
|
||||||
log::warn!("could not find row for handle {}", handle);
|
log::warn!("could not find row for handle {handle}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
row.set_hostname(client.hostname);
|
row.set_hostname(client.hostname);
|
||||||
@@ -334,11 +334,11 @@ impl Window {
|
|||||||
|
|
||||||
pub(super) fn update_client_state(&self, handle: ClientHandle, state: ClientState) {
|
pub(super) fn update_client_state(&self, handle: ClientHandle, state: ClientState) {
|
||||||
let Some(row) = self.row_for_handle(handle) else {
|
let Some(row) = self.row_for_handle(handle) else {
|
||||||
log::warn!("could not find row for handle {}", handle);
|
log::warn!("could not find row for handle {handle}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(client_object) = self.client_object_for_handle(handle) else {
|
let Some(client_object) = self.client_object_for_handle(handle) else {
|
||||||
log::warn!("could not find row for handle {}", handle);
|
log::warn!("could not find row for handle {handle}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -474,6 +474,9 @@ impl Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn request_authorization(&self, fingerprint: &str) {
|
pub(super) fn request_authorization(&self, fingerprint: &str) {
|
||||||
|
if let Some(w) = self.imp().authorization_window.borrow_mut().take() {
|
||||||
|
w.close();
|
||||||
|
}
|
||||||
let window = AuthorizationWindow::new(fingerprint);
|
let window = AuthorizationWindow::new(fingerprint);
|
||||||
window.set_transient_for(Some(self));
|
window.set_transient_for(Some(self));
|
||||||
window.connect_closure(
|
window.connect_closure(
|
||||||
@@ -496,5 +499,6 @@ impl Window {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
window.present();
|
window.present();
|
||||||
|
self.imp().authorization_window.replace(Some(window));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ use gtk::{gdk, gio, glib, Button, CompositeTemplate, Entry, Image, Label, ListBo
|
|||||||
|
|
||||||
use lan_mouse_ipc::{FrontendRequestWriter, DEFAULT_PORT};
|
use lan_mouse_ipc::{FrontendRequestWriter, DEFAULT_PORT};
|
||||||
|
|
||||||
|
use crate::authorization_window::AuthorizationWindow;
|
||||||
|
|
||||||
#[derive(CompositeTemplate, Default)]
|
#[derive(CompositeTemplate, Default)]
|
||||||
#[template(resource = "/de/feschber/LanMouse/window.ui")]
|
#[template(resource = "/de/feschber/LanMouse/window.ui")]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
@@ -49,6 +51,7 @@ pub struct Window {
|
|||||||
pub port: Cell<u16>,
|
pub port: Cell<u16>,
|
||||||
pub capture_active: Cell<bool>,
|
pub capture_active: Cell<bool>,
|
||||||
pub emulation_active: Cell<bool>,
|
pub emulation_active: Cell<bool>,
|
||||||
|
pub authorization_window: RefCell<Option<AuthorizationWindow>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ impl AsyncFrontendListener {
|
|||||||
let (socket_path, listener) = {
|
let (socket_path, listener) = {
|
||||||
let socket_path = crate::default_socket_path()?;
|
let socket_path = crate::default_socket_path()?;
|
||||||
|
|
||||||
log::debug!("remove socket: {:?}", socket_path);
|
log::debug!("remove socket: {socket_path:?}");
|
||||||
if socket_path.exists() {
|
if socket_path.exists() {
|
||||||
// try to connect to see if some other instance
|
// try to connect to see if some other instance
|
||||||
// of lan-mouse is already running
|
// of lan-mouse is already running
|
||||||
|
|||||||
93
scripts/copy-macos-dylib.sh
Executable file
93
scripts/copy-macos-dylib.sh
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
homebrew_path=""
|
||||||
|
exec_path="target/debug/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
$0: Copy all Homebrew libraries into the macOS app bundle.
|
||||||
|
USAGE: $0 [-h] [-b homebrew_path] [exec_path]
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-h, --help Show this help message and exit
|
||||||
|
-b Path to Homebrew installation (default: $homebrew_path)
|
||||||
|
exec_path Path to the main executable in the app bundle
|
||||||
|
(default: get from `brew --prefix`)
|
||||||
|
|
||||||
|
When macOS apps are linked to dynamic libraries (.dylib files),
|
||||||
|
the fully qualified path to the library is embedded in the binary.
|
||||||
|
If the libraries come from Homebrew, that means that Homebrew must be present
|
||||||
|
and the libraries must be installed in the same location on the user's machine.
|
||||||
|
|
||||||
|
This script copies all of the Homebrew libraries that an executable links to into the app bundle
|
||||||
|
and tells all the binaries in the bundle to look for them there.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Gather command-line arguments
|
||||||
|
while test $# -gt 0; do
|
||||||
|
case "$1" in
|
||||||
|
-h | --help ) usage; exit 0;;
|
||||||
|
-b | --homebrew ) homebrew_path="$1"; shift 2;;
|
||||||
|
* ) exec_path="$1"; shift;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$homebrew_path" ]; then
|
||||||
|
homebrew_path="$(brew --prefix)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Path to the .app bundle
|
||||||
|
bundle_path=$(dirname "$(dirname "$(dirname "$exec_path")")")
|
||||||
|
# Path to the Frameworks directory
|
||||||
|
fwks_path="$bundle_path/Contents/Frameworks"
|
||||||
|
mkdir -p "$fwks_path"
|
||||||
|
|
||||||
|
# Copy and fix references for a binary (executable or dylib)
|
||||||
|
#
|
||||||
|
# This function will:
|
||||||
|
# - Copy any referenced dylibs from /opt/homebrew to the Frameworks directory
|
||||||
|
# - Update the binary to reference the local copy instead
|
||||||
|
# - Add the Frameworks directory to the binary's RPATH
|
||||||
|
# - Recursively process the copied dylibs
|
||||||
|
fix_references() {
|
||||||
|
local bin="$1"
|
||||||
|
|
||||||
|
# Get all Homebrew libraries referenced by the binary
|
||||||
|
libs=$(otool -L "$bin" | awk -v homebrew="$homebrew_path" '$0 ~ homebrew {print $1}')
|
||||||
|
|
||||||
|
echo "$libs" | while IFS= read -r old_path; do
|
||||||
|
local base_name="$(basename "$old_path")"
|
||||||
|
local dest="$fwks_path/$base_name"
|
||||||
|
|
||||||
|
if [ ! -e "$dest" ]; then
|
||||||
|
echo "Copying $old_path -> $dest"
|
||||||
|
cp -f "$old_path" "$dest"
|
||||||
|
# Ensure the copied dylib is writable so that xattr -rd /path/to/Lan\ Mouse.app works.
|
||||||
|
chmod 644 "$dest"
|
||||||
|
|
||||||
|
echo "Updating $dest to have install_name of @rpath/$base_name..."
|
||||||
|
install_name_tool -id "@rpath/$base_name" "$dest"
|
||||||
|
|
||||||
|
# Recursively process this dylib
|
||||||
|
fix_references "$dest"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Updating $bin to reference @rpath/$base_name..."
|
||||||
|
install_name_tool -change "$old_path" "@rpath/$base_name" "$bin"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
fix_references "$exec_path"
|
||||||
|
|
||||||
|
# Ensure the main executable has our Frameworks path in its RPATH
|
||||||
|
if ! otool -l "$exec_path" | grep -q "@executable_path/../Frameworks"; then
|
||||||
|
echo "Adding RPATH to $exec_path"
|
||||||
|
install_name_tool -add_rpath "@executable_path/../Frameworks" "$exec_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Se-sign the .app
|
||||||
|
codesign --force --deep --sign - "$bundle_path"
|
||||||
|
|
||||||
|
echo "Done!"
|
||||||
@@ -3,7 +3,7 @@ use std::{collections::HashMap, net::IpAddr};
|
|||||||
use local_channel::mpsc::{channel, Receiver, Sender};
|
use local_channel::mpsc::{channel, Receiver, Sender};
|
||||||
use tokio::task::{spawn_local, JoinHandle};
|
use tokio::task::{spawn_local, JoinHandle};
|
||||||
|
|
||||||
use hickory_resolver::{error::ResolveError, TokioAsyncResolver};
|
use hickory_resolver::{ResolveError, TokioResolver};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
use lan_mouse_ipc::ClientHandle;
|
use lan_mouse_ipc::ClientHandle;
|
||||||
@@ -26,7 +26,7 @@ pub(crate) enum DnsEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct DnsTask {
|
struct DnsTask {
|
||||||
resolver: TokioAsyncResolver,
|
resolver: TokioResolver,
|
||||||
request_rx: Receiver<DnsRequest>,
|
request_rx: Receiver<DnsRequest>,
|
||||||
event_tx: Sender<DnsEvent>,
|
event_tx: Sender<DnsEvent>,
|
||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
@@ -35,7 +35,7 @@ struct DnsTask {
|
|||||||
|
|
||||||
impl DnsResolver {
|
impl DnsResolver {
|
||||||
pub(crate) fn new() -> Result<Self, ResolveError> {
|
pub(crate) fn new() -> Result<Self, ResolveError> {
|
||||||
let resolver = TokioAsyncResolver::tokio_from_system_conf()?;
|
let resolver = TokioResolver::builder_tokio()?.build();
|
||||||
let (request_tx, request_rx) = channel();
|
let (request_tx, request_rx) = channel();
|
||||||
let (event_tx, event_rx) = channel();
|
let (event_tx, event_rx) = channel();
|
||||||
let cancellation_token = CancellationToken::new();
|
let cancellation_token = CancellationToken::new();
|
||||||
|
|||||||
@@ -128,6 +128,7 @@ impl ListenTask {
|
|||||||
async fn run(mut self) {
|
async fn run(mut self) {
|
||||||
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
||||||
let mut last_response = HashMap::new();
|
let mut last_response = HashMap::new();
|
||||||
|
let mut rejected_connections = HashMap::new();
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
e = self.listener.next() => {match e {
|
e = self.listener.next() => {match e {
|
||||||
@@ -156,7 +157,10 @@ impl ListenTask {
|
|||||||
self.event_tx.send(EmulationEvent::Connected { addr, fingerprint }).expect("channel closed");
|
self.event_tx.send(EmulationEvent::Connected { addr, fingerprint }).expect("channel closed");
|
||||||
}
|
}
|
||||||
Some(ListenEvent::Rejected { fingerprint }) => {
|
Some(ListenEvent::Rejected { fingerprint }) => {
|
||||||
self.event_tx.send(EmulationEvent::ConnectionAttempt { fingerprint }).expect("channel closed");
|
if rejected_connections.insert(fingerprint.clone(), Instant::now())
|
||||||
|
.is_none_or(|i| i.elapsed() >= Duration::from_secs(2)) {
|
||||||
|
self.event_tx.send(EmulationEvent::ConnectionAttempt { fingerprint }).expect("channel closed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => break
|
None => break
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ async fn read_loop(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log::info!("dtls client disconnected {:?}", addr);
|
log::info!("dtls client disconnected {addr:?}");
|
||||||
let mut conns = conns.lock().await;
|
let mut conns = conns.lock().await;
|
||||||
let index = conns
|
let index = conns
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::{
|
|||||||
listen::{LanMouseListener, ListenerCreationError},
|
listen::{LanMouseListener, ListenerCreationError},
|
||||||
};
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use hickory_resolver::error::ResolveError;
|
use hickory_resolver::ResolveError;
|
||||||
use lan_mouse_ipc::{
|
use lan_mouse_ipc::{
|
||||||
AsyncFrontendListener, ClientConfig, ClientHandle, ClientState, FrontendEvent, FrontendRequest,
|
AsyncFrontendListener, ClientConfig, ClientHandle, ClientState, FrontendEvent, FrontendRequest,
|
||||||
IpcError, IpcListenerCreationError, Position, Status,
|
IpcError, IpcListenerCreationError, Position, Status,
|
||||||
|
|||||||
Reference in New Issue
Block a user