diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..32ebb15 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.rs] +indent_style = space +indent_size = 4 +max_line_length = 100 + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.toml] +indent_style = space +indent_size = 4 + +[*.nix] +indent_style = space +indent_size = 2 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..456b693 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Ensure we're at the repo root +cd "$(git rev-parse --show-toplevel)" || exit 1 + +echo "Running cargo fmt (auto-format)..." +# Run formatter to apply fixes but do not stage them. If formatting changed, +# fail the commit so the user can review and stage the changes manually. +cargo fmt --all +if ! git diff --quiet --exit-code; then + echo "" >&2 + echo "ERROR: cargo fmt modified files. Review changes, stage them, and commit again." >&2 + git --no-pager diff --name-only + exit 1 +fi + +echo "Running cargo clippy..." +# Matches CI: deny warnings to keep code health strict +if ! cargo clippy --workspace --all-targets --all-features -- -D warnings; then + echo "" >&2 + echo "ERROR: clippy found warnings/errors. Fix them before committing." >&2 + exit 1 +fi + +echo "Running cargo test..." +if ! cargo test --workspace --all-features; then + echo "" >&2 + echo "ERROR: Some tests failed. Fix tests before committing." >&2 + exit 1 +fi + +echo "All pre-commit checks passed." +exit 0 diff --git a/.github/workflows/cachix.yml b/.github/workflows/cachix.yml index 494c803..10d7568 100644 --- a/.github/workflows/cachix.yml +++ b/.github/workflows/cachix.yml @@ -7,6 +7,10 @@ on: branches: ["main"] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: nix: strategy: @@ -19,7 +23,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive diff --git a/.github/workflows/pre-release.yml b/.github/workflows/release.yml similarity index 55% rename from .github/workflows/pre-release.yml rename to .github/workflows/release.yml index cf70278..1b8a353 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/release.yml @@ -1,10 +1,17 @@ -name: "pre-release" +name: "Release" +run-name: "Release - ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || github.event.inputs.name || github.ref_name }}" on: push: branches: [ "main" ] - - + tags: + - v** + workflow_dispatch: + inputs: + name: + description: 'Development release name' + required: false + default: '' env: CARGO_TERM_COLOR: always @@ -13,19 +20,40 @@ jobs: linux-release-build: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: install dependencies 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 + run: | + cargo build --release + cp target/release/lan-mouse lan-mouse-linux-x86_64 - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: - name: lan-mouse-linux - path: target/release/lan-mouse + name: lan-mouse-linux-x86_64 + path: lan-mouse-linux-x86_64 + + linux-arm64-release-build: + runs-on: ubuntu-22.04-arm + steps: + - uses: actions/checkout@v6 + - name: install dependencies + 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 + cp target/release/lan-mouse lan-mouse-linux-arm64 + - name: Upload build artifact + uses: actions/upload-artifact@v6 + with: + name: lan-mouse-linux-arm64 + path: lan-mouse-linux-arm64 windows-release-build: runs-on: windows-latest @@ -64,7 +92,7 @@ jobs: gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg Move-Item "C:\Program Files\Git\usr\notbin" "C:\Program Files\Git\usr\bin" Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin" - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Release Build run: cargo build --release - name: Create Archive @@ -72,19 +100,21 @@ jobs: mkdir "lan-mouse-windows" Get-Childitem -Path "C:\\gtk-build\\gtk\\x64\\release\\bin\\*.dll" -File -Recurse | Copy-Item -Destination "lan-mouse-windows" Copy-Item -Path "target\release\lan-mouse.exe" -Destination "lan-mouse-windows" - Compress-Archive -Path "lan-mouse-windows\*" -DestinationPath lan-mouse-windows.zip + Compress-Archive -Path "lan-mouse-windows\*" -DestinationPath lan-mouse-windows-x86_64.zip - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: - name: lan-mouse-windows - path: lan-mouse-windows.zip + name: lan-mouse-windows-x86_64 + path: lan-mouse-windows-x86_64.zip macos-release-build: runs-on: macos-15-intel steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: install dependencies - run: brew install gtk4 libadwaita imagemagick + run: | + brew install --cask inkscape + brew install gtk4 libadwaita imagemagick librsvg - name: Release Build run: | cargo build --release @@ -102,21 +132,23 @@ jobs: cd target/release/bundle/osx zip -r "lan-mouse-macos-intel.zip" "Lan Mouse.app" - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: lan-mouse-macos-intel path: target/release/bundle/osx/lan-mouse-macos-intel.zip - macos-aarch64-release-build: - runs-on: macos-14 + macos-arm64-release-build: + runs-on: macos-15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: install dependencies - run: brew install gtk4 libadwaita imagemagick + run: | + brew install --cask inkscape + brew install gtk4 libadwaita imagemagick librsvg - name: Release Build run: | cargo build --release - cp target/release/lan-mouse lan-mouse-macos-aarch64 + cp target/release/lan-mouse lan-mouse-macos-arm64 - name: Make icns run: scripts/makeicns.sh - name: Install cargo bundle @@ -128,29 +160,45 @@ jobs: - name: Zip bundle run: | cd target/release/bundle/osx - zip -r "lan-mouse-macos-aarch64.zip" "Lan Mouse.app" + zip -r "lan-mouse-macos-arm64.zip" "Lan Mouse.app" - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: - name: lan-mouse-macos-aarch64 - path: target/release/bundle/osx/lan-mouse-macos-aarch64.zip + name: lan-mouse-macos-arm64 + path: target/release/bundle/osx/lan-mouse-macos-arm64.zip - pre-release: - name: "Pre Release" - needs: [windows-release-build, linux-release-build, macos-release-build, macos-aarch64-release-build] + release: + name: "Release" + needs: [windows-release-build, linux-release-build, linux-arm64-release-build, macos-release-build, macos-arm64-release-build] runs-on: "ubuntu-latest" steps: - name: Download build artifacts uses: actions/download-artifact@v4 - - name: Create Release - uses: "marvinpinto/action-automatic-releases@latest" + - name: Create Pre-Release + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: softprops/action-gh-release@v2 with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - automatic_release_tag: "latest" + token: ${{ secrets.GITHUB_TOKEN }} + tag_name: ${{ github.event.inputs.name || github.ref_name }} + name: ${{ github.event.inputs.name || github.ref_name }} prerelease: true - title: "Development Build" + generate_release_notes: true files: | - lan-mouse-linux/lan-mouse + lan-mouse-linux-x86_64/lan-mouse-linux-x86_64 + lan-mouse-linux-arm64/lan-mouse-linux-arm64 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 + lan-mouse-macos-arm64/lan-mouse-macos-arm64.zip + lan-mouse-windows-x86_64/lan-mouse-windows-x86_64.zip + - name: Create Tagged Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + name: ${{ github.ref_name }} + generate_release_notes: true + files: | + lan-mouse-linux-x86_64/lan-mouse-linux-x86_64 + lan-mouse-linux-arm64/lan-mouse-linux-arm64 + lan-mouse-macos-intel/lan-mouse-macos-intel.zip + lan-mouse-macos-arm64/lan-mouse-macos-arm64.zip + lan-mouse-windows-x86_64/lan-mouse-windows-x86_64.zip diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ecef06e..e62f710 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,12 +9,16 @@ on: env: CARGO_TERM_COLOR: always +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: fmt: name: Formatting runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: cargo fmt run: cargo fmt --check @@ -35,7 +39,7 @@ jobs: - clippy - test steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: Swatinem/rust-cache@v2 - name: Install Linux deps if: runner.os == 'Linux' diff --git a/.github/workflows/tagged-release.yml b/.github/workflows/tagged-release.yml deleted file mode 100644 index 73075a8..0000000 --- a/.github/workflows/tagged-release.yml +++ /dev/null @@ -1,150 +0,0 @@ -name: "Tagged Release" - -on: - push: - tags: - - v** - -jobs: - linux-release-build: - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - name: install dependencies - 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 - uses: actions/upload-artifact@v4 - with: - name: lan-mouse-linux - path: target/release/lan-mouse - - windows-release-build: - runs-on: windows-latest - steps: - - uses: actions/setup-python@v5 - with: - python-version: '3.11' - # needed for cache restore - - name: create gtk dir - run: mkdir C:\gtk-build\gtk\x64\release - - uses: actions/cache@v3 - id: cache - with: - path: c:/gtk-build/gtk/x64/release/** - key: gtk-windows-build - restore-keys: gtk-windows-build - - name: Update path - run: | - echo "PKG_CONFIG=C:\gtk-build\gtk\x64\release\bin\pkgconf.exe" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - echo "C:\pkg-config-lite-0.28-1\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - echo "C:\gtk-build\gtk\x64\release\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - echo $env:GITHUB_PATH - echo $env:PATH - - name: Install dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: | - # choco install msys2 - # choco install visualstudio2022-workload-vctools - # choco install pkgconfiglite - py -m venv .venv - .venv\Scripts\activate.ps1 - py -m pip install gvsbuild - # see https://github.com/wingtk/gvsbuild/pull/1004 - Move-Item "C:\Program Files\Git\usr\bin" "C:\Program Files\Git\usr\notbin" - Move-Item "C:\Program Files\Git\bin" "C:\Program Files\Git\notbin" - gvsbuild build --msys-dir=C:\msys64 gtk4 libadwaita librsvg - Move-Item "C:\Program Files\Git\usr\notbin" "C:\Program Files\Git\usr\bin" - Move-Item "C:\Program Files\Git\notbin" "C:\Program Files\Git\bin" - - uses: actions/checkout@v4 - - name: Release Build - run: cargo build --release - - name: Create Archive - run: | - mkdir "lan-mouse-windows" - Get-Childitem -Path "C:\\gtk-build\\gtk\\x64\\release\\bin\\*.dll" -File -Recurse | Copy-Item -Destination "lan-mouse-windows" - Copy-Item -Path "target\release\lan-mouse.exe" -Destination "lan-mouse-windows" - Compress-Archive -Path "lan-mouse-windows\*" -DestinationPath lan-mouse-windows.zip - - name: Upload build artifact - uses: actions/upload-artifact@v4 - with: - name: lan-mouse-windows - path: lan-mouse-windows.zip - - macos-release-build: - runs-on: macos-15-intel - steps: - - uses: actions/checkout@v4 - - name: install dependencies - 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 - scripts/copy-macos-dylib.sh "target/release/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse" - - 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.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 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 - scripts/copy-macos-dylib.sh "target/release/bundle/osx/Lan Mouse.app/Contents/MacOS/lan-mouse" - - 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.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, macos-aarch64-release-build] - runs-on: "ubuntu-latest" - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4 - - name: Create Release - uses: "marvinpinto/action-automatic-releases@latest" - with: - repo_token: "${{ secrets.GITHUB_TOKEN }}" - prerelease: false - files: | - lan-mouse-linux/lan-mouse - 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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..05414c4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,65 @@ +# Lan Mouse Agent Instructions + +## Overview + +Lan Mouse is an open-source Software KVM sharing mouse/keyboard input across local networks. The Rust workspace combines a GTK frontend, CLI/daemon mode, and multi-OS capture/emulation backends for Linux, Windows, and macOS. + +## Core principles + +- **Scope discipline.** Only implement what was requested; describe follow-up work instead of absorbing it. +- **Clarify OS behavior.** Ask when requirements touch OS-specific capture/emulation (they differ significantly). +- **Docs stay current.** Update [README.md](README.md) or [DOC.md](DOC.md) when touching public APIs or platform support. +- **Rust idioms.** Use `Result`/`Option`, `thiserror` for errors, descriptive logs, and concise comments for non-obvious invariants. + +## Terminology + +- **Client:** A remote machine that can receive or send input events. Each client is either _active_ (receiving events) or _inactive_ (can send events back). This mutual exclusion prevents feedback loops. +- **Backend:** OS-specific implementation for capture or emulation (e.g., libei, layer-shell, wlroots, X11, Windows, macOS). +- **Handle:** A per-client identifier used to route events and track state (pressed keys, position). + +## Architecture + +**Pipeline:** `input-capture` → `lan-mouse-ipc` → `input-emulation` + +- **input-capture:** Reads OS events into a `Stream`. Backends tried in priority order (libei → layer-shell → X11 → fallback). Tracks `pressed_keys` to avoid stuck modifiers. `position_map` queues events when multiple clients share a screen edge. +- **input-emulation:** Replays events via the `Emulation` trait (`consume`, `create`, `destroy`, `terminate`). Maintains `pressed_keys` and releases them on disconnect. +- **lan-mouse-ipc / lan-mouse-proto:** Protocol glue and serialization. Events are UDP; connection requests are TCP on the same port. Version bumps required when serialization changes. +- **input-event:** Shared scancode enums and abstract event types—extend here, don't duplicate translations. + +## Feature & cfg discipline + +- Feature flags live in root `Cargo.toml`. Gate OS-specific modules with tight cfgs (e.g., `cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))`). +- Prefer module-level gating over per-function cfgs to avoid empty stubs. +- New backends: add feature in `Cargo.toml`, create gated module, log backend selection. + +## Async patterns + +- Tokio runtime with `futures` streams and `async_trait`. Model new flows as streams or async methods. +- Avoid blocking; use `spawn_blocking` if needed. Prefer existing single-threaded stream handling. +- `InputCapture` implements `Stream` and manually pumps backends—don't short-circuit this logic. + +## Commands + +```sh +cargo build --workspace # full build +cargo build -p # single crate +cargo test --workspace # all tests +cargo fmt && cargo clippy --workspace --all-targets --all-features # lint +RUST_LOG=lan_mouse=debug cargo run # debug logging +``` + +Run from repo root—no `cd` in scripts. + +## Testing + +- Unit tests for utilities; integration tests for protocol behavior. +- OS-specific backends: test via GTK/CLI on target OS or document manual verification. +- Dummy backend exercises pipeline without real dependencies. +- Verify `terminate()` releases keys on unexpected disconnect. + +## Workflow + +1. Clarify ambiguous requirements, especially OS-specific behavior. +2. Implement minimal change; flag follow-up work. +3. Add proportional tests; run `cargo test` on affected crates. +4. Run `cargo fmt` and `cargo clippy --workspace --all-targets --all-features`. diff --git a/Cargo.lock b/Cargo.lock index b5fcf02..b241e01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -54,9 +39,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -72,9 +57,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.19" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -87,44 +72,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arraydeque" @@ -140,18 +125,16 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ashpd" -version = "0.11.0" +version = "0.13.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +checksum = "13bdf0fd848239dcd5e64eeeee35dbc00378ebcc6f3aa4ead0a305eec83d0cfb" dependencies = [ "enumflags2", - "futures-channel", "futures-util", - "rand 0.9.2", + "getrandom 0.4.2", "serde", "serde_repr", "tokio", - "url", "zbus", ] @@ -231,9 +214,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", @@ -246,21 +229,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -275,9 +243,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bincode" @@ -296,9 +264,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -320,9 +288,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -332,9 +300,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cairo-rs" @@ -342,7 +310,7 @@ version = "0.20.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e3bd0f4e25afa9cabc157908d14eeef9067d6448c49414d17b3fb55f0eadd0" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "cairo-sys-rs", "glib", "libc", @@ -361,51 +329,35 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.10" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" dependencies = [ - "serde", + "serde_core", ] [[package]] name = "cargo-platform" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84982c6c0ae343635a3a4ee6dedef965513735c8b183caa7289fa6e27399ebd4" +checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" dependencies = [ "serde", -] - -[[package]] -name = "cargo-util-schemas" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63d2780ac94487eb9f1fea7b0d56300abc9eb488800854ca217f102f5caccca" -dependencies = [ - "semver", - "serde", - "serde-untagged", - "serde-value", - "thiserror 1.0.69", - "toml", - "unicode-xid", - "url", + "serde_core", ] [[package]] name = "cargo_metadata" -version = "0.20.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7835cfc6135093070e95eb2b53e5d9b5c403dc3a6be6040ee026270aa82502" +checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", "cargo-platform", - "cargo-util-schemas", "semver", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -419,10 +371,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.30" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -442,9 +395,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.1" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d0390889d58f934f01cd49736275b4c2da15bcfc328c78ff2349907e6cabf22" +checksum = "3c6b04e07d8080154ed4ac03546d9a2b303cc2fe1901ba0b35b301516e289368" dependencies = [ "smallvec", "target-lexicon", @@ -452,15 +405,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cipher" @@ -474,9 +421,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.42" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -484,9 +431,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.42" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -496,9 +443,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -508,15 +455,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "concurrent-queue" @@ -535,9 +482,9 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -575,7 +522,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation", "core-graphics-types", "foreign-types", @@ -588,7 +535,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation", "libc", ] @@ -646,9 +593,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -692,9 +639,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" @@ -723,9 +670,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -796,9 +743,9 @@ dependencies = [ [[package]] name = "endi" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" [[package]] name = "enum-as-inner" @@ -835,9 +782,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" dependencies = [ "log", "regex", @@ -845,9 +792,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ "anstream", "anstyle", @@ -862,31 +809,21 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" -dependencies = [ - "serde", - "typeid", -] - [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -905,9 +842,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" @@ -935,6 +872,18 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.5.0" @@ -964,18 +913,27 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] [[package]] -name = "futures" -version = "0.3.31" +name = "fsevent-sys" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -988,9 +946,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -998,15 +956,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1015,15 +973,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ "fastrand", "futures-core", @@ -1034,9 +992,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1045,21 +1003,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1069,7 +1027,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1130,20 +1087,6 @@ dependencies = [ "system-deps", ] -[[package]] -name = "generator" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1157,25 +1100,38 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", ] [[package]] @@ -1188,12 +1144,6 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "gio" version = "0.20.12" @@ -1226,11 +1176,11 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "libc", "libgit2-sys", "log", @@ -1243,7 +1193,7 @@ version = "0.20.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc4b6e352d4716d84d7dde562dd9aee2a7d48beb872dd9ece7f2d1515b2d683" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -1420,9 +1370,18 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -1454,7 +1413,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.12", + "thiserror 2.0.18", "tinyvec", "tokio", "tracing", @@ -1477,7 +1436,7 @@ dependencies = [ "rand 0.9.2", "resolv-conf", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -1502,20 +1461,20 @@ dependencies = [ [[package]] name = "hostname" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" +checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd" dependencies = [ "cfg-if", "libc", - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1523,7 +1482,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core", + "windows-core 0.62.2", ] [[package]] @@ -1537,12 +1496,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1550,9 +1510,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1563,11 +1523,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1578,42 +1537,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1622,10 +1577,16 @@ dependencies = [ ] [[package]] -name = "idna" -version = "1.0.3" +name = "id-arena" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -1644,12 +1605,34 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inotify" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" +dependencies = [ + "bitflags 2.11.0", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", ] [[package]] @@ -1668,7 +1651,7 @@ version = "0.3.0" dependencies = [ "ashpd", "async-trait", - "bitflags 2.9.1", + "bitflags 2.11.0", "core-foundation", "core-foundation-sys", "core-graphics", @@ -1682,7 +1665,7 @@ dependencies = [ "once_cell", "reis", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tokio-util", "wayland-client", @@ -1698,7 +1681,9 @@ version = "0.3.0" dependencies = [ "ashpd", "async-trait", - "bitflags 2.9.1", + "bitflags 2.11.0", + "core-foundation", + "core-foundation-sys", "core-graphics", "futures", "input-event", @@ -1706,7 +1691,7 @@ dependencies = [ "log", "once_cell", "reis", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "wayland-client", "wayland-protocols", @@ -1725,37 +1710,27 @@ dependencies = [ "num_enum", "reis", "serde", - "thiserror 2.0.12", -] - -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", + "thiserror 2.0.18", ] [[package]] name = "ipconfig" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" dependencies = [ - "socket2 0.5.10", + "socket2", "widestring", - "windows-sys 0.48.0", - "winreg", + "windows-registry", + "windows-result 0.4.1", + "windows-sys 0.61.2", ] [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "is_debug" @@ -1765,34 +1740,34 @@ checksum = "1fe266d2e243c931d8190177f20bf7f24eed45e96f39e87dc49a27b32d12d407" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", "portable-atomic", "portable-atomic-util", - "serde", + "serde_core", ] [[package]] name = "jiff-static" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", @@ -1801,19 +1776,19 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ "once_cell", "wasm-bindgen", @@ -1827,7 +1802,7 @@ checksum = "29541831d33940ea1c68a1b8980382c1a507c95a528a98c0e335b361b9726975" dependencies = [ "arraydeque", "arrayvec", - "bitflags 2.9.1", + "bitflags 2.11.0", "keycode_macro", ] @@ -1843,6 +1818,26 @@ dependencies = [ "quote", ] +[[package]] +name = "kqueue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lan-mouse" version = "0.10.0" @@ -1861,6 +1856,7 @@ dependencies = [ "libc", "local-channel", "log", + "notify", "rcgen", "rustls", "serde", @@ -1868,11 +1864,11 @@ dependencies = [ "sha2", "shadow-rs", "slab", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tokio-util", - "toml", - "toml_edit", + "toml 0.8.23", + "toml_edit 0.22.27", "webrtc-dtls", "webrtc-util", ] @@ -1884,7 +1880,7 @@ dependencies = [ "clap", "futures", "lan-mouse-ipc", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", ] @@ -1899,7 +1895,7 @@ dependencies = [ "lan-mouse-ipc", "libadwaita", "log", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -1910,7 +1906,7 @@ dependencies = [ "log", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tokio-stream", ] @@ -1922,7 +1918,7 @@ dependencies = [ "input-event", "num_enum", "paste", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -1931,6 +1927,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libadwaita" version = "0.7.2" @@ -1964,15 +1966,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libgit2-sys" -version = "0.18.2+1.9.1" +version = "0.18.3+1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" +checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" dependencies = [ "cc", "libc", @@ -1982,9 +1984,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.22" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" +checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" dependencies = [ "cc", "libc", @@ -2000,15 +2002,15 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "local-channel" @@ -2029,47 +2031,24 @@ checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap" @@ -2105,42 +2084,32 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - [[package]] name = "mio" -version = "1.0.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "log", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "moka" -version = "0.12.10" +version = "0.12.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "loom", + "equivalent", "parking_lot", "portable-atomic", - "rustc_version", "smallvec", "tagptr", - "thiserror 1.0.69", "uuid", ] @@ -2157,19 +2126,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "cfg_aliases", - "libc", - "memoffset 0.9.1", -] - [[package]] name = "nom" version = "7.1.3" @@ -2181,13 +2137,30 @@ dependencies = [ ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "notify" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "overload", - "winapi", + "bitflags 2.11.0", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "walkdir", + "windows-sys 0.60.2", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.0", ] [[package]] @@ -2202,9 +2175,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2226,9 +2199,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", "rustversion", @@ -2236,9 +2209,9 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2255,15 +2228,6 @@ dependencies = [ "libc", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "oid-registry" version = "0.7.1" @@ -2275,9 +2239,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" dependencies = [ "critical-section", "portable-atomic", @@ -2285,9 +2249,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "opaque-debug" @@ -2295,15 +2259,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "ordered-stream" version = "0.2.0" @@ -2314,12 +2269,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.13.2" @@ -2376,9 +2325,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -2386,15 +2335,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -2405,12 +2354,12 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ "base64", - "serde", + "serde_core", ] [[package]] @@ -2424,15 +2373,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2470,24 +2419,24 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2507,6 +2456,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -2518,36 +2477,36 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "toml_edit", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "958f21e8e7ceb5a1aa7fa87fab28e7c75976e0bfe7e23ff069e0a260f894067d" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2558,6 +2517,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -2576,7 +2541,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2596,7 +2561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2605,16 +2570,16 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -2632,56 +2597,41 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reis" @@ -2696,9 +2646,9 @@ dependencies = [ [[package]] name = "resolv-conf" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "rfc6979" @@ -2718,18 +2668,12 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", ] -[[package]] -name = "rustc-demangle" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - [[package]] name = "rustc_version" version = "0.4.1" @@ -2754,7 +2698,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -2763,22 +2707,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "once_cell", "ring", @@ -2790,18 +2734,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", @@ -2810,21 +2754,18 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "ryu" -version = "1.0.20" +name = "same-file" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "scopeguard" @@ -2848,48 +2789,38 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", + "serde_core", ] [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] -[[package]] -name = "serde-untagged" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" -dependencies = [ - "erased-serde", - "serde", - "typeid", -] - -[[package]] -name = "serde-value" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" -dependencies = [ - "ordered-float", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -2898,14 +2829,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -2928,6 +2860,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2952,9 +2893,9 @@ dependencies = [ [[package]] name = "shadow-rs" -version = "1.2.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6fd27df794ced2ef39872879c93a9f87c012607318af8621cd56d2c3a8b3a2" +checksum = "3c798acfc78a69c7b038adde44084d8df875555b091da42c90ae46257cdcc41a" dependencies = [ "cargo_metadata", "const_format", @@ -2965,15 +2906,6 @@ dependencies = [ "tzdb", ] -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - [[package]] name = "shlex" version = "1.3.0" @@ -2982,10 +2914,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -3001,9 +2934,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.10" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -3013,22 +2946,12 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" -dependencies = [ - "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3043,15 +2966,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -3067,9 +2984,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.104" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3089,14 +3006,14 @@ dependencies = [ [[package]] name = "system-deps" -version = "7.0.5" +version = "7.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" dependencies = [ "cfg-expr", "heck", "pkg-config", - "toml", + "toml 1.1.2+spec-1.1.0", "version-compare", ] @@ -3108,21 +3025,21 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "target-lexicon" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.20.0" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.3", + "getrandom 0.4.2", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix 1.1.4", + "windows-sys 0.61.2", ] [[package]] @@ -3136,11 +3053,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] @@ -3156,29 +3073,20 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - [[package]] name = "time" -version = "0.3.41" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -3186,22 +3094,22 @@ dependencies = [ "num-conv", "num_threads", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -3209,9 +3117,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3219,9 +3127,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -3234,30 +3142,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43864ed400b6043a4757a25c7a64a8efde741aed79a056a2fb348a406701bb35" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3266,9 +3171,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -3277,9 +3182,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3295,9 +3200,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.1", ] [[package]] @@ -3309,6 +3229,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -3317,10 +3246,31 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.1", ] [[package]] @@ -3330,10 +3280,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] -name = "tracing" -version = "0.1.41" +name = "toml_writer" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -3342,9 +3298,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -3353,66 +3309,30 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", ] -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "tz-rs" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1450bf2b99397e72070e7935c89facaa80092ac812502200375f1f7d33c71a1" +checksum = "4fc6c929ffa10fb34f4a3c7e9a73620a83ef2e85e47f9ec3381b8289e6762f42" [[package]] name = "tzdb" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be2ea5956f295449f47c0b825c5e109022ff1a6a53bb4f77682a87c2341fbf5" +checksum = "56d4e985b6dda743ae7fd4140c28105316ffd75bc58258ee6cc12934e3eb7a0c" dependencies = [ "iana-time-zone", "tz-rs", @@ -3421,29 +3341,29 @@ dependencies = [ [[package]] name = "tzdb_data" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4c81d75033770e40fbd3643ce7472a1a9fd301f90b7139038228daf8af03ec" +checksum = "125a0a63c4bd75c73f61863463cb400db4b1aa5039b203b0ee1d628a7e3dabb2" dependencies = [ "tz-rs", ] [[package]] name = "uds_windows" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset 0.9.1", "tempfile", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" @@ -3469,9 +3389,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -3493,21 +3413,16 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.17.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.4.2", "js-sys", + "serde_core", "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3516,9 +3431,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version-compare" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" [[package]] name = "version_check" @@ -3526,6 +3441,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3533,45 +3458,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" 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", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3579,58 +3500,92 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] [[package]] -name = "wayland-backend" -version = "0.3.11" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" dependencies = [ "cc", "downcast-rs", - "rustix 1.0.8", + "rustix 1.1.4", "smallvec", "wayland-sys", ] [[package]] name = "wayland-client" -version = "0.31.11" +version = "0.31.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" dependencies = [ - "bitflags 2.9.1", - "rustix 1.0.8", + "bitflags 2.11.0", + "rustix 1.1.4", "wayland-backend", "wayland-scanner", ] [[package]] name = "wayland-protocols" -version = "0.32.9" +version = "0.32.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +checksum = "563a85523cade2429938e790815fd7319062103b9f4a2dc806e9b53b95982d8f" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-scanner", @@ -3638,11 +3593,11 @@ dependencies = [ [[package]] name = "wayland-protocols-misc" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfe33d551eb8bffd03ff067a8b44bb963919157841a99957151299a6307d19c" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3651,11 +3606,11 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.11.0", "wayland-backend", "wayland-client", "wayland-protocols", @@ -3664,9 +3619,9 @@ dependencies = [ [[package]] name = "wayland-scanner" -version = "0.31.7" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" dependencies = [ "proc-macro2", "quick-xml", @@ -3675,9 +3630,9 @@ dependencies = [ [[package]] name = "wayland-sys" -version = "0.31.7" +version = "0.31.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" dependencies = [ "pkg-config", ] @@ -3733,7 +3688,7 @@ dependencies = [ "lazy_static", "libc", "log", - "nix 0.26.4", + "nix", "portable-atomic", "rand 0.8.5", "thiserror 1.0.69", @@ -3743,9 +3698,9 @@ dependencies = [ [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -3763,6 +3718,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3776,9 +3740,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core", + "windows-core 0.61.2", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -3788,7 +3752,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core", + "windows-core 0.61.2", ] [[package]] @@ -3799,9 +3763,22 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", - "windows-result", - "windows-strings", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -3810,16 +3787,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core", - "windows-link", + "windows-core 0.61.2", + "windows-link 0.1.3", "windows-threading", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -3828,9 +3805,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -3843,14 +3820,31 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core", - "windows-link", + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -3859,7 +3853,16 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -3868,16 +3871,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link 0.2.1", ] [[package]] @@ -3904,22 +3907,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link 0.2.1", ] [[package]] @@ -3940,19 +3937,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -3961,15 +3958,9 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -3978,15 +3969,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -3996,15 +3981,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -4014,9 +3993,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -4026,15 +4005,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -4044,15 +4017,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -4062,15 +4029,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -4080,15 +4041,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -4098,43 +4053,121 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "bitflags 2.9.1", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x11" @@ -4186,11 +4219,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -4198,9 +4230,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4210,9 +4242,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.9.0" +version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad" +checksum = "ca82f95dbd3943a40a53cfded6c2d0a2ca26192011846a1810c4256ef92c60bc" dependencies = [ "async-broadcast", "async-recursion", @@ -4222,15 +4254,17 @@ dependencies = [ "futures-core", "futures-lite", "hex", - "nix 0.30.1", + "libc", "ordered-stream", + "rustix 1.1.4", "serde", "serde_repr", "tokio", "tracing", "uds_windows", - "windows-sys 0.59.0", - "winnow", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.15", "zbus_macros", "zbus_names", "zvariant", @@ -4238,9 +4272,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.9.0" +version = "5.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659" +checksum = "897e79616e84aac4b2c46e9132a4f63b93105d54fe8c0e8f6bffc21fa8d49222" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -4253,30 +4287,29 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.2.0" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" dependencies = [ "serde", - "static_assertions", - "winnow", + "winnow 0.7.15", "zvariant", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4285,18 +4318,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4306,18 +4339,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -4326,9 +4359,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4337,9 +4370,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4348,9 +4381,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", @@ -4358,25 +4391,30 @@ dependencies = [ ] [[package]] -name = "zvariant" -version = "5.6.0" +name = "zmij" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "5.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5708299b21903bbe348e94729f22c49c55d04720a004aa350f1f9c122fd2540b" dependencies = [ "endi", "enumflags2", "serde", - "url", - "winnow", + "winnow 0.7.15", "zvariant_derive", "zvariant_utils", ] [[package]] name = "zvariant_derive" -version = "5.6.0" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208" +checksum = "5b59b012ebe9c46656f9cc08d8da8b4c726510aef12559da3e5f1bf72780752c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -4387,14 +4425,13 @@ dependencies = [ [[package]] name = "zvariant_utils" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34" +checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" dependencies = [ "proc-macro2", "quote", "serde", - "static_assertions", "syn", - "winnow", + "winnow 0.7.15", ] diff --git a/Cargo.toml b/Cargo.toml index 29c0097..dc673fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ rustls = { version = "0.23.12", default-features = false, features = [ ] } rcgen = "0.13.1" sha2 = "0.10.8" +notify = "8.2.0" [target.'cfg(unix)'.dependencies] libc = "0.2.148" @@ -95,3 +96,5 @@ rdp_emulation = ["input-emulation/remote_desktop_portal"] name = "Lan Mouse" icon = ["target/icon.icns"] identifier = "de.feschber.LanMouse" +osx_info_plist_exts = ["build-aux/macos-lsui-element.plist"] +resources = ["target/menubar-template.png"] diff --git a/README.md b/README.md index 00e9674..78834c3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ # Lan Mouse + +[![CI](https://github.com/feschber/lan-mouse/actions/workflows/rust.yml/badge.svg)](https://github.com/feschber/lan-mouse/actions/workflows/rust.yml) [![Cachix](https://github.com/feschber/lan-mouse/actions/workflows/cachix.yml/badge.svg)](https://github.com/feschber/lan-mouse/actions/workflows/cachix.yml) [![Release](https://github.com/feschber/lan-mouse/actions/workflows/release.yml/badge.svg)](https://github.com/feschber/lan-mouse/actions/workflows/release.yml) + +[![crates.io](https://img.shields.io/crates/v/lan-mouse.svg)](https://crates.io/crates/lan-mouse) [![license](https://img.shields.io/crates/l/lan-mouse.svg)](https://github.com/feschber/lan-mouse/blob/main/Cargo.toml) + Lan Mouse is a *cross-platform* mouse and keyboard sharing software similar to universal-control on Apple devices. It allows for using multiple PCs via a single set of mouse and keyboard. This is also known as a Software KVM switch. @@ -100,6 +105,7 @@ dnf install lan-mouse - Unzip it - Remove the quarantine with `xattr -rd com.apple.quarantine "Lan Mouse.app"` - Launch the app +- Use the menu bar item to open the settings window or quit Lan Mouse. Bundled macOS builds run as a menu bar app and do not keep a Dock icon visible. - Grant accessibility permissions in System Preferences @@ -177,8 +183,27 @@ For a detailed list of available features, checkout the [Cargo.toml](./Cargo.tom +## Development -## Installing Dependencies for Development / Compiling from Source +### Git pre-commit hook + +This repository includes a local git hooks directory `.githooks/` with a `pre-commit` script that enforces formatting, lints, and tests before allowing a commit. It is optional to enable it, but it will prevent you from committing code with failing unit tests or that needs clippy/fmt fixes. To enable the hook locally: + +1. Make the hook executable: + +```sh +chmod +x .githooks/pre-commit +``` + +2. Point git to the hooks directory (one-time per clone): + +```sh +git config core.hooksPath .githooks +``` + +The `pre-commit` script runs `cargo fmt --all` (and fails if files were modified), `cargo clippy --workspace --all-targets --all-features -- -D warnings`, and `cargo test --workspace --all-features`. + +### Dependencies & Compiling from Source
MacOS diff --git a/build-aux/macos-lsui-element.plist b/build-aux/macos-lsui-element.plist new file mode 100644 index 0000000..5ac2193 --- /dev/null +++ b/build-aux/macos-lsui-element.plist @@ -0,0 +1,8 @@ + LSUIElement + + NSAppSleepDisabled + + NSInputMonitoringUsageDescription + Lan Mouse needs Input Monitoring access to capture keyboard and mouse input and forward it to remote machines on your network. + NSAppleEventsUsageDescription + Lan Mouse uses Apple Events to deliver synthesized keyboard and mouse events to the system. diff --git a/input-capture/Cargo.toml b/input-capture/Cargo.toml index 0f77eb2..9b8d598 100644 --- a/input-capture/Cargo.toml +++ b/input-capture/Cargo.toml @@ -12,7 +12,7 @@ futures-core = "0.3.30" log = "0.4.22" input-event = { path = "../input-event", version = "0.3.0" } memmap = "0.7" -tempfile = "3.8" +tempfile = "3.25.0" thiserror = "2.0.0" tokio = { version = "1.32.0", features = [ "io-util", @@ -41,7 +41,8 @@ wayland-protocols-wlr = { version = "0.3.1", features = [ "client", ], optional = true } x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true } -ashpd = { version = "0.11.0", default-features = false, features = [ +ashpd = { version = "0.13.9", default-features = false, features = [ + "input_capture", "tokio", ], optional = true } reis = { version = "0.5.0", features = ["tokio"], optional = true } diff --git a/input-capture/src/error.rs b/input-capture/src/error.rs index 469234a..7324156 100644 --- a/input-capture/src/error.rs +++ b/input-capture/src/error.rs @@ -149,6 +149,10 @@ pub enum MacosCaptureCreationError { #[cfg(target_os = "macos")] #[error("event tap creation failed")] EventTapCreation, + #[error("accessibility permission is required")] + AccessibilityPermission, + #[error("input monitoring permission is required")] + InputMonitoringPermission, #[error("failed to set CG Cursor property")] CGCursorProperty, #[cfg(target_os = "macos")] diff --git a/input-capture/src/lib.rs b/input-capture/src/lib.rs index 4767503..b1ef6c0 100644 --- a/input-capture/src/lib.rs +++ b/input-capture/src/lib.rs @@ -171,6 +171,19 @@ impl InputCapture { self.capture.release().await } + /// Drain and return every key the capture has forwarded as + /// down-but-not-up. The caller is expected to synthesize key-up + /// events to the remote peer for each — otherwise the peer + /// retains phantom-held keys after capture is released. The + /// canonical case is the release-bind chord + /// (Ctrl+Shift+Alt+Meta): the down events were sent while + /// capture was active, but the matching up events arrive after + /// the local tap has flipped to passthrough and never reach + /// the peer. + pub fn take_pressed_keys(&mut self) -> HashSet { + std::mem::take(&mut self.pressed_keys) + } + /// destroy the input capture pub async fn terminate(&mut self) -> Result<(), CaptureError> { self.capture.terminate().await diff --git a/input-capture/src/libei.rs b/input-capture/src/libei.rs index 697b782..fa16895 100644 --- a/input-capture/src/libei.rs +++ b/input-capture/src/libei.rs @@ -2,8 +2,8 @@ use ashpd::{ desktop::{ Session, input_capture::{ - Activated, ActivatedBarrier, Barrier, BarrierID, Capabilities, InputCapture, Region, - Zones, + Activated, ActivatedBarrier, Barrier, BarrierID, Capabilities, CreateSessionOptions, + InputCapture, Region, ReleaseOptions, Zones, }, }, enumflags2::BitFlags, @@ -58,8 +58,8 @@ enum LibeiNotifyEvent { } #[allow(dead_code)] -pub struct LibeiInputCapture<'a> { - input_capture: Pin>>, +pub struct LibeiInputCapture { + input_capture: Pin>, capture_task: JoinHandle>, event_rx: Receiver<(Position, CaptureEvent)>, notify_capture: Sender, @@ -130,12 +130,15 @@ fn select_barriers( } async fn update_barriers( - input_capture: &InputCapture<'_>, - session: &Session<'_, InputCapture<'_>>, + input_capture: &InputCapture, + session: &Session, active_clients: &[Position], next_barrier_id: &mut NonZeroU32, ) -> Result<(Vec, HashMap), ashpd::Error> { - let zones = input_capture.zones(session).await?.response()?; + let zones = input_capture + .zones(session, Default::default()) + .await? + .response()?; log::debug!("zones: {zones:?}"); let (barriers, id_map) = select_barriers(&zones, active_clients, next_barrier_id); @@ -144,31 +147,38 @@ async fn update_barriers( let ashpd_barriers: Vec = barriers.iter().copied().map(|b| b.into()).collect(); let response = input_capture - .set_pointer_barriers(session, &ashpd_barriers, zones.zone_set()) + .set_pointer_barriers( + session, + &ashpd_barriers, + zones.zone_set(), + Default::default(), + ) .await?; let response = response.response()?; log::debug!("{response:?}"); Ok((barriers, id_map)) } -async fn create_session<'a>( - input_capture: &'a InputCapture<'a>, -) -> std::result::Result<(Session<'a, InputCapture<'a>>, BitFlags), ashpd::Error> { +async fn create_session( + input_capture: &InputCapture, +) -> std::result::Result<(Session, BitFlags), ashpd::Error> { log::debug!("creating input capture session"); + let create_session_options = CreateSessionOptions::default().set_capabilities( + Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen, + ); input_capture - .create_session( - None, - Capabilities::Keyboard | Capabilities::Pointer | Capabilities::Touchscreen, - ) + .create_session(None, create_session_options) .await } async fn connect_to_eis( - input_capture: &InputCapture<'_>, - session: &Session<'_, InputCapture<'_>>, + input_capture: &InputCapture, + session: &Session, ) -> Result<(ei::Context, Connection, EiConvertEventStream), CaptureError> { log::debug!("connect_to_eis"); - let fd = input_capture.connect_to_eis(session).await?; + let fd = input_capture + .connect_to_eis(session, Default::default()) + .await?; // create unix stream from fd let stream = UnixStream::from(fd); @@ -201,10 +211,10 @@ async fn libei_event_handler( } } -impl LibeiInputCapture<'_> { +impl LibeiInputCapture { pub async fn new() -> std::result::Result { let input_capture = Box::pin(InputCapture::new().await?); - let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>; + let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture; let first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?); let (event_tx, event_rx) = mpsc::channel(1); @@ -238,10 +248,10 @@ impl LibeiInputCapture<'_> { } async fn do_capture( - input_capture: *const InputCapture<'static>, + input_capture: *const InputCapture, mut capture_event: Receiver, notify_release: Arc, - session: Option<(Session<'_, InputCapture<'_>>, BitFlags)>, + session: Option<(Session, BitFlags)>, event_tx: Sender<(Position, CaptureEvent)>, cancellation_token: CancellationToken, ) -> Result<(), CaptureError> { @@ -307,7 +317,7 @@ async fn do_capture( // disable capture log::debug!("disabling input capture"); - if let Err(e) = input_capture.disable(&session).await { + if let Err(e) = input_capture.disable(&session, Default::default()).await { log::warn!("input_capture.disable(&session) {e}"); } if let Err(e) = session.close().await { @@ -336,8 +346,8 @@ async fn do_capture( } async fn do_capture_session( - input_capture: &InputCapture<'_>, - session: &mut Session<'_, InputCapture<'_>>, + input_capture: &InputCapture, + session: &mut Session, event_tx: &Sender<(Position, CaptureEvent)>, active_clients: &[Position], next_barrier_id: &mut NonZeroU32, @@ -356,7 +366,7 @@ async fn do_capture_session( update_barriers(input_capture, session, active_clients, next_barrier_id).await?; log::debug!("enabling session"); - input_capture.enable(session).await?; + input_capture.enable(session, Default::default()).await?; // cancellation token to release session let release_session = Arc::new(Notify::new()); @@ -462,9 +472,9 @@ async fn do_capture_session( Ok(()) } -async fn release_capture<'a>( - input_capture: &InputCapture<'a>, - session: &Session<'a, InputCapture<'a>>, +async fn release_capture( + input_capture: &InputCapture, + session: &Session, activated: Activated, current_pos: Position, ) -> Result<(), CaptureError> { @@ -484,9 +494,10 @@ async fn release_capture<'a>( }; // release 1px to the right of the entered zone let cursor_position = (x as f64 + dx, y as f64 + dy); - input_capture - .release(session, activated.activation_id(), Some(cursor_position)) - .await?; + let release_options = ReleaseOptions::default() + .set_activation_id(activated.activation_id()) + .set_cursor_position(Some(cursor_position)); + input_capture.release(session, release_options).await?; Ok(()) } @@ -561,7 +572,7 @@ async fn handle_ei_event( } #[async_trait] -impl LanMouseInputCapture for LibeiInputCapture<'_> { +impl LanMouseInputCapture for LibeiInputCapture { async fn create(&mut self, pos: Position) -> Result<(), CaptureError> { let _ = self .notify_capture @@ -598,7 +609,7 @@ impl LanMouseInputCapture for LibeiInputCapture<'_> { } } -impl Drop for LibeiInputCapture<'_> { +impl Drop for LibeiInputCapture { fn drop(&mut self) { if !self.terminated { /* this workaround is needed until async drop is stabilized */ @@ -607,10 +618,10 @@ impl Drop for LibeiInputCapture<'_> { } } -impl Stream for LibeiInputCapture<'_> { +impl Stream for LibeiInputCapture { type Item = Result<(Position, CaptureEvent), CaptureError>; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { match self.capture_task.poll_unpin(cx) { Poll::Ready(r) => match r.expect("failed to join") { Ok(()) => Poll::Ready(None), diff --git a/input-capture/src/macos.rs b/input-capture/src/macos.rs index 2abb15e..dc941b2 100644 --- a/input-capture/src/macos.rs +++ b/input-capture/src/macos.rs @@ -2,7 +2,7 @@ use super::{Capture, CaptureError, CaptureEvent, Position, error::MacosCaptureCr use async_trait::async_trait; use bitflags::bitflags; use core_foundation::{ - base::{CFRelease, kCFAllocatorDefault}, + base::{CFRelease, TCFType, kCFAllocatorDefault}, date::CFTimeInterval, number::{CFBooleanRef, kCFBooleanTrue}, runloop::{CFRunLoop, CFRunLoopSource, kCFRunLoopCommonModes}, @@ -28,7 +28,7 @@ use std::{ collections::HashSet, ffi::{CString, c_char}, pin::Pin, - sync::Arc, + sync::{Arc, OnceLock}, task::{Context, Poll, ready}, thread::{self}, }; @@ -67,6 +67,7 @@ enum ProducerEvent { Destroy(Position), Grab(Position), EventTapDisabled, + DisplayReconfigured, } impl InputCaptureState { @@ -176,7 +177,31 @@ impl InputCaptureState { } self.active_clients.remove(&p); } - ProducerEvent::EventTapDisabled => return Err(CaptureError::EventTapDisabled), + ProducerEvent::EventTapDisabled => { + // Tap death can happen mid-capture (TCC Accessibility + // revoked, tap-timeout, etc). Release state so we + // don't leave the cursor hidden even if the outer + // task only logs this error rather than propagating. + if self.current_pos.is_some() { + self.show_cursor()?; + self.current_pos = None; + } + return Err(CaptureError::EventTapDisabled); + } + ProducerEvent::DisplayReconfigured => { + // The macOS display configuration changed — a monitor + // was plugged in/out, the resolution changed, the + // arrangement was rearranged, etc. Re-fetch the + // active-display bounds so barrier crossings and the + // cursor-warp on capture-start use the current + // geometry instead of whatever was true at process + // start. + if let Err(e) = self.update_bounds() { + log::warn!("failed to refresh display bounds: {e}"); + } else { + log::info!("display reconfigured: {:?}", self.bounds); + } + } }; Ok(()) } @@ -385,6 +410,14 @@ fn create_event_tap<'a>( notify_tx: Sender, event_tx: Sender<(Position, CaptureEvent)>, ) -> Result, MacosCaptureCreationError> { + // Shared slot for the tap's mach port pointer. Stored as `usize` + // because raw pointers aren't `Send`, but the integer + // representation is — and CGEventTapEnable is documented as + // thread-safe. Set immediately after CGEventTap::new returns; + // read by the callback to recover from a TapDisabledByTimeout. + let tap_mach_port: Arc> = Arc::new(OnceLock::new()); + let tap_mach_port_cb = Arc::clone(&tap_mach_port); + let cg_events_of_interest: Vec = vec![ CGEventType::LeftMouseDown, CGEventType::LeftMouseUp, @@ -402,76 +435,114 @@ fn create_event_tap<'a>( CGEventType::FlagsChanged, ]; - let event_tap_callback = - move |_proxy: CGEventTapProxy, event_type: CGEventType, cg_ev: &CGEvent| { - log::trace!("Got event from tap: {event_type:?}"); - let mut state = client_state.blocking_lock(); - let mut capture_position = None; - let mut res_events = vec![]; + let event_tap_callback = move |_proxy: CGEventTapProxy, + event_type: CGEventType, + cg_ev: &CGEvent| { + log::trace!("Got event from tap: {event_type:?}"); + let mut state = client_state.blocking_lock(); + let mut capture_position = None; + let mut res_events = vec![]; + if matches!(event_type, CGEventType::TapDisabledByTimeout) { + // The kernel disables the tap when our callback runs + // longer than ~1s on a single event — typical causes + // are heavy load, scheduler contention, or this + // process being briefly suspended (e.g. App Nap on a + // long idle). It is NOT a fatal condition: Apple's + // documented recovery is to call CGEventTapEnable + // and resume processing. Re-enable in place and KEEP + // existing capture state so the user doesn't see the + // cursor pop back to the local screen mid-session. + if let Some(&port) = tap_mach_port_cb.get() { + log::warn!("CGEventTap disabled by timeout — re-enabling"); + unsafe { + CGEventTapEnable(port as *mut c_void, true); + } + } else { + log::error!( + "CGEventTap disabled by timeout, but mach port not yet stored — cannot re-enable" + ); + } + return CallbackResult::Keep; + } + + if matches!(event_type, CGEventType::TapDisabledByUserInput) { + // Deliberate kill — secure-input mode (e.g. password + // field), TCC Accessibility revoked mid-session, or + // the user disabling event-monitoring. We can't + // recover from this; drop captured state synchronously + // and return Keep on this event. Otherwise the + // `current_pos.is_some()` branch below would drop this + // event (and any racing callback still in flight) back + // into `CallbackResult::Drop`, silently eating the + // user's clicks and keypresses while the tap winds + // down. Clear state + show the cursor here, then + // notify the producer loop so the service can tear + // down cleanly. + log::error!("CGEventTap disabled by user input, releasing capture state"); + if state.current_pos.is_some() { + let _ = CGDisplay::show_cursor(&CGDisplay::main()); + state.current_pos = None; + } + notify_tx + .blocking_send(ProducerEvent::EventTapDisabled) + .unwrap_or_else(|e| { + log::error!("Failed to send notification: {e}"); + }); + return CallbackResult::Keep; + } + + // Are we in a client? + if let Some(current_pos) = state.current_pos { + capture_position = Some(current_pos); + get_events( + &event_type, + cg_ev, + &mut res_events, + &mut state.modifier_state, + ) + .unwrap_or_else(|e| { + log::error!("Failed to get events: {e}"); + }); + + // Keep (hidden) cursor at the edge of the screen if matches!( event_type, - CGEventType::TapDisabledByTimeout | CGEventType::TapDisabledByUserInput + CGEventType::MouseMoved + | CGEventType::LeftMouseDragged + | CGEventType::RightMouseDragged + | CGEventType::OtherMouseDragged ) { - log::error!("CGEventTap disabled"); + state.reset_cursor().unwrap_or_else(|e| log::warn!("{e}")); + } + } else if matches!(event_type, CGEventType::MouseMoved) { + // Did we cross a barrier? + if let Some(new_pos) = state.crossed(cg_ev) { + capture_position = Some(new_pos); + state + .start_capture(cg_ev, new_pos) + .unwrap_or_else(|e| log::warn!("{e}")); + res_events.push(CaptureEvent::Begin); notify_tx - .blocking_send(ProducerEvent::EventTapDisabled) - .unwrap_or_else(|e| { - log::error!("Failed to send notification: {e}"); - }); + .blocking_send(ProducerEvent::Grab(new_pos)) + .expect("Failed to send notification"); } + } - // Are we in a client? - if let Some(current_pos) = state.current_pos { - capture_position = Some(current_pos); - get_events( - &event_type, - cg_ev, - &mut res_events, - &mut state.modifier_state, - ) - .unwrap_or_else(|e| { - log::error!("Failed to get events: {e}"); - }); - - // Keep (hidden) cursor at the edge of the screen - if matches!( - event_type, - CGEventType::MouseMoved - | CGEventType::LeftMouseDragged - | CGEventType::RightMouseDragged - | CGEventType::OtherMouseDragged - ) { - state.reset_cursor().unwrap_or_else(|e| log::warn!("{e}")); - } - } else if matches!(event_type, CGEventType::MouseMoved) { - // Did we cross a barrier? - if let Some(new_pos) = state.crossed(cg_ev) { - capture_position = Some(new_pos); - state - .start_capture(cg_ev, new_pos) - .unwrap_or_else(|e| log::warn!("{e}")); - res_events.push(CaptureEvent::Begin); - notify_tx - .blocking_send(ProducerEvent::Grab(new_pos)) - .expect("Failed to send notification"); - } - } - - if let Some(pos) = capture_position { - res_events.iter().for_each(|e| { - // error must be ignored, since the event channel - // may already be closed when the InputCapture instance is dropped. - let _ = event_tx.blocking_send((pos, *e)); - }); - // Returning Drop should stop the event from being processed - // but core fundation still returns the event - cg_ev.set_type(CGEventType::Null); - CallbackResult::Drop - } else { - CallbackResult::Keep - } - }; + if let Some(pos) = capture_position { + res_events.iter().for_each(|e| { + // error must be ignored, since the event channel + // may already be closed when the InputCapture instance is dropped. + let _ = event_tx.blocking_send((pos, *e)); + }); + // Returning Drop should stop the event from being processed + // but core fundation still returns the event + cg_ev.set_type(CGEventType::Null); + CallbackResult::Drop + } else { + CallbackResult::Keep + } + }; let tap = CGEventTap::new( CGEventTapLocation::Session, @@ -482,6 +553,13 @@ fn create_event_tap<'a>( ) .map_err(|_| MacosCaptureCreationError::EventTapCreation)?; + // Hand the mach port pointer to the callback so it can re-enable + // the tap on TapDisabledByTimeout. The pointer is valid for the + // lifetime of `tap` (which lives on the event-tap thread until + // the run loop exits). + let port_ptr = tap.mach_port().as_concrete_TypeRef() as usize; + let _ = tap_mach_port.set(port_ptr); + let tap_source: CFRunLoopSource = tap .mach_port() .create_runloop_source(0) @@ -501,6 +579,9 @@ fn event_tap_thread( ready: std::sync::mpsc::Sender>, exit: oneshot::Sender<()>, ) { + // Clone now: create_event_tap consumes notify_tx into its closure. + let display_notify_tx = notify_tx.clone(); + let _tap = match create_event_tap(client_state, notify_tx, event_tx) { Err(e) => { ready.send(Err(e)).expect("channel closed"); @@ -512,13 +593,62 @@ fn event_tap_thread( tap } }; + + // Register a Quartz display-reconfiguration callback so the + // capture state's bounds get refreshed when the user plugs in a + // monitor, changes resolution, or rearranges displays. The + // callback runs on this thread's CFRunLoop. Box-leak the sender + // so the C side has a stable user_info pointer; reclaim it after + // the run loop exits. + let display_user_info = Box::into_raw(Box::new(display_notify_tx)) as *mut c_void; + unsafe { + CGDisplayRegisterReconfigurationCallback( + display_reconfiguration_callback, + display_user_info, + ); + } + log::debug!("running CFRunLoop..."); CFRunLoop::run_current(); log::debug!("event tap thread exiting!..."); + unsafe { + CGDisplayRemoveReconfigurationCallback(display_reconfiguration_callback, display_user_info); + // Reclaim the leaked sender Box so we don't leak a tokio + // channel sender on every capture create/destroy cycle. + drop(Box::from_raw( + display_user_info as *mut Sender, + )); + } + let _ = exit.send(()); } +/// Quartz display-reconfiguration callback. Fires twice per change: +/// once with `kCGDisplayBeginConfigurationFlag` set (BEFORE the +/// change is applied — the bounds are still stale at this point), +/// then again afterwards with the actual change flags (Add, Remove, +/// Mode, DesktopShapeChanged, etc.). Skip the begin phase; on the +/// real notification, kick the producer task to refresh bounds. +extern "C" fn display_reconfiguration_callback(_display: u32, flags: u32, user_info: *mut c_void) { + const K_CG_DISPLAY_BEGIN_CONFIGURATION_FLAG: u32 = 1 << 0; + if flags & K_CG_DISPLAY_BEGIN_CONFIGURATION_FLAG != 0 { + return; + } + if user_info.is_null() { + return; + } + // SAFETY: user_info is a Box::into_raw of Sender + // owned by `event_tap_thread`. It's valid for the lifetime of + // that thread; the registration is removed before the box is + // freed. The callback only fires while the run loop is running + // on that thread, so we know the box is live here. + let sender = unsafe { &*(user_info as *const Sender) }; + if let Err(e) = sender.blocking_send(ProducerEvent::DisplayReconfigured) { + log::warn!("failed to notify display reconfiguration: {e}"); + } +} + pub struct MacOSInputCapture { event_rx: Receiver<(Position, CaptureEvent)>, notify_tx: Sender, @@ -527,6 +657,8 @@ pub struct MacOSInputCapture { impl MacOSInputCapture { pub async fn new() -> Result { + request_macos_capture_permissions()?; + let state = Arc::new(Mutex::new(InputCaptureState::new()?)); let (event_tx, event_rx) = mpsc::channel(32); let (notify_tx, mut notify_rx) = mpsc::channel(32); @@ -580,6 +712,38 @@ impl MacOSInputCapture { } } +fn request_macos_capture_permissions() -> Result<(), MacosCaptureCreationError> { + // Call both request functions unconditionally so macOS surfaces both + // TCC prompts on the very first launch. TCC always returns `false` the + // first time a permission is requested (the grant only becomes visible + // on the next process launch), so returning early on the first failure + // would skip the second prompt and force the user through an extra + // relaunch just to see it. + let accessibility = request_accessibility_permission(); + let input_monitoring = request_input_monitoring_permission(); + + if !accessibility { + return Err(MacosCaptureCreationError::AccessibilityPermission); + } + if !input_monitoring { + return Err(MacosCaptureCreationError::InputMonitoringPermission); + } + Ok(()) +} + +fn request_accessibility_permission() -> bool { + // Silent check. The GUI owns the one-time user-visible prompt at + // startup (see lan_mouse_gtk::macos_privacy) so retries triggered by + // clicking the "Reenable" button don't pop a fresh Accessibility + // alert every time. + unsafe { AXIsProcessTrusted() } +} + +fn request_input_monitoring_permission() -> bool { + // Silent check, same reasoning as above. + unsafe { CGPreflightListenEventAccess() } +} + impl Drop for MacOSInputCapture { fn drop(&mut self) { self.run_loop.stop(); @@ -651,6 +815,30 @@ extern "C" { event_source: CGEventSource, seconds: CFTimeInterval, ); + fn CGPreflightListenEventAccess() -> bool; + /// Re-enable an event tap that was disabled by a + /// `kCGEventTapDisabledByTimeout` event. The Apple-documented + /// recovery path: see Quartz Event Services Reference. The `tap` + /// argument is a `CFMachPortRef`; we pass the raw pointer so we + /// can store it as `usize` for cross-thread sharing. + fn CGEventTapEnable(tap: *mut c_void, enable: bool); + + /// Register a callback invoked when the display configuration + /// changes (monitor add/remove, resolution change, mirror, + /// rearrange, etc). See Quartz Display Services Reference. + fn CGDisplayRegisterReconfigurationCallback( + callback: extern "C" fn(u32, u32, *mut c_void), + user_info: *mut c_void, + ) -> CGError; + fn CGDisplayRemoveReconfigurationCallback( + callback: extern "C" fn(u32, u32, *mut c_void), + user_info: *mut c_void, + ) -> CGError; +} + +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + fn AXIsProcessTrusted() -> bool; } unsafe fn configure_cf_settings() -> Result<(), MacosCaptureCreationError> { diff --git a/input-emulation/Cargo.toml b/input-emulation/Cargo.toml index e2c98b1..3839122 100644 --- a/input-emulation/Cargo.toml +++ b/input-emulation/Cargo.toml @@ -40,13 +40,17 @@ wayland-protocols-misc = { version = "0.3.1", features = [ "client", ], optional = true } x11 = { version = "2.21.0", features = ["xlib", "xtest"], optional = true } -ashpd = { version = "0.11.0", default-features = false, features = [ +ashpd = { version = "0.13.9", default-features = false, features = [ + "remote_desktop", + "screencast", "tokio", ], optional = true } reis = { version = "0.5.0", features = ["tokio"], optional = true } [target.'cfg(target_os="macos")'.dependencies] bitflags = "2.6.0" +core-foundation = "0.10.0" +core-foundation-sys = "0.8.6" core-graphics = { version = "0.25.0", features = ["highsierra"] } keycode = "1.0.0" diff --git a/input-emulation/src/error.rs b/input-emulation/src/error.rs index 078e851..9ba9f0e 100644 --- a/input-emulation/src/error.rs +++ b/input-emulation/src/error.rs @@ -154,6 +154,10 @@ pub enum X11EmulationCreationError { pub enum MacOSEmulationCreationError { #[error("could not create event source")] EventSourceCreation, + #[error("accessibility permission is required")] + AccessibilityPermission, + #[error("input control permission is required")] + InputControlPermission, } #[cfg(windows)] diff --git a/input-emulation/src/libei.rs b/input-emulation/src/libei.rs index 4e60c98..3ac6e89 100644 --- a/input-emulation/src/libei.rs +++ b/input-emulation/src/libei.rs @@ -13,7 +13,7 @@ use tokio::task::JoinHandle; use ashpd::desktop::{ PersistMode, Session, - remote_desktop::{DeviceType, RemoteDesktop}, + remote_desktop::{DeviceType, RemoteDesktop, SelectDevicesOptions}, }; use async_trait::async_trait; @@ -40,15 +40,15 @@ struct Devices { keyboard: Arc>>, } -pub(crate) struct LibeiEmulation<'a> { +pub(crate) struct LibeiEmulation { context: ei::Context, conn: event::Connection, devices: Devices, ei_task: JoinHandle<()>, error: Arc>>, libei_error: Arc, - _remote_desktop: RemoteDesktop<'a>, - session: Session<'a, RemoteDesktop<'a>>, + _remote_desktop: RemoteDesktop, + session: Session, } /// Get the path to the RemoteDesktop token file @@ -84,27 +84,26 @@ fn write_token(token: &str) -> io::Result<()> { Ok(()) } -async fn get_ei_fd<'a>() --> Result<(RemoteDesktop<'a>, Session<'a, RemoteDesktop<'a>>, OwnedFd), ashpd::Error> { +async fn get_ei_fd() -> Result<(RemoteDesktop, Session, OwnedFd), ashpd::Error> { let remote_desktop = RemoteDesktop::new().await?; let restore_token = read_token(); log::debug!("creating session ..."); - let session = remote_desktop.create_session().await?; + let session = remote_desktop.create_session(Default::default()).await?; log::debug!("selecting devices ..."); - remote_desktop - .select_devices( - &session, - DeviceType::Keyboard | DeviceType::Pointer, - restore_token.as_deref(), - PersistMode::ExplicitlyRevoked, - ) - .await?; + let options = SelectDevicesOptions::default() + .set_devices(DeviceType::Keyboard | DeviceType::Pointer) + .set_persist_mode(PersistMode::ExplicitlyRevoked) + .set_restore_token(restore_token.as_deref()); + remote_desktop.select_devices(&session, options).await?; log::info!("requesting permission for input emulation"); - let start_response = remote_desktop.start(&session, None).await?.response()?; + let start_response = remote_desktop + .start(&session, None, Default::default()) + .await? + .response()?; // The restore token is only valid once, we need to re-save it each time if let Some(token_str) = start_response.restore_token() { @@ -113,11 +112,13 @@ async fn get_ei_fd<'a>() } } - let fd = remote_desktop.connect_to_eis(&session).await?; + let fd = remote_desktop + .connect_to_eis(&session, Default::default()) + .await?; Ok((remote_desktop, session, fd)) } -impl LibeiEmulation<'_> { +impl LibeiEmulation { pub(crate) async fn new() -> Result { let (_remote_desktop, session, eifd) = get_ei_fd().await?; let stream = UnixStream::from(eifd); @@ -152,14 +153,14 @@ impl LibeiEmulation<'_> { } } -impl Drop for LibeiEmulation<'_> { +impl Drop for LibeiEmulation { fn drop(&mut self) { self.ei_task.abort(); } } #[async_trait] -impl Emulation for LibeiEmulation<'_> { +impl Emulation for LibeiEmulation { async fn consume( &mut self, event: Event, diff --git a/input-emulation/src/macos.rs b/input-emulation/src/macos.rs index ae5f307..881fc22 100644 --- a/input-emulation/src/macos.rs +++ b/input-emulation/src/macos.rs @@ -61,6 +61,8 @@ unsafe impl Send for MacOSEmulation {} impl MacOSEmulation { pub(crate) fn new() -> Result { + request_macos_emulation_permissions()?; + let event_source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState) .map_err(|_| MacOSEmulationCreationError::EventSourceCreation)?; Ok(Self { @@ -119,6 +121,42 @@ impl MacOSEmulation { } } +fn request_macos_emulation_permissions() -> Result<(), MacOSEmulationCreationError> { + // Request both permissions up front so the user sees both TCC prompts + // on the first launch. See the matching comment in input-capture/src/ + // macos.rs::request_macos_capture_permissions for the rationale. + let accessibility = request_accessibility_permission(); + let input_control = request_input_control_permission(); + + if !accessibility { + return Err(MacOSEmulationCreationError::AccessibilityPermission); + } + if !input_control { + return Err(MacOSEmulationCreationError::InputControlPermission); + } + Ok(()) +} + +fn request_accessibility_permission() -> bool { + // Silent check. The GUI owns the one-time user-visible prompt at + // startup (see lan_mouse_gtk::macos_privacy). + unsafe { AXIsProcessTrusted() } +} + +fn request_input_control_permission() -> bool { + unsafe { CGPreflightPostEventAccess() } +} + +#[link(name = "CoreGraphics", kind = "framework")] +extern "C" { + fn CGPreflightPostEventAccess() -> bool; +} + +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + fn AXIsProcessTrusted() -> bool; +} + fn key_event(event_source: CGEventSource, key: u16, state: u8, modifiers: XMods) { let event = match CGEvent::new_keyboard_event(event_source, key, state != 0) { Ok(e) => e, diff --git a/input-emulation/src/xdg_desktop_portal.rs b/input-emulation/src/xdg_desktop_portal.rs index 37d2974..7c2d133 100644 --- a/input-emulation/src/xdg_desktop_portal.rs +++ b/input-emulation/src/xdg_desktop_portal.rs @@ -1,7 +1,10 @@ use ashpd::{ desktop::{ PersistMode, Session, - remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop}, + remote_desktop::{ + Axis, DeviceType, KeyState, NotifyPointerAxisOptions, RemoteDesktop, + SelectDevicesOptions, + }, }, zbus::AsyncDrop, }; @@ -17,32 +20,31 @@ use crate::error::EmulationError; use super::{Emulation, EmulationHandle, error::XdpEmulationCreationError}; -pub(crate) struct DesktopPortalEmulation<'a> { - proxy: RemoteDesktop<'a>, - session: Session<'a, RemoteDesktop<'a>>, +pub(crate) struct DesktopPortalEmulation { + proxy: RemoteDesktop, + session: Session, } -impl<'a> DesktopPortalEmulation<'a> { - pub(crate) async fn new() -> Result, XdpEmulationCreationError> { +impl DesktopPortalEmulation { + pub(crate) async fn new() -> Result { log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ..."); let proxy = RemoteDesktop::new().await?; // retry when user presses the cancel button log::debug!("creating session ..."); - let session = proxy.create_session().await?; + let session = proxy.create_session(Default::default()).await?; log::debug!("selecting devices ..."); - proxy - .select_devices( - &session, - DeviceType::Keyboard | DeviceType::Pointer, - None, - PersistMode::ExplicitlyRevoked, - ) - .await?; + let options = SelectDevicesOptions::default() + .set_devices(DeviceType::Keyboard | DeviceType::Pointer) + .set_persist_mode(PersistMode::ExplicitlyRevoked); + proxy.select_devices(&session, options).await?; log::info!("requesting permission for input emulation"); - let _devices = proxy.start(&session, None).await?.response()?; + let _devices = proxy + .start(&session, None, Default::default()) + .await? + .response()?; log::debug!("started session"); let session = session; @@ -52,7 +54,7 @@ impl<'a> DesktopPortalEmulation<'a> { } #[async_trait] -impl Emulation for DesktopPortalEmulation<'_> { +impl Emulation for DesktopPortalEmulation { async fn consume( &mut self, event: input_event::Event, @@ -62,7 +64,7 @@ impl Emulation for DesktopPortalEmulation<'_> { Pointer(p) => match p { PointerEvent::Motion { time: _, dx, dy } => { self.proxy - .notify_pointer_motion(&self.session, dx, dy) + .notify_pointer_motion(&self.session, dx, dy, Default::default()) .await?; } PointerEvent::Button { @@ -75,7 +77,12 @@ impl Emulation for DesktopPortalEmulation<'_> { _ => KeyState::Pressed, }; self.proxy - .notify_pointer_button(&self.session, button as i32, state) + .notify_pointer_button( + &self.session, + button as i32, + state, + Default::default(), + ) .await?; } PointerEvent::AxisDiscrete120 { axis, value } => { @@ -84,7 +91,12 @@ impl Emulation for DesktopPortalEmulation<'_> { _ => Axis::Horizontal, }; self.proxy - .notify_pointer_axis_discrete(&self.session, axis, value / 120) + .notify_pointer_axis_discrete( + &self.session, + axis, + value / 120, + Default::default(), + ) .await?; } PointerEvent::Axis { @@ -101,7 +113,12 @@ impl Emulation for DesktopPortalEmulation<'_> { Axis::Horizontal => (value, 0.), }; self.proxy - .notify_pointer_axis(&self.session, dx, dy, true) + .notify_pointer_axis( + &self.session, + dx, + dy, + NotifyPointerAxisOptions::default().set_finish(true), + ) .await?; } }, @@ -117,7 +134,12 @@ impl Emulation for DesktopPortalEmulation<'_> { _ => KeyState::Pressed, }; self.proxy - .notify_keyboard_keycode(&self.session, key as i32, state) + .notify_keyboard_keycode( + &self.session, + key as i32, + state, + Default::default(), + ) .await?; } KeyboardEvent::Modifiers { .. } => { @@ -141,7 +163,7 @@ impl Emulation for DesktopPortalEmulation<'_> { } } -impl AsyncDrop for DesktopPortalEmulation<'_> { +impl AsyncDrop for DesktopPortalEmulation { #[doc = r" Perform the async cleanup."] #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] fn async_drop<'async_trait>( diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/edit-copy-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/edit-copy-symbolic.svg new file mode 100644 index 0000000..5964403 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/edit-copy-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/edit-delete-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/edit-delete-symbolic.svg new file mode 100644 index 0000000..4131277 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/edit-delete-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/emblem-ok-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/emblem-ok-symbolic.svg new file mode 100644 index 0000000..7a9551f --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/emblem-ok-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/list-add-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/list-add-symbolic.svg new file mode 100644 index 0000000..cf68622 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/list-add-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/object-rotate-right-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/object-rotate-right-symbolic.svg new file mode 100644 index 0000000..2794d53 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/object-rotate-right-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/object-select-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/object-select-symbolic.svg new file mode 100644 index 0000000..7a9551f --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/object-select-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/open-menu-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/open-menu-symbolic.svg new file mode 100644 index 0000000..7f44743 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/open-menu-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/actions/process-stop-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/actions/process-stop-symbolic.svg new file mode 100644 index 0000000..19b9537 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/actions/process-stop-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/devices/auth-fingerprint-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/devices/auth-fingerprint-symbolic.svg new file mode 100644 index 0000000..f64af0a --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/devices/auth-fingerprint-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/devices/network-wired-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/devices/network-wired-symbolic.svg new file mode 100644 index 0000000..166b48f --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/devices/network-wired-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/places/user-trash-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/places/user-trash-symbolic.svg new file mode 100644 index 0000000..2e20f9c --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/places/user-trash-symbolic.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/status/dialog-warning-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/status/dialog-warning-symbolic.svg new file mode 100644 index 0000000..0b8cbe5 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/status/dialog-warning-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lan-mouse-gtk/resources/icons/scalable/status/network-wired-disconnected-symbolic.svg b/lan-mouse-gtk/resources/icons/scalable/status/network-wired-disconnected-symbolic.svg new file mode 100644 index 0000000..df1f039 --- /dev/null +++ b/lan-mouse-gtk/resources/icons/scalable/status/network-wired-disconnected-symbolic.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lan-mouse-gtk/resources/resources.gresource.xml b/lan-mouse-gtk/resources/resources.gresource.xml index c94be6b..d382074 100644 --- a/lan-mouse-gtk/resources/resources.gresource.xml +++ b/lan-mouse-gtk/resources/resources.gresource.xml @@ -9,5 +9,23 @@ de.feschber.LanMouse.svg + + icons/scalable/actions/edit-copy-symbolic.svg + icons/scalable/actions/edit-delete-symbolic.svg + icons/scalable/actions/emblem-ok-symbolic.svg + icons/scalable/actions/list-add-symbolic.svg + icons/scalable/actions/object-rotate-right-symbolic.svg + icons/scalable/actions/object-select-symbolic.svg + icons/scalable/actions/open-menu-symbolic.svg + icons/scalable/actions/process-stop-symbolic.svg + icons/scalable/devices/auth-fingerprint-symbolic.svg + icons/scalable/devices/network-wired-symbolic.svg + icons/scalable/places/user-trash-symbolic.svg + icons/scalable/status/dialog-warning-symbolic.svg + icons/scalable/status/network-wired-disconnected-symbolic.svg diff --git a/lan-mouse-gtk/resources/window.ui b/lan-mouse-gtk/resources/window.ui index a3d1c89..609ea4e 100644 --- a/lan-mouse-gtk/resources/window.ui +++ b/lan-mouse-gtk/resources/window.ui @@ -63,7 +63,7 @@ center @@ -89,7 +89,7 @@ center diff --git a/lan-mouse-gtk/src/lib.rs b/lan-mouse-gtk/src/lib.rs index 9908e05..ecdd708 100644 --- a/lan-mouse-gtk/src/lib.rs +++ b/lan-mouse-gtk/src/lib.rs @@ -4,6 +4,10 @@ mod client_row; mod fingerprint_window; mod key_object; mod key_row; +#[cfg(target_os = "macos")] +mod macos_privacy; +#[cfg(target_os = "macos")] +mod macos_status_item; mod window; use std::{env, process, str}; @@ -47,6 +51,12 @@ pub fn run() -> Result<(), GtkError> { } fn gtk_main() -> glib::ExitCode { + #[cfg(target_os = "macos")] + { + configure_macos_bundle_environment(); + install_macos_gtk_log_filter(); + } + gio::resources_register_include!("lan-mouse.gresource").expect("Failed to register resources."); let app = Application::builder() @@ -64,6 +74,64 @@ fn gtk_main() -> glib::ExitCode { app.run_with_args(&args) } +#[cfg(target_os = "macos")] +fn install_macos_gtk_log_filter() { + glib::log_set_writer_func(|level, fields| { + if level == glib::LogLevel::Warning && is_gtk_theme_parser_warning(fields) { + return glib::LogWriterOutput::Handled; + } + + glib::log_writer_default(level, fields) + }); +} + +#[cfg(target_os = "macos")] +fn is_gtk_theme_parser_warning(fields: &[glib::LogField<'_>]) -> bool { + let mut domain = None; + let mut message = None; + + for field in fields { + match field.key() { + "GLIB_DOMAIN" => domain = field.value_str(), + "MESSAGE" => message = field.value_str(), + _ => {} + } + } + + domain == Some("Gtk") + && message.is_some_and(|message| message.starts_with("Theme parser warning: gtk.css:")) +} + +#[cfg(target_os = "macos")] +fn configure_macos_bundle_environment() { + let Ok(exe) = env::current_exe() else { + return; + }; + let Some(contents) = exe + .parent() + .and_then(|dir| dir.parent()) + .map(std::path::Path::to_owned) + else { + return; + }; + + let share = contents.join("Resources").join("share"); + if !share.exists() { + return; + } + + let schemas = share.join("glib-2.0").join("schemas"); + if schemas.exists() { + env::set_var("GSETTINGS_SCHEMA_DIR", schemas); + } + + env::set_var("XDG_DATA_DIRS", &share); + env::set_var( + "GTK_DATA_PREFIX", + contents.join("Resources").to_string_lossy().as_ref(), + ); +} + fn load_icons() { let display = &Display::default().expect("Could not connect to a display."); let icon_theme = IconTheme::for_display(display); @@ -123,6 +191,41 @@ fn build_ui(app: &Application) { }); let window = Window::new(app, frontend_tx); + #[cfg(target_os = "macos")] + { + window.connect_close_request(|window| { + window.set_visible(false); + glib::Propagation::Stop + }); + macos_status_item::setup(app, &window); + // First-launch TCC prompts. No-op when already granted. + macos_privacy::fire_initial_prompts(); + // Watch the Accessibility grant continuously for the lifetime + // of the process. On a grant, swap the warning row into its + // "relaunch required" state (the daemon subprocess already + // bailed and can't recover without a restart). On a REVOKE, + // quit immediately — an active CGEventTap at + // HeadInsertEventTap can wedge system input if the process + // lingers after losing AX, and forcing the process to exit is + // the only bulletproof way to guarantee the kernel tears the + // tap down. + let window_weak = window.downgrade(); + let app_weak = app.downgrade(); + macos_privacy::watch_accessibility_state(move |change| match change { + macos_privacy::AccessibilityChange::Granted => { + if let Some(window) = window_weak.upgrade() { + window.present(); + window.refresh_capture_emulation_status(); + } + } + macos_privacy::AccessibilityChange::Revoked => { + log::warn!("Accessibility revoked — quitting to avoid wedging system input"); + if let Some(app) = app_weak.upgrade() { + app.quit(); + } + } + }); + } glib::spawn_future_local(clone!( #[weak] @@ -171,5 +274,18 @@ fn build_ui(app: &Application) { } )); + #[cfg(not(target_os = "macos"))] window.present(); + + // On macOS, default to presenting the main window on every launch + // so the user gets a visible confirmation that the app is running + // — including the post-grant relaunch and normal Dock/Finder/`open` + // launches. Opt out by setting `LAN_MOUSE_HIDDEN=1` in the + // environment (useful for a LaunchAgent / login-item configuration + // where the user wants the app to come up quietly into the menu + // bar only, with no window on boot). + #[cfg(target_os = "macos")] + if env::var_os("LAN_MOUSE_HIDDEN").is_none() { + window.present(); + } } diff --git a/lan-mouse-gtk/src/macos_privacy.rs b/lan-mouse-gtk/src/macos_privacy.rs new file mode 100644 index 0000000..f9fbd9c --- /dev/null +++ b/lan-mouse-gtk/src/macos_privacy.rs @@ -0,0 +1,256 @@ +//! Tiny macOS Privacy-pane helpers used by the GUI. +//! +//! On macOS 13+, the Accessibility grant transitively confers the +//! listen-only event-tap privilege that Input Monitoring gates and the +//! synthesize-event privilege that Post Event gates, and the bundle +//! typically isn't even listed in those separate panes. So the single +//! user-facing action for any missing-capture or missing-emulation +//! scenario is "re-toggle Accessibility" — we don't route elsewhere. + +use std::ffi::{c_uchar, c_void}; +use std::process::Command; +use std::sync::Once; + +use gtk::glib; + +// Apple declares `AXIsProcessTrusted` as returning `Boolean` (`unsigned char`), +// NOT C's `bool`. Rust's `bool` has a strict bit pattern (0 or 1) so binding +// a `Boolean`-returning function as `-> bool` is technically UB if Apple ever +// returns a non-canonical true value. Keep these as `c_uchar` and normalize. +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + fn AXIsProcessTrusted() -> c_uchar; + fn AXIsProcessTrustedWithOptions(options: *const c_void) -> c_uchar; +} + +#[link(name = "CoreFoundation", kind = "framework")] +extern "C" { + static kCFAllocatorDefault: *const c_void; + static kCFTypeDictionaryKeyCallBacks: *const c_void; + static kCFTypeDictionaryValueCallBacks: *const c_void; + static kCFBooleanTrue: *const c_void; + fn CFDictionaryCreate( + allocator: *const c_void, + keys: *const *const c_void, + values: *const *const c_void, + num: isize, + key_callbacks: *const c_void, + value_callbacks: *const c_void, + ) -> *const c_void; + fn CFRelease(cf: *const c_void); +} + +// kAXTrustedCheckOptionPrompt is a CFStringRef exported from ApplicationServices. +#[link(name = "ApplicationServices", kind = "framework")] +extern "C" { + static kAXTrustedCheckOptionPrompt: *const c_void; +} + +#[link(name = "CoreGraphics", kind = "framework")] +extern "C" { + fn CGRequestListenEventAccess() -> c_uchar; + fn CGRequestPostEventAccess() -> c_uchar; + + // CFMachPortRef CGEventTapCreate( + // CGEventTapLocation tap, CGEventTapPlacement place, + // CGEventTapOptions options, CGEventMask eventsOfInterest, + // CGEventTapCallBack callback, void *userInfo); + fn CGEventTapCreate( + tap: u32, + place: u32, + options: u32, + events_of_interest: u64, + callback: *const c_void, + user_info: *const c_void, + ) -> *const c_void; +} + +pub fn accessibility_granted() -> bool { + let raw = unsafe { AXIsProcessTrusted() }; + log::debug!("AXIsProcessTrusted() = {raw}"); + raw != 0 +} + +pub enum AccessibilityChange { + /// AX was missing at startup and the user has now granted it. + /// Capture/emulation still need a relaunch to take effect, since + /// the daemon subprocess already bailed. + Granted, + /// AX was granted and the user has now revoked it. Quit immediately + /// — leaving the process alive with an active CGEventTap at + /// HeadInsertEventTap can wedge system input (clicks/keys silently + /// consumed) until the process dies. See + /// macos-cgeventtap-drop-fallthrough-tcc-revoke skill for the + /// underlying event-tap-disable footgun. + Revoked, +} + +/// Poll for Accessibility grant/revoke transitions. Starts a 1-second +/// GLib timer that fires `on_change` every time `AXIsProcessTrusted()` +/// flips, and keeps running for the lifetime of the process. +/// +/// We rely on polling rather than AXObserver because the AX notification +/// API requires a trusted process to subscribe — the precondition we +/// can't assume. This runs on the GTK main thread (via +/// `timeout_add_seconds_local`). +pub fn watch_accessibility_state(mut on_change: F) +where + F: FnMut(AccessibilityChange) + 'static, +{ + let mut last = accessibility_granted(); + log::info!("watching Accessibility state (initial = {last})"); + glib::timeout_add_seconds_local(1, move || { + let current = accessibility_granted(); + if current != last { + log::info!("Accessibility state flip: {last} -> {current}"); + on_change(if current { + AccessibilityChange::Granted + } else { + AccessibilityChange::Revoked + }); + last = current; + } + glib::ControlFlow::Continue + }); +} + +pub fn open_accessibility_settings() { + open_url("x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"); +} + +/// Spawn a fresh instance of the current `.app` bundle via Launch Services +/// after a 1-second delay, so the new instance starts *after* the current +/// process has exited — otherwise Launch Services reactivates the existing +/// process instead of launching a fresh one, and the stale IPC socket +/// would block the new daemon subprocess. The caller is responsible for +/// quitting the current process (e.g. `Application::quit()`) after this. +pub fn relaunch_bundle() { + // Resolve the .app bundle path from the current executable: it lives + // at /Contents/MacOS/lan-mouse, so three parents up is the + // bundle root we hand to `open`. + let Ok(exe) = std::env::current_exe() else { + return; + }; + let Some(bundle) = exe + .parent() + .and_then(std::path::Path::parent) + .and_then(std::path::Path::parent) + else { + return; + }; + + // Trailing `&` backgrounds the sleep+open so our shell call returns + // immediately; the spawned shell is adopted by launchd once we exit. + let cmd = format!("(sleep 1 && open {bundle:?}) &"); + let _ = Command::new("sh").arg("-c").arg(cmd).spawn(); +} + +/// Make sure the app appears in System Settings → Privacy → Input Monitoring. +/// +/// `CGRequestListenEventAccess()` is *supposed* to register the app in the +/// list (and prompt) on first call, but in practice — particularly after a +/// `tccutil reset ListenEvent ` — it often silently no-ops and the +/// app never gets added. The reliable way to force registration is to +/// attempt a protected action: create a `CGEventTap`. If permission is +/// missing the call returns null, but the attempt itself causes TCC to add +/// the bundle to the Input Monitoring pane so the user can toggle it on. +/// If permission already exists the tap is created successfully, and we +/// tear it down immediately so it doesn't intercept events. +unsafe fn ensure_listed_in_input_monitoring() { + let req = CGRequestListenEventAccess(); + log::debug!("CGRequestListenEventAccess() = {req}"); + let cb = input_monitoring_noop_tap_callback as *const c_void; + // Use kCGSessionEventTap (1), NOT kCGHIDEventTap (0). The HID tap sits + // below window-server input and requires Accessibility in addition to + // Input Monitoring, so attempting it when Accessibility isn't granted + // surfaces an Accessibility prompt as a side effect — which is confusing + // on top of the real Accessibility prompt we already fire explicitly. + // The session tap requires only Input Monitoring, so its failure is a + // clean "Input Monitoring missing" signal that TCC uses to list the + // bundle under the Input Monitoring pane. + // kCGHeadInsertEventTap = 0, kCGEventTapOptionListenOnly = 1, + // mask kCGEventKeyDown = 1 << 10. + let tap = CGEventTapCreate(1, 0, 1, 1 << 10, cb, std::ptr::null()); + log::debug!("CGEventTapCreate(kCGSessionEventTap) -> {tap:?}"); + if !tap.is_null() { + CFRelease(tap); + } +} + +extern "C" fn input_monitoring_noop_tap_callback( + _proxy: *const c_void, + _ty: u32, + event: *const c_void, + _refcon: *const c_void, +) -> *const c_void { + // Pass through unchanged. This tap is never added to a run loop, so + // in practice the callback never fires — it exists only so the tap + // can be created (and the attempt is what forces TCC registration). + event +} + +fn open_url(url: &str) { + if let Err(e) = Command::new("open").arg(url).spawn() { + log::warn!("failed to open {url}: {e}"); + } +} + +/// One-shot, at GUI startup: if a permission is missing, fire the system +/// prompt. This is where the familiar first-launch "Lan Mouse.app would +/// like to control this computer" alert comes from. Subsequent clicks on +/// the Reenable button use URL-scheme navigation instead, so we never +/// double up alerts on retries. +/// +/// Guarded with a `Once` because GApplication::activate can fire more +/// than once in a process (reactivation, window presentation) and we +/// must not re-pop the TCC alert on each activation — that looks like a +/// bug to the user. +pub fn fire_initial_prompts() { + static FIRED: Once = Once::new(); + FIRED.call_once(fire_initial_prompts_inner); +} + +fn fire_initial_prompts_inner() { + if !accessibility_granted() { + // When Accessibility isn't granted yet, ONLY fire the Accessibility + // prompt. Do NOT also try to register Input Monitoring or Post Event + // — those paths have been observed to surface a second Accessibility + // dialog on top of the one we fire explicitly (Post Event is part of + // the Accessibility category on modern macOS, and CGEventTap attempts + // can bail on Accessibility before they reach the Input Monitoring + // check). Once the user grants Accessibility and relaunches, this + // branch is skipped and we register the other grants cleanly below. + log::info!("firing first-launch Accessibility prompt"); + unsafe { + let key = kAXTrustedCheckOptionPrompt; + let value = kCFBooleanTrue; + let options = CFDictionaryCreate( + kCFAllocatorDefault, + &key as *const _, + &value as *const _, + 1, + kCFTypeDictionaryKeyCallBacks, + kCFTypeDictionaryValueCallBacks, + ); + AXIsProcessTrustedWithOptions(options); + CFRelease(options); + } + return; + } + // Accessibility is granted. Attempt Input Monitoring registration + // unconditionally — even if preflight returns true — so the bundle gets + // listed in System Settings under its own identity (otherwise launches + // from a parent process that already has Input Monitoring, e.g. Terminal, + // inherit the grant but the bundle is never listed for the user to + // toggle persistently). + log::info!("ensuring Lan Mouse is listed under Input Monitoring"); + unsafe { + ensure_listed_in_input_monitoring(); + } + // Same for Post Event: now that Accessibility is present, this call is + // safe — it won't surface the generic Accessibility prompt. + log::info!("ensuring Lan Mouse is listed under Accessibility > Post Event"); + unsafe { + CGRequestPostEventAccess(); + } +} diff --git a/lan-mouse-gtk/src/macos_status_item.rs b/lan-mouse-gtk/src/macos_status_item.rs new file mode 100644 index 0000000..663ec2e --- /dev/null +++ b/lan-mouse-gtk/src/macos_status_item.rs @@ -0,0 +1,346 @@ +#![allow(clashing_extern_declarations)] + +use std::{ + cell::RefCell, + ffi::{CStr, CString, c_char, c_double, c_uint, c_void}, + sync::OnceLock, +}; + +use adw::prelude::*; +use gtk::{gio, glib}; + +use crate::window::Window; + +type Id = *mut c_void; +type Class = *mut c_void; +type Sel = *mut c_void; +type Bool = i8; + +struct StatusItem { + app: glib::WeakRef, + window: glib::WeakRef, + _hold: gio::ApplicationHoldGuard, + _delegate: Id, + _status_item: Id, +} + +thread_local! { + static STATUS_ITEM: RefCell> = const { RefCell::new(None) }; +} + +pub fn setup(app: &adw::Application, window: &Window) { + log::debug!("macos_status_item::setup entered"); + STATUS_ITEM.with(|item| { + let already_initialized = item.borrow().is_some(); + if already_initialized { + let mut cell = item.borrow_mut(); + if let Some(existing) = cell.as_mut() { + existing.app.set(Some(app)); + existing.window.set(Some(window)); + } + return; + } + + unsafe { + let hold = app.hold(); + + let ns_app = msg_send_id(class(c"NSApplication"), sel(c"sharedApplication")); + assert!( + !ns_app.is_null(), + "NSApplication sharedApplication returned null" + ); + msg_send_bool_usize(ns_app, sel(c"setActivationPolicy:"), 1); + + let delegate = new_delegate(); + let menu = menu(&[ + menu_item(c"Open Lan Mouse", c"showLanMouse:"), + separator_item(), + menu_item(c"Quit Lan Mouse", c"quitLanMouse:"), + ]); + + let status_bar = msg_send_id(class(c"NSStatusBar"), sel(c"systemStatusBar")); + assert!( + !status_bar.is_null(), + "NSStatusBar systemStatusBar returned null" + ); + let status_item = msg_send_id_f64(status_bar, sel(c"statusItemWithLength:"), -1.0); + assert!(!status_item.is_null(), "statusItemWithLength returned null"); + // Retain so the status item survives autorelease pool drain. + let status_item = msg_send_id(status_item, sel(c"retain")); + + let button = msg_send_id(status_item, sel(c"button")); + assert!(!button.is_null(), "NSStatusItem.button was null"); + set_button_image(button); + msg_send_void_id(button, sel(c"setToolTip:"), nsstring(c"Lan Mouse")); + msg_send_void_id(status_item, sel(c"setMenu:"), menu); + + for item in menu_items(menu) { + msg_send_void_id(item, sel(c"setTarget:"), delegate); + } + + install_reopen_handler(delegate); + + log::debug!("macos_status_item ready at {status_item:p}"); + + item.replace(Some(StatusItem { + app: app.downgrade(), + window: window.downgrade(), + _hold: hold, + _delegate: delegate, + _status_item: status_item, + })); + } + }); +} + +// Prefer a pre-rendered template PNG (black silhouette with alpha) so macOS +// auto-tints the glyph to match the menu bar in light and dark modes. +// Falls back to the full-color icns, then to "LM" text. +unsafe fn set_button_image(button: Id) { + if let Some(image) = load_menubar_template() { + msg_send_void_bool(image, sel(c"setTemplate:"), 1); + msg_send_void_id(button, sel(c"setImage:"), image); + return; + } + if let Some(image) = load_app_icon() { + msg_send_void_id(button, sel(c"setImage:"), image); + return; + } + log::warn!("no menu bar image available; falling back to text title"); + msg_send_void_id(button, sel(c"setTitle:"), nsstring(c"LM")); +} + +unsafe fn load_menubar_template() -> Option { + load_resource_image(c"menubar-template", c"png", MENUBAR_ICON_SIZE) +} + +unsafe fn load_app_icon() -> Option { + load_resource_image(c"icon", c"icns", MENUBAR_ICON_SIZE) +} + +unsafe fn load_resource_image(name: &CStr, ext: &CStr, size_pt: c_double) -> Option { + let bundle = msg_send_id(class(c"NSBundle"), sel(c"mainBundle")); + if bundle.is_null() { + return None; + } + let path = msg_send_id_id_id( + bundle, + sel(c"pathForResource:ofType:"), + nsstring(name), + nsstring(ext), + ); + if path.is_null() { + return None; + } + let image = msg_send_id_id( + msg_send_id(class(c"NSImage"), sel(c"alloc")), + sel(c"initWithContentsOfFile:"), + path, + ); + if image.is_null() { + return None; + } + // Render at menu bar height; 22pt is the full status bar icon height. + msg_send_void_size(image, sel(c"setSize:"), size_pt, size_pt); + Some(image) +} + +const MENUBAR_ICON_SIZE: c_double = 22.0; + +unsafe fn menu(items: &[Id]) -> Id { + let menu = msg_send_id(msg_send_id(class(c"NSMenu"), sel(c"alloc")), sel(c"init")); + for item in items { + msg_send_void_id(menu, sel(c"addItem:"), *item); + } + menu +} + +unsafe fn menu_item(title: &CStr, action: &CStr) -> Id { + msg_send_id_id_sel_id( + msg_send_id(class(c"NSMenuItem"), sel(c"alloc")), + sel(c"initWithTitle:action:keyEquivalent:"), + nsstring(title), + sel(action), + nsstring(c""), + ) +} + +unsafe fn separator_item() -> Id { + msg_send_id(class(c"NSMenuItem"), sel(c"separatorItem")) +} + +unsafe fn menu_items(menu: Id) -> Vec { + let count = msg_send_usize(menu, sel(c"numberOfItems")); + (0..count) + .map(|idx| msg_send_id_usize(menu, sel(c"itemAtIndex:"), idx)) + .collect() +} + +unsafe fn new_delegate() -> Id { + let class = delegate_class(); + msg_send_id(msg_send_id(class, sel(c"alloc")), sel(c"init")) +} + +fn delegate_class() -> Class { + static CLASS: OnceLock = OnceLock::new(); + + *CLASS.get_or_init(|| unsafe { + let superclass = class(c"NSObject"); + let class_name = CString::new("LanMouseStatusItemDelegate").unwrap(); + let class = objc_allocateClassPair(superclass, class_name.as_ptr(), 0); + assert!(!class.is_null(), "failed to allocate status item delegate"); + + class_addMethod( + class, + sel(c"showLanMouse:"), + show_lan_mouse as *const c_void, + c"v@:@".as_ptr(), + ); + class_addMethod( + class, + sel(c"quitLanMouse:"), + quit_lan_mouse as *const c_void, + c"v@:@".as_ptr(), + ); + // kAEReopenApplication handler — fires when the user re-launches + // the .app while it's already running (Finder, `open`, Dock). + class_addMethod( + class, + sel(c"handleReopenEvent:withReplyEvent:"), + handle_reopen_event as *const c_void, + c"v@:@@".as_ptr(), + ); + objc_registerClassPair(class); + class as usize + }) as Class +} + +extern "C" fn show_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) { + present_window(); +} + +extern "C" fn handle_reopen_event(_this: Id, _cmd: Sel, _event: Id, _reply: Id) { + log::debug!("kAEReopenApplication received — presenting main window"); + present_window(); +} + +fn present_window() { + STATUS_ITEM.with(|item| { + let item = item.borrow(); + let Some(item) = item.as_ref() else { + return; + }; + if let Some(window) = item.window.upgrade() { + window.present(); + } + + unsafe { + let ns_app = msg_send_id(class(c"NSApplication"), sel(c"sharedApplication")); + msg_send_void_bool(ns_app, sel(c"activateIgnoringOtherApps:"), 1); + } + }); +} + +// Register the status-item delegate as the handler for the +// kAEReopenApplication Apple Event ('aevt'/'rapp'). NSApplication +// installs a default handler at -finishLaunching that just delegates to +// applicationShouldHandleReopen:hasVisibleWindows: — which is a no-op +// here because GApplication owns NSApp's delegate. Replacing it lets us +// re-present the window when the user double-clicks the .app while +// we're already running. +unsafe fn install_reopen_handler(delegate: Id) { + const K_CORE_EVENT_CLASS: c_uint = 0x6165_7674; // 'aevt' + const K_AE_REOPEN_APPLICATION: c_uint = 0x7261_7070; // 'rapp' + + let manager = msg_send_id( + class(c"NSAppleEventManager"), + sel(c"sharedAppleEventManager"), + ); + if manager.is_null() { + log::warn!("NSAppleEventManager unavailable; re-launch will not re-open window"); + return; + } + msg_send_void_id_sel_u32_u32( + manager, + sel(c"setEventHandler:andSelector:forEventClass:andEventID:"), + delegate, + sel(c"handleReopenEvent:withReplyEvent:"), + K_CORE_EVENT_CLASS, + K_AE_REOPEN_APPLICATION, + ); +} + +extern "C" fn quit_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) { + STATUS_ITEM.with(|item| { + if let Some(app) = item.borrow().as_ref().and_then(|item| item.app.upgrade()) { + app.quit(); + } + }); +} + +unsafe fn class(name: &CStr) -> Class { + let class = objc_getClass(name.as_ptr()); + assert!(!class.is_null(), "missing Objective-C class {name:?}"); + class +} + +unsafe fn sel(name: &CStr) -> Sel { + sel_registerName(name.as_ptr()) +} + +unsafe fn nsstring(value: &CStr) -> Id { + msg_send_id_ptr( + class(c"NSString"), + sel(c"stringWithUTF8String:"), + value.as_ptr(), + ) +} + +#[link(name = "objc")] +extern "C" { + fn objc_allocateClassPair(superclass: Class, name: *const c_char, extra_bytes: usize) -> Class; + fn objc_getClass(name: *const c_char) -> Class; + fn objc_registerClassPair(class: Class); + fn sel_registerName(name: *const c_char) -> Sel; + fn class_addMethod(class: Class, name: Sel, imp: *const c_void, types: *const c_char) -> Bool; +} + +#[link(name = "AppKit", kind = "framework")] +extern "C" {} + +#[link(name = "objc")] +extern "C" { + #[link_name = "objc_msgSend"] + fn msg_send_id(receiver: Id, selector: Sel) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_id_f64(receiver: Id, selector: Sel, value: c_double) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_id_id_sel_id(receiver: Id, selector: Sel, a: Id, b: Sel, c: Id) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_id_id_id(receiver: Id, selector: Sel, a: Id, b: Id) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_id_id(receiver: Id, selector: Sel, a: Id) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_void_size(receiver: Id, selector: Sel, width: c_double, height: c_double); + #[link_name = "objc_msgSend"] + fn msg_send_id_ptr(receiver: Id, selector: Sel, value: *const c_char) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_id_usize(receiver: Id, selector: Sel, value: usize) -> Id; + #[link_name = "objc_msgSend"] + fn msg_send_usize(receiver: Id, selector: Sel) -> usize; + #[link_name = "objc_msgSend"] + fn msg_send_void_bool(receiver: Id, selector: Sel, value: Bool); + #[link_name = "objc_msgSend"] + fn msg_send_void_id(receiver: Id, selector: Sel, value: Id); + #[link_name = "objc_msgSend"] + fn msg_send_bool_usize(receiver: Id, selector: Sel, value: usize) -> Bool; + #[link_name = "objc_msgSend"] + fn msg_send_void_id_sel_u32_u32( + receiver: Id, + selector: Sel, + a: Id, + b: Sel, + c: c_uint, + d: c_uint, + ); +} diff --git a/lan-mouse-gtk/src/window.rs b/lan-mouse-gtk/src/window.rs index 47db926..f650157 100644 --- a/lan-mouse-gtk/src/window.rs +++ b/lan-mouse-gtk/src/window.rs @@ -22,6 +22,17 @@ use crate::{ use super::{client_object::ClientObject, client_row::ClientRow}; +#[cfg(target_os = "macos")] +fn set_button_content_label(button: >k::Button, label: &str) { + // The Reenable/Grant/Relaunch button wraps its icon+label in an + // AdwButtonContent (see window.ui). Walk into it and swap the label + // rather than GtkButton::set_label, which would replace the content + // widget and drop the icon. + if let Some(content) = button.child().and_downcast::() { + content.set_label(label); + } +} + glib::wrapper! { pub struct Window(ObjectSubclass) @extends adw::ApplicationWindow, gtk::Window, gtk::Widget, @@ -432,6 +443,10 @@ impl Window { pub(super) fn show_toast(&self, msg: &str) { let toast = adw::Toast::new(msg); + self.add_toast(toast); + } + + pub(super) fn add_toast(&self, toast: adw::Toast) { let toast_overlay = &self.imp().toast_overlay; toast_overlay.add_toast(toast); } @@ -446,14 +461,61 @@ impl Window { self.update_capture_emulation_status(); } + #[cfg(target_os = "macos")] + pub(super) fn refresh_capture_emulation_status(&self) { + self.update_capture_emulation_status(); + } + fn update_capture_emulation_status(&self) { let capture = self.imp().capture_active.get(); let emulation = self.imp().emulation_active.get(); - self.imp().capture_status_row.set_visible(!capture); - self.imp().emulation_status_row.set_visible(!emulation); - self.imp() - .capture_emulation_group - .set_visible(!capture || !emulation); + + #[cfg(target_os = "macos")] + { + // On macOS, capture and emulation share the same TCC gate + // (Accessibility). Collapse to a single warning row — + // emulation_status_row stays hidden and capture_status_row + // doubles as the shared status indicator. Its text and + // button mutate based on whether we're waiting for AX or + // waiting for the user to relaunch the app. + let anything_off = !capture || !emulation; + self.imp().emulation_status_row.set_visible(false); + self.imp().capture_status_row.set_visible(anything_off); + self.imp().capture_emulation_group.set_visible(anything_off); + + if anything_off { + self.update_macos_warning_row_text(); + } + } + + #[cfg(not(target_os = "macos"))] + { + self.imp().capture_status_row.set_visible(!capture); + self.imp().emulation_status_row.set_visible(!emulation); + self.imp() + .capture_emulation_group + .set_visible(!capture || !emulation); + } + } + + #[cfg(target_os = "macos")] + fn update_macos_warning_row_text(&self) { + let row = &self.imp().capture_status_row; + let button = &self.imp().input_capture_button; + + if crate::macos_privacy::accessibility_granted() { + // AX granted but capture/emulation still off → the daemon + // subprocess bailed at startup and needs a fresh process to + // re-initialize with the new grant in place. + row.set_title("relaunch required"); + row.set_subtitle("Accessibility granted — restart to activate capture and emulation"); + set_button_content_label(button, "Relaunch"); + } else { + // AX missing → send the user to System Settings. + row.set_title("input capture is disabled"); + row.set_subtitle("grant Accessibility permission to enable"); + set_button_content_label(button, "Grant"); + } } pub(super) fn set_authorized_keys(&self, fingerprints: HashMap) { diff --git a/lan-mouse-gtk/src/window/imp.rs b/lan-mouse-gtk/src/window/imp.rs index f37647e..bb7d48c 100644 --- a/lan-mouse-gtk/src/window/imp.rs +++ b/lan-mouse-gtk/src/window/imp.rs @@ -142,11 +142,32 @@ impl Window { #[template_callback] fn handle_emulation(&self) { + // On macOS the emulation_status_row is hidden — capture_status_row + // acts as the shared warning (see update_capture_emulation_status). + // This handler still fires for the non-macOS platforms where the + // emulation row is distinct. self.obj().request_emulation(); } #[template_callback] fn handle_capture(&self) { + #[cfg(target_os = "macos")] + { + use crate::macos_privacy; + if macos_privacy::accessibility_granted() { + // AX granted but the row is still visible => the daemon + // subprocess bailed before AX was in place and needs a + // fresh process. Quit + relaunch via Launch Services. + log::info!("capture row clicked in relaunch-required state"); + macos_privacy::relaunch_bundle(); + if let Some(app) = self.obj().application() { + app.quit(); + } + return; + } + log::info!("capture row clicked in AX-missing state, opening pane"); + macos_privacy::open_accessibility_settings(); + } self.obj().request_capture(); } diff --git a/scripts/copy-macos-dylib.sh b/scripts/copy-macos-dylib.sh index f2ba501..84fb4d9 100755 --- a/scripts/copy-macos-dylib.sh +++ b/scripts/copy-macos-dylib.sh @@ -43,6 +43,9 @@ bundle_path=$(dirname "$(dirname "$(dirname "$exec_path")")") # Path to the Frameworks directory fwks_path="$bundle_path/Contents/Frameworks" mkdir -p "$fwks_path" +# Path to bundled GTK/GSettings data +resources_path="$bundle_path/Contents/Resources" +share_path="$resources_path/share" # Copy and fix references for a binary (executable or dylib) # @@ -58,6 +61,10 @@ fix_references() { libs=$(otool -L "$bin" | awk -v homebrew="$homebrew_path" '$0 ~ homebrew {print $1}') echo "$libs" | while IFS= read -r old_path; do + if [ -z "$old_path" ]; then + continue + fi + local base_name="$(basename "$old_path")" local dest="$fwks_path/$base_name" @@ -81,6 +88,42 @@ fix_references() { fix_references "$exec_path" +copy_runtime_data() { + mkdir -p "$share_path" + + if [ -d "$homebrew_path/share/glib-2.0/schemas" ]; then + mkdir -p "$share_path/glib-2.0" + rm -rf "$share_path/glib-2.0/schemas" + cp -RL "$homebrew_path/share/glib-2.0/schemas" "$share_path/glib-2.0/schemas" + if command -v glib-compile-schemas >/dev/null 2>&1; then + glib-compile-schemas "$share_path/glib-2.0/schemas" + elif [ -x "$homebrew_path/bin/glib-compile-schemas" ]; then + "$homebrew_path/bin/glib-compile-schemas" "$share_path/glib-2.0/schemas" + fi + fi + + if [ -d "$homebrew_path/share/gtk-4.0" ]; then + rm -rf "$share_path/gtk-4.0" + cp -RL "$homebrew_path/share/gtk-4.0" "$share_path/gtk-4.0" + fi + + if [ -d "$homebrew_path/share/icons/Adwaita" ]; then + mkdir -p "$share_path/icons" + rm -rf "$share_path/icons/Adwaita" + cp -RL "$homebrew_path/share/icons/Adwaita" "$share_path/icons/Adwaita" + fi +} + +copy_runtime_data + +# cargo-bundle preserves the source path under Contents/Resources (so +# `target/menubar-template.png` lands at `Resources/target/...`). Flatten it +# so NSBundle pathForResource: finds the file at the Resources root. +if [ -f "$resources_path/target/menubar-template.png" ]; then + mv "$resources_path/target/menubar-template.png" "$resources_path/menubar-template.png" + rmdir "$resources_path/target" 2>/dev/null || true +fi + # 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" diff --git a/scripts/makeicns.sh b/scripts/makeicns.sh index babce97..a820898 100755 --- a/scripts/makeicns.sh +++ b/scripts/makeicns.sh @@ -3,8 +3,15 @@ set -e usage() { cat </dev/null 2>&1; then + echo "iconutil failed and perl is not available for the fallback icns writer" >&2 + exit 1 + fi + + echo "iconutil rejected the iconset; writing icns directly" >&2 + perl - "$icns" "$iconset" <<'PERL' +use strict; +use warnings; + +my ($icns, $iconset) = @ARGV; +my @icons = ( + [ 'icp4', "$iconset/icon_16x16.png" ], + [ 'ic11', "$iconset/icon_16x16\@2x.png" ], + [ 'icp5', "$iconset/icon_32x32.png" ], + [ 'ic12', "$iconset/icon_32x32\@2x.png" ], + [ 'ic07', "$iconset/icon_128x128.png" ], + [ 'ic13', "$iconset/icon_128x128\@2x.png" ], + [ 'ic08', "$iconset/icon_256x256.png" ], + [ 'ic14', "$iconset/icon_256x256\@2x.png" ], + [ 'ic09', "$iconset/icon_512x512.png" ], + [ 'ic10', "$iconset/icon_512x512\@2x.png" ], +); + +my $body = ''; +for my $icon (@icons) { + my ($type, $path) = @$icon; + open my $fh, '<:raw', $path or die "$path: $!"; + local $/; + my $png = <$fh>; + $body .= $type . pack('N', length($png) + 8) . $png; +} + +open my $out, '>:raw', $icns or die "$icns: $!"; +print {$out} 'icns' . pack('N', length($body) + 8) . $body; +PERL +fi diff --git a/src/capture.rs b/src/capture.rs index e4d9c8a..8f739bd 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -8,7 +8,7 @@ use futures::StreamExt; use input_capture::{ CaptureError, CaptureEvent, CaptureHandle, InputCapture, InputCaptureError, Position, }; -use input_event::scancode; +use input_event::{Event, KeyboardEvent, scancode}; use lan_mouse_proto::ProtoEvent; use local_channel::mpsc::{Receiver, Sender, channel}; use tokio::task::{JoinHandle, spawn_local}; @@ -49,7 +49,7 @@ pub(crate) enum CaptureType { EnterOnly, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Debug)] enum CaptureRequest { /// capture must release the mouse Release, @@ -59,6 +59,8 @@ enum CaptureRequest { Destroy(CaptureHandle), /// reenable input capture Reenable, + /// set release bind + SetReleaseBind(Vec), } impl Capture { @@ -131,6 +133,10 @@ impl Capture { pub(crate) async fn event(&mut self) -> ICaptureEvent { self.event_rx.recv().await.expect("channel closed") } + + pub(crate) fn set_release_bind(&mut self, bind: Vec) { + let _ = self.request_tx.send(CaptureRequest::SetReleaseBind(bind)); + } } /// debounce a statement `$st`, i.e. the statement is executed only if the @@ -205,6 +211,9 @@ impl CaptureTask { CaptureRequest::Create(h, p, t) => self.add_capture(h, p, t), CaptureRequest::Destroy(h) => self.remove_capture(h), CaptureRequest::Release => { /* nothing to do */ } + CaptureRequest::SetReleaseBind(bind) => { + self.release_bind.borrow_mut().clone_from(&bind); + } }, _ = self.cancellation_token.cancelled() => return, } @@ -295,6 +304,9 @@ impl CaptureTask { self.remove_capture(h); capture.destroy(h).await?; } + CaptureRequest::SetReleaseBind(bind) => { + self.release_bind.borrow_mut().clone_from(&bind); + } }, _ = self.cancellation_token.cancelled() => break, } @@ -364,6 +376,41 @@ impl CaptureTask { async fn release_capture(&mut self, capture: &mut InputCapture) -> Result<(), CaptureError> { // If we have an active client, notify them we're leaving if let Some(handle) = self.active_client.take() { + // Synthesize key-up events for every key still held in the + // capture's pressed_keys set BEFORE sending Leave. Without + // this, pressing the release-bind chord (typically all four + // modifiers) leaves the peer with phantom held modifiers: + // the down events were forwarded while capture was active, + // but the matching up events arrive after the local tap + // flips to passthrough and never reach the peer. The peer + // then runs every subsequent keystroke through those held + // mods until its watchdog times out (1+ s) or our Leave + // arrives — and Leave can be lost over UDP/DTLS. + for key in capture.take_pressed_keys() { + let key_up = ProtoEvent::Input(Event::Keyboard(KeyboardEvent::Key { + time: 0, + key: key as u32, + state: 0, + })); + if let Err(e) = self.conn.send(key_up, handle).await { + log::warn!("failed to send key-up to client {handle}: {e}"); + } + } + // Reset the modifier mask too. The peer's input-emulation + // layer keeps a separate XKB-style modifier state that's + // updated by KeyboardEvent::Modifiers, distinct from the + // pressed_keys set drained above. Without this, an + // already-locked CapsLock would survive the release. + let mods_zero = ProtoEvent::Input(Event::Keyboard(KeyboardEvent::Modifiers { + depressed: 0, + latched: 0, + locked: 0, + group: 0, + })); + if let Err(e) = self.conn.send(mods_zero, handle).await { + log::warn!("failed to reset modifiers on client {handle}: {e}"); + } + log::info!("sending Leave event to client {handle}"); if let Err(e) = self.conn.send(ProtoEvent::Leave(0), handle).await { log::warn!("failed to send Leave to client {handle}: {e}"); diff --git a/src/client.rs b/src/client.rs index b67f787..3229c8c 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,6 +9,8 @@ use slab::Slab; use lan_mouse_ipc::{ClientConfig, ClientHandle, ClientState, Position}; +use crate::config::ConfigClient; + #[derive(Clone, Default)] pub struct ClientManager { clients: Rc>>, @@ -24,6 +26,25 @@ impl ClientManager { .collect::>() } + pub fn add_with_config(&self, config_client: ConfigClient) -> ClientHandle { + let config = ClientConfig { + hostname: config_client.hostname, + fix_ips: config_client.ips.into_iter().collect(), + port: config_client.port, + pos: config_client.pos, + cmd: config_client.enter_hook, + }; + let state = ClientState { + active: config_client.active, + ips: HashSet::from_iter(config.fix_ips.iter().cloned()), + ..Default::default() + }; + let handle = self.add_client(); + self.set_config(handle, config); + self.set_state(handle, state); + handle + } + /// add a new client to this manager pub fn add_client(&self) -> ClientHandle { self.clients.borrow_mut().insert(Default::default()) as ClientHandle @@ -230,6 +251,15 @@ impl ClientManager { .and_then(|(c, _)| c.cmd.clone()) } + /// returns all clients that are currently registered + pub(crate) fn registered_clients(&self) -> Vec { + self.clients + .borrow() + .iter() + .map(|(h, _)| h as ClientHandle) + .collect() + } + /// returns all clients that are currently active pub(crate) fn active_clients(&self) -> Vec { self.clients diff --git a/src/config.rs b/src/config.rs index 78750f3..3faa59b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use crate::capture_test::TestCaptureArgs; use crate::emulation_test::TestEmulationArgs; use clap::{Parser, Subcommand, ValueEnum}; +use notify::{EventKind, RecommendedWatcher, Watcher}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::env::{self, VarError}; @@ -46,7 +47,7 @@ fn default_path() -> Result { Ok(PathBuf::from(default_path)) } -#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] struct ConfigToml { capture_backend: Option, emulation_backend: Option, @@ -244,8 +245,14 @@ pub struct Config { cert_path: PathBuf, /// path to the config file used config_path: PathBuf, + /// path to config directory (parent of above) + config_dir: PathBuf, /// the (optional) toml config and it's path config_toml: Option, + // filesystem watcher + watcher: notify::RecommendedWatcher, + // channel for filesystem events + watch_rx: tokio::sync::mpsc::Receiver>, } pub struct ConfigClient { @@ -311,6 +318,8 @@ pub enum ConfigError { Io(#[from] io::Error), #[error(transparent)] Var(#[from] VarError), + #[error(transparent)] + Watcher(#[from] notify::Error), } const DEFAULT_RELEASE_KEYS: [scancode::Linux; 4] = @@ -325,6 +334,23 @@ impl Config { .config .clone() .unwrap_or(default_path()?.join(CONFIG_FILE_NAME)); + let config_dir = config_path + .parent() + .expect("config directory") + .to_path_buf(); + + // Ensure the config directory exists and write a default config file + // if none is present. Runs on every Config::new(), regardless of which + // entry path (GUI main, spawned daemon, CLI, test commands) we're on, + // so a fresh Mac never hits "No such file or directory" on config.toml + // and notify::Watcher (which requires the dir to exist on macOS + // FSEvents and some Linux backends) has a concrete path to watch. + fs::create_dir_all(&config_dir)?; + if !config_path.exists() { + let default_toml = toml::to_string_pretty(&ConfigToml::default()) + .expect("default ConfigToml serialization cannot fail"); + fs::write(&config_path, default_toml)?; + } let config_toml = match ConfigToml::new(&config_path) { Err(e) => { @@ -342,12 +368,51 @@ impl Config { .or(config_toml.as_ref().and_then(|c| c.cert_path.clone())) .unwrap_or(default_path()?.join(CERT_FILE_NAME)); - Ok(Config { + let (tx, watch_rx) = tokio::sync::mpsc::channel(16); + let watcher = RecommendedWatcher::new( + move |res| { + let _ = tx.blocking_send(res); + }, + notify::Config::default(), + )?; + let mut config = Config { args, cert_path, config_path, + config_dir, config_toml, - }) + watcher, + watch_rx, + }; + config.watch()?; + Ok(config) + } + + fn watch(&mut self) -> Result<(), notify::Error> { + self.watcher + .watch(&self.config_dir, notify::RecursiveMode::NonRecursive)?; + Ok(()) + } + + fn unwatch(&mut self) -> Result<(), notify::Error> { + self.watcher.unwatch(&self.config_dir)?; + Ok(()) + } + + pub async fn changed(&mut self) -> Result<(), notify::Error> { + loop { + let event = self.watch_rx.recv().await.expect("channel closed"); + let event = event.expect("filesystem event"); + if event.paths.contains(&self.config_path) + && matches!( + event.kind, + EventKind::Create(_) | EventKind::Modify(_) | EventKind::Remove(_) + ) + && self.read_from_disk()? + { + return Ok(()); + } + } } /// the command to run @@ -428,11 +493,8 @@ impl Config { /// set authorized keys pub fn set_authorized_keys(&mut self, fingerprints: HashMap) { - if fingerprints.is_empty() { - return; - } if self.config_toml.is_none() { - self.config_toml = Default::default(); + self.config_toml = Some(Default::default()); } self.config_toml .as_mut() @@ -440,38 +502,58 @@ impl Config { .authorized_fingerprints = Some(fingerprints); } - pub fn write_back(&self) -> Result<(), io::Error> { - log::info!("writing config to {:?}", &self.config_path); - /* load the current configuration file */ - let current_config = match fs::read_to_string(&self.config_path) { - Ok(c) => c.parse::().unwrap_or_default(), + pub fn read_from_disk(&mut self) -> Result { + log::info!("reading config from {:?}", &self.config_path); + + let current_config = fs::read_to_string(&self.config_path)?; + let current_config = match current_config.parse::() { + Ok(c) => c, Err(e) => { - log::info!("{:?} {e} => creating new config", self.config_path()); - Default::default() + log::warn!("{:?} {e}", self.config_path()); + return Ok(false); } }; - let _current_config = - toml_edit::de::from_document::(current_config).unwrap_or_default(); + let mut changed = false; + match toml_edit::de::from_document::(current_config) { + Ok(current_config) => { + changed = self + .config_toml + .as_ref() + .is_none_or(|c| c != ¤t_config); + self.config_toml.replace(current_config); + } + Err(e) => log::warn!("{:?} {e}", self.config_path()), + }; + Ok(changed) + } + pub fn write_back(&mut self) -> Result<(), io::Error> { + log::info!("writing config to {:?}", &self.config_path); /* the new config */ let new_config = self.config_toml.clone().unwrap_or_default(); - // let new_config = toml_edit::ser::to_document::(&new_config).expect("fixme"); let new_config = toml_edit::ser::to_string_pretty(&new_config).expect("config"); /* - * TODO merge documents => eventually we might want to split this up into clients configured + * TODO merge with current config file to preserve comments + * => eventually we might want to split this up into clients configured * via the config file and clients managed through the GUI / frontend. * The latter should be saved to $XDG_DATA_HOME instead of $XDG_CONFIG_HOME, * and clients configured through .config could be made permanent. * For now we just override the config file. */ + let _ = self.unwatch(); /* write new config to file */ if let Some(p) = self.config_path().parent() { fs::create_dir_all(p)?; } - let mut f = File::create(self.config_path())?; - f.write_all(new_config.as_bytes())?; + { + let mut f = File::create(self.config_path())?; + f.write_all(new_config.as_bytes())?; + f.sync_all()?; + } + + let _ = self.watch(); Ok(()) } diff --git a/src/service.rs b/src/service.rs index ff7c22f..d0772f6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -11,8 +11,8 @@ use crate::{ use futures::StreamExt; use hickory_resolver::ResolveError; use lan_mouse_ipc::{ - AsyncFrontendListener, ClientConfig, ClientHandle, ClientState, FrontendEvent, FrontendRequest, - IpcError, IpcListenerCreationError, Position, Status, + AsyncFrontendListener, ClientHandle, FrontendEvent, FrontendRequest, IpcError, + IpcListenerCreationError, Position, Status, }; use log; use std::{ @@ -83,21 +83,7 @@ impl Service { pub async fn new(config: Config) -> Result { let client_manager = ClientManager::default(); for client in config.clients() { - let config = ClientConfig { - hostname: client.hostname, - fix_ips: client.ips.into_iter().collect(), - port: client.port, - pos: client.pos, - cmd: client.enter_hook, - }; - let state = ClientState { - active: client.active, - ips: HashSet::from_iter(config.fix_ips.iter().cloned()), - ..Default::default() - }; - let handle = client_manager.add_client(); - client_manager.set_config(handle, config); - client_manager.set_state(handle, state); + client_manager.add_with_config(client); } // load certificate @@ -164,6 +150,7 @@ impl Service { event = self.emulation.event() => self.handle_emulation_event(event), event = self.capture.event() => self.handle_capture_event(event), event = self.resolver.event() => self.handle_resolver_event(event), + _ = self.config.changed() => self.handle_config_change(), r = signal::ctrl_c() => break r.expect("failed to wait for CTRL+C"), } } @@ -255,6 +242,30 @@ impl Service { } } + fn handle_config_change(&mut self) { + for h in self.client_manager.registered_clients() { + self.remove_client(h); + } + for c in self.config.clients() { + let handle = self.client_manager.add_with_config(c); + log::info!("added client {handle}"); + let (c, s) = self.client_manager.get_state(handle).unwrap(); + if s.active { + self.client_manager.deactivate_client(handle); + self.activate_client(handle); + } + self.notify_frontend(FrontendEvent::Created(handle, c, s)); + } + let release_bind = self.config.release_bind(); + self.capture.set_release_bind(release_bind); + let authorized_keys = self.config.authorized_fingerprints(); + self.authorized_keys + .write() + .unwrap() + .clone_from(&authorized_keys); + self.sync_frontend(); + } + async fn handle_frontend_pending(&mut self) { while let Some(event) = self.pending_frontend_events.pop_front() { self.frontend_listener.broadcast(event).await; @@ -477,7 +488,7 @@ impl Service { } fn activate_client(&mut self, handle: ClientHandle) { - log::debug!("activating client"); + log::debug!("activating client {handle}"); /* resolve dns on activate */ self.resolve(handle);