From 36f9461c857d35990142ee790ee2885f089c8031 Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Fri, 1 May 2026 15:08:08 -0400 Subject: [PATCH 1/3] chore: migrate nmrs-gui off of nmrs --- .github/workflows/ci.yml | 26 +- .github/workflows/release-gui.yml | 89 ---- AGENTS.md | 7 +- CHANGELOG.md | 4 +- CONTRIBUTING.md | 5 - Cargo.lock | 648 +---------------------- Cargo.toml | 12 +- README.md | 60 +-- SECURITY.md | 11 +- default.nix | 4 - docker-compose.yml | 2 +- docs/src/SUMMARY.md | 8 - docs/src/advanced/async-runtimes.md | 4 - docs/src/appendix/changelog.md | 40 +- docs/src/appendix/faq.md | 18 - docs/src/appendix/troubleshooting.md | 19 - docs/src/development/contributing.md | 6 - docs/src/development/releases.md | 20 +- docs/src/getting-started/installation.md | 71 +-- docs/src/getting-started/requirements.md | 47 +- docs/src/gui/configuration.md | 145 ----- docs/src/gui/installation.md | 113 ---- docs/src/gui/overview.md | 67 --- docs/src/gui/themes.md | 83 --- docs/src/gui/waybar.md | 67 --- docs/src/introduction.md | 18 - flake.lock | 158 ------ flake.nix | 87 --- nmrs-gui/CHANGELOG.md | 105 ---- nmrs-gui/Cargo.toml | 24 - nmrs-gui/build.rs | 19 - nmrs-gui/src/file_lock.rs | 59 --- nmrs-gui/src/lib.rs | 52 -- nmrs-gui/src/main.rs | 4 - nmrs-gui/src/objects/mod.rs | 1 - nmrs-gui/src/objects/theme.rs | 38 -- nmrs-gui/src/style.css | 314 ----------- nmrs-gui/src/style.rs | 96 ---- nmrs-gui/src/theme_config.rs | 26 - nmrs-gui/src/themes/catppuccin.css | 364 ------------- nmrs-gui/src/themes/dracula.css | 364 ------------- nmrs-gui/src/themes/gruvbox.css | 395 -------------- nmrs-gui/src/themes/nord.css | 364 ------------- nmrs-gui/src/themes/tokyo.css | 364 ------------- nmrs-gui/src/ui/connect.rs | 256 --------- nmrs-gui/src/ui/header.rs | 552 ------------------- nmrs-gui/src/ui/mod.rs | 258 --------- nmrs-gui/src/ui/network_page.rs | 243 --------- nmrs-gui/src/ui/networks.rs | 315 ----------- nmrs-gui/src/ui/settings_page.rs | 156 ------ nmrs-gui/src/ui/wired_devices.rs | 180 ------- nmrs-gui/src/ui/wired_page.rs | 136 ----- nmrs-gui/tests/smoke_test.rs | 13 - nmrs-gui/tests/style_test.rs | 9 - nmrs.desktop | 9 - package.nix | 50 -- scripts/README.md | 54 +- scripts/bump_version.py | 73 +-- 58 files changed, 62 insertions(+), 6670 deletions(-) delete mode 100644 .github/workflows/release-gui.yml delete mode 100644 default.nix delete mode 100644 docs/src/gui/configuration.md delete mode 100644 docs/src/gui/installation.md delete mode 100644 docs/src/gui/overview.md delete mode 100644 docs/src/gui/themes.md delete mode 100644 docs/src/gui/waybar.md delete mode 100644 flake.lock delete mode 100644 flake.nix delete mode 100644 nmrs-gui/CHANGELOG.md delete mode 100644 nmrs-gui/Cargo.toml delete mode 100644 nmrs-gui/build.rs delete mode 100644 nmrs-gui/src/file_lock.rs delete mode 100644 nmrs-gui/src/lib.rs delete mode 100644 nmrs-gui/src/main.rs delete mode 100644 nmrs-gui/src/objects/mod.rs delete mode 100644 nmrs-gui/src/objects/theme.rs delete mode 100644 nmrs-gui/src/style.css delete mode 100644 nmrs-gui/src/style.rs delete mode 100644 nmrs-gui/src/theme_config.rs delete mode 100644 nmrs-gui/src/themes/catppuccin.css delete mode 100644 nmrs-gui/src/themes/dracula.css delete mode 100644 nmrs-gui/src/themes/gruvbox.css delete mode 100644 nmrs-gui/src/themes/nord.css delete mode 100644 nmrs-gui/src/themes/tokyo.css delete mode 100644 nmrs-gui/src/ui/connect.rs delete mode 100644 nmrs-gui/src/ui/header.rs delete mode 100644 nmrs-gui/src/ui/mod.rs delete mode 100644 nmrs-gui/src/ui/network_page.rs delete mode 100644 nmrs-gui/src/ui/networks.rs delete mode 100644 nmrs-gui/src/ui/settings_page.rs delete mode 100644 nmrs-gui/src/ui/wired_devices.rs delete mode 100644 nmrs-gui/src/ui/wired_page.rs delete mode 100644 nmrs-gui/tests/smoke_test.rs delete mode 100644 nmrs-gui/tests/style_test.rs delete mode 100644 nmrs.desktop delete mode 100644 package.nix diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee97dde2..85a28357 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,13 +44,7 @@ jobs: sudo apt-get update sudo apt-get install -y \ pkg-config \ - libglib2.0-dev \ - libgirepository1.0-dev \ - libgdk-pixbuf2.0-dev \ - libpango1.0-dev \ - libcairo2-dev \ - libgtk-4-dev \ - libadwaita-1-dev + libdbus-1-dev - uses: dtolnay/rust-toolchain@stable with: @@ -79,13 +73,7 @@ jobs: sudo apt-get update sudo apt-get install -y \ pkg-config \ - libglib2.0-dev \ - libgirepository1.0-dev \ - libgdk-pixbuf2.0-dev \ - libpango1.0-dev \ - libcairo2-dev \ - libgtk-4-dev \ - libadwaita-1-dev + libdbus-1-dev - uses: dtolnay/rust-toolchain@stable @@ -137,7 +125,7 @@ jobs: matrix: include: - target: x86_64-unknown-linux-gnu - packages: nmrs nmrs-gui + packages: nmrs - target: aarch64-unknown-linux-gnu packages: nmrs steps: @@ -148,13 +136,7 @@ jobs: sudo apt-get update sudo apt-get install -y \ pkg-config \ - libglib2.0-dev \ - libgirepository1.0-dev \ - libgdk-pixbuf2.0-dev \ - libpango1.0-dev \ - libcairo2-dev \ - libgtk-4-dev \ - libadwaita-1-dev + libdbus-1-dev - name: Install cross-compilation tools if: matrix.target == 'aarch64-unknown-linux-gnu' diff --git a/.github/workflows/release-gui.yml b/.github/workflows/release-gui.yml deleted file mode 100644 index 877bee2d..00000000 --- a/.github/workflows/release-gui.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Release GUI - -on: - push: - tags: - - 'gui-v*' - -jobs: - release: - name: Build and Release nmrs-gui - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - - name: Parse version from tag - id: version - run: | - TAG=${GITHUB_REF#refs/tags/gui-v} - echo "VERSION=$TAG" >> $GITHUB_ENV - echo "Releasing nmrs-gui version: $TAG" - - - name: Install dependencies - run: | - sudo apt-get update - sudo apt-get install -y \ - pkg-config \ - libglib2.0-dev \ - libgirepository1.0-dev \ - libgdk-pixbuf2.0-dev \ - libpango1.0-dev \ - libcairo2-dev \ - libgtk-4-dev \ - libadwaita-1-dev - - - uses: dtolnay/rust-toolchain@stable - - - name: Verify version matches tag - run: | - CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "nmrs-gui") | .version') - if [ "$CARGO_VERSION" != "${{ env.VERSION }}" ]; then - echo "Error: Cargo.toml version ($CARGO_VERSION) doesn't match tag version (${{ env.VERSION }})" - exit 1 - fi - echo "Version check passed: $CARGO_VERSION" - - - name: Build nmrs-gui - run: cargo build --release --package nmrs-gui - - - name: Create release archive - run: | - mkdir -p release - cp target/release/nmrs-gui release/ - cp LICENSE-MIT LICENSE-APACHE release/ - cp README.md release/ - cd release - tar -czf nmrs-gui-${{ env.VERSION }}-x86_64-linux.tar.gz * - - - name: Create GitHub Release - uses: softprops/action-gh-release@v3 - with: - tag_name: gui-v${{ env.VERSION }} - name: nmrs-gui ${{ env.VERSION }} - body: | - ## nmrs-gui ${{ env.VERSION }} - - A Wayland-compatible NetworkManager frontend built with GTK4. - - ### Installation - - **Arch Linux (AUR)** - ```bash - yay -S nmrs - ``` - - **Manual Installation** - Download the binary, extract, and move to your PATH: - ```bash - tar -xzf nmrs-gui-${{ env.VERSION }}-x86_64-linux.tar.gz - sudo mv nmrs-gui /usr/local/bin/nmrs - ``` - - See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/master/CHANGELOG.md) for full details. - files: | - release/nmrs-gui-${{ env.VERSION }}-x86_64-linux.tar.gz - draft: false - prerelease: false - diff --git a/AGENTS.md b/AGENTS.md index f10d389f..25e701fe 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,10 +5,6 @@ Context for AI agents working in this repository. ## Project overview `nmrs` is a Rust library for managing network connections via NetworkManager over D-Bus. -The workspace has two crates: - -- **`nmrs/`** — the core library (async, D-Bus, no GUI dependencies) -- **`nmrs-gui/`** — a GTK4/libadwaita GUI frontend ## Architecture @@ -79,11 +75,10 @@ Atomic commits — one logical change per commit. ## Changelog -[Keep a Changelog](https://keepachangelog.com/) format in `nmrs/CHANGELOG.md` and `nmrs-gui/CHANGELOG.md`. +[Keep a Changelog](https://keepachangelog.com/) format in `nmrs/CHANGELOG.md`. Sections: `Added`, `Changed`, `Fixed`. Link PRs/issues in parentheses. ## Things to watch out for - The `VpnCredentials` type is deprecated — prefer `WireGuardConfig` for new WireGuard code. -- `nmrs-gui` depends on GTK4/libadwaita and only builds on Linux with those dev libs installed. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f12e048..006b1262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,3 @@ # Changelog -See the per-crate changelogs: -- [`nmrs/CHANGELOG.md`](./nmrs/CHANGELOG.md) — Core library -- [`nmrs-gui/CHANGELOG.md`](./nmrs-gui/CHANGELOG.md) — GUI application \ No newline at end of file +See [`nmrs/CHANGELOG.md`](./nmrs/CHANGELOG.md) for the full changelog. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9779a312..25cd05bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,8 +35,6 @@ docker run --rm nmrs-lib cargo test -p nmrs --lib docker run --rm -it -v $(pwd):/app nmrs-lib # mounts local changes ``` -It goes without saying that this image only works with nmrs. nmrs-gui requires GTK deps which in that case, you are better off just running a VM or learning how to use Linux on a machine instead. - If you decide to run the shell, ensure you run all commands from within the nmrs directory, not root. ```bash cargo test -p nmrs # run library tests @@ -44,9 +42,6 @@ cargo build -p nmrs # build the library cargo check # you get the point... ``` -**To develop nmrs-gui, you'll need:** -- GTK4 and libadwaita development libraries -- A Wayland compositor ## When your branch falls behind `master` If the respective branch for a PR goes out of sync, I prefer you _rebase_. diff --git a/Cargo.lock b/Cargo.lock index ae9619bd..41917469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,56 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - [[package]] name = "anyhow" version = "1.0.102" @@ -232,97 +182,12 @@ version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" -[[package]] -name = "bytes" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" - -[[package]] -name = "cairo-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95" -dependencies = [ - "bitflags", - "cairo-sys-rs", - "glib", - "libc", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b4985713047f5faee02b8db6a6ef32bbb50269ff53c1aee716d1d195b76d54" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "cfg-expr" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2c5f3bf25ec225351aa1c8e230d04d880d3bd89dea133537dafad4ae291e5c" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" -[[package]] -name = "clap" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" - -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -338,27 +203,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - [[package]] name = "endi" version = "1.1.0" @@ -429,32 +273,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "fs2" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "futures" version = "0.3.32" @@ -562,74 +386,6 @@ dependencies = [ "slab", ] -[[package]] -name = "gdk-pixbuf" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f420376dbee041b2db374ce4573892a36222bb3f6c0c43e24f0d67eae9b646" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f31b37b1fc4b48b54f6b91b7ef04c18e00b4585d98359dd7b998774bbd91fb" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk4" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9a95621b043a35e70ea9f6da89e1a471658ee5771258874f000a11f6a9cb89" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk4-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk4-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d322515677e4a12e10efe7e758743c3e5faa56940237f19ba6890ba8edbbb76" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.3.3" @@ -639,7 +395,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.7+wasi-0.2.4", + "wasi", ] [[package]] @@ -655,196 +411,6 @@ dependencies = [ "wasip3", ] -[[package]] -name = "gio" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816b6743c46b217aa8fba679095ac6f2162fd53259dc8f186fcdbff9c555db03" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64729ba2772c080448f9f966dba8f4456beeb100d8c28a865ef8a0f2ef4987e1" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys", -] - -[[package]] -name = "glib" -version = "0.22.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a" -dependencies = [ - "bitflags", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.22.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda575994e3689b1bc12f89c3df621ead46ff292623b76b4710a3a5b79be54bb" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48073e3b228419faa80b9b7f7122759d4ab2f44cd52a065fde7ca08f34c03147" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "gobject-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18eda93f09d3778f38255b231b17ef67195013a592c91624a4daf8bead875565" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "graphene-rs" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d1b7881f96869f49808b6adfe906a93a57a34204952253444d68c3208d71f1" -dependencies = [ - "glib", - "graphene-sys", - "libc", -] - -[[package]] -name = "graphene-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "517f062f3fd6b7fd3e57a3f038a74b3c23ca32f51199ff028aa704609943f79c" -dependencies = [ - "glib-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gsk4" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c84a7e778764e5c8e67440c616e11a0da21828bbf04490655c4058f6c917af6f" -dependencies = [ - "cairo-rs", - "gdk4", - "glib", - "graphene-rs", - "gsk4-sys", - "libc", - "pango", -] - -[[package]] -name = "gsk4-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbfbe080ce408e35a94eded4b9cc7a16fa2ffbaf6f04ab5a35d9c3c73841c88" -dependencies = [ - "cairo-sys-rs", - "gdk4-sys", - "glib-sys", - "gobject-sys", - "graphene-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk4" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d47a7ca9ec6f50b5ace32eaaf11fe152c9bbc4f780a35e42c9b7fc5b046f9c" -dependencies = [ - "cairo-rs", - "field-offset", - "futures-channel", - "gdk-pixbuf", - "gdk4", - "gio", - "glib", - "graphene-rs", - "gsk4", - "gtk4-macros", - "gtk4-sys", - "libc", - "pango", -] - -[[package]] -name = "gtk4-macros" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3581b242ba62fdff122ebb626ea641582ec326031622bd19d60f85029c804a87" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "gtk4-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f063fc314b4d23aac4316a877159c02e86c1b2ba1f0a13e9aafbbebdce9c0800" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk4-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "graphene-sys", - "gsk4-sys", - "libc", - "pango-sys", - "system-deps", -] - [[package]] name = "hashbrown" version = "0.15.5" @@ -890,12 +456,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itoa" version = "1.0.17" @@ -924,16 +484,6 @@ version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" -[[package]] -name = "libredox" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" -dependencies = [ - "bitflags", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -979,40 +529,12 @@ dependencies = [ "zvariant", ] -[[package]] -name = "nmrs-gui" -version = "1.5.1" -dependencies = [ - "anyhow", - "clap", - "dirs", - "fs2", - "glib", - "gtk4", - "log", - "nmrs", - "tokio", - "tokio-util", -] - [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "ordered-stream" version = "0.2.0" @@ -1023,30 +545,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "pango" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25d8f224eddef627b896d2f7b05725b3faedbd140e0e8343446f0d34f34238ee" -dependencies = [ - "gio", - "glib", - "libc", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd111a20ca90fedf03e09c59783c679c00900f1d8491cca5399f5e33609d5d6" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "parking" version = "2.2.1" @@ -1070,12 +568,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - [[package]] name = "polling" version = "3.11.0" @@ -1106,7 +598,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit 0.23.6", + "toml_edit", ] [[package]] @@ -1133,26 +625,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "1.1.2" @@ -1232,15 +704,6 @@ dependencies = [ "syn", ] -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - [[package]] name = "sha1_smol" version = "1.0.1" @@ -1262,18 +725,6 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "syn" version = "2.0.117" @@ -1285,25 +736,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "system-deps" -version = "7.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" - [[package]] name = "tempfile" version = "3.23.0" @@ -1358,40 +790,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - [[package]] name = "toml_datetime" version = "0.7.2" @@ -1401,19 +799,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.23.6" @@ -1421,7 +806,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" dependencies = [ "indexmap", - "toml_datetime 0.7.2", + "toml_datetime", "toml_parser", "winnow 0.7.13", ] @@ -1489,12 +874,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "uuid" version = "1.23.1" @@ -1508,18 +887,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasi" version = "0.14.7+wasi-0.2.4" @@ -1677,15 +1044,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.7.13" diff --git a/Cargo.toml b/Cargo.toml index e1f215b1..9b1c5c6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ - "nmrs", - "nmrs-gui" + "nmrs" ] resolver = "3" @@ -12,8 +11,6 @@ license = "MIT" repository = "https://github.com/cachebag/nmrs" [workspace.lints.rust] -# This shouldn't apply to nmrs-gui -# missing_docs = "warn" unused = { level = "warn", priority = -1 } [workspace.lints.clippy] @@ -33,13 +30,6 @@ futures = "0.3.32" futures-timer = "3.0.3" base64 = "0.22.1" nmrs = { path = "nmrs", version = "3.0" } -gtk = { version = "0.11.2", package = "gtk4" } -glib = "0.22.7" -dirs = "6.0.0" -fs2 = "0.4.3" -anyhow = "1.0.102" -clap = { version = "4.6.1", features = ["derive"] } async-trait = "0.1.89" bitflags = "2.11.1" tokio = { version = "1.52.1", features = ["rt-multi-thread", "macros", "sync", "time"] } -tokio-util = { version = "0.7.18" } diff --git a/README.md b/README.md index 68124245..278c57f6 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,6 @@ An async-first Rust API for [NetworkManager](https://networkmanager.dev/) over [D-Bus](https://dbus.freedesktop.org/doc/dbus-specification.html). The goal is to provide a safe and simple high-level API for managing Wi-Fi connections on Linux systems, built on [`zbus`](https://docs.rs/zbus) for reliable D-Bus communication. -The project is divided into the following crates: - -* `nmrs`: The core library providing NetworkManager bindings and Wi-Fi management API. -* `nmrs-gui`: A Wayland-compatible GTK4 graphical interface for NetworkManager. - -[Jump to the GUI section of this repo](#installation) - ## Documentation - **[User Guide](https://cachebag.github.io/nmrs/)** - Comprehensive guide with tutorials and examples @@ -109,53 +102,6 @@ async fn main() -> nmrs::Result<()> { ``` To follow and/or discuss the development of nmrs, you can join the [public Discord channel](https://discord.gg/Sk3VfrHrN4). - -#

nmrs-gui

- -[![Crates.io](https://img.shields.io/crates/v/nmrs-gui)](https://crates.io/crates/nmrs-gui) -[![Nix](https://github.com/cachebag/nmrs/actions/workflows/nix.yml/badge.svg)](https://github.com/cachebag/nmrs/actions/workflows/nix.yml) - -This repository also includes `nmrs-gui`, a Wayland-compatible NetworkManager frontend built with GTK4. - -

-image -

- -### Installation - -**Arch Linux (AUR)** - -```bash -yay -S nmrs -# or -paru -S nmrs -``` - -**Nix** - -```bash -nix-shell -p nmrs -``` - -### Configuration - -**Waybar Integration** - -```json -"network": { - "on-click": "nmrs" -} -``` - -**Tiling Window Managers** (Hyprland, Sway, i3) - -``` -windowrule = float 1, match:class org.nmrs.ui -``` - -**Custom Styling** - -Edit `~/.config/nmrs/style.css` to customize the interface. There are also pre-defined themes you can pick from in the interface itself.
Roadmap / Implementation Status @@ -217,8 +163,8 @@ Edit `~/.config/nmrs/style.css` to customize the interface. There are also pre-d - [ ] DNS Manager - [ ] PPP - [x] Secret Agent -- [x] VPN Connection (WireGuard + plugin VPNs) -- [ ] VPN Plugin +- [x] VPN Connection (WireGuard + OpenVPN) +- [x] General Plugin VPN (OpenConnect, strongSwan, PPTP, L2TP, etc.) - [ ] Wi-Fi P2P - [ ] WiMAX NSP @@ -230,7 +176,7 @@ Contributions are welcome. Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for ## Requirements -- **Rust**: 1.90.0+ for `nmrs`, 1.94.0+ for `nmrs-gui` +- **Rust**: 1.90.0+ - **NetworkManager**: Running and accessible via D-Bus - **Linux**: This library is Linux-specific diff --git a/SECURITY.md b/SECURITY.md index 244b5ed3..c0ec97cf 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ ## Supported Versions -We take security seriously and provide security updates for the latest version of nmrs and nmrs-gui alike. +We take security seriously and provide security updates for the latest version of nmrs. We strongly recommend keeping your nmrs dependencies up to date. ## Reporting a Vulnerability @@ -34,11 +34,6 @@ For nmrs, security vulnerabilities may include but are not limited to: - **Race conditions**: Timing vulnerabilities in connection state management that could lead to security issues - **Dependency vulnerabilities**: Security issues in upstream crates (zbus, tokio, etc.) that affect nmrs -For nmrs-gui specifically: -- **UI injection**: Malicious network names or data that could execute unintended actions in the GUI -- **File system access**: Unauthorized reading or writing of configuration files outside the intended scope - - ## Response Timeline We are committed to responding to security reports promptly: @@ -90,7 +85,7 @@ If you prefer to remain anonymous, please let us know in your report. ## Scope -This security policy covers both nmrs and nmrs-gui alike. +This security policy covers nmrs. ## Additional Resources @@ -98,4 +93,4 @@ This security policy covers both nmrs and nmrs-gui alike. - [Code of Conduct](https://www.rust-lang.org/policies/code-of-conduct) - [Rust Security Policy](https://www.rust-lang.org/policies/security) -Thank you for helping to keep nmrs and the Rust ecosystem secure! +Thank you for helping keep nmrs and the Rust ecosystem secure! diff --git a/default.nix b/default.nix deleted file mode 100644 index ec29eaab..00000000 --- a/default.nix +++ /dev/null @@ -1,4 +0,0 @@ -{ - pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-unstable.tar.gz") { }, -}: -pkgs.callPackage ./package.nix { } diff --git a/docker-compose.yml b/docker-compose.yml index 3078e12a..cfec3e7d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -38,7 +38,7 @@ services: sleep 1 && NetworkManager --no-daemon & sleep 3 && - cargo test --workspace --exclude nmrs-gui + cargo test --workspace " shell: diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index e0f82f1f..c631117d 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -47,14 +47,6 @@ - [Network Monitor Dashboard](./examples/network-monitor.md) - [Connection Manager](./examples/connection-manager.md) -# nmrs-gui - -- [GUI Overview](./gui/overview.md) -- [Installation](./gui/installation.md) -- [Configuration](./gui/configuration.md) -- [Themes](./gui/themes.md) -- [Waybar Integration](./gui/waybar.md) - # API Reference - [Core Types](./api/types.md) diff --git a/docs/src/advanced/async-runtimes.md b/docs/src/advanced/async-runtimes.md index cd2036a0..71f755ec 100644 --- a/docs/src/advanced/async-runtimes.md +++ b/docs/src/advanced/async-runtimes.md @@ -39,8 +39,6 @@ async fn main() -> nmrs::Result<()> { } ``` -This is what `nmrs-gui` uses internally. - ## async-std ```rust @@ -100,8 +98,6 @@ glib::MainContext::default().spawn_local(async { }); ``` -This is how `nmrs-gui` integrates nmrs into its GTK4 interface. - ## How It Works nmrs uses `zbus` for D-Bus communication, which itself uses an internal async runtime. When you call `NetworkManager::new().await`, zbus establishes a connection to the system D-Bus. This connection is runtime-agnostic — zbus handles the async I/O internally. diff --git a/docs/src/appendix/changelog.md b/docs/src/appendix/changelog.md index ed9eddca..df6550e7 100644 --- a/docs/src/appendix/changelog.md +++ b/docs/src/appendix/changelog.md @@ -1,9 +1,6 @@ # Changelog -Each crate maintains its own changelog. See the full changelogs on GitHub: - -- [**nmrs** (library) CHANGELOG](https://github.com/cachebag/nmrs/blob/master/nmrs/CHANGELOG.md) -- [**nmrs-gui** (GUI) CHANGELOG](https://github.com/cachebag/nmrs/blob/master/nmrs-gui/CHANGELOG.md) +See the full changelog on GitHub: [**nmrs** CHANGELOG](https://github.com/cachebag/nmrs/blob/master/nmrs/CHANGELOG.md) ## nmrs (Library) Highlights @@ -40,38 +37,3 @@ Each crate maintains its own changelog. See the full changelogs on GitHub: - VPN error handling improvements - Docker image for testing - Initial release with Wi-Fi and Ethernet support - -## nmrs-gui (Application) Highlights - -### 1.1.0 - -- Binary name fix for `.desktop` files and Nix - -### 0.5.0-beta - -- Ethernet support -- UI freeze fixes -- WPA-EAP certificate path support - -### 0.4.0-beta - -- Five themes: Catppuccin, Dracula, Gruvbox, Nord, Tokyo Night -- `--version` flag -- Crate rename to `nmrs-gui` - -### 0.3.0-beta - -- System default light/dark toggle - -### 0.2.0-beta - -- Default CSS file creation -- Nix dependencies -- Connection success feedback - -### 0.1.0-beta - -- Initial GTK4 GUI -- Basic and advanced network detail pages -- Refresh functionality -- Desktop entry and AUR support diff --git a/docs/src/appendix/faq.md b/docs/src/appendix/faq.md index fa2196a5..689e4eeb 100644 --- a/docs/src/appendix/faq.md +++ b/docs/src/appendix/faq.md @@ -70,24 +70,6 @@ Yes. Use `nm.import_ovpn("client.ovpn", Some("user"), Some("pass")).await?` to p Not directly. Extract the values from the config file and pass them to `WireGuardConfig::new()`. -## GUI - -### Is nmrs-gui required to use nmrs? - -No. `nmrs` is a library crate. `nmrs-gui` is a separate application built on top of it. You can use the library without the GUI. - -### Does nmrs-gui work on X11? - -Yes. While it's designed for Wayland, it works on X11 through GTK4's X11 backend. - -### Can I use nmrs-gui with a tiling window manager? - -Yes! nmrs-gui is designed to work well with tiling WMs like Hyprland, Sway, and i3. Add a floating window rule for `org.nmrs.ui`. See [Configuration](../gui/configuration.md). - -### How do I change the theme? - -Use the theme dropdown in the header bar, or edit `~/.config/nmrs/theme`. See [Themes](../gui/themes.md). - ## Troubleshooting ### Where can I get help? diff --git a/docs/src/appendix/troubleshooting.md b/docs/src/appendix/troubleshooting.md index f21fe048..616406ce 100644 --- a/docs/src/appendix/troubleshooting.md +++ b/docs/src/appendix/troubleshooting.md @@ -185,25 +185,6 @@ Monitor NetworkManager's own logs: journalctl -u NetworkManager -f ``` -## GUI Issues - -### nmrs-gui won't start - -- Check if another instance is running (single-instance lock) -- Verify GTK4 is installed: `pkg-config --modversion gtk4` -- Check for missing libraries: `ldd $(which nmrs) | grep "not found"` - -### Theme not loading - -- Check `~/.config/nmrs/theme` contains a valid theme name -- Delete the file to reset to defaults - -### Custom CSS not working - -- Verify the file exists: `~/.config/nmrs/style.css` -- User CSS loads last and should override themes -- Check CSS syntax in the file - ## Getting Help - **Discord:** [discord.gg/Sk3VfrHrN4](https://discord.gg/Sk3VfrHrN4) diff --git a/docs/src/development/contributing.md b/docs/src/development/contributing.md index b09aa54c..84bf8ce0 100644 --- a/docs/src/development/contributing.md +++ b/docs/src/development/contributing.md @@ -26,8 +26,6 @@ docker compose run test docker compose run shell ``` -It goes without saying that this image only works with nmrs. nmrs-gui requires GTK deps which in that case, you are better off just running a VM or learning how to use Linux on a machine instead. - If you decide to run the shell, ensure you run all commands from within the nmrs directory, not root. ```bash cargo test -p nmrs # run library tests @@ -35,10 +33,6 @@ cargo build -p nmrs # build the library cargo check # you get the point... ``` -### To develop nmrs-gui, you'll need: -- GTK4 and libadwaita development libraries -- A Wayland compositor - ## When your branch falls behind `master` If the respective branch for a PR goes out of sync, I prefer you _rebase_. diff --git a/docs/src/development/releases.md b/docs/src/development/releases.md index ca3c2604..889faf2c 100644 --- a/docs/src/development/releases.md +++ b/docs/src/development/releases.md @@ -1,6 +1,6 @@ # Release Process -This page documents the release process for nmrs and nmrs-gui. +This page documents the release process for nmrs. ## Versioning @@ -10,21 +10,9 @@ nmrs follows [Semantic Versioning](https://semver.org/): - **Minor** (0.X.0) — new features, backward-compatible - **Patch** (0.0.X) — bug fixes, backward-compatible -The library (`nmrs`) and GUI (`nmrs-gui`) are versioned independently. +## Changelog -## Current Versions - -| Crate | Version | -|-------|---------| -| `nmrs` | 2.2.0 | -| `nmrs-gui` | 1.1.0 | - -## Changelogs - -Each crate maintains its own changelog: - -- [`nmrs/CHANGELOG.md`](https://github.com/cachebag/nmrs/blob/master/nmrs/CHANGELOG.md) — Core library -- [`nmrs-gui/CHANGELOG.md`](https://github.com/cachebag/nmrs/blob/master/nmrs-gui/CHANGELOG.md) — GUI application +See [`nmrs/CHANGELOG.md`](https://github.com/cachebag/nmrs/blob/master/nmrs/CHANGELOG.md) for the full changelog. ## Release Checklist @@ -43,8 +31,6 @@ Each crate maintains its own changelog: | Channel | Package | |---------|---------| | [crates.io](https://crates.io/crates/nmrs) | `nmrs` library | -| [AUR](https://aur.archlinux.org/packages/nmrs) | `nmrs` (GUI binary) | -| [Nix](https://github.com/cachebag/nmrs/blob/master/flake.nix) | Nix flake | ## API Stability diff --git a/docs/src/getting-started/installation.md b/docs/src/getting-started/installation.md index 5309b0b4..f9083fd4 100644 --- a/docs/src/getting-started/installation.md +++ b/docs/src/getting-started/installation.md @@ -1,8 +1,6 @@ # Installation -This guide covers installation for both the **nmrs library** (for developers) and **nmrs-gui** (for end users). - -## nmrs Library +This guide covers installation for the **nmrs library**. ### Using Cargo @@ -44,73 +42,13 @@ async fn main() -> nmrs::Result<()> { } ``` -## nmrs-gui Application - -### Arch Linux (AUR) - -For Arch Linux users, install from the AUR: - -```bash -yay -S nmrs -# or -paru -S nmrs -``` - -### Nix/NixOS - -Install via Nix: - -```bash -nix-shell -p nmrs -``` - -Or add to your NixOS configuration: - -```nix -environment.systemPackages = with pkgs; [ - nmrs -]; -``` - -### From Source (GUI) - -Requirements: -- Rust 1.85.1 or later -- GTK4 development libraries -- libadwaita - -```bash -# Install dependencies (Arch Linux) -sudo pacman -S gtk4 libadwaita - -# Install dependencies (Ubuntu/Debian) -sudo apt install libgtk-4-dev libadwaita-1-dev - -# Install dependencies (Fedora) -sudo dnf install gtk4-devel libadwaita-devel - -# Build and install -git clone https://github.com/cachebag/nmrs.git -cd nmrs -cargo build --release -p nmrs-gui -sudo cp target/release/nmrs-gui /usr/local/bin/nmrs -``` - ## System Requirements -### For the Library (nmrs) - **Operating System**: Linux (any modern distribution) -- **Rust**: 1.78.0 or later +- **Rust**: 1.90.0 or later - **NetworkManager**: Version 1.0 or later, running and accessible via D-Bus - **D-Bus**: System bus must be available -### For the GUI (nmrs-gui) -All of the above, plus: -- **Rust**: 1.85.1 or later -- **GTK4**: Version 4.0 or later -- **libadwaita**: For modern GNOME styling -- **Wayland** or **X11**: Display server - ## Permissions nmrs requires permission to manage network connections. On most systems, this is handled by PolicyKit. Ensure your user is in the appropriate groups: @@ -140,6 +78,5 @@ sudo systemctl enable NetworkManager # Start on boot ## Next Steps -- **Library Users**: Continue to the [Quick Start](./quick-start.md) guide -- **GUI Users**: See the [GUI Configuration](../gui/configuration.md) guide -- **Having Issues?**: Check [Troubleshooting](../appendix/troubleshooting.md) +- Continue to the [Quick Start](./quick-start.md) guide +- Having issues? Check [Troubleshooting](../appendix/troubleshooting.md) diff --git a/docs/src/getting-started/requirements.md b/docs/src/getting-started/requirements.md index 685914b5..897a3106 100644 --- a/docs/src/getting-started/requirements.md +++ b/docs/src/getting-started/requirements.md @@ -55,20 +55,11 @@ dbus-send --system --print-reply \ ## Rust Requirements -### For nmrs Library - -- **Rust**: 1.78.0 or later -- **Edition**: 2021 +- **Rust**: 1.90.0 or later +- **Edition**: 2024 The library uses stable Rust features only. -### For nmrs-gui - -- **Rust**: 1.85.1 or later -- **Edition**: 2021 - -The GUI requires a newer Rust version due to GTK4 bindings. - ## Dependencies ### nmrs Library Dependencies @@ -83,30 +74,6 @@ The library depends on: All dependencies are automatically handled by Cargo. -### nmrs-gui Dependencies - -Additional system libraries required: - -**Arch Linux:** -```bash -sudo pacman -S gtk4 libadwaita -``` - -**Ubuntu/Debian:** -```bash -sudo apt install libgtk-4-dev libadwaita-1-dev build-essential -``` - -**Fedora:** -```bash -sudo dnf install gtk4-devel libadwaita-devel -``` - -**NixOS:** -```nix -# Handled automatically by the Nix package -``` - ## Permissions ### PolicyKit @@ -250,20 +217,12 @@ cargo update ## Version Compatibility -### nmrs Library - | nmrs Version | Minimum Rust | NetworkManager | Notable Features | |--------------|--------------|----------------|------------------| +| 3.0.0 | 1.90.0 | 1.0+ | Edition 2024 | | 2.0.0 | 1.78.0 | 1.0+ | Full API rewrite | | 1.x | 1.70.0 | 1.0+ | Initial release | -### nmrs-gui - -| GUI Version | Minimum Rust | GTK | Notable Features | -|-------------|--------------|-----|------------------| -| 1.1.0 | 1.85.1 | 4.0 | Themes support | -| 1.0.0 | 1.82.0 | 4.0 | Initial release | - ## Next Steps Once you have all requirements met: diff --git a/docs/src/gui/configuration.md b/docs/src/gui/configuration.md deleted file mode 100644 index af789a24..00000000 --- a/docs/src/gui/configuration.md +++ /dev/null @@ -1,145 +0,0 @@ -# GUI Configuration - -nmrs-gui stores its configuration in `~/.config/nmrs/`. - -## Configuration Files - -| File | Purpose | -|------|---------| -| `~/.config/nmrs/theme` | Selected theme name | -| `~/.config/nmrs/style.css` | Custom CSS overrides | - -## Theme Selection - -The active theme is stored in `~/.config/nmrs/theme` as a plain text string: - -- `gruvbox` — Gruvbox theme -- `nord` — Nord theme -- `dracula` — Dracula theme -- `catppuccin` — Catppuccin theme -- `tokyo` — Tokyo Night theme -- `light` — System default, light mode -- `dark` — System default, dark mode - -You can change the theme through the GUI using the dropdown in the header bar, or by editing the file directly. - -## Custom CSS - -nmrs-gui creates a default `style.css` at `~/.config/nmrs/style.css` on first launch. Edit this file to customize the interface. - -### CSS Loading Order - -1. **Bundled stylesheet** — base styles at application priority -2. **Selected theme** — theme overrides at user priority -3. **User stylesheet** — `~/.config/nmrs/style.css` at user priority (highest) - -Since user CSS loads last, it always takes precedence over themes. - -### CSS Variables - -Themes define CSS variables that you can override: - -```css -/* Override theme colors */ -:root { - --bg-primary: #1a1b26; - --bg-secondary: #24283b; - --bg-tertiary: #414868; - --text-primary: #c0caf5; - --text-secondary: #a9b1d6; - --text-tertiary: #565f89; - --border-color: #3b4261; - --border-color-hover: #545c7e; - --accent-color: #7aa2f7; - --success-color: #9ece6a; - --warning-color: #e0af68; - --error-color: #f7768e; -} -``` - -### Example Customizations - -**Larger font:** - -```css -window { - font-size: 16px; -} -``` - -**Custom accent color:** - -```css -:root { - --accent-color: #ff6b6b; -} -``` - -**Rounded corners:** - -```css -.network-row { - border-radius: 8px; -} -``` - -## Tiling Window Manager Configuration - -### Hyprland - -Add to `~/.config/hypr/hyprland.conf`: - -``` -windowrule = float, class:org.nmrs.ui -windowrule = size 400 600, class:org.nmrs.ui -windowrule = center, class:org.nmrs.ui -``` - -### Sway - -Add to `~/.config/sway/config`: - -``` -for_window [app_id="org.nmrs.ui"] floating enable -for_window [app_id="org.nmrs.ui"] resize set width 400 height 600 -``` - -### i3 - -Add to `~/.config/i3/config`: - -``` -for_window [class="org.nmrs.ui"] floating enable -for_window [class="org.nmrs.ui"] resize set 400 600 -``` - -## Signal Strength Indicators - -nmrs-gui uses CSS classes for signal strength: - -| Class | Signal Range | -|-------|-------------| -| `network-good` | Strong signal | -| `network-okay` | Medium signal | -| `network-poor` | Weak signal | - -You can style these in your CSS: - -```css -.network-good { color: var(--success-color); } -.network-okay { color: var(--warning-color); } -.network-poor { color: var(--error-color); } -``` - -## Application ID - -The GTK application ID is `org.nmrs.ui`. Use this for window rules and desktop integration. - -## Single Instance - -nmrs-gui uses a file lock to ensure only one instance runs at a time. If you try to launch a second instance, it will exit silently. - -## Next Steps - -- [Themes](./themes.md) — explore available themes -- [Waybar Integration](./waybar.md) — launch from your status bar diff --git a/docs/src/gui/installation.md b/docs/src/gui/installation.md deleted file mode 100644 index c4fb8366..00000000 --- a/docs/src/gui/installation.md +++ /dev/null @@ -1,113 +0,0 @@ -# GUI Installation - -## Arch Linux (AUR) - -The easiest way to install on Arch Linux: - -```bash -yay -S nmrs -# or -paru -S nmrs -``` - -## Nix/NixOS - -### Nix Shell - -```bash -nix-shell -p nmrs -``` - -### NixOS Configuration - -Add to your NixOS configuration: - -```nix -environment.systemPackages = with pkgs; [ - nmrs -]; -``` - -### Nix Flake - -```bash -nix run github:cachebag/nmrs -``` - -## From Source - -### Dependencies - -Install build dependencies for your distribution: - -**Arch Linux:** - -```bash -sudo pacman -S gtk4 libadwaita rust -``` - -**Ubuntu/Debian:** - -```bash -sudo apt install libgtk-4-dev libadwaita-1-dev build-essential -``` - -**Fedora:** - -```bash -sudo dnf install gtk4-devel libadwaita-devel rust cargo -``` - -### Build - -```bash -git clone https://github.com/cachebag/nmrs.git -cd nmrs -cargo build --release -p nmrs-gui -``` - -### Install - -```bash -sudo cp target/release/nmrs-gui /usr/local/bin/nmrs -``` - -## Verification - -After installation, launch nmrs-gui: - -```bash -nmrs -``` - -The window should appear showing available Wi-Fi networks. - -## System Requirements - -- **Rust:** 1.94.0+ (for building from source) -- **GTK4:** 4.0+ -- **NetworkManager:** running and accessible via D-Bus -- **Display:** Wayland or X11 -- **Linux:** any modern distribution - -## Desktop Entry - -If you installed from source, you may want to create a desktop entry: - -```ini -[Desktop Entry] -Name=nmrs -Comment=NetworkManager GUI -Exec=nmrs -Icon=network-wireless -Type=Application -Categories=Network;Settings; -``` - -Save as `~/.local/share/applications/nmrs.desktop`. - -## Next Steps - -- [Configuration](./configuration.md) — customize the GUI -- [Themes](./themes.md) — change the visual theme -- [Waybar Integration](./waybar.md) — launch from your status bar diff --git a/docs/src/gui/overview.md b/docs/src/gui/overview.md deleted file mode 100644 index c0bd14c8..00000000 --- a/docs/src/gui/overview.md +++ /dev/null @@ -1,67 +0,0 @@ -# GUI Overview - -**nmrs-gui** is a Wayland-compatible GTK4 graphical interface for NetworkManager. It provides a modern, lightweight network management UI that integrates well with tiling window managers. - -## Features - -- **Wi-Fi Management** — scan, connect, disconnect, and view network details -- **Ethernet Support** — view and manage wired connections -- **Enterprise Wi-Fi** — WPA-EAP/802.1X with password, username, and certificate support -- **Multiple Themes** — Gruvbox, Nord, Dracula, Catppuccin, Tokyo Night (light and dark) -- **Custom Styling** — CSS-based customization via `~/.config/nmrs/style.css` -- **Real-Time Updates** — D-Bus signal monitoring for live network and device state -- **Single Instance** — file lock ensures only one instance runs at a time -- **Wayland Native** — first-class Wayland support, also works on X11 - -## Architecture - -``` -nmrs-gui -├── Main entry (single-instance lock, GTK Application) -├── CSS loading (bundled → theme → user overrides) -├── Header -│ ├── Wi-Fi label and status -│ ├── Theme selector dropdown -│ ├── Refresh button -│ ├── Light/Dark toggle -│ └── Wi-Fi enable/disable switch -├── Network list view -│ ├── Grouped by SSID + band -│ ├── Signal strength indicators -│ └── Double-click to connect, arrow for details -├── Network details page -│ ├── SSID, status, signal, BSSID -│ ├── Frequency, channel, mode, speed -│ ├── Security type -│ └── Forget button -├── Wired device list view -│ └── Double-click to connect, arrow for details -├── Wired details page -│ ├── Interface, state, type, MAC, driver -│ └── Managed status -├── Connect modal -│ ├── Password field -│ ├── EAP username (for enterprise) -│ ├── CA certificate path -│ └── System CA checkbox -└── D-Bus monitors - ├── Network changes (debounced refresh) - └── Device changes (debounced refresh) -``` - -## Screenshots - -nmrs-gui uses a clean, minimal interface that adapts to your chosen theme and color scheme. - -## Requirements - -- GTK4 4.0+ -- Linux with NetworkManager running -- Wayland or X11 display server - -## Next Steps - -- [Installation](./installation.md) — install nmrs-gui -- [Configuration](./configuration.md) — customize behavior -- [Themes](./themes.md) — choose and customize themes -- [Waybar Integration](./waybar.md) — launch from your status bar diff --git a/docs/src/gui/themes.md b/docs/src/gui/themes.md deleted file mode 100644 index c0f553ad..00000000 --- a/docs/src/gui/themes.md +++ /dev/null @@ -1,83 +0,0 @@ -# Themes - -nmrs-gui ships with five themes, each with light and dark variants. Themes are selected from the dropdown in the header bar. - -## Available Themes - -### Gruvbox - -A retro groove color scheme with warm colors. Inspired by the popular Vim color scheme. - -### Nord - -An arctic, north-bluish color palette. Clean and minimal with cool blue tones. - -### Dracula - -A dark theme with vibrant colors. Popular across many editors and terminal emulators. - -### Catppuccin - -A soothing pastel theme with four flavors. nmrs uses the Mocha (dark) and Latte (light) variants. - -### Tokyo Night - -Inspired by the lights of Tokyo at night. A clean dark theme with vibrant accents. - -## Light/Dark Mode - -Each theme has both light and dark variants. Toggle between them using the light/dark button in the header bar. The toggle saves your preference to `~/.config/nmrs/theme`. - -You can also use the system default by setting `light` or `dark` in the theme file, which uses the GTK4 default appearance. - -## Theme CSS Variables - -All themes override the same set of CSS variables: - -| Variable | Purpose | -|----------|---------| -| `--bg-primary` | Main background color | -| `--bg-secondary` | Secondary/card background | -| `--bg-tertiary` | Tertiary/hover background | -| `--text-primary` | Main text color | -| `--text-secondary` | Secondary text color | -| `--text-tertiary` | Muted text color | -| `--border-color` | Default border color | -| `--border-color-hover` | Hover state border color | -| `--accent-color` | Primary accent (links, active items) | -| `--success-color` | Success indicators (connected) | -| `--warning-color` | Warning indicators (weak signal) | -| `--error-color` | Error indicators (disconnected) | - -## Creating a Custom Theme - -You can create your own theme by overriding CSS variables in `~/.config/nmrs/style.css`: - -```css -:root { - --bg-primary: #0d1117; - --bg-secondary: #161b22; - --bg-tertiary: #21262d; - --text-primary: #e6edf3; - --text-secondary: #8b949e; - --text-tertiary: #484f58; - --border-color: #30363d; - --border-color-hover: #484f58; - --accent-color: #58a6ff; - --success-color: #3fb950; - --warning-color: #d29922; - --error-color: #f85149; -} -``` - -Since user CSS loads after the selected theme, your overrides will always take effect. - -## Theme Storage - -- Theme selection: `~/.config/nmrs/theme` (plain text, e.g., `nord`) -- User CSS: `~/.config/nmrs/style.css` - -## Next Steps - -- [Configuration](./configuration.md) — full configuration reference -- [Waybar Integration](./waybar.md) — launch from your status bar diff --git a/docs/src/gui/waybar.md b/docs/src/gui/waybar.md deleted file mode 100644 index 819535d3..00000000 --- a/docs/src/gui/waybar.md +++ /dev/null @@ -1,67 +0,0 @@ -# Waybar Integration - -[Waybar](https://github.com/Alexays/Waybar) is a popular status bar for Wayland compositors. You can configure it to launch nmrs-gui when clicking the network module. - -## Basic Configuration - -Add to your Waybar config (`~/.config/waybar/config`): - -```json -"network": { - "on-click": "nmrs" -} -``` - -This launches nmrs-gui when you click the network module in Waybar. - -## Full Network Module Example - -```json -"network": { - "format-wifi": "{icon} {essid}", - "format-ethernet": "󰈀 {ifname}", - "format-disconnected": "󰤭 Disconnected", - "format-icons": ["󰤯", "󰤟", "󰤢", "󰤥", "󰤨"], - "tooltip-format-wifi": "{essid} ({signalStrength}%)\n{ipaddr}/{cidr}", - "tooltip-format-ethernet": "{ifname}\n{ipaddr}/{cidr}", - "on-click": "nmrs", - "interval": 5 -} -``` - -## Keybinding Integration - -You can also bind nmrs-gui to a keyboard shortcut. - -### Hyprland - -``` -bind = $mainMod, N, exec, nmrs -windowrule = float, class:org.nmrs.ui -windowrule = size 400 600, class:org.nmrs.ui -windowrule = center, class:org.nmrs.ui -``` - -### Sway - -``` -bindsym $mod+n exec nmrs -for_window [app_id="org.nmrs.ui"] floating enable -for_window [app_id="org.nmrs.ui"] resize set width 400 height 600 -``` - -### i3 - -``` -bindsym $mod+n exec nmrs -for_window [class="org.nmrs.ui"] floating enable -``` - -## Single Instance Behavior - -nmrs-gui enforces single-instance mode. If you click the Waybar module while nmrs-gui is already open, the second instance will exit immediately and the existing window remains. This means you can safely bind the launch command to a click handler without worrying about duplicate windows. - -## Next Steps - -- [Configuration](./configuration.md) — customize the interface -- [Themes](./themes.md) — change the visual theme diff --git a/docs/src/introduction.md b/docs/src/introduction.md index 500393b6..68bed3e3 100644 --- a/docs/src/introduction.md +++ b/docs/src/introduction.md @@ -14,32 +14,14 @@ Welcome to the **nmrs** documentation! This guide will help you understand and u - **Type Safety** - Comprehensive error handling with specific failure reasons - **Async/Await** - Built on modern async Rust with runtime flexibility -## Project Structure - -The nmrs project consists of two main components: - -### nmrs (Library) -The core Rust library providing NetworkManager bindings and network management capabilities. This is what you'll use if you're building applications that need to manage network connections programmatically. - -### nmrs-gui (Application) -A beautiful, Wayland-compatible GTK4 graphical interface for NetworkManager. Perfect for desktop users who want a modern network management GUI. - ## Why nmrs? -### For Developers - **Safe Abstractions** - No unsafe code, leveraging Rust's type system - **Async-First** - Built for modern async Rust applications - **Signal-Based** - Efficient D-Bus signal monitoring instead of polling - **Well-Documented** - Comprehensive docs with examples for every feature - **Runtime Agnostic** - Works with Tokio, async-std, smol, and more -### For Users (nmrs-gui) -- **Modern UI** - Clean GTK4 interface with multiple themes -- **Wayland Native** - First-class Wayland support -- **Lightweight** - Fast and efficient -- **Customizable** - CSS-based theming system -- **DE Integration** - Works great with tiling WMs (Hyprland, Sway, i3) - ## Quick Example Here's a taste of what nmrs can do: diff --git a/flake.lock b/flake.lock deleted file mode 100644 index 985c2c32..00000000 --- a/flake.lock +++ /dev/null @@ -1,158 +0,0 @@ -{ - "nodes": { - "fenix": { - "inputs": { - "nixpkgs": [ - "naersk", - "nixpkgs" - ], - "rust-analyzer-src": "rust-analyzer-src" - }, - "locked": { - "lastModified": 1752475459, - "narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=", - "owner": "nix-community", - "repo": "fenix", - "rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "fenix", - "type": "github" - } - }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "naersk": { - "inputs": { - "fenix": "fenix", - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1769799857, - "narHash": "sha256-88IFXZ7Sa1vxbz5pty0Io5qEaMQMMUPMonLa3Ls/ss4=", - "owner": "nix-community", - "repo": "naersk", - "rev": "9d4ed44d8b8cecdceb1d6fd76e74123d90ae6339", - "type": "github" - }, - "original": { - "owner": "nix-community", - "ref": "master", - "repo": "naersk", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1752077645, - "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1773821835, - "narHash": "sha256-TJ3lSQtW0E2JrznGVm8hOQGVpXjJyXY2guAxku2O9A4=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "b40629efe5d6ec48dd1efba650c797ddbd39ace0", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "naersk": "naersk", - "nixpkgs": "nixpkgs_2", - "rust-overlay": "rust-overlay" - } - }, - "rust-analyzer-src": { - "flake": false, - "locked": { - "lastModified": 1752428706, - "narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=", - "owner": "rust-lang", - "repo": "rust-analyzer", - "rev": "591e3b7624be97e4443ea7b5542c191311aa141d", - "type": "github" - }, - "original": { - "owner": "rust-lang", - "ref": "nightly", - "repo": "rust-analyzer", - "type": "github" - } - }, - "rust-overlay": { - "inputs": { - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1773975983, - "narHash": "sha256-zrRVwdfhDdohANqEhzY/ydeza6EXEi8AG6cyMRNYT9Q=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "cc80954a95f6f356c303ed9f08d0b63ca86216ac", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index f300a6e9..00000000 --- a/flake.nix +++ /dev/null @@ -1,87 +0,0 @@ -{ - description = "nmrs development flake"; - - inputs = { - nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; - naersk.url = "github:nix-community/naersk/master"; - flake-utils.url = "github:numtide/flake-utils"; - - rust-overlay = { - url = "github:oxalica/rust-overlay"; - inputs.nixpkgs.follows = "nixpkgs"; - }; - }; - - outputs = { - self, - nixpkgs, - flake-utils, - naersk, - rust-overlay, - }: - flake-utils.lib.eachDefaultSystem ( - system: let - pkgs = import nixpkgs { - inherit system; - overlays = [(import rust-overlay)]; - }; - toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; - - naersk-package = pkgs.callPackage naersk { - cargo = toolchain; - rustc = toolchain; - clippy = toolchain; - }; - in { - devShell = with pkgs; - mkShell { - buildInputs = [ - cargo - cargo-info - rustc - rustfmt - clippy - rust-analyzer - just - - eza - fd - fzf - bat - - pkg-config - libxkbcommon - glib - gobject-introspection - gtk4 - libadwaita - ]; - RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; - - shellHook = '' - alias ls=eza - alias find=fd - ''; - }; - - packages.default = naersk-package.buildPackage { - name = "nmrs"; - version = self.shortRev or self.dirtyShortRev; - src = ./.; - - buildInputs = with pkgs; [ - pkg-config - wrapGAppsHook4 - libxkbcommon - glib - ]; - - postInstall = '' - install -D nmrs.desktop -t $out/share/applications - ''; - - meta.mainProgram = "nmrs-gui"; - }; - } - ); -} diff --git a/nmrs-gui/CHANGELOG.md b/nmrs-gui/CHANGELOG.md deleted file mode 100644 index 36e9a830..00000000 --- a/nmrs-gui/CHANGELOG.md +++ /dev/null @@ -1,105 +0,0 @@ -# Changelog - -All notable changes to the `nmrs-gui` crate will be documented in this file. - -## [Unreleased] -### Changed -- Use `Arc` for monitor callbacks to satisfy `Send` bound ([#359](https://github.com/cachebag/nmrs/pull/359)) - -## [1.5.1] - 2026-04-10 -### Fixed -- Set a location for our `.lock` file ([#310](https://github.com/cachebag/nmrs/pull/310)) - -## [1.5.0] - 2026-03-19 -### Fixed -- Custom `~/.config/nmrs/style.css` is now applied after the saved theme, ensuring it always takes precedence over any predefined theme ([#274](https://github.com/cachebag/nmrs/pull/274)) -- Fixed header status label inflating natural width with long status strings ([#279](https://github.com/cachebag/nmrs/pull/279)) - -## [1.1.0] - 2025-12-19 - -### Fixed -- Corrected binary name for `.desktop` file + `postInstall` hook for Nix flake ([#146](https://github.com/cachebag/nmrs/pull/146)) - -## [0.5.0-beta] - 2025-12-15 - -### Added -- Full support for Ethernet devices ([#88](https://github.com/cachebag/nmrs/issues/88)) - -### Fixed -- Fixed UI freeze when connecting/forgetting networks -- Supply option to provide cert paths for WPA-EAP connections ([#56](https://github.com/cachebag/nmrs/issues/56)) - -## [0.4.0-beta] - 2025-12-11 - -### Breaking Changes -- Renamed crate from `nmrs-ui` to `nmrs-gui` - -### Added -- Pre-defined themes (Catppuccin, Dracula, Gruvbox, Nord, Tokyo) ([#106](https://github.com/cachebag/nmrs/issues/106)) -- `--version` flag with build hash extraction ([#108](https://github.com/cachebag/nmrs/issues/108)) - -### Fixed -- Re-aligned refresh button ([#111](https://github.com/cachebag/nmrs/issues/111)) -- Show connection status when connecting with saved credentials ([#61](https://github.com/cachebag/nmrs/issues/61)) - -## [0.3.0-beta] - 2025-12-08 - -### Fixed -- Fixed UI not freezing on connections ([#101](https://github.com/cachebag/nmrs/pull/101)) -- Fixed separate `ScrolledWindow` for each stack child ([#103](https://github.com/cachebag/nmrs/pull/103)) -- Dropped deps that aren't needed for now ([#104](https://github.com/cachebag/nmrs/pull/104)) - -### Added -- Expose system default theme toggle (light/dark) ([#102](https://github.com/cachebag/nmrs/pull/102)) - -## [0.2.0-beta] - 2025-12-03 - -### Added -- Write `.css` file for user by default ([#58](https://github.com/cachebag/nmrs/pull/58)) -- Config: Nix installation deps ([#60](https://github.com/cachebag/nmrs/pull/60)) -- Visual indication on successful connection ([#64](https://github.com/cachebag/nmrs/pull/64)) -- Refactored `network.rs` and `network_page.rs` to follow best practices ([#66](https://github.com/cachebag/nmrs/pull/90)) - -## [0.1.1-beta] - 2025-11-21 - -### Added -- Exposed layout to allow users to place a `style.css` in `~/.config/nmrs/` for custom styling ([#55](https://github.com/cachebag/nmrs/pull/55)) -- Styling section with example CSS for customization in README - -## [0.1.0-beta] - 2025-11-20 - -### Added -- Initial BETA release of nmrs GUI -- GTK4-based user interface -- Basic and advanced network information pages -- Persistent saved-connection state tracking -- Refresh button for manual network scanning ([#51](https://github.com/cachebag/nmrs/pull/51)) -- `.desktop` file for launcher integration -- AUR package support (via `yay` and `paru`) -- Initial smoke tests ([#48](https://github.com/cachebag/nmrs/pull/48)) -- Issue templates for bug reports and feature requests -- Contributing guidelines (CONTRIBUTING.md) -- Installation support for Arch Linux, Debian/Ubuntu, and from source - -### Fixed -- UI enhancements for connected devices -- Password authentication failure handling -- `refresh_networks` helper for proper scanning ([#50](https://github.com/cachebag/nmrs/pull/50)) -- Adjusted polling to accurately display networks on launch -- `forget_btn` thread locks -- Context correction for network settings - -### Documentation -- Initial README with installation and usage instructions - -[1.5.0]: https://github.com/cachebag/nmrs/compare/gui-v1.1.0...gui-v1.5.0 -[1.5.1]: https://github.com/cachebag/nmrs/compare/gui-v1.5.0...gui-v1.5.1 -[Unreleased]: https://github.com/cachebag/nmrs/compare/gui-v1.5.1...HEAD -[1.1.0]: https://github.com/cachebag/nmrs/compare/gui-v0.5.0-beta...gui-v1.1.0 -[0.5.0-beta]: https://github.com/cachebag/nmrs/compare/v0.4.0-beta...gui-v0.5.0-beta -[0.4.0-beta]: https://github.com/cachebag/nmrs/compare/v0.3.0-beta...v0.4.0-beta -[0.3.0-beta]: https://github.com/cachebag/nmrs/compare/v0.2.0-beta...v0.3.0-beta -[0.2.0-beta]: https://github.com/cachebag/nmrs/compare/v0.1.1-beta...v0.2.0-beta -[0.1.1-beta]: https://github.com/cachebag/nmrs/compare/v0.1.0-beta...v0.1.1-beta -[0.1.0-beta]: https://github.com/cachebag/nmrs/releases/tag/v0.1.0-beta - diff --git a/nmrs-gui/Cargo.toml b/nmrs-gui/Cargo.toml deleted file mode 100644 index 4881c95e..00000000 --- a/nmrs-gui/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "nmrs-gui" -version = "1.5.1" -authors = ["Akrm Al-Hakimi "] -edition.workspace = true -rust-version = "1.90.0" -description = "GTK4 GUI for managing NetworkManager connections" -license.workspace = true -repository.workspace = true -keywords = ["networkmanager", "gui", "gtk", "linux"] -categories = ["gui"] -publish = true - -[dependencies] -nmrs.workspace = true -gtk.workspace = true -glib.workspace = true -tokio.workspace = true -log.workspace = true -dirs.workspace = true -fs2.workspace = true -anyhow.workspace = true -clap.workspace = true -tokio-util.workspace = true diff --git a/nmrs-gui/build.rs b/nmrs-gui/build.rs deleted file mode 100644 index f67398d3..00000000 --- a/nmrs-gui/build.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::process::Command; - -fn main() { - let output = Command::new("git") - .args(["rev-parse", "--short", "HEAD"]) - .output(); - - let hash = match output { - Ok(output) if output.status.success() => { - String::from_utf8_lossy(&output.stdout).trim().to_string() - } - _ => { - println!("cargo:warning=Unable to determine git hash, using 'unknown'"); - String::from("unknown") - } - }; - - println!("cargo:rustc-env=GIT_HASH={}", hash); -} diff --git a/nmrs-gui/src/file_lock.rs b/nmrs-gui/src/file_lock.rs deleted file mode 100644 index 885916ff..00000000 --- a/nmrs-gui/src/file_lock.rs +++ /dev/null @@ -1,59 +0,0 @@ -use fs2::FileExt; -use std::fs; -use std::fs::File; - -pub fn acquire_app_lock() -> Result { - // Prefer XDG_RUNTIME_DIR for ephemeral lock files (should be /run/usr/). Fall back to local data dir. - let mut lock_dir = dirs::runtime_dir() - .or_else(dirs::data_local_dir) - .unwrap_or(std::env::temp_dir()); - - lock_dir.push("nmrs"); - - fs::create_dir_all(&lock_dir).map_err(|e| format!("Failed to create lock directory: {e}"))?; - - let lock_path = lock_dir.join("app.lock"); - - let file = File::create(&lock_path).map_err(|e| format!("Failed to create lock file: {e}"))?; - - // Exclusive lock; fails if another instance holds it - file.try_lock_exclusive() - .map_err(|_| "Another instance is already running".to_string())?; - - Ok(file) -} - -// Used so each test can be run independently. -// Without that, with tests being run in parallel, the second test can cause the first one to fail. -#[cfg(test)] -static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); - -#[test] -fn test_lock_file_is_created() { - // Test if the lock file is properly created. - let _guard = TEST_LOCK.lock().unwrap(); - let file = acquire_app_lock(); - assert!(file.is_ok(), "Failed to acquire lock: {:?}", file.err()); - - let lock_path = dirs::runtime_dir() - .or_else(dirs::data_local_dir) - .unwrap_or(std::env::temp_dir()) - .join("nmrs") - .join("app.lock"); - - assert!( - lock_path.exists(), - "Lock file was not created at {:?}", - lock_path - ); -} - -#[test] -fn test_second_instance_is_rejected() { - // Test if acquire_app_lock fails as it should when called twice. - let _guard = TEST_LOCK.lock().unwrap(); - let _first = acquire_app_lock().expect("First lock should succeed"); - let second = acquire_app_lock(); - assert!(second.is_err(), "Second instance should have been rejected"); - assert_eq!(second.unwrap_err(), "Another instance is already running"); -} diff --git a/nmrs-gui/src/lib.rs b/nmrs-gui/src/lib.rs deleted file mode 100644 index d9a87b88..00000000 --- a/nmrs-gui/src/lib.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub mod file_lock; -pub mod objects; -pub mod style; -pub mod theme_config; -pub mod ui; - -use clap::{ArgAction, Parser}; -use gtk::Application; -use gtk::prelude::*; - -use crate::file_lock::acquire_app_lock; -use crate::ui::build_ui; - -#[derive(Parser, Debug)] -#[command(name = "nmrs")] -#[command(disable_version_flag = true)] -#[command(version)] -struct Args { - #[arg(short = 'V', long = "version", action = ArgAction::SetTrue)] - version: bool, -} - -pub fn run() -> anyhow::Result<()> { - let args = Args::parse(); - - if let Args { version: true } = args { - println!( - "nmrs {}-beta ({})", - env!("CARGO_PKG_VERSION"), - env!("GIT_HASH") - ); - return Ok(()); - } - - let app = Application::builder().application_id("org.nmrs.ui").build(); - - let _lock = match acquire_app_lock() { - Ok(lock) => lock, - Err(e) => { - eprintln!("Failed to start: {e}"); - std::process::exit(1); - } - }; - - app.connect_activate(|app| { - crate::style::init(include_str!("style.css")); - build_ui(app); - }); - - app.run(); - Ok(()) -} diff --git a/nmrs-gui/src/main.rs b/nmrs-gui/src/main.rs deleted file mode 100644 index e53349e4..00000000 --- a/nmrs-gui/src/main.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[tokio::main(flavor = "current_thread")] -async fn main() { - nmrs_gui::run().ok(); -} diff --git a/nmrs-gui/src/objects/mod.rs b/nmrs-gui/src/objects/mod.rs deleted file mode 100644 index 9757ba5f..00000000 --- a/nmrs-gui/src/objects/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod theme; diff --git a/nmrs-gui/src/objects/theme.rs b/nmrs-gui/src/objects/theme.rs deleted file mode 100644 index a3be5740..00000000 --- a/nmrs-gui/src/objects/theme.rs +++ /dev/null @@ -1,38 +0,0 @@ -use gtk::glib; -use gtk::subclass::prelude::ObjectSubclassIsExt; - -mod imp { - use super::*; - use crate::objects::theme::glib::subclass::prelude::ObjectImpl; - use crate::objects::theme::glib::subclass::prelude::ObjectSubclass; - use std::cell::RefCell; - - #[derive(Default)] - pub struct Theme { - pub label: RefCell, - } - - #[glib::object_subclass] - impl ObjectSubclass for Theme { - const NAME: &'static str = "ThemeObject"; - type Type = super::Theme; - } - - impl ObjectImpl for Theme {} -} - -glib::wrapper! { - pub struct Theme(ObjectSubclass); -} - -impl Theme { - pub fn new(label: &str) -> Self { - let obj: Self = glib::Object::new(); - obj.imp().label.replace(label.to_string()); - obj - } - - pub fn label(&self) -> String { - self.imp().label.borrow().clone() - } -} diff --git a/nmrs-gui/src/style.css b/nmrs-gui/src/style.css deleted file mode 100644 index 10d3eaa0..00000000 --- a/nmrs-gui/src/style.css +++ /dev/null @@ -1,314 +0,0 @@ -/* Themes */ -window.light-theme { - --bg-primary: #ffffff; - --bg-secondary: #f5f5f5; - --bg-tertiary: #e5e5e5; - --text-primary: #1a1a1a; - --text-secondary: #4a4a4a; - --text-tertiary: #6a6a6a; - --border-color: #d0d0d0; - --border-color-hover: #b0b0b0; - --accent-color: #2563eb; - --success-color: #16a34a; - --warning-color: #d97706; - --error-color: #dc2626; -} -window, -window.dark-theme { - --bg-primary: #121212; - --bg-secondary: #1e1e1e; - --bg-tertiary: #2a2a2a; - --text-primary: #e0e0e0; - --text-secondary: #bbbbbb; - --text-tertiary: #9ca3af; - --border-color: #2a2a2a; - --border-color-hover: #3a3a3a; - --accent-color: #2563eb; - --success-color: #22c55e; - --warning-color: #f59e0b; - --error-color: #ef4444; -} - -/* Window */ -window { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -/* Header */ -headerbar { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 1px solid var(--border-color); -} - -/* Switch */ -switch { - background-color: var(--bg-tertiary); -} -switch:checked { - background-color: var(--accent-color); -} - -/* Wi-Fi label */ -.wifi-label { - font-weight: 600; - color: var(--text-primary); -} - -/* List */ -list { - background: var(--bg-primary); - border: none; -} -list > row { - background: transparent; - border: none; - padding: 0; -} -list > row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} - -/* Entry fields */ -.pw-entry { - background-color: transparent; - color: var(--text-primary); - border-color: transparent; -} - -/* Network selection */ -.network-selection { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} -.network-selection:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} -.network-selection.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} -.network-selection.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} -.network-selection label { - font-size: 14px; - color: var(--text-primary); -} -.connected-label { - font-size: 12px; - color: var(--success-color); - font-style: italic; - margin-left: 8px; - opacity: 0.9; -} - -/* Network quality labels */ -label.network-good { color: var(--success-color); } -label.network-okay { color: var(--warning-color); } -label.network-poor { color: var(--error-color); } - -/* Network page */ -.network-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -/* Back button */ -.back-button { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.back-button:hover { color: var(--text-primary); } - -/* Network details */ -.network-icon { - color: var(--text-primary); -} -.network-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} -.network-arrow { - color: var(--text-primary); -} -.network-info { - margin-top: 14px; - padding-left: 6px; -} -.info-row { padding: 2px 0; } -.info-value { - color: var(--text-primary); - font-size: 14px; - font-weight: 500; -} - -/* Section headers */ -.section-header { - font-weight: 600; - font-size: 13px; - text-transform: uppercase; - border-bottom: 1px solid var(--border-color); - padding-bottom: 4px; - margin-bottom: 6px; - color: var(--text-secondary); - letter-spacing: 0.5px; -} - -/* Info keys and values */ -.basic-key, -.info-label { - font-weight: 600; - font-size: 13px; - color: var(--text-tertiary); - margin-bottom: 2px; - text-decoration: underline; -} -.basic-value, -.info-value { - font-size: 14px; - color: var(--text-primary); - font-weight: 500; - margin-bottom: 8px; -} - -/* Divider */ -.divider { - margin-top: 10px; - margin-bottom: 10px; - opacity: 0.25; - border-bottom: 1px solid var(--border-color); -} - -/* Wi-Fi secure/open */ -.wifi-secure { color: var(--text-primary); } -.wifi-open { color: var(--text-primary); } - -/* Loading spinner */ -.loading-spinner { - margin-top: 12px; - margin-bottom: 12px; - opacity: 0.6; -} - -/* Forget button */ -.forget-button { - font-size: 0.85em; - opacity: 0.7; - padding: 2px 6px; - border-radius: 6px; -} -.forget-button:hover { opacity: 1; } - -/* Refresh button */ -.refresh-btn { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.refresh-btn:hover { color: var(--text-primary); } - -/* Theme toggle */ -.theme-toggle-btn { - background: none; - border: none; - color: var(--text-tertiary); - padding: 4px 8px; - opacity: 0.7; - transition: opacity 150ms ease, color 150ms ease; -} -.theme-toggle-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -/* Dropdown */ -.dropdown { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button label { - color: var(--text-primary); -} -.dropdown button:hover { - background: var(--bg-tertiary); - border-radius: 0; -} - -/* Popover */ -popover.background, -popover contents { - background: var(--bg-secondary); - border-radius: 0; -} -popover row { - background: var(--bg-secondary); - color: var(--text-primary); - border-radius: 0; -} -popover row:hover { - background: var(--bg-tertiary); -} -popover row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} -popover row label { - color: var(--text-primary); -} - -/* Wired device styles */ -.wired-section-header, -.wireless-section-header { - font-weight: 700; - font-size: 14px; - text-transform: uppercase; - color: var(--text-secondary); - letter-spacing: 0.8px; - opacity: 0.8; -} - -.wired-devices-list { - background: var(--bg-primary); - margin-bottom: 8px; -} - -.wired-icon { - color: var(--success-color); - opacity: 0.8; - margin-left: 6px; -} - -.device-separator { - background: var(--border-color); - opacity: 0.5; -} - diff --git a/nmrs-gui/src/style.rs b/nmrs-gui/src/style.rs deleted file mode 100644 index d354e382..00000000 --- a/nmrs-gui/src/style.rs +++ /dev/null @@ -1,96 +0,0 @@ -use gtk::gdk::Display; -use gtk::gio::File; -use gtk::{CssProvider, STYLE_PROVIDER_PRIORITY_USER}; -use std::cell::RefCell; -use std::fs; -use std::io::Write; -use std::path::PathBuf; - -thread_local! { - static PROVIDER: RefCell = RefCell::new(CssProvider::new()); -} - -fn config_dir() -> PathBuf { - dirs::config_dir().unwrap_or_default().join("nmrs") -} - -fn style_path() -> PathBuf { - config_dir().join("style.css") -} - -fn custom_backup_path() -> PathBuf { - config_dir().join("style.custom.css") -} - -/// Register a single persistent CSS provider and load `~/.config/nmrs/style.css`. -/// If it doesn't exist, seeds it with the bundled default. -pub fn init(default_css: &str) { - let display = Display::default().expect("No display found"); - - PROVIDER.with(|p| { - gtk::style_context_add_provider_for_display( - &display, - &*p.borrow(), - STYLE_PROVIDER_PRIORITY_USER, - ); - }); - - ensure_dir(); - if !style_path().exists() { - write_file(&style_path(), default_css); - } - reload(); -} - -/// Switch to a named theme: if the user was on "Custom", back up their -/// `style.css` to `style.custom.css` first. Then overwrite `style.css` -/// with the theme content and reload. -pub fn switch_to_theme(css: &str) { - let current = crate::theme_config::load_theme().unwrap_or_default(); - if current == "custom" { - backup_custom(); - } - write_file(&style_path(), css); - reload(); -} - -/// Switch to "Custom": restore `style.custom.css` back to `style.css` -/// if a backup exists, then reload. -pub fn switch_to_custom() { - let backup = custom_backup_path(); - if backup.exists() - && let Ok(contents) = fs::read_to_string(&backup) - { - write_file(&style_path(), &contents); - } - reload(); -} - -/// Reload `~/.config/nmrs/style.css` into the persistent provider. -pub fn reload() { - let path = style_path(); - if path.exists() { - let file = File::for_path(&path); - PROVIDER.with(|p| { - p.borrow().load_from_file(&file); - }); - } -} - -fn backup_custom() { - let src = style_path(); - if src.exists() { - fs::copy(&src, custom_backup_path()).ok(); - } -} - -fn write_file(path: &PathBuf, contents: &str) { - ensure_dir(); - let mut f = fs::File::create(path).expect("Failed to write CSS file"); - f.write_all(contents.as_bytes()) - .expect("Failed to write CSS file"); -} - -fn ensure_dir() { - fs::create_dir_all(config_dir()).ok(); -} diff --git a/nmrs-gui/src/theme_config.rs b/nmrs-gui/src/theme_config.rs deleted file mode 100644 index 3f73735c..00000000 --- a/nmrs-gui/src/theme_config.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -fn get_config_path() -> Option { - dirs::config_dir().map(|mut path| { - path.push("nmrs"); - fs::create_dir_all(&path).ok()?; - path.push("theme"); - Some(path) - })? -} - -/// Save the selected theme *name* (e.g. "nord", "gruvbox", "dracula") -pub fn save_theme(name: &str) { - if let Some(path) = get_config_path() { - let _ = fs::write(path, name); - } -} - -/// Load the previously selected theme. -/// Returns Some("nord") or None if missing. -pub fn load_theme() -> Option { - get_config_path() - .and_then(|path| fs::read_to_string(path).ok()) - .map(|s| s.trim().to_string()) -} diff --git a/nmrs-gui/src/themes/catppuccin.css b/nmrs-gui/src/themes/catppuccin.css deleted file mode 100644 index 5abf5395..00000000 --- a/nmrs-gui/src/themes/catppuccin.css +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Catppuccin Theme for nmrs-gui - * - * Customizable CSS Variables: - * --bg-primary: Main background color - * --bg-secondary: Secondary elements (headerbar, cards) - * --bg-tertiary: Hover states - * --text-primary: Primary text color - * --text-secondary: Secondary text (labels, headers) - * --text-tertiary: Tertiary text (back button, toggle) - * --border-color: Default border color - * --border-color-hover: Border color on hover - * --accent-color: Accent color (selected items, switches) - * --success-color: Success indicators (connected, good signal) - * --warning-color: Warning indicators (okay signal) - * --error-color: Error indicators (poor signal) - */ - -/* Light theme */ -window.light-theme { - --bg-primary: #f1f5f9; - --bg-secondary: #e2e8f0; - --bg-tertiary: #cbd5e1; - --text-primary: #0f172a; - --text-secondary: #1e293b; - --text-tertiary: #475569; - --border-color: #cbd5e1; - --border-color-hover: #94a3b8; - --accent-color: #6366f1; - --success-color: #10b981; - --warning-color: #f59e0b; - --error-color: #ef4444; -} - -/* Dark theme */ -window, -window.dark-theme { - --bg-primary: #0f172a; - --bg-secondary: #1e293b; - --bg-tertiary: #334155; - --text-primary: #e2e8f0; - --text-secondary: #cbd5e1; - --text-tertiary: #94a3b8; - --border-color: #334155; - --border-color-hover: #475569; - --accent-color: #6366f1; - --success-color: #10b981; - --warning-color: #f59e0b; - --error-color: #ef4444; -} - -window { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -headerbar { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 1px solid var(--border-color); -} - -switch { - background-color: var(--bg-tertiary); -} -switch:checked { - background-color: var(--accent-color); -} - -.wifi-label { - font-weight: 600; - color: var(--text-primary); -} - -list { - background: var(--bg-primary); - border: none; -} -list > row { - background: transparent; - border: none; - padding: 0; -} -list > row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} - -.pw-entry { - background-color: transparent; - color: var(--text-primary); - border-color: transparent; -} - -.network-selection { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} -.network-selection:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} -.network-selection.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} -.network-selection.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} -.network-selection label { - font-size: 14px; - color: var(--text-primary); -} -.connected-label { - font-size: 12px; - color: var(--success-color); - font-style: italic; - margin-left: 8px; - opacity: 0.9; -} - -label.network-good { color: var(--success-color); } -label.network-okay { color: var(--warning-color); } -label.network-poor { color: var(--error-color); } - -.network-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.back-button { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.back-button:hover { color: var(--text-primary); } - -.network-icon { - color: var(--text-primary); -} -.network-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} -.network-arrow { - color: var(--text-primary); -} -.network-info { - margin-top: 14px; - padding-left: 6px; -} -.info-row { padding: 2px 0; } -.info-value { - color: var(--text-primary); - font-size: 14px; - font-weight: 500; -} - -.section-header { - font-weight: 600; - font-size: 13px; - text-transform: uppercase; - border-bottom: 1px solid var(--border-color); - padding-bottom: 4px; - margin-bottom: 6px; - color: var(--text-secondary); - letter-spacing: 0.5px; -} - -.basic-key, -.info-label { - font-weight: 600; - font-size: 13px; - color: var(--text-tertiary); - margin-bottom: 2px; - text-decoration: underline; -} -.basic-value, -.info-value { - font-size: 14px; - color: var(--text-primary); - font-weight: 500; - margin-bottom: 8px; -} - -.divider { - margin-top: 10px; - margin-bottom: 10px; - opacity: 0.25; - border-bottom: 1px solid var(--border-color); -} - -.wifi-secure { color: var(--text-primary); } -.wifi-open { color: var(--text-primary); } - -.loading-spinner { - margin-top: 12px; - margin-bottom: 12px; - opacity: 0.6; -} - -.forget-button { - font-size: 0.85em; - opacity: 0.7; - padding: 2px 6px; - border-radius: 6px; -} -.forget-button:hover { opacity: 1; } - -.refresh-btn { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.refresh-btn:hover { color: var(--text-primary); } - -.theme-toggle-btn { - background: none; - border: none; - color: var(--text-tertiary); - padding: 4px 8px; - opacity: 0.7; - transition: opacity 150ms ease, color 150ms ease; -} -.theme-toggle-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -.dropdown { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button label { - color: var(--text-primary); -} -.dropdown button:hover { - background: var(--bg-tertiary); - border-radius: 0; -} - -popover.background, -popover contents { - background: var(--bg-secondary); - border-radius: 0; -} -popover row { - background: var(--bg-secondary); - color: var(--text-primary); - border-radius: 0; -} -popover row:hover { - background: var(--bg-tertiary); -} -popover row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} -popover row label { - color: var(--text-primary); -} - -/* Wired Device Styles */ -.wired-section-header { - color: var(--text-secondary); -} - -.wireless-section-header { - color: var(--text-secondary); -} - -.wired-devices-list { - background: var(--bg-primary); - border: none; -} - -.wired-device-row { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} - -.wired-device-row:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} - -.wired-device-row.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} - -.wired-device-row.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} - -.wired-device-row label { - font-size: 14px; - color: var(--text-primary); -} - -.device-separator { - background: var(--border-color); - opacity: 0.3; -} - -.wired-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.wired-device-icon { - color: var(--text-primary); -} - -.wired-device-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} - -.cert-browse-btn { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} - -.cert-browse-btn:hover { - background: var(--bg-secondary); - border-color: var(--border-color-hover); -} - diff --git a/nmrs-gui/src/themes/dracula.css b/nmrs-gui/src/themes/dracula.css deleted file mode 100644 index 363d5d79..00000000 --- a/nmrs-gui/src/themes/dracula.css +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Dracula Theme for nmrs-gui - * - * Customizable CSS Variables: - * --bg-primary: Main background color - * --bg-secondary: Secondary elements (headerbar, cards) - * --bg-tertiary: Hover states - * --text-primary: Primary text color - * --text-secondary: Secondary text (labels, headers) - * --text-tertiary: Tertiary text (back button, toggle) - * --border-color: Default border color - * --border-color-hover: Border color on hover - * --accent-color: Accent color (selected items, switches) - * --success-color: Success indicators (connected, good signal) - * --warning-color: Warning indicators (okay signal) - * --error-color: Error indicators (poor signal) - */ - -/* Light theme */ -window.light-theme { - --bg-primary: #f8f8f2; - --bg-secondary: #e9e9e4; - --bg-tertiary: #dcdcd7; - --text-primary: #282a36; - --text-secondary: #44475a; - --text-tertiary: #6d7080; - --border-color: #dcdcd7; - --border-color-hover: #c9c9c4; - --accent-color: #6272a4; - --success-color: #50fa7b; - --warning-color: #f1fa8c; - --error-color: #ff5555; -} - -/* Dark theme */ -window, -window.dark-theme { - --bg-primary: #282a36; - --bg-secondary: #3a3c4e; - --bg-tertiary: #44475a; - --text-primary: #f8f8f2; - --text-secondary: #e5e5e1; - --text-tertiary: #cfcfcb; - --border-color: #44475a; - --border-color-hover: #5a5d70; - --accent-color: #6272a4; - --success-color: #50fa7b; - --warning-color: #f1fa8c; - --error-color: #ff5555; -} - -window { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -headerbar { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 1px solid var(--border-color); -} - -switch { - background-color: var(--bg-tertiary); -} -switch:checked { - background-color: var(--accent-color); -} - -.wifi-label { - font-weight: 600; - color: var(--text-primary); -} - -list { - background: var(--bg-primary); - border: none; -} -list > row { - background: transparent; - border: none; - padding: 0; -} -list > row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} - -.pw-entry { - background-color: transparent; - color: var(--text-primary); - border-color: transparent; -} - -.network-selection { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} -.network-selection:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} -.network-selection.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} -.network-selection.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} -.network-selection label { - font-size: 14px; - color: var(--text-primary); -} -.connected-label { - font-size: 12px; - color: var(--success-color); - font-style: italic; - margin-left: 8px; - opacity: 0.9; -} - -label.network-good { color: var(--success-color); } -label.network-okay { color: var(--warning-color); } -label.network-poor { color: var(--error-color); } - -.network-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.back-button { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.back-button:hover { color: var(--text-primary); } - -.network-icon { - color: var(--text-primary); -} -.network-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} -.network-arrow { - color: var(--text-primary); -} -.network-info { - margin-top: 14px; - padding-left: 6px; -} -.info-row { padding: 2px 0; } -.info-value { - color: var(--text-primary); - font-size: 14px; - font-weight: 500; -} - -.section-header { - font-weight: 600; - font-size: 13px; - text-transform: uppercase; - border-bottom: 1px solid var(--border-color); - padding-bottom: 4px; - margin-bottom: 6px; - color: var(--text-secondary); - letter-spacing: 0.5px; -} - -.basic-key, -.info-label { - font-weight: 600; - font-size: 13px; - color: var(--text-tertiary); - margin-bottom: 2px; - text-decoration: underline; -} -.basic-value, -.info-value { - font-size: 14px; - color: var(--text-primary); - font-weight: 500; - margin-bottom: 8px; -} - -.divider { - margin-top: 10px; - margin-bottom: 10px; - opacity: 0.25; - border-bottom: 1px solid var(--border-color); -} - -.wifi-secure { color: var(--text-primary); } -.wifi-open { color: var(--text-primary); } - -.loading-spinner { - margin-top: 12px; - margin-bottom: 12px; - opacity: 0.6; -} - -.forget-button { - font-size: 0.85em; - opacity: 0.7; - padding: 2px 6px; - border-radius: 6px; -} -.forget-button:hover { opacity: 1; } - -.refresh-btn { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.refresh-btn:hover { color: var(--text-primary); } - -.theme-toggle-btn { - background: none; - border: none; - color: var(--text-tertiary); - padding: 4px 8px; - opacity: 0.7; - transition: opacity 150ms ease, color 150ms ease; -} -.theme-toggle-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -.dropdown { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button label { - color: var(--text-primary); -} -.dropdown button:hover { - background: var(--bg-tertiary); - border-radius: 0; -} - -popover.background, -popover contents { - background: var(--bg-secondary); - border-radius: 0; -} -popover row { - background: var(--bg-secondary); - color: var(--text-primary); - border-radius: 0; -} -popover row:hover { - background: var(--bg-tertiary); -} -popover row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} -popover row label { - color: var(--text-primary); -} - -/* Wired Device Styles */ -.wired-section-header { - color: var(--text-secondary); -} - -.wireless-section-header { - color: var(--text-secondary); -} - -.wired-devices-list { - background: var(--bg-primary); - border: none; -} - -.wired-device-row { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} - -.wired-device-row:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} - -.wired-device-row.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} - -.wired-device-row.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} - -.wired-device-row label { - font-size: 14px; - color: var(--text-primary); -} - -.device-separator { - background: var(--border-color); - opacity: 0.3; -} - -.wired-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.wired-device-icon { - color: var(--text-primary); -} - -.wired-device-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} - -.cert-browse-btn { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} - -.cert-browse-btn:hover { - background: var(--bg-secondary); - border-color: var(--border-color-hover); -} - diff --git a/nmrs-gui/src/themes/gruvbox.css b/nmrs-gui/src/themes/gruvbox.css deleted file mode 100644 index 8b08bdd6..00000000 --- a/nmrs-gui/src/themes/gruvbox.css +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Gruvbox Theme for nmrs-gui - * - * Customizable CSS Variables: - * - * Colors: - * --bg-primary: Main background color - * --bg-secondary: Secondary elements (headerbar, cards) - * --bg-tertiary: Hover states - * --text-primary: Primary text color - * --text-secondary: Secondary text (labels, headers) - * --text-tertiary: Tertiary text (back button, toggle) - * --border-color: Default border color - * --border-color-hover: Border color on hover - * --accent-color: Accent color (selected items, switches) - * --success-color: Success indicators (connected, good signal) - * --warning-color: Warning indicators (okay signal) - * --error-color: Error indicators (poor signal) - * - * Layout & Spacing: - * --network-row-padding: Padding for network/device rows - * --network-row-margin: Margin between rows - * --section-header-spacing: Bottom margin for section headers - * --page-padding: Padding for detail pages - * - * Typography: - * --font-size-base: Base font size (14px) - * --font-size-small: Small text (12px) - * --font-size-large: Large headings (18px) - * --font-size-header: Section headers (13px) - * - * Borders: - * --border-width: Border width for elements - * --border-radius: Border radius (currently 0 for sharp look) - */ - -/* Light theme */ -window.light-theme { - --bg-primary: #fbf1c7; - --bg-secondary: #f2e5bc; - --bg-tertiary: #ebdbb2; - --text-primary: #3c3836; - --text-secondary: #504945; - --text-tertiary: #7c6f64; - --border-color: #d5c4a1; - --border-color-hover: #bdae93; - --accent-color: #d79921; - --success-color: #98971a; - --warning-color: #d65d0e; - --error-color: #cc241d; - - --network-row-padding: 6px 10px; - --network-row-margin: 2px 0; - --section-header-spacing: 6px; - --page-padding: 16px 20px; - - --font-size-base: 14px; - --font-size-small: 12px; - --font-size-large: 18px; - --font-size-header: 13px; - - --border-width: 1px; - --border-radius: 0; -} - -/* Dark theme */ -window, -window.dark-theme { - --bg-primary: #282828; - --bg-secondary: #3c3836; - --bg-tertiary: #504945; - --text-primary: #ebdbb2; - --text-secondary: #d5c4a1; - --text-tertiary: #bdae93; - --border-color: #504945; - --border-color-hover: #665c54; - --accent-color: #d79921; - --success-color: #98971a; - --warning-color: #d65d0e; - --error-color: #cc241d; -} - -window { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -headerbar { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 1px solid var(--border-color); -} - -switch { - background-color: var(--bg-tertiary); -} -switch:checked { - background-color: var(--accent-color); -} - -.wifi-label { - font-weight: 600; - color: var(--text-primary); -} - -list { - background: var(--bg-primary); - border: none; -} -list > row { - background: transparent; - border: none; - padding: 0; -} -list > row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} - -.pw-entry { - background-color: transparent; - color: var(--text-primary); - border-color: transparent; -} - -.network-selection { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} -.network-selection:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} -.network-selection.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} -.network-selection.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} -.network-selection label { - font-size: 14px; - color: var(--text-primary); -} -.connected-label { - font-size: 12px; - color: var(--success-color); - font-style: italic; - margin-left: 8px; - opacity: 0.9; -} - -label.network-good { color: var(--success-color); } -label.network-okay { color: var(--warning-color); } -label.network-poor { color: var(--error-color); } - -.network-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.back-button { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.back-button:hover { color: var(--text-primary); } - -.network-icon { - color: var(--text-primary); -} -.network-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} -.network-arrow { - color: var(--text-primary); -} -.network-info { - margin-top: 14px; - padding-left: 6px; -} -.info-row { padding: 2px 0; } -.info-value { - color: var(--text-primary); - font-size: 14px; - font-weight: 500; -} - -.section-header { - font-weight: 600; - font-size: 13px; - text-transform: uppercase; - border-bottom: 1px solid var(--border-color); - padding-bottom: 4px; - margin-bottom: 6px; - color: var(--text-secondary); - letter-spacing: 0.5px; -} - -.basic-key, -.info-label { - font-weight: 600; - font-size: 13px; - color: var(--text-tertiary); - margin-bottom: 2px; - text-decoration: underline; -} -.basic-value, -.info-value { - font-size: 14px; - color: var(--text-primary); - font-weight: 500; - margin-bottom: 8px; -} - -.divider { - margin-top: 10px; - margin-bottom: 10px; - opacity: 0.25; - border-bottom: 1px solid var(--border-color); -} - -.wifi-secure { color: var(--text-primary); } -.wifi-open { color: var(--text-primary); } - -.loading-spinner { - margin-top: 12px; - margin-bottom: 12px; - opacity: 0.6; -} - -.forget-button { - font-size: 0.85em; - opacity: 0.7; - padding: 2px 6px; - border-radius: 6px; -} -.forget-button:hover { opacity: 1; } - -.refresh-btn { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.refresh-btn:hover { color: var(--text-primary); } - -.theme-toggle-btn { - background: none; - border: none; - color: var(--text-tertiary); - padding: 4px 8px; - opacity: 0.7; - transition: opacity 150ms ease, color 150ms ease; -} -.theme-toggle-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -.dropdown { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button label { - color: var(--text-primary); -} -.dropdown button:hover { - background: var(--bg-tertiary); - border-radius: 0; -} - -popover.background, -popover contents { - background: var(--bg-secondary); - border-radius: 0; -} -popover row { - background: var(--bg-secondary); - color: var(--text-primary); - border-radius: 0; -} -popover row:hover { - background: var(--bg-tertiary); -} -popover row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} -popover row label { - color: var(--text-primary); -} - -/* Wired Device Styles */ -.wired-section-header { - color: var(--text-secondary); -} - -.wireless-section-header { - color: var(--text-secondary); -} - -.wired-devices-list { - background: var(--bg-primary); - border: none; -} - -.wired-device-row { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} - -.wired-device-row:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} - -.wired-device-row.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} - -.wired-device-row.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} - -.wired-device-row label { - font-size: 14px; - color: var(--text-primary); -} - -.device-separator { - background: var(--border-color); - opacity: 0.3; -} - -.wired-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.wired-device-icon { - color: var(--text-primary); -} - -.wired-device-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} - -.cert-browse-btn { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} - -.cert-browse-btn:hover { - background: var(--bg-secondary); - border-color: var(--border-color-hover); -} - diff --git a/nmrs-gui/src/themes/nord.css b/nmrs-gui/src/themes/nord.css deleted file mode 100644 index 5ec7a32a..00000000 --- a/nmrs-gui/src/themes/nord.css +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Nord Theme for nmrs-gui - * - * Customizable CSS Variables: - * --bg-primary: Main background color - * --bg-secondary: Secondary elements (headerbar, cards) - * --bg-tertiary: Hover states - * --text-primary: Primary text color - * --text-secondary: Secondary text (labels, headers) - * --text-tertiary: Tertiary text (back button, toggle) - * --border-color: Default border color - * --border-color-hover: Border color on hover - * --accent-color: Accent color (selected items, switches) - * --success-color: Success indicators (connected, good signal) - * --warning-color: Warning indicators (okay signal) - * --error-color: Error indicators (poor signal) - */ - -/* Light theme */ -window.light-theme { - --bg-primary: #eceff4; - --bg-secondary: #e5e9f0; - --bg-tertiary: #d8dee9; - --text-primary: #2e3440; - --text-secondary: #4c566a; - --text-tertiary: #6c7a96; - --border-color: #d8dee9; - --border-color-hover: #c2c8d3; - --accent-color: #5e81ac; - --success-color: #a3be8c; - --warning-color: #ebcb8b; - --error-color: #bf616a; -} - -/* Dark theme */ -window, -window.dark-theme { - --bg-primary: #2e3440; - --bg-secondary: #3b4252; - --bg-tertiary: #434c5e; - --text-primary: #eceff4; - --text-secondary: #e5e9f0; - --text-tertiary: #d8dee9; - --border-color: #434c5e; - --border-color-hover: #4c556a; - --accent-color: #81a1c1; - --success-color: #a3be8c; - --warning-color: #ebcb8b; - --error-color: #bf616a; -} - -window { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -headerbar { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 1px solid var(--border-color); -} - -switch { - background-color: var(--bg-tertiary); -} -switch:checked { - background-color: var(--accent-color); -} - -.wifi-label { - font-weight: 600; - color: var(--text-primary); -} - -list { - background: var(--bg-primary); - border: none; -} -list > row { - background: transparent; - border: none; - padding: 0; -} -list > row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} - -.pw-entry { - background-color: transparent; - color: var(--text-primary); - border-color: transparent; -} - -.network-selection { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} -.network-selection:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} -.network-selection.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} -.network-selection.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} -.network-selection label { - font-size: 14px; - color: var(--text-primary); -} -.connected-label { - font-size: 12px; - color: var(--success-color); - font-style: italic; - margin-left: 8px; - opacity: 0.9; -} - -label.network-good { color: var(--success-color); } -label.network-okay { color: var(--warning-color); } -label.network-poor { color: var(--error-color); } - -.network-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.back-button { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.back-button:hover { color: var(--text-primary); } - -.network-icon { - color: var(--text-primary); -} -.network-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} -.network-arrow { - color: var(--text-primary); -} -.network-info { - margin-top: 14px; - padding-left: 6px; -} -.info-row { padding: 2px 0; } -.info-value { - color: var(--text-primary); - font-size: 14px; - font-weight: 500; -} - -.section-header { - font-weight: 600; - font-size: 13px; - text-transform: uppercase; - border-bottom: 1px solid var(--border-color); - padding-bottom: 4px; - margin-bottom: 6px; - color: var(--text-secondary); - letter-spacing: 0.5px; -} - -.basic-key, -.info-label { - font-weight: 600; - font-size: 13px; - color: var(--text-tertiary); - margin-bottom: 2px; - text-decoration: underline; -} -.basic-value, -.info-value { - font-size: 14px; - color: var(--text-primary); - font-weight: 500; - margin-bottom: 8px; -} - -.divider { - margin-top: 10px; - margin-bottom: 10px; - opacity: 0.25; - border-bottom: 1px solid var(--border-color); -} - -.wifi-secure { color: var(--text-primary); } -.wifi-open { color: var(--text-primary); } - -.loading-spinner { - margin-top: 12px; - margin-bottom: 12px; - opacity: 0.6; -} - -.forget-button { - font-size: 0.85em; - opacity: 0.7; - padding: 2px 6px; - border-radius: 6px; -} -.forget-button:hover { opacity: 1; } - -.refresh-btn { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.refresh-btn:hover { color: var(--text-primary); } - -.theme-toggle-btn { - background: none; - border: none; - color: var(--text-tertiary); - padding: 4px 8px; - opacity: 0.7; - transition: opacity 150ms ease, color 150ms ease; -} -.theme-toggle-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -.dropdown { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button label { - color: var(--text-primary); -} -.dropdown button:hover { - background: var(--bg-tertiary); - border-radius: 0; -} - -popover.background, -popover contents { - background: var(--bg-secondary); - border-radius: 0; -} -popover row { - background: var(--bg-secondary); - color: var(--text-primary); - border-radius: 0; -} -popover row:hover { - background: var(--bg-tertiary); -} -popover row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} -popover row label { - color: var(--text-primary); -} - -/* Wired Device Styles */ -.wired-section-header { - color: var(--text-secondary); -} - -.wireless-section-header { - color: var(--text-secondary); -} - -.wired-devices-list { - background: var(--bg-primary); - border: none; -} - -.wired-device-row { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} - -.wired-device-row:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} - -.wired-device-row.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} - -.wired-device-row.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} - -.wired-device-row label { - font-size: 14px; - color: var(--text-primary); -} - -.device-separator { - background: var(--border-color); - opacity: 0.3; -} - -.wired-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.wired-device-icon { - color: var(--text-primary); -} - -.wired-device-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} - -.cert-browse-btn { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} - -.cert-browse-btn:hover { - background: var(--bg-secondary); - border-color: var(--border-color-hover); -} - diff --git a/nmrs-gui/src/themes/tokyo.css b/nmrs-gui/src/themes/tokyo.css deleted file mode 100644 index b4df0be6..00000000 --- a/nmrs-gui/src/themes/tokyo.css +++ /dev/null @@ -1,364 +0,0 @@ -/* - * Tokyo Night Theme for nmrs-gui - * - * Customizable CSS Variables: - * --bg-primary: Main background color - * --bg-secondary: Secondary elements (headerbar, cards) - * --bg-tertiary: Hover states - * --text-primary: Primary text color - * --text-secondary: Secondary text (labels, headers) - * --text-tertiary: Tertiary text (back button, toggle) - * --border-color: Default border color - * --border-color-hover: Border color on hover - * --accent-color: Accent color (selected items, switches) - * --success-color: Success indicators (connected, good signal) - * --warning-color: Warning indicators (okay signal) - * --error-color: Error indicators (poor signal) - */ - -/* Light theme */ -window.light-theme { - --bg-primary: #dfe1e8; - --bg-secondary: #cfd1d9; - --bg-tertiary: #bfc2cc; - --text-primary: #1a1b26; - --text-secondary: #414868; - --text-tertiary: #565f89; - --border-color: #bfc2cc; - --border-color-hover: #a9acb8; - --accent-color: #7aa2f7; - --success-color: #9ece6a; - --warning-color: #e0af68; - --error-color: #f7768e; -} - -/* Dark theme */ -window, -window.dark-theme { - --bg-primary: #1a1b26; - --bg-secondary: #24283b; - --bg-tertiary: #2f3549; - --text-primary: #c0caf5; - --text-secondary: #a9b1d6; - --text-tertiary: #737aa2; - --border-color: #2f3549; - --border-color-hover: #414868; - --accent-color: #7aa2f7; - --success-color: #9ece6a; - --warning-color: #e0af68; - --error-color: #f7768e; -} - -window { - background-color: var(--bg-primary); - color: var(--text-primary); -} - -headerbar { - background: var(--bg-secondary); - color: var(--text-primary); - border-bottom: 1px solid var(--border-color); -} - -switch { - background-color: var(--bg-tertiary); -} -switch:checked { - background-color: var(--accent-color); -} - -.wifi-label { - font-weight: 600; - color: var(--text-primary); -} - -list { - background: var(--bg-primary); - border: none; -} -list > row { - background: transparent; - border: none; - padding: 0; -} -list > row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} - -.pw-entry { - background-color: transparent; - color: var(--text-primary); - border-color: transparent; -} - -.network-selection { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} -.network-selection:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} -.network-selection.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} -.network-selection.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} -.network-selection label { - font-size: 14px; - color: var(--text-primary); -} -.connected-label { - font-size: 12px; - color: var(--success-color); - font-style: italic; - margin-left: 8px; - opacity: 0.9; -} - -label.network-good { color: var(--success-color); } -label.network-okay { color: var(--warning-color); } -label.network-poor { color: var(--error-color); } - -.network-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.back-button { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.back-button:hover { color: var(--text-primary); } - -.network-icon { - color: var(--text-primary); -} -.network-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} -.network-arrow { - color: var(--text-primary); -} -.network-info { - margin-top: 14px; - padding-left: 6px; -} -.info-row { padding: 2px 0; } -.info-value { - color: var(--text-primary); - font-size: 14px; - font-weight: 500; -} - -.section-header { - font-weight: 600; - font-size: 13px; - text-transform: uppercase; - border-bottom: 1px solid var(--border-color); - padding-bottom: 4px; - margin-bottom: 6px; - color: var(--text-secondary); - letter-spacing: 0.5px; -} - -.basic-key, -.info-label { - font-weight: 600; - font-size: 13px; - color: var(--text-tertiary); - margin-bottom: 2px; - text-decoration: underline; -} -.basic-value, -.info-value { - font-size: 14px; - color: var(--text-primary); - font-weight: 500; - margin-bottom: 8px; -} - -.divider { - margin-top: 10px; - margin-bottom: 10px; - opacity: 0.25; - border-bottom: 1px solid var(--border-color); -} - -.wifi-secure { color: var(--text-primary); } -.wifi-open { color: var(--text-primary); } - -.loading-spinner { - margin-top: 12px; - margin-bottom: 12px; - opacity: 0.6; -} - -.forget-button { - font-size: 0.85em; - opacity: 0.7; - padding: 2px 6px; - border-radius: 6px; -} -.forget-button:hover { opacity: 1; } - -.refresh-btn { - background: none; - border: none; - color: var(--text-tertiary); - font-weight: 500; - font-size: 13px; - padding: 4px 0; -} -.refresh-btn:hover { color: var(--text-primary); } - -.theme-toggle-btn { - background: none; - border: none; - color: var(--text-tertiary); - padding: 4px 8px; - opacity: 0.7; - transition: opacity 150ms ease, color 150ms ease; -} -.theme-toggle-btn:hover { - opacity: 1; - color: var(--text-primary); -} - -.dropdown { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button { - background: var(--bg-secondary); - border: none; - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} -.dropdown button label { - color: var(--text-primary); -} -.dropdown button:hover { - background: var(--bg-tertiary); - border-radius: 0; -} - -popover.background, -popover contents { - background: var(--bg-secondary); - border-radius: 0; -} -popover row { - background: var(--bg-secondary); - color: var(--text-primary); - border-radius: 0; -} -popover row:hover { - background: var(--bg-tertiary); -} -popover row:selected { - background: var(--accent-color); - color: var(--bg-primary); -} -popover row label { - color: var(--text-primary); -} - -/* Wired Device Styles */ -.wired-section-header { - color: var(--text-secondary); -} - -.wireless-section-header { - color: var(--text-secondary); -} - -.wired-devices-list { - background: var(--bg-primary); - border: none; -} - -.wired-device-row { - padding: 6px 10px; - margin: 2px 0; - background: var(--bg-secondary); - border: 1px solid var(--border-color); -} - -.wired-device-row:hover { - background: var(--bg-tertiary); - border-color: var(--border-color-hover); - transition: background 150ms ease, border-color 150ms ease; -} - -.wired-device-row.connected { - background: color-mix(in srgb, var(--success-color) 15%, transparent); - border-color: color-mix(in srgb, var(--success-color) 30%, transparent); -} - -.wired-device-row.connected:hover { - background: color-mix(in srgb, var(--success-color) 20%, transparent); - border-color: color-mix(in srgb, var(--success-color) 40%, transparent); -} - -.wired-device-row label { - font-size: 14px; - color: var(--text-primary); -} - -.device-separator { - background: var(--border-color); - opacity: 0.3; -} - -.wired-page { - background: var(--bg-primary); - padding: 16px 20px; - color: var(--text-primary); - border: none; -} - -.wired-device-icon { - color: var(--text-primary); -} - -.wired-device-title { - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: 4px; -} - -.cert-browse-btn { - background: var(--bg-tertiary); - border: 1px solid var(--border-color); - color: var(--text-primary); - padding: 6px 12px; - font-size: 13px; -} - -.cert-browse-btn:hover { - background: var(--bg-secondary); - border-color: var(--border-color-hover); -} - diff --git a/nmrs-gui/src/ui/connect.rs b/nmrs-gui/src/ui/connect.rs deleted file mode 100644 index bf365312..00000000 --- a/nmrs-gui/src/ui/connect.rs +++ /dev/null @@ -1,256 +0,0 @@ -use glib::Propagation; -use gtk::{ - ApplicationWindow, Box as GtkBox, Button, CheckButton, Dialog, Entry, EventControllerKey, - FileChooserAction, FileChooserDialog, Label, Orientation, ResponseType, prelude::*, -}; -use log::{debug, error}; -use nmrs::{ - NetworkManager, - models::{EapMethod, EapOptions, Phase2, WifiSecurity}, -}; -use std::rc::Rc; - -pub fn connect_modal( - nm: Rc, - parent: &ApplicationWindow, - ssid: &str, - is_eap: bool, - on_connection_success: Rc, -) { - let ssid_owned = ssid.to_string(); - let parent_weak = parent.downgrade(); - - glib::MainContext::default().spawn_local(async move { - if let Some(current) = nm.current_ssid().await - && current == ssid_owned - { - debug!("Already connected to {current}, skipping modal"); - return; - } - - if let Some(parent) = parent_weak.upgrade() { - draw_connect_modal(nm, &parent, &ssid_owned, is_eap, on_connection_success); - } - }); -} - -fn draw_connect_modal( - nm: Rc, - parent: &ApplicationWindow, - ssid: &str, - is_eap: bool, - on_connection_success: Rc, -) { - let dialog = Dialog::new(); - dialog.set_title(Some("Connect to Network")); - dialog.set_transient_for(Some(parent)); - dialog.set_modal(true); - dialog.add_css_class("diag-buttons"); - - let content_area = dialog.content_area(); - let vbox = GtkBox::new(Orientation::Vertical, 8); - vbox.set_margin_top(32); - vbox.set_margin_bottom(32); - vbox.set_margin_start(48); - vbox.set_margin_end(48); - - let user_entry = if is_eap { - let user_label = Label::new(Some("Username:")); - let user_entry = Entry::new(); - user_entry.add_css_class("pw-entry"); - user_entry.set_placeholder_text(Some("email, username, id...")); - vbox.append(&user_label); - vbox.append(&user_entry); - Some(user_entry) - } else { - None - }; - - let label = Label::new(Some("Password:")); - let entry = Entry::new(); - entry.add_css_class("pw-entry"); - entry.set_placeholder_text(Some("Password")); - entry.set_visibility(false); - vbox.append(&label); - vbox.append(&entry); - - let (cert_entry, use_system_certs, browse_btn) = if is_eap { - let cert_label = Label::new(Some("CA Certificate (optional):")); - cert_label.set_margin_top(8); - let cert_entry = Entry::new(); - cert_entry.add_css_class("pw-entry"); - cert_entry.set_placeholder_text(Some("/path/to/ca-cert.pem")); - - let cert_hbox = GtkBox::new(Orientation::Horizontal, 8); - let browse_btn = Button::with_label("Browse..."); - browse_btn.add_css_class("cert-browse-btn"); - cert_hbox.append(&cert_entry); - cert_hbox.append(&browse_btn); - - vbox.append(&cert_label); - vbox.append(&cert_hbox); - - let system_certs_check = CheckButton::with_label("Use system CA certificates"); - system_certs_check.set_active(true); - system_certs_check.set_margin_top(4); - vbox.append(&system_certs_check); - - (Some(cert_entry), Some(system_certs_check), Some(browse_btn)) - } else { - (None, None, None) - }; - - content_area.append(&vbox); - - let dialog_rc = Rc::new(dialog); - let ssid_owned = ssid.to_string(); - let user_entry_clone = user_entry.clone(); - - let status_label = Label::new(Some("")); - status_label.add_css_class("status-label"); - vbox.append(&status_label); - - if let Some(browse_btn) = browse_btn { - let cert_entry_for_browse = cert_entry.clone(); - let dialog_weak = dialog_rc.downgrade(); - browse_btn.connect_clicked(move |_| { - if let Some(parent_dialog) = dialog_weak.upgrade() { - let file_dialog = FileChooserDialog::new( - Some("Select CA Certificate"), - Some(&parent_dialog), - FileChooserAction::Open, - &[ - ("Cancel", ResponseType::Cancel), - ("Open", ResponseType::Accept), - ], - ); - - let cert_entry = cert_entry_for_browse.clone(); - file_dialog.connect_response(move |dialog, response| { - if response == ResponseType::Accept - && let Some(file) = dialog.file() - && let Some(path) = file.path() - { - cert_entry - .as_ref() - .unwrap() - .set_text(&path.to_string_lossy()); - } - dialog.close(); - }); - - file_dialog.show(); - } - }); - } - - { - let dialog_rc = dialog_rc.clone(); - let status_label = status_label.clone(); - let refresh_callback = on_connection_success.clone(); - let nm = nm.clone(); - let cert_entry_clone = cert_entry.clone(); - let use_system_certs_clone = use_system_certs.clone(); - - entry.connect_activate(move |entry| { - let pwd = entry.text().to_string(); - - let username = user_entry_clone - .as_ref() - .map(|e| e.text().to_string()) - .unwrap_or_default(); - - let cert_path = cert_entry_clone.as_ref().and_then(|e| { - let text = e.text().to_string(); - if text.trim().is_empty() { - None - } else { - Some(text) - } - }); - - let use_system_ca = use_system_certs_clone - .as_ref() - .map(|cb| cb.is_active()) - .unwrap_or(true); - - let ssid = ssid_owned.clone(); - let dialog = dialog_rc.clone(); - let status = status_label.clone(); - let entry = entry.clone(); - let user_entry = user_entry_clone.clone(); - let on_success = refresh_callback.clone(); - let nm = nm.clone(); - - entry.set_sensitive(false); - if let Some(ref user_entry) = user_entry { - user_entry.set_sensitive(false); - } - status.set_text("Connecting..."); - - glib::MainContext::default().spawn_local(async move { - let creds = if is_eap { - let mut opts = EapOptions::new(username, pwd) - .with_method(EapMethod::Peap) - .with_phase2(Phase2::Mschapv2) - .with_system_ca_certs(use_system_ca); - - if let Some(cert) = cert_path { - opts = opts.with_ca_cert_path(format!("file://{}", cert)); - } - - WifiSecurity::WpaEap { opts } - } else { - WifiSecurity::WpaPsk { psk: pwd } - }; - - debug!("Calling nm.connect() for '{ssid}'"); - match nm.connect(&ssid, None, creds).await { - Ok(_) => { - debug!("nm.connect() succeeded!"); - status.set_text("✓ Connected!"); - on_success(); - glib::timeout_future_seconds(1).await; - dialog.close(); - } - Err(err) => { - error!("nm.connect() failed: {err}"); - let err_str = err.to_string().to_lowercase(); - if err_str.contains("authentication") - || err_str.contains("supplicant") - || err_str.contains("password") - || err_str.contains("psk") - || err_str.contains("wrong") - { - status.set_text("Wrong password, try again"); - entry.set_text(""); - entry.grab_focus(); - } else { - status.set_text(&format!("✗ Failed: {err}")); - } - entry.set_sensitive(true); - if let Some(ref user_entry) = user_entry { - user_entry.set_sensitive(true); - } - } - } - }); - }); - } - - { - let dialog_rc = dialog_rc.clone(); - let key_controller = EventControllerKey::new(); - key_controller.connect_key_pressed(move |_, key, _, _| { - if key == gtk::gdk::Key::Escape { - dialog_rc.close(); - Propagation::Stop - } else { - Propagation::Proceed - } - }); - entry.add_controller(key_controller); - } - - dialog_rc.show(); -} diff --git a/nmrs-gui/src/ui/header.rs b/nmrs-gui/src/ui/header.rs deleted file mode 100644 index 963b0acd..00000000 --- a/nmrs-gui/src/ui/header.rs +++ /dev/null @@ -1,552 +0,0 @@ -use glib::clone; -use gtk::prelude::*; -use gtk::{Align, Box as GtkBox, HeaderBar, Label, ListBox, Orientation, Switch, glib}; -use std::cell::Cell; -use std::collections::HashSet; -use std::rc::Rc; - -use nmrs::ConnectivityState; -use nmrs::models; - -use crate::ui::networks; -use crate::ui::networks::NetworksContext; -use crate::ui::wired_devices; - -pub struct ThemeDef { - pub key: &'static str, - pub name: &'static str, - pub css: &'static str, -} - -pub static THEMES: &[ThemeDef] = &[ - ThemeDef { - key: "gruvbox", - name: "Gruvbox", - css: include_str!("../themes/gruvbox.css"), - }, - ThemeDef { - key: "nord", - name: "Nord", - css: include_str!("../themes/nord.css"), - }, - ThemeDef { - key: "dracula", - name: "Dracula", - css: include_str!("../themes/dracula.css"), - }, - ThemeDef { - key: "catppuccin", - name: "Catppuccin", - css: include_str!("../themes/catppuccin.css"), - }, - ThemeDef { - key: "tokyo", - name: "Tokyo Night", - css: include_str!("../themes/tokyo.css"), - }, -]; - -pub fn build_header( - ctx: Rc, - list_container: &GtkBox, - is_scanning: Rc>, -) -> HeaderBar { - let header = HeaderBar::new(); - header.set_show_title_buttons(false); - - let list_container = list_container.clone(); - - // Left side: status label - ctx.status.set_hexpand(true); - ctx.status.set_halign(Align::Start); - header.pack_start(&ctx.status); - - // Right side: settings gear - let settings_btn = gtk::Button::from_icon_name("emblem-system-symbolic"); - settings_btn.set_has_frame(false); - settings_btn.set_valign(Align::Center); - settings_btn.set_tooltip_text(Some("Settings")); - settings_btn.add_css_class("settings-btn"); - { - let stack = ctx.stack.clone(); - settings_btn.connect_clicked(move |_| { - stack.set_visible_child_name("settings"); - }); - } - header.pack_end(&settings_btn); - - // Right side: radio controls (airplane + wifi switch) - let airplane_btn = gtk::Button::new(); - airplane_btn.set_valign(Align::Center); - airplane_btn.set_has_frame(false); - airplane_btn.set_icon_name("airplane-mode-symbolic"); - airplane_btn.set_tooltip_text(Some("Toggle Airplane Mode")); - airplane_btn.add_css_class("airplane-btn"); - header.pack_end(&airplane_btn); - - let wifi_switch = Switch::new(); - wifi_switch.set_valign(Align::Center); - wifi_switch.set_size_request(24, 24); - header.pack_end(&wifi_switch); - - // Right side: refresh - let refresh_btn = gtk::Button::from_icon_name("view-refresh-symbolic"); - refresh_btn.add_css_class("refresh-btn"); - refresh_btn.set_has_frame(false); - refresh_btn.set_tooltip_text(Some("Refresh networks and devices")); - header.pack_end(&refresh_btn); - refresh_btn.connect_clicked(clone!( - #[weak] - list_container, - #[strong] - ctx, - #[strong] - is_scanning, - move |_| { - let ctx = ctx.clone(); - let list_container = list_container.clone(); - let is_scanning = is_scanning.clone(); - - glib::MainContext::default().spawn_local(async move { - refresh_networks(ctx, &list_container, &is_scanning).await; - }); - } - )); - - { - let list_container = list_container.clone(); - let wifi_switch = wifi_switch.clone(); - let airplane_btn = airplane_btn.clone(); - let ctx = ctx.clone(); - let is_scanning = is_scanning.clone(); - - glib::MainContext::default().spawn_local(async move { - ctx.stack.set_visible_child_name("loading"); - clear_children(&list_container); - - apply_airplane_icon(&airplane_btn, &ctx).await; - apply_connectivity_status(&ctx).await; - - match ctx.nm.wifi_state().await.map(|s| s.enabled) { - Ok(enabled) => { - wifi_switch.set_active(enabled); - if enabled { - refresh_networks(ctx, &list_container, &is_scanning).await; - } - } - Err(err) => { - ctx.status - .set_text(&format!("Error fetching networks: {err}")); - } - } - }) - }; - - { - let ctx = ctx.clone(); - airplane_btn.connect_clicked(clone!( - #[weak] - list_container, - #[strong] - wifi_switch, - #[strong] - is_scanning, - move |btn| { - let ctx = ctx.clone(); - let list_container = list_container.clone(); - let is_scanning = is_scanning.clone(); - let wifi_switch = wifi_switch.clone(); - let btn = btn.clone(); - - glib::MainContext::default().spawn_local(async move { - let currently_airplane = ctx - .nm - .airplane_mode_state() - .await - .map(|s| s.is_airplane_mode()) - .unwrap_or(false); - - let new_state = !currently_airplane; - - if let Err(err) = ctx.nm.set_airplane_mode(new_state).await { - ctx.status.set_text(&format!("Airplane mode error: {err}")); - return; - } - - apply_airplane_icon(&btn, &ctx).await; - - if new_state { - wifi_switch.set_active(false); - clear_children(&list_container); - ctx.status.set_text("Airplane mode on"); - } else { - let wifi_on = ctx - .nm - .wifi_state() - .await - .map(|s| s.enabled) - .unwrap_or(false); - wifi_switch.set_active(wifi_on); - if wifi_on && ctx.nm.wait_for_wifi_ready().await.is_ok() { - refresh_networks(ctx, &list_container, &is_scanning).await; - } - } - }); - } - )); - } - - { - let ctx = ctx.clone(); - - wifi_switch.connect_active_notify(move |sw| { - let ctx = ctx.clone(); - let list_container = list_container.clone(); - let sw = sw.clone(); - let is_scanning = is_scanning.clone(); - - glib::MainContext::default().spawn_local(async move { - clear_children(&list_container); - - if let Err(err) = ctx.nm.set_wireless_enabled(sw.is_active()).await { - ctx.status.set_text(&format!("Error setting Wi-Fi: {err}")); - return; - } - - if sw.is_active() { - if ctx.nm.wait_for_wifi_ready().await.is_ok() { - refresh_networks(ctx, &list_container, &is_scanning).await; - } else { - ctx.status.set_text("Wi-Fi failed to initialize"); - } - } - }); - }); - } - - header -} - -async fn apply_airplane_icon(btn: >k::Button, ctx: &NetworksContext) { - match ctx.nm.airplane_mode_state().await { - Ok(state) => { - if state.is_airplane_mode() { - btn.set_icon_name("airplane-mode-symbolic"); - btn.set_tooltip_text(Some("Airplane Mode is ON — click to disable")); - btn.add_css_class("airplane-active"); - } else { - btn.set_icon_name("network-wireless-symbolic"); - btn.set_tooltip_text(Some("Airplane Mode is OFF — click to enable")); - btn.remove_css_class("airplane-active"); - } - - if state.any_hardware_killed() { - btn.set_tooltip_text(Some("A hardware radio kill switch is active")); - } - } - Err(_) => { - btn.set_icon_name("airplane-mode-symbolic"); - btn.set_sensitive(false); - btn.set_tooltip_text(Some("Airplane mode unavailable")); - } - } -} - -async fn apply_connectivity_status(ctx: &NetworksContext) { - if let Ok(report) = ctx.nm.connectivity_report().await { - let text = connectivity_label(&report.state, report.captive_portal_url.as_deref()); - if !text.is_empty() { - ctx.status.set_text(&text); - } - } -} - -fn connectivity_label(state: &ConnectivityState, portal_url: Option<&str>) -> String { - match state { - ConnectivityState::Full => String::new(), - ConnectivityState::Portal => match portal_url { - Some(url) => format!("Captive portal: {url}"), - None => "Captive portal detected".to_string(), - }, - ConnectivityState::Limited => "Limited connectivity".to_string(), - ConnectivityState::None => "No internet".to_string(), - ConnectivityState::Unknown => String::new(), - _ => String::new(), - } -} - -pub async fn refresh_networks( - ctx: Rc, - list_container: &GtkBox, - is_scanning: &Rc>, -) { - if is_scanning.get() { - ctx.status.set_text("Scan already in progress"); - return; - } - is_scanning.set(true); - - clear_children(list_container); - ctx.status.set_text("Scanning..."); - - // Fetch wired devices first - match ctx.nm.list_wired_devices().await { - Ok(wired_devices) => { - // eprintln!("Found {} wired devices total", wired_devices.len()); - - let available_devices: Vec<_> = wired_devices - .into_iter() - .filter(|dev| { - let show = matches!( - dev.state, - models::DeviceState::Activated - | models::DeviceState::Disconnected - | models::DeviceState::Prepare - | models::DeviceState::Config - ); - /* eprintln!( - " - {} ({}): {} -> {}", - dev.interface, - dev.device_type, - dev.state, - if show { "SHOW" } else { "HIDE" } - ); */ - show - }) - .collect(); - - /* eprintln!( - "Showing {} available wired devices", - available_devices.len() - ); */ - - if !available_devices.is_empty() { - let wired_header = Label::new(Some("Wired")); - wired_header.add_css_class("section-header"); - wired_header.add_css_class("wired-section-header"); - wired_header.set_halign(Align::Start); - wired_header.set_margin_top(8); - wired_header.set_margin_bottom(4); - wired_header.set_margin_start(12); - list_container.append(&wired_header); - - let wired_list = wired_devices::wired_devices_view( - ctx.clone(), - &available_devices, - ctx.wired_details_page.clone(), - ); - wired_list.add_css_class("wired-devices-list"); - list_container.append(&wired_list); - - let separator = gtk::Separator::new(Orientation::Horizontal); - separator.add_css_class("device-separator"); - separator.set_margin_top(12); - separator.set_margin_bottom(12); - list_container.append(&separator); - } - } - Err(e) => { - eprintln!("Failed to list wired devices: {}", e); - } - } - - let wireless_header = Label::new(Some("Wireless")); - wireless_header.add_css_class("section-header"); - wireless_header.add_css_class("wireless-section-header"); - wireless_header.set_halign(Align::Start); - wireless_header.set_margin_top(8); - wireless_header.set_margin_bottom(4); - wireless_header.set_margin_start(12); - list_container.append(&wireless_header); - - if let Err(err) = ctx.nm.scan_networks(None).await { - ctx.status.set_text(&format!("Scan failed: {err}")); - is_scanning.set(false); - return; - } - - let mut last_len = 0; - for _ in 0..5 { - let nets = ctx.nm.list_networks(None).await.unwrap_or_default(); - if nets.len() == last_len && last_len > 0 { - break; - } - last_len = nets.len(); - glib::timeout_future_seconds(1).await; - } - - let saved_ssids = saved_network_ids(&ctx).await; - - match ctx.nm.list_networks(None).await { - Ok(mut nets) => { - let current_conn = ctx.nm.current_connection_info().await; - let (current_ssid, current_band) = if let Some((ssid, freq)) = current_conn { - let ssid_str = ssid.clone(); - let band: Option = freq - .and_then(crate::ui::freq_to_band) - .map(|s| s.to_string()); - (Some(ssid_str), band) - } else { - (None, None) - }; - - nets.sort_by_key(|b| std::cmp::Reverse(b.strength.unwrap_or(0))); - - let mut seen_combinations = HashSet::new(); - nets.retain(|net| { - let band = net.frequency.and_then(crate::ui::freq_to_band); - let key = (net.ssid.clone(), band); - seen_combinations.insert(key) - }); - - ctx.status.set_text(""); - - let list: ListBox = networks::networks_view( - ctx.clone(), - &nets, - current_ssid.as_deref(), - current_band.as_deref(), - &saved_ssids, - ); - list_container.append(&list); - ctx.stack.set_visible_child_name("networks"); - } - Err(err) => ctx - .status - .set_text(&format!("Error fetching networks: {err}")), - } - - apply_connectivity_status(&ctx).await; - - is_scanning.set(false); -} - -pub fn clear_children(container: >k::Box) { - let mut child = container.first_child(); - while let Some(widget) = child { - child = widget.next_sibling(); - container.remove(&widget); - } -} - -async fn saved_network_ids(ctx: &NetworksContext) -> HashSet { - ctx.nm - .list_saved_connections_brief() - .await - .unwrap_or_default() - .into_iter() - .filter(|c| c.connection_type == "802-11-wireless") - .map(|c| c.id) - .collect() -} - -/// Refresh the network list WITHOUT triggering a new scan. -/// This is useful for live updates when the network list changes -/// (e.g., wired device state changes, AP added/removed). -pub async fn refresh_networks_no_scan( - ctx: Rc, - list_container: &GtkBox, - is_scanning: &Rc>, -) { - if is_scanning.get() { - return; - } - - is_scanning.set(true); - - clear_children(list_container); - - if let Ok(wired_devices) = ctx.nm.list_wired_devices().await { - let available_devices: Vec<_> = wired_devices - .into_iter() - .filter(|dev| { - matches!( - dev.state, - models::DeviceState::Activated - | models::DeviceState::Disconnected - | models::DeviceState::Prepare - | models::DeviceState::Config - | models::DeviceState::Unmanaged - ) - }) - .collect(); - - if !available_devices.is_empty() { - let wired_header = Label::new(Some("Wired")); - wired_header.add_css_class("section-header"); - wired_header.add_css_class("wired-section-header"); - wired_header.set_halign(Align::Start); - wired_header.set_margin_top(8); - wired_header.set_margin_bottom(4); - wired_header.set_margin_start(12); - list_container.append(&wired_header); - - let wired_list = wired_devices::wired_devices_view( - ctx.clone(), - &available_devices, - ctx.wired_details_page.clone(), - ); - wired_list.add_css_class("wired-devices-list"); - list_container.append(&wired_list); - - let separator = gtk::Separator::new(Orientation::Horizontal); - separator.add_css_class("device-separator"); - separator.set_margin_top(12); - separator.set_margin_bottom(12); - list_container.append(&separator); - } - } - - let wireless_header = Label::new(Some("Wireless")); - wireless_header.add_css_class("section-header"); - wireless_header.add_css_class("wireless-section-header"); - wireless_header.set_halign(Align::Start); - wireless_header.set_margin_top(8); - wireless_header.set_margin_bottom(4); - wireless_header.set_margin_start(12); - list_container.append(&wireless_header); - - let saved_ssids = saved_network_ids(&ctx).await; - - match ctx.nm.list_networks(None).await { - Ok(mut nets) => { - let current_conn = ctx.nm.current_connection_info().await; - let (current_ssid, current_band) = if let Some((ssid, freq)) = current_conn { - let ssid_str = ssid.clone(); - let band: Option = freq - .and_then(crate::ui::freq_to_band) - .map(|s| s.to_string()); - (Some(ssid_str), band) - } else { - (None, None) - }; - - nets.sort_by_key(|b| std::cmp::Reverse(b.strength.unwrap_or(0))); - - let mut seen_combinations = HashSet::new(); - nets.retain(|net| { - let band = net.frequency.and_then(crate::ui::freq_to_band); - let key = (net.ssid.clone(), band); - seen_combinations.insert(key) - }); - - let list: ListBox = networks::networks_view( - ctx.clone(), - &nets, - current_ssid.as_deref(), - current_band.as_deref(), - &saved_ssids, - ); - list_container.append(&list); - ctx.stack.set_visible_child_name("networks"); - } - Err(err) => { - ctx.status - .set_text(&format!("Error fetching networks: {err}")); - } - } - - apply_connectivity_status(&ctx).await; - - is_scanning.set(false); -} diff --git a/nmrs-gui/src/ui/mod.rs b/nmrs-gui/src/ui/mod.rs deleted file mode 100644 index dd5da190..00000000 --- a/nmrs-gui/src/ui/mod.rs +++ /dev/null @@ -1,258 +0,0 @@ -pub mod connect; -pub mod header; -pub mod network_page; -pub mod networks; -pub mod settings_page; -pub mod wired_devices; -pub mod wired_page; - -use gtk::prelude::*; -use gtk::{ - Application, ApplicationWindow, Box as GtkBox, Label, Orientation, ScrolledWindow, Spinner, - Stack, pango::EllipsizeMode, -}; -use std::cell::Cell; -use std::rc::Rc; -use std::sync::Arc; -use tokio::sync::Notify; - -type Callback = Rc; -type CallbackCell = Rc>>; - -pub fn freq_to_band(freq: u32) -> Option<&'static str> { - match freq { - 2400..=2500 => Some("2.4GHz"), - 5150..=5925 => Some("5GHz"), - 5926..=7125 => Some("6GHz"), - _ => None, - } -} - -pub fn build_ui(app: &Application) { - let win = ApplicationWindow::new(app); - win.set_title(Some("")); - win.set_default_size(100, 600); - win.add_css_class("dark-theme"); - - let vbox = GtkBox::new(Orientation::Vertical, 0); - let status = Label::new(None); - status.set_xalign(0.0); - status.set_ellipsize(EllipsizeMode::End); - status.set_max_width_chars(36); - let list_container = GtkBox::new(Orientation::Vertical, 0); - let stack = Stack::new(); - let is_scanning = Rc::new(Cell::new(false)); - - let spinner = Spinner::new(); - spinner.set_halign(gtk::Align::Center); - spinner.set_valign(gtk::Align::Center); - spinner.set_property("width-request", 24i32); - spinner.set_property("height-request", 24i32); - spinner.add_css_class("loading-spinner"); - spinner.start(); - - stack.add_named(&spinner, Some("loading")); - stack.set_visible_child_name("loading"); - - let status_clone = status.clone(); - let list_container_clone = list_container.clone(); - let stack_clone = stack.clone(); - let win_clone = win.clone(); - let is_scanning_clone = is_scanning.clone(); - let vbox_clone = vbox.clone(); - - glib::MainContext::default().spawn_local(async move { - match nmrs::NetworkManager::new().await { - Ok(nm) => { - let nm = Rc::new(nm); - - let details_page = Rc::new(network_page::NetworkPage::new(&stack_clone)); - let details_scroller = ScrolledWindow::new(); - details_scroller.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); - details_scroller.set_child(Some(details_page.widget())); - stack_clone.add_named(&details_scroller, Some("details")); - - let wired_details_page = Rc::new(wired_page::WiredPage::new(&stack_clone)); - let wired_details_scroller = ScrolledWindow::new(); - wired_details_scroller - .set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); - wired_details_scroller.set_child(Some(wired_details_page.widget())); - stack_clone.add_named(&wired_details_scroller, Some("wired-details")); - - let settings = settings_page::SettingsPage::new(&stack_clone, &win_clone); - let settings_scroller = ScrolledWindow::new(); - settings_scroller.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); - settings_scroller.set_child(Some(settings.widget())); - stack_clone.add_named(&settings_scroller, Some("settings")); - - let on_success: Rc = { - let list_container = list_container_clone.clone(); - let is_scanning = is_scanning_clone.clone(); - let nm = nm.clone(); - let status = status_clone.clone(); - let stack = stack_clone.clone(); - let parent_window = win_clone.clone(); - let details_page = details_page.clone(); - let wired_details_page = wired_details_page.clone(); - - let on_success_cell: CallbackCell = Rc::new(std::cell::RefCell::new(None)); - let on_success_cell_clone = on_success_cell.clone(); - - let callback = Rc::new(move || { - let list_container = list_container.clone(); - let is_scanning = is_scanning.clone(); - let nm = nm.clone(); - let status = status.clone(); - let stack = stack.clone(); - let parent_window = parent_window.clone(); - let on_success_cell = on_success_cell.clone(); - let details_page = details_page.clone(); - let wired_details_page = wired_details_page.clone(); - - glib::MainContext::default().spawn_local(async move { - let callback = on_success_cell.borrow().as_ref().map(|cb| cb.clone()); - let refresh_ctx = Rc::new(networks::NetworksContext { - nm, - on_success: callback.unwrap_or_else(|| Rc::new(|| {})), - status, - stack, - parent_window, - details_page: details_page.clone(), - wired_details_page: wired_details_page.clone(), - }); - header::refresh_networks(refresh_ctx, &list_container, &is_scanning) - .await; - }); - }) as Rc; - - *on_success_cell_clone.borrow_mut() = Some(callback.clone()); - - callback - }; - - let ctx = Rc::new(networks::NetworksContext { - nm: nm.clone(), - on_success: on_success.clone(), - status: status_clone.clone(), - stack: stack_clone.clone(), - parent_window: win_clone.clone(), - details_page: details_page.clone(), - wired_details_page, - }); - - details_page.set_on_success(on_success); - - let header = header::build_header( - ctx.clone(), - &list_container_clone, - is_scanning_clone.clone(), - ); - vbox_clone.prepend(&header); - - { - let nm_device_monitor = nm.clone(); - let device_notify = Arc::new(Notify::new()); - - let notify_clone = device_notify.clone(); - glib::MainContext::default().spawn_local(async move { - loop { - let notify = notify_clone.clone(); - let result = nm_device_monitor - .monitor_device_changes(move || { - notify.notify_one(); - }) - .await; - - if let Err(e) = result { - eprintln!("Device monitoring error: {}, restarting in 5s...", e) - } - glib::timeout_future_seconds(5).await; - } - }); - - let list_container_device = list_container_clone.clone(); - let is_scanning_device = is_scanning_clone.clone(); - let ctx_device = ctx.clone(); - glib::MainContext::default().spawn_local(async move { - loop { - device_notify.notified().await; - glib::timeout_future_seconds(3).await; - - let current_page = ctx_device.stack.visible_child_name(); - let on_networks_page = current_page.as_deref() == Some("networks"); - - if !is_scanning_device.get() && on_networks_page { - header::refresh_networks_no_scan( - ctx_device.clone(), - &list_container_device, - &is_scanning_device, - ) - .await; - } - } - }); - } - - { - let nm_network_monitor = nm.clone(); - let network_notify = Arc::new(Notify::new()); - - let notify_clone = network_notify.clone(); - glib::MainContext::default().spawn_local(async move { - loop { - let notify = notify_clone.clone(); - let result = nm_network_monitor - .monitor_network_changes(move || { - notify.notify_one(); - }) - .await; - - if let Err(e) = result { - eprintln!("Network monitoring error: {}, restarting in 5s...", e) - } - glib::timeout_future_seconds(5).await; - } - }); - - let list_container_network = list_container_clone.clone(); - let is_scanning_network = is_scanning_clone.clone(); - let ctx_network = ctx.clone(); - glib::MainContext::default().spawn_local(async move { - loop { - network_notify.notified().await; - glib::timeout_future_seconds(8).await; - - let current_page = ctx_network.stack.visible_child_name(); - let on_networks_page = current_page.as_deref() == Some("networks"); - - if !is_scanning_network.get() && on_networks_page { - header::refresh_networks_no_scan( - ctx_network.clone(), - &list_container_network, - &is_scanning_network, - ) - .await; - } - } - }); - } - } - Err(err) => { - status_clone.set_text(&format!("Failed to initialize: {err}")); - } - } - }); - - let networks_scroller = ScrolledWindow::new(); - networks_scroller.set_vexpand(true); - networks_scroller.set_policy(gtk::PolicyType::Never, gtk::PolicyType::Automatic); - networks_scroller.set_child(Some(&list_container)); - - stack.add_named(&networks_scroller, Some("networks")); - - stack.set_vexpand(true); - vbox.append(&stack); - - win.set_child(Some(&vbox)); - win.show(); -} diff --git a/nmrs-gui/src/ui/network_page.rs b/nmrs-gui/src/ui/network_page.rs deleted file mode 100644 index 0dccb3f7..00000000 --- a/nmrs-gui/src/ui/network_page.rs +++ /dev/null @@ -1,243 +0,0 @@ -use glib::clone; -use gtk::prelude::*; -use gtk::{Align, Box, Button, Image, Label, Orientation}; -use nmrs::AccessPoint; -use nmrs::NetworkManager; -use nmrs::models::NetworkInfo; -use std::cell::RefCell; -use std::rc::Rc; - -type OnSuccessCallback = Rc>>>; - -pub struct NetworkPage { - root: gtk::Box, - - title: gtk::Label, - status: gtk::Label, - strength: gtk::Label, - bars: gtk::Label, - - bssid: gtk::Label, - freq: gtk::Label, - channel: gtk::Label, - mode: gtk::Label, - rate: gtk::Label, - security: gtk::Label, - - interface: gtk::Label, - device_state: gtk::Label, - last_seen: gtk::Label, - - current_ssid: Rc>, - on_success: OnSuccessCallback, -} - -impl NetworkPage { - pub fn new(stack: >k::Stack) -> Self { - let root = Box::new(Orientation::Vertical, 12); - root.add_css_class("network-page"); - - let back = Button::with_label("← Back"); - back.add_css_class("back-button"); - back.set_halign(Align::Start); - back.set_cursor_from_name(Some("pointer")); - back.connect_clicked(clone![ - #[weak] - stack, - move |_| { - stack.set_visible_child_name("networks"); - } - ]); - root.append(&back); - - let header = Box::new(Orientation::Horizontal, 6); - let icon = Image::from_icon_name("network-wireless-signal-excellent-symbolic"); - icon.set_pixel_size(24); - - let title = Label::new(None); - title.add_css_class("network-title"); - - let spacer = Box::new(Orientation::Horizontal, 0); - spacer.set_hexpand(true); - - let forget_btn = Button::with_label("Forget"); - forget_btn.add_css_class("forget-button"); - forget_btn.set_halign(Align::End); - forget_btn.set_valign(Align::Center); - forget_btn.set_cursor_from_name(Some("pointer")); - - let current_ssid = Rc::new(RefCell::new(String::new())); - let on_success_callback: OnSuccessCallback = Rc::new(RefCell::new(None)); - - { - let stack_clone = stack.clone(); - let current_ssid_clone = current_ssid.clone(); - let on_success_clone = on_success_callback.clone(); - - forget_btn.connect_clicked(move |_| { - let stack = stack_clone.clone(); - let ssid = current_ssid_clone.borrow().clone(); - let on_success = on_success_clone.clone(); - - glib::MainContext::default().spawn_local(async move { - if let Ok(nm) = NetworkManager::new().await - && nm.forget(&ssid).await.is_ok() - { - stack.set_visible_child_name("networks"); - if let Some(callback) = on_success.borrow().as_ref() { - callback(); - } - } - }); - }); - } - - header.append(&icon); - header.append(&title); - header.append(&spacer); - header.append(&forget_btn); - root.append(&header); - - let basic_box = Box::new(Orientation::Vertical, 6); - basic_box.add_css_class("basic-section"); - - let basic_header = Label::new(Some("Basic")); - basic_header.add_css_class("section-header"); - basic_box.append(&basic_header); - - let status = Label::new(None); - let strength = Label::new(None); - let bars = Label::new(None); - - Self::add_row(&basic_box, "Connection Status", &status); - Self::add_row(&basic_box, "Signal Strength", &strength); - Self::add_row(&basic_box, "Bars", &bars); - - root.append(&basic_box); - - let advanced_box = Box::new(Orientation::Vertical, 8); - advanced_box.add_css_class("advanced-section"); - - let advanced_header = Label::new(Some("Advanced")); - advanced_header.add_css_class("section-header"); - advanced_box.append(&advanced_header); - - let bssid = Label::new(None); - let freq = Label::new(None); - let channel = Label::new(None); - let mode = Label::new(None); - let rate = Label::new(None); - let security = Label::new(None); - let interface = Label::new(None); - let device_state = Label::new(None); - let last_seen = Label::new(None); - - Self::add_row(&advanced_box, "BSSID", &bssid); - Self::add_row(&advanced_box, "Frequency", &freq); - Self::add_row(&advanced_box, "Channel", &channel); - Self::add_row(&advanced_box, "Mode", &mode); - Self::add_row(&advanced_box, "Speed", &rate); - Self::add_row(&advanced_box, "Security", &security); - Self::add_row(&advanced_box, "Interface", &interface); - Self::add_row(&advanced_box, "Device State", &device_state); - Self::add_row(&advanced_box, "Last Seen", &last_seen); - - root.append(&advanced_box); - - Self { - root, - title, - status, - strength, - bars, - - bssid, - freq, - channel, - mode, - rate, - security, - interface, - device_state, - last_seen, - current_ssid, - on_success: on_success_callback, - } - } - - pub fn set_on_success(&self, callback: Rc) { - *self.on_success.borrow_mut() = Some(callback); - } - - fn add_row(parent: >k::Box, key_text: &str, val_widget: >k::Label) { - let row = Box::new(Orientation::Vertical, 3); - row.set_halign(Align::Start); - - let key = Label::new(Some(key_text)); - key.add_css_class("info-label"); - key.set_halign(Align::Start); - - val_widget.add_css_class("info-value"); - val_widget.set_halign(Align::Start); - - row.append(&key); - row.append(val_widget); - parent.append(&row); - } - - pub fn update(&self, info: &NetworkInfo) { - self.current_ssid.replace(info.ssid.clone()); - self.title.set_text(&info.ssid); - self.status.set_text(&info.status); - self.strength.set_text(&format!("{}%", info.strength)); - self.bars.set_text(&info.bars); - - self.bssid.set_text(&info.bssid); - self.freq.set_text( - &info - .freq - .map(|f| format!("{:.1} GHz", f as f32 / 1000.0)) - .unwrap_or_else(|| "-".into()), - ); - self.channel.set_text( - &info - .channel - .map(|c| c.to_string()) - .unwrap_or_else(|| "-".into()), - ); - self.mode.set_text(&info.mode); - self.rate.set_text( - &info - .rate_mbps - .map(|r| format!("{r:.2} Mbps")) - .unwrap_or_else(|| "-".into()), - ); - self.security.set_text(&info.security); - - self.interface.set_text("-"); - self.device_state.set_text("-"); - self.last_seen.set_text("-"); - } - - pub fn enrich_with_ap(&self, ap: &AccessPoint) { - self.bssid.set_text(&ap.bssid); - self.interface.set_text(&ap.interface); - self.device_state - .set_text(&format!("{:?}", ap.device_state)); - self.last_seen.set_text( - &ap.last_seen_secs - .map(|s| format!("{s}s ago")) - .unwrap_or_else(|| "-".into()), - ); - self.freq - .set_text(&format!("{:.1} GHz", ap.frequency_mhz as f32 / 1000.0)); - if ap.max_bitrate_kbps > 0 { - self.rate - .set_text(&format!("{:.1} Mbps", ap.max_bitrate_kbps as f32 / 1000.0)); - } - } - - pub fn widget(&self) -> >k::Box { - &self.root - } -} diff --git a/nmrs-gui/src/ui/networks.rs b/nmrs-gui/src/ui/networks.rs deleted file mode 100644 index 7e29d9f6..00000000 --- a/nmrs-gui/src/ui/networks.rs +++ /dev/null @@ -1,315 +0,0 @@ -use anyhow::Result; -use gtk::Align; -use gtk::GestureClick; -use gtk::prelude::*; -use gtk::{Box, Image, Label, ListBox, ListBoxRow, Orientation}; -use nmrs::models::WifiSecurity; -use nmrs::{NetworkManager, models}; -use std::collections::HashSet; -use std::rc::Rc; - -use crate::ui::connect; -use crate::ui::network_page::NetworkPage; - -pub struct NetworkRowController { - pub row: gtk::ListBoxRow, - pub arrow: gtk::Image, - pub ctx: Rc, - pub net: models::Network, - pub details_page: Rc, -} - -pub struct NetworksContext { - pub nm: Rc, - pub on_success: Rc, - pub status: Label, - pub stack: gtk::Stack, - pub parent_window: gtk::ApplicationWindow, - pub details_page: Rc, - pub wired_details_page: Rc, -} - -impl NetworksContext { - pub async fn new( - on_success: Rc, - status: &Label, - stack: >k::Stack, - parent_window: >k::ApplicationWindow, - details_page: Rc, - wired_details_page: Rc, - ) -> Result { - let nm = Rc::new(NetworkManager::new().await?); - - Ok(Self { - nm, - on_success, - status: status.clone(), - stack: stack.clone(), - parent_window: parent_window.clone(), - details_page, - wired_details_page, - }) - } -} - -impl NetworkRowController { - pub fn new( - row: gtk::ListBoxRow, - arrow: gtk::Image, - ctx: Rc, - net: models::Network, - details_page: Rc, - ) -> Self { - Self { - row, - arrow, - ctx, - net, - details_page, - } - } - - pub fn attach(&self) { - self.attach_arrow(); - self.attach_row_double(); - } - - fn attach_arrow(&self) { - let click = GestureClick::new(); - - let ctx = self.ctx.clone(); - let net = self.net.clone(); - let stack = self.ctx.stack.clone(); - let page = self.details_page.clone(); - - click.connect_pressed(move |_, _, _, _| { - let ctx_c = ctx.clone(); - let net_c = net.clone(); - let stack_c = stack.clone(); - let page_c = page.clone(); - - glib::MainContext::default().spawn_local(async move { - if let Ok(info) = ctx_c.nm.show_details(&net_c).await { - page_c.update(&info); - - if let Ok(aps) = ctx_c.nm.list_access_points(None).await { - let best = aps - .iter() - .filter(|ap| ap.ssid == net_c.ssid) - .max_by_key(|ap| ap.strength); - if let Some(ap) = best { - page_c.enrich_with_ap(ap); - } - } - - stack_c.set_visible_child_name("details"); - } - }); - }); - - self.arrow.add_controller(click); - } - - fn attach_row_double(&self) { - let click = GestureClick::new(); - - let ctx = self.ctx.clone(); - let net = self.net.clone(); - let ssid = net.ssid.clone(); - let secured = net.secured; - let is_eap = net.is_eap; - - let status = ctx.status.clone(); - let window = ctx.parent_window.clone(); - let on_success = ctx.on_success.clone(); - - click.connect_pressed(move |_, n, _, _| { - if n != 2 { - return; - } - - status.set_text(&format!("Connecting to {ssid}...")); - - let ssid_c = ssid.clone(); - let nm_c = ctx.nm.clone(); - let status_c = status.clone(); - let window_c = window.clone(); - let on_success_c = on_success.clone(); - - glib::MainContext::default().spawn_local(async move { - if secured { - let have = nm_c.has_saved_connection(&ssid_c).await.unwrap_or(false); - - if have { - status_c.set_text(&format!("Connecting to {}...", ssid_c)); - window_c.set_sensitive(false); - let creds = WifiSecurity::WpaPsk { psk: "".into() }; - match nm_c.connect(&ssid_c, None, creds).await { - Ok(_) => { - status_c.set_text(""); - on_success_c(); - } - Err(e) => status_c.set_text(&format!("Failed to connect: {e}")), - } - window_c.set_sensitive(true); - } else { - connect::connect_modal( - nm_c.clone(), - &window_c, - &ssid_c, - is_eap, - on_success_c.clone(), - ); - } - } else { - status_c.set_text(&format!("Connecting to {}...", ssid_c)); - window_c.set_sensitive(false); - let creds = WifiSecurity::Open; - match nm_c.connect(&ssid_c, None, creds).await { - Ok(_) => { - status_c.set_text(""); - on_success_c(); - } - Err(e) => status_c.set_text(&format!("Failed to connect: {e}")), - } - window_c.set_sensitive(true); - } - - status_c.set_text(""); - }); - }); - - self.row.add_controller(click); - } -} - -pub fn networks_view( - ctx: Rc, - networks: &[models::Network], - current_ssid: Option<&str>, - current_band: Option<&str>, - saved_ssids: &HashSet, -) -> ListBox { - let conn_threshold = 75; - let list = ListBox::new(); - - let mut sorted_networks: Vec<_> = networks - .iter() - .filter(|net| !net.ssid.trim().is_empty()) - .cloned() - .collect(); - - sorted_networks.sort_by(|a, b| { - let a_connected = is_current_network(a, current_ssid, current_band); - let b_connected = is_current_network(b, current_ssid, current_band); - - match (a_connected, b_connected) { - (true, false) => std::cmp::Ordering::Less, - (false, true) => std::cmp::Ordering::Greater, - _ => b.strength.unwrap_or(0).cmp(&a.strength.unwrap_or(0)), - } - }); - - for net in sorted_networks { - let row = ListBoxRow::new(); - let hbox = Box::new(Orientation::Horizontal, 6); - - row.add_css_class("network-selection"); - - if is_current_network(&net, current_ssid, current_band) { - row.add_css_class("connected"); - } - - let display_name = match net.frequency.and_then(crate::ui::freq_to_band) { - Some(band) => format!("{} ({band})", net.ssid), - None => net.ssid.clone(), - }; - - hbox.append(&Label::new(Some(&display_name))); - - if is_current_network(&net, current_ssid, current_band) { - let connected_label = Label::new(Some("Connected")); - connected_label.add_css_class("connected-label"); - hbox.append(&connected_label); - } else if saved_ssids.contains(&net.ssid) { - let saved_label = Label::new(Some("Saved")); - saved_label.add_css_class("saved-label"); - hbox.append(&saved_label); - } - - let spacer = Box::new(Orientation::Horizontal, 0); - spacer.set_hexpand(true); - hbox.append(&spacer); - - if let Some(s) = net.strength { - let icon_name = if net.secured { - "network-wireless-encrypted-symbolic" - } else { - "network-wireless-signal-excellent-symbolic" - }; - - let image = Image::from_icon_name(icon_name); - if net.secured { - image.add_css_class("wifi-secure"); - } else { - image.add_css_class("wifi-open"); - } - - let strength_label = Label::new(Some(&format!("{s}%"))); - hbox.append(&image); - hbox.append(&strength_label); - - if s >= conn_threshold { - strength_label.add_css_class("network-good"); - } else if s > 65 { - strength_label.add_css_class("network-okay"); - } else { - strength_label.add_css_class("network-poor"); - } - } - - let arrow = Image::from_icon_name("go-next-symbolic"); - arrow.set_halign(Align::End); - arrow.add_css_class("network-arrow"); - arrow.set_cursor_from_name(Some("pointer")); - hbox.append(&arrow); - - row.set_child(Some(&hbox)); - - let controller = NetworkRowController::new( - row.clone(), - arrow.clone(), - ctx.clone(), - net.clone(), - ctx.details_page.clone(), - ); - - controller.attach(); - - list.append(&row); - } - list -} - -fn is_current_network( - net: &models::Network, - current_ssid: Option<&str>, - current_band: Option<&str>, -) -> bool { - let ssid = match current_ssid { - Some(s) => s, - None => return false, - }; - - if net.ssid != ssid { - return false; - } - - if let Some(band) = current_band { - let net_band = net.frequency.and_then(crate::ui::freq_to_band); - - return net_band == Some(band); - } - - true -} diff --git a/nmrs-gui/src/ui/settings_page.rs b/nmrs-gui/src/ui/settings_page.rs deleted file mode 100644 index 98478298..00000000 --- a/nmrs-gui/src/ui/settings_page.rs +++ /dev/null @@ -1,156 +0,0 @@ -use gtk::prelude::*; -use gtk::{Align, Box, Button, Label, Orientation}; - -use crate::ui::header::THEMES; - -const CUSTOM_INDEX: u32 = 0; - -pub struct SettingsPage { - root: gtk::Box, -} - -impl SettingsPage { - pub fn new(stack: >k::Stack, window: >k::ApplicationWindow) -> Self { - let root = Box::new(Orientation::Vertical, 12); - root.add_css_class("settings-page"); - root.set_margin_top(12); - root.set_margin_bottom(12); - root.set_margin_start(16); - root.set_margin_end(16); - - let back = Button::with_label("← Back"); - back.add_css_class("back-button"); - back.set_halign(Align::Start); - back.set_cursor_from_name(Some("pointer")); - { - let stack = stack.clone(); - back.connect_clicked(move |_| { - stack.set_visible_child_name("networks"); - }); - } - root.append(&back); - - let title = Label::new(Some("Settings")); - title.add_css_class("section-header"); - title.set_halign(Align::Start); - root.append(&title); - - Self::build_theme_section(&root); - Self::build_light_dark_section(&root, window); - - Self { root } - } - - fn build_theme_section(root: >k::Box) { - let section = Box::new(Orientation::Vertical, 6); - - let label = Label::new(Some("Theme")); - label.add_css_class("info-label"); - label.set_halign(Align::Start); - section.append(&label); - - let hint = Label::new(Some( - "Your overrides in ~/.config/nmrs/style.css are preserved", - )); - hint.add_css_class("info-value"); - hint.set_halign(Align::Start); - hint.set_opacity(0.6); - section.append(&hint); - - let mut names: Vec<&str> = vec!["Custom"]; - names.extend(THEMES.iter().map(|t| t.name)); - let dropdown = gtk::DropDown::from_strings(&names); - dropdown.set_halign(Align::Start); - dropdown.set_hexpand(false); - - if let Some(saved) = crate::theme_config::load_theme() { - if let Some(idx) = THEMES.iter().position(|t| t.key == saved.as_str()) { - dropdown.set_selected(idx as u32 + 1); - } else { - dropdown.set_selected(CUSTOM_INDEX); - } - } else { - dropdown.set_selected(CUSTOM_INDEX); - } - - dropdown.connect_selected_notify(move |dd| { - let idx = dd.selected(); - - if idx == CUSTOM_INDEX { - crate::style::switch_to_custom(); - crate::theme_config::save_theme("custom"); - return; - } - - let theme_idx = (idx - 1) as usize; - if theme_idx >= THEMES.len() { - return; - } - - let theme = &THEMES[theme_idx]; - crate::style::switch_to_theme(theme.css); - crate::theme_config::save_theme(theme.key); - }); - - section.append(&dropdown); - root.append(§ion); - } - - fn build_light_dark_section(root: >k::Box, window: >k::ApplicationWindow) { - let section = Box::new(Orientation::Vertical, 6); - - let label = Label::new(Some("Appearance")); - label.add_css_class("info-label"); - label.set_halign(Align::Start); - section.append(&label); - - let toggle_box = Box::new(Orientation::Horizontal, 8); - - let light_btn = Button::with_label("Light"); - light_btn.add_css_class("appearance-btn"); - - let dark_btn = Button::with_label("Dark"); - dark_btn.add_css_class("appearance-btn"); - - { - let window_weak = window.downgrade(); - let dark_btn_clone = dark_btn.clone(); - light_btn.connect_clicked(move |btn| { - if let Some(window) = window_weak.upgrade() { - window.remove_css_class("dark-theme"); - window.add_css_class("light-theme"); - btn.add_css_class("appearance-active"); - dark_btn_clone.remove_css_class("appearance-active"); - } - }); - } - - { - let window_weak = window.downgrade(); - let light_btn_clone = light_btn.clone(); - dark_btn.connect_clicked(move |btn| { - if let Some(window) = window_weak.upgrade() { - window.remove_css_class("light-theme"); - window.add_css_class("dark-theme"); - btn.add_css_class("appearance-active"); - light_btn_clone.remove_css_class("appearance-active"); - } - }); - } - - if window.has_css_class("light-theme") { - light_btn.add_css_class("appearance-active"); - } else { - dark_btn.add_css_class("appearance-active"); - } - - toggle_box.append(&light_btn); - toggle_box.append(&dark_btn); - section.append(&toggle_box); - root.append(§ion); - } - - pub fn widget(&self) -> >k::Box { - &self.root - } -} diff --git a/nmrs-gui/src/ui/wired_devices.rs b/nmrs-gui/src/ui/wired_devices.rs deleted file mode 100644 index 3546e429..00000000 --- a/nmrs-gui/src/ui/wired_devices.rs +++ /dev/null @@ -1,180 +0,0 @@ -use gtk::Align; -use gtk::GestureClick; -use gtk::prelude::*; -use gtk::{Box, Image, Label, ListBox, ListBoxRow, Orientation}; -use nmrs::models; -use std::rc::Rc; - -use crate::ui::networks::NetworksContext; -use crate::ui::wired_page::WiredPage; - -pub struct WiredDeviceRowController { - pub row: gtk::ListBoxRow, - pub arrow: gtk::Image, - pub ctx: Rc, - pub device: models::Device, - pub details_page: Rc, -} - -impl WiredDeviceRowController { - pub fn new( - row: gtk::ListBoxRow, - arrow: gtk::Image, - ctx: Rc, - device: models::Device, - details_page: Rc, - ) -> Self { - Self { - row, - arrow, - ctx, - device, - details_page, - } - } - - pub fn attach(&self) { - self.attach_arrow(); - self.attach_row_double(); - } - - fn attach_arrow(&self) { - let click = GestureClick::new(); - - let device = self.device.clone(); - let stack = self.ctx.stack.clone(); - let page = self.details_page.clone(); - - click.connect_pressed(move |_, _, _, _| { - let device_c = device.clone(); - let stack_c = stack.clone(); - let page_c = page.clone(); - - glib::MainContext::default().spawn_local(async move { - page_c.update(&device_c); - stack_c.set_visible_child_name("wired-details"); - }); - }); - - self.arrow.add_controller(click); - } - - fn attach_row_double(&self) { - let click = GestureClick::new(); - - let ctx = self.ctx.clone(); - let device = self.device.clone(); - let interface = device.interface.clone(); - - let status = ctx.status.clone(); - let window = ctx.parent_window.clone(); - let on_success = ctx.on_success.clone(); - - click.connect_pressed(move |_, n, _, _| { - if n != 2 { - return; - } - - status.set_text(&format!("Connecting to {interface}...")); - - let nm_c = ctx.nm.clone(); - let status_c = status.clone(); - let window_c = window.clone(); - let on_success_c = on_success.clone(); - - glib::MainContext::default().spawn_local(async move { - window_c.set_sensitive(false); - match nm_c.connect_wired().await { - Ok(_) => { - status_c.set_text(""); - on_success_c(); - } - Err(e) => status_c.set_text(&format!("Failed to connect: {e}")), - } - window_c.set_sensitive(true); - status_c.set_text(""); - }); - }); - - self.row.add_controller(click); - } -} - -pub fn wired_devices_view( - ctx: Rc, - devices: &[models::Device], - details_page: Rc, -) -> ListBox { - let list = ListBox::new(); - - for device in devices { - let row = ListBoxRow::new(); - let hbox = Box::new(Orientation::Horizontal, 6); - - row.add_css_class("network-selection"); - - if device.state == models::DeviceState::Activated { - row.add_css_class("connected"); - } - - let display_name = format!("{} ({})", device.interface, device.device_type); - hbox.append(&Label::new(Some(&display_name))); - - if device.state == models::DeviceState::Activated { - let connected_label = Label::new(Some("Connected")); - connected_label.add_css_class("connected-label"); - hbox.append(&connected_label); - } - - let spacer = Box::new(Orientation::Horizontal, 0); - spacer.set_hexpand(true); - hbox.append(&spacer); - - // Only show state for meaningful states (not transitional ones) - let state_text = match device.state { - models::DeviceState::Activated => Some("Connected"), - models::DeviceState::Disconnected => Some("Disconnected"), - models::DeviceState::Unavailable => Some("Unavailable"), - models::DeviceState::Failed => Some("Failed"), - // Hide transitional states (Unmanaged, Prepare, Config, etc) - _ => None, - }; - - if let Some(text) = state_text { - let state_label = Label::new(Some(text)); - state_label.add_css_class(match device.state { - models::DeviceState::Activated => "network-good", - models::DeviceState::Unavailable - | models::DeviceState::Disconnected - | models::DeviceState::Failed => "network-poor", - _ => "network-okay", - }); - hbox.append(&state_label); - } - - let icon = Image::from_icon_name("network-wired-symbolic"); - icon.add_css_class("wired-icon"); - hbox.append(&icon); - - let arrow = Image::from_icon_name("go-next-symbolic"); - arrow.set_halign(Align::End); - arrow.add_css_class("network-arrow"); - arrow.set_cursor_from_name(Some("pointer")); - hbox.append(&arrow); - - row.set_child(Some(&hbox)); - - let controller = WiredDeviceRowController::new( - row.clone(), - arrow.clone(), - ctx.clone(), - device.clone(), - details_page.clone(), - ); - - controller.attach(); - - list.append(&row); - } - list -} diff --git a/nmrs-gui/src/ui/wired_page.rs b/nmrs-gui/src/ui/wired_page.rs deleted file mode 100644 index 92b6bbff..00000000 --- a/nmrs-gui/src/ui/wired_page.rs +++ /dev/null @@ -1,136 +0,0 @@ -use glib::clone; -use gtk::prelude::*; -use gtk::{Align, Box, Button, Image, Label, Orientation}; -use nmrs::models::Device; - -pub struct WiredPage { - root: gtk::Box, - - title: gtk::Label, - state_label: gtk::Label, - - interface: gtk::Label, - device_type: gtk::Label, - mac_address: gtk::Label, - driver: gtk::Label, - managed: gtk::Label, -} - -impl WiredPage { - pub fn new(stack: >k::Stack) -> Self { - let root = Box::new(Orientation::Vertical, 12); - root.add_css_class("network-page"); - - let back = Button::with_label("← Back"); - back.add_css_class("back-button"); - back.set_halign(Align::Start); - back.set_cursor_from_name(Some("pointer")); - back.connect_clicked(clone![ - #[weak] - stack, - move |_| { - stack.set_visible_child_name("networks"); - } - ]); - root.append(&back); - - let header = Box::new(Orientation::Horizontal, 6); - let icon = Image::from_icon_name("network-wired-symbolic"); - icon.set_pixel_size(24); - - let title = Label::new(None); - title.add_css_class("network-title"); - - let spacer = Box::new(Orientation::Horizontal, 0); - spacer.set_hexpand(true); - - header.append(&icon); - header.append(&title); - header.append(&spacer); - root.append(&header); - - let basic_box = Box::new(Orientation::Vertical, 6); - basic_box.add_css_class("basic-section"); - - let basic_header = Label::new(Some("Basic")); - basic_header.add_css_class("section-header"); - basic_box.append(&basic_header); - - let state_label = Label::new(None); - let interface = Label::new(None); - - Self::add_row(&basic_box, "Connection State", &state_label); - Self::add_row(&basic_box, "Interface", &interface); - - root.append(&basic_box); - - let advanced_box = Box::new(Orientation::Vertical, 8); - advanced_box.add_css_class("advanced-section"); - - let advanced_header = Label::new(Some("Advanced")); - advanced_header.add_css_class("section-header"); - advanced_box.append(&advanced_header); - - let device_type = Label::new(None); - let mac_address = Label::new(None); - let driver = Label::new(None); - let managed = Label::new(None); - - Self::add_row(&advanced_box, "Device Type", &device_type); - Self::add_row(&advanced_box, "MAC Address", &mac_address); - Self::add_row(&advanced_box, "Driver", &driver); - Self::add_row(&advanced_box, "Managed", &managed); - - root.append(&advanced_box); - - Self { - root, - title, - state_label, - - interface, - device_type, - mac_address, - driver, - managed, - } - } - - fn add_row(parent: >k::Box, key_text: &str, val_widget: >k::Label) { - let row = Box::new(Orientation::Vertical, 3); - row.set_halign(Align::Start); - - let key = Label::new(Some(key_text)); - key.add_css_class("info-label"); - key.set_halign(Align::Start); - - val_widget.add_css_class("info-value"); - val_widget.set_halign(Align::Start); - - row.append(&key); - row.append(val_widget); - parent.append(&row); - } - - pub fn update(&self, device: &Device) { - self.title - .set_text(&format!("Wired Device: {}", device.interface)); - self.state_label.set_text(&format!("{}", device.state)); - self.interface.set_text(&device.interface); - self.device_type - .set_text(&format!("{}", device.device_type)); - self.mac_address.set_text(&device.identity.current_mac); - self.driver - .set_text(&device.driver.clone().unwrap_or_else(|| "-".into())); - self.managed.set_text( - device - .managed - .map(|m| if m { "Yes" } else { "No" }) - .unwrap_or("-"), - ); - } - - pub fn widget(&self) -> >k::Box { - &self.root - } -} diff --git a/nmrs-gui/tests/smoke_test.rs b/nmrs-gui/tests/smoke_test.rs deleted file mode 100644 index 697b74f3..00000000 --- a/nmrs-gui/tests/smoke_test.rs +++ /dev/null @@ -1,13 +0,0 @@ -#[test] -fn app_initializes_without_panic() { - // Skip when no display (e.g. in CI) - if std::env::var("CI").is_ok() { - return; - } - - gtk::init().unwrap(); - let result = std::panic::catch_unwind(|| { - nmrs_gui::run().ok(); - }); - assert!(result.is_ok(), "UI startup panicked"); -} diff --git a/nmrs-gui/tests/style_test.rs b/nmrs-gui/tests/style_test.rs deleted file mode 100644 index 614a4e61..00000000 --- a/nmrs-gui/tests/style_test.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[test] -fn style_css_loads() { - if std::env::var("CI").is_ok() { - return; - } - - gtk::init().unwrap(); - nmrs_gui::style::init(include_str!("../src/style.css")); -} diff --git a/nmrs.desktop b/nmrs.desktop deleted file mode 100644 index 9a190b87..00000000 --- a/nmrs.desktop +++ /dev/null @@ -1,9 +0,0 @@ -[Desktop Entry] -Name=nmrs-gui -Comment=Wayland-native NetworkManager GUI -Exec=nmrs-gui -Icon=network-wireless -Terminal=false -Type=Application -Categories=Network;System;GTK; -Keywords=network;wifi;wireless;networkmanager; diff --git a/package.nix b/package.nix deleted file mode 100644 index 579e78e7..00000000 --- a/package.nix +++ /dev/null @@ -1,50 +0,0 @@ -{ - lib, - stdenv, - rustPlatform, - glib-networking, - pkg-config, - wrapGAppsHook4, - libxkbcommon, - wayland, - glib, - gobject-introspection, - gtk4, - libadwaita, -}: - -rustPlatform.buildRustPackage { - pname = "nmrs"; - version = "1.1.0-stable"; - - src = ./.; - - cargoHash = "sha256-WtFi0P88QCNVFjgH7ZpaY2knXpcJ2J9uD8cu4wRQyHQ="; - - nativeBuildInputs = [ - pkg-config - ] - ++ lib.optionals stdenv.hostPlatform.isLinux [ wrapGAppsHook4 ]; - - buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ - glib-networking - libxkbcommon - wayland - glib - gobject-introspection - gtk4 - libadwaita - ]; - - doCheck = false; - doInstallCheck = true; - - meta = with lib; { - description = "Wayland-native frontend for NetworkManager. "; - homepage = "https://github.com/cachebag/nmrs"; - license = licenses.mit; - maintainers = [ ]; - mainProgram = "nmrs"; - platforms = platforms.linux ++ platforms.darwin; - }; -} \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md index b55df1ac..3a1986d9 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -9,73 +9,43 @@ Prepares a release by updating version numbers and changelog. ### Usage ```bash -python3 scripts/bump_version.py --crate +python3 scripts/bump_version.py ``` ### Arguments -- `version`: Version number in semver format (e.g., `1.2.0`) +- `version`: Version number in semver format (e.g., `3.1.0`) - `release_type`: Either `beta` or `stable` -- `--crate`: Either `nmrs` or `nmrs-gui` ### Examples ```bash -# Prepare nmrs library 1.2.0 stable release -python3 scripts/bump_version.py 1.2.0 stable --crate nmrs +# Prepare nmrs 3.1.0 stable release +python3 scripts/bump_version.py 3.1.0 stable -# Prepare nmrs-gui 1.1.0 stable release -python3 scripts/bump_version.py 1.1.0 stable --crate nmrs-gui +# Prepare nmrs 3.2.0 beta release +python3 scripts/bump_version.py 3.2.0 beta ``` ### What it does -1. Updates `version` in the crate's `Cargo.toml` -2. Updates the crate's `CHANGELOG.md` (moves Unreleased section to new version) +1. Updates `version` in `nmrs/Cargo.toml` +2. Updates `nmrs/CHANGELOG.md` (moves Unreleased section to new version) ## Releasing -### nmrs (library) - ```bash # 1. Bump version and update changelog -python3 scripts/bump_version.py 1.2.0 stable --crate nmrs +python3 scripts/bump_version.py 3.1.0 stable # 2. Review and commit git diff -git commit -am "chore(nmrs): prepare 1.2.0 release" +git commit -am "chore(nmrs): prepare 3.1.0 release" # 3. Push to master and tag git push origin master -git tag nmrs-v1.2.0 -git push origin nmrs-v1.2.0 +git tag nmrs-v3.1.0 +git push origin nmrs-v3.1.0 # CI automatically publishes to crates.io ``` - -### nmrs-gui (binary) - -```bash -# 1. Bump version and update changelog -python3 scripts/bump_version.py 1.1.0 stable --crate nmrs-gui - -# 2. Review and commit -git diff -git commit -am "chore(nmrs-gui): prepare 1.1.0 release" - -# 3. Push to master and tag -git push origin master -git tag gui-v1.1.0 -git push origin gui-v1.1.0 - -# CI automatically creates GitHub release with binary - -# 4. Manually update AUR in the nmrs-aur/ directory -cd nmrs-aur/ -# Update PKGBUILD version and source URL -updpkgsums -makepkg --printsrcinfo > .SRCINFO -git add PKGBUILD .SRCINFO -git commit -m "Update to 1.1.0" -git push -``` diff --git a/scripts/bump_version.py b/scripts/bump_version.py index e11b9f37..4a1c58b6 100755 --- a/scripts/bump_version.py +++ b/scripts/bump_version.py @@ -2,19 +2,18 @@ """ Version bumping script for nmrs. -This script updates version numbers for a specific crate: -- Cargo.toml (nmrs or nmrs-gui) -- CHANGELOG.md (per-crate, in the crate's directory) +This script updates version numbers: +- nmrs/Cargo.toml +- nmrs/CHANGELOG.md Usage: - python3 scripts/bump_version.py --crate + python3 scripts/bump_version.py """ import re import sys from datetime import datetime from pathlib import Path -from typing import Optional def update_cargo_toml(file_path: Path, version: str) -> bool: @@ -38,8 +37,8 @@ def update_cargo_toml(file_path: Path, version: str) -> bool: return False -def update_changelog(file_path: Path, version: str, release_type: str, crate: str) -> bool: - """Update per-crate CHANGELOG.md: move Unreleased to new version section.""" +def update_changelog(file_path: Path, version: str, release_type: str) -> bool: + """Update CHANGELOG.md: move Unreleased to new version section.""" try: content = file_path.read_text() today = datetime.now().strftime("%Y-%m-%d") @@ -77,11 +76,7 @@ def update_changelog(file_path: Path, version: str, release_type: str, crate: st flags=re.DOTALL ) - # Determine git tag format - if crate == "nmrs-gui": - git_tag = f"gui-v{version_tag}" - else: - git_tag = f"nmrs-v{version_tag}" + git_tag = f"nmrs-v{version_tag}" # Update the [Unreleased] comparison link unreleased_link_pattern = r'\[Unreleased\]:\s*https://github\.com/[^/]+/[^/]+/compare/[^\s]+\.\.\.HEAD' @@ -127,17 +122,16 @@ def update_changelog(file_path: Path, version: str, release_type: str, crate: st def main(): """Main entry point.""" - if len(sys.argv) < 4 or '--crate' not in sys.argv: - print("Usage: bump_version.py --crate ") + if len(sys.argv) < 3: + print("Usage: bump_version.py ") print() print("Arguments:") print(" version Version number (e.g., 1.2.0)") print(" release_type 'beta' or 'stable'") - print(" --crate Required: 'nmrs' or 'nmrs-gui'") print() print("Examples:") - print(" python3 scripts/bump_version.py 1.2.0 stable --crate nmrs") - print(" python3 scripts/bump_version.py 0.6.0 beta --crate nmrs-gui") + print(" python3 scripts/bump_version.py 3.1.0 stable") + print(" python3 scripts/bump_version.py 3.1.0 beta") print() print("This script should be run on the dev branch before creating a PR to master.") sys.exit(1) @@ -145,13 +139,6 @@ def main(): version = sys.argv[1] release_type = sys.argv[2] - # Parse --crate argument - crate: Optional[str] = None - if '--crate' in sys.argv: - crate_idx = sys.argv.index('--crate') - if crate_idx + 1 < len(sys.argv): - crate = sys.argv[crate_idx + 1] - # Validate inputs if not re.match(r'^\d+\.\d+\.\d+$', version): print(f"✗ Invalid version format: {version}") @@ -163,21 +150,16 @@ def main(): print("Expected: 'beta' or 'stable'") sys.exit(1) - if crate not in ['nmrs', 'nmrs-gui']: - print(f"✗ Invalid or missing crate: {crate}") - print("Expected: 'nmrs' or 'nmrs-gui'") - sys.exit(1) - script_dir = Path(__file__).parent project_root = script_dir.parent - print(f"Preparing {crate} release: {version}-{release_type}") + print(f"Preparing nmrs release: {version}-{release_type}") print("=" * 50) success = True - # Update Cargo.toml for the specified crate - cargo_toml_path = project_root / crate / 'Cargo.toml' + # Update Cargo.toml + cargo_toml_path = project_root / 'nmrs' / 'Cargo.toml' if not cargo_toml_path.exists(): print(f"✗ File not found: {cargo_toml_path}") success = False @@ -185,14 +167,14 @@ def main(): if not update_cargo_toml(cargo_toml_path, version): success = False - # Update per-crate CHANGELOG.md - changelog_path = project_root / crate / 'CHANGELOG.md' + # Update CHANGELOG.md + changelog_path = project_root / 'nmrs' / 'CHANGELOG.md' if not changelog_path.exists(): print(f"✗ File not found: {changelog_path}") - print(f" Create {crate}/CHANGELOG.md with an [Unreleased] section first") + print(" Create nmrs/CHANGELOG.md with an [Unreleased] section first") success = False else: - if not update_changelog(changelog_path, version, release_type, crate): + if not update_changelog(changelog_path, version, release_type): success = False print("=" * 50) @@ -204,27 +186,16 @@ def main(): else: version_tag = f"{version}-{release_type}" - if crate == "nmrs-gui": - git_tag = f"gui-v{version_tag}" - else: - git_tag = f"nmrs-v{version_tag}" + git_tag = f"nmrs-v{version_tag}" - print(f"✓ Successfully prepared {crate} release {version}-{release_type}") + print(f"✓ Successfully prepared nmrs release {version}-{release_type}") print() print("Next steps:") print(f" 1. Review the changes: git diff") - print(f" 2. Commit: git commit -am 'chore({crate}): prepare {version_tag} release'") + print(f" 2. Commit: git commit -am 'chore(nmrs): prepare {version_tag} release'") print(f" 3. Push and open PR to master") print(f" 4. After merge, create tag: git tag {git_tag} && git push origin {git_tag}") - if crate == "nmrs": - print(f" 5. CI will automatically publish to crates.io and create GitHub release") - else: - print(f" 5. CI will automatically create GitHub release with binary") - print() - print("For nmrs-gui releases:") - print(f" 6. Manually update AUR in nmrs-aur/ directory") - print(f" 7. Update PKGBUILD and run: updpkgsums && makepkg --printsrcinfo > .SRCINFO") - print(f" 8. Commit and push to AUR repository") + print(f" 5. CI will automatically publish to crates.io and create GitHub release") else: print("✗ Some errors occurred during version bumping") sys.exit(1) From 846f74561fc7270a4f65d1ef69f0c525f2aa7bba Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Fri, 1 May 2026 15:10:12 -0400 Subject: [PATCH 2/3] chore: remove nix workflow --- .github/workflows/nix.yml | 80 --------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 .github/workflows/nix.yml diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml deleted file mode 100644 index 65f2775d..00000000 --- a/.github/workflows/nix.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Nix Derivation - -on: - pull_request_target: - branches: [master] - paths: - - 'Cargo.lock' - - 'package.nix' - - '.github/workflows/nix.yml' - -jobs: - fix-hashes: - name: Fix Nix Hashes - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - steps: - - uses: actions/checkout@v6 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - - - name: Install Nix - uses: cachix/install-nix-action@v31 - - - uses: dtolnay/rust-toolchain@stable - - - name: Install fixsha - run: cargo install fixsha - - - name: Run fixsha - run: fixsha - - - name: Push hash fix if needed - if: github.event.pull_request.head.repo.full_name == github.repository - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [[ -z $(git status -s package.nix) ]]; then - echo "No hash changes needed" - exit 0 - fi - - CONTENT=$(base64 -w 0 package.nix) - SHA=$(gh api repos/${{ github.repository }}/contents/package.nix \ - --jq '.sha' \ - -H "Accept: application/vnd.github+json" \ - --method GET \ - -f ref=${{ github.event.pull_request.head.ref }}) - - gh api repos/${{ github.repository }}/contents/package.nix \ - --method PUT \ - -H "Accept: application/vnd.github+json" \ - -f message="fix(nix): update cargoHash" \ - -f content="$CONTENT" \ - -f sha="$SHA" \ - -f branch=${{ github.event.pull_request.head.ref }} - - echo "Nix hashes updated via API commit" - - - name: Comment on fork PRs - if: github.event.pull_request.head.repo.full_name != github.repository - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - if [[ -z $(git status -s package.nix) ]]; then - echo "No hash changes needed" - exit 0 - fi - - NEW_HASH=$(grep 'cargoHash' package.nix | sed 's/.*"\(.*\)".*/\1/') - - gh pr comment ${{ github.event.pull_request.number }} \ - -R ${{ github.repository }} \ - --body "The \`cargoHash\` in \`package.nix\` needs updating. Please update it to: - - \`\`\`nix - cargoHash = \"$NEW_HASH\"; - \`\`\`" From 798690209393fd4c2f444492071149baebaed0dc Mon Sep 17 00:00:00 2001 From: Akrm Al-Hakimi Date: Fri, 1 May 2026 15:18:32 -0400 Subject: [PATCH 3/3] chore: add lock file to .gitignore --- .gitignore | 3 +- Cargo.lock | 1264 ---------------------------------------------------- 2 files changed, 2 insertions(+), 1265 deletions(-) delete mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 0a816e9d..a09551bd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ __pycache__/ vendor/ /pkg/ /src/ -*.pkg.tar.zst \ No newline at end of file +*.pkg.tar.zst +Cargo.lock \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 41917469..00000000 --- a/Cargo.lock +++ /dev/null @@ -1,1264 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys", -] - -[[package]] -name = "async-lock" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-signal" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" - -[[package]] -name = "cfg-if" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "endi" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "indexmap" -version = "2.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" -dependencies = [ - "equivalent", - "hashbrown", - "serde", - "serde_core", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "js-sys" -version = "0.3.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.176" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "nmrs" -version = "3.0.1" -dependencies = [ - "async-trait", - "base64", - "bitflags", - "futures", - "futures-timer", - "log", - "serde", - "thiserror", - "tokio", - "uuid", - "zbus", - "zvariant", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys", -] - -[[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 = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rustix" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -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_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "signal-hook-registry" -version = "1.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" -dependencies = [ - "fastrand", - "getrandom 0.3.3", - "once_cell", - "rustix", - "windows-sys", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio" -version = "1.52.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" -dependencies = [ - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "toml_datetime" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow 0.7.13", -] - -[[package]] -name = "toml_parser" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" -dependencies = [ - "winnow 0.7.13", -] - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" -dependencies = [ - "once_cell", -] - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unicode-ident" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "getrandom 0.4.1", - "js-sys", - "serde_core", - "sha1_smol", - "wasm-bindgen", -] - -[[package]] -name = "wasi" -version = "0.14.7+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" -dependencies = [ - "wasip2", -] - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen 0.46.0", -] - -[[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 0.51.0", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -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", - "hashbrown", - "indexmap", - "semver", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" - -[[package]] -name = "windows-sys" -version = "0.61.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" -dependencies = [ - "windows-link", -] - -[[package]] -name = "winnow" -version = "0.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "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", - "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 = "zbus" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys", - "winnow 1.0.2", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" -dependencies = [ - "serde", - "winnow 1.0.2", - "zvariant", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zvariant" -version = "5.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db0ecb8987cf5e92653c57c098f7f0e39a03112edb796f4fe089fb7eaa14ff" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 1.0.2", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b949b639ab1b4bed763aa7481ba0e368af68d8b55532f8ed4bec86a59f2ca98" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn", - "winnow 1.0.2", -]