Compare commits

...

10 Commits

Author SHA1 Message Date
Ferdinand Schober
9e685a2039 cancel previous dns request if a new one is made 2025-03-14 22:15:42 +01:00
Micah R Ledbetter
5d1745a60c Add cmd-q shortcut on macOS (#270) 2025-02-28 15:28:35 +01:00
Ferdinand Schober
615c75817a fix clippy lint 2025-02-27 16:57:53 +01:00
Ferdinand Schober
03407b9826 update flake 2025-02-27 16:57:38 +01:00
Ferdinand Schober
89684e1481 fix names in pre release as well 2025-02-21 13:45:00 +01:00
Ferdinand Schober
a1d4effcf9 fix file names 2025-02-21 13:28:52 +01:00
Ferdinand Schober
da054b7a9a fix bundle path 2025-02-21 12:45:54 +01:00
Micah R Ledbetter
508d066700 Build a macOS bundle for Intel and ARM (#266)
* Build a macOS bundle for Intel and ARM

* Build icon.icns file in a script

* Add imagemagick

* Add macOS bundling to pre/tagged-release actions
2025-02-21 12:35:13 +01:00
Ferdinand Schober
21c24f7fa1 layer-shell: handle added/removed globals
closes #253
2025-02-13 22:10:12 +01:00
Ferdinand Schober
3e1c3e95b7 use shadow-rs instead of executing git describe
this removes git from the build dependencies
2025-01-27 16:51:25 +01:00
12 changed files with 690 additions and 203 deletions

View File

@@ -84,36 +84,56 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: install dependencies
run: brew install gtk4 libadwaita
run: brew install gtk4 libadwaita imagemagick
- name: Release Build
run: |
cargo build --release
cp target/release/lan-mouse lan-mouse-macos-intel
- name: Make icns
run: scripts/makeicns.sh
- name: Install cargo bundle
run: cargo install cargo-bundle
- name: Bundle
run: cargo bundle --release
- name: Zip bundle
run: |
cd target/release/bundle/osx
zip -r "lan-mouse-macos-intel.zip" "Lan Mouse.app"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: lan-mouse-macos-intel
path: lan-mouse-macos-intel
path: target/release/bundle/osx/lan-mouse-macos-intel.zip
macos-aarch64-release-build:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: install dependencies
run: brew install gtk4 libadwaita
run: brew install gtk4 libadwaita imagemagick
- name: Release Build
run: |
cargo build --release
cp target/release/lan-mouse lan-mouse-macos-aarch64
- name: Make icns
run: scripts/makeicns.sh
- name: Install cargo bundle
run: cargo install cargo-bundle
- name: Bundle
run: cargo bundle --release
- name: Zip bundle
run: |
cd target/release/bundle/osx
zip -r "lan-mouse-macos-aarch64.zip" "Lan Mouse.app"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: lan-mouse-macos-aarch64
path: lan-mouse-macos-aarch64
path: target/release/bundle/osx/lan-mouse-macos-aarch64.zip
pre-release:
name: "Pre Release"
needs: [windows-release-build, linux-release-build, macos-release-build]
needs: [windows-release-build, linux-release-build, macos-release-build, macos-aarch64-release-build]
runs-on: "ubuntu-latest"
steps:
- name: Download build artifacts
@@ -127,6 +147,6 @@ jobs:
title: "Development Build"
files: |
lan-mouse-linux/lan-mouse
lan-mouse-macos-intel/lan-mouse-macos-intel
lan-mouse-macos-aarch64/lan-mouse-macos-aarch64
lan-mouse-macos-intel/lan-mouse-macos-intel.zip
lan-mouse-macos-aarch64/lan-mouse-macos-aarch64.zip
lan-mouse-windows/lan-mouse-windows.zip

View File

@@ -98,7 +98,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: install dependencies
run: brew install gtk4 libadwaita
run: brew install gtk4 libadwaita imagemagick
- name: Build
run: cargo build --verbose
- name: Run tests
@@ -107,18 +107,28 @@ jobs:
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-features --all-targets -- --deny warnings
- name: Make icns
run: scripts/makeicns.sh
- name: Install cargo bundle
run: cargo install cargo-bundle
- name: Bundle
run: cargo bundle
- name: Zip bundle
run: |
cd target/debug/bundle/osx
zip -r "Lan Mouse macOS (Intel).zip" "Lan Mouse.app"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: lan-mouse-macos
path: target/debug/lan-mouse
name: Lan Mouse macOS (Intel)
path: target/debug/bundle/osx/Lan Mouse macOS (Intel).zip
build-macos-aarch64:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: install dependencies
run: brew install gtk4 libadwaita
run: brew install gtk4 libadwaita imagemagick
- name: Build
run: cargo build --verbose
- name: Run tests
@@ -127,8 +137,18 @@ jobs:
run: cargo fmt --check
- name: Clippy
run: cargo clippy --all-features --all-targets -- --deny warnings
- name: Make icns
run: scripts/makeicns.sh
- name: Install cargo bundle
run: cargo install cargo-bundle
- name: Bundle
run: cargo bundle
- name: Zip bundle
run: |
cd target/debug/bundle/osx
zip -r "Lan Mouse macOS (ARM).zip" "Lan Mouse.app"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: lan-mouse-macos-aarch64
path: target/debug/lan-mouse
name: Lan Mouse macOS (ARM)
path: target/debug/bundle/osx/Lan Mouse macOS (ARM).zip

View File

@@ -80,36 +80,56 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: install dependencies
run: brew install gtk4 libadwaita
run: brew install gtk4 libadwaita imagemagick
- name: Release Build
run: |
cargo build --release
cp target/release/lan-mouse lan-mouse-macos-intel
- name: Make icns
run: scripts/makeicns.sh
- name: Install cargo bundle
run: cargo install cargo-bundle
- name: Bundle
run: cargo bundle --release
- name: Zip bundle
run: |
cd target/release/bundle/osx
zip -r "lan-mouse-macos-intel.zip" "Lan Mouse.app"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: lan-mouse-macos-intel
path: lan-mouse-macos-intel
name: lan-mouse-macos-intel.zip
path: target/release/bundle/osx/lan-mouse-macos-intel.zip
macos-aarch64-release-build:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- name: install dependencies
run: brew install gtk4 libadwaita
run: brew install gtk4 libadwaita imagemagick
- name: Release Build
run: |
cargo build --release
cp target/release/lan-mouse lan-mouse-macos-aarch64
- name: Make icns
run: scripts/makeicns.sh
- name: Install cargo bundle
run: cargo install cargo-bundle
- name: Bundle
run: cargo bundle --release
- name: Zip bundle
run: |
cd target/release/bundle/osx
zip -r "lan-mouse-macos-aarch64.zip" "Lan Mouse.app"
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: lan-mouse-macos-aarch64
path: lan-mouse-macos-aarch64
name: lan-mouse-macos-aarch64.zip
path: target/release/bundle/osx/lan-mouse-macos-aarch64.zip
tagged-release:
name: "Tagged Release"
needs: [windows-release-build, linux-release-build, macos-release-build]
needs: [windows-release-build, linux-release-build, macos-release-build, macos-aarch64-release-build]
runs-on: "ubuntu-latest"
steps:
- name: Download build artifacts
@@ -121,6 +141,6 @@ jobs:
prerelease: false
files: |
lan-mouse-linux/lan-mouse
lan-mouse-macos-intel/lan-mouse-macos-intel
lan-mouse-macos-aarch64/lan-mouse-macos-aarch64
lan-mouse-macos-intel/lan-mouse-macos-intel.zip
lan-mouse-macos-aarch64/lan-mouse-macos-aarch64.zip
lan-mouse-windows/lan-mouse-windows.zip

289
Cargo.lock generated
View File

@@ -61,6 +61,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.18"
@@ -441,6 +450,12 @@ dependencies = [
"piper",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -476,6 +491,38 @@ dependencies = [
"system-deps",
]
[[package]]
name = "camino"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3"
dependencies = [
"serde",
]
[[package]]
name = "cargo-platform"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
dependencies = [
"serde",
]
[[package]]
name = "cargo_metadata"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037"
dependencies = [
"camino",
"cargo-platform",
"semver",
"serde",
"serde_json",
"thiserror 1.0.68",
]
[[package]]
name = "cbc"
version = "0.1.2"
@@ -491,6 +538,8 @@ version = "1.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baee610e9452a8f6f0a1b6194ec09ff9e2d85dea54432acdae41aa0761c95d70"
dependencies = [
"jobserver",
"libc",
"shlex",
]
@@ -599,6 +648,32 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "const_fn"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f8a2ca5ac02d09563609681103aada9e1777d54fc57a5acd7a41404f9c93b6e"
[[package]]
name = "const_format"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "core-foundation"
version = "0.10.0"
@@ -1232,6 +1307,19 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "git2"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fda788993cc341f69012feba8bf45c0ba4f3291fcc08e214b4d5a7332d88aff"
dependencies = [
"bitflags 2.6.0",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "glib"
version = "0.20.5"
@@ -1543,6 +1631,29 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core 0.52.0",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
@@ -1796,6 +1907,12 @@ version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is_debug"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8ea828c9d6638a5bd3d8b14e37502b4d56cae910ccf8a5b7f51c7a0eb1d0508"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
@@ -1808,6 +1925,25 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jobserver"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "keycode"
version = "0.4.0"
@@ -1855,6 +1991,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"shadow-rs",
"slab",
"thiserror 2.0.0",
"tokio",
@@ -1952,6 +2089,18 @@ version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libgit2-sys"
version = "0.18.0+1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1a117465e7e1597e8febea8bb0c410f1c7fb93b1e1cddf34363f8390367ffec"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.8.5"
@@ -1962,6 +2111,18 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "libz-sys"
version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
@@ -2186,6 +2347,15 @@ dependencies = [
"syn 2.0.87",
]
[[package]]
name = "num_threads"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
"libc",
]
[[package]]
name = "object"
version = "0.36.5"
@@ -2667,6 +2837,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "ryu"
version = "1.0.18"
@@ -2704,6 +2880,9 @@ name = "semver"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
dependencies = [
"serde",
]
[[package]]
name = "serde"
@@ -2779,6 +2958,21 @@ dependencies = [
"digest",
]
[[package]]
name = "shadow-rs"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d433b5df1e1958a668457ebe4a9c5b7bcfe844f4eb2276ac43cf273baddd54"
dependencies = [
"cargo_metadata",
"const_format",
"git2",
"is_debug",
"serde_json",
"time",
"tzdb",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -2994,7 +3188,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
dependencies = [
"deranged",
"itoa",
"libc",
"num-conv",
"num_threads",
"powerfmt",
"serde",
"time-core",
@@ -3167,6 +3363,35 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "tz-rs"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33851b15c848fad2cf4b105c6bb66eb9512b6f6c44a4b13f57c53c73c707e2b4"
dependencies = [
"const_fn",
]
[[package]]
name = "tzdb"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b580f6b365fa89f5767cdb619a55d534d04a4e14c2d7e5b9a31e94598687fb1"
dependencies = [
"iana-time-zone",
"tz-rs",
"tzdb_data",
]
[[package]]
name = "tzdb_data"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4471adcfcbd3052e8c5b5890a04a559837444b3be26b9cbbd622063171cec9d"
dependencies = [
"tz-rs",
]
[[package]]
name = "uds_windows"
version = "1.1.0"
@@ -3257,6 +3482,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.2.0"
@@ -3275,6 +3506,64 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn 2.0.87",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wayland-backend"
version = "0.3.7"

View File

@@ -23,6 +23,9 @@ lto = "fat"
strip = true
panic = "abort"
[build-dependencies]
shadow-rs = "0.38.0"
[dependencies]
input-event = { path = "input-event", version = "0.3.0" }
input-emulation = { path = "input-emulation", version = "0.3.0", default-features = false }
@@ -31,6 +34,7 @@ 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-ipc = { path = "lan-mouse-ipc", version = "0.2.0" }
lan-mouse-proto = { path = "lan-mouse-proto", version = "0.2.0" }
shadow-rs = { version = "0.38.0", features = ["metadata"] }
hickory-resolver = "0.24.1"
toml = "0.8"
@@ -85,3 +89,8 @@ libei_emulation = ["input-event/libei", "input-emulation/libei"]
wlroots_emulation = ["input-emulation/wlroots"]
x11_emulation = ["input-emulation/x11"]
rdp_emulation = ["input-emulation/remote_desktop_portal"]
[package.metadata.bundle]
name = "Lan Mouse"
icon = ["target/icon.icns"]
identifier = "de.feschber.LanMouse"

View File

@@ -1,20 +1,8 @@
use std::process::Command;
use shadow_rs::ShadowBuilder;
fn main() {
// commit hash
let git_describe = Command::new("git")
.arg("describe")
.arg("--always")
.arg("--dirty")
.arg("--tags")
.output()
.map(|output| String::from_utf8(output.stdout).ok())
.ok()
.flatten()
.unwrap_or_else(|| {
println!("cargo:warning=Failed to get git describe");
String::from("unknown")
});
let git_describe = git_describe.trim().to_string();
println!("cargo::rustc-env=GIT_DESCRIBE={git_describe}");
ShadowBuilder::builder()
.deny_const(Default::default())
.build()
.expect("shadow build");
}

12
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1728018373,
"narHash": "sha256-NOiTvBbRLIOe5F6RbHaAh6++BNjsb149fGZd1T4+KBg=",
"lastModified": 1740560979,
"narHash": "sha256-Vr3Qi346M+8CjedtbyUevIGDZW8LcA1fTG0ugPY/Hic=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "bc947f541ae55e999ffdb4013441347d83b00feb",
"rev": "5135c59491985879812717f4c9fea69604e7f26f",
"type": "github"
},
"original": {
@@ -29,11 +29,11 @@
]
},
"locked": {
"lastModified": 1728181869,
"narHash": "sha256-sQXHXsjIcGEoIHkB+RO6BZdrPfB+43V1TEpyoWRI3ww=",
"lastModified": 1740623427,
"narHash": "sha256-3SdPQrZoa4odlScFDUHd4CUPQ/R1gtH4Mq9u8CBiK8M=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "cd46aa3906c14790ef5cbe278d9e54f2c38f95c0",
"rev": "d342e8b5fd88421ff982f383c853f0fc78a847ab",
"type": "github"
},
"original": {

View File

@@ -1,8 +1,9 @@
use async_trait::async_trait;
use futures_core::Stream;
use std::{
collections::VecDeque,
collections::{HashSet, VecDeque},
env,
fmt::{self, Display},
io::{self, ErrorKind},
os::fd::{AsFd, RawFd},
pin::Pin,
@@ -46,13 +47,15 @@ use wayland_protocols_wlr::layer_shell::v1::client::{
use wayland_client::{
backend::{ReadEventsGuard, WaylandError},
delegate_noop,
globals::{registry_queue_init, GlobalListContents},
globals::{registry_queue_init, Global, GlobalList, GlobalListContents},
protocol::{
wl_buffer, wl_compositor,
wl_keyboard::{self, WlKeyboard},
wl_output::{self, WlOutput},
wl_pointer::{self, WlPointer},
wl_region, wl_registry, wl_seat, wl_shm, wl_shm_pool,
wl_region,
wl_registry::{self, WlRegistry},
wl_seat, wl_shm, wl_shm_pool,
wl_surface::WlSurface,
},
Connection, Dispatch, DispatchError, EventQueue, QueueHandle, WEnum,
@@ -75,28 +78,42 @@ struct Globals {
seat: wl_seat::WlSeat,
shm: wl_shm::WlShm,
layer_shell: ZwlrLayerShellV1,
outputs: Vec<WlOutput>,
xdg_output_manager: ZxdgOutputManagerV1,
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
struct Output {
wl_output: WlOutput,
global: Global,
info: Option<OutputInfo>,
pending_info: OutputInfo,
has_xdg_info: bool,
}
impl Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(info) = &self.info {
write!(
f,
"{} {}x{} @pos {:?} ({})",
info.name, info.size.0, info.size.1, info.position, info.description
)
} else {
write!(f, "unknown output")
}
}
}
#[derive(Clone, Debug, Default)]
struct OutputInfo {
description: String,
name: String,
position: (i32, i32),
size: (i32, i32),
}
impl OutputInfo {
fn new() -> Self {
Self {
name: "".to_string(),
position: (0, 0),
size: (0, 0),
}
}
}
struct State {
active_positions: HashSet<Position>,
pointer: Option<WlPointer>,
keyboard: Option<WlKeyboard>,
pointer_lock: Option<ZwpLockedPointerV1>,
@@ -104,12 +121,13 @@ struct State {
shortcut_inhibitor: Option<ZwpKeyboardShortcutsInhibitorV1>,
active_windows: Vec<Arc<Window>>,
focused: Option<Arc<Window>>,
g: Globals,
global_list: GlobalList,
globals: Globals,
wayland_fd: RawFd,
read_guard: Option<ReadEventsGuard>,
qh: QueueHandle<Self>,
pending_events: VecDeque<(Position, CaptureEvent)>,
output_info: Vec<(WlOutput, OutputInfo)>,
outputs: Vec<Output>,
scroll_discrete_pending: bool,
}
@@ -142,7 +160,7 @@ impl Window {
size: (i32, i32),
) -> Window {
log::debug!("creating window output: {output:?}, size: {size:?}");
let g = &state.g;
let g = &state.globals;
let (width, height) = match pos {
Position::Left | Position::Right => (1, size.1 as u32),
@@ -203,41 +221,36 @@ impl Drop for Window {
}
}
fn get_edges(outputs: &[(WlOutput, OutputInfo)], pos: Position) -> Vec<(WlOutput, i32)> {
fn get_edges(outputs: &[Output], pos: Position) -> Vec<(Output, i32)> {
outputs
.iter()
.map(|(o, i)| {
(
o.clone(),
match pos {
Position::Left => i.position.0,
Position::Right => i.position.0 + i.size.0,
Position::Top => i.position.1,
Position::Bottom => i.position.1 + i.size.1,
},
)
.filter_map(|output| {
output.info.as_ref().map(|info| {
(
output.clone(),
match pos {
Position::Left => info.position.0,
Position::Right => info.position.0 + info.size.0,
Position::Top => info.position.1,
Position::Bottom => info.position.1 + info.size.1,
},
)
})
})
.collect()
}
fn get_output_configuration(state: &State, pos: Position) -> Vec<(WlOutput, OutputInfo)> {
fn get_output_configuration(state: &State, pos: Position) -> Vec<Output> {
// get all output edges corresponding to the position
let edges = get_edges(&state.output_info, pos);
log::debug!("edges: {edges:?}");
let opposite_edges = get_edges(&state.output_info, pos.opposite());
let edges = get_edges(&state.outputs, pos);
let opposite_edges = get_edges(&state.outputs, pos.opposite());
// remove those edges that are at the same position
// as an opposite edge of a different output
let outputs: Vec<WlOutput> = edges
edges
.iter()
.filter(|(_, edge)| !opposite_edges.iter().map(|(_, e)| *e).any(|e| &e == edge))
.map(|(o, _)| o.clone())
.collect();
state
.output_info
.iter()
.filter(|(o, _)| outputs.contains(o))
.map(|(o, i)| (o.clone(), i.clone()))
.collect()
}
@@ -259,36 +272,36 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
impl LayerShellInputCapture {
pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
let conn = Connection::connect_to_env()?;
let (g, mut queue) = registry_queue_init::<State>(&conn)?;
let (global_list, mut queue) = registry_queue_init::<State>(&conn)?;
let qh = queue.handle();
let compositor: wl_compositor::WlCompositor = g
let compositor: wl_compositor::WlCompositor = global_list
.bind(&qh, 4..=5, ())
.map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?;
let xdg_output_manager: ZxdgOutputManagerV1 = g
let xdg_output_manager: ZxdgOutputManagerV1 = global_list
.bind(&qh, 1..=3, ())
.map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?;
let shm: wl_shm::WlShm = g
let shm: wl_shm::WlShm = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "wl_shm"))?;
let layer_shell: ZwlrLayerShellV1 = g
let layer_shell: ZwlrLayerShellV1 = global_list
.bind(&qh, 3..=4, ())
.map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?;
let seat: wl_seat::WlSeat = g
let seat: wl_seat::WlSeat = global_list
.bind(&qh, 7..=8, ())
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
let pointer_constraints: ZwpPointerConstraintsV1 = g
let pointer_constraints: ZwpPointerConstraintsV1 = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "zwp_pointer_constraints_v1"))?;
let relative_pointer_manager: ZwpRelativePointerManagerV1 = g
let relative_pointer_manager: ZwpRelativePointerManagerV1 = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "zwp_relative_pointer_manager_v1"))?;
let shortcut_inhibit_manager: Result<
ZwpKeyboardShortcutsInhibitManagerV1,
WaylandBindError,
> = g
> = global_list
.bind(&qh, 1..=1, ())
.map_err(|e| WaylandBindError::new(e, "zwp_keyboard_shortcuts_inhibit_manager_v1"));
// layer-shell backend still works without this protocol so we make it an optional dependency
@@ -297,65 +310,41 @@ impl LayerShellInputCapture {
to the client");
}
let shortcut_inhibit_manager = shortcut_inhibit_manager.ok();
let outputs = vec![];
let g = Globals {
compositor,
shm,
layer_shell,
seat,
pointer_constraints,
relative_pointer_manager,
shortcut_inhibit_manager,
outputs,
xdg_output_manager,
};
// flush outgoing events
queue.flush()?;
let wayland_fd = queue.as_fd().as_raw_fd();
let mut state = State {
active_positions: Default::default(),
pointer: None,
keyboard: None,
g,
global_list,
globals: Globals {
compositor,
shm,
layer_shell,
seat,
pointer_constraints,
relative_pointer_manager,
shortcut_inhibit_manager,
xdg_output_manager,
},
pointer_lock: None,
rel_pointer: None,
shortcut_inhibitor: None,
active_windows: Vec::new(),
focused: None,
qh,
wayland_fd,
wayland_fd: queue.as_fd().as_raw_fd(),
read_guard: None,
pending_events: VecDeque::new(),
output_info: vec![],
outputs: vec![],
scroll_discrete_pending: false,
};
// dispatch registry to () again, in order to read all wl_outputs
conn.display().get_registry(&state.qh, ());
log::debug!("==============> requested registry");
// roundtrip to read wl_output globals
queue.roundtrip(&mut state)?;
log::debug!("==============> roundtrip 1 done");
// read outputs
for output in state.g.outputs.iter() {
state
.g
.xdg_output_manager
.get_xdg_output(output, &state.qh, output.clone());
for global in state.global_list.contents().clone_list() {
state.register_global(global);
}
// roundtrip to read xdg_output events
queue.roundtrip(&mut state)?;
log::debug!("==============> roundtrip 2 done");
for i in &state.output_info {
log::debug!("{:#?}", i.1);
}
// flush outgoing events
queue.flush()?;
let read_guard = loop {
match queue.prepare_read() {
@@ -379,6 +368,7 @@ impl LayerShellInputCapture {
fn delete_client(&mut self, pos: Position) {
let inner = self.0.get_mut();
inner.state.active_positions.remove(&pos);
// remove all windows corresponding to this client
while let Some(i) = inner.state.active_windows.iter().position(|w| w.pos == pos) {
inner.state.active_windows.remove(i);
@@ -388,6 +378,52 @@ impl LayerShellInputCapture {
}
impl State {
fn update_output_info(&mut self, name: u32) {
let output = self
.outputs
.iter_mut()
.find(|o| o.global.name == name)
.expect("output not found");
if output.has_xdg_info {
output.info.replace(output.pending_info.clone());
self.update_windows();
}
}
fn register_global(&mut self, global: Global) {
if global.interface.as_str() == "wl_output" {
log::debug!("new output global: wl_output {}", global.name);
let wl_output = self.global_list.registry().bind::<WlOutput, _, _>(
global.name,
4,
&self.qh,
global.name,
);
self.globals
.xdg_output_manager
.get_xdg_output(&wl_output, &self.qh, global.name);
self.outputs.push(Output {
wl_output,
global,
info: None,
has_xdg_info: false,
pending_info: Default::default(),
})
}
}
fn deregister_global(&mut self, name: u32) {
self.outputs.retain(|o| {
if o.global.name == name {
log::debug!("{o} (global {:?}) removed", o.global);
o.wl_output.release();
false
} else {
true
}
});
}
fn grab(
&mut self,
surface: &WlSurface,
@@ -408,7 +444,7 @@ impl State {
// lock pointer
if self.pointer_lock.is_none() {
self.pointer_lock = Some(self.g.pointer_constraints.lock_pointer(
self.pointer_lock = Some(self.globals.pointer_constraints.lock_pointer(
surface,
pointer,
None,
@@ -420,7 +456,7 @@ impl State {
// request relative input
if self.rel_pointer.is_none() {
self.rel_pointer = Some(self.g.relative_pointer_manager.get_relative_pointer(
self.rel_pointer = Some(self.globals.relative_pointer_manager.get_relative_pointer(
pointer,
qh,
(),
@@ -428,10 +464,14 @@ impl State {
}
// capture modifier keys
if let Some(shortcut_inhibit_manager) = &self.g.shortcut_inhibit_manager {
if let Some(shortcut_inhibit_manager) = &self.globals.shortcut_inhibit_manager {
if self.shortcut_inhibitor.is_none() {
self.shortcut_inhibitor =
Some(shortcut_inhibit_manager.inhibit_shortcuts(surface, &self.g.seat, qh, ()));
self.shortcut_inhibitor = Some(shortcut_inhibit_manager.inhibit_shortcuts(
surface,
&self.globals.seat,
qh,
(),
));
}
}
}
@@ -469,21 +509,39 @@ impl State {
}
fn add_client(&mut self, pos: Position) {
self.active_positions.insert(pos);
let outputs = get_output_configuration(self, pos);
log::debug!("outputs: {outputs:?}");
outputs.iter().for_each(|(o, i)| {
let window = Window::new(self, &self.qh, o, pos, i.size);
let window = Arc::new(window);
self.active_windows.push(window);
log::info!(
"adding capture for position {pos} - using outputs: {:?}",
outputs
.iter()
.map(|o| o
.info
.as_ref()
.map(|i| i.name.to_owned())
.unwrap_or("unknown output".to_owned()))
.collect::<Vec<_>>()
);
outputs.iter().for_each(|o| {
if let Some(info) = o.info.as_ref() {
let window = Window::new(self, &self.qh, &o.wl_output, pos, info.size);
let window = Arc::new(window);
self.active_windows.push(window);
}
});
}
fn update_windows(&mut self) {
log::debug!("updating windows");
log::debug!("output info: {:?}", self.output_info);
let clients: Vec<_> = self.active_windows.drain(..).map(|w| w.pos).collect();
for pos in clients {
log::info!("active outputs: ");
for output in self.outputs.iter().filter(|o| o.info.is_some()) {
log::info!(" * {}", output);
}
self.active_windows.clear();
let active_positions = self.active_positions.iter().cloned().collect::<Vec<_>>();
for pos in active_positions {
self.add_client(pos);
}
}
@@ -835,7 +893,7 @@ impl Dispatch<ZwpRelativePointerV1, ()> for State {
} = event
{
if let Some(window) = &app.focused {
let time = (((utime_hi as u64) << 32 | utime_lo as u64) / 1000) as u32;
let time = ((((utime_hi as u64) << 32) | utime_lo as u64) / 1000) as u32;
app.pending_events.push_back((
window.pos,
CaptureEvent::Input(Event::Pointer(PointerEvent::Motion { time, dx, dy })),
@@ -872,94 +930,89 @@ impl Dispatch<ZwlrLayerSurfaceV1, ()> for State {
}
// delegate wl_registry events to App itself
impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for State {
fn event(
_state: &mut Self,
_proxy: &wl_registry::WlRegistry,
_event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
}
}
impl Dispatch<wl_registry::WlRegistry, ()> for State {
impl Dispatch<WlRegistry, GlobalListContents> for State {
fn event(
state: &mut Self,
registry: &wl_registry::WlRegistry,
event: <wl_registry::WlRegistry as wayland_client::Proxy>::Event,
_: &(),
_: &Connection,
qh: &QueueHandle<Self>,
_registry: &WlRegistry,
event: <WlRegistry as wayland_client::Proxy>::Event,
_data: &GlobalListContents,
_conn: &Connection,
_qh: &QueueHandle<Self>,
) {
match event {
wl_registry::Event::Global {
name,
interface,
version: _,
version,
} => {
if interface.as_str() == "wl_output" {
log::debug!("wl_output global");
state
.g
.outputs
.push(registry.bind::<WlOutput, _, _>(name, 4, qh, ()))
}
state.register_global(Global {
name,
interface,
version,
});
}
wl_registry::Event::GlobalRemove { name } => {
state.deregister_global(name);
}
wl_registry::Event::GlobalRemove { .. } => {}
_ => {}
}
}
}
impl Dispatch<ZxdgOutputV1, WlOutput> for State {
impl Dispatch<ZxdgOutputV1, u32> for State {
fn event(
state: &mut Self,
_: &ZxdgOutputV1,
event: <ZxdgOutputV1 as wayland_client::Proxy>::Event,
wl_output: &WlOutput,
name: &u32,
_: &Connection,
_: &QueueHandle<Self>,
) {
log::debug!("xdg-output - {event:?}");
let output_info = match state.output_info.iter_mut().find(|(o, _)| o == wl_output) {
Some((_, c)) => c,
None => {
let output_info = OutputInfo::new();
state.output_info.push((wl_output.clone(), output_info));
&mut state.output_info.last_mut().unwrap().1
}
};
let output = state
.outputs
.iter_mut()
.find(|o| o.global.name == *name)
.expect("output");
log::debug!("xdg_output {name} - {:?}", event);
match event {
zxdg_output_v1::Event::LogicalPosition { x, y } => {
output_info.position = (x, y);
output.pending_info.position = (x, y);
output.has_xdg_info = true;
}
zxdg_output_v1::Event::LogicalSize { width, height } => {
output_info.size = (width, height);
output.pending_info.size = (width, height);
output.has_xdg_info = true;
}
zxdg_output_v1::Event::Done => {
log::warn!("Use of deprecated xdg-output event \"done\"");
state.update_output_info(*name);
}
zxdg_output_v1::Event::Done => {}
zxdg_output_v1::Event::Name { name } => {
output_info.name = name;
output.pending_info.name = name;
output.has_xdg_info = true;
}
zxdg_output_v1::Event::Description { .. } => {}
_ => {}
zxdg_output_v1::Event::Description { description } => {
output.pending_info.description = description;
output.has_xdg_info = true;
}
_ => todo!(),
}
}
}
impl Dispatch<WlOutput, ()> for State {
impl Dispatch<WlOutput, u32> for State {
fn event(
state: &mut Self,
_proxy: &WlOutput,
_wl_output: &WlOutput,
event: <WlOutput as wayland_client::Proxy>::Event,
_data: &(),
name: &u32,
_conn: &Connection,
_qhandle: &QueueHandle<Self>,
) {
log::debug!("wl_output {name} - {:?}", event);
if let wl_output::Event::Done = event {
state.update_windows();
state.update_output_info(*name);
}
}
}

View File

@@ -49,7 +49,11 @@ fn gtk_main() -> glib::ExitCode {
.application_id("de.feschber.LanMouse")
.build();
app.connect_startup(|_| load_icons());
app.connect_startup(|app| {
load_icons();
setup_actions(app);
setup_menu(app);
});
app.connect_activate(build_ui);
let args: Vec<&'static str> = vec![];
@@ -62,6 +66,33 @@ fn load_icons() {
icon_theme.add_resource_path("/de/feschber/LanMouse/icons");
}
// Add application actions
fn setup_actions(app: &adw::Application) {
// Quit action
// This is important on macOS, where users expect a File->Quit action with a Cmd+Q shortcut.
let quit_action = gio::SimpleAction::new("quit", None);
quit_action.connect_activate({
let app = app.clone();
move |_, _| {
app.quit();
}
});
app.add_action(&quit_action);
}
// Set up a global menu
//
// Currently this is used only on macOS
fn setup_menu(app: &adw::Application) {
let menu = gio::Menu::new();
let file_menu = gio::Menu::new();
file_menu.append(Some("Quit"), Some("app.quit"));
menu.append_submenu(Some("_File"), &file_menu);
app.set_menubar(Some(&menu))
}
fn build_ui(app: &Application) {
log::debug!("connecting to lan-mouse-socket");
let (mut frontend_rx, frontend_tx) = match lan_mouse_ipc::connect() {

42
scripts/makeicns.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/sh
set -e
usage() {
cat <<EOF
$0: Make a macOS icns file from an SVG with ImageMagick and iconutil.
usage: $0 [SVG [ICNS [ICONSET]]
ARGUMENTS
SVG The SVG file to convert
Defaults to ./lan-mouse-gtk/resources/de.feschber.LanMouse.svg
ICNS The icns file to create
Defaults to ./target/icon.icns
ICONSET The iconset directory to create
Defaults to ./target/icon.iconset
This is just a temporary directory
EOF
}
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
usage
exit 0
fi
svg="${1:-./lan-mouse-gtk/resources/de.feschber.LanMouse.svg}"
icns="${2:-./target/icon.icns}"
iconset="${3:-./target/icon.iconset}"
set -u
mkdir -p "$iconset"
magick convert -background none -resize 1024x1024 "$svg" "$iconset"/icon_512x512@2x.png
magick convert -background none -resize 512x512 "$svg" "$iconset"/icon_512x512.png
magick convert -background none -resize 256x256 "$svg" "$iconset"/icon_256x256.png
magick convert -background none -resize 128x128 "$svg" "$iconset"/icon_128x128.png
magick convert -background none -resize 64x64 "$svg" "$iconset"/icon_32x32@2x.png
magick convert -background none -resize 32x32 "$svg" "$iconset"/icon_32x32.png
magick convert -background none -resize 16x16 "$svg" "$iconset"/icon_16x16.png
cp "$iconset"/icon_512x512.png "$iconset"/icon_256x256@2x.png
cp "$iconset"/icon_256x256.png "$iconset"/icon_128x128@2x.png
cp "$iconset"/icon_32x32.png "$iconset"/icon_16x16@2x.png
iconutil -c icns "$iconset" -o "$icns"

View File

@@ -17,6 +17,10 @@ use input_event::scancode::{
Linux::{KeyLeftAlt, KeyLeftCtrl, KeyLeftMeta, KeyLeftShift},
};
use shadow_rs::shadow;
shadow!(build);
#[derive(Serialize, Deserialize, Debug)]
pub struct ConfigToml {
pub capture_backend: Option<CaptureBackend>,
@@ -51,7 +55,7 @@ impl ConfigToml {
}
#[derive(Parser, Debug)]
#[command(author, version=env!("GIT_DESCRIBE"), about, long_about = None)]
#[command(author, version=build::CLAP_LONG_VERSION, about, long_about = None)]
struct CliArgs {
/// the listen port for lan-mouse
#[arg(short, long)]

View File

@@ -1,4 +1,4 @@
use std::net::IpAddr;
use std::{collections::HashMap, net::IpAddr};
use local_channel::mpsc::{channel, Receiver, Sender};
use tokio::task::{spawn_local, JoinHandle};
@@ -30,6 +30,7 @@ struct DnsTask {
request_rx: Receiver<DnsRequest>,
event_tx: Sender<DnsEvent>,
cancellation_token: CancellationToken,
active_tasks: HashMap<ClientHandle, JoinHandle<()>>,
}
impl DnsResolver {
@@ -39,6 +40,7 @@ impl DnsResolver {
let (event_tx, event_rx) = channel();
let cancellation_token = CancellationToken::new();
let dns_task = DnsTask {
active_tasks: Default::default(),
resolver,
request_rx,
event_tx,
@@ -81,6 +83,14 @@ impl DnsTask {
while let Some(dns_request) = self.request_rx.recv().await {
let DnsRequest { handle, hostname } = dns_request;
/* abort previous dns task */
let previous_task = self.active_tasks.remove(&handle);
if let Some(task) = previous_task {
if !task.is_finished() {
task.abort();
}
}
self.event_tx
.send(DnsEvent::Resolving(handle))
.expect("channel closed");
@@ -90,7 +100,7 @@ impl DnsTask {
let resolver = self.resolver.clone();
let cancellation_token = self.cancellation_token.clone();
tokio::task::spawn_local(async move {
let task = tokio::task::spawn_local(async move {
tokio::select! {
ips = resolver.lookup_ip(&hostname) => {
let ips = ips.map(|ips| ips.iter().collect::<Vec<_>>());
@@ -101,6 +111,7 @@ impl DnsTask {
_ = cancellation_token.cancelled() => {},
}
});
self.active_tasks.insert(handle, task);
}
}
}