1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2026-01-08 12:16:44 +01:00

Compare commits

...

63 commits

Author SHA1 Message Date
a106f41b0a
fix(networkmanager): notify upon new device from watch_device()
Additionally:
- Pass the device proxy to watch_device_state() now that we've had to
  create one anyway
- Improve event received logging in module widget
2025-09-04 20:41:55 +02:00
db88e12b8e
refactor(networkmanager): identify devices by their number outside of the client 2025-09-04 14:54:23 +02:00
af49acb40b
fix(networkmanager): remove icons for removed devices 2025-09-04 14:25:20 +02:00
d752e88abf
refactor(networkmanager): make dbus connection a ClientInner field
Should be more efficient as the connection will now only be created once.
2025-09-04 13:25:48 +02:00
f83c9e6852
refactor(networkmanager): merge devices and watchers fields in ClientInner 2025-09-04 11:17:42 +02:00
01de9da7e0
refactor(networkmanager): store property watcher join handles & stop them when no longer needed
Also optimise dbus connection and proxy creation.
2025-09-03 18:11:56 +02:00
13c2520c76
refactor(networkmanager): use inner client with static lifetime, make its functions methods 2025-09-03 12:41:20 +02:00
5385c7e705
refactor(networkmanager): Replace Mutex with RwLock for shared state in run(), add debug logging 2025-09-03 11:29:40 +02:00
3ffb668e6b
refactor(networkmanager): pass device proxy directly to device state watcher
Also clarify what receiver we're dealing with in handle_update_events.
2025-09-02 23:23:44 +02:00
4c516a1c2a
refactor(networkmanager): rename DeviceStateChanged event to DeviceChanged
Also add a little TODO about icon order.
2025-09-02 22:44:14 +02:00
ec00b2ce69
refactor(networkmanager): break Client::run up into multiple functions
Also replace its shared state lifetime and synchonisation mechanisms with Arc<Mutex<_>>.
2025-09-02 22:26:30 +02:00
226b32ce6a
fix(networkmanager): support late module initialisation
For example when a second monitor is connected while Ironbar is already running.
2025-09-02 20:42:26 +02:00
4594271c42
refactor(networkmanager): remove now unused state.rs 2025-08-20 20:02:47 +02:00
dfad982204
refactor(networkmanager): implement clippy::pedantic suggestions 2025-08-20 19:08:42 +02:00
4a09e95370
Merge branch 'master' into feat/networkmanager-multi-icon 2025-08-20 17:40:27 +02:00
48493c6193
refactor(networkmanager): event-based approach, update module interfaces
An extensive refactor of the multiple icons features, containing the following changes:

