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/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\";
- \`\`\`"
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/.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/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
deleted file mode 100644
index ae9619bd..00000000
--- a/Cargo.lock
+++ /dev/null
@@ -1,1906 +0,0 @@
-# This file is automatically @generated by Cargo.
-# 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"
-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 = "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"
-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 = "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"
-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 = "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"
-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 = "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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
-dependencies = [
- "cfg-if",
- "libc",
- "r-efi",
- "wasi 0.14.7+wasi-0.2.4",
-]
-
-[[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 = "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"
-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 = "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"
-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 = "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"
-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 = "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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
-dependencies = [
- "futures-core",
- "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"
-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 = "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"
-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 0.23.6",
-]
-
-[[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 = "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"
-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 = "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"
-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 = "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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
-dependencies = [
- "proc-macro2",
- "quote",
- "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"
-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 = "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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1"
-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"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3effe7c0e86fdff4f69cdd2ccc1b96f933e24811c5441d44904e8683e27184b"
-dependencies = [
- "indexmap",
- "toml_datetime 0.7.2",
- "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 = "utf8parse"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-
-[[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 = "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"
-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.5.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
-dependencies = [
- "memchr",
-]
-
-[[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",
-]
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
-
-[](https://crates.io/crates/nmrs-gui)
-[](https://github.com/cachebag/nmrs/actions/workflows/nix.yml)
-
-This repository also includes `nmrs-gui`, a Wayland-compatible NetworkManager frontend built with GTK4.
-
-
-
-
-
-### 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)
| | |