- Reflect the changes in module UI and client interfaces to the rest of Ironbar
- Replace state-based communication from client to UI with an event-based one
- D-bus device watching rewritten without the use of macros
- Defining which types of devices get an icon now takes place in the UI code
2025-08-20 15:16:27 +02:00
c7e002ee6c
Merge pull request #1107 from JakeStanger/dependabot/cargo/serde_json-1.0.142
build(deps): bump serde_json from 1.0.141 to 1.0.142
2025-08-06 09:49:47 +01:00
a2e72bcdde
Merge pull request #1106 from JakeStanger/dependabot/cargo/wayland-protocols-wlr-0.3.9
build(deps): bump wayland-protocols-wlr from 0.3.8 to 0.3.9
2025-08-06 09:49:36 +01:00
ef26554434
Merge pull request #1093 from slowsage/wmclass-async
fix(launcher): improve app ID handling: fallback for desktop entries.…
2025-08-05 15:10:07 +01:00
dependabot[bot]
ca77a991f7
build(deps): bump serde_json from 1.0.141 to 1.0.142
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.141 to 1.0.142.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.141...v1.0.142)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.142
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 08:12:55 +00:00
dependabot[bot]
279ef938d1
build(deps): bump wayland-protocols-wlr from 0.3.8 to 0.3.9
Bumps [wayland-protocols-wlr](https://github.com/smithay/wayland-rs) from 0.3.8 to 0.3.9.
- [Release notes](https://github.com/smithay/wayland-rs/releases)
- [Changelog](https://github.com/Smithay/wayland-rs/blob/master/historical_changelog.md)
- [Commits](https://github.com/smithay/wayland-rs/commits)

---
updated-dependencies:
- dependency-name: wayland-protocols-wlr
  dependency-version: 0.3.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 08:12:51 +00:00
24ef1b700a
Merge pull request #1105 from JakeStanger/dependabot/cargo/clap-4.5.42
build(deps): bump clap from 4.5.41 to 4.5.42
2025-08-05 09:10:45 +01:00
b7557bc78b
Merge pull request #1104 from JakeStanger/dependabot/cargo/tokio-1.47.1
build(deps): bump tokio from 1.46.1 to 1.47.1
2025-08-05 09:10:18 +01:00
037de421cd
Merge pull request #1103 from JakeStanger/dependabot/cargo/notify-8.2.0
build(deps): bump notify from 8.1.0 to 8.2.0
2025-08-05 09:09:59 +01:00
dependabot[bot]
4041e080d0
build(deps): bump clap from 4.5.41 to 4.5.42
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.41 to 4.5.42.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.41...clap_complete-v4.5.42)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.42
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 18:51:20 +00:00
dependabot[bot]
491e055aa1
build(deps): bump tokio from 1.46.1 to 1.47.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.46.1 to 1.47.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.46.1...tokio-1.47.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-version: 1.47.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 18:35:36 +00:00
dependabot[bot]
304b1d3e17
build(deps): bump notify from 8.1.0 to 8.2.0
Bumps [notify](https://github.com/notify-rs/notify) from 8.1.0 to 8.2.0.
- [Release notes](https://github.com/notify-rs/notify/releases)
- [Changelog](https://github.com/notify-rs/notify/blob/main/CHANGELOG.md)
- [Commits](https://github.com/notify-rs/notify/compare/notify-8.1.0...notify-8.2.0)

---
updated-dependencies:
- dependency-name: notify
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-04 18:13:45 +00:00
slowsage
0159c0155e fix: app_id StartupWMClass resolution in launcher 2025-08-03 16:22:06 -04:00
dea571506f
Merge pull request #1101 from JakeStanger/update_flake_lock_action
Update flake.lock
2025-08-01 08:42:25 +01:00
github-actions[bot]
bd5745f2ef flake.lock: Update
Flake lock file updates:

• Updated input 'naersk':
    'github:nix-community/naersk/38bc60bbc157ae266d4a0c96671c6c742ee17a5f?narHash=sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0%3D' (2025-04-29)
  → 'github:nix-community/naersk/0e72363d0938b0208d6c646d10649164c43f4d64?narHash=sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc%3D' (2025-07-16)
• Added input 'naersk/fenix':
    'github:nix-community/fenix/bf0d6f70f4c9a9cf8845f992105652173f4b617f?narHash=sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU%3D' (2025-07-14)
• Added input 'naersk/fenix/nixpkgs':
    follows 'naersk/nixpkgs'
• Added input 'naersk/fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/591e3b7624be97e4443ea7b5542c191311aa141d?narHash=sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu%2BQEnJn2Sfg%3D' (2025-07-13)
• Updated input 'naersk/nixpkgs':
    'github:NixOS/nixpkgs/b95255df2360a45ddbb03817a68869d5cb01bf96?narHash=sha256-IJWIzZSkBsDzS7iS/iwSwur%2BxFkWqeLYC4kdf8ObtOM%3D' (2025-06-30)
  → 'github:NixOS/nixpkgs/be9e214982e20b8310878ac2baa063a961c1bdf6?narHash=sha256-HM791ZQtXV93xtCY%2BZxG1REzhQenSQO020cu6rHtAPk%3D' (2025-07-09)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/30e2e2857ba47844aa71991daa6ed1fc678bcbb7?narHash=sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM%3D' (2025-06-27)
  → 'github:nixos/nixpkgs/dc9637876d0dcc8c9e5e22986b857632effeb727?narHash=sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM%3D' (2025-07-28)
2025-08-01 00:20:13 +00:00
106e23e311
Merge pull request #1096 from JakeStanger/dependabot/cargo/serde_json-1.0.141
build(deps): bump serde_json from 1.0.140 to 1.0.141
2025-07-21 16:52:44 +01:00
546e75c43c
Merge pull request #1099 from JakeStanger/dependabot/cargo/zbus-5.9.0
build(deps): bump zbus from 5.8.0 to 5.9.0
2025-07-21 16:47:56 +01:00
50ab48500f
Merge pull request #1097 from JakeStanger/dependabot/cargo/mlua-0.11.1
build(deps): bump mlua from 0.11.0 to 0.11.1
2025-07-21 16:45:11 +01:00
798a71cc13
Merge pull request #1098 from JakeStanger/dependabot/cargo/sysinfo-0.36.1
build(deps): bump sysinfo from 0.36.0 to 0.36.1
2025-07-21 16:44:59 +01:00
dependabot[bot]
dca0e07ce2
build(deps): bump serde_json from 1.0.140 to 1.0.141
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.140 to 1.0.141.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.140...v1.0.141)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-version: 1.0.141
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 15:42:30 +00:00
dependabot[bot]
8c9673eb1f
build(deps): bump zbus from 5.8.0 to 5.9.0
Bumps [zbus](https://github.com/dbus2/zbus) from 5.8.0 to 5.9.0.
- [Release notes](https://github.com/dbus2/zbus/releases)
- [Commits](https://github.com/dbus2/zbus/compare/zbus-5.8.0...zbus-5.9.0)

---
updated-dependencies:
- dependency-name: zbus
  dependency-version: 5.9.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 15:35:25 +00:00
a92e6684a0
Merge pull request #1095 from JakeStanger/dependabot/cargo/rustix-1.0.8
build(deps): bump rustix from 1.0.7 to 1.0.8
2025-07-21 16:35:18 +01:00
dependabot[bot]
eadc7a7bab
build(deps): bump sysinfo from 0.36.0 to 0.36.1
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.36.0 to 0.36.1.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.36.0...v0.36.1)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-version: 0.36.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 15:32:44 +00:00
dependabot[bot]
c3c3dab11b
build(deps): bump mlua from 0.11.0 to 0.11.1
Bumps [mlua](https://github.com/mlua-rs/mlua) from 0.11.0 to 0.11.1.
- [Release notes](https://github.com/mlua-rs/mlua/releases)
- [Changelog](https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mlua-rs/mlua/compare/v0.11.0...v0.11.1)

---
updated-dependencies:
- dependency-name: mlua
  dependency-version: 0.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 15:29:37 +00:00
dependabot[bot]
dd9a0c2065
build(deps): bump rustix from 1.0.7 to 1.0.8
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 1.0.7 to 1.0.8.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Changelog](https://github.com/bytecodealliance/rustix/blob/main/CHANGES.md)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v1.0.7...v1.0.8)

---
updated-dependencies:
- dependency-name: rustix
  dependency-version: 1.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-21 15:25:58 +00:00
Alan
5520562a18
feat: support glob patterns for keyboard layout icons (#949)
* Simplistic globbing for matching keyboard layout icons

Update the logic for determining the display text for the current keyboard layout.
Instead of a direct map lookup, iterate through the layout map to support wildcard matching.

Patterns ending with `*` will match any language string starting with the characters before the `*`. This allows grouping similar layouts (e.g., `English`, `English (Colemak-DH ISO)`) under a single pattern like `English*`.

* Use `IndexMap` instead of `HashMap` for keyboard layout icons map

This enables users to choose which globs to prioritize via ordering in the config

* Enable feature `serde` for `indexmap`

* Document wildcard matching for keyboard layouts

* Enable `indexmap2` feature flag for `schemars`

* Add missing period

* use string slices

* Fix formatting
2025-07-20 22:19:58 +01:00
233f7f1ee2
Merge pull request #1094 from JakeStanger/fix/launcher-regression
fix: regression caused by #1089
2025-07-20 16:26:33 +01:00
feccc29fd1
fix: regression caused by #1089
This resolves an issue caused by a previous fix which broke launching applications with the `launcher` and `menu` modules
2025-07-20 16:21:31 +01:00
ccb57c8458
Merge pull request #1092 from slowsage/app_id_lowercase
fix(desktop_file): case-insensitive app_id -> desktop_file find
2025-07-19 09:33:56 +01:00
slowsage
ff185d0de9 fix(desktop_file): case-insensitive app_id -> desktop_file find 2025-07-19 04:01:51 -04:00
34da5edfe0
docs(sysinfo): add info on querying values
Resolves #1090
2025-07-15 22:45:49 +01:00
622ca3cc4f
Merge pull request #1089 from JakeStanger/fix/gtk-launch-zombie
fix: opening programs via `launcher` and `menu` leaving zombie processes
2025-07-14 21:45:40 +01:00
40adfaf810
fix: opening programs via launcher and menu leaving zombie processes
Fixes #1020
2025-07-14 21:42:11 +01:00
749b9f0433
Merge pull request #1067 from JakeStanger/fix/ipc-limit
fix(ipc): message size limited to 1024 bytes
2025-07-14 21:10:31 +01:00
c4f5485d53
fix(ipc): message size limited to 1024 bytes
Fixes #1065
2025-07-14 21:06:57 +01:00
a67e722dee
Merge pull request #1088 from JakeStanger/chores
Chores
2025-07-14 20:09:20 +01:00
f1a8b42cfa
chore: clippy/fmt 2025-07-14 19:55:46 +01:00
3a3888a2d6
chore: update deps 2025-07-14 19:55:25 +01:00
b68e4b4af9
chore(deps): update system-tray 2025-07-14 19:34:04 +01:00
f1dc35e873
Merge pull request #1086 from JakeStanger/dependabot/cargo/clap-4.5.41
build(deps): bump clap from 4.5.40 to 4.5.41
2025-07-14 19:32:25 +01:00
dc8af7ada8
Merge pull request #1085 from JakeStanger/dependabot/cargo/sysinfo-0.36.0
build(deps): bump sysinfo from 0.35.2 to 0.36.0
2025-07-14 19:30:48 +01:00
613495ce0b
Merge pull request #1084 from JakeStanger/dependabot/cargo/zbus-5.8.0
build(deps): bump zbus from 5.7.1 to 5.8.0
2025-07-14 19:28:25 +01:00
dependabot[bot]
98e9af68d9
build(deps): bump clap from 4.5.40 to 4.5.41
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.40 to 4.5.41.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.41)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.5.41
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 18:24:12 +00:00
dependabot[bot]
1fbe655480
build(deps): bump sysinfo from 0.35.2 to 0.36.0
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.35.2 to 0.36.0.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/compare/v0.35.2...v0.36.0)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-version: 0.36.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 17:46:09 +00:00
dependabot[bot]
93a797be3f
build(deps): bump zbus from 5.7.1 to 5.8.0
Bumps [zbus](https://github.com/dbus2/zbus) from 5.7.1 to 5.8.0.
- [Release notes](https://github.com/dbus2/zbus/releases)
- [Commits](https://github.com/dbus2/zbus/compare/zbus-5.7.1...zbus-5.8.0)

---
updated-dependencies:
- dependency-name: zbus
  dependency-version: 5.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-14 17:15:47 +00:00
2c68b4a58c
fix: issues introduced by merge (see parent) 2024-08-04 22:22:13 +02:00
f5f81da12c
Merge branch 'master' into feat/networkmanager-multi-icon 2024-08-04 21:56:02 +02:00
e1945d1e93
fix: revert inclusion of local volume module patch 2024-06-30 19:27:42 +02:00
20 changed files with 991 additions and 580 deletions

250
Cargo.lock generated
View file

@ -265,9 +265,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -309,7 +309,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cairo-sys-rs", "cairo-sys-rs",
"glib", "glib",
"libc", "libc",
@ -330,11 +330,11 @@ dependencies = [
[[package]] [[package]]
name = "calloop" name = "calloop"
version = "0.12.4" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"log", "log",
"polling", "polling",
"rustix 0.38.44", "rustix 0.38.44",
@ -344,9 +344,9 @@ dependencies = [
[[package]] [[package]]
name = "calloop-wayland-source" name = "calloop-wayland-source"
version = "0.2.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
dependencies = [ dependencies = [
"calloop", "calloop",
"rustix 0.38.44", "rustix 0.38.44",
@ -410,9 +410,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.40" version = "4.5.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" checksum = "ed87a9d530bb41a67537289bafcac159cb3ee28460e0a4571123d2a778a6a882"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -420,9 +420,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.40" version = "4.5.42"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" checksum = "64f4f3f3c77c94aff3c7e9aac9a2ca1974a5adf392a8bb751e827d6d127ab966"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -432,18 +432,18 @@ dependencies = [
[[package]] [[package]]
name = "clap_complete" name = "clap_complete"
version = "4.5.54" version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aad5b1b4de04fead402672b48897030eec1f3bfe1550776322f59f6d6e6a5677" checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
dependencies = [ dependencies = [
"clap", "clap",
] ]
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.40" version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
@ -821,11 +821,11 @@ dependencies = [
[[package]] [[package]]
name = "evdev-rs" name = "evdev-rs"
version = "0.6.1" version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9812d5790fb6fcce449333eb6713dad335e8c979225ed98755c84a3987e06dba" checksum = "53b9cb6084eed4e72c0306e1cbcd3fd4c8acb613044e66810f9f5d3c7896bfb7"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.9.1",
"evdev-sys", "evdev-sys",
"libc", "libc",
"log", "log",
@ -833,9 +833,9 @@ dependencies = [
[[package]] [[package]]
name = "evdev-sys" name = "evdev-sys"
version = "0.2.5" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14ead42b547b15d47089c1243d907bcf0eb94e457046d3b315a26ac9c9e9ea6d" checksum = "cdcf0d489f4d9a80ac2b3b35b92fdd8fcf68d33bb67f947afe5cd36e482de576"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -1203,7 +1203,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-executor", "futures-executor",
@ -1303,7 +1303,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc759b3184830a547b31549ab40c4b54450ab702bba79ba23f049bc1d1e3ca98" checksum = "bc759b3184830a547b31549ab40c4b54450ab702bba79ba23f049bc1d1e3ca98"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"gdk", "gdk",
"glib", "glib",
"glib-sys 0.18.1", "glib-sys 0.18.1",
@ -1525,7 +1525,7 @@ dependencies = [
"libc", "libc",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2 0.5.10",
"tokio", "tokio",
"tower-service", "tower-service",
"tracing", "tracing",
@ -1755,7 +1755,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"inotify-sys", "inotify-sys",
"libc", "libc",
] ]
@ -1775,7 +1775,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"libc", "libc",
] ]
@ -1824,7 +1824,7 @@ dependencies = [
"notify", "notify",
"regex", "regex",
"reqwest", "reqwest",
"rustix 1.0.7", "rustix 1.0.8",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
@ -1842,7 +1842,7 @@ dependencies = [
"universal-config", "universal-config",
"walkdir", "walkdir",
"wayland-client", "wayland-client",
"wayland-protocols-wlr 0.3.8", "wayland-protocols-wlr",
"zbus", "zbus",
] ]
@ -1896,9 +1896,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.173" version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8cfeafaffdbc32176b64fb251369d52ea9f0a8fbc6f8759edffef7b525d64bb" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]] [[package]]
name = "libcorn" name = "libcorn"
@ -1929,7 +1929,7 @@ version = "2.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397" checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"libc", "libc",
"libpulse-sys", "libpulse-sys",
"num-derive", "num-derive",
@ -1956,7 +1956,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"libc", "libc",
] ]
@ -1990,9 +1990,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.26" version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]] [[package]]
name = "lua-src" name = "lua-src"
@ -2065,9 +2065,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua" name = "mlua"
version = "0.10.5" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0" checksum = "de25fc513588ac1273aa8c6dc0fffee6d32c12f38dc75f5cdc74547121a107ef"
dependencies = [ dependencies = [
"bstr", "bstr",
"either", "either",
@ -2080,9 +2080,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua-sys" name = "mlua-sys"
version = "0.6.8" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93" checksum = "bcdf7c9e260ca82aaa32ac11148941952b856bb8c69aa5a9e65962f21fcb8637"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@ -2163,7 +2163,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@ -2182,11 +2182,11 @@ dependencies = [
[[package]] [[package]]
name = "notify" name = "notify"
version = "8.1.0" version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3163f59cd3fa0e9ef8c32f242966a7b9994fd7378366099593e0e73077cd8c97" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"inotify", "inotify",
"kqueue", "kqueue",
"libc", "libc",
@ -2254,7 +2254,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -2288,7 +2288,7 @@ version = "0.10.72"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -2658,7 +2658,7 @@ version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -2672,6 +2672,26 @@ dependencies = [
"thiserror 2.0.12", "thiserror 2.0.12",
] ]
[[package]]
name = "ref-cast"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
dependencies = [
"proc-macro2",
"quote 1.0.39",
"syn 2.0.99",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.11.1" version = "1.11.1"
@ -2775,7 +2795,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f"
dependencies = [ dependencies = [
"base64", "base64",
"bitflags 2.9.0", "bitflags 2.9.1",
"serde", "serde",
"serde_derive", "serde_derive",
"unicode-ident", "unicode-ident",
@ -2808,7 +2828,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys 0.4.15",
@ -2817,15 +2837,15 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.9.3", "linux-raw-sys 0.9.3",
"windows-sys 0.59.0", "windows-sys 0.60.2",
] ]
[[package]] [[package]]
@ -2890,11 +2910,13 @@ dependencies = [
[[package]] [[package]]
name = "schemars" name = "schemars"
version = "0.8.22" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
dependencies = [ dependencies = [
"dyn-clone", "dyn-clone",
"indexmap",
"ref-cast",
"schemars_derive", "schemars_derive",
"serde", "serde",
"serde_json", "serde_json",
@ -2902,9 +2924,9 @@ dependencies = [
[[package]] [[package]]
name = "schemars_derive" name = "schemars_derive"
version = "0.8.22" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.39", "quote 1.0.39",
@ -2924,7 +2946,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@ -2980,9 +3002,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.140" version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -3093,11 +3115,11 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]] [[package]]
name = "smithay-client-toolkit" name = "smithay-client-toolkit"
version = "0.18.1" version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"calloop", "calloop",
"calloop-wayland-source", "calloop-wayland-source",
"cursor-icon", "cursor-icon",
@ -3110,8 +3132,8 @@ dependencies = [
"wayland-client", "wayland-client",
"wayland-csd-frame", "wayland-csd-frame",
"wayland-cursor", "wayland-cursor",
"wayland-protocols 0.31.2", "wayland-protocols",
"wayland-protocols-wlr 0.2.0", "wayland-protocols-wlr",
"wayland-scanner", "wayland-scanner",
"xkeysym", "xkeysym",
] ]
@ -3126,6 +3148,16 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.0" version = "1.2.0"
@ -3254,9 +3286,9 @@ dependencies = [
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.35.2" version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
dependencies = [ dependencies = [
"libc", "libc",
"memchr", "memchr",
@ -3294,10 +3326,11 @@ dependencies = [
[[package]] [[package]]
name = "system-tray" name = "system-tray"
version = "0.7.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3397841ed755bf361606a845779e0f7333d35fb4e39627ef6f656d7cdad4c73" checksum = "90d5d024b1573d22079347055d817863c21ea0903df404668095499c08800e4a"
dependencies = [ dependencies = [
"cfg-if",
"dbusmenu-gtk3-sys", "dbusmenu-gtk3-sys",
"futures-lite", "futures-lite",
"gtk", "gtk",
@ -3421,9 +3454,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.46.1" version = "1.47.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -3433,10 +3466,10 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"slab", "slab",
"socket2", "socket2 0.6.0",
"tokio-macros", "tokio-macros",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -3571,7 +3604,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2" checksum = "5cc2d9e086a412a451384326f521c8123a99a466b329941a9403696bff9b0da2"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@ -3929,25 +3962,25 @@ dependencies = [
[[package]] [[package]]
name = "wayland-backend" name = "wayland-backend"
version = "0.3.10" version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
dependencies = [ dependencies = [
"cc", "cc",
"downcast-rs", "downcast-rs",
"rustix 0.38.44", "rustix 1.0.8",
"smallvec", "smallvec",
"wayland-sys", "wayland-sys",
] ]
[[package]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.31.10" version = "0.31.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"rustix 0.38.44", "rustix 1.0.8",
"wayland-backend", "wayland-backend",
"wayland-scanner", "wayland-scanner",
] ]
@ -3958,7 +3991,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"cursor-icon", "cursor-icon",
"wayland-backend", "wayland-backend",
] ]
@ -3976,23 +4009,11 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols" name = "wayland-protocols"
version = "0.31.2" version = "0.32.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"wayland-backend",
"wayland-client",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols"
version = "0.32.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "779075454e1e9a521794fed15886323ea0feda3f8b0fc1390f5398141310422a"
dependencies = [
"bitflags 2.9.0",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-scanner", "wayland-scanner",
@ -4000,35 +4021,22 @@ dependencies = [
[[package]] [[package]]
name = "wayland-protocols-wlr" name = "wayland-protocols-wlr"
version = "0.2.0" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
"wayland-backend", "wayland-backend",
"wayland-client", "wayland-client",
"wayland-protocols 0.31.2", "wayland-protocols",
"wayland-scanner",
]
[[package]]
name = "wayland-protocols-wlr"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cb6cdc73399c0e06504c437fe3cf886f25568dd5454473d565085b36d6a8bbf"
dependencies = [
"bitflags 2.9.0",
"wayland-backend",
"wayland-client",
"wayland-protocols 0.32.8",
"wayland-scanner", "wayland-scanner",
] ]
[[package]] [[package]]
name = "wayland-scanner" name = "wayland-scanner"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml", "quick-xml",
@ -4037,9 +4045,9 @@ dependencies = [
[[package]] [[package]]
name = "wayland-sys" name = "wayland-sys"
version = "0.31.6" version = "0.31.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbcebb399c77d5aa9fa5db874806ee7b4eba4e73650948e8f93963f128896615" checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
dependencies = [ dependencies = [
"pkg-config", "pkg-config",
] ]
@ -4374,7 +4382,7 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [ dependencies = [
"bitflags 2.9.0", "bitflags 2.9.1",
] ]
[[package]] [[package]]
@ -4427,9 +4435,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus" name = "zbus"
version = "5.7.1" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68" checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-recursion", "async-recursion",
@ -4455,9 +4463,9 @@ dependencies = [
[[package]] [[package]]
name = "zbus_macros" name = "zbus_macros"
version = "5.7.1" version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a" checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
dependencies = [ dependencies = [
"proc-macro-crate 3.2.0", "proc-macro-crate 3.2.0",
"proc-macro2", "proc-macro2",

View file

@ -115,7 +115,7 @@ schema = ["dep:schemars"]
gtk = "0.18.2" gtk = "0.18.2"
gtk-layer-shell = "0.8.2" gtk-layer-shell = "0.8.2"
glib = "0.18.5" glib = "0.18.5"
tokio = { version = "1.46.1", features = [ tokio = { version = "1.47.1", features = [
"macros", "macros",
"rt-multi-thread", "rt-multi-thread",
"time", "time",
@ -132,13 +132,13 @@ tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0" strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.5" color-eyre = "0.6.5"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
indexmap = "2.10.0" indexmap = { version = "2.10.0", features = ["serde"] }
dirs = "6.0.0" dirs = "6.0.0"
walkdir = "2.5.0" walkdir = "2.5.0"
notify = { version = "8.1.0", default-features = false } notify = { version = "8.2.0", default-features = false }
wayland-client = "0.31.1" wayland-client = "0.31.1"
wayland-protocols-wlr = { version = "0.3.8", features = ["client"] } wayland-protocols-wlr = { version = "0.3.9", features = ["client"] }
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [ smithay-client-toolkit = { version = "0.19.2", default-features = false, features = [
"calloop", "calloop",
] } ] }
universal-config = { version = "0.5.1", default-features = false } universal-config = { version = "0.5.1", default-features = false }
@ -146,14 +146,14 @@ ctrlc = "3.4.7"
cfg-if = "1.0.1" cfg-if = "1.0.1"
# cli # cli
clap = { version = "4.5.40", optional = true, features = ["derive"] } clap = { version = "4.5.42", optional = true, features = ["derive"] }
# http # http
reqwest = { version = "0.12.22", default-features = false, features = ["default-tls", "http2"], optional = true } reqwest = { version = "0.12.22", default-features = false, features = ["default-tls", "http2"], optional = true }
# cairo # cairo
lua-src = { version = "548.1.1", optional = true } lua-src = { version = "548.1.1", optional = true }
mlua = { version = "0.10.5", optional = true, features = ["luajit", "send"] } mlua = { version = "0.11.1", optional = true, features = ["luajit", "send"] }
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] } cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
# clock # clock
@ -161,7 +161,7 @@ chrono = { version = "0.4.41", optional = true, default-features = false, featur
# keyboard # keyboard
colpetto = { version = "0.6.0", features = ["tokio", "tracing"], optional = true } colpetto = { version = "0.6.0", features = ["tokio", "tracing"], optional = true }
evdev-rs = { version = "0.6.1", optional = true } evdev-rs = { version = "0.6.2", optional = true }
# music # music
mpd-utils = { version = "0.2.1", optional = true } mpd-utils = { version = "0.2.1", optional = true }
@ -174,27 +174,28 @@ regex = { version = "1.11.1", default-features = false, features = [
tokio-stream = { version = "0.1.17", optional = true } tokio-stream = { version = "0.1.17", optional = true }
# sys_info # sys_info
sysinfo = { version = "0.35.2", optional = true } sysinfo = { version = "0.36.1", optional = true }
# tray # tray
system-tray = { version = "0.7.0", features = ["dbusmenu-gtk3"], optional = true } system-tray = { version = "0.8.1", features = ["dbusmenu-gtk3"], optional = true }
# volume # volume
libpulse-binding = { version = "2.30.1", optional = true } libpulse-binding = { version = "2.30.1", optional = true }
# shared # shared
futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces, keyboard futures-lite = { version = "2.6.0", optional = true } # network_manager, upower, workspaces, keyboard
zbus = { version = "5.7.1", default-features = false, features = ["blocking-api", "tokio"], optional = true } # network_manager, notifications, upower zbus = { version = "5.9.0", default-features = false, features = ["tokio"], optional = true } # network_manager, notifications, upower
swayipc-async = { version = "2.1.0", optional = true } # workspaces, keyboard swayipc-async = { version = "2.1.0", optional = true } # workspaces, keyboard
hyprland = { version = "0.4.0-beta.2", optional = true } # workspaces, keyboard hyprland = { version = "0.4.0-beta.2", optional = true } # workspaces, keyboard
rustix = { version = "1.0.7", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input rustix = { version = "1.0.8", default-features = false, features = ["std", "fs", "pipe", "event"], optional = true } # clipboard, input
serde_json = { version = "1.0.140", optional = true } # ipc, niri serde_json = { version = "1.0.142", optional = true } # ipc, niri
# schema # schema
schemars = { version = "0.8.22", optional = true }
schemars = { version = "1.0.4", optional = true, features = ["indexmap2"] }
[build-dependencies] [build-dependencies]
clap = { version = "4.5.40", features = ["derive"] } clap = { version = "4.5.42", features = ["derive"] }
clap_complete = "4.5.54" clap_complete = "4.5.55"
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140" serde_json = "1.0.142"

View file

@ -35,13 +35,11 @@ All error responses will cause the CLI to exit code 3.
The server listens on a Unix socket. The server listens on a Unix socket.
The path is printed on startup, and can usually be found at `/run/user/$UID/ironbar-ipc.sock`. The path is printed on startup, and can usually be found at `/run/user/$UID/ironbar-ipc.sock`.
Commands and responses are sent as JSON objects. Commands and responses are sent as JSON objects.
The JSON should be minified and must NOT contain any `\n` characters.
Commands will have a `command` key, and a `subcommand` key when part of a sub-command. Commands will have a `command` key, and a `subcommand` key when part of a sub-command.
The message buffer is currently limited to `1024` bytes.
Particularly large messages will be truncated or cause an error.
The full spec can be found below. The full spec can be found below.
## Libraries ## Libraries

View file

@ -25,7 +25,7 @@ Displays the toggle state of the capslock, num lock and scroll lock keys, and th
| `icons.num_off` | `string` or [image](images) | `''` | Icon to show for disabled num lock indicator. | | `icons.num_off` | `string` or [image](images) | `''` | Icon to show for disabled num lock indicator. |
| `icons.scroll_on` | `string` or [image](images) | `` | Icon to show for enabled scroll lock indicator. | | `icons.scroll_on` | `string` or [image](images) | `` | Icon to show for enabled scroll lock indicator. |
| `icons.scroll_off` | `string` or [image](images) | `''` | Icon to show for disabled scroll lock indicator. | | `icons.scroll_off` | `string` or [image](images) | `''` | Icon to show for disabled scroll lock indicator. |
| `icons.layout_map` | `Map<string, string or image>` | `{}` | Map of icons or labels to show for a particular keyboard layout. Layouts use their actual name if not present in the map. | | `icons.layout_map` | `Map<string, string or image>` | `{}` | Map of icons or labels to show for a particular keyboard layout. Layouts use their actual name if not present in the map. Layouts are matched in the order they appear in the map. If a pattern to match ends with a `*`, it acts as a wildcard, matching any layout name that begins with the part before the `*`. |
| `seat` | `string` | `seat0` | ID of the Wayland seat to attach to. | | `seat` | `string` | `seat0` | ID of the Wayland seat to attach to. |
<details> <details>

View file

@ -194,12 +194,12 @@ The list of available functions is shown below:
It is also possible to get only a single value from the set by specifying a name instead of a function. It is also possible to get only a single value from the set by specifying a name instead of a function.
| Token category | Valid name | | Token category | Valid name |
|----------------|-------------------------------------------------------------------------| |----------------|------------------------------------------|
| CPU | A CPU thread, eg `cpu0`, `cpu1`, ... | | CPU | A CPU thread, eg `cpu0`, `cpu1`, ... |
| Temperature | A sensor name, eg `CPUTIN`. These line up with the output of `sensors`. | | Temperature | A sensor name, eg `CPUTIN`. |
| Disk | A disk mountpoint, eg `/`, `/home`, ... | | Disk | A disk mountpoint, eg `/`, `/home`, ... |
| Network | An adapter name, eg `eth0` or `enp30s0`. | | Network | An adapter name, eg `eth0` or `enp30s0`. |
To specify a name or function, use a `@`. For example, to show disk percent for `/home`: To specify a name or function, use a `@`. For example, to show disk percent for `/home`:
@ -214,6 +214,22 @@ To show total CPU utilization where each core represents 100% (like `htop` etc):
"{cpu_percent@sum}%" "{cpu_percent@sum}%"
``` ```
> [!TIP]
> Available values can be queried over IPC using the CLI.
> This can be particularly useful for sensors, which tend not to have obvious names.
>
> ```shell
> ironbar var list sysinfo.temp_c
> ```
>
> Some usual cases to look out for:
>
> - `k10temp` is an AMD CPU internal sensor
> - Motherboard chipsets tend to prefix their sensors accordingly. For example, `CPUTIN`, `nct6687 CPU`, `asusec AMD`.
> - `amdgpu` is as it suggests.
>
> Sensor names are pulled from `hwmon` and should vaguely line up with the output of `sensors`
#### Prefixes and units #### Prefixes and units
For tokens which return an appropriate unit, you can specify the SI prefix (or unit in some special cases). For tokens which return an appropriate unit, you can specify the SI prefix (or unit in some special cases).

58
flake.lock generated
View file

@ -1,5 +1,27 @@
{ {
"nodes": { "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-compat": { "flake-compat": {
"locked": { "locked": {
"lastModified": 1747046372, "lastModified": 1747046372,
@ -17,14 +39,15 @@
}, },
"naersk": { "naersk": {
"inputs": { "inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1745925850, "lastModified": 1752689277,
"narHash": "sha256-cyAAMal0aPrlb1NgzMxZqeN1mAJ2pJseDhm2m6Um8T0=", "narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
"owner": "nix-community", "owner": "nix-community",
"repo": "naersk", "repo": "naersk",
"rev": "38bc60bbc157ae266d4a0c96671c6c742ee17a5f", "rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -50,11 +73,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1751251929, "lastModified": 1752077645,
"narHash": "sha256-IJWIzZSkBsDzS7iS/iwSwur+xFkWqeLYC4kdf8ObtOM=", "narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "b95255df2360a45ddbb03817a68869d5cb01bf96", "rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -66,11 +89,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1751011381, "lastModified": 1753694789,
"narHash": "sha256-krGXKxvkBhnrSC/kGBmg5MyupUUT5R6IBCLEzx9jhMM=", "narHash": "sha256-cKgvtz6fKuK1Xr5LQW/zOUiAC0oSQoA9nOISB0pJZqM=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "30e2e2857ba47844aa71991daa6ed1fc678bcbb7", "rev": "dc9637876d0dcc8c9e5e22986b857632effeb727",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -87,6 +110,23 @@
"nix-systems": "nix-systems", "nix-systems": "nix-systems",
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
} }
},
"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"
}
} }
}, },
"root": "root", "root": "root",

View file

@ -1,13 +1,18 @@
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType}; use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Event { pub enum ClientToModuleEvent {
DeviceAdded { DeviceChanged {
interface: String, number: u32,
},
DeviceStateChanged {
interface: String,
r#type: DeviceType, r#type: DeviceType,
state: DeviceState, new_state: DeviceState,
},
DeviceRemoved {
number: u32,
}, },
} }
#[derive(Debug, Clone)]
pub enum ModuleToClientEvent {
NewController,
}

View file

@ -1,15 +1,17 @@
use color_eyre::Result; use color_eyre::Result;
use color_eyre::eyre::Ok; use color_eyre::eyre::Ok;
use futures_lite::StreamExt; use futures_lite::StreamExt;
use std::collections::HashSet; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::broadcast; use tokio::sync::{RwLock, broadcast};
use tokio::task::JoinHandle;
use tracing::debug;
use zbus::Connection; use zbus::Connection;
use zbus::zvariant::{ObjectPath, Str}; use zbus::zvariant::ObjectPath;
use crate::clients::ClientResult; use crate::clients::ClientResult;
use crate::clients::networkmanager::dbus::{DbusProxy, DeviceDbusProxy}; use crate::clients::networkmanager::dbus::{DbusProxy, DeviceDbusProxy};
use crate::clients::networkmanager::event::Event; use crate::clients::networkmanager::event::{ClientToModuleEvent, ModuleToClientEvent};
use crate::{register_fallible_client, spawn}; use crate::{register_fallible_client, spawn};
pub mod dbus; pub mod dbus;
@ -17,102 +19,221 @@ pub mod event;
#[derive(Debug)] #[derive(Debug)]
pub struct Client { pub struct Client {
tx: broadcast::Sender<Event>, inner: &'static ClientInner,
} }
impl Client { impl Client {
fn new() -> Result<Client> { fn new() -> Client {
let (tx, _) = broadcast::channel(64); let inner = Box::leak(Box::new(ClientInner::new()));
Ok(Client { tx }) Client { inner }
} }
fn run(&self) -> Result<()> { fn run(&self) -> Result<()> {
let tx = self.tx.clone(); self.inner.run()
spawn(async move { }
let dbus_connection = Connection::system().await?;
let root = DbusProxy::new(&dbus_connection).await?;
let mut devices = HashSet::new(); pub fn subscribe(&self) -> broadcast::Receiver<ClientToModuleEvent> {
self.inner.subscribe()
}
let mut devices_changes = root.receive_all_devices_changed().await; pub fn get_sender(&self) -> broadcast::Sender<ModuleToClientEvent> {
while let Some(devices_change) = devices_changes.next().await { self.inner.get_sender()
// The new list of devices from dbus, not to be confused with the added devices below }
let new_devices = HashSet::from_iter(devices_change.get().await?); }
let added_devices = new_devices.difference(&devices); #[derive(Debug)]
for added_device in added_devices { struct ClientInner {
spawn(watch_device(added_device.to_owned(), tx.clone())); controller_sender: broadcast::Sender<ClientToModuleEvent>,
} sender: broadcast::Sender<ModuleToClientEvent>,
device_watchers: RwLock<HashMap<ObjectPath<'static>, DeviceWatcher>>,
dbus_connection: RwLock<Option<Connection>>,
}
let removed_devices = devices.difference(&new_devices); #[derive(Clone, Debug)]
// TODO: Cook up some way to notify closures for removed devices to exit struct DeviceWatcher {
state_watcher: Arc<JoinHandle<Result<()>>>,
}
devices = new_devices; impl ClientInner {
} fn new() -> ClientInner {
let (controller_sender, _) = broadcast::channel(64);
let (sender, _) = broadcast::channel(8);
let device_watchers = RwLock::new(HashMap::new());
let dbus_connection = RwLock::new(None);
ClientInner {
controller_sender,
sender,
device_watchers,
dbus_connection,
}
}
Ok(()) fn run(&'static self) -> Result<()> {
}); debug!("Client running");
spawn(self.watch_devices_list());
let receiver = self.sender.subscribe();
spawn(self.handle_received_events(receiver));
Ok(()) Ok(())
} }
pub fn subscribe(&self) -> broadcast::Receiver<Event> { fn subscribe(&self) -> broadcast::Receiver<ClientToModuleEvent> {
self.tx.subscribe() self.controller_sender.subscribe()
}
fn get_sender(&self) -> broadcast::Sender<ModuleToClientEvent> {
self.sender.clone()
}
async fn watch_devices_list(&'static self) -> Result<()> {
debug!("D-Bus devices list watcher starting");
let root = DbusProxy::new(&self.dbus_connection().await?).await?;
let mut devices_changes = root.receive_all_devices_changed().await;
while let Some(devices_change) = devices_changes.next().await {
// The new list of devices from dbus, not to be confused with the added devices below
let new_device_paths = devices_change
.get()
.await?
.iter()
.map(ObjectPath::to_owned)
.collect::<HashSet<_>>();
let mut watchers = self.device_watchers.write().await;
let device_paths = watchers.keys().cloned().collect::<HashSet<_>>();
let added_device_paths = new_device_paths.difference(&device_paths);
for added_device_path in added_device_paths {
debug_assert!(!watchers.contains_key(added_device_path));
let watcher = self.watch_device(added_device_path.clone()).await?;
watchers.insert(added_device_path.clone(), watcher);
}
let removed_device_paths = device_paths.difference(&new_device_paths);
for removed_device_path in removed_device_paths {
let watcher = watchers
.get(removed_device_path)
.expect("Device to be removed should be present in watchers");
watcher.state_watcher.abort();
watchers.remove(removed_device_path);
let number = get_number_from_dbus_path(removed_device_path);
self.controller_sender
.send(ClientToModuleEvent::DeviceRemoved { number })?;
debug!("D-bus device watchers for {} stopped", removed_device_path);
}
}
Ok(())
}
async fn handle_received_events(
&'static self,
mut receiver: broadcast::Receiver<ModuleToClientEvent>,
) -> Result<()> {
while let Result::Ok(event) = receiver.recv().await {
match event {
ModuleToClientEvent::NewController => {
debug!("Client received NewController event");
for device_path in self.device_watchers.read().await.keys() {
let dbus_connection = &self.dbus_connection().await?;
let device = DeviceDbusProxy::new(dbus_connection, device_path).await?;
let number = get_number_from_dbus_path(device_path);
let r#type = device.device_type().await?;
let new_state = device.state().await?;
self.controller_sender
.send(ClientToModuleEvent::DeviceChanged {
number,
r#type,
new_state,
})?;
}
}
}
}
Ok(())
}
async fn watch_device(&'static self, path: ObjectPath<'_>) -> Result<DeviceWatcher> {
let dbus_connection = &self.dbus_connection().await?;
let proxy = DeviceDbusProxy::new(dbus_connection, path.to_owned()).await?;
let number = get_number_from_dbus_path(&path);
let r#type = proxy.device_type().await?;
let new_state = proxy.state().await?;
// Notify modules that the device exists even if its properties don't change
self.controller_sender
.send(ClientToModuleEvent::DeviceChanged {
number,
r#type: r#type.clone(),
new_state,
})?;
let state_watcher = Arc::new(spawn(self.watch_device_state(proxy)));
Ok(DeviceWatcher { state_watcher })
}
async fn watch_device_state(&'static self, proxy: DeviceDbusProxy<'_>) -> Result<()> {
let path = proxy.inner().path();
debug!("D-Bus device state watcher for {} starting", path);
let number = get_number_from_dbus_path(path);
let r#type = proxy.device_type().await?;
let mut changes = proxy.receive_state_changed().await;
while let Some(change) = changes.next().await {
let new_state = change.get().await?;
self.controller_sender
.send(ClientToModuleEvent::DeviceChanged {
number,
r#type: r#type.clone(),
new_state,
})?;
}
Ok(())
}
async fn dbus_connection(&self) -> Result<Connection> {
let dbus_connection_guard = self.dbus_connection.read().await;
if let Some(dbus_connection) = &*dbus_connection_guard {
Ok(dbus_connection.clone())
} else {
// Yes it's a bit awkward to first obtain a read lock and then a write lock but it
// needs to happen only once, and after that all read lock acquisitions will be
// instant
drop(dbus_connection_guard);
let dbus_connection = Connection::system().await?;
*self.dbus_connection.write().await = Some(dbus_connection.clone());
Ok(dbus_connection)
}
} }
} }
pub fn create_client() -> ClientResult<Client> { pub fn create_client() -> ClientResult<Client> {
let client = Arc::new(Client::new()?); let client = Arc::new(Client::new());
client.run()?; client.run()?;
Ok(client) Ok(client)
} }
async fn watch_device(device_path: ObjectPath<'_>, tx: broadcast::Sender<Event>) -> Result<()> { fn get_number_from_dbus_path(path: &ObjectPath) -> u32 {
let dbus_connection = Connection::system().await?; let (_, number_str) = path
let device = DeviceDbusProxy::new(&dbus_connection, device_path.to_owned()).await?; .rsplit_once('/')
.expect("Path must have at least two segments to contain an object number");
let interface = device.interface().await?; number_str
tx.send(Event::DeviceAdded { .parse()
interface: interface.to_string(), .expect("Last segment was not a positive integer")
})?;
spawn(watch_device_state(
device_path.to_owned(),
interface.to_owned(),
tx.clone(),
));
Ok(())
}
async fn watch_device_state(
device_path: ObjectPath<'_>,
interface: Str<'_>,
tx: broadcast::Sender<Event>,
) -> Result<()> {
let dbus_connection = Connection::system().await?;
let device = DeviceDbusProxy::new(&dbus_connection, &device_path).await?;
let r#type = device.device_type().await?;
// Send an event communicating the initial state
let state = device.state().await?;
tx.send(Event::DeviceStateChanged {
interface: interface.to_string(),
r#type: r#type.clone(),
state,
})?;
let mut state_changes = device.receive_state_changed().await;
while let Some(state_change) = state_changes.next().await {
let state = state_change.get().await?;
tx.send(Event::DeviceStateChanged {
interface: interface.to_string(),
r#type: r#type.clone(),
state,
})?;
}
Ok(())
} }
register_fallible_client!(Client, network_manager); register_fallible_client!(Client, network_manager);

View file

@ -1,165 +0,0 @@
use crate::clients::networkmanager::dbus::{
ActiveConnectionDbusProxy, DeviceDbusProxy, DeviceState, DeviceType,
};
use color_eyre::Result;
use std::collections::HashMap;
use zbus::zvariant::ObjectPath;
type PathMap<'l, ValueType> = HashMap<ObjectPath<'l>, ValueType>;
#[derive(Clone, Debug)]
pub struct State {
pub wired: WiredState,
pub wifi: WifiState,
pub cellular: CellularState,
pub vpn: VpnState,
}
#[derive(Clone, Debug)]
pub enum WiredState {
Connected,
Disconnected,
NotPresent,
Unknown,
}
#[derive(Clone, Debug)]
pub enum WifiState {
Connected(WifiConnectedState),
Disconnected,
Disabled,
NotPresent,
Unknown,
}
#[derive(Clone, Debug)]
pub struct WifiConnectedState {
pub ssid: String,
}
#[derive(Clone, Debug)]
pub enum CellularState {
Connected,
Disconnected,
Disabled,
NotPresent,
Unknown,
}
#[derive(Clone, Debug)]
pub enum VpnState {
Connected(VpnConnectedState),
Disconnected,
Unknown,
}
#[derive(Clone, Debug)]
pub struct VpnConnectedState {
pub name: String,
}
pub(super) async fn determine_wired_state(
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
) -> Result<WiredState> {
let mut present = false;
let mut connected = false;
for device in devices.values() {
if device.device_type().await? == DeviceType::Ethernet {
present = true;
if device.state().await?.is_enabled() {
connected = true;
break;
}
}
}
if connected {
Ok(WiredState::Connected)
} else if present {
Ok(WiredState::Disconnected)
} else {
Ok(WiredState::NotPresent)
}
}
pub(super) async fn determine_wifi_state(
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
) -> Result<WifiState> {
let mut present = false;
let mut enabled = false;
let mut connected = false;
for device in devices.values() {
if device.device_type().await? == DeviceType::Wifi {
present = true;
if device.state().await?.is_enabled() {
enabled = true;
if device.state().await? == DeviceState::Activated {
connected = true;
break;
}
}
}
}
if connected {
Ok(WifiState::Connected(WifiConnectedState {
// TODO: Implement obtaining SSID
ssid: "unknown".into(),
}))
} else if enabled {
Ok(WifiState::Disconnected)
} else if present {
Ok(WifiState::Disabled)
} else {
Ok(WifiState::NotPresent)
}
}
pub(super) async fn determine_cellular_state(
devices: &PathMap<'_, DeviceDbusProxy<'_>>,
) -> Result<CellularState> {
let mut present = false;
let mut enabled = false;
let mut connected = false;
for device in devices.values() {
if device.device_type().await? == DeviceType::Modem {
present = true;
if device.state().await?.is_enabled() {
enabled = true;
if device.state().await? == DeviceState::Activated {
connected = true;
break;
}
}
}
}
if connected {
Ok(CellularState::Connected)
} else if enabled {
Ok(CellularState::Disconnected)
} else if present {
Ok(CellularState::Disabled)
} else {
Ok(CellularState::NotPresent)
}
}
pub(super) async fn determine_vpn_state(
active_connections: &PathMap<'_, ActiveConnectionDbusProxy<'_>>,
) -> Result<VpnState> {
for connection in active_connections.values() {
match connection.type_().await?.as_str() {
"vpn" | "wireguard" => {
return Ok(VpnState::Connected(VpnConnectedState {
name: "unknown".into(),
}));
}
_ => {}
}
}
Ok(VpnState::Disconnected)
}

View file

@ -52,16 +52,11 @@ where
} }
#[cfg(feature = "schema")] #[cfg(feature = "schema")]
pub fn schema_layer(generator: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema { pub fn schema_layer(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
use schemars::JsonSchema; schemars::json_schema!({
let mut schema: schemars::schema::SchemaObject = <String>::json_schema(generator).into(); "type": "string",
schema.enum_values = Some(vec![ "enum": ["background", "bottom", "top", "overlay"],
"background".into(), })
"bottom".into(),
"top".into(),
"overlay".into(),
]);
schema.into()
} }
impl BarPosition { impl BarPosition {

View file

@ -3,9 +3,10 @@ use color_eyre::{Help, Report, Result};
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Stdio}; use std::process::Stdio;
use std::sync::Arc; use std::sync::Arc;
use tokio::io::{AsyncBufReadExt, BufReader}; use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::{debug, error}; use tracing::{debug, error};
use walkdir::{DirEntry, WalkDir}; use walkdir::{DirEntry, WalkDir};
@ -238,6 +239,7 @@ impl DesktopFiles {
/// Checks file contents for an exact or partial match of the provided input. /// Checks file contents for an exact or partial match of the provided input.
async fn find_by_file_contents(&self, app_id: &str) -> Result<Option<DesktopFile>> { async fn find_by_file_contents(&self, app_id: &str) -> Result<Option<DesktopFile>> {
let mut files = self.files.lock().await; let mut files = self.files.lock().await;
let app_id_lower = app_id.to_lowercase();
// first pass - check name for exact match // first pass - check name for exact match
for (_, file_ref) in files.iter_mut() { for (_, file_ref) in files.iter_mut() {
@ -253,7 +255,7 @@ impl DesktopFiles {
for (_, file_ref) in files.iter_mut() { for (_, file_ref) in files.iter_mut() {
let file = file_ref.get().await?; let file = file_ref.get().await?;
if let Some(name) = &file.name { if let Some(name) = &file.name {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
@ -264,19 +266,19 @@ impl DesktopFiles {
let file = file_ref.get().await?; let file = file_ref.get().await?;
if let Some(name) = &file.exec { if let Some(name) = &file.exec {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
if let Some(name) = &file.startup_wm_class { if let Some(name) = &file.startup_wm_class {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
if let Some(name) = &file.icon { if let Some(name) = &file.icon {
if name.to_lowercase().contains(app_id) { if name.to_lowercase().contains(&app_id_lower) {
return Ok(Some(file)); return Ok(Some(file));
} }
} }
@ -325,21 +327,37 @@ fn files(dir: &Path) -> Vec<PathBuf> {
} }
/// Starts a `.desktop` file with the provided formatted command. /// Starts a `.desktop` file with the provided formatted command.
pub fn open_program(file_name: &str, str: &str) { pub async fn open_program(file_name: &str, launch_command: &str) {
let expanded = str.replace("{app_name}", file_name); let expanded = launch_command.replace("{app_name}", file_name);
let launch_command_parts: Vec<&str> = expanded.split_whitespace().collect(); let launch_command_parts: Vec<&str> = expanded.split_whitespace().collect();
if let Err(err) = Command::new(&launch_command_parts[0])
debug!("running {launch_command_parts:?}");
let exit_status = match Command::new(launch_command_parts[0])
.args(&launch_command_parts[1..]) .args(&launch_command_parts[1..])
.stdin(Stdio::null())
.stdout(Stdio::null()) .stdout(Stdio::null())
.stderr(Stdio::null()) .stderr(Stdio::null())
.kill_on_drop(true)
.spawn() .spawn()
{ {
error!( Ok(mut child) => Some(child.wait().await),
"{:?}", Err(err) => {
Report::new(err) error!(
.wrap_err("Failed to run launch command.") "{:?}",
.suggestion("Perhaps the applications file is invalid?") Report::new(err)
); .wrap_err("Failed to run launch command.")
.suggestion("Perhaps the desktop file is invalid or orphaned?")
);
None
}
};
match exit_status {
Some(Ok(exit_status)) if !exit_status.success() => {
error!("received non-success exit status running {launch_command_parts:?}")
}
Some(Err(err)) => error!("{err:?}"),
_ => {}
} }
} }

View file

@ -2,7 +2,7 @@ use super::Ipc;
use crate::ipc::{Command, Response}; use crate::ipc::{Command, Response};
use color_eyre::Result; use color_eyre::Result;
use color_eyre::{Help, Report}; use color_eyre::{Help, Report};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::UnixStream; use tokio::net::UnixStream;
impl Ipc { impl Ipc {
@ -16,18 +16,20 @@ impl Ipc {
.suggestion("Is Ironbar running?")), .suggestion("Is Ironbar running?")),
}?; }?;
let write_buffer = serde_json::to_vec(&command)?; let mut write_buffer = serde_json::to_vec(&command)?;
if debug { if debug {
eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?); eprintln!("REQUEST JSON: {}", serde_json::to_string(&command)?);
} }
write_buffer.push(b'\n');
stream.write_all(&write_buffer).await?; stream.write_all(&write_buffer).await?;
let mut read_buffer = vec![0; 1024]; let mut read_buffer = String::new();
let bytes = stream.read(&mut read_buffer).await?; let mut reader = BufReader::new(stream);
let bytes = reader.read_line(&mut read_buffer).await?;
let response = serde_json::from_slice(&read_buffer[..bytes])?; let response = serde_json::from_str(&read_buffer[..bytes])?;
Ok(response) Ok(response)
} }
} }

View file

@ -8,10 +8,10 @@ use std::rc::Rc;
use color_eyre::{Report, Result}; use color_eyre::{Report, Result};
use gtk::Application; use gtk::Application;
use gtk::prelude::*; use gtk::prelude::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::net::{UnixListener, UnixStream}; use tokio::net::{UnixListener, UnixStream};
use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, trace, warn};
use super::Ipc; use super::Ipc;
use crate::channels::{AsyncSenderExt, MpscReceiverExt}; use crate::channels::{AsyncSenderExt, MpscReceiverExt};
@ -52,11 +52,13 @@ impl Ipc {
loop { loop {
match listener.accept().await { match listener.accept().await {
Ok((stream, _addr)) => { Ok((stream, _addr)) => {
debug!("handling incoming connection");
if let Err(err) = if let Err(err) =
Self::handle_connection(stream, &cmd_tx, &mut res_rx).await Self::handle_connection(stream, &cmd_tx, &mut res_rx).await
{ {
error!("{err:?}"); error!("{err:?}");
} }
debug!("done");
} }
Err(err) => { Err(err) => {
error!("{err:?}"); error!("{err:?}");
@ -80,10 +82,16 @@ impl Ipc {
cmd_tx: &Sender<Command>, cmd_tx: &Sender<Command>,
res_rx: &mut Receiver<Response>, res_rx: &mut Receiver<Response>,
) -> Result<()> { ) -> Result<()> {
let (mut stream_read, mut stream_write) = stream.split(); trace!("awaiting readable state");
stream.readable().await?;
let mut read_buffer = vec![0; 1024]; let mut read_buffer = Vec::with_capacity(1024);
let bytes = stream_read.read(&mut read_buffer).await?;
let mut reader = BufReader::new(&mut stream);
trace!("reading bytes");
let bytes = reader.read_until(b'\n', &mut read_buffer).await?;
debug!("read {} bytes", bytes);
// FIXME: Error on invalid command // FIXME: Error on invalid command
let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?; let command = serde_json::from_slice::<Command>(&read_buffer[..bytes])?;
@ -95,10 +103,18 @@ impl Ipc {
.recv() .recv()
.await .await
.unwrap_or(Response::Err { message: None }); .unwrap_or(Response::Err { message: None });
let res = serde_json::to_vec(&res)?;
stream_write.write_all(&res).await?; let mut res = serde_json::to_vec(&res)?;
stream_write.shutdown().await?; res.push(b'\n');
trace!("awaiting writable state");
stream.writable().await?;
debug!("writing {} bytes", res.len());
stream.write_all(&res).await?;
trace!("bytes written, shutting down stream");
stream.shutdown().await?;
Ok(()) Ok(())
} }

View file

@ -1,8 +1,7 @@
use std::collections::HashMap;
use color_eyre::Result; use color_eyre::Result;
use color_eyre::eyre::Report; use color_eyre::eyre::Report;
use gtk::prelude::*; use gtk::prelude::*;
use indexmap::IndexMap;
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::{debug, trace}; use tracing::{debug, trace};
@ -129,7 +128,7 @@ struct Icons {
/// } /// }
/// ``` /// ```
#[serde(default)] #[serde(default)]
layout_map: HashMap<String, String>, layout_map: IndexMap<String, String>,
} }
impl Default for Icons { impl Default for Icons {
@ -141,7 +140,7 @@ impl Default for Icons {
num_off: String::new(), num_off: String::new(),
scroll_on: default_icon_scroll(), scroll_on: default_icon_scroll(),
scroll_off: String::new(), scroll_off: String::new(),
layout_map: HashMap::new(), layout_map: IndexMap::new(),
} }
} }
} }
@ -338,7 +337,19 @@ impl Module<gtk::Box> for KeyboardModule {
} }
} }
KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => { KeyboardUpdate::Layout(KeyboardLayoutUpdate(language)) => {
let text = icons.layout_map.get(&language).unwrap_or(&language); let text = icons
.layout_map
.iter()
.find_map(|(pattern, display_text)| {
let is_match = if pattern.ends_with("*") {
language.starts_with(&pattern[..pattern.len() - 1])
} else {
pattern == &language
};
is_match.then(|| display_text)
})
.unwrap_or(&language);
layout_button.set_label(text); layout_button.set_label(text);
} }
}); });

View file

@ -244,21 +244,43 @@ impl Module<gtk::Box> for LauncherModule {
let tx2 = context.tx.clone(); let tx2 = context.tx.clone();
let wl = context.client::<wayland::Client>(); let wl = context.client::<wayland::Client>();
let desktop_files = context.ironbar.desktop_files();
spawn(async move { spawn(async move {
let items = items2; let items = items2;
let tx = tx2; let tx = tx2;
// Build app_id mapping once at startup
let mut app_id_map = IndexMap::<String, String>::new();
{
let favorites: Vec<_> = lock!(items).keys().cloned().collect();
for fav in favorites {
if let Ok(Some(file)) = desktop_files.find(&fav).await {
if let Some(wm_class) = file.startup_wm_class {
app_id_map.insert(wm_class, fav);
}
}
}
}
let resolve_app_id = |app_id: &str| {
app_id_map
.get(app_id)
.cloned()
.unwrap_or_else(|| app_id.to_string())
};
let mut wlrx = wl.subscribe_toplevels(); let mut wlrx = wl.subscribe_toplevels();
let handles = wl.toplevel_info_all(); let handles = wl.toplevel_info_all();
for info in handles { for info in handles {
let mut items = lock!(items); let mut items = lock!(items);
let item = items.get_mut(&info.app_id); let app_id = resolve_app_id(&info.app_id);
if let Some(item) = item { if let Some(item) = items.get_mut(&app_id) {
item.merge_toplevel(info.clone()); item.merge_toplevel(info.clone());
} else { } else {
let item = Item::from(info.clone()); let mut item = Item::from(info.clone());
items.insert(info.app_id.clone(), item); item.app_id = app_id.clone();
items.insert(app_id, item);
} }
} }
@ -284,14 +306,14 @@ impl Module<gtk::Box> for LauncherModule {
match event { match event {
ToplevelEvent::New(info) => { ToplevelEvent::New(info) => {
let app_id = info.app_id.clone(); let app_id = resolve_app_id(&info.app_id);
let new_item = { let new_item = {
let mut items = lock!(items); let mut items = lock!(items);
let item = items.get_mut(&info.app_id); match items.get_mut(&app_id) {
match item {
None => { None => {
let item: Item = info.into(); let mut item: Item = info.into();
item.app_id = app_id.clone();
items.insert(app_id.clone(), item.clone()); items.insert(app_id.clone(), item.clone());
ItemOrWindow::Item(item) ItemOrWindow::Item(item)
@ -313,9 +335,10 @@ impl Module<gtk::Box> for LauncherModule {
}?; }?;
} }
ToplevelEvent::Update(info) => { ToplevelEvent::Update(info) => {
let app_id = resolve_app_id(&info.app_id);
// check if open, as updates can be sent as program closes // check if open, as updates can be sent as program closes
// if it's a focused favourite closing, it otherwise incorrectly re-focuses. // if it's a focused favourite closing, it otherwise incorrectly re-focuses.
let is_open = if let Some(item) = lock!(items).get_mut(&info.app_id) { let is_open = if let Some(item) = lock!(items).get_mut(&app_id) {
item.set_window_focused(info.id, info.focused); item.set_window_focused(info.id, info.focused);
item.set_window_name(info.id, info.title.clone()); item.set_window_name(info.id, info.title.clone());
@ -325,27 +348,27 @@ impl Module<gtk::Box> for LauncherModule {
}; };
send_update(LauncherUpdate::Focus( send_update(LauncherUpdate::Focus(
info.app_id.clone(), app_id.clone(),
is_open && info.focused, is_open && info.focused,
)) ))
.await?; .await?;
send_update(LauncherUpdate::Title( send_update(LauncherUpdate::Title(
info.app_id.clone(), app_id.clone(),
info.id, info.id,
info.title.clone(), info.title.clone(),
)) ))
.await?; .await?;
} }
ToplevelEvent::Remove(info) => { ToplevelEvent::Remove(info) => {
let app_id = resolve_app_id(&info.app_id);
let remove_item = { let remove_item = {
let mut items = lock!(items); let mut items = lock!(items);
let item = items.get_mut(&info.app_id); match items.get_mut(&app_id) {
match item {
Some(item) => { Some(item) => {
item.unmerge_toplevel(&info); item.unmerge_toplevel(&info);
if item.windows.is_empty() { if item.windows.is_empty() {
items.shift_remove(&info.app_id); items.shift_remove(&app_id);
Some(ItemOrWindowId::Item) Some(ItemOrWindowId::Item)
} else { } else {
Some(ItemOrWindowId::Window) Some(ItemOrWindowId::Window)
@ -357,15 +380,11 @@ impl Module<gtk::Box> for LauncherModule {
match remove_item { match remove_item {
Some(ItemOrWindowId::Item) => { Some(ItemOrWindowId::Item) => {
send_update(LauncherUpdate::RemoveItem(info.app_id.clone())) send_update(LauncherUpdate::RemoveItem(app_id.clone())).await?;
.await?;
} }
Some(ItemOrWindowId::Window) => { Some(ItemOrWindowId::Window) => {
send_update(LauncherUpdate::RemoveWindow( send_update(LauncherUpdate::RemoveWindow(app_id.clone(), info.id))
info.app_id.clone(), .await?;
info.id,
))
.await?;
} }
None => {} None => {}
} }
@ -388,7 +407,7 @@ impl Module<gtk::Box> for LauncherModule {
if let ItemEvent::OpenItem(app_id) = event { if let ItemEvent::OpenItem(app_id) = event {
match desktop_files.find(&app_id).await { match desktop_files.find(&app_id).await {
Ok(Some(file)) => { Ok(Some(file)) => {
open_program(&file.file_name, &launch_command_str); open_program(&file.file_name, &launch_command_str).await;
} }
Ok(None) => warn!("Could not find applications file for {}", app_id), Ok(None) => warn!("Could not find applications file for {}", app_id),
Err(err) => error!("Failed to find parse file for {}: {}", app_id, err), Err(err) => error!("Failed to find parse file for {}: {}", app_id, err),

View file

@ -100,7 +100,11 @@ where
let tx = tx.clone(); let tx = tx.clone();
button.connect_clicked(move |_button| { button.connect_clicked(move |_button| {
open_program(&file_name, &command); // TODO: this needs refactoring to call open from the controller
let file_name = file_name.clone();
let command = command.clone();
spawn(async move { open_program(&file_name, &command).await });
sub_menu.hide(); sub_menu.hide();
tx.send_spawn(ModuleUpdateEvent::ClosePopup); tx.send_spawn(ModuleUpdateEvent::ClosePopup);

View file

@ -1,6 +1,6 @@
use crate::clients::networkmanager::Client; use crate::clients::networkmanager::Client;
use crate::clients::networkmanager::dbus::{DeviceState, DeviceType}; use crate::clients::networkmanager::dbus::{DeviceState, DeviceType};
use crate::clients::networkmanager::event::Event; use crate::clients::networkmanager::event::{ClientToModuleEvent, ModuleToClientEvent};
use crate::config::CommonConfig; use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::IronbarGtkExt;
use crate::image::Provider; use crate::image::Provider;
@ -13,6 +13,7 @@ use gtk::{Image, Orientation};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap; use std::collections::HashMap;
use tokio::sync::{broadcast, mpsc}; use tokio::sync::{broadcast, mpsc};
use tracing::debug;
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
@ -29,7 +30,7 @@ const fn default_icon_size() -> i32 {
} }
impl Module<gtk::Box> for NetworkManagerModule { impl Module<gtk::Box> for NetworkManagerModule {
type SendMessage = Event; type SendMessage = ClientToModuleEvent;
type ReceiveMessage = (); type ReceiveMessage = ();
module_impl!("network_manager"); module_impl!("network_manager");
@ -37,19 +38,24 @@ impl Module<gtk::Box> for NetworkManagerModule {
fn spawn_controller( fn spawn_controller(
&self, &self,
_info: &ModuleInfo, _info: &ModuleInfo,
context: &WidgetContext<Event, ()>, context: &WidgetContext<ClientToModuleEvent, ()>,
_rx: mpsc::Receiver<()>, _widget_receiver: mpsc::Receiver<()>,
) -> Result<()> { ) -> Result<()> {
let client = context.try_client::<Client>()?; let client = context.try_client::<Client>()?;
// Should we be using context.tx with ModuleUpdateEvent::Update instead? // Should we be using context.tx with ModuleUpdateEvent::Update instead?
let tx = context.update_tx.clone(); let widget_sender = context.update_tx.clone();
// Must be done here synchronously to avoid race condition
let mut client_rx = client.subscribe();
spawn(async move {
while let Result::Ok(event) = client_rx.recv().await {
tx.send(event)?;
}
// Must be done here otherwise we miss the response to our `NewController` event
let mut client_receiver = client.subscribe();
client
.get_sender()
.send(ModuleToClientEvent::NewController)?;
spawn(async move {
while let Result::Ok(event) = client_receiver.recv().await {
widget_sender.send(event)?;
}
Ok(()) Ok(())
}); });
@ -58,16 +64,17 @@ impl Module<gtk::Box> for NetworkManagerModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Event, ()>, context: WidgetContext<ClientToModuleEvent, ()>,
_info: &ModuleInfo, _info: &ModuleInfo,
) -> Result<ModuleParts<gtk::Box>> { ) -> Result<ModuleParts<gtk::Box>> {
// Must be done here otherwise we miss the response to our `NewController` event
let receiver = context.subscribe();
let container = gtk::Box::new(Orientation::Horizontal, 0); let container = gtk::Box::new(Orientation::Horizontal, 0);
// Must be done here synchronously to avoid race condition
let rx = context.subscribe();
// We cannot use recv_glib_async here because the lifetimes don't work out // We cannot use recv_glib_async here because the lifetimes don't work out
spawn_future_local(handle_update_events( spawn_future_local(handle_update_events(
rx, receiver,
container.clone(), container.clone(),
self.icon_size, self.icon_size,
context.ironbar.image_provider(), context.ironbar.image_provider(),
@ -78,31 +85,37 @@ impl Module<gtk::Box> for NetworkManagerModule {
} }
async fn handle_update_events( async fn handle_update_events(
mut rx: broadcast::Receiver<Event>, mut widget_receiver: broadcast::Receiver<ClientToModuleEvent>,
container: gtk::Box, container: gtk::Box,
icon_size: i32, icon_size: i32,
image_provider: Provider, image_provider: Provider,
) { ) -> Result<()> {
let mut icons = HashMap::<String, Image>::new(); // TODO: Ensure the visible icons are always in the same order
let mut icons = HashMap::<u32, Image>::new();
while let Result::Ok(event) = rx.recv().await { while let Result::Ok(event) = widget_receiver.recv().await {
match event { match event {
Event::DeviceAdded { interface, .. } => { ClientToModuleEvent::DeviceChanged {
let icon = Image::new(); number,
icon.add_class("icon");
container.add(&icon);
icons.insert(interface, icon);
}
Event::DeviceStateChanged {
interface,
r#type, r#type,
state, new_state,
} => { } => {
let icon = icons debug!(
.get(&interface) "Module widget received DeviceChanged event for number {}",
.expect("the icon for the interface to be present"); number
);
let icon: &_ = icons.entry(number).or_insert_with(|| {
debug!("Adding icon for device {}", number);
let icon = Image::new();
icon.add_class("icon");
container.add(&icon);
icon
});
// TODO: Make this configurable at runtime // TODO: Make this configurable at runtime
let icon_name = get_icon_for_device_state(&r#type, &state); let icon_name = get_icon_for_device_state(&r#type, &new_state);
match icon_name { match icon_name {
Some(icon_name) => { Some(icon_name) => {
image_provider image_provider
@ -115,45 +128,55 @@ async fn handle_update_events(
} }
} }
} }
}; ClientToModuleEvent::DeviceRemoved { number } => {
debug!(
"Module widget received DeviceRemoved event for number {}",
number
);
let icon = icons
.get(&number)
.expect("The icon for {} was about to be removed but was not present");
container.remove(icon);
icons.remove(&number);
}
}
} }
Ok(())
} }
fn get_icon_for_device_state(r#type: &DeviceType, state: &DeviceState) -> Option<&'static str> { fn get_icon_for_device_state(r#type: &DeviceType, state: &DeviceState) -> Option<&'static str> {
match r#type { match r#type {
DeviceType::Ethernet => match state { DeviceType::Ethernet => match state {
DeviceState::Unavailable => Some("icon:network-wired-disconnected-symbolic"), DeviceState::Unavailable
DeviceState::Disconnected => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::Disconnected
DeviceState::Prepare => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::Prepare
DeviceState::Config => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::Config
DeviceState::NeedAuth => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::NeedAuth
DeviceState::IpConfig => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::IpConfig
DeviceState::IpCheck => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::IpCheck
DeviceState::Secondaries => Some("icon:network-wired-disconnected-symbolic"), | DeviceState::Secondaries
| DeviceState::Deactivating
| DeviceState::Failed => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Activated => Some("icon:network-wired-symbolic"), DeviceState::Activated => Some("icon:network-wired-symbolic"),
DeviceState::Deactivating => Some("icon:network-wired-disconnected-symbolic"),
DeviceState::Failed => Some("icon:network-wired-disconnected-symbolic"),
_ => None, _ => None,
}, },
DeviceType::Wifi => match state { DeviceType::Wifi => match state {
DeviceState::Unavailable => Some("icon:network-wireless-hardware-disabled-symbolic"), DeviceState::Unavailable => Some("icon:network-wireless-hardware-disabled-symbolic"),
DeviceState::Disconnected => Some("icon:network-wireless-offline-symbolic"), DeviceState::Disconnected
DeviceState::Prepare => Some("icon:network-wireless-offline-symbolic"), | DeviceState::Prepare
DeviceState::Config => Some("icon:network-wireless-offline-symbolic"), | DeviceState::Config
DeviceState::NeedAuth => Some("icon:network-wireless-offline-symbolic"), | DeviceState::NeedAuth
DeviceState::IpConfig => Some("icon:network-wireless-offline-symbolic"), | DeviceState::IpConfig
DeviceState::IpCheck => Some("icon:network-wireless-offline-symbolic"), | DeviceState::IpCheck
DeviceState::Secondaries => Some("icon:network-wireless-offline-symbolic"), | DeviceState::Secondaries
| DeviceState::Deactivating
| DeviceState::Failed => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Activated => Some("icon:network-wireless-connected-symbolic"), DeviceState::Activated => Some("icon:network-wireless-connected-symbolic"),
DeviceState::Deactivating => Some("icon:network-wireless-offline-symbolic"),
DeviceState::Failed => Some("icon:network-wireless-offline-symbolic"),
_ => None, _ => None,
}, },
DeviceType::Tun => match state { DeviceType::Tun | DeviceType::Wireguard => match state {
DeviceState::Activated => Some("icon:network-vpn-symbolic"),
_ => None,
},
DeviceType::Wireguard => match state {
DeviceState::Activated => Some("icon:network-vpn-symbolic"), DeviceState::Activated => Some("icon:network-vpn-symbolic"),
_ => None, _ => None,
}, },

View file

@ -11,6 +11,7 @@ use std::collections::HashSet;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::ptr; use std::ptr;
use system_tray::item::IconPixmap;
/// Gets the GTK icon theme search paths by calling the FFI function. /// Gets the GTK icon theme search paths by calling the FFI function.
/// Conveniently returns the result as a `HashSet`. /// Conveniently returns the result as a `HashSet`.
@ -45,10 +46,10 @@ pub fn get_image(
icon_theme: &IconTheme, icon_theme: &IconTheme,
) -> Result<Image> { ) -> Result<Image> {
if !prefer_icons && item.icon_pixmap.is_some() { if !prefer_icons && item.icon_pixmap.is_some() {
get_image_from_pixmap(item, size) get_image_from_pixmap(item.icon_pixmap.as_deref(), size)
} else { } else {
get_image_from_icon_name(item, size, icon_theme) get_image_from_icon_name(item, size, icon_theme)
.or_else(|_| get_image_from_pixmap(item, size)) .or_else(|_| get_image_from_pixmap(item.icon_pixmap.as_deref(), size))
} }
} }
@ -81,12 +82,10 @@ fn get_image_from_icon_name(item: &TrayMenu, size: u32, icon_theme: &IconTheme)
/// which has 8 bits per sample and a bit stride of `4*width`. /// which has 8 bits per sample and a bit stride of `4*width`.
/// The Pixbuf expects RGBA32 format, so some channel shuffling /// The Pixbuf expects RGBA32 format, so some channel shuffling
/// is required. /// is required.
fn get_image_from_pixmap(item: &TrayMenu, size: u32) -> Result<Image> { fn get_image_from_pixmap(item: Option<&[IconPixmap]>, size: u32) -> Result<Image> {
const BITS_PER_SAMPLE: i32 = 8; const BITS_PER_SAMPLE: i32 = 8;
let pixmap = item let pixmap = item
.icon_pixmap
.as_ref()
.and_then(|pixmap| pixmap.first()) .and_then(|pixmap| pixmap.first())
.ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?; .ok_or_else(|| Report::msg("Failed to get pixmap from tray icon"))?;

View file

@ -181,9 +181,14 @@ fn on_update(
UpdateEvent::AttentionIcon(_icon) => { UpdateEvent::AttentionIcon(_icon) => {
warn!("received unimplemented NewAttentionIcon event"); warn!("received unimplemented NewAttentionIcon event");
} }
UpdateEvent::Icon(icon) => { UpdateEvent::Icon {
if icon.as_ref() != menu_item.icon_name() { icon_name,
menu_item.set_icon_name(icon); icon_pixmap,
} => {
menu_item.icon_pixmap = icon_pixmap;
if icon_name.as_ref() != menu_item.icon_name() {
menu_item.set_icon_name(icon_name);
match icon::get_image(menu_item, icon_size, prefer_icons, icon_theme) { match icon::get_image(menu_item, icon_size, prefer_icons, icon_theme) {
Ok(image) => menu_item.set_image(&image), Ok(image) => menu_item.set_image(&image),
Err(_) => menu_item.show_label(), Err(_) => menu_item.show_label(),

View file

@ -1,20 +1,30 @@
use crate::channels::{AsyncSenderExt, BroadcastReceiverExt}; use crate::channels::{AsyncSenderExt, BroadcastReceiverExt};
use crate::clients::volume::{self, Event}; use crate::clients::volume::{self, Event};
use crate::config::{CommonConfig, LayoutConfig, TruncateMode}; use crate::config::{CommonConfig, LayoutConfig, TruncateMode};
use crate::gtk_helpers::IronbarGtkExt; use crate::gtk_helpers::{IronbarGtkExt, IronbarLabelExt};
use crate::modules::{ use crate::modules::{
Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, PopupButton, WidgetContext, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
}; };
use crate::{lock, module_impl, spawn}; use crate::{lock, module_impl, spawn};
use glib::Propagation;
use gtk::pango::EllipsizeMode;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Button, Image, Label, Scale, ToggleButton}; use gtk::{Button, CellRendererText, ComboBoxText, Label, Orientation, Scale, ToggleButton};
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tracing::trace; use tracing::trace;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct VolumeModule { pub struct VolumeModule {
/// The format string to use for the widget button label.
/// For available tokens, see [below](#formatting-tokens).
///
/// **Default**: `{icon} {percentage}%`
#[serde(default = "default_format")]
format: String,
/// Maximum value to allow volume sliders to reach. /// Maximum value to allow volume sliders to reach.
/// Pulse supports values > 100 but this may result in distortion. /// Pulse supports values > 100 but this may result in distortion.
/// ///
@ -22,8 +32,11 @@ pub struct VolumeModule {
#[serde(default = "default_max_volume")] #[serde(default = "default_max_volume")]
max_volume: f64, max_volume: f64,
#[serde(default = "default_icon_size")] /// Volume state icons.
icon_size: i32, ///
/// See [icons](#icons).
#[serde(default)]
icons: Icons,
// -- Common -- // -- Common --
/// See [truncate options](module-level-options#truncate-mode). /// See [truncate options](module-level-options#truncate-mode).
@ -40,12 +53,77 @@ pub struct VolumeModule {
pub common: Option<CommonConfig>, pub common: Option<CommonConfig>,
} }
fn default_format() -> String {
String::from("{icon} {percentage}%")
}
#[derive(Debug, Clone, Deserialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
pub struct Icons {
/// Icon to show for high volume levels.
///
/// **Default**: `󰕾`
#[serde(default = "default_icon_volume_high")]
volume_high: String,
/// Icon to show for medium volume levels.
///
/// **Default**: `󰖀`
#[serde(default = "default_icon_volume_medium")]
volume_medium: String,
/// Icon to show for low volume levels.
///
/// **Default**: `󰕿`
#[serde(default = "default_icon_volume_low")]
volume_low: String,
/// Icon to show for muted outputs.
///
/// **Default**: `󰝟`
#[serde(default = "default_icon_muted")]
muted: String,
}
impl Icons {
fn volume_icon(&self, volume_percent: f64) -> &str {
match volume_percent as u32 {
0..=33 => &self.volume_low,
34..=66 => &self.volume_medium,
67.. => &self.volume_high,
}
}
}
impl Default for Icons {
fn default() -> Self {
Self {
volume_high: default_icon_volume_high(),
volume_medium: default_icon_volume_medium(),
volume_low: default_icon_volume_low(),
muted: default_icon_muted(),
}
}
}
const fn default_max_volume() -> f64 { const fn default_max_volume() -> f64 {
100.0 100.0
} }
const fn default_icon_size() -> i32 { fn default_icon_volume_high() -> String {
24 String::from("󰕾")
}
fn default_icon_volume_medium() -> String {
String::from("󰖀")
}
fn default_icon_volume_low() -> String {
String::from("󰕿")
}
fn default_icon_muted() -> String {
String::from("󰝟")
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -133,12 +211,19 @@ impl Module<Button> for VolumeModule {
fn into_widget( fn into_widget(
self, self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>, context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo, info: &ModuleInfo,
) -> color_eyre::Result<ModuleParts<Button>> ) -> color_eyre::Result<ModuleParts<Button>>
where where
<Self as Module<Button>>::SendMessage: Clone, <Self as Module<Button>>::SendMessage: Clone,
{ {
let button_label = Label::builder()
.use_markup(true)
.angle(self.layout.angle(info))
.justify(self.layout.justify.into())
.build();
let button = Button::new(); let button = Button::new();
button.add(&button_label);
{ {
let tx = context.tx.clone(); let tx = context.tx.clone();
@ -150,32 +235,255 @@ impl Module<Button> for VolumeModule {
let rx = context.subscribe(); let rx = context.subscribe();
let image_icon = Image::new(); rx.recv_glib(
image_icon.add_class("icon"); (&self.icons, &self.format),
button.set_image(Some(&image_icon)); move |(icons, format), event| match event {
Event::AddSink(sink) | Event::UpdateSink(sink) if sink.active => {
let label = format
.replace(
"{icon}",
if sink.muted {
&icons.muted
} else {
icons.volume_icon(sink.volume)
},
)
.replace("{percentage}", &sink.volume.to_string())
.replace("{name}", &sink.description);
rx.recv_glib_async((), move |(), event| { button_label.set_label_escaped(&label);
let image_icon = image_icon.clone();
let image_provider = context.ironbar.image_provider();
async move {
match event {
Event::AddSink(sink) | Event::UpdateSink(sink) if sink.active => {
image_provider
.load_into_image_silent(
&determine_volume_icon(sink.muted, sink.volume),
self.icon_size,
false,
&image_icon,
)
.await;
}
_ => {}
} }
} _ => {}
}); },
);
Ok(ModuleParts::new(button, None)) let popup = self
.into_popup(context, info)
.into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup))
}
fn into_popup(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
) -> Option<gtk::Box>
where
Self: Sized,
{
let container = gtk::Box::new(Orientation::Horizontal, 10);
let sink_container = gtk::Box::new(Orientation::Vertical, 5);
sink_container.add_class("device-box");
let input_container = gtk::Box::new(Orientation::Vertical, 5);
input_container.add_class("apps-box");
container.add(&sink_container);
container.add(&input_container);
let sink_selector = ComboBoxText::new();
sink_selector.add_class("device-selector");
let renderer = sink_selector
.cells()
.first()
.expect("to exist")
.clone()
.downcast::<CellRendererText>()
.expect("to be valid cast");
renderer.set_width_chars(20);
renderer.set_ellipsize(EllipsizeMode::End);
{
let tx = context.controller_tx.clone();
sink_selector.connect_changed(move |selector| {
if let Some(name) = selector.active_id() {
tx.send_spawn(Update::SinkChange(name.into()));
}
});
}
sink_container.add(&sink_selector);
let slider = Scale::builder()
.orientation(Orientation::Vertical)
.height_request(100)
.inverted(true)
.build();
slider.add_class("slider");
slider.set_range(0.0, self.max_volume);
slider.set_value(50.0);
sink_container.add(&slider);
{
let tx = context.controller_tx.clone();
let selector = sink_selector.clone();
slider.connect_button_release_event(move |scale, _| {
if let Some(sink) = selector.active_id() {
// GTK will send values outside min/max range
let val = scale.value().clamp(0.0, self.max_volume);
tx.send_spawn(Update::SinkVolume(sink.into(), val));
}
Propagation::Proceed
});
}
let btn_mute = ToggleButton::new();
btn_mute.add_class("btn-mute");
sink_container.add(&btn_mute);
{
let tx = context.controller_tx.clone();
let selector = sink_selector.clone();
btn_mute.connect_toggled(move |btn| {
if let Some(sink) = selector.active_id() {
let muted = btn.is_active();
tx.send_spawn(Update::SinkMute(sink.into(), muted));
}
});
}
container.show_all();
let mut inputs = HashMap::new();
let mut sinks = vec![];
context
.subscribe()
.recv_glib(&input_container, move |input_container, event| {
match event {
Event::AddSink(info) => {
sink_selector.append(Some(&info.name), &info.description);
if info.active {
sink_selector.set_active(Some(sinks.len() as u32));
slider.set_value(info.volume);
btn_mute.set_active(info.muted);
btn_mute.set_label(if info.muted {
&self.icons.muted
} else {
self.icons.volume_icon(info.volume)
});
}
sinks.push(info);
}
Event::UpdateSink(info) => {
if info.active {
if let Some(pos) = sinks.iter().position(|s| s.name == info.name) {
sink_selector.set_active(Some(pos as u32));
slider.set_value(info.volume);
btn_mute.set_active(info.muted);
btn_mute.set_label(if info.muted {
&self.icons.muted
} else {
self.icons.volume_icon(info.volume)
});
}
}
}
Event::RemoveSink(name) => {
if let Some(pos) = sinks.iter().position(|s| s.name == name) {
ComboBoxTextExt::remove(&sink_selector, pos as i32);
sinks.remove(pos);
}
}
Event::AddInput(info) => {
let index = info.index;
let item_container = gtk::Box::new(Orientation::Vertical, 0);
item_container.add_class("app-box");
let label = Label::new(Some(&info.name));
label.add_class("title");
if let Some(truncate) = self.truncate {
label.truncate(truncate);
};
let slider = Scale::builder().sensitive(info.can_set_volume).build();
slider.set_range(0.0, self.max_volume);
slider.set_value(info.volume);
slider.add_class("slider");
{
let tx = context.controller_tx.clone();
slider.connect_button_release_event(move |scale, _| {
// GTK will send values outside min/max range
let val = scale.value().clamp(0.0, self.max_volume);
tx.send_spawn(Update::InputVolume(index, val));
Propagation::Proceed
});
}
let btn_mute = ToggleButton::new();
btn_mute.add_class("btn-mute");
btn_mute.set_active(info.muted);
btn_mute.set_label(if info.muted {
&self.icons.muted
} else {
self.icons.volume_icon(info.volume)
});
{
let tx = context.controller_tx.clone();
btn_mute.connect_toggled(move |btn| {
let muted = btn.is_active();
tx.send_spawn(Update::InputMute(index, muted));
});
}
item_container.add(&label);
item_container.add(&slider);
item_container.add(&btn_mute);
item_container.show_all();
input_container.add(&item_container);
inputs.insert(
info.index,
InputUi {
container: item_container,
label,
slider,
btn_mute,
},
);
}
Event::UpdateInput(info) => {
if let Some(ui) = inputs.get(&info.index) {
ui.label.set_label(&info.name);
ui.slider.set_value(info.volume);
ui.slider.set_sensitive(info.can_set_volume);
ui.btn_mute.set_label(if info.muted {
&self.icons.muted
} else {
self.icons.volume_icon(info.volume)
});
}
}
Event::RemoveInput(index) => {
if let Some(ui) = inputs.remove(&index) {
input_container.remove(&ui.container);
}
}
}
});
Some(container)
} }
} }
@ -185,16 +493,3 @@ struct InputUi {
slider: Scale, slider: Scale,
btn_mute: ToggleButton, btn_mute: ToggleButton,
} }
fn determine_volume_icon(muted: bool, volume: f64) -> String {
let icon_variant = if muted {
"muted"
} else if volume <= 33.3333 {
"low"
} else if volume <= 66.6667 {
"medium"
} else {
"high"
};
format!("audio-volume-{icon_variant}-symbolic")
}