175 Commits

Author SHA1 Message Date
Jake Stanger
1df3d734b9 Merge pull request #395 from JakeStanger/dependabot/cargo/clap-4.4.12
build(deps): bump clap from 4.4.11 to 4.4.12
2024-01-02 12:59:14 +00:00
dependabot[bot]
130e184722 build(deps): bump clap from 4.4.11 to 4.4.12
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.11 to 4.4.12.
- [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/v4.4.11...v4.4.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-02 12:52:13 +00:00
Jake Stanger
2c91e58317 Merge pull request #394 from JakeStanger/dependabot/cargo/serde_json-1.0.109
build(deps): bump serde_json from 1.0.108 to 1.0.109
2024-01-02 12:51:25 +00:00
dependabot[bot]
d50075021d build(deps): bump serde_json from 1.0.108 to 1.0.109
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.108 to 1.0.109.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.108...v1.0.109)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 14:13:20 +00:00
Jake Stanger
45d5df6bb1 Merge pull request #392 from JakeStanger/update_flake_lock_action
Update flake.lock
2024-01-01 01:19:55 +00:00
github-actions[bot]
99b468aed2 flake.lock: Update
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/8b9bad9b30bd7a9ed08782e64846b7485f9d0a38' (2023-11-30)
  → 'github:ipetkov/crane/afdcd41180e3dfe4dac46b5ee396e3b12ccc967a' (2023-12-24)
• Updated input 'naersk/nixpkgs':
    'github:NixOS/nixpkgs/f5c27c6136db4d76c30e533c20517df6864c46ee' (2023-11-30)
  → 'github:NixOS/nixpkgs/d44d59d2b5bd694cd9d996fd8c51d03e3e9ba7f7' (2023-12-31)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/8cfef6986adfb599ba379ae53c9f5631ecd2fd9c' (2023-11-27)
  → 'github:nixos/nixpkgs/cfc3698c31b1fb9cdcf10f36c9643460264d0ca8' (2023-12-27)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/6d3c6e185198b8bf7ad639f22404a75aa9a09bff' (2023-11-30)
  → 'github:oxalica/rust-overlay/319f57cd2c34348c55970a4bf2b35afe82088681' (2023-12-30)
2024-01-01 00:56:52 +00:00
Jake Stanger
651a27b143 Merge pull request #391 from JakeStanger/fix/workspace-issues
Workspace Issues Fixes
2023-12-31 16:04:22 +00:00
Jake Stanger
b4d75450ac fix(regression): GTK refactor causing updates to be missed
Regression introduced by recent GTK refactor.

The `glib_recv` macros  previously using the passed in expression as the receiver, which was causing a new receiver to be created *every* time an event was received. This caused some peculiar behaviours where some events just never got through if sent too close to each other.

This was most obvious in the `workspaces` module.

Fixes #381
2023-12-31 15:56:41 +00:00
Jake Stanger
b2a37a32b0 refactor: fix clippy warning 2023-12-31 00:50:41 +00:00
Jake Stanger
967801dc32 refactor(workspaces): avoid sending unknown update info 2023-12-31 00:50:41 +00:00
Jake Stanger
1b54de98e6 build(deps): update glib 2023-12-31 00:42:14 +00:00
Jake Stanger
c356b22401 fix(workspaces): favourites missing inactive class on startup
Fixes #390
2023-12-30 23:30:03 +00:00
Jake Stanger
b3a3c78a3d Merge pull request #389 from eclairevoyant/nerdfont-fix
docs: fix nerdfont icons
2023-12-28 13:15:37 +00:00
éclairevoyant
a1598259eb docs: fix nerdfont icons 2023-12-28 01:11:54 -05:00
Jake Stanger
e19e80d6d8 Merge pull request #387 from JakeStanger/dependabot/cargo/reqwest-0.11.23
build(deps): bump reqwest from 0.11.22 to 0.11.23
2023-12-25 23:36:39 +00:00
Jake Stanger
9be8638291 Merge pull request #386 from JakeStanger/dependabot/cargo/tokio-1.35.1
build(deps): bump tokio from 1.35.0 to 1.35.1
2023-12-25 23:36:27 +00:00
Jake Stanger
fb87112ac9 Merge pull request #385 from JakeStanger/dependabot/cargo/futures-util-0.3.30
build(deps): bump futures-util from 0.3.29 to 0.3.30
2023-12-25 23:36:09 +00:00
Jake Stanger
ace1f848ba Merge pull request #384 from JakeStanger/dependabot/cargo/ctrlc-3.4.2
build(deps): bump ctrlc from 3.4.1 to 3.4.2
2023-12-25 23:35:59 +00:00
dependabot[bot]
c3d5556fb1 build(deps): bump reqwest from 0.11.22 to 0.11.23
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.22 to 0.11.23.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.22...v0.11.23)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-25 14:38:37 +00:00
dependabot[bot]
a882976f93 build(deps): bump tokio from 1.35.0 to 1.35.1
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.35.0 to 1.35.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.35.0...tokio-1.35.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-25 14:38:27 +00:00
dependabot[bot]
4e6b0e38ff build(deps): bump futures-util from 0.3.29 to 0.3.30
Bumps [futures-util](https://github.com/rust-lang/futures-rs) from 0.3.29 to 0.3.30.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.29...0.3.30)

---
updated-dependencies:
- dependency-name: futures-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-25 14:38:15 +00:00
dependabot[bot]
bdd0015eb4 build(deps): bump ctrlc from 3.4.1 to 3.4.2
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.4.1 to 3.4.2.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.1...3.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-25 14:38:00 +00:00
Jake Stanger
b6351003ab Merge pull request #383 from JakeStanger/fix/await-sync
fix: some modules crashing due to recent gtk refactor
2023-12-25 00:21:39 +00:00
Jake Stanger
80de5dd824 fix: some modules crashing due to recent gtk refactor
Fixes a crash introduced by commit bea442e where the `await_sync` function incorrectly tried to use the current tokio runtime, which it is often outside, instead of the singleton.

Fixes #382
2023-12-24 13:44:38 +00:00
Jake Stanger
29eeefbd24 Merge pull request #380 from JakeStanger/refactor/update-gtk
Update GTK-related crates, replace GLib channels with Tokio channels, refactor Tokio runtime code
2023-12-18 22:17:44 +00:00
Jake Stanger
e847a84c21 refactor: fix casting based clippy warnings 2023-12-18 22:09:21 +00:00
Jake Stanger
bea442ed96 refactor: update gtk/glib, remove glib channels
This is a major refactor which updates GTK, GLib and GTK Layer Shell to their latest versions.

GLib channels, previously used for receiving events on the GLib Main Context thread have been deprecated and a new method for running Futures on the main thread has been added instead. This commit also replaces all the deprecated code with this.

As part of the above, a bug was uncovered related to creating the GLib main context inside the Tokio runtime. Spawning of Tokio tasks has been refactored to fix this.
2023-12-18 22:09:21 +00:00
Dylan Premo
f2c4ddb914 docs(sys info): fix cpu temp examples
Use - instead of _ for [sensor], as in #129
2023-12-17 13:34:02 +00:00
Jake Stanger
a9e28e163f chore: update lock file 2023-12-12 23:20:51 +00:00
Jake Stanger
2c2f1c1243 Merge pull request #377 from JakeStanger/refactor/update-wayland
refactor: update wayland crates to latest versions
2023-12-11 22:11:13 +00:00
Jake Stanger
ed5a16237d refactor: update wayland crates to latest versions 2023-12-11 22:01:50 +00:00
Jake Stanger
9a7ee6babc Merge pull request #372 from JakeStanger/feat/auto-hide
feat: bar auto-hide options
2023-12-11 21:37:55 +00:00
Jake Stanger
b784b7b0d2 Merge pull request #373 from JakeStanger/feat/top-level-default
feat: use top-level config as fallback when using monitor-based config
2023-12-11 21:36:47 +00:00
Jake Stanger
5653191604 Merge pull request #375 from JakeStanger/dependabot/cargo/tokio-1.35.0
build(deps): bump tokio from 1.34.0 to 1.35.0
2023-12-11 21:33:01 +00:00
Jake Stanger
85498feadc Merge pull request #374 from JakeStanger/dependabot/cargo/clap-4.4.11
build(deps): bump clap from 4.4.10 to 4.4.11
2023-12-11 21:32:26 +00:00
dependabot[bot]
44733bd62d build(deps): bump tokio from 1.34.0 to 1.35.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.34.0 to 1.35.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.34.0...tokio-1.35.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 15:00:40 +00:00
dependabot[bot]
bfe9c4ee2b build(deps): bump clap from 4.4.10 to 4.4.11
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.10 to 4.4.11.
- [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/v4.4.10...v4.4.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-11 15:00:27 +00:00
Jake Stanger
659c93dd2a feat: use top-level config as fallback when using monitor-based config
This allows you to configure a default bar to use, then override specific monitors.

Not setting anything at the top level will hide bars which are not explicitly configured.

This actually came about as a bug in the recent refactorings, but now it's a feature :)
2023-12-10 23:12:21 +00:00
Jake Stanger
ee04cd025a feat: bar auto-hide options
Adds two new bar-level options:

- `start_hidden`, which stops a bar from showing when Ironbar starts. It can then be hidden via IPC or auto-hide.
- `autohide`, which takes a delay after which the bar will be hidden when the cursor leaves. Hovering at the screen edge where the bar is located reveals the bar again.

Resolves #167
2023-12-10 22:56:43 +00:00
Jake Stanger
56f423e408 Merge pull request #371 from JakeStanger/refactor/encapsulate
refactor: begin restructuring core code to better encapsulate
2023-12-09 21:37:08 +00:00
Jake Stanger
b2fa19ab6c refactor: begin restructuring core code to better encapsulate
This is a first pass towards trying to structure things a bit better, with data generally encapsulated under a single hierarchical tree, rather than lots of globals all over the place. Lots of work is still required.

The plan is that with this and some more work, #291 should become a lot easier to sort.
2023-12-08 22:39:27 +00:00
Jake Stanger
7175cf2f5c Merge pull request #370 from JakeStanger/dependabot/cargo/futures-lite-2.1.0
build(deps): bump futures-lite from 2.0.1 to 2.1.0
2023-12-04 17:24:45 +00:00
Jake Stanger
ed48e5c8c3 Merge pull request #369 from JakeStanger/dependabot/cargo/clap-4.4.10
build(deps): bump clap from 4.4.8 to 4.4.10
2023-12-04 17:23:41 +00:00
dependabot[bot]
5094129f55 build(deps): bump futures-lite from 2.0.1 to 2.1.0
Bumps [futures-lite](https://github.com/smol-rs/futures-lite) from 2.0.1 to 2.1.0.
- [Release notes](https://github.com/smol-rs/futures-lite/releases)
- [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/futures-lite/compare/v2.0.1...v2.1.0)

---
updated-dependencies:
- dependency-name: futures-lite
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 14:53:07 +00:00
dependabot[bot]
d343b16341 build(deps): bump clap from 4.4.8 to 4.4.10
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.8 to 4.4.10.
- [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/v4.4.8...v4.4.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 14:52:56 +00:00
Jake Stanger
96615502a8 Merge pull request #360 from chmanie/fix/tray-icon-themes
Re-use existing IconTheme during icon retrieval
2023-12-03 11:49:42 +00:00
Chris Maniewski
5f82b6e9e0 fix(tray): existing icons rendering as text 2023-12-02 22:26:15 +01:00
Jake Stanger
4fe20865bf Merge pull request #368 from JakeStanger/update_flake_lock_action
Update flake.lock
2023-12-02 15:45:19 +00:00
github-actions[bot]
5b47ebdeaf flake.lock: Update
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/b7db46f0f1751f7b1d1911f6be7daf568ad5bc65' (2023-10-24)
  → 'github:ipetkov/crane/8b9bad9b30bd7a9ed08782e64846b7485f9d0a38' (2023-11-30)
• Updated input 'naersk/nixpkgs':
    'github:NixOS/nixpkgs/90e85bc7c1a6fc0760a94ace129d3a1c61c3d035' (2023-10-29)
  → 'github:NixOS/nixpkgs/f5c27c6136db4d76c30e533c20517df6864c46ee' (2023-11-30)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/0cbe9f69c234a7700596e943bfae7ef27a31b735' (2023-10-29)
  → 'github:nixos/nixpkgs/8cfef6986adfb599ba379ae53c9f5631ecd2fd9c' (2023-11-27)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/ec19bd20af08f3b004089cc12ab54c823ed899b7' (2023-10-31)
  → 'github:oxalica/rust-overlay/6d3c6e185198b8bf7ad639f22404a75aa9a09bff' (2023-11-30)
2023-12-02 15:22:06 +00:00
Jake Stanger
f92845aecb Merge pull request #366 from JakeStanger/dependabot/cargo/sysinfo-0.29.11
build(deps): bump sysinfo from 0.29.10 to 0.29.11
2023-11-27 22:51:32 +00:00
Jake Stanger
b443b8f868 Merge pull request #365 from JakeStanger/dependabot/cargo/serde-1.0.193
build(deps): bump serde from 1.0.192 to 1.0.193
2023-11-27 22:51:15 +00:00
dependabot[bot]
12d3d76a57 build(deps): bump sysinfo from 0.29.10 to 0.29.11
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.10 to 0.29.11.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 14:25:32 +00:00
dependabot[bot]
2289755991 build(deps): bump serde from 1.0.192 to 1.0.193
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.192 to 1.0.193.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.192...v1.0.193)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-27 14:25:12 +00:00
Jake Stanger
2016a904c1 Merge pull request #362 from JakeStanger/dependabot/cargo/tracing-appender-0.2.3
build(deps): bump tracing-appender from 0.2.2 to 0.2.3
2023-11-20 19:42:32 +00:00
dependabot[bot]
ac44175b77 build(deps): bump tracing-appender from 0.2.2 to 0.2.3
Bumps [tracing-appender](https://github.com/tokio-rs/tracing) from 0.2.2 to 0.2.3.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-appender-0.2.2...tracing-appender-0.2.3)

---
updated-dependencies:
- dependency-name: tracing-appender
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 14:43:32 +00:00
Jake Stanger
ae75fcf2b2 Merge pull request #359 from delliottxyz/master
fix: Home Manager systemdIntegration warnings
2023-11-14 21:19:48 +00:00
Darragh Elliott
518c2ef023 fix: Home Manager systemdIntegration warnings
The option for wayland.windowManager.hyprland.systemdIntegration has been renamed to wayland.windowManager.hyprland.systemd.enable as of home-manager commit ed0770e96225f998ea128549ad446d9b1568cb2c.
The option for wayland.windowManager.sway.systemdIntegration has been renamed to wayland.windowManager.sway.systemd.enable as of home-manager commit 0144ac418ef633bfc9dbd89b8c199ad3a617c59f.
Using the systemdIntegration options will still work for as long as the corresponding mkRenamedModuleOption stays in the respective home-manager modules but using the old option triggers a warning about the the renamed options.
2023-11-14 16:43:49 +00:00
Jake Stanger
422edb3152 Merge pull request #356 from JakeStanger/dependabot/cargo/clap-4.4.8
build(deps): bump clap from 4.4.7 to 4.4.8
2023-11-13 19:24:49 +00:00
Jake Stanger
375860efb4 Merge pull request #355 from JakeStanger/dependabot/cargo/tokio-1.34.0
build(deps): bump tokio from 1.33.0 to 1.34.0
2023-11-13 19:24:03 +00:00
Jake Stanger
62229c7a3b Merge pull request #354 from JakeStanger/dependabot/cargo/serde-1.0.192
build(deps): bump serde from 1.0.190 to 1.0.192
2023-11-13 19:22:50 +00:00
dependabot[bot]
ce43895394 build(deps): bump clap from 4.4.7 to 4.4.8
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.7 to 4.4.8.
- [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/v4.4.7...v4.4.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 14:26:35 +00:00
dependabot[bot]
9eb1f60351 build(deps): bump tokio from 1.33.0 to 1.34.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.33.0 to 1.34.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.33.0...tokio-1.34.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 14:26:23 +00:00
dependabot[bot]
fbbc667b36 build(deps): bump serde from 1.0.190 to 1.0.192
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.190 to 1.0.192.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.190...v1.0.192)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-13 14:26:11 +00:00
Jake Stanger
0d9e4545aa Merge pull request #353 from JakeStanger/dependabot/cargo/serde_json-1.0.108
build(deps): bump serde_json from 1.0.107 to 1.0.108
2023-11-07 17:02:07 +00:00
Jake Stanger
5fff15d953 Merge pull request #352 from JakeStanger/dependabot/cargo/futures-lite-2.0.1
build(deps): bump futures-lite from 2.0.0 to 2.0.1
2023-11-07 17:01:34 +00:00
Jake Stanger
6707fb6f7e Merge pull request #351 from JakeStanger/dependabot/cargo/indexmap-2.1.0
build(deps): bump indexmap from 2.0.2 to 2.1.0
2023-11-07 17:01:18 +00:00
dependabot[bot]
0d8029f89c build(deps): bump serde_json from 1.0.107 to 1.0.108
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.107 to 1.0.108.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.107...v1.0.108)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 14:27:51 +00:00
dependabot[bot]
9b34dc0d2d build(deps): bump futures-lite from 2.0.0 to 2.0.1
Bumps [futures-lite](https://github.com/smol-rs/futures-lite) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/smol-rs/futures-lite/releases)
- [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/futures-lite/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: futures-lite
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 14:27:19 +00:00
dependabot[bot]
897512720a build(deps): bump indexmap from 2.0.2 to 2.1.0
Bumps [indexmap](https://github.com/bluss/indexmap) from 2.0.2 to 2.1.0.
- [Changelog](https://github.com/bluss/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/bluss/indexmap/compare/2.0.2...2.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-06 14:27:00 +00:00
Jake Stanger
ae70f1d432 Merge pull request #348 from JakeStanger/update_flake_lock_action
Update flake.lock
2023-11-01 08:22:13 +00:00
github-actions[bot]
7b5069a47e flake.lock: Update
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/3de322e06fc88ada5e3589dc8a375b73e749f512' (2023-09-23)
  → 'github:ipetkov/crane/b7db46f0f1751f7b1d1911f6be7daf568ad5bc65' (2023-10-24)
• Removed input 'crane/flake-compat'
• Removed input 'crane/flake-utils'
• Removed input 'crane/flake-utils/systems'
• Removed input 'crane/rust-overlay'
• Removed input 'crane/rust-overlay/flake-utils'
• Removed input 'crane/rust-overlay/nixpkgs'
• Updated input 'naersk':
    'github:nix-community/naersk/3f976d822b7b37fc6fb8e6f157c2dd05e7e94e89' (2023-09-07)
  → 'github:nix-community/naersk/aeb58d5e8faead8980a807c840232697982d47b9' (2023-10-27)
• Updated input 'naersk/nixpkgs':
    'github:NixOS/nixpkgs/bd9b686c0168041aea600222be0805a0de6e6ab8' (2023-09-29)
  → 'github:NixOS/nixpkgs/90e85bc7c1a6fc0760a94ace129d3a1c61c3d035' (2023-10-29)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/8a86b98f0ba1c405358f1b71ff8b5e1d317f5db2' (2023-09-27)
  → 'github:nixos/nixpkgs/0cbe9f69c234a7700596e943bfae7ef27a31b735' (2023-10-29)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/a4c3c904ab29e04a20d3a6da6626d66030385773' (2023-09-30)
  → 'github:oxalica/rust-overlay/ec19bd20af08f3b004089cc12ab54c823ed899b7' (2023-10-31)
2023-11-01 00:52:52 +00:00
Jake Stanger
876504ec77 Merge pull request #347 from JakeStanger/dependabot/cargo/serde-1.0.190
build(deps): bump serde from 1.0.189 to 1.0.190
2023-10-30 22:11:07 +00:00
Jake Stanger
11f217ef54 Merge pull request #345 from JakeStanger/dependabot/cargo/futures-lite-2.0.0
build(deps): bump futures-lite from 1.13.0 to 2.0.0
2023-10-30 22:10:39 +00:00
dependabot[bot]
23473cf070 build(deps): bump serde from 1.0.189 to 1.0.190
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.189 to 1.0.190.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.189...v1.0.190)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 21:26:35 +00:00
dependabot[bot]
bc74f6f802 build(deps): bump futures-lite from 1.13.0 to 2.0.0
Bumps [futures-lite](https://github.com/smol-rs/futures-lite) from 1.13.0 to 2.0.0.
- [Release notes](https://github.com/smol-rs/futures-lite/releases)
- [Changelog](https://github.com/smol-rs/futures-lite/blob/master/CHANGELOG.md)
- [Commits](https://github.com/smol-rs/futures-lite/compare/v1.13.0...v2.0.0)

---
updated-dependencies:
- dependency-name: futures-lite
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 21:25:56 +00:00
Jake Stanger
49de52edda Merge pull request #346 from JakeStanger/dependabot/cargo/mpd_client-1.3.0
build(deps): bump mpd_client from 1.2.0 to 1.3.0
2023-10-30 21:25:27 +00:00
Jake Stanger
fd33714d9c Merge pull request #344 from JakeStanger/dependabot/cargo/clap-4.4.7
build(deps): bump clap from 4.4.6 to 4.4.7
2023-10-30 21:24:28 +00:00
Jake Stanger
c7baa38e32 Merge pull request #343 from JakeStanger/dependabot/cargo/futures-util-0.3.29
build(deps): bump futures-util from 0.3.28 to 0.3.29
2023-10-30 21:23:46 +00:00
dependabot[bot]
895f4ec03b build(deps): bump mpd_client from 1.2.0 to 1.3.0
Bumps [mpd_client](https://github.com/elomatreb/mpd_client) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/elomatreb/mpd_client/releases)
- [Commits](https://github.com/elomatreb/mpd_client/compare/mpd_client-v1.2.0...mpd_client-v1.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 14:45:24 +00:00
dependabot[bot]
4a3de2edb0 build(deps): bump clap from 4.4.6 to 4.4.7
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.6 to 4.4.7.
- [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/v4.4.6...v4.4.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 14:45:04 +00:00
dependabot[bot]
8156662835 build(deps): bump futures-util from 0.3.28 to 0.3.29
Bumps [futures-util](https://github.com/rust-lang/futures-rs) from 0.3.28 to 0.3.29.
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.28...0.3.29)

---
updated-dependencies:
- dependency-name: futures-util
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-30 14:44:54 +00:00
Jake Stanger
74930df83b docs(compiling): fix fedora instructions 2023-10-29 21:19:53 +00:00
Jake Stanger
a8fd1d1dd1 Merge pull request #340 from JakeStanger/fix/focus-workspace-switch
fix(focused): not clearing when switching to empty workspace
2023-10-23 16:28:11 +01:00
Jake Stanger
2c07c9c31f Merge pull request #341 from JakeStanger/dependabot/cargo/tracing-0.1.40
build(deps): bump tracing from 0.1.39 to 0.1.40
2023-10-23 16:27:54 +01:00
dependabot[bot]
d48ef0802d build(deps): bump tracing from 0.1.39 to 0.1.40
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.39 to 0.1.40.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.39...tracing-0.1.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-23 14:09:04 +00:00
Jake Stanger
34ed6a9e11 fix(focused): not clearing when switching to empty workspace 2023-10-19 22:43:18 +01:00
Jake Stanger
08e354e019 refactor: fix new clippy warnings 2023-10-19 21:12:19 +01:00
Jake Stanger
4aa3009f4f Merge pull request #338 from JakeStanger/fix/focus-clearing
fix(focused): clear when no window is focused
2023-10-19 21:08:38 +01:00
Jake Stanger
f24b21d242 fix(focused): clear when no window is focused
Fixes #337
2023-10-19 20:34:18 +01:00
Jake Stanger
8fc516a85d Merge pull request #336 from JakeStanger/fix/vim-style-watcher
Fix style hot reloading when editing with Vim
2023-10-17 20:38:14 +01:00
Jake Stanger
5582dcf373 refactor: fix new clippy warning 2023-10-17 20:22:19 +01:00
Jake Stanger
40998475e2 fix(styles): hot reload not working when edited with vim
Possibly resolves the same issue with other editors or scripts.

Fixes #304
2023-10-17 20:22:02 +01:00
Jake Stanger
917c1bd52e docs(dynamic values): link to scripts/ironvars pages
Also adds a little bit of clarification around script syntax.
2023-10-16 22:39:48 +01:00
Jake Stanger
f93a66f83a Merge pull request #328 from Schweber/patch-1
Update Styling guide: add sentences explaining how to increase specificity of entries
2023-10-16 20:23:47 +01:00
Schweber
a768164515 docs(styling guide): add explanation on specificity 2023-10-16 20:10:50 +01:00
Jake Stanger
a10ec50b72 Merge pull request #332 from JakeStanger/dependabot/cargo/serde-1.0.189
build(deps): bump serde from 1.0.188 to 1.0.189
2023-10-16 19:59:47 +01:00
Jake Stanger
85431b9636 Merge pull request #330 from JakeStanger/dependabot/cargo/tracing-0.1.39
build(deps): bump tracing from 0.1.37 to 0.1.39
2023-10-16 19:59:24 +01:00
Jake Stanger
ef827f26b7 Merge pull request #329 from JakeStanger/dependabot/cargo/regex-1.10.2
build(deps): bump regex from 1.9.6 to 1.10.2
2023-10-16 19:59:04 +01:00
dependabot[bot]
497332198c build(deps): bump serde from 1.0.188 to 1.0.189
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.188 to 1.0.189.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.188...v1.0.189)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:58:11 +00:00
dependabot[bot]
01e8c8a195 build(deps): bump tracing from 0.1.37 to 0.1.39
Bumps [tracing](https://github.com/tokio-rs/tracing) from 0.1.37 to 0.1.39.
- [Release notes](https://github.com/tokio-rs/tracing/releases)
- [Commits](https://github.com/tokio-rs/tracing/compare/tracing-0.1.37...tracing-0.1.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:57:01 +00:00
dependabot[bot]
4987be2cd2 build(deps): bump regex from 1.9.6 to 1.10.2
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.6 to 1.10.2.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.6...1.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-16 14:56:34 +00:00
Jake Stanger
c2306d6680 docs(styling): add another example for selecting gtk widgets 2023-10-13 22:16:16 +01:00
Jake Stanger
66e111e8a2 Merge pull request #326 from JakeStanger/dependabot/cargo/reqwest-0.11.22
build(deps): bump reqwest from 0.11.20 to 0.11.22
2023-10-09 23:43:35 +01:00
Jake Stanger
983036b7d2 Merge pull request #325 from JakeStanger/dependabot/cargo/tokio-1.33.0
build(deps): bump tokio from 1.32.0 to 1.33.0
2023-10-09 16:24:19 +01:00
dependabot[bot]
b8358b1d47 build(deps): bump reqwest from 0.11.20 to 0.11.22
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.20 to 0.11.22.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.20...v0.11.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 14:15:07 +00:00
dependabot[bot]
d12d6f5bc8 build(deps): bump tokio from 1.32.0 to 1.33.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.32.0...tokio-1.33.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-09 14:14:18 +00:00
Jake Stanger
abbd3ab623 Merge pull request #324 from JakeStanger/dependabot/cargo/indexmap-2.0.2
build(deps): bump indexmap from 2.0.0 to 2.0.2
2023-10-02 20:31:31 +01:00
Jake Stanger
121bc2c925 Merge pull request #323 from JakeStanger/dependabot/cargo/regex-1.9.6
build(deps): bump regex from 1.9.5 to 1.9.6
2023-10-02 20:30:38 +01:00
Jake Stanger
043b4d32e8 Merge pull request #322 from JakeStanger/dependabot/cargo/clap-4.4.6
build(deps): bump clap from 4.4.4 to 4.4.6
2023-10-02 20:30:10 +01:00
dependabot[bot]
c55a3016c3 build(deps): bump indexmap from 2.0.0 to 2.0.2
Bumps [indexmap](https://github.com/bluss/indexmap) from 2.0.0 to 2.0.2.
- [Changelog](https://github.com/bluss/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/bluss/indexmap/compare/2.0.0...2.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:38:19 +00:00
dependabot[bot]
49c8e379de build(deps): bump regex from 1.9.5 to 1.9.6
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.5 to 1.9.6.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.5...1.9.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:38:06 +00:00
dependabot[bot]
f452b0e8fd build(deps): bump clap from 4.4.4 to 4.4.6
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.6.
- [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/v4.4.4...v4.4.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 14:37:46 +00:00
Jake Stanger
6585935d40 Merge pull request #321 from JakeStanger/update_flake_lock_action
Update flake.lock
2023-10-01 18:51:28 +01:00
github-actions[bot]
d84a562f62 flake.lock: Update
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/174604795d316b75777e28185c3a4918bc69b399' (2023-08-30)
  → 'github:ipetkov/crane/3de322e06fc88ada5e3589dc8a375b73e749f512' (2023-09-23)
• Updated input 'crane/flake-utils':
    'github:numtide/flake-utils/919d646de7be200f3bf08cb76ae1f09402b6f9b4' (2023-07-11)
  → 'github:numtide/flake-utils/ff7b65b44d01cf9ba6a71320833626af21126384' (2023-09-12)
• Updated input 'crane/rust-overlay':
    'github:oxalica/rust-overlay/b520a3889b24aaf909e287d19d406862ced9ffc9' (2023-08-07)
  → 'github:oxalica/rust-overlay/b87a14abea512d956f0b89d0d8a1e9b41f3e20ff' (2023-09-18)
• Updated input 'naersk':
    'github:nix-community/naersk/78789c30d64dea2396c9da516bbcc8db3a475207' (2023-08-18)
  → 'github:nix-community/naersk/3f976d822b7b37fc6fb8e6f157c2dd05e7e94e89' (2023-09-07)
• Updated input 'naersk/nixpkgs':
    'github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9' (2023-08-30)
  → 'github:NixOS/nixpkgs/bd9b686c0168041aea600222be0805a0de6e6ab8' (2023-09-29)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/e7f38be3775bab9659575f192ece011c033655f0' (2023-08-30)
  → 'github:nixos/nixpkgs/8a86b98f0ba1c405358f1b71ff8b5e1d317f5db2' (2023-09-27)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/40e851593ef4f9f8cd0b69c8cae7b722b9953a23' (2023-08-31)
  → 'github:oxalica/rust-overlay/a4c3c904ab29e04a20d3a6da6626d66030385773' (2023-09-30)
2023-10-01 00:55:35 +00:00
Jake Stanger
0c0163cfa1 Merge pull request #317 from JakeStanger/dependabot/cargo/hyprland-0.3.12
build(deps): bump hyprland from 0.3.11 to 0.3.12
2023-09-25 20:06:23 +01:00
Jake Stanger
e1b1d6f465 Merge pull request #315 from JakeStanger/dependabot/cargo/clap-4.4.4
build(deps): bump clap from 4.4.2 to 4.4.4
2023-09-25 20:06:06 +01:00
dependabot[bot]
1f824edd70 build(deps): bump hyprland from 0.3.11 to 0.3.12
Bumps [hyprland](https://github.com/hyprland-community/hyprland-rs) from 0.3.11 to 0.3.12.
- [Release notes](https://github.com/hyprland-community/hyprland-rs/releases)
- [Commits](https://github.com/hyprland-community/hyprland-rs/compare/0.3.11...0.3.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 14:53:06 +00:00
dependabot[bot]
088ac971c1 build(deps): bump clap from 4.4.2 to 4.4.4
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.2 to 4.4.4.
- [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/v4.4.2...v4.4.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 14:52:09 +00:00
Jake Stanger
59f66df079 Merge pull request #311 from JakeStanger/dependabot/cargo/serde_json-1.0.107
build(deps): bump serde_json from 1.0.105 to 1.0.107
2023-09-18 20:03:44 +01:00
Jake Stanger
2d5d1b450c Merge pull request #314 from JakeStanger/dependabot/cargo/chrono-0.4.31
build(deps): bump chrono from 0.4.30 to 0.4.31
2023-09-18 20:03:34 +01:00
Jake Stanger
7a2edcdffc Merge pull request #310 from JakeStanger/dependabot/cargo/sysinfo-0.29.10
build(deps): bump sysinfo from 0.29.9 to 0.29.10
2023-09-18 20:02:36 +01:00
dependabot[bot]
8f4ad02c75 build(deps): bump chrono from 0.4.30 to 0.4.31
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.30 to 0.4.31.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.30...v0.4.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 14:28:54 +00:00
dependabot[bot]
c5a1694b10 build(deps): bump serde_json from 1.0.105 to 1.0.107
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.105 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.105...v1.0.107)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 14:27:13 +00:00
dependabot[bot]
f8cc634cc9 build(deps): bump sysinfo from 0.29.9 to 0.29.10
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.9 to 0.29.10.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-18 14:26:43 +00:00
Jake Stanger
ccc6ff2d94 docs(readme): add nixpkgs details
Resolves #303.
2023-09-16 22:59:03 +01:00
Jake Stanger
71ea185a65 Merge pull request #305 from JakeStanger/dependabot/cargo/walkdir-2.4.0
build(deps): bump walkdir from 2.3.3 to 2.4.0
2023-09-11 21:54:34 +01:00
Jake Stanger
c793cd3c1a Merge pull request #306 from JakeStanger/dependabot/cargo/chrono-0.4.30
build(deps): bump chrono from 0.4.28 to 0.4.30
2023-09-11 21:36:32 +01:00
dependabot[bot]
1f4b349423 build(deps): bump walkdir from 2.3.3 to 2.4.0
Bumps [walkdir](https://github.com/BurntSushi/walkdir) from 2.3.3 to 2.4.0.
- [Commits](https://github.com/BurntSushi/walkdir/compare/2.3.3...2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 20:35:49 +00:00
Jake Stanger
4755e2c827 Merge pull request #307 from JakeStanger/dependabot/cargo/regex-1.9.5
build(deps): bump regex from 1.9.4 to 1.9.5
2023-09-11 21:35:13 +01:00
Jake Stanger
b548e3ec01 Merge pull request #308 from JakeStanger/dependabot/cargo/serde-1.0.188
build(deps): bump serde from 1.0.185 to 1.0.188
2023-09-11 21:34:45 +01:00
Jake Stanger
7033d29c6a Merge pull request #309 from JakeStanger/dependabot/cargo/hyprland-0.3.11
build(deps): bump hyprland from 0.3.9 to 0.3.11
2023-09-11 21:33:18 +01:00
dependabot[bot]
0335aa4470 build(deps): bump hyprland from 0.3.9 to 0.3.11
Bumps [hyprland](https://github.com/hyprland-community/hyprland-rs) from 0.3.9 to 0.3.11.
- [Release notes](https://github.com/hyprland-community/hyprland-rs/releases)
- [Commits](https://github.com/hyprland-community/hyprland-rs/compare/0.3.9...0.3.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:41:24 +00:00
dependabot[bot]
dce245ef5f build(deps): bump serde from 1.0.185 to 1.0.188
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.185 to 1.0.188.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.185...v1.0.188)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:41:14 +00:00
dependabot[bot]
bd002e278b build(deps): bump regex from 1.9.4 to 1.9.5
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.4 to 1.9.5.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.4...1.9.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:41:00 +00:00
dependabot[bot]
ae1bc2ef84 build(deps): bump chrono from 0.4.28 to 0.4.30
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.28 to 0.4.30.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.28...v0.4.30)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-11 14:40:52 +00:00
Jake Stanger
4e67b73a83 refactor(wlr data control): update to new nix epoll bindings 2023-09-05 22:43:29 +01:00
Jake Stanger
60bb69feec feat: add widget and widget-container css classes on all widgets 2023-09-05 22:37:58 +01:00
Jake Stanger
5bc7b7f317 Merge pull request #300 from JakeStanger/dependabot/cargo/nix-0.27.1
build(deps): bump nix from 0.26.2 to 0.27.1
2023-09-04 19:59:50 +01:00
dependabot[bot]
f88bec93ff build(deps): bump nix from 0.26.2 to 0.27.1
Bumps [nix](https://github.com/nix-rust/nix) from 0.26.2 to 0.27.1.
- [Changelog](https://github.com/nix-rust/nix/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nix-rust/nix/compare/v0.26.2...v0.27.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 18:43:49 +00:00
Jake Stanger
0d0447bbd2 Merge pull request #301 from JakeStanger/dependabot/cargo/clap-4.4.2
build(deps): bump clap from 4.4.1 to 4.4.2
2023-09-04 19:43:16 +01:00
Jake Stanger
49a79bb011 Merge pull request #299 from JakeStanger/dependabot/cargo/ctrlc-3.4.1
build(deps): bump ctrlc from 3.4.0 to 3.4.1
2023-09-04 19:42:33 +01:00
Jake Stanger
1dcda6c90e Merge pull request #298 from JakeStanger/dependabot/cargo/chrono-0.4.28
build(deps): bump chrono from 0.4.26 to 0.4.28
2023-09-04 19:35:13 +01:00
dependabot[bot]
4e9c05761e build(deps): bump clap from 4.4.1 to 4.4.2
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.1 to 4.4.2.
- [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/v4.4.1...v4.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 14:57:51 +00:00
dependabot[bot]
af674bb769 build(deps): bump ctrlc from 3.4.0 to 3.4.1
Bumps [ctrlc](https://github.com/Detegr/rust-ctrlc) from 3.4.0 to 3.4.1.
- [Release notes](https://github.com/Detegr/rust-ctrlc/releases)
- [Commits](https://github.com/Detegr/rust-ctrlc/compare/3.4.0...3.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 14:56:47 +00:00
dependabot[bot]
a31a07e451 build(deps): bump chrono from 0.4.26 to 0.4.28
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.26 to 0.4.28.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.26...v0.4.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 14:56:16 +00:00
Jake Stanger
0df3d4c725 Merge pull request #297 from JakeStanger/update_flake_lock_action
Update flake.lock
2023-09-01 17:33:36 +01:00
github-actions[bot]
50ab2e4742 flake.lock: Update
Flake lock file updates:

• Updated input 'crane':
    'github:ipetkov/crane/8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e' (2023-07-07)
  → 'github:ipetkov/crane/174604795d316b75777e28185c3a4918bc69b399' (2023-08-30)
• Updated input 'crane/flake-utils':
    'github:numtide/flake-utils/dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7' (2023-06-25)
  → 'github:numtide/flake-utils/919d646de7be200f3bf08cb76ae1f09402b6f9b4' (2023-07-11)
• Updated input 'crane/rust-overlay':
    'github:oxalica/rust-overlay/f9b92316727af9e6c7fee4a761242f7f46880329' (2023-07-03)
  → 'github:oxalica/rust-overlay/b520a3889b24aaf909e287d19d406862ced9ffc9' (2023-08-07)
• Updated input 'naersk':
    'github:nix-community/naersk/d9a33d69a9c421d64c8d925428864e93be895dcc' (2023-07-26)
  → 'github:nix-community/naersk/78789c30d64dea2396c9da516bbcc8db3a475207' (2023-08-18)
• Updated input 'naersk/nixpkgs':
    'github:NixOS/nixpkgs/9418167277f665de6f4a29f414d438cf39c55b9e' (2023-07-31)
  → 'github:NixOS/nixpkgs/a63a64b593dcf2fe05f7c5d666eb395950f36bc9' (2023-08-30)
• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/fb942492b7accdee4e6d17f5447091c65897dde4' (2023-07-31)
  → 'github:nixos/nixpkgs/e7f38be3775bab9659575f192ece011c033655f0' (2023-08-30)
• Updated input 'rust-overlay':
    'github:oxalica/rust-overlay/05d480a7aef1aae1bfb67a39134dcf48c5322528' (2023-07-30)
  → 'github:oxalica/rust-overlay/40e851593ef4f9f8cd0b69c8cae7b722b9953a23' (2023-08-31)
2023-09-01 00:51:26 +00:00
Jake Stanger
abd1f80548 docs(examples): update discord icon, temporarily disable random label 2023-08-30 21:45:53 +01:00
Jake Stanger
3ddf799739 build: update universal-config/corn 2023-08-30 21:37:46 +01:00
Jake Stanger
6fbe0d5e71 chore: bump version to 0.14.0-pre 2023-08-29 22:49:26 +01:00
Jake Stanger
bc553b4918 Merge pull request #292 from JakeStanger/dependabot/cargo/clap-4.4.1
build(deps): bump clap from 4.3.23 to 4.4.1
2023-08-28 21:36:54 +01:00
Jake Stanger
4ee2ce4d67 Merge pull request #290 from JakeStanger/dependabot/cargo/regex-1.9.4
build(deps): bump regex from 1.9.3 to 1.9.4
2023-08-28 21:08:08 +01:00
dependabot[bot]
cf3dc17ad3 build(deps): bump clap from 4.3.23 to 4.4.1
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.23 to 4.4.1.
- [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/v4.3.23...v4.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 20:04:35 +00:00
Jake Stanger
8b8ccf7be7 Merge pull request #288 from JakeStanger/dependabot/cargo/reqwest-0.11.20
build(deps): bump reqwest from 0.11.18 to 0.11.20
2023-08-28 21:03:47 +01:00
Jake Stanger
1035ce670f Merge pull request #287 from JakeStanger/dependabot/cargo/sysinfo-0.29.9
build(deps): bump sysinfo from 0.29.8 to 0.29.9
2023-08-28 21:03:30 +01:00
Jake Stanger
43b446d266 Merge pull request #286 from JakeStanger/dependabot/cargo/notify-6.1.1
build(deps): bump notify from 6.0.1 to 6.1.1
2023-08-28 21:02:38 +01:00
dependabot[bot]
93eb4c6472 build(deps): bump regex from 1.9.3 to 1.9.4
Bumps [regex](https://github.com/rust-lang/regex) from 1.9.3 to 1.9.4.
- [Release notes](https://github.com/rust-lang/regex/releases)
- [Changelog](https://github.com/rust-lang/regex/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/regex/compare/1.9.3...1.9.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 14:30:34 +00:00
dependabot[bot]
40432c8704 build(deps): bump reqwest from 0.11.18 to 0.11.20
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.18 to 0.11.20.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.18...v0.11.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 14:29:54 +00:00
dependabot[bot]
9ced8597e4 build(deps): bump sysinfo from 0.29.8 to 0.29.9
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.29.8 to 0.29.9.
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 14:29:40 +00:00
dependabot[bot]
10f8bbae3f build(deps): bump notify from 6.0.1 to 6.1.1
Bumps [notify](https://github.com/notify-rs/notify) from 6.0.1 to 6.1.1.
- [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-6.0.1...notify-6.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-28 14:29:26 +00:00
Jake Stanger
af1f9e39b0 Merge pull request #283 from malicean/feat/workspaces-visible
feat(workspaces): visible CSS selector
2023-08-26 22:20:40 +01:00
Alice Janik
25c490b8b4 feat(workspaces): visible CSS selector 2023-08-25 20:50:51 -05:00
Jake Stanger
6c38ff29c4 Merge pull request #281 from JakeStanger/dependabot/cargo/serde-1.0.185
build(deps): bump serde from 1.0.183 to 1.0.185
2023-08-25 23:03:01 +01:00
Jake Stanger
fea1f18524 refactor: fix new clippy warnings, fmt 2023-08-25 22:55:12 +01:00
Jake Stanger
b9c41af0f7 docs(workspaces): add missing .inactive selector 2023-08-24 23:29:16 +01:00
dependabot[bot]
2a8a62eea6 build(deps): bump serde from 1.0.183 to 1.0.185
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.183 to 1.0.185.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.183...v1.0.185)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 20:37:58 +00:00
Jake Stanger
d010fd6398 Merge pull request #280 from JakeStanger/dependabot/cargo/hyprland-0.3.9
build(deps): bump hyprland from 0.3.8 to 0.3.9
2023-08-21 21:26:18 +01:00
Jake Stanger
57cca121bf Merge pull request #279 from JakeStanger/dependabot/cargo/serde_json-1.0.105
build(deps): bump serde_json from 1.0.104 to 1.0.105
2023-08-21 21:24:48 +01:00
dependabot[bot]
7f6ba90bfd build(deps): bump serde_json from 1.0.104 to 1.0.105
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.104 to 1.0.105.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.104...v1.0.105)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 20:07:26 +00:00
Jake Stanger
9431d09de7 Merge pull request #278 from JakeStanger/dependabot/cargo/clap-4.3.23
build(deps): bump clap from 4.3.21 to 4.3.23
2023-08-21 21:06:44 +01:00
Jake Stanger
69bd650810 Merge pull request #277 from JakeStanger/dependabot/cargo/tokio-1.32.0
build(deps): bump tokio from 1.31.0 to 1.32.0
2023-08-21 21:02:24 +01:00
dependabot[bot]
5f9ac64892 build(deps): bump hyprland from 0.3.8 to 0.3.9
Bumps [hyprland](https://github.com/hyprland-community/hyprland-rs) from 0.3.8 to 0.3.9.
- [Release notes](https://github.com/hyprland-community/hyprland-rs/releases)
- [Commits](https://github.com/hyprland-community/hyprland-rs/compare/0.3.8...0.3.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 14:50:45 +00:00
dependabot[bot]
3f7904fefb build(deps): bump clap from 4.3.21 to 4.3.23
Bumps [clap](https://github.com/clap-rs/clap) from 4.3.21 to 4.3.23.
- [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/v4.3.21...v4.3.23)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 14:50:26 +00:00
dependabot[bot]
84e0065c0c build(deps): bump tokio from 1.31.0 to 1.32.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.31.0 to 1.32.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.31.0...tokio-1.32.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-21 14:50:17 +00:00
JakeStanger
e5281e9619 docs: update CHANGELOG.md for v0.13.0 [skip ci] 2023-08-16 19:32:13 +00:00
66 changed files with 2028 additions and 1652 deletions

View File

@@ -4,6 +4,58 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.13.0] - 2023-08-16
### :sparkles: New Features
- [`c3e9654`](https://github.com/JakeStanger/ironbar/commit/c3e9654cd3dfcea4276f5b114112b7541dd847fd) - **upower**: icon size option *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`f5bdc5a`](https://github.com/JakeStanger/ironbar/commit/f5bdc5a0272fefca4c91336699ea63913cdf3c08) - ipc server and cli *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`ded50cc`](https://github.com/JakeStanger/ironbar/commit/ded50cca6f01f08a8e44257394fdde634d421e8e) - support for 'ironvar' dynamic variables *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`c6319b7`](https://github.com/JakeStanger/ironbar/commit/c6319b78fd3992ad43327e90b6326ab653238f2e) - **ipc**: support for injecting additional stylesheets *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`27f920d`](https://github.com/JakeStanger/ironbar/commit/27f920d01217bedba279003291ad48c1aaa56bb0) - **launcher**: slightly improve focus logic when clicking item with multiple windows *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`bd90167`](https://github.com/JakeStanger/ironbar/commit/bd90167f4ea90cb97984b9f3b5e6f65b375d0c4a) - **clock**: format option for popup header *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`12053f1`](https://github.com/JakeStanger/ironbar/commit/12053f111a6f05a59e33396b9042821b98b9bc5c) - **music**: progress/seek bar in popup *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`7d3bb02`](https://github.com/JakeStanger/ironbar/commit/7d3bb02b4612f278bcc8a268a48c61b239c63e82) - **ipc**: reload config command *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`b310ea7`](https://github.com/JakeStanger/ironbar/commit/b310ea76362bcdf10e187d6b00cd2b8ed2870c41) - **clock**: localization support *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`738b9e3`](https://github.com/JakeStanger/ironbar/commit/738b9e3da714c9b998030e9f60b9b6f50c62ec76) - **config**: use default fallback with config instructions *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`2ccb263`](https://github.com/JakeStanger/ironbar/commit/2ccb2633c6c4d7f6eb264a6c49951853b728c9f3) - IPC for get_visible, set_visible, new bar `name` config option *(commit by [@A-Cloud-Ninja](https://github.com/A-Cloud-Ninja))*
- [`b7ee794`](https://github.com/JakeStanger/ironbar/commit/b7ee794bfc86730e7921c8a930cf8d8bb44474ad) - **ipc**: commands for opening/closing popups *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`ef443e6`](https://github.com/JakeStanger/ironbar/commit/ef443e6978949479388129760dabc3f8930c0b0f) - **image resolver**: add fallback image *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`9f65cf2`](https://github.com/JakeStanger/ironbar/commit/9f65cf293d9527a2c536847f0005957421a9be33) - **workspaces**: add `favorites` and `hidden` options *(commit by [@yavko](https://github.com/yavko))*
- [`19c684e`](https://github.com/JakeStanger/ironbar/commit/19c684e49facb57e3e2acf9cafecf177c2e1bfbf) - **nix**: automatic development environment with direnv *(commit by [@yavko](https://github.com/yavko))*
### :bug: Bug Fixes
- [`6db7742`](https://github.com/JakeStanger/ironbar/commit/6db7742e068dc03d67dbf35e0d9db27f900392fe) - crash on startup introduced by recent refactors *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`f78c7f9`](https://github.com/JakeStanger/ironbar/commit/f78c7f9b981c98676e855ff6a63e33a51927c709) - not resolving flatpak application icons *(commit by [@body20002](https://github.com/body20002))*
- [`1759945`](https://github.com/JakeStanger/ironbar/commit/1759945912e376581e5fcd5ed2916f89e2090f2b) - **music**: correctly show/hide popup elements based on player capabilities *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`a9ac29d`](https://github.com/JakeStanger/ironbar/commit/a9ac29d8857256d13e14104db235117e3c752972) - clipboard partially behind wrong feature flag *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`c711dd8`](https://github.com/JakeStanger/ironbar/commit/c711dd858554140bcfb6df515a43b40ee2baee67) - failing to resolve icons with home_manager *(commit by [@christoph00](https://github.com/christoph00))*
- [`1a272e0`](https://github.com/JakeStanger/ironbar/commit/1a272e00fbeca4b5e39b527ffed439bc51fd4080) - **label**: not using markup *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`4ca17d1`](https://github.com/JakeStanger/ironbar/commit/4ca17d1337acfbbc21c04058d97f689a1cce60a6) - **launcher**: incorrectly resolving some applications *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`eee2182`](https://github.com/JakeStanger/ironbar/commit/eee2182ab90fdc22cd05da9417cbee17e4c67088) - **ipc**: command/response casing *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`c582bc3`](https://github.com/JakeStanger/ironbar/commit/c582bc33905702a9ebe323e6dfa9413485f48fb7) - **cli**: `set-visible` command causing panic *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`87dd764`](https://github.com/JakeStanger/ironbar/commit/87dd7646fc9223ac7e67842934f3bd104b4eea80) - **launcher**: not clearing focused state when closing window *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`6f57ad4`](https://github.com/JakeStanger/ironbar/commit/6f57ad47ac30348c0ae2b7dba35d5ffdbf96f72d) - **launcher**: not setting focus state when opening favourite *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`2367faa`](https://github.com/JakeStanger/ironbar/commit/2367faab0440327620052de845c6a0d3032f2f05) - **image**: using fallback in places it shouldn't *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`7f6fef6`](https://github.com/JakeStanger/ironbar/commit/7f6fef6338d7a8c909f3224b32426dfc2aacc295) - **image**: matching desktop file names too eagerly *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`89ec06f`](https://github.com/JakeStanger/ironbar/commit/89ec06fc7b252052f96e45f5d0f6d6504878a13a) - **music**: hide album art widget when no image *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`2902331`](https://github.com/JakeStanger/ironbar/commit/2902331af00f2e52fdea06964fbd89d72fe2ebbb) - **dynamic string**: incorrectly handling strings containing multipoint utf-8 chars *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`901a86c`](https://github.com/JakeStanger/ironbar/commit/901a86caa491648268f0618e17a25b978552db0c) - **custom**: crash when clicking non-popup button *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`54f0f23`](https://github.com/JakeStanger/ironbar/commit/54f0f232f208602699e5021942c3d0f3947ca6de) - **launcher**: popup not closing when hover leaves widget *(commit by [@JakeStanger](https://github.com/JakeStanger))*
### :recycle: Refactors
- [`d121dc3`](https://github.com/JakeStanger/ironbar/commit/d121dc3d1e9468a67deb528c35fc3897c3840f77) - fix unused var warning *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`cc181a8`](https://github.com/JakeStanger/ironbar/commit/cc181a8b6d0ac1cccd4ed2f9f420c138ed5383d2) - fix new clippy warnings *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`7016f7f`](https://github.com/JakeStanger/ironbar/commit/7016f7f79e7e29a3318b535ba224aa78bd91688a) - use new smart pointer macros throughout codebase *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`06251e2`](https://github.com/JakeStanger/ironbar/commit/06251e293e8f56e1897fed80335f114fdea57183) - fix new pedantic clippy warnings *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`36f3db7`](https://github.com/JakeStanger/ironbar/commit/36f3db741178b959070ee90bcd6448e1b2a6ef26) - **image**: do not try to read desktop files where definitely not necessary *(commit by [@JakeStanger](https://github.com/JakeStanger))*
### :memo: Documentation Changes
- [`aea8de2`](https://github.com/JakeStanger/ironbar/commit/aea8de25522e5f5e7f92f46a6248eb2e79cb457e) - update CHANGELOG.md for v0.12.1 [skip ci] *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`607c728`](https://github.com/JakeStanger/ironbar/commit/607c7285d7e01265a8c8417e2941b2099e61aa42) - update for ipc/cli, tidy a bit *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`4b88079`](https://github.com/JakeStanger/ironbar/commit/4b88079561e5c9fec63480afe56a1f89c76dc094) - fix header *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`4620f29`](https://github.com/JakeStanger/ironbar/commit/4620f29d381394aef8b241b03009ef8c3b8d0145) - **examples**: update stylesheet *(commit by [@JakeStanger](https://github.com/JakeStanger))*
- [`3d94987`](https://github.com/JakeStanger/ironbar/commit/3d949874de90b0e5c06cb62726629133d0ea76e3) - **ipc**: add link to luajit library *(commit by [@JakeStanger](https://github.com/JakeStanger))*
## [v0.12.1] - 2023-06-18
### :boom: BREAKING CHANGES
- due to [`e11177f`](https://github.com/JakeStanger/ironbar/commit/e11177fea3095560057278d71cebca01bed295d6) - add sensible class names for icon labels *(commit by [@JakeStanger](https://github.com/JakeStanger))*:
@@ -372,4 +424,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[v0.10.0]: https://github.com/JakeStanger/ironbar/compare/v0.9.0...v0.10.0
[v0.11.0]: https://github.com/JakeStanger/ironbar/compare/v0.10.0...v0.11.0
[v0.12.0]: https://github.com/JakeStanger/ironbar/compare/v0.11.0...v0.12.0
[v0.12.1]: https://github.com/JakeStanger/ironbar/compare/v0.12.0...v0.12.1
[v0.12.1]: https://github.com/JakeStanger/ironbar/compare/v0.12.0...v0.12.1
[v0.13.0]: https://github.com/JakeStanger/ironbar/compare/v0.12.1...v0.13.0

766
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "ironbar"
version = "0.13.0"
version = "0.14.0-pre"
edition = "2021"
license = "MIT"
description = "Customisable GTK Layer Shell wlroots/sway bar"
@@ -63,10 +63,10 @@ workspaces = ["futures-util"]
[dependencies]
# core
gtk = "0.17.0"
gtk-layer-shell = "0.6.0"
glib = "0.17.10"
tokio = { version = "1.31.0", features = [
gtk = "0.18.1"
gtk-layer-shell = "0.8.0"
glib = "0.18.5"
tokio = { version = "1.35.1", features = [
"macros",
"rt-multi-thread",
"time",
@@ -75,66 +75,66 @@ tokio = { version = "1.31.0", features = [
"io-util",
"net",
] }
tracing = "0.1.37"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tracing-error = "0.2.0"
tracing-appender = "0.2.2"
tracing-appender = "0.2.3"
strip-ansi-escapes = "0.2.0"
color-eyre = "0.6.2"
serde = { version = "1.0.183", features = ["derive"] }
indexmap = "2.0.0"
serde = { version = "1.0.193", features = ["derive"] }
indexmap = "2.1.0"
dirs = "5.0.1"
walkdir = "2.3.2"
notify = { version = "6.0.1", default-features = false }
wayland-client = "0.30.2"
wayland-protocols = { version = "0.30.1", features = ["unstable", "client"] }
wayland-protocols-wlr = { version = "0.1.0", features = ["client"] }
smithay-client-toolkit = { version = "0.17.0", default-features = false, features = [
walkdir = "2.4.0"
notify = { version = "6.1.1", default-features = false }
wayland-client = "0.31.1"
wayland-protocols = { version = "0.31.0", features = ["unstable", "client"] }
wayland-protocols-wlr = { version = "0.2.0", features = ["client"] }
smithay-client-toolkit = { version = "0.18.0", default-features = false, features = [
"calloop",
] }
universal-config = { version = "0.4.2", default_features = false }
ctrlc = "3.4.0"
universal-config = { version = "0.4.3", default_features = false }
ctrlc = "3.4.2"
lazy_static = "1.4.0"
async_once = "0.2.6"
cfg-if = "1.0.0"
# cli
clap = { version = "4.3.21", optional = true, features = ["derive"] }
clap = { version = "4.4.12", optional = true, features = ["derive"] }
# ipc
serde_json = { version = "1.0.104", optional = true }
serde_json = { version = "1.0.109", optional = true }
# http
reqwest = { version = "0.11.18", optional = true }
reqwest = { version = "0.11.23", optional = true }
# clipboard
nix = { version = "0.26.2", optional = true, features = ["event"] }
nix = { version = "0.27.1", optional = true, features = ["event"] }
# clock
chrono = { version = "0.4.26", optional = true, features = ["unstable-locales"] }
chrono = { version = "0.4.31", optional = true, features = ["unstable-locales"] }
# music
mpd_client = { version = "1.2.0", optional = true }
mpd_client = { version = "1.3.0", optional = true }
mpris = { version = "2.0.1", optional = true }
# sys_info
sysinfo = { version = "0.29.8", optional = true }
sysinfo = { version = "0.29.11", optional = true }
# tray
system-tray = { version = "0.1.4", optional = true }
# upower
upower_dbus = { version = "0.3.2", optional = true }
futures-lite = { version = "1.12.0", optional = true }
futures-lite = { version = "2.1.0", optional = true }
zbus = { version = "3.14.1", optional = true }
# workspaces
swayipc-async = { version = "2.0.1", optional = true }
hyprland = { version = "0.3.8", features = ["silent"], optional = true }
futures-util = { version = "0.3.21", optional = true }
hyprland = { version = "0.3.12", features = ["silent"], optional = true }
futures-util = { version = "0.3.30", optional = true }
# shared
regex = { version = "1.8.4", default-features = false, features = [
regex = { version = "1.10.2", default-features = false, features = [
"std",
], optional = true } # music, sys_info

View File

@@ -75,7 +75,15 @@ cargo install ironbar
yay -S ironbar-git
```
### Nix Flake
### Nix
[nix package](https://search.nixos.org/packages?channel=unstable&show=ironbar)
```sh
nix-shell -p ironbar
```
#### Flake
A flake is included with the repo which can be used with Home Manager.
@@ -170,4 +178,4 @@ All are welcome, but I ask a few basic things to help make things easier. Please
- [Waybar](https://github.com/Alexays/Waybar) - A lot of the initial inspiration, and a pretty great bar.
- [Rustbar](https://github.com/zeroeightysix/rustbar) - Served as a good demo for writing a basic GTK bar in Rust
- [Smithay Client Toolkit](https://github.com/Smithay/client-toolkit) - Essential in being able to communicate to Wayland
- [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell) - Ironbar and many other projects would be impossible without this
- [gtk-layer-shell](https://github.com/wmww/gtk-layer-shell) - Ironbar and many other projects would be impossible without this

View File

@@ -18,6 +18,8 @@ You also need rust; only the latest stable version is supported.
```shell
pacman -S gtk3 gtk-layer-shell
# for http support
pacman -S openssl
```
### Ubuntu/Debian
@@ -31,7 +33,9 @@ apt install libssl-dev
### Fedora
```shell
dnf install gtk3 gtk-layer-shell
dnf install gtk3-devel gtk-layer-shell-devel
# for http support
dnf install openssl-devel
```
## Features

View File

@@ -95,7 +95,11 @@ Create a map/object called `monitors` inside the top-level object.
Each of the map's keys should be an output name,
and each value should be an object containing the bar config.
To find your output names, run `wayland-info | grep wl_output -A1`.
You can still define a top-level "default" config to use for unspecified monitors.
Alternatively, leave the top-level `start`, `center` and `end` keys null to hide bars on unspecified monitors.
> [!TIP]
> To find your output names, run `wayland-info | grep wl_output -A1`.
<details>
<summary>JSON</summary>
@@ -267,22 +271,24 @@ Check [here](config) for an example config file for a fully configured bar in ea
The following table lists each of the top-level bar config options:
| Name | Type | Default | Description |
|--------------------|----------------------------------------|-----------|-----------------------------------------------------------------------------------------------------------------|
| `name` | `string` | `bar-<n>` | A unique identifier for the bar, used for controlling it over IPC. If not set, uses a generated integer suffix. |
| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
| `height` | `integer` | `42` | The bar's height in pixels. |
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
| `margin.top` | `integer` | `0` | The margin on the top of the bar |
| `margin.bottom` | `integer` | `0` | The margin on the bottom of the bar |
| `margin.left` | `integer` | `0` | The margin on the left of the bar |
| `margin.right` | `integer` | `0` | The margin on the right of the bar |
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
| `ironvar_defaults` | `Map<string, string>` | `{}` | Map of [ironvar](ironvars) keys against their default values. |
| `start` | `Module[]` | `[]` | Array of left or top modules. |
| `center` | `Module[]` | `[]` | Array of center modules. |
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
| Name | Type | Default | Description |
|--------------------|----------------------------------------|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------|
| `name` | `string` | `bar-<n>` | A unique identifier for the bar, used for controlling it over IPC. If not set, uses a generated integer suffix. |
| `position` | `top` or `bottom` or `left` or `right` | `bottom` | The bar's position on screen. |
| `anchor_to_edges` | `boolean` | `false` | Whether to anchor the bar to the edges of the screen. Setting to false centres the bar. |
| `height` | `integer` | `42` | The bar's height in pixels. |
| `popup_gap` | `integer` | `5` | The gap between the bar and popup window. |
| `margin.top` | `integer` | `0` | The margin on the top of the bar |
| `margin.bottom` | `integer` | `0` | The margin on the bottom of the bar |
| `margin.left` | `integer` | `0` | The margin on the left of the bar |
| `margin.right` | `integer` | `0` | The margin on the right of the bar |
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
| `ironvar_defaults` | `Map<string, string>` | `{}` | Map of [ironvar](ironvars) keys against their default values. |
| `start_hidden` | `boolean` | `false`, or `true` if `autohide` set | Whether the bar should be hidden when the application starts. Enabled by default when `autohide` is set. |
| `autohide` | `integer` | `null` | The duration in milliseconds before the bar is hidden after the cursor leaves. Leave unset to disable auto-hide behaviour. |
| `start` | `Module[]` | `[]` | Array of left or top modules. |
| `center` | `Module[]` | `[]` | Array of center modules. |
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
### 3.2 Module-level options

View File

@@ -1,7 +1,7 @@
In some configuration locations, Ironbar supports dynamic values,
meaning you can inject content into the bar from an external source.
Currently two dynamic content sources are supported - scripts and ironvars.
Currently two dynamic content sources are supported - [scripts](scripts) (via shorthand syntax) and [ironvars](ironvars).
## Dynamic String
@@ -36,4 +36,4 @@ Example:
```toml
show_if = "exit 0" # script
show_if = "#show_module" # variable
```
```

View File

@@ -10,25 +10,34 @@ which only includes a subset of the full web spec (plus a few non-standard prope
The below table describes the selectors provided by the bar itself.
Information on styling individual modules can be found on their pages in the sidebar.
| Selector | Description |
|----------------|--------------------------------------------|
| `.background` | Top-level window. |
| `#bar` | Bar root box. |
| `#bar #start` | Bar left or top modules container box. |
| `#bar #center` | Bar center modules container box. |
| `#bar #end` | Bar right or bottom modules container box. |
| `.container` | All of the above. |
| `.popup` | Any popup box. |
| Selector | Description |
|---------------------|--------------------------------------------|
| `.background` | Top-level window. |
| `#bar` | Bar root box. |
| `#bar #start` | Bar left or top modules container box. |
| `#bar #center` | Bar center modules container box. |
| `#bar #end` | Bar right or bottom modules container box. |
| `.container` | All of the above. |
| `.widget-container` | The `EventBox` wrapping any widget. |
| `.widget` | Any widget. |
| `.popup` | Any popup box. |
Every widget can be selected using a `kebab-case` class name matching its name.
Every Ironbar widget can be selected using a `kebab-case` class name matching its name.
You can also target popups by prefixing `popup-` to the name. For example, you can use `.clock` and `.popup-clock` respectively.
Setting the `name` option on a widget allows you to target that specific instance using `#name`.
You can also add additional classes to re-use styles. In both cases, `popup-` is automatically prefixed to the popup (`#popup-name` or `.popup-my-class`).
You can also target all GTK widgets of a certain type directly using their name. For example, `button:hover` will select the hover state on *all* buttons.
You can also target all GTK widgets of a certain type directly using their name. For example, `label` will select all labels, and `button:hover` will select the hover state on *all* buttons.
These names are all lower case with no separator, so `MenuBar` -> `menubar`.
> [!NOTE]
> If an entry takes no effect you might have to use a more specific selector.
> For example, attempting to set text size on `.popup-clipboard .item` will likely have no effect.
> Instead, you can target the more specific `.popup-clipboard .item label`.
Running `ironbar inspect` can be used to find out how to address an element.
GTK CSS does not support custom properties, but it does have its own custom `@define-color` syntax which you can use for re-using colours:
```css
@@ -37,4 +46,4 @@ GTK CSS does not support custom properties, but it does have its own custom `@de
box, menubar {
background-color: @color_bg;
}
```
```

View File

@@ -21,12 +21,12 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
| `truncate.max_length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
| `icons.play` | `string` or [image](images) | `` | Icon to show when playing. |
| `icons.pause` | `string` or [image](images) | `` | Icon to show when paused. |
| `icons.prev` | `string` or [image](images) | `` | Icon to show on previous button. |
| `icons.next` | `string` or [image](images) | `` | Icon to show on next button. |
| `icons.volume` | `string` or [image](images) | `` | Icon to show under popup volume slider. |
| `icons.track` | `string` or [image](images) | `` | Icon to show next to track title. |
| `icons.album` | `string` or [image](images) | `` | Icon to show next to album name. |
| `icons.artist` | `string` or [image](images) | `` | Icon to show next to artist name. |
| `icons.prev` | `string` or [image](images) | `󰒮` | Icon to show on previous button. |
| `icons.next` | `string` or [image](images) | `󰒭` | Icon to show on next button. |
| `icons.volume` | `string` or [image](images) | `󰕾` | Icon to show under popup volume slider. |
| `icons.track` | `string` or [image](images) | `󰎈` | Icon to show next to track title. |
| `icons.album` | `string` or [image](images) | `󰀥` | Icon to show next to album name. |
| `icons.artist` | `string` or [image](images) | `󰠃` | Icon to show next to artist name. |
| `show_status_icon` | `boolean` | `true` | Whether to show the play/pause icon on the widget. |
| `icon_size` | `integer` | `32` | Size to render icon at (image icons only). |
| `cover_image_size` | `integer` | `128` | Size to render album art image at inside popup. |

View File

@@ -28,13 +28,13 @@ Pango markup is supported.
"end": [
{
"format": [
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C",
" {memory_used} / {memory_total} GB ({memory_percent}%)",
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
" {load_average:1} | {load_average:5} | {load_average:15}",
" {uptime}"
"󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
"󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps",
"󰖡 {load_average:1} | {load_average:5} | {load_average:15}",
"󰥔 {uptime}"
],
"interval": {
"cpu": 1,
@@ -58,13 +58,13 @@ Pango markup is supported.
[[end]]
type = 'sys_info'
format = [
' {cpu_percent}% | {temp_c:k10temp_Tccd1}°C',
' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C',
' {memory_used} / {memory_total} GB ({memory_percent}%)',
'| {swap_used} / {swap_total} GB ({swap_percent}%)',
' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
' {net_down:enp39s0} / {net_up:enp39s0} Mbps',
' {load_average:1} | {load_average:5} | {load_average:15}',
' {uptime}',
'󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
'󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps',
'󰖡 {load_average:1} | {load_average:5} | {load_average:15}',
'󰥔 {uptime}',
]
[end.interval]
@@ -85,13 +85,13 @@ temps = 5
```yaml
end:
- format:
- ' {cpu_percent}% | {temp_c:k10temp_Tccd1}°C'
- ' {cpu_percent}% | {temp_c:k10temp-Tccd1}°C'
- ' {memory_used} / {memory_total} GB ({memory_percent}%)'
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
- ' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)'
- ' {net_down:enp39s0} / {net_up:enp39s0} Mbps'
- ' {load_average:1} | {load_average:5} | {load_average:15}'
- ' {uptime}'
- '󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)'
- '󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps'
- '󰖡 {load_average:1} | {load_average:5} | {load_average:15}'
- '󰥔 {uptime}'
interval:
cpu: 1
disks: 300
@@ -119,13 +119,13 @@ end:
interval.networks = 3
format = [
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C"
" {cpu_percent}% | {temp_c:k10temp-Tccd1}°C"
" {memory_used} / {memory_total} GB ({memory_percent}%)"
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
" {load_average:1} | {load_average:5} | {load_average:15}"
" {uptime}"
"󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
"󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps"
"󰖡 {load_average:1} | {load_average:5} | {load_average:15}"
"󰥔 {uptime}"
]
}
]
@@ -168,7 +168,7 @@ The following tokens can be used in the `format` configuration option:
| `{load_average:15}` | 15-minute load average. |
| `{uptime}` | System uptime formatted as `HH:mm`. |
For Intel CPUs, you can typically use `coretemp-Package-id-0` for the temperature sensor. For AMD, you can use `k10temp_Tccd1`.
For Intel CPUs, you can typically use `coretemp-Package-id-0` for the temperature sensor. For AMD, you can use `k10temp-Tccd1`.
## Styling

View File

@@ -103,6 +103,8 @@ end:
| `.workspaces` | Workspaces widget box |
| `.workspaces .item` | Workspace button |
| `.workspaces .item.focused` | Workspace button (workspace focused) |
| `.workspaces .item.visible` | Workspace button (workspace visible, including focused) |
| `.workspaces .item.inactive` | Workspace button (favourite, not currently open)
| `.workspaces .item .icon` | Workspace button icon (any type) |
| `.workspaces .item .text-icon` | Workspace button icon (textual only) |
| `.workspaces .item .image` | Workspace button icon (image only) |

View File

@@ -3,7 +3,7 @@ let {
type = "workspaces"
all_monitors = false
name_map = {
1 = ""
1 = "󰙯"
2 = "icon:firefox"
3 = ""
Games = "icon:steam"
@@ -46,10 +46,10 @@ let {
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C"
" {memory_used} / {memory_total} GB ({memory_percent}%)"
"| {swap_used} / {swap_total} GB ({swap_percent}%)"
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
" {net_down:enp39s0} / {net_up:enp39s0} Mbps"
" {load_average:1} | {load_average:5} | {load_average:15}"
" {uptime}"
"󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)"
"󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps"
"󰖡 {load_average:1} | {load_average:5} | {load_average:15}"
"󰥔 {uptime}"
]
}
@@ -67,7 +67,7 @@ let {
$clipboard = { type = "clipboard" max_items = 3 truncate.mode = "end" truncate.length = 50 }
$label = { type = "label" label = "random num: {{500:echo $RANDOM}}" }
$label = { type = "label" label = "random num: {{500:echo FIXME}}" }
// -- begin custom --
$button = { type = "button" name="power-btn" label = "" on_click = "popup:toggle" }

View File

@@ -29,10 +29,10 @@
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
" {memory_used} / {memory_total} GB ({memory_percent}%)",
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
" {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
" {net_down:enp39s0} / {net_up:enp39s0} Mbps",
" {load_average:1} | {load_average:5} | {load_average:15}",
" {uptime}"
"󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
"󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps",
"󰖡 {load_average:1} | {load_average:5} | {load_average:15}",
"󰥔 {uptime}"
],
"interval": {
"cpu": 1,
@@ -109,7 +109,7 @@
{
"all_monitors": false,
"name_map": {
"1": "",
"1": "󰙯",
"2": "icon:firefox",
"3": "",
"Code": "",
@@ -128,7 +128,7 @@
"type": "launcher"
},
{
"label": "random num: {{500:echo $RANDOM}}",
"label": "random num: {{500:echo FIXME}}",
"type": "label"
}
]

View File

@@ -1,41 +1,41 @@
anchor_to_edges = true
icon_theme = 'Paper'
position = 'bottom'
icon_theme = "Paper"
position = "bottom"
[[end]]
music_dir = '/home/jake/Music'
player_type = 'mpd'
type = 'music'
music_dir = "/home/jake/Music"
player_type = "mpd"
type = "music"
[end.truncate]
max_length = 100
mode = 'end'
mode = "end"
[[end]]
host = 'chloe:6600'
player_type = 'mpd'
truncate = 'end'
type = 'music'
host = "chloe:6600"
player_type = "mpd"
truncate = "end"
type = "music"
[[end]]
cmd = '/home/jake/bin/phone-battery'
type = 'script'
cmd = "/home/jake/bin/phone-battery"
type = "script"
[end.show_if]
cmd = '/home/jake/bin/phone-connected'
cmd = "/home/jake/bin/phone-connected"
interval = 500
[[end]]
type = 'sys_info'
format = [
' {cpu_percent}% | {temp_c:k10temp_Tccd1}°C',
' {memory_used} / {memory_total} GB ({memory_percent}%)',
'| {swap_used} / {swap_total} GB ({swap_percent}%)',
' {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)',
'李 {net_down:enp39s0} / {net_up:enp39s0} Mbps',
'猪 {load_average:1} | {load_average:5} | {load_average:15}',
' {uptime}',
" {cpu_percent}% | {temp_c:k10temp_Tccd1}°C",
" {memory_used} / {memory_total} GB ({memory_percent}%)",
"| {swap_used} / {swap_total} GB ({swap_percent}%)",
"󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)",
"󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps",
"󰖡 {load_average:1} | {load_average:5} | {load_average:15}",
"󰥔 {uptime}",
]
type = "sys_info"
[end.interval]
cpu = 1
@@ -46,77 +46,77 @@ temps = 5
[[end]]
max_items = 3
type = 'clipboard'
type = "clipboard"
[end.truncate]
length = 50
mode = 'end'
mode = "end"
[[end]]
class = 'power-menu'
tooltip = '''Up: {{30000:uptime -p | cut -d ' ' -f2-}}'''
type = 'custom'
class = "power-menu"
tooltip = "Up: {{30000:uptime -p | cut -d ' ' -f2-}}"
type = "custom"
[[end.bar]]
label = ''
name = 'power-btn'
on_click = 'popup:toggle'
type = 'button'
label = ""
name = "power-btn"
on_click = "popup:toggle"
type = "button"
[[end.popup]]
orientation = 'vertical'
type = 'box'
orientation = "vertical"
type = "box"
[[end.popup.widgets]]
label = 'Power menu'
name = 'header'
type = 'label'
label = "Power menu"
name = "header"
type = "label"
[[end.popup.widgets]]
type = 'box'
type = "box"
[[end.popup.widgets.widgets]]
class = 'power-btn'
label = '''<span font-size='40pt'></span>'''
on_click = '!shutdown now'
type = 'button'
class = "power-btn"
label = "<span font-size='40pt'></span>"
on_click = "!shutdown now"
type = "button"
[[end.popup.widgets.widgets]]
class = 'power-btn'
label = '''<span font-size='40pt'></span>'''
on_click = '!reboot'
type = 'button'
class = "power-btn"
label = "<span font-size='40pt'></span>"
on_click = "!reboot"
type = "button"
[[end.popup.widgets]]
label = '''Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}'''
name = 'uptime'
type = 'label'
label = "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}"
name = "uptime"
type = "label"
[[end]]
type = 'clock'
type = "clock"
[[start]]
all_monitors = false
type = 'workspaces'
type = "workspaces"
[start.name_map]
1 = 'ﭮ'
2 = 'icon:firefox'
3 = ''
Code = ''
Games = 'icon:steam'
1 = "󰙯"
2 = "icon:firefox"
3 = ""
Code = ""
Games = "icon:steam"
[[start]]
favorites = [
"firefox",
"discord",
"steam",
]
show_icons = true
show_names = false
type = 'launcher'
favorites = [
'firefox',
'discord',
'steam',
]
type = "launcher"
[[start]]
label = 'random num: {{500:echo $RANDOM}}'
type = 'label'
label = "random num: {{500:echo FIXME}}"
type = "label"

View File

@@ -1,87 +1,87 @@
anchor_to_edges: true
end:
- music_dir: /home/jake/Music
player_type: mpd
truncate:
max_length: 100
mode: end
type: music
- host: chloe:6600
player_type: mpd
truncate: end
type: music
- cmd: /home/jake/bin/phone-battery
show_if:
cmd: /home/jake/bin/phone-connected
interval: 500
type: script
- format:
-  {cpu_percent}% | {temp_c:k10temp_Tccd1}°C
-  {memory_used} / {memory_total} GB ({memory_percent}%)
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
- {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)
- {net_down:enp39s0} / {net_up:enp39s0} Mbps
- {load_average:1} | {load_average:5} | {load_average:15}
- {uptime}
interval:
cpu: 1
disks: 300
memory: 30
networks: 3
temps: 5
type: sys_info
- max_items: 3
truncate:
length: 50
mode: end
type: clipboard
- bar:
- label:
name: power-btn
on_click: popup:toggle
- music_dir: /home/jake/Music
player_type: mpd
truncate:
max_length: 100
mode: end
type: music
- host: chloe:6600
player_type: mpd
truncate: end
type: music
- cmd: /home/jake/bin/phone-battery
show_if:
cmd: /home/jake/bin/phone-connected
interval: 500
type: script
- format:
-  {cpu_percent}% | {temp_c:k10temp_Tccd1}°C
-  {memory_used} / {memory_total} GB ({memory_percent}%)
- '| {swap_used} / {swap_total} GB ({swap_percent}%)'
- 󰋊 {disk_used:/} / {disk_total:/} GB ({disk_percent:/}%)
- 󰓢 {net_down:enp39s0} / {net_up:enp39s0} Mbps
- 󰖡 {load_average:1} | {load_average:5} | {load_average:15}
- 󰥔 {uptime}
interval:
cpu: 1
disks: 300
memory: 30
networks: 3
temps: 5
type: sys_info
- max_items: 3
truncate:
length: 50
mode: end
type: clipboard
- bar:
- label:
name: power-btn
on_click: popup:toggle
type: button
class: power-menu
popup:
- orientation: vertical
type: box
widgets:
- label: Power menu
name: header
type: label
- type: box
widgets:
- class: power-btn
label: <span font-size='40pt'></span>
on_click: '!shutdown now'
type: button
class: power-menu
popup:
- orientation: vertical
type: box
widgets:
- label: Power menu
name: header
type: label
- type: box
widgets:
- class: power-btn
label: <span font-size='40pt'></span>
on_click: '!shutdown now'
type: button
- class: power-btn
label: <span font-size='40pt'></span>
on_click: '!reboot'
type: button
- label: 'Uptime: {{30000:uptime -p | cut -d '' '' -f2-}}'
name: uptime
type: label
tooltip: 'Up: {{30000:uptime -p | cut -d '' '' -f2-}}'
type: custom
- type: clock
- class: power-btn
label: <span font-size='40pt'></span>
on_click: '!reboot'
type: button
- label: 'Uptime: {{30000:uptime -p | cut -d '' '' -f2-}}'
name: uptime
type: label
tooltip: 'Up: {{30000:uptime -p | cut -d '' '' -f2-}}'
type: custom
- type: clock
icon_theme: Paper
position: bottom
start:
- all_monitors: false
name_map:
'1':
'2': icon:firefox
'3':
Code:
Games: icon:steam
type: workspaces
- favorites:
- firefox
- discord
- steam
show_icons: true
show_names: false
type: launcher
- label: 'random num: {{500:echo $RANDOM}}'
type: label
- all_monitors: false
name_map:
'1': 󰙯
'2': icon:firefox
'3':
Code:
Games: icon:steam
type: workspaces
- favorites:
- firefox
- discord
- steam
show_icons: true
show_names: false
type: launcher
- label: 'random num: {{500:echo FIXME}}'
type: label

113
flake.lock generated
View File

@@ -2,19 +2,16 @@
"nodes": {
"crane": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
],
"rust-overlay": "rust-overlay"
]
},
"locked": {
"lastModified": 1688772518,
"narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
"lastModified": 1703439018,
"narHash": "sha256-VT+06ft/x3eMZ1MJxWzQP3zXFGcrxGo5VR2rB7t88hs=",
"owner": "ipetkov",
"repo": "crane",
"rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
"rev": "afdcd41180e3dfe4dac46b5ee396e3b12ccc967a",
"type": "github"
},
"original": {
@@ -23,44 +20,10 @@
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1687709756,
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
@@ -80,11 +43,11 @@
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1690373729,
"narHash": "sha256-e136hTT7LqQ2QjOTZQMW+jnsevWwBpMj78u6FRUsH9I=",
"lastModified": 1698420672,
"narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=",
"owner": "nix-community",
"repo": "naersk",
"rev": "d9a33d69a9c421d64c8d925428864e93be895dcc",
"rev": "aeb58d5e8faead8980a807c840232697982d47b9",
"type": "github"
},
"original": {
@@ -95,11 +58,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1690833316,
"narHash": "sha256-+YU+/pTJmVKNW12R07/SJiTn7PQk90xwCI4D2PfLRPs=",
"lastModified": 1704008649,
"narHash": "sha256-rGPSWjXTXTurQN9beuHdyJhB8O761w1Zc5BqSSmHvoM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9418167277f665de6f4a29f414d438cf39c55b9e",
"rev": "d44d59d2b5bd694cd9d996fd8c51d03e3e9ba7f7",
"type": "github"
},
"original": {
@@ -109,11 +72,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1690789960,
"narHash": "sha256-3K+2HuyGTiJUSZNJxXXvc0qj4xFx1FHC/ItYtEa7/Xs=",
"lastModified": 1703637592,
"narHash": "sha256-8MXjxU0RfFfzl57Zy3OfXCITS0qWDNLzlBAdwxGZwfY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "fb942492b7accdee4e6d17f5447091c65897dde4",
"rev": "cfc3698c31b1fb9cdcf10f36c9643460264d0ca8",
"type": "github"
},
"original": {
@@ -128,47 +91,22 @@
"crane": "crane",
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay_2"
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": [
"crane",
"flake-utils"
],
"nixpkgs": [
"crane",
"nixpkgs"
]
},
"locked": {
"lastModified": 1688351637,
"narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "f9b92316727af9e6c7fee4a761242f7f46880329",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"rust-overlay_2": {
"inputs": {
"flake-utils": "flake-utils_2",
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1690683485,
"narHash": "sha256-Sp/QpbMg86v12xhCsa6q0yTH8LYaJIcxzbf9LO1zFzM=",
"lastModified": 1703902408,
"narHash": "sha256-qXdWvu+tlgNjeoz8yQMRKSom6QyRROfgpmeOhwbujqw=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "05d480a7aef1aae1bfb67a39134dcf48c5322528",
"rev": "319f57cd2c34348c55970a4bf2b35afe82088681",
"type": "github"
},
"original": {
@@ -191,21 +129,6 @@
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",

View File

@@ -193,8 +193,8 @@
ExecStart = "${pkg}/bin/ironbar";
};
Install.WantedBy = [
(lib.mkIf config.wayland.windowManager.hyprland.systemdIntegration "hyprland-session.target")
(lib.mkIf config.wayland.windowManager.sway.systemdIntegration "sway-session.target")
(lib.mkIf config.wayland.windowManager.hyprland.systemd.enable "hyprland-session.target")
(lib.mkIf config.wayland.windowManager.sway.systemd.enable "sway-session.target")
];
};
};

View File

@@ -3,140 +3,311 @@ use crate::modules::{
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
};
use crate::popup::Popup;
use crate::unique_id::get_unique_usize;
use crate::{arc_rw, Config, GlobalState};
use crate::{Config, Ironbar};
use color_eyre::Result;
use glib::Propagation;
use gtk::gdk::Monitor;
use gtk::prelude::*;
use gtk::{Application, ApplicationWindow, IconTheme, Orientation};
use gtk::{Application, ApplicationWindow, IconTheme, Orientation, Window, WindowType};
use gtk_layer_shell::LayerShell;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use tracing::{debug, info};
/// Creates a new window for a bar,
/// sets it up and adds its widgets.
pub fn create_bar(
app: &Application,
monitor: &Monitor,
monitor_name: &str,
config: Config,
global_state: &Rc<RefCell<GlobalState>>,
) -> Result<()> {
let win = ApplicationWindow::builder().application(app).build();
let bar_name = config
.name
.clone()
.unwrap_or_else(|| format!("bar-{}", get_unique_usize()));
win.set_widget_name(&bar_name);
info!("Creating bar {}", bar_name);
setup_layer_shell(
&win,
monitor,
config.position,
config.anchor_to_edges,
config.margin,
);
let orientation = config.position.get_orientation();
let content = gtk::Box::builder()
.orientation(orientation)
.spacing(0)
.hexpand(false)
.name("bar");
let content = if orientation == Orientation::Horizontal {
content.height_request(config.height)
} else {
content.width_request(config.height)
}
.build();
content.style_context().add_class("container");
let start = create_container("start", orientation);
let center = create_container("center", orientation);
let end = create_container("end", orientation);
content.add(&start);
content.set_center_widget(Some(&center));
content.pack_end(&end, false, false, 0);
let load_result = load_modules(&start, &center, &end, app, config, monitor, monitor_name)?;
global_state
.borrow_mut()
.popups_mut()
.insert(bar_name.into(), load_result.popup);
win.add(&content);
win.connect_destroy_event(|_, _| {
info!("Shutting down");
gtk::main_quit();
Inhibit(false)
});
debug!("Showing bar");
// show each box but do not use `show_all`.
// this ensures `show_if` option works as intended.
start.show();
center.show();
end.show();
content.show();
win.show();
Ok(())
#[derive(Debug, Clone)]
enum Inner {
New { config: Option<Config> },
Loaded { popup: Rc<RefCell<Popup>> },
}
/// Sets up GTK layer shell for a provided application window.
fn setup_layer_shell(
win: &ApplicationWindow,
monitor: &Monitor,
#[derive(Debug, Clone)]
pub struct Bar {
name: String,
monitor_name: String,
position: BarPosition,
anchor_to_edges: bool,
margin: MarginConfig,
) {
gtk_layer_shell::init_for_window(win);
gtk_layer_shell::set_monitor(win, monitor);
gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top);
gtk_layer_shell::auto_exclusive_zone_enable(win);
gtk_layer_shell::set_namespace(win, env!("CARGO_PKG_NAME"));
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Top, margin.top);
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Bottom, margin.bottom);
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, margin.left);
gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, margin.right);
window: ApplicationWindow,
let bar_orientation = position.get_orientation();
content: gtk::Box,
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Top,
position == BarPosition::Top
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Bottom,
position == BarPosition::Bottom
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Left,
position == BarPosition::Left
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
gtk_layer_shell::set_anchor(
win,
gtk_layer_shell::Edge::Right,
position == BarPosition::Right
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
start: gtk::Box,
center: gtk::Box,
end: gtk::Box,
inner: Inner,
}
impl Bar {
pub fn new(app: &Application, monitor_name: String, config: Config) -> Self {
let window = ApplicationWindow::builder()
.application(app)
.type_(WindowType::Toplevel)
.build();
let name = config
.name
.clone()
.unwrap_or_else(|| format!("bar-{}", Ironbar::unique_id()));
window.set_widget_name(&name);
let position = config.position;
let orientation = position.get_orientation();
let content = gtk::Box::builder()
.orientation(orientation)
.spacing(0)
.hexpand(false)
.name("bar");
let content = if orientation == Orientation::Horizontal {
content.height_request(config.height)
} else {
content.width_request(config.height)
}
.build();
content.style_context().add_class("container");
let start = create_container("start", orientation);
let center = create_container("center", orientation);
let end = create_container("end", orientation);
content.add(&start);
content.set_center_widget(Some(&center));
content.pack_end(&end, false, false, 0);
window.add(&content);
window.connect_destroy_event(|_, _| {
info!("Shutting down");
gtk::main_quit();
Propagation::Proceed
});
Bar {
name,
monitor_name,
position,
window,
content,
start,
center,
end,
inner: Inner::New {
config: Some(config),
},
}
}
pub fn init(mut self, monitor: &Monitor) -> Result<Self> {
let Inner::New { ref mut config } = self.inner else {
return Ok(self);
};
let Some(config) = config.take() else {
return Ok(self);
};
info!(
"Initializing bar '{}' on '{}'",
self.name, self.monitor_name
);
self.setup_layer_shell(
&self.window,
true,
config.anchor_to_edges,
config.margin,
monitor,
);
let start_hidden = config.start_hidden.unwrap_or(config.autohide.is_some());
if let Some(autohide) = config.autohide {
let hotspot_window = Window::new(WindowType::Toplevel);
Self::setup_autohide(&self.window, &hotspot_window, autohide);
self.setup_layer_shell(
&hotspot_window,
false,
config.anchor_to_edges,
config.margin,
monitor,
);
if start_hidden {
hotspot_window.show();
}
}
let load_result = self.load_modules(config, monitor)?;
self.show(!start_hidden);
self.inner = Inner::Loaded {
popup: load_result.popup,
};
Ok(self)
}
/// Sets up GTK layer shell for a provided application window.
fn setup_layer_shell(
&self,
win: &impl IsA<Window>,
exclusive_zone: bool,
anchor_to_edges: bool,
margin: MarginConfig,
monitor: &Monitor,
) {
let position = self.position;
win.init_layer_shell();
win.set_monitor(monitor);
win.set_layer(gtk_layer_shell::Layer::Top);
win.set_namespace(env!("CARGO_PKG_NAME"));
if exclusive_zone {
win.auto_exclusive_zone_enable();
}
win.set_layer_shell_margin(gtk_layer_shell::Edge::Top, margin.top);
win.set_layer_shell_margin(gtk_layer_shell::Edge::Bottom, margin.bottom);
win.set_layer_shell_margin(gtk_layer_shell::Edge::Left, margin.left);
win.set_layer_shell_margin(gtk_layer_shell::Edge::Right, margin.right);
let bar_orientation = position.get_orientation();
win.set_anchor(
gtk_layer_shell::Edge::Top,
position == BarPosition::Top
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
win.set_anchor(
gtk_layer_shell::Edge::Bottom,
position == BarPosition::Bottom
|| (bar_orientation == Orientation::Vertical && anchor_to_edges),
);
win.set_anchor(
gtk_layer_shell::Edge::Left,
position == BarPosition::Left
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
win.set_anchor(
gtk_layer_shell::Edge::Right,
position == BarPosition::Right
|| (bar_orientation == Orientation::Horizontal && anchor_to_edges),
);
}
fn setup_autohide(window: &ApplicationWindow, hotspot_window: &Window, timeout: u64) {
hotspot_window.hide();
hotspot_window.set_opacity(0.0);
hotspot_window.set_decorated(false);
hotspot_window.set_size_request(0, 1);
{
let hotspot_window = hotspot_window.clone();
window.connect_leave_notify_event(move |win, _| {
let win = win.clone();
let hotspot_window = hotspot_window.clone();
glib::timeout_add_local_once(Duration::from_millis(timeout), move || {
win.hide();
hotspot_window.show();
});
Propagation::Proceed
});
}
{
let win = window.clone();
hotspot_window.connect_enter_notify_event(move |hotspot_win, _| {
hotspot_win.hide();
win.show();
Propagation::Proceed
});
}
}
/// Loads the configured modules onto a bar.
fn load_modules(&self, config: Config, monitor: &Monitor) -> Result<BarLoadResult> {
let icon_theme = IconTheme::new();
if let Some(ref theme) = config.icon_theme {
icon_theme.set_custom_theme(Some(theme));
}
let app = &self.window.application().expect("to exist");
macro_rules! info {
($location:expr) => {
ModuleInfo {
app,
bar_position: config.position,
monitor,
output_name: &self.monitor_name,
location: $location,
icon_theme: &icon_theme,
}
};
}
// popup ignores module location so can bodge this for now
let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap);
let popup = Rc::new(RefCell::new(popup));
if let Some(modules) = config.start {
let info = info!(ModuleLocation::Left);
add_modules(&self.start, modules, &info, &popup)?;
}
if let Some(modules) = config.center {
let info = info!(ModuleLocation::Center);
add_modules(&self.center, modules, &info, &popup)?;
}
if let Some(modules) = config.end {
let info = info!(ModuleLocation::Right);
add_modules(&self.end, modules, &info, &popup)?;
}
let result = BarLoadResult { popup };
Ok(result)
}
fn show(&self, include_window: bool) {
debug!("Showing bar: {}", self.name);
// show each box but do not use `show_all`.
// this ensures `show_if` option works as intended.
self.start.show();
self.center.show();
self.end.show();
self.content.show();
if include_window {
self.window.show();
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn popup(&self) -> Rc<RefCell<Popup>> {
match &self.inner {
Inner::New { .. } => {
panic!("Attempted to get popup of uninitialized bar. This is a serious bug!")
}
Inner::Loaded { popup } => popup.clone(),
}
}
}
/// Creates a `gtk::Box` container to place widgets inside.
@@ -153,59 +324,7 @@ fn create_container(name: &str, orientation: Orientation) -> gtk::Box {
#[derive(Debug)]
struct BarLoadResult {
popup: Arc<RwLock<Popup>>,
}
/// Loads the configured modules onto a bar.
fn load_modules(
left: &gtk::Box,
center: &gtk::Box,
right: &gtk::Box,
app: &Application,
config: Config,
monitor: &Monitor,
output_name: &str,
) -> Result<BarLoadResult> {
let icon_theme = IconTheme::new();
if let Some(ref theme) = config.icon_theme {
icon_theme.set_custom_theme(Some(theme));
}
macro_rules! info {
($location:expr) => {
ModuleInfo {
app,
bar_position: config.position,
monitor,
output_name,
location: $location,
icon_theme: &icon_theme,
}
};
}
// popup ignores module location so can bodge this for now
let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap);
let popup = arc_rw!(popup);
if let Some(modules) = config.start {
let info = info!(ModuleLocation::Left);
add_modules(left, modules, &info, &popup)?;
}
if let Some(modules) = config.center {
let info = info!(ModuleLocation::Center);
add_modules(center, modules, &info, &popup)?;
}
if let Some(modules) = config.end {
let info = info!(ModuleLocation::Right);
add_modules(right, modules, &info, &popup)?;
}
let result = BarLoadResult { popup };
Ok(result)
popup: Rc<RefCell<Popup>>,
}
/// Adds modules into a provided GTK box,
@@ -214,7 +333,7 @@ fn add_modules(
content: &gtk::Box,
modules: Vec<ModuleConfig>,
info: &ModuleInfo,
popup: &Arc<RwLock<Popup>>,
popup: &Rc<RefCell<Popup>>,
) -> Result<()> {
let orientation = info.bar_position.get_orientation();
@@ -226,7 +345,7 @@ fn add_modules(
$id,
common.name.clone(),
&info,
&Arc::clone(&popup),
&Rc::clone(&popup),
)?;
set_widget_identifiers(&widget_parts, &common);
@@ -236,7 +355,7 @@ fn add_modules(
}
for config in modules {
let id = get_unique_usize();
let id = Ironbar::unique_id();
match config {
#[cfg(feature = "clipboard")]
ModuleConfig::Clipboard(mut module) => add_module!(module, id),
@@ -262,3 +381,13 @@ fn add_modules(
Ok(())
}
pub fn create_bar(
app: &Application,
monitor: &Monitor,
monitor_name: String,
config: Config,
) -> Result<Bar> {
let bar = Bar::new(app, monitor_name, config);
bar.init(monitor)
}

View File

@@ -1,44 +0,0 @@
use crate::send;
use tokio::spawn;
use tokio::sync::mpsc;
/// MPSC async -> GTK sync channel.
/// The sender uses `tokio::sync::mpsc`
/// while the receiver uses `glib::MainContext::channel`.
///
/// This makes it possible to send events asynchronously
/// and receive them on the main thread,
/// allowing UI updates to be handled on the receiving end.
pub struct BridgeChannel<T> {
async_tx: mpsc::Sender<T>,
sync_rx: glib::Receiver<T>,
}
impl<T: Send + 'static> BridgeChannel<T> {
/// Creates a new channel
pub fn new() -> Self {
let (async_tx, mut async_rx) = mpsc::channel(32);
let (sync_tx, sync_rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
spawn(async move {
while let Some(val) = async_rx.recv().await {
send!(sync_tx, val);
}
});
Self { async_tx, sync_rx }
}
/// Gets a clone of the sender.
pub fn create_sender(&self) -> mpsc::Sender<T> {
self.async_tx.clone()
}
/// Attaches a callback to the receiver.
pub fn recv<F>(self, f: F) -> glib::SourceId
where
F: FnMut(T) -> glib::Continue + 'static,
{
self.sync_rx.attach(None, f)
}
}

View File

@@ -1,10 +1,9 @@
use super::wayland::{self, ClipboardItem};
use crate::{arc_mut, lock, try_send};
use crate::{arc_mut, lock, spawn, try_send};
use indexmap::map::Iter;
use indexmap::IndexMap;
use lazy_static::lazy_static;
use std::sync::{Arc, Mutex};
use tokio::spawn;
use tokio::sync::mpsc;
use tracing::{debug, trace};

View File

@@ -1,14 +1,13 @@
use super::{Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{arc_mut, lock, send};
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{arc_mut, lock, send, spawn_blocking};
use color_eyre::Result;
use hyprland::data::{Workspace as HWorkspace, Workspaces};
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
use hyprland::event_listener::EventListener;
use hyprland::prelude::*;
use hyprland::shared::WorkspaceType;
use hyprland::shared::{HyprDataVec, WorkspaceType};
use lazy_static::lazy_static;
use tokio::sync::broadcast::{channel, Receiver, Sender};
use tokio::task::spawn_blocking;
use tracing::{debug, error, info};
pub struct EventClient {
@@ -52,11 +51,8 @@ impl EventClient {
let workspace_name = get_workspace_name(workspace_type);
let prev_workspace = lock!(active);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let Some(workspace) = workspace {
send!(tx, WorkspaceUpdate::Add(workspace));
@@ -80,10 +76,7 @@ impl EventClient {
);
let workspace_name = get_workspace_name(workspace_type);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
workspace.map_or_else(
|| {
@@ -91,8 +84,7 @@ impl EventClient {
},
|workspace| {
// there may be another type of update so dispatch that regardless of focus change
send!(tx, WorkspaceUpdate::Update(workspace.clone()));
if !focused {
if !workspace.visibility.is_focused() {
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
}
},
@@ -117,12 +109,11 @@ impl EventClient {
);
let workspace_name = get_workspace_name(workspace_type);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let (Some(workspace), false) = (workspace, focused) {
if let Some((false, workspace)) =
workspace.map(|w| (w.visibility.is_focused(), w))
{
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
} else {
error!("Unable to locate workspace");
@@ -142,15 +133,12 @@ impl EventClient {
let mut prev_workspace = lock!(active);
let workspace_name = get_workspace_name(workspace_type);
let focused = prev_workspace
.as_ref()
.map_or(false, |w| w.name == workspace_name);
let workspace = Self::get_workspace(&workspace_name, focused);
let workspace = Self::get_workspace(&workspace_name, prev_workspace.as_ref());
if let Some(workspace) = workspace {
send!(tx, WorkspaceUpdate::Move(workspace.clone()));
if !focused {
if !workspace.visibility.is_focused() {
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
}
}
@@ -180,32 +168,28 @@ impl EventClient {
workspace: Workspace,
tx: &Sender<WorkspaceUpdate>,
) {
let old = prev_workspace
.as_ref()
.map(|w| w.name.clone())
.unwrap_or_default();
send!(
tx,
WorkspaceUpdate::Focus {
old,
new: workspace.name.clone(),
old: prev_workspace.take(),
new: workspace.clone(),
}
);
prev_workspace.replace(workspace);
}
/// Gets a workspace by name from the server.
///
/// Use `focused` to manually mark the workspace as focused,
/// as this is not automatically checked.
fn get_workspace(name: &str, focused: bool) -> Option<Workspace> {
/// Gets a workspace by name from the server, given the active workspace if known.
fn get_workspace(name: &str, active: Option<&Workspace>) -> Option<Workspace> {
Workspaces::get()
.expect("Failed to get workspaces")
.find_map(|w| {
if w.name == name {
Some(Workspace::from((focused, w)))
let vis = Visibility::from((&w, active.map(|w| w.name.as_ref()), &|w| {
create_is_visible()(w)
}));
Some(Workspace::from((vis, w)))
} else {
None
}
@@ -214,7 +198,7 @@ impl EventClient {
/// Gets the active workspace from the server.
fn get_active_workspace() -> Result<Workspace> {
let w = HWorkspace::get_active().map(|w| Workspace::from((true, w)))?;
let w = HWorkspace::get_active().map(|w| Workspace::from((Visibility::focused(), w)))?;
Ok(w)
}
}
@@ -236,13 +220,16 @@ impl WorkspaceClient for EventClient {
{
let tx = self.workspace_tx.clone();
let active_name = HWorkspace::get_active()
.map(|active| active.name)
.unwrap_or_default();
let active_id = HWorkspace::get_active().ok().map(|active| active.name);
let is_visible = create_is_visible();
let workspaces = Workspaces::get()
.expect("Failed to get workspaces")
.map(|w| Workspace::from((w.name == active_name, w)))
.map(|w| {
let vis = Visibility::from((&w, active_id.as_deref(), &is_visible));
Workspace::from((vis, w))
})
.collect();
send!(tx, WorkspaceUpdate::Init(workspaces));
@@ -271,13 +258,39 @@ fn get_workspace_name(name: WorkspaceType) -> String {
}
}
impl From<(bool, hyprland::data::Workspace)> for Workspace {
fn from((focused, workspace): (bool, hyprland::data::Workspace)) -> Self {
/// Creates a function which determines if a workspace is visible.
///
/// This function makes a Hyprland call that allocates so it should be cached when possible,
/// but it is only valid so long as workspaces do not change so it should not be stored long term
fn create_is_visible() -> impl Fn(&HWorkspace) -> bool {
let monitors = hyprland::data::Monitors::get().map_or(Vec::new(), HyprDataVec::to_vec);
move |w| monitors.iter().any(|m| m.active_workspace.id == w.id)
}
impl From<(Visibility, HWorkspace)> for Workspace {
fn from((visibility, workspace): (Visibility, HWorkspace)) -> Self {
Self {
id: workspace.id.to_string(),
name: workspace.name,
monitor: workspace.monitor,
focused,
visibility,
}
}
}
impl<'a, 'f, F> From<(&'a HWorkspace, Option<&str>, F)> for Visibility
where
F: FnOnce(&'f HWorkspace) -> bool,
'a: 'f,
{
fn from((workspace, active_name, is_visible): (&'a HWorkspace, Option<&str>, F)) -> Self {
if Some(workspace.name.as_str()) == active_name {
Self::focused()
} else if is_visible(workspace) {
Self::visible()
} else {
Self::Hidden
}
}
}

View File

@@ -75,8 +75,38 @@ pub struct Workspace {
pub name: String,
/// Name of the monitor (output) the workspace is located on
pub monitor: String,
/// Whether the workspace is in focus
pub focused: bool,
/// How visible the workspace is
pub visibility: Visibility,
}
/// Indicates workspace visibility. Visible workspaces have a boolean flag to indicate if they are also focused.
/// Yes, this is the same signature as Option<bool>, but it's impl is a lot more suited for our case.
#[derive(Debug, Copy, Clone)]
pub enum Visibility {
Visible(bool),
Hidden,
}
impl Visibility {
pub fn visible() -> Self {
Self::Visible(false)
}
pub fn focused() -> Self {
Self::Visible(true)
}
pub fn is_visible(self) -> bool {
matches!(self, Self::Visible(_))
}
pub fn is_focused(self) -> bool {
if let Self::Visible(focused) = self {
focused
} else {
false
}
}
}
#[derive(Debug, Clone)]
@@ -86,13 +116,17 @@ pub enum WorkspaceUpdate {
Init(Vec<Workspace>),
Add(Workspace),
Remove(String),
Update(Workspace),
Move(Workspace),
/// Declares focus moved from the old workspace to the new.
Focus {
old: String,
new: String,
old: Option<Workspace>,
new: Workspace,
},
/// An update was triggered by the compositor but this was not mapped by Ironbar.
///
/// This is purely used for ergonomics within the compositor clients
/// and should be ignored by consumers.
Unknown,
}
pub trait WorkspaceClient {

View File

@@ -1,12 +1,11 @@
use super::{Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{await_sync, send};
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
use crate::{await_sync, send, spawn};
use async_once::AsyncOnce;
use color_eyre::Report;
use futures_util::StreamExt;
use lazy_static::lazy_static;
use std::sync::Arc;
use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent};
use tokio::spawn;
use tokio::sync::broadcast::{channel, Receiver, Sender};
use tokio::sync::Mutex;
use tracing::{info, trace};
@@ -32,8 +31,11 @@ impl SwayEventClient {
while let Some(event) = events.next().await {
trace!("event: {:?}", event);
if let Event::Workspace(ev) = event? {
workspace_tx.send(WorkspaceUpdate::from(*ev))?;
if let Event::Workspace(event) = event? {
let event = WorkspaceUpdate::from(*event);
if !matches!(event, WorkspaceUpdate::Unknown) {
workspace_tx.send(event)?;
}
};
}
@@ -105,22 +107,50 @@ pub fn get_sub_client() -> &'static SwayEventClient {
impl From<Node> for Workspace {
fn from(node: Node) -> Self {
let visibility = Visibility::from(&node);
Self {
id: node.id.to_string(),
name: node.name.unwrap_or_default(),
monitor: node.output.unwrap_or_default(),
focused: node.focused,
visibility,
}
}
}
impl From<swayipc_async::Workspace> for Workspace {
fn from(workspace: swayipc_async::Workspace) -> Self {
let visibility = Visibility::from(&workspace);
Self {
id: workspace.id.to_string(),
name: workspace.name,
monitor: workspace.output,
focused: workspace.focused,
visibility,
}
}
}
impl From<&Node> for Visibility {
fn from(node: &Node) -> Self {
if node.focused {
Self::focused()
} else if node.visible.unwrap_or(false) {
Self::visible()
} else {
Self::Hidden
}
}
}
impl From<&swayipc_async::Workspace> for Visibility {
fn from(workspace: &swayipc_async::Workspace) -> Self {
if workspace.focused {
Self::focused()
} else if workspace.visible {
Self::visible()
} else {
Self::Hidden
}
}
}
@@ -139,21 +169,13 @@ impl From<WorkspaceEvent> for WorkspaceUpdate {
.unwrap_or_default(),
),
WorkspaceChange::Focus => Self::Focus {
old: event
.old
.expect("Missing old workspace")
.name
.unwrap_or_default(),
new: event
.current
.expect("Missing current workspace")
.name
.unwrap_or_default(),
old: event.old.map(Workspace::from),
new: Workspace::from(event.current.expect("Missing current workspace")),
},
WorkspaceChange::Move => {
Self::Move(event.current.expect("Missing current workspace").into())
}
_ => Self::Update(event.current.expect("Missing current workspace").into()),
_ => Self::Unknown,
}
}
}

View File

@@ -1,7 +1,7 @@
use super::{
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
};
use crate::{await_sync, send};
use crate::{await_sync, send, spawn};
use color_eyre::Result;
use lazy_static::lazy_static;
use mpd_client::client::{Connection, ConnectionEvent, Subsystem};
@@ -17,7 +17,6 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use tokio::net::{TcpStream, UnixStream};
use tokio::spawn;
use tokio::sync::broadcast;
use tokio::sync::Mutex;
use tokio::time::sleep;

View File

@@ -1,6 +1,6 @@
use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL_MS};
use crate::clients::music::ProgressTick;
use crate::{arc_mut, lock, send};
use crate::{arc_mut, lock, send, spawn_blocking};
use color_eyre::Result;
use lazy_static::lazy_static;
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
@@ -10,7 +10,6 @@ use std::thread::sleep;
use std::time::Duration;
use std::{cmp, string};
use tokio::sync::broadcast;
use tokio::task::spawn_blocking;
use tracing::{debug, error, trace};
lazy_static! {
@@ -245,7 +244,7 @@ impl MusicClient for Client {
fn set_volume_percent(&self, vol: u8) -> Result<()> {
if let Some(player) = Self::get_player(self) {
player.set_volume(vol as f64 / 100.0)?;
player.set_volume(f64::from(vol) / 100.0)?;
} else {
error!("Could not find player");
}

View File

@@ -1,5 +1,4 @@
use crate::unique_id::get_unique_usize;
use crate::{arc_mut, lock, send};
use crate::{arc_mut, lock, send, spawn, Ironbar};
use async_once::AsyncOnce;
use color_eyre::Report;
use lazy_static::lazy_static;
@@ -9,7 +8,6 @@ use system_tray::message::menu::TrayMenu;
use system_tray::message::tray::StatusNotifierItem;
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
use system_tray::StatusNotifierWatcher;
use tokio::spawn;
use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error, trace};
@@ -25,7 +23,7 @@ pub struct TrayEventReceiver {
impl TrayEventReceiver {
async fn new() -> system_tray::error::Result<Self> {
let id = format!("ironbar-{}", get_unique_usize());
let id = format!("ironbar-{}", Ironbar::unique_id());
let (tx, rx) = mpsc::channel(16);
let (b_tx, b_rx) = broadcast::channel(16);

View File

@@ -3,22 +3,22 @@ use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
use super::wlr_foreign_toplevel::ToplevelEvent;
use super::Environment;
use crate::error::ERR_CHANNEL_RECV;
use crate::send;
use crate::{send, spawn_blocking};
use cfg_if::cfg_if;
use color_eyre::Report;
use smithay_client_toolkit::output::{OutputInfo, OutputState};
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
use smithay_client_toolkit::reexports::calloop::EventLoop;
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
use smithay_client_toolkit::registry::RegistryState;
use smithay_client_toolkit::seat::SeatState;
use std::collections::HashMap;
use std::sync::mpsc;
use tokio::sync::broadcast;
use tokio::task::spawn_blocking;
use tracing::{debug, error, trace};
use wayland_client::globals::registry_queue_init;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::{Connection, WaylandSource};
use wayland_client::Connection;
cfg_if! {
if #[cfg(feature = "clipboard")] {
@@ -106,8 +106,7 @@ impl WaylandClient {
let mut event_loop =
EventLoop::<Environment>::try_new().expect("Failed to create new event loop");
WaylandSource::new(queue)
.expect("Failed to create Wayland source from queue")
WaylandSource::new(conn, queue)
.insert(event_loop.handle())
.expect("Failed to insert Wayland event queue into event loop");

View File

@@ -7,14 +7,13 @@ use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
use self::source::DataControlSourceHandler;
use crate::clients::wayland::Environment;
use crate::unique_id::get_unique_usize;
use crate::{lock, send};
use crate::{lock, send, Ironbar};
use device::DataControlDevice;
use glib::Bytes;
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
use nix::sys::epoll::{epoll_create, epoll_ctl, epoll_wait, EpollEvent, EpollFlags, EpollOp};
use nix::sys::epoll::{Epoll, EpollCreateFlags, EpollEvent, EpollFlags};
use smithay_client_toolkit::data_device_manager::WritePipe;
use smithay_client_toolkit::reexports::calloop::RegistrationToken;
use smithay_client_toolkit::reexports::calloop::{PostAction, RegistrationToken};
use std::cmp::min;
use std::fmt::{Debug, Formatter};
use std::fs::File;
@@ -145,7 +144,7 @@ impl Environment {
};
Ok(ClipboardItem {
id: get_unique_usize(),
id: Ironbar::unique_id(),
value,
mime_type: mime_type.value.clone(),
})
@@ -196,29 +195,31 @@ impl DataControlDeviceHandler for Environment {
let tx = self.clipboard_tx.clone();
let clipboard = self.clipboard.clone();
let token = self
.loop_handle
.insert_source(read_pipe, move |_, file, state| {
let item = state
.selection_offers
.iter()
.position(|o| o.offer == offer_clone)
.map(|p| state.selection_offers.remove(p))
.expect("Failed to find selection offer item");
let token =
self.loop_handle
.insert_source(read_pipe, move |(), file, state| unsafe {
let item = state
.selection_offers
.iter()
.position(|o| o.offer == offer_clone)
.map(|p| state.selection_offers.remove(p))
.expect("Failed to find selection offer item");
match Self::read_file(&mime_type, file) {
Ok(item) => {
let item = Arc::new(item);
lock!(clipboard).replace(item.clone());
send!(tx, item);
match Self::read_file(&mime_type, file.get_mut()) {
Ok(item) => {
let item = Arc::new(item);
lock!(clipboard).replace(item.clone());
send!(tx, item);
}
Err(err) => error!("{err:?}"),
}
Err(err) => error!("{err:?}"),
}
state
.loop_handle
.remove(item.token.expect("Missing item token"));
});
state
.loop_handle
.remove(item.token.expect("Missing item token"));
PostAction::Remove
});
match token {
Ok(token) => {
@@ -289,23 +290,22 @@ impl DataControlSourceHandler for Environment {
trace!("Num bytes: {}", bytes.len());
let mut events = (0..16).map(|_| EpollEvent::empty()).collect::<Vec<_>>();
let mut epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0);
let epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0);
let epoll_fd = epoll_create().expect("to get valid file descriptor");
epoll_ctl(
epoll_fd,
EpollOp::EpollCtlAdd,
fd.as_raw_fd(),
&mut epoll_event,
)
.expect("to send valid epoll operation");
let epoll_fd =
Epoll::new(EpollCreateFlags::empty()).expect("to get valid file descriptor");
epoll_fd
.add(fd, epoll_event)
.expect("to send valid epoll operation");
while !bytes.is_empty() {
let chunk = &bytes[..min(pipe_size as usize, bytes.len())];
trace!("Writing {} bytes ({} remain)", chunk.len(), bytes.len());
epoll_wait(epoll_fd, &mut events, 100).expect("Failed to wait to epoll");
epoll_fd
.wait(&mut events, 100)
.expect("Failed to wait to epoll");
match file.write(chunk) {
Ok(_) => bytes = &bytes[chunk.len()..],

View File

@@ -5,7 +5,7 @@ use nix::unistd::{close, pipe2};
use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError;
use smithay_client_toolkit::data_device_manager::ReadPipe;
use std::ops::DerefMut;
use std::os::fd::FromRawFd;
use std::os::fd::{BorrowedFd, FromRawFd};
use std::sync::{Arc, Mutex};
use tracing::{trace, warn};
use wayland_client::{Connection, Dispatch, Proxy, QueueHandle};
@@ -37,7 +37,7 @@ impl PartialEq for SelectionOffer {
impl SelectionOffer {
pub fn receive(&self, mime_type: String) -> Result<ReadPipe, DataOfferError> {
receive(&self.data_offer, mime_type).map_err(DataOfferError::Io)
unsafe { receive(&self.data_offer, mime_type) }.map_err(DataOfferError::Io)
}
}
@@ -169,15 +169,18 @@ where
///
/// Fails if too many file descriptors were already open and a pipe
/// could not be created.
pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result<ReadPipe> {
pub unsafe fn receive(
offer: &ZwlrDataControlOfferV1,
mime_type: String,
) -> std::io::Result<ReadPipe> {
// create a pipe
let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?;
offer.receive(mime_type, writefd);
offer.receive(mime_type, BorrowedFd::borrow_raw(writefd));
if let Err(err) = close(writefd) {
warn!("Failed to close write pipe: {}", err);
}
Ok(unsafe { FromRawFd::from_raw_fd(readfd) })
Ok(FromRawFd::from_raw_fd(readfd))
}

View File

@@ -1,6 +1,5 @@
use super::manager::ToplevelManagerState;
use crate::lock;
use crate::unique_id::get_unique_usize;
use crate::{lock, Ironbar};
use std::collections::HashSet;
use std::sync::{Arc, Mutex};
use tracing::trace;
@@ -68,7 +67,7 @@ pub struct ToplevelInfo {
impl Default for ToplevelInfo {
fn default() -> Self {
Self {
id: get_unique_usize(),
id: Ironbar::unique_id(),
app_id: String::new(),
title: String::new(),
fullscreen: false,

View File

@@ -1,5 +1,6 @@
use crate::dynamic_value::{dynamic_string, DynamicBool};
use crate::script::{Script, ScriptInput};
use glib::Propagation;
use gtk::gdk::ScrollDirection;
use gtk::prelude::*;
use gtk::{EventBox, Orientation, Revealer, RevealerTransitionType};
@@ -75,7 +76,7 @@ impl CommonConfig {
script.run_as_oneshot(None);
}
Inhibit(false)
Propagation::Proceed
});
let scroll_up_script = self.on_scroll_up.map(Script::new_polling);
@@ -93,7 +94,7 @@ impl CommonConfig {
script.run_as_oneshot(None);
}
Inhibit(false)
Propagation::Proceed
});
macro_rules! install_oneshot {
@@ -101,7 +102,7 @@ impl CommonConfig {
$option.map(Script::new_polling).map(|script| {
container.$method(move |_, _| {
script.run_as_oneshot(None);
Inhibit(false)
Propagation::Proceed
});
})
};
@@ -114,7 +115,6 @@ impl CommonConfig {
let container = container.clone();
dynamic_string(&tooltip, move |string| {
container.set_tooltip_text(Some(&string));
Continue(true)
});
}
}
@@ -136,7 +136,6 @@ impl CommonConfig {
container.show_all();
}
revealer.set_reveal_child(success);
Continue(true)
});
}

View File

@@ -26,7 +26,7 @@ use serde::Deserialize;
use std::collections::HashMap;
pub use self::common::{CommonConfig, TransitionType};
pub use self::truncate::{EllipsizeMode, TruncateMode};
pub use self::truncate::TruncateMode;
#[derive(Debug, Deserialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
@@ -99,6 +99,11 @@ pub struct Config {
pub popup_gap: i32,
pub name: Option<String>,
#[serde(default)]
pub start_hidden: Option<bool>,
#[serde(default)]
pub autohide: Option<u64>,
/// GTK icon theme to use.
pub icon_theme: Option<String>,
@@ -127,6 +132,8 @@ impl Default for Config {
height: default_bar_height(),
margin: MarginConfig::default(),
name: None,
start_hidden: None,
autohide: None,
popup_gap: default_popup_gap(),
icon_theme: None,
ironvar_defaults: None,

View File

@@ -109,7 +109,9 @@ fn find_desktop_file_by_filedata(app_id: &str, files: &[PathBuf]) -> Option<Path
let files = files
.iter()
.filter_map(|file| {
let Some(parsed_desktop_file) = parse_desktop_file(file) else { return None };
let Some(parsed_desktop_file) = parse_desktop_file(file) else {
return None;
};
desktop_files_cache.insert(file.clone(), parsed_desktop_file.clone());
Some((file.clone(), parsed_desktop_file))
@@ -162,7 +164,9 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopFile> {
file.lines()
.filter_map(|line| {
let Some((key, value)) = line.split_once('=') else { return None };
let Some((key, value)) = line.split_once('=') else {
return None;
};
let key = key.trim();
let value = value.trim();
@@ -176,7 +180,7 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopFile> {
.for_each(|(key, value)| {
desktop_file
.entry(key.to_string())
.or_insert_with(Vec::new)
.or_default()
.push(value.to_string());
});
@@ -185,7 +189,9 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopFile> {
/// Attempts to get the icon name from the app's `.desktop` file.
pub fn get_desktop_icon_name(app_id: &str) -> Option<String> {
let Some(path) = find_desktop_file(app_id) else { return None };
let Some(path) = find_desktop_file(app_id) else {
return None;
};
let mut desktop_files_cache = lock!(DESKTOP_FILES);

View File

@@ -1,11 +1,10 @@
#[cfg(feature = "ipc")]
use crate::ironvar::get_variable_manager;
use crate::script::Script;
use crate::send;
use crate::{glib_recv_mpsc, spawn, try_send};
#[cfg(feature = "ipc")]
use crate::{send_async, Ironbar};
use cfg_if::cfg_if;
use glib::Continue;
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc;
#[derive(Debug, Deserialize, Clone)]
#[serde(untagged)]
@@ -18,9 +17,9 @@ pub enum DynamicBool {
}
impl DynamicBool {
pub fn subscribe<F>(self, f: F)
pub fn subscribe<F>(self, mut f: F)
where
F: FnMut(bool) -> Continue + 'static,
F: FnMut(bool) + 'static,
{
let value = match self {
Self::Unknown(input) => {
@@ -40,29 +39,29 @@ impl DynamicBool {
_ => self,
};
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let (tx, rx) = mpsc::channel(32);
rx.attach(None, f);
glib_recv_mpsc!(rx, val => f(val));
spawn(async move {
match value {
DynamicBool::Script(script) => {
script
.run(None, |_, success| {
send!(tx, success);
try_send!(tx, success);
})
.await;
}
#[cfg(feature = "ipc")]
DynamicBool::Variable(variable) => {
let variable_manager = get_variable_manager();
let variable_manager = Ironbar::variable_manager();
let variable_name = variable[1..].into(); // remove hash
let mut rx = crate::write_lock!(variable_manager).subscribe(variable_name);
while let Ok(value) = rx.recv().await {
let has_value = value.map(|s| is_truthy(&s)).unwrap_or_default();
send!(tx, has_value);
send_async!(tx, has_value);
}
}
DynamicBool::Unknown(_) => unreachable!(),
@@ -71,7 +70,10 @@ impl DynamicBool {
}
}
/// Check if a string ironvar is 'truthy'
/// Check if a string ironvar is 'truthy',
/// i.e should be evaluated to true.
///
/// This loosely follows the common JavaScript cases.
#[cfg(feature = "ipc")]
fn is_truthy(string: &str) -> bool {
!(string.is_empty() || string == "0" || string == "false")

View File

@@ -1,9 +1,8 @@
#[cfg(feature = "ipc")]
use crate::ironvar::get_variable_manager;
use crate::script::{OutputStream, Script};
use crate::{arc_mut, lock, send};
use gtk::prelude::*;
use tokio::spawn;
#[cfg(feature = "ipc")]
use crate::Ironbar;
use crate::{arc_mut, glib_recv_mpsc, lock, spawn, try_send};
use tokio::sync::mpsc;
/// A segment of a dynamic string,
/// containing either a static string
@@ -24,17 +23,16 @@ enum DynamicStringSegment {
/// ```rs
/// dynamic_string(&text, move |string| {
/// label.set_markup(&string);
/// Continue(true)
/// });
/// ```
pub fn dynamic_string<F>(input: &str, f: F)
pub fn dynamic_string<F>(input: &str, mut f: F)
where
F: FnMut(String) -> Continue + 'static,
F: FnMut(String) + 'static,
{
let tokens = parse_input(input);
let label_parts = arc_mut!(vec![]);
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let (tx, rx) = mpsc::channel(32);
for (i, segment) in tokens.into_iter().enumerate() {
match segment {
@@ -57,7 +55,7 @@ where
let _: String = std::mem::replace(&mut label_parts[i], out);
let string = label_parts.join("");
send!(tx, string);
try_send!(tx, string);
}
})
.await;
@@ -72,7 +70,7 @@ where
lock!(label_parts).push(String::new());
spawn(async move {
let variable_manager = get_variable_manager();
let variable_manager = Ironbar::variable_manager();
let mut rx = crate::write_lock!(variable_manager).subscribe(name);
while let Ok(value) = rx.recv().await {
@@ -82,7 +80,7 @@ where
let _: String = std::mem::replace(&mut label_parts[i], value);
let string = label_parts.join("");
send!(tx, string);
try_send!(tx, string);
}
}
});
@@ -90,12 +88,12 @@ where
}
}
rx.attach(None, f);
glib_recv_mpsc!(rx , val => f(val));
// initialize
{
let label_parts = lock!(label_parts).join("");
send!(tx, label_parts);
try_send!(tx, label_parts);
}
}

View File

@@ -1,43 +0,0 @@
use crate::popup::Popup;
use crate::write_lock;
use std::collections::HashMap;
use std::sync::{Arc, RwLock, RwLockWriteGuard};
/// Global application state shared across all bars.
///
/// Data that needs to be accessed from anywhere
/// that is not otherwise accessible should be placed on here.
#[derive(Debug)]
pub struct GlobalState {
popups: HashMap<Box<str>, Arc<RwLock<Popup>>>,
}
impl GlobalState {
pub(crate) fn new() -> Self {
Self {
popups: HashMap::new(),
}
}
pub fn popups(&self) -> &HashMap<Box<str>, Arc<RwLock<Popup>>> {
&self.popups
}
pub fn popups_mut(&mut self) -> &mut HashMap<Box<str>, Arc<RwLock<Popup>>> {
&mut self.popups
}
pub fn with_popup_mut<F, T>(&self, monitor_name: &str, f: F) -> Option<T>
where
F: FnOnce(RwLockWriteGuard<Popup>) -> T,
{
let popup = self.popups().get(monitor_name);
if let Some(popup) = popup {
let popup = write_lock!(popup);
Some(f(popup))
} else {
None
}
}
}

View File

@@ -1,4 +1,6 @@
use crate::desktop_file::get_desktop_icon_name;
#[cfg(feature = "http")]
use crate::{glib_recv_mpsc, send_async, spawn};
use cfg_if::cfg_if;
use color_eyre::{Help, Report, Result};
use gtk::cairo::Surface;
@@ -7,13 +9,13 @@ use gtk::gdk_pixbuf::Pixbuf;
use gtk::prelude::*;
use gtk::{IconLookupFlags, IconTheme};
use std::path::{Path, PathBuf};
#[cfg(feature = "http")]
use tokio::sync::mpsc;
use tracing::warn;
cfg_if!(
if #[cfg(feature = "http")] {
use crate::send;
use gtk::gio::{Cancellable, MemoryInputStream};
use tokio::spawn;
use tracing::error;
}
);
@@ -143,18 +145,18 @@ impl<'a> ImageProvider<'a> {
#[cfg(feature = "http")]
if let ImageLocation::Remote(url) = &self.location {
let url = url.clone();
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let (tx, rx) = mpsc::channel(64);
spawn(async move {
let bytes = Self::get_bytes_from_http(url).await;
if let Ok(bytes) = bytes {
send!(tx, bytes);
send_async!(tx, bytes);
}
});
{
let size = self.size;
rx.attach(None, move |bytes| {
glib_recv_mpsc!(rx, bytes => {
let stream = MemoryInputStream::from_bytes(&bytes);
let scale = image.scale_factor();
@@ -175,8 +177,6 @@ impl<'a> ImageProvider<'a> {
Err(err) => error!("{err:?}"),
_ => {}
}
Continue(false)
});
}
} else {

View File

@@ -3,25 +3,21 @@ pub mod commands;
pub mod responses;
mod server;
use std::cell::RefCell;
use std::path::{Path, PathBuf};
use std::rc::Rc;
use tracing::warn;
use crate::GlobalState;
pub use commands::Command;
pub use responses::Response;
#[derive(Debug)]
pub struct Ipc {
path: PathBuf,
global_state: Rc<RefCell<GlobalState>>,
}
impl Ipc {
/// Creates a new IPC instance.
/// This can be used as both a server and client.
pub fn new(global_state: Rc<RefCell<GlobalState>>) -> Self {
pub fn new() -> Self {
let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR")
.map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from)
.join("ironbar-ipc.sock");
@@ -32,7 +28,6 @@ impl Ipc {
Self {
path: ipc_socket_file,
global_state,
}
}

View File

@@ -1,24 +1,19 @@
use std::cell::RefCell;
use std::fs;
use std::path::Path;
use std::rc::Rc;
use color_eyre::{Report, Result};
use glib::Continue;
use gtk::prelude::*;
use gtk::Application;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{UnixListener, UnixStream};
use tokio::spawn;
use tokio::sync::mpsc::{self, Receiver, Sender};
use tracing::{debug, error, info, warn};
use crate::bridge_channel::BridgeChannel;
use crate::ipc::{Command, Response};
use crate::ironvar::get_variable_manager;
use crate::modules::PopupButton;
use crate::style::load_css;
use crate::{read_lock, send_async, try_send, write_lock, GlobalState};
use crate::{glib_recv_mpsc, read_lock, send_async, spawn, try_send, write_lock, Ironbar};
use super::Ipc;
@@ -26,9 +21,8 @@ impl Ipc {
/// Starts the IPC server on its socket.
///
/// Once started, the server will begin accepting connections.
pub fn start(&self, application: &Application) {
let bridge = BridgeChannel::<Command>::new();
let cmd_tx = bridge.create_sender();
pub fn start(&self, application: &Application, ironbar: Rc<Ironbar>) {
let (cmd_tx, cmd_rx) = mpsc::channel(32);
let (res_tx, mut res_rx) = mpsc::channel(32);
let path = self.path.clone();
@@ -70,11 +64,9 @@ impl Ipc {
});
let application = application.clone();
let global_state = self.global_state.clone();
bridge.recv(move |command| {
let res = Self::handle_command(command, &application, &global_state);
glib_recv_mpsc!(cmd_rx, command => {
let res = Self::handle_command(command, &application, &ironbar);
try_send!(res_tx, res);
Continue(true)
});
}
@@ -112,11 +104,7 @@ impl Ipc {
/// Takes an input command, runs it and returns with the appropriate response.
///
/// This runs on the main thread, allowing commands to interact with GTK.
fn handle_command(
command: Command,
application: &Application,
global_state: &Rc<RefCell<GlobalState>>,
) -> Response {
fn handle_command(command: Command, application: &Application, ironbar: &Ironbar) -> Response {
match command {
Command::Inspect => {
gtk::Window::set_interactive_debugging(true);
@@ -129,20 +117,20 @@ impl Ipc {
window.close();
}
crate::load_interface(application, global_state);
*ironbar.bars.borrow_mut() = crate::load_interface(application);
Response::Ok
}
Command::Set { key, value } => {
let variable_manager = get_variable_manager();
let variable_manager = Ironbar::variable_manager();
let mut variable_manager = write_lock!(variable_manager);
match variable_manager.set(key, value) {
Ok(_) => Response::Ok,
Ok(()) => Response::Ok,
Err(err) => Response::error(&format!("{err}")),
}
}
Command::Get { key } => {
let variable_manager = get_variable_manager();
let variable_manager = Ironbar::variable_manager();
let value = read_lock!(variable_manager).get(&key);
match value {
Some(value) => Response::OkValue { value },
@@ -158,68 +146,85 @@ impl Ipc {
}
}
Command::TogglePopup { bar_name, name } => {
let global_state = global_state.borrow();
let response = global_state.with_popup_mut(&bar_name, |mut popup| {
let current_widget = popup.current_widget();
popup.hide();
let bar = ironbar.bar_by_name(&bar_name);
let data = popup
.cache
.iter()
.find(|(_, (module_name, _))| module_name == &name)
.map(|module| (module, module.1 .1.buttons.first()));
match bar {
Some(bar) => {
let popup = bar.popup();
let current_widget = popup.borrow().current_widget();
match data {
Some(((&id, _), Some(button))) if current_widget != Some(id) => {
let button_id = button.popup_id();
popup.show(id, button_id);
popup.borrow_mut().hide();
Response::Ok
let data = popup
.borrow()
.cache
.iter()
.find(|(_, value)| value.name == name)
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
match data {
Some((id, Some(button))) if current_widget != Some(id) => {
let button_id = button.popup_id();
let mut popup = popup.borrow_mut();
if popup.is_visible() {
popup.hide();
} else {
popup.show(id, button_id);
}
Response::Ok
}
Some((_, None)) => Response::error("Module has no popup functionality"),
Some(_) => Response::Ok,
None => Response::error("Invalid module name"),
}
Some((_, None)) => Response::error("Module has no popup functionality"),
Some(_) => Response::Ok,
None => Response::error("Invalid module name"),
}
});
response.unwrap_or_else(|| Response::error("Invalid monitor name"))
None => Response::error("Invalid bar name"),
}
}
Command::OpenPopup { bar_name, name } => {
let global_state = global_state.borrow();
let response = global_state.with_popup_mut(&bar_name, |mut popup| {
// only one popup per bar, so hide if open for another widget
popup.hide();
let bar = ironbar.bar_by_name(&bar_name);
let data = popup
.cache
.iter()
.find(|(_, (module_name, _))| module_name == &name)
.map(|module| (module, module.1 .1.buttons.first()));
match bar {
Some(bar) => {
let popup = bar.popup();
match data {
Some(((&id, _), Some(button))) => {
let button_id = button.popup_id();
popup.show(id, button_id);
// only one popup per bar, so hide if open for another widget
popup.borrow_mut().hide();
Response::Ok
let data = popup
.borrow()
.cache
.iter()
.find(|(_, value)| value.name == name)
.map(|(id, value)| (*id, value.content.buttons.first().cloned()));
match data {
Some((id, Some(button))) => {
let button_id = button.popup_id();
popup.borrow_mut().show(id, button_id);
Response::Ok
}
Some((_, None)) => Response::error("Module has no popup functionality"),
None => Response::error("Invalid module name"),
}
Some((_, None)) => Response::error("Module has no popup functionality"),
None => Response::error("Invalid module name"),
}
});
response.unwrap_or_else(|| Response::error("Invalid monitor name"))
None => Response::error("Invalid bar name"),
}
}
Command::ClosePopup { bar_name } => {
let global_state = global_state.borrow();
let popup_found = global_state
.with_popup_mut(&bar_name, |mut popup| popup.hide())
.is_some();
let bar = ironbar.bar_by_name(&bar_name);
if popup_found {
Response::Ok
} else {
Response::error("Invalid monitor name")
match bar {
Some(bar) => {
let popup = bar.popup();
popup.borrow_mut().hide();
Response::Ok
}
None => Response::error("Invalid bar name"),
}
}
Command::Ping => Response::Ok,

View File

@@ -1,25 +1,21 @@
#![doc = include_str!("../docs/Ironvars.md")]
use crate::{arc_rw, send};
use crate::send;
use color_eyre::{Report, Result};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use tokio::sync::broadcast;
lazy_static! {
static ref VARIABLE_MANAGER: Arc<RwLock<VariableManager>> = arc_rw!(VariableManager::new());
}
pub fn get_variable_manager() -> Arc<RwLock<VariableManager>> {
VARIABLE_MANAGER.clone()
}
/// Global singleton manager for `IronVar` variables.
pub struct VariableManager {
variables: HashMap<Box<str>, IronVar>,
}
impl Default for VariableManager {
fn default() -> Self {
Self::new()
}
}
impl VariableManager {
pub fn new() -> Self {
Self {

View File

@@ -43,6 +43,59 @@ macro_rules! try_send {
};
}
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
/// in a loop.
///
/// This allows use of `GObjects` and futures in the same context.
///
/// For use with receivers which return a `Result`.
///
/// # Example
///
/// ```rs
/// let (tx, mut rx) = broadcast::channel(32);
/// glib_recv(rx, msg => println!("{msg}"));
/// ```
#[macro_export]
macro_rules! glib_recv {
($rx:expr, $val:ident => $expr:expr) => {{
glib::spawn_future_local(async move {
// re-delcare in case ie `context.subscribe()` is passed directly
let mut rx = $rx;
while let Ok($val) = rx.recv().await {
$expr
}
});
}};
}
/// Spawns a `GLib` future on the local thread, and calls `rx.recv()`
/// in a loop.
///
/// This allows use of `GObjects` and futures in the same context.
///
/// For use with receivers which return an `Option`,
/// such as Tokio's `mpsc` channel.
///
/// # Example
///
/// ```rs
/// let (tx, mut rx) = broadcast::channel(32);
/// glib_recv_mpsc(rx, msg => println!("{msg}"));
/// ```
#[macro_export]
macro_rules! glib_recv_mpsc {
($rx:expr, $val:ident => $expr:expr) => {{
glib::spawn_future_local(async move {
// re-delcare in case ie `context.subscribe()` is passed directly
let mut rx = $rx;
while let Some($val) = rx.recv().await {
$expr
}
});
}};
}
/// Locks a `Mutex`.
/// Panics if the `Mutex` cannot be locked.
///

View File

@@ -1,12 +1,15 @@
#![doc = include_str!("../README.md")]
use std::cell::{Cell, RefCell};
use std::cell::RefCell;
use std::env;
use std::future::Future;
use std::path::PathBuf;
use std::process::exit;
use std::rc::Rc;
use std::sync::mpsc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
#[cfg(feature = "ipc")]
use std::sync::RwLock;
use std::sync::{mpsc, Arc};
use cfg_if::cfg_if;
#[cfg(feature = "cli")]
@@ -14,24 +17,25 @@ use clap::Parser;
use color_eyre::eyre::Result;
use color_eyre::Report;
use dirs::config_dir;
use glib::PropertySet;
use gtk::gdk::Display;
use gtk::prelude::*;
use gtk::Application;
use tokio::runtime::Handle;
use tokio::task::{block_in_place, spawn_blocking};
use tokio::runtime::Runtime;
use tokio::task::{block_in_place, JoinHandle};
use tracing::{debug, error, info, warn};
use universal_config::ConfigLoader;
use clients::wayland;
use crate::bar::create_bar;
use crate::bar::{create_bar, Bar};
use crate::config::{Config, MonitorConfig};
use crate::error::ExitCode;
use crate::global_state::GlobalState;
#[cfg(feature = "ipc")]
use crate::ironvar::VariableManager;
use crate::style::load_css;
mod bar;
mod bridge_channel;
#[cfg(feature = "cli")]
mod cli;
mod clients;
@@ -39,7 +43,6 @@ mod config;
mod desktop_file;
mod dynamic_value;
mod error;
mod global_state;
mod gtk_helpers;
mod image;
#[cfg(feature = "ipc")]
@@ -52,112 +55,180 @@ mod modules;
mod popup;
mod script;
mod style;
mod unique_id;
const GTK_APP_ID: &str = "dev.jstanger.ironbar";
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[tokio::main]
async fn main() {
fn main() {
let _guard = logging::install_logging();
let global_state = Rc::new(RefCell::new(GlobalState::new()));
cfg_if! {
if #[cfg(feature = "cli")] {
run_with_args(global_state).await;
run_with_args();
} else {
start_ironbar(global_state);
start_ironbar();
}
}
}
#[cfg(feature = "cli")]
async fn run_with_args(global_state: Rc<RefCell<GlobalState>>) {
fn run_with_args() {
let args = cli::Args::parse();
match args.command {
Some(command) => {
let ipc = ipc::Ipc::new(global_state);
match ipc.send(command).await {
Ok(res) => cli::handle_response(res),
Err(err) => error!("{err:?}"),
};
let rt = create_runtime();
rt.block_on(async move {
let ipc = ipc::Ipc::new();
match ipc.send(command).await {
Ok(res) => cli::handle_response(res),
Err(err) => error!("{err:?}"),
};
});
}
None => start_ironbar(global_state),
None => start_ironbar(),
}
}
fn start_ironbar(global_state: Rc<RefCell<GlobalState>>) {
info!("Ironbar version {}", VERSION);
info!("Starting application");
static COUNTER: AtomicUsize = AtomicUsize::new(1);
let app = Application::builder().application_id(GTK_APP_ID).build();
let _ = wayland::get_client(); // force-init
lazy_static::lazy_static! {
static ref RUNTIME: Arc<Runtime> = Arc::new(create_runtime());
}
let running = Rc::new(Cell::new(false));
#[cfg(feature = "ipc")]
lazy_static::lazy_static! {
static ref VARIABLE_MANAGER: Arc<RwLock<VariableManager>> = arc_rw!(VariableManager::new());
}
app.connect_activate(move |app| {
if running.get() {
info!("Ironbar already running, returning");
return;
#[derive(Debug)]
pub struct Ironbar {
bars: Rc<RefCell<Vec<Bar>>>,
}
impl Ironbar {
fn new() -> Self {
Self {
bars: Rc::new(RefCell::new(vec![])),
}
}
running.set(true);
fn start(self) {
info!("Ironbar version {}", VERSION);
info!("Starting application");
cfg_if! {
if #[cfg(feature = "ipc")] {
let ipc = ipc::Ipc::new(global_state.clone());
ipc.start(app);
let app = Application::builder().application_id(GTK_APP_ID).build();
let running = AtomicBool::new(false);
let instance = Rc::new(self);
// force start wayland client ahead of ui
let wl = wayland::get_client();
lock!(wl).roundtrip();
app.connect_activate(move |app| {
if running.load(Ordering::Relaxed) {
info!("Ironbar already running, returning");
return;
}
}
load_interface(app, &global_state);
running.set(true);
let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|| {
config_dir().map_or_else(
|| {
let report = Report::msg("Failed to locate user config dir");
error!("{:?}", report);
exit(ExitCode::CreateBars as i32);
},
|dir| dir.join("ironbar").join("style.css"),
)
},
PathBuf::from,
);
cfg_if! {
if #[cfg(feature = "ipc")] {
let ipc = ipc::Ipc::new();
ipc.start(app, instance.clone());
}
}
if style_path.exists() {
load_css(style_path);
}
*instance.bars.borrow_mut() = load_interface(app);
let (tx, rx) = mpsc::channel();
let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|| {
config_dir().map_or_else(
|| {
let report = Report::msg("Failed to locate user config dir");
error!("{:?}", report);
exit(ExitCode::CreateBars as i32);
},
|dir| dir.join("ironbar").join("style.css"),
)
},
PathBuf::from,
);
#[cfg(feature = "ipc")]
let ipc_path = ipc.path().to_path_buf();
spawn_blocking(move || {
rx.recv().expect("to receive from channel");
if style_path.exists() {
load_css(style_path);
}
info!("Shutting down");
let (tx, rx) = mpsc::channel();
#[cfg(feature = "ipc")]
ipc::Ipc::shutdown(ipc_path);
let ipc_path = ipc.path().to_path_buf();
spawn_blocking(move || {
rx.recv().expect("to receive from channel");
exit(0);
info!("Shutting down");
#[cfg(feature = "ipc")]
ipc::Ipc::shutdown(ipc_path);
exit(0);
});
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
.expect("Error setting Ctrl-C handler");
// TODO: Start wayland client - listen for outputs
// All bar loading should happen as an event response to this
});
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
.expect("Error setting Ctrl-C handler");
});
// Ignore CLI args
// Some are provided by swaybar_config but not currently supported
app.run_with_args(&Vec::<&str>::new());
}
// Ignore CLI args
// Some are provided by swaybar_config but not currently supported
app.run_with_args(&Vec::<&str>::new());
/// Gets the current Tokio runtime.
#[must_use]
pub fn runtime() -> Arc<Runtime> {
RUNTIME.clone()
}
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
/// This is just a static `AtomicUsize` that increments every time this function is called.
pub fn unique_id() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}
/// Gets the `Ironvar` manager singleton.
#[cfg(feature = "ipc")]
#[must_use]
pub fn variable_manager() -> Arc<RwLock<VariableManager>> {
VARIABLE_MANAGER.clone()
}
/// Gets a clone of a bar by its unique name.
///
/// Since the bar contains mostly GTK objects,
/// the clone is cheap enough to not worry about.
#[must_use]
pub fn bar_by_name(&self, name: &str) -> Option<Bar> {
self.bars
.borrow()
.iter()
.find(|&bar| bar.name() == name)
.cloned()
}
}
fn start_ironbar() {
let ironbar = Ironbar::new();
ironbar.start();
}
/// Loads the Ironbar config and interface.
pub fn load_interface(app: &Application, global_state: &Rc<RefCell<GlobalState>>) {
pub fn load_interface(app: &Application) -> Vec<Bar> {
let display = Display::default().map_or_else(
|| {
let report = Report::msg("Failed to get default GTK display");
@@ -185,7 +256,7 @@ pub fn load_interface(app: &Application, global_state: &Rc<RefCell<GlobalState>>
#[cfg(feature = "ipc")]
if let Some(ironvars) = config.ironvar_defaults.take() {
let variable_manager = ironvar::get_variable_manager();
let variable_manager = Ironbar::variable_manager();
for (k, v) in ironvars {
if write_lock!(variable_manager).set(k.clone(), v).is_err() {
warn!("Ignoring invalid ironvar: '{k}'");
@@ -193,21 +264,20 @@ pub fn load_interface(app: &Application, global_state: &Rc<RefCell<GlobalState>>
}
}
if let Err(err) = create_bars(app, &display, &config, global_state) {
error!("{:?}", err);
exit(ExitCode::CreateBars as i32);
match create_bars(app, &display, &config) {
Ok(bars) => {
debug!("Created {} bars", bars.len());
bars
}
Err(err) => {
error!("{:?}", err);
exit(ExitCode::CreateBars as i32);
}
}
debug!("Created bars");
}
/// Creates each of the bars across each of the (configured) outputs.
fn create_bars(
app: &Application,
display: &Display,
config: &Config,
global_state: &Rc<RefCell<GlobalState>>,
) -> Result<()> {
fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<Vec<Bar>> {
let wl = wayland::get_client();
let outputs = lock!(wl).get_outputs();
@@ -216,6 +286,10 @@ fn create_bars(
let num_monitors = display.n_monitors();
let show_default_bar =
config.start.is_some() || config.center.is_some() || config.end.is_some();
let mut all_bars = vec![];
for i in 0..num_monitors {
let monitor = display
.monitor(i)
@@ -224,35 +298,65 @@ fn create_bars(
.get(i as usize)
.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?;
let Some(monitor_name) = &output.name else { continue };
let Some(monitor_name) = &output.name else {
continue;
};
config.monitors.as_ref().map_or_else(
|| {
info!("Creating bar on '{}'", monitor_name);
create_bar(app, &monitor, monitor_name, config.clone(), global_state)
},
|config| {
let config = config.get(monitor_name);
match &config {
Some(MonitorConfig::Single(config)) => {
info!("Creating bar on '{}'", monitor_name);
create_bar(app, &monitor, monitor_name, config.clone(), global_state)
}
Some(MonitorConfig::Multiple(configs)) => {
for config in configs {
info!("Creating bar on '{}'", monitor_name);
create_bar(app, &monitor, monitor_name, config.clone(), global_state)?;
}
let mut bars = match config
.monitors
.as_ref()
.and_then(|config| config.get(monitor_name))
{
Some(MonitorConfig::Single(config)) => {
vec![create_bar(
app,
&monitor,
monitor_name.to_string(),
config.clone(),
)?]
}
Some(MonitorConfig::Multiple(configs)) => configs
.iter()
.map(|config| create_bar(app, &monitor, monitor_name.to_string(), config.clone()))
.collect::<Result<_>>()?,
None if show_default_bar => vec![create_bar(
app,
&monitor,
monitor_name.to_string(),
config.clone(),
)?],
None => vec![],
};
Ok(())
}
_ => Ok(()),
}
},
)?;
all_bars.append(&mut bars);
}
Ok(())
Ok(all_bars)
}
fn create_runtime() -> Runtime {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("tokio to create a valid runtime")
}
/// Calls `spawn` on the Tokio runtime.
pub fn spawn<F>(f: F) -> JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
Ironbar::runtime().spawn(f)
}
/// Calls `spawn_blocking` on the Tokio runtime.
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R>
where
F: FnOnce() -> R + Send + 'static,
R: Send + 'static,
{
Ironbar::runtime().spawn_blocking(f)
}
/// Blocks on a `Future` until it resolves.
@@ -266,5 +370,5 @@ fn create_bars(
///
/// TODO: remove all instances of this once async trait funcs are stable
pub fn await_sync<F: Future>(f: F) -> F::Output {
block_in_place(|| Handle::current().block_on(f))
block_in_place(|| Ironbar::runtime().block_on(f))
}

View File

@@ -5,7 +5,8 @@ use crate::image::new_icon_button;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
};
use crate::try_send;
use crate::{glib_recv, spawn, try_send};
use glib::Propagation;
use gtk::gdk_pixbuf::Pixbuf;
use gtk::gio::{Cancellable, MemoryInputStream};
use gtk::prelude::*;
@@ -13,8 +14,7 @@ use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget};
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error};
#[derive(Debug, Deserialize, Clone)]
@@ -72,8 +72,8 @@ impl Module<Button> for ClipboardModule {
fn spawn_controller(
&self,
_info: &ModuleInfo,
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: Receiver<Self::ReceiveMessage>,
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> color_eyre::Result<()> {
let max_items = self.max_items;
@@ -129,19 +129,14 @@ impl Module<Button> for ClipboardModule {
let button = new_icon_button(&self.icon, info.icon_theme, self.icon_size);
button.style_context().add_class("btn");
let tx = context.tx.clone();
button.connect_clicked(move |button| {
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(button.popup_id())
);
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
});
// we need to bind to the receiver as the channel does not open
// until the popup is first opened.
context.widget_rx.attach(None, |_| Continue(true));
let rx = context.subscribe();
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup(context.controller_tx, rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup))
@@ -149,8 +144,8 @@ impl Module<Button> for ClipboardModule {
fn into_popup(
self,
tx: Sender<Self::ReceiveMessage>,
rx: glib::Receiver<Self::SendMessage>,
tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>,
_info: &ModuleInfo,
) -> Option<gtk::Box>
where
@@ -168,7 +163,7 @@ impl Module<Button> for ClipboardModule {
{
let hidden_option = hidden_option.clone();
rx.attach(None, move |event| {
glib_recv!(rx, event => {
match event {
ControllerEvent::Add(id, item) => {
debug!("Adding new value with ID {}", id);
@@ -234,7 +229,7 @@ impl Module<Button> for ClipboardModule {
try_send!(tx, UIEvent::Copy(id));
}
Inhibit(true)
Propagation::Stop
},
);
}
@@ -293,8 +288,6 @@ impl Module<Button> for ClipboardModule {
hidden_option.set_active(true);
}
}
Continue(true)
});
}

View File

@@ -2,12 +2,10 @@ use std::env;
use chrono::{DateTime, Local, Locale};
use color_eyre::Result;
use glib::Continue;
use gtk::prelude::*;
use gtk::{Align, Button, Calendar, Label, Orientation};
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc;
use tokio::sync::{broadcast, mpsc};
use tokio::time::sleep;
use crate::config::CommonConfig;
@@ -15,7 +13,7 @@ use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
};
use crate::{send_async, try_send};
use crate::{glib_recv, send_async, spawn, try_send};
#[derive(Debug, Deserialize, Clone)]
pub struct ClockModule {
@@ -104,24 +102,22 @@ impl Module<Button> for ClockModule {
label.set_angle(info.bar_position.get_angle());
button.add(&label);
let tx = context.tx.clone();
button.connect_clicked(move |button| {
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(button.popup_id())
);
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
});
let format = self.format.clone();
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
context.widget_rx.attach(None, move |date| {
let rx = context.subscribe();
glib_recv!(rx, date => {
let date_string = format!("{}", date.format_localized(&format, locale));
label.set_label(&date_string);
Continue(true)
});
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
.into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup))
@@ -130,7 +126,7 @@ impl Module<Button> for ClockModule {
fn into_popup(
self,
_tx: mpsc::Sender<Self::ReceiveMessage>,
rx: glib::Receiver<Self::SendMessage>,
rx: broadcast::Receiver<Self::SendMessage>,
_info: &ModuleInfo,
) -> Option<gtk::Box> {
let container = gtk::Box::new(Orientation::Vertical, 0);
@@ -147,10 +143,9 @@ impl Module<Button> for ClockModule {
let format = self.format_popup;
let locale = Locale::try_from(self.locale.as_str()).unwrap_or(Locale::POSIX);
rx.attach(None, move |date| {
glib_recv!(rx, date => {
let date_string = format!("{}", date.format_localized(&format, locale));
clock.set_label(&date_string);
Continue(true)
});
container.show_all();

View File

@@ -30,7 +30,6 @@ impl CustomWidget for ButtonWidget {
dynamic_string(&text, move |string| {
label.set_markup(&string);
Continue(true)
});
}

View File

@@ -34,8 +34,6 @@ impl CustomWidget for ImageWidget {
dynamic_string(&self.src, move |src| {
ImageProvider::parse(&src, &icon_theme, false, self.size)
.map(|image| image.load_into_image(gtk_image.clone()));
Continue(true)
});
}

View File

@@ -26,7 +26,6 @@ impl CustomWidget for LabelWidget {
let label = label.clone();
dynamic_string(&self.label, move |string| {
label.set_markup(&string);
Continue(true)
});
}

View File

@@ -16,15 +16,14 @@ use crate::modules::{
wrap_widget, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::script::Script;
use crate::send_async;
use crate::{send_async, spawn};
use color_eyre::{Report, Result};
use gtk::prelude::*;
use gtk::{Button, IconTheme, Orientation};
use serde::Deserialize;
use std::cell::RefCell;
use std::rc::Rc;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error};
#[derive(Debug, Deserialize, Clone)]
@@ -59,7 +58,7 @@ pub enum Widget {
#[derive(Clone)]
struct CustomWidgetContext<'a> {
tx: &'a Sender<ExecEvent>,
tx: &'a mpsc::Sender<ExecEvent>,
bar_orientation: Orientation,
icon_theme: &'a IconTheme,
popup_buttons: Rc<RefCell<Vec<Button>>>,
@@ -159,8 +158,8 @@ impl Module<gtk::Box> for CustomModule {
fn spawn_controller(
&self,
_info: &ModuleInfo,
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: Receiver<Self::ReceiveMessage>,
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> Result<()> {
spawn(async move {
while let Some(event) = rx.recv().await {
@@ -213,7 +212,7 @@ impl Module<gtk::Box> for CustomModule {
});
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
.into_popup_parts_owned(popup_buttons.take());
Ok(ModuleParts {
@@ -224,8 +223,8 @@ impl Module<gtk::Box> for CustomModule {
fn into_popup(
self,
tx: Sender<Self::ReceiveMessage>,
_rx: glib::Receiver<Self::SendMessage>,
tx: mpsc::Sender<Self::ReceiveMessage>,
_rx: broadcast::Receiver<Self::SendMessage>,
info: &ModuleInfo,
) -> Option<gtk::Box>
where

View File

@@ -1,13 +1,13 @@
use gtk::prelude::*;
use gtk::ProgressBar;
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc;
use tracing::error;
use crate::dynamic_value::dynamic_string;
use crate::modules::custom::set_length;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{build, send};
use crate::{build, glib_recv_mpsc, spawn, try_send};
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
@@ -47,13 +47,13 @@ impl CustomWidget for ProgressWidget {
let script = Script::from(value);
let progress = progress.clone();
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let (tx, rx) = mpsc::channel(128);
spawn(async move {
script
.run(None, move |stream, _success| match stream {
OutputStream::Stdout(out) => match out.parse::<f64>() {
Ok(value) => send!(tx, value),
Ok(value) => try_send!(tx, value),
Err(err) => error!("{err:?}"),
},
OutputStream::Stderr(err) => error!("{err:?}"),
@@ -61,10 +61,7 @@ impl CustomWidget for ProgressWidget {
.await;
});
rx.attach(None, move |value| {
progress.set_fraction(value / self.max);
Continue(true)
});
glib_recv_mpsc!(rx, value => progress.set_fraction(value / self.max));
}
if let Some(text) = self.label {
@@ -73,7 +70,6 @@ impl CustomWidget for ProgressWidget {
dynamic_string(&text, move |string| {
progress.set_text(Some(&string));
Continue(true)
});
}

View File

@@ -1,15 +1,16 @@
use glib::Propagation;
use std::cell::Cell;
use std::ops::Neg;
use gtk::prelude::*;
use gtk::Scale;
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc;
use tracing::error;
use crate::modules::custom::set_length;
use crate::script::{OutputStream, Script, ScriptInput};
use crate::{build, send, try_send};
use crate::{build, glib_recv_mpsc, spawn, try_send};
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
@@ -77,7 +78,7 @@ impl CustomWidget for SliderWidget {
};
scale.set_value(value + delta);
Inhibit(false)
Propagation::Proceed
});
scale.connect_change_value(move |_, _, val| {
@@ -97,7 +98,7 @@ impl CustomWidget for SliderWidget {
prev_value.set(val);
}
Inhibit(false)
Propagation::Proceed
});
}
@@ -105,13 +106,13 @@ impl CustomWidget for SliderWidget {
let script = Script::from(value);
let scale = scale.clone();
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let (tx, rx) = mpsc::channel(128);
spawn(async move {
script
.run(None, move |stream, _success| match stream {
OutputStream::Stdout(out) => match out.parse() {
Ok(value) => send!(tx, value),
Ok(value) => try_send!(tx, value),
Err(err) => error!("{err:?}"),
},
OutputStream::Stderr(err) => error!("{err:?}"),
@@ -119,10 +120,7 @@ impl CustomWidget for SliderWidget {
.await;
});
rx.attach(None, move |value| {
scale.set_value(value);
Continue(true)
});
glib_recv_mpsc!(rx, value => scale.set_value(value));
}
scale

View File

@@ -3,13 +3,11 @@ use crate::config::{CommonConfig, TruncateMode};
use crate::gtk_helpers::IronbarGtkExt;
use crate::image::ImageProvider;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{lock, send_async, try_send};
use crate::{glib_recv, lock, send_async, spawn, try_send};
use color_eyre::Result;
use glib::Continue;
use gtk::prelude::*;
use gtk::Label;
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::debug;
@@ -49,7 +47,7 @@ const fn default_icon_size() -> i32 {
}
impl Module<gtk::Box> for FocusedModule {
type SendMessage = (String, String);
type SendMessage = Option<(String, String)>;
type ReceiveMessage = ();
fn name() -> &'static str {
@@ -78,21 +76,36 @@ impl Module<gtk::Box> for FocusedModule {
if let Some(focused) = focused {
try_send!(
tx,
ModuleUpdateEvent::Update((focused.title.clone(), focused.app_id))
ModuleUpdateEvent::Update(Some((focused.title.clone(), focused.app_id)))
);
};
while let Ok(event) = wlrx.recv().await {
if let ToplevelEvent::Update(handle) = event {
let info = handle.info().unwrap_or_default();
match event {
ToplevelEvent::Update(handle) => {
let info = handle.info().unwrap_or_default();
if info.focused {
debug!("Changing focus");
send_async!(
tx,
ModuleUpdateEvent::Update((info.title.clone(), info.app_id.clone()))
);
if info.focused {
debug!("Changing focus");
send_async!(
tx,
ModuleUpdateEvent::Update(Some((
info.title.clone(),
info.app_id.clone()
)))
);
} else {
send_async!(tx, ModuleUpdateEvent::Update(None));
}
}
ToplevelEvent::Remove(handle) => {
let info = handle.info().unwrap_or_default();
if info.focused {
debug!("Clearing focus");
send_async!(tx, ModuleUpdateEvent::Update(None));
}
}
ToplevelEvent::New(_) => {}
}
}
});
@@ -126,21 +139,25 @@ impl Module<gtk::Box> for FocusedModule {
{
let icon_theme = icon_theme.clone();
context.widget_rx.attach(None, move |(name, id)| {
if self.show_icon {
match ImageProvider::parse(&id, &icon_theme, true, self.icon_size)
.map(|image| image.load_into_image(icon.clone()))
{
Some(Ok(_)) => icon.show(),
_ => icon.hide(),
glib_recv!(context.subscribe(), data => {
if let Some((name, id)) = data {
if self.show_icon {
match ImageProvider::parse(&id, &icon_theme, true, self.icon_size)
.map(|image| image.load_into_image(icon.clone()))
{
Some(Ok(())) => icon.show(),
_ => icon.hide(),
}
}
}
if self.show_title {
label.set_label(&name);
if self.show_title {
label.show();
label.set_label(&name);
}
} else {
icon.hide();
label.hide();
}
Continue(true)
});
}

View File

@@ -1,9 +1,8 @@
use crate::config::CommonConfig;
use crate::dynamic_value::dynamic_string;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::try_send;
use crate::{glib_recv, try_send};
use color_eyre::Result;
use glib::Continue;
use gtk::prelude::*;
use gtk::Label;
use serde::Deserialize;
@@ -42,7 +41,6 @@ impl Module<Label> for LabelModule {
) -> Result<()> {
dynamic_string(&self.label, move |string| {
try_send!(tx, ModuleUpdateEvent::Update(string));
Continue(true)
});
Ok(())
@@ -58,10 +56,7 @@ impl Module<Label> for LabelModule {
{
let label = label.clone();
context.widget_rx.attach(None, move |string| {
label.set_markup(&string);
Continue(true)
});
glib_recv!(context.subscribe(), string => label.set_markup(&string));
}
Ok(ModuleParts {

View File

@@ -7,6 +7,7 @@ use crate::modules::launcher::{ItemEvent, LauncherUpdate};
use crate::modules::ModuleUpdateEvent;
use crate::{read_lock, try_send};
use color_eyre::{Report, Result};
use glib::Propagation;
use gtk::prelude::*;
use gtk::{Button, IconTheme};
use indexmap::IndexMap;
@@ -258,7 +259,7 @@ impl ItemButton {
try_send!(tx, ModuleUpdateEvent::ClosePopup);
}
Inhibit(false)
Propagation::Proceed
});
}
@@ -273,9 +274,9 @@ impl ItemButton {
let (x, y) = ev.position();
let close = match bar_position {
BarPosition::Top => y + THRESHOLD < alloc.height() as f64,
BarPosition::Top => y + THRESHOLD < f64::from(alloc.height()),
BarPosition::Bottom => y > THRESHOLD,
BarPosition::Left => x + THRESHOLD < alloc.width() as f64,
BarPosition::Left => x + THRESHOLD < f64::from(alloc.width()),
BarPosition::Right => x > THRESHOLD,
};
@@ -283,7 +284,7 @@ impl ItemButton {
try_send!(tx, ModuleUpdateEvent::ClosePopup);
}
Inhibit(false)
Propagation::Proceed
});
}

View File

@@ -10,17 +10,15 @@ use crate::modules::launcher::item::AppearanceOptions;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{arc_mut, lock, send_async, try_send, write_lock};
use crate::{arc_mut, glib_recv, lock, send_async, spawn, try_send, write_lock};
use color_eyre::{Help, Report};
use glib::Continue;
use gtk::prelude::*;
use gtk::{Button, Orientation};
use indexmap::IndexMap;
use serde::Deserialize;
use std::process::{Command, Stdio};
use std::sync::Arc;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error, trace};
#[derive(Debug, Deserialize, Clone)]
@@ -92,8 +90,8 @@ impl Module<gtk::Box> for LauncherModule {
fn spawn_controller(
&self,
_info: &ModuleInfo,
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: Receiver<Self::ReceiveMessage>,
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> crate::Result<()> {
let items = self
.favorites
@@ -338,7 +336,9 @@ impl Module<gtk::Box> for LauncherModule {
let mut buttons = IndexMap::<String, ItemButton>::new();
context.widget_rx.attach(None, move |event| {
let tx = context.tx.clone();
let rx = context.subscribe();
glib_recv!(rx, event => {
match event {
LauncherUpdate::AddItem(item) => {
debug!("Adding item with id {}", item.app_id);
@@ -351,7 +351,7 @@ impl Module<gtk::Box> for LauncherModule {
appearance_options,
&icon_theme,
bar_position,
&context.tx,
&tx,
&controller_tx,
);
@@ -411,13 +411,12 @@ impl Module<gtk::Box> for LauncherModule {
}
LauncherUpdate::Hover(_) => {}
};
Continue(true)
});
}
let rx = context.subscribe();
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup(context.controller_tx, rx, info)
.into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
Ok(ModuleParts {
@@ -428,8 +427,8 @@ impl Module<gtk::Box> for LauncherModule {
fn into_popup(
self,
controller_tx: Sender<Self::ReceiveMessage>,
rx: glib::Receiver<Self::SendMessage>,
controller_tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>,
_info: &ModuleInfo,
) -> Option<gtk::Box> {
const MAX_WIDTH: i32 = 250;
@@ -445,7 +444,7 @@ impl Module<gtk::Box> for LauncherModule {
{
let container = container.clone();
rx.attach(None, move |event| {
glib_recv!(rx, event => {
match event {
LauncherUpdate::AddItem(item) => {
let app_id = item.app_id.clone();
@@ -532,8 +531,6 @@ impl Module<gtk::Box> for LauncherModule {
}
_ => {}
}
Continue(true)
});
}

View File

@@ -1,18 +1,19 @@
use std::sync::{Arc, RwLock};
use std::cell::RefCell;
use std::fmt::Debug;
use std::rc::Rc;
use color_eyre::Result;
use glib::IsA;
use gtk::gdk::{EventMask, Monitor};
use gtk::prelude::*;
use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widget};
use tokio::sync::mpsc;
use tokio::sync::{broadcast, mpsc};
use tracing::debug;
use crate::bridge_channel::BridgeChannel;
use crate::config::{BarPosition, CommonConfig, TransitionType};
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
use crate::popup::Popup;
use crate::{send, write_lock};
use crate::{glib_recv_mpsc, send};
#[cfg(feature = "clipboard")]
pub mod clipboard;
@@ -55,8 +56,8 @@ pub struct ModuleInfo<'a> {
pub icon_theme: &'a IconTheme,
}
#[derive(Debug)]
pub enum ModuleUpdateEvent<T> {
#[derive(Debug, Clone)]
pub enum ModuleUpdateEvent<T: Clone> {
/// Sends an update to the module UI.
Update(T),
/// Toggles the open state of the popup.
@@ -70,12 +71,25 @@ pub enum ModuleUpdateEvent<T> {
ClosePopup,
}
pub struct WidgetContext<TSend, TReceive> {
pub struct WidgetContext<TSend, TReceive>
where
TSend: Clone,
{
pub id: usize,
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
pub update_tx: broadcast::Sender<TSend>,
pub controller_tx: mpsc::Sender<TReceive>,
pub widget_rx: glib::Receiver<TSend>,
pub popup_rx: glib::Receiver<TSend>,
_update_rx: broadcast::Receiver<TSend>,
}
impl<TSend, TReceive> WidgetContext<TSend, TReceive>
where
TSend: Clone,
{
pub fn subscribe(&self) -> broadcast::Receiver<TSend> {
self.update_tx.subscribe()
}
}
pub struct ModuleParts<W: IsA<Widget>> {
@@ -150,18 +164,22 @@ where
info: &ModuleInfo,
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> Result<()>;
) -> Result<()>
where
<Self as Module<W>>::SendMessage: Clone;
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
info: &ModuleInfo,
) -> Result<ModuleParts<W>>;
) -> Result<ModuleParts<W>>
where
<Self as Module<W>>::SendMessage: Clone;
fn into_popup(
self,
_tx: mpsc::Sender<Self::ReceiveMessage>,
_rx: glib::Receiver<Self::SendMessage>,
_rx: broadcast::Receiver<Self::SendMessage>,
_info: &ModuleInfo,
) -> Option<gtk::Box>
where
@@ -178,68 +196,57 @@ pub fn create_module<TModule, TWidget, TSend, TRec>(
id: usize,
name: Option<String>,
info: &ModuleInfo,
popup: &Arc<RwLock<Popup>>,
popup: &Rc<RefCell<Popup>>,
) -> Result<ModuleParts<TWidget>>
where
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
TWidget: IsA<Widget>,
TSend: Clone + Send + 'static,
TSend: Debug + Clone + Send + 'static,
{
let (w_tx, w_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
let (p_tx, p_rx) = glib::MainContext::channel::<TSend>(glib::PRIORITY_DEFAULT);
let (ui_tx, ui_rx) = mpsc::channel::<ModuleUpdateEvent<TSend>>(64);
let (controller_tx, controller_rx) = mpsc::channel::<TRec>(64);
let channel = BridgeChannel::<ModuleUpdateEvent<TSend>>::new();
let (ui_tx, ui_rx) = mpsc::channel::<TRec>(16);
let (tx, rx) = broadcast::channel(64);
module.spawn_controller(info, channel.create_sender(), ui_rx)?;
module.spawn_controller(info, ui_tx.clone(), controller_rx)?;
let context = WidgetContext {
id,
widget_rx: w_rx,
popup_rx: p_rx,
tx: channel.create_sender(),
controller_tx: ui_tx,
tx: ui_tx,
update_tx: tx.clone(),
controller_tx,
_update_rx: rx,
};
let module_name = TModule::name();
let instance_name = name.unwrap_or_else(|| module_name.to_string());
let module_parts = module.into_widget(context, info)?;
module_parts.widget.style_context().add_class(module_name);
module_parts.widget.add_class("widget");
module_parts.widget.add_class(module_name);
let has_popup = if let Some(popup_content) = module_parts.popup.clone() {
if let Some(popup_content) = module_parts.popup.clone() {
popup_content
.container
.style_context()
.add_class(&format!("popup-{module_name}"));
register_popup_content(popup, id, instance_name, popup_content);
true
} else {
false
};
}
setup_receiver(
channel,
w_tx,
p_tx,
popup.clone(),
module_name,
id,
has_popup,
);
setup_receiver(tx, ui_rx, popup.clone(), module_name, id);
Ok(module_parts)
}
/// Registers the popup content with the popup.
fn register_popup_content(
popup: &Arc<RwLock<Popup>>,
popup: &Rc<RefCell<Popup>>,
id: usize,
name: String,
popup_content: ModulePopupParts,
) {
write_lock!(popup).register_content(id, name, popup_content);
popup.borrow_mut().register_content(id, name, popup_content);
}
/// Sets up the bridge channel receiver
@@ -248,32 +255,26 @@ fn register_popup_content(
/// Handles opening/closing popups
/// and communicating update messages between controllers and widgets/popups.
fn setup_receiver<TSend>(
channel: BridgeChannel<ModuleUpdateEvent<TSend>>,
w_tx: glib::Sender<TSend>,
p_tx: glib::Sender<TSend>,
popup: Arc<RwLock<Popup>>,
tx: broadcast::Sender<TSend>,
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
popup: Rc<RefCell<Popup>>,
name: &'static str,
id: usize,
has_popup: bool,
) where
TSend: Clone + Send + 'static,
TSend: Debug + Clone + Send + 'static,
{
// some rare cases can cause the popup to incorrectly calculate its size on first open.
// we can fix that by just force re-rendering it on its first open.
let mut has_popup_opened = false;
channel.recv(move |ev| {
glib_recv_mpsc!(rx, ev => {
match ev {
ModuleUpdateEvent::Update(update) => {
if has_popup {
send!(p_tx, update.clone());
}
send!(w_tx, update);
send!(tx, update);
}
ModuleUpdateEvent::TogglePopup(button_id) => {
debug!("Toggling popup for {} [#{}]", name, id);
let mut popup = write_lock!(popup);
let mut popup = popup.borrow_mut();
if popup.is_visible() {
popup.hide();
} else {
@@ -289,7 +290,7 @@ fn setup_receiver<TSend>(
ModuleUpdateEvent::OpenPopup(button_id) => {
debug!("Opening popup for {} [#{}]", name, id);
let mut popup = write_lock!(popup);
let mut popup = popup.borrow_mut();
popup.hide();
popup.show(id, button_id);
@@ -302,7 +303,7 @@ fn setup_receiver<TSend>(
ModuleUpdateEvent::OpenPopupAt(geometry) => {
debug!("Opening popup for {} [#{}]", name, id);
let mut popup = write_lock!(popup);
let mut popup = popup.borrow_mut();
popup.hide();
popup.show_at(id, geometry);
@@ -315,12 +316,10 @@ fn setup_receiver<TSend>(
ModuleUpdateEvent::ClosePopup => {
debug!("Closing popup for {} [#{}]", name, id);
let mut popup = write_lock!(popup);
let mut popup = popup.borrow_mut();
popup.hide();
}
}
Continue(true)
});
}
@@ -375,6 +374,8 @@ pub fn wrap_widget<W: IsA<Widget>>(
revealer.set_reveal_child(true);
let container = EventBox::new();
container.add_class("widget-container");
container.add_events(EventMask::SCROLL_MASK);
container.add(&revealer);

View File

@@ -4,12 +4,11 @@ use std::sync::Arc;
use std::time::Duration;
use color_eyre::Result;
use glib::{Continue, PropertySet};
use glib::{Propagation, PropertySet};
use gtk::prelude::*;
use gtk::{Button, IconTheme, Label, Orientation, Scale};
use regex::Regex;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::{broadcast, mpsc};
use tracing::error;
use crate::clients::music::{
@@ -21,7 +20,7 @@ use crate::modules::PopupButton;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{send_async, try_send};
use crate::{glib_recv, send_async, spawn, try_send};
pub use self::config::MusicModule;
use self::config::PlayerType;
@@ -91,8 +90,8 @@ impl Module<Button> for MusicModule {
fn spawn_controller(
&self,
_info: &ModuleInfo,
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: Receiver<Self::ReceiveMessage>,
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> Result<()> {
let format = self.format.clone();
@@ -213,10 +212,14 @@ impl Module<Button> for MusicModule {
{
let button = button.clone();
let tx = context.tx.clone();
context.widget_rx.attach(None, move |event| {
let ControllerEvent::Update(mut event) = event else { return Continue(true) };
let tx = context.tx.clone();
let rx = context.subscribe();
glib_recv!(rx, event => {
let ControllerEvent::Update(mut event) = event else {
continue;
};
if let Some(event) = event.take() {
label.set_label(&event.display_string);
@@ -246,13 +249,12 @@ impl Module<Button> for MusicModule {
button.hide();
try_send!(tx, ModuleUpdateEvent::ClosePopup);
}
Continue(true)
});
};
let rx = context.subscribe();
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup(context.controller_tx, rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup))
@@ -260,8 +262,8 @@ impl Module<Button> for MusicModule {
fn into_popup(
self,
tx: Sender<Self::ReceiveMessage>,
rx: glib::Receiver<Self::SendMessage>,
tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>,
info: &ModuleInfo,
) -> Option<gtk::Box> {
let icon_theme = info.icon_theme;
@@ -353,7 +355,7 @@ impl Module<Button> for MusicModule {
let tx_vol = tx.clone();
volume_slider.connect_change_value(move |_, _, val| {
try_send!(tx_vol, PlayerCommand::Volume(val as u8));
Inhibit(false)
Propagation::Proceed
});
let progress_box = gtk::Box::new(Orientation::Horizontal, 5);
@@ -378,7 +380,7 @@ impl Module<Button> for MusicModule {
let drag_lock = drag_lock.clone();
progress.connect_button_press_event(move |_, _| {
drag_lock.set(true);
Inhibit(false)
Propagation::Proceed
});
}
@@ -389,7 +391,7 @@ impl Module<Button> for MusicModule {
try_send!(tx, PlayerCommand::Seek(Duration::from_secs_f64(value)));
drag_lock.set(false);
Inhibit(false)
Propagation::Proceed
});
}
@@ -400,7 +402,7 @@ impl Module<Button> for MusicModule {
let image_size = self.cover_image_size;
let mut prev_cover = None;
rx.attach(None, move |event| {
glib_recv!(rx, event => {
match event {
ControllerEvent::Update(Some(update)) => {
// only update art when album changes
@@ -458,7 +460,7 @@ impl Module<Button> for MusicModule {
btn_next.set_sensitive(enable_next);
if let Some(volume) = update.status.volume_percent {
volume_slider.set_value(volume as f64);
volume_slider.set_value(f64::from(volume));
volume_box.show();
} else {
volume_box.hide();
@@ -485,8 +487,6 @@ impl Module<Button> for MusicModule {
}
_ => {}
};
Continue(true)
});
}

View File

@@ -1,12 +1,11 @@
use crate::config::CommonConfig;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::script::{OutputStream, Script, ScriptMode};
use crate::try_send;
use crate::{glib_recv, spawn, try_send};
use color_eyre::{Help, Report, Result};
use gtk::prelude::*;
use gtk::Label;
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::error;
@@ -89,10 +88,7 @@ impl Module<Label> for ScriptModule {
{
let label = label.clone();
context.widget_rx.attach(None, move |s| {
label.set_markup(s.as_str());
Continue(true)
});
glib_recv!(context.subscribe(), s => label.set_markup(s.as_str()));
}
Ok(ModuleParts {

View File

@@ -1,7 +1,7 @@
use crate::config::CommonConfig;
use crate::gtk_helpers::IronbarGtkExt;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::send_async;
use crate::{glib_recv, send_async, spawn};
use color_eyre::Result;
use gtk::prelude::*;
use gtk::Label;
@@ -10,7 +10,6 @@ use serde::Deserialize;
use std::collections::HashMap;
use std::time::Duration;
use sysinfo::{ComponentExt, CpuExt, DiskExt, NetworkExt, RefreshKind, System, SystemExt};
use tokio::spawn;
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::time::sleep;
@@ -205,7 +204,7 @@ impl Module<gtk::Box> for SysInfoModule {
{
let formats = self.format;
context.widget_rx.attach(None, move |info| {
glib_recv!(context.subscribe(), info => {
for (format, label) in formats.iter().zip(labels.clone()) {
let format_compiled = re.replace_all(format, |caps: &Captures| {
info.get(&caps[1])
@@ -215,8 +214,6 @@ impl Module<gtk::Box> for SysInfoModule {
label.set_markup(format_compiled.as_ref());
}
Continue(true)
});
}

View File

@@ -1,8 +1,11 @@
use crate::clients::system_tray::get_tray_event_client;
use crate::config::CommonConfig;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{await_sync, try_send};
use crate::{await_sync, glib_recv, spawn, try_send};
use color_eyre::Result;
use glib::ffi::g_strfreev;
use glib::translate::ToGlibPtr;
use gtk::ffi::gtk_icon_theme_get_search_path;
use gtk::gdk_pixbuf::{Colorspace, InterpType};
use gtk::prelude::*;
use gtk::{
@@ -10,11 +13,13 @@ use gtk::{
SeparatorMenuItem,
};
use serde::Deserialize;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
use system_tray::message::menu::{MenuItem as MenuItemInfo, MenuType};
use system_tray::message::tray::StatusNotifierItem;
use system_tray::message::{NotifierItemCommand, NotifierItemMessage};
use tokio::spawn;
use tokio::sync::mpsc;
use tokio::sync::mpsc::{Receiver, Sender};
@@ -24,21 +29,43 @@ pub struct TrayModule {
pub common: Option<CommonConfig>,
}
/// Gets the GTK icon theme search paths by calling the FFI function.
/// Conveniently returns the result as a `HashSet`.
fn get_icon_theme_search_paths(icon_theme: &IconTheme) -> HashSet<String> {
let mut gtk_paths: *mut *mut c_char = ptr::null_mut();
let mut n_elements: c_int = 0;
let mut paths = HashSet::new();
unsafe {
gtk_icon_theme_get_search_path(
icon_theme.to_glib_none().0,
&mut gtk_paths,
&mut n_elements,
);
// n_elements is never negative (that would be weird)
for i in 0..n_elements as usize {
let c_str = CStr::from_ptr(*gtk_paths.add(i));
if let Ok(str) = c_str.to_str() {
paths.insert(str.to_owned());
}
}
g_strfreev(gtk_paths);
}
paths
}
/// Attempts to get a GTK `Image` component
/// for the status notifier item's icon.
fn get_image_from_icon_name(item: &StatusNotifierItem) -> Option<Image> {
let theme = item
.icon_theme_path
.as_ref()
.map(|path| {
let theme = IconTheme::new();
theme.append_search_path(path);
theme
})
.unwrap_or_default();
fn get_image_from_icon_name(item: &StatusNotifierItem, icon_theme: &IconTheme) -> Option<Image> {
if let Some(path) = item.icon_theme_path.as_ref() {
if !path.is_empty() && !get_icon_theme_search_paths(icon_theme).contains(path) {
icon_theme.append_search_path(path);
}
}
item.icon_name.as_ref().and_then(|icon_name| {
let icon_info = theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
let icon_info = icon_theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
})
}
@@ -171,16 +198,17 @@ impl Module<MenuBar> for TrayModule {
fn into_widget(
self,
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
_info: &ModuleInfo,
info: &ModuleInfo,
) -> Result<ModuleParts<MenuBar>> {
let container = MenuBar::new();
{
let container = container.clone();
let mut widgets = HashMap::new();
let icon_theme = info.icon_theme.clone();
// listen for UI updates
context.widget_rx.attach(None, move |update| {
glib_recv!(context.subscribe(), update => {
match update {
NotifierItemMessage::Update {
item,
@@ -192,7 +220,7 @@ impl Module<MenuBar> for TrayModule {
let menu_item = MenuItem::new();
menu_item.style_context().add_class("item");
get_image_from_icon_name(&item)
get_image_from_icon_name(&item, &icon_theme)
.or_else(|| get_image_from_pixmap(&item))
.map_or_else(
|| {
@@ -233,8 +261,6 @@ impl Module<MenuBar> for TrayModule {
}
}
};
Continue(true)
});
};

View File

@@ -3,8 +3,7 @@ use futures_lite::stream::StreamExt;
use gtk::{prelude::*, Button};
use gtk::{Label, Orientation};
use serde::Deserialize;
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::sync::{broadcast, mpsc};
use upower_dbus::BatteryState;
use zbus;
@@ -16,7 +15,7 @@ use crate::modules::PopupButton;
use crate::modules::{
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
};
use crate::{await_sync, error, send_async, try_send};
use crate::{await_sync, error, glib_recv, send_async, spawn, try_send};
const DAY: i64 = 24 * 60 * 60;
const HOUR: i64 = 60 * 60;
@@ -62,8 +61,8 @@ impl Module<gtk::Button> for UpowerModule {
fn spawn_controller(
&self,
_info: &ModuleInfo,
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
_rx: Receiver<Self::ReceiveMessage>,
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
_rx: mpsc::Receiver<Self::ReceiveMessage>,
) -> Result<()> {
spawn(async move {
// await_sync due to strange "higher-ranked lifetime error"
@@ -174,29 +173,28 @@ impl Module<gtk::Button> for UpowerModule {
container.add(&label);
button.add(&container);
let tx = context.tx.clone();
button.connect_clicked(move |button| {
try_send!(
context.tx,
ModuleUpdateEvent::TogglePopup(button.popup_id())
);
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
});
label.set_angle(info.bar_position.get_angle());
let format = self.format.clone();
context
.widget_rx
.attach(None, move |properties: UpowerProperties| {
let format = format.replace("{percentage}", &properties.percentage.to_string());
let icon_name = String::from("icon:") + &properties.icon_name;
ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size)
.map(|provider| provider.load_into_image(icon.clone()));
label.set_markup(format.as_ref());
Continue(true)
});
let rx = context.subscribe();
glib_recv!(rx, properties => {
let format = format.replace("{percentage}", &properties.percentage.to_string());
let icon_name = String::from("icon:") + &properties.icon_name;
ImageProvider::parse(&icon_name, &icon_theme, false, self.icon_size)
.map(|provider| provider.load_into_image(icon.clone()));
label.set_markup(format.as_ref());
});
let rx = context.subscribe();
let popup = self
.into_popup(context.controller_tx, context.popup_rx, info)
.into_popup(context.controller_tx, rx, info)
.into_popup_parts(vec![&button]);
Ok(ModuleParts::new(button, popup))
@@ -204,8 +202,8 @@ impl Module<gtk::Button> for UpowerModule {
fn into_popup(
self,
_tx: Sender<Self::ReceiveMessage>,
rx: glib::Receiver<Self::SendMessage>,
_tx: mpsc::Sender<Self::ReceiveMessage>,
rx: broadcast::Receiver<Self::SendMessage>,
_info: &ModuleInfo,
) -> Option<gtk::Box>
where
@@ -219,7 +217,7 @@ impl Module<gtk::Button> for UpowerModule {
label.add_class("upower-details");
container.add(&label);
rx.attach(None, move |properties| {
glib_recv!(rx, properties => {
let state = u32_to_battery_state(properties.state);
let format = match state {
Ok(BatteryState::Charging | BatteryState::PendingCharge) => {
@@ -246,7 +244,6 @@ impl Module<gtk::Button> for UpowerModule {
};
label.set_markup(&format);
Continue(true)
});
container.show_all();

View File

@@ -1,17 +1,16 @@
use crate::clients::compositor::{Compositor, Workspace, WorkspaceUpdate};
use crate::clients::compositor::{Compositor, Visibility, Workspace, WorkspaceUpdate};
use crate::config::CommonConfig;
use crate::image::new_icon_button;
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
use crate::{send_async, try_send};
use crate::{glib_recv, send_async, spawn, try_send};
use color_eyre::{Report, Result};
use gtk::prelude::*;
use gtk::{Button, IconTheme};
use serde::Deserialize;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use tokio::spawn;
use tokio::sync::mpsc::{Receiver, Sender};
use tracing::trace;
use tracing::{debug, trace, warn};
#[derive(Debug, Deserialize, Clone, Copy, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
@@ -76,8 +75,7 @@ const fn default_icon_size() -> i32 {
/// Creates a button from a workspace
fn create_button(
name: &str,
focused: bool,
inactive: bool,
visibility: Visibility,
name_map: &HashMap<String, String>,
icon_theme: &IconTheme,
icon_size: i32,
@@ -91,10 +89,16 @@ fn create_button(
let style_context = button.style_context();
style_context.add_class("item");
if focused {
if visibility.is_visible() {
style_context.add_class("visible");
}
if visibility.is_focused() {
style_context.add_class("focused");
} else if inactive {
style_context.add_class("inactive");
}
if !visibility.is_visible() {
style_context.add_class("inactive")
}
{
@@ -131,7 +135,7 @@ fn reorder_workspaces(container: &gtk::Box) {
impl WorkspacesModule {
fn show_workspace_check(&self, output: &String, work: &Workspace) -> bool {
(work.focused || !self.hidden.contains(&work.name))
(work.visibility.is_focused() || !self.hidden.contains(&work.name))
&& (self.all_monitors || output == &work.monitor)
}
}
@@ -158,9 +162,10 @@ impl Module<gtk::Box> for WorkspacesModule {
client.subscribe_workspace_change()
};
trace!("Set up Sway workspace subscription");
trace!("Set up workspace subscription");
while let Ok(payload) = srx.recv().await {
debug!("Received update: {payload:?}");
send_async!(tx, ModuleUpdateEvent::Update(payload));
}
});
@@ -204,7 +209,7 @@ impl Module<gtk::Box> for WorkspacesModule {
// since it fires for every workspace subscriber
let mut has_initialized = false;
context.widget_rx.attach(None, move |event| {
glib_recv!(context.subscribe(), event => {
match event {
WorkspaceUpdate::Init(workspaces) => {
if !has_initialized {
@@ -212,11 +217,10 @@ impl Module<gtk::Box> for WorkspacesModule {
let mut added = HashSet::new();
let mut add_workspace = |name: &str, focused: bool| {
let mut add_workspace = |name: &str, visibility: Visibility| {
let item = create_button(
name,
focused,
false,
visibility,
&name_map,
&icon_theme,
icon_size,
@@ -230,7 +234,7 @@ impl Module<gtk::Box> for WorkspacesModule {
// add workspaces from client
for workspace in &workspaces {
if self.show_workspace_check(&output_name, workspace) {
add_workspace(&workspace.name, workspace.focused);
add_workspace(&workspace.name, workspace.visibility);
added.insert(workspace.name.to_string());
}
}
@@ -238,7 +242,7 @@ impl Module<gtk::Box> for WorkspacesModule {
let mut add_favourites = |names: &Vec<String>| {
for name in names {
if !added.contains(name) {
add_workspace(name, false);
add_workspace(name, Visibility::Hidden);
added.insert(name.to_string());
fav_names.push(name.to_string());
}
@@ -264,14 +268,20 @@ impl Module<gtk::Box> for WorkspacesModule {
}
}
WorkspaceUpdate::Focus { old, new } => {
let old = button_map.get(&old);
if let Some(old) = old {
old.style_context().remove_class("focused");
if let Some(btn) = old.as_ref().and_then(|w| button_map.get(&w.name)) {
if Some(new.monitor) == old.map(|w| w.monitor) {
btn.style_context().remove_class("visible");
}
btn.style_context().remove_class("focused");
}
let new = button_map.get(&new);
if let Some(new) = new {
new.style_context().add_class("focused");
let new = button_map.get(&new.name);
if let Some(btn) = new {
let style = btn.style_context();
style.add_class("visible");
style.add_class("focused");
}
}
WorkspaceUpdate::Add(workspace) => {
@@ -284,8 +294,7 @@ impl Module<gtk::Box> for WorkspacesModule {
let name = workspace.name;
let item = create_button(
&name,
workspace.focused,
false,
workspace.visibility,
&name_map,
&icon_theme,
icon_size,
@@ -310,8 +319,7 @@ impl Module<gtk::Box> for WorkspacesModule {
let name = workspace.name;
let item = create_button(
&name,
workspace.focused,
false,
workspace.visibility,
&name_map,
&icon_theme,
icon_size,
@@ -344,10 +352,8 @@ impl Module<gtk::Box> for WorkspacesModule {
}
}
}
WorkspaceUpdate::Update(_) => {}
WorkspaceUpdate::Unknown => warn!("Received unknown type workspace event")
};
Continue(true)
});
}

View File

@@ -1,19 +1,27 @@
use glib::Propagation;
use std::collections::HashMap;
use gtk::gdk::Monitor;
use gtk::prelude::*;
use gtk::{ApplicationWindow, Orientation};
use gtk_layer_shell::LayerShell;
use tracing::debug;
use crate::config::BarPosition;
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton};
use crate::unique_id::get_unique_usize;
use crate::Ironbar;
#[derive(Debug, Clone)]
pub struct PopupCacheValue {
pub name: String,
pub content: ModulePopupParts,
}
#[derive(Debug, Clone)]
pub struct Popup {
pub window: ApplicationWindow,
pub cache: HashMap<usize, (String, ModulePopupParts)>,
pub cache: HashMap<usize, PopupCacheValue>,
monitor: Monitor,
pos: BarPosition,
current_widget: Option<usize>,
@@ -31,52 +39,38 @@ impl Popup {
.application(module_info.app)
.build();
gtk_layer_shell::init_for_window(&win);
gtk_layer_shell::set_monitor(&win, module_info.monitor);
gtk_layer_shell::set_layer(&win, gtk_layer_shell::Layer::Overlay);
gtk_layer_shell::set_namespace(&win, env!("CARGO_PKG_NAME"));
win.init_layer_shell();
win.set_monitor(module_info.monitor);
win.set_layer(gtk_layer_shell::Layer::Overlay);
win.set_namespace(env!("CARGO_PKG_NAME"));
gtk_layer_shell::set_margin(
&win,
win.set_layer_shell_margin(
gtk_layer_shell::Edge::Top,
if pos == BarPosition::Top { gap } else { 0 },
);
gtk_layer_shell::set_margin(
&win,
win.set_layer_shell_margin(
gtk_layer_shell::Edge::Bottom,
if pos == BarPosition::Bottom { gap } else { 0 },
);
gtk_layer_shell::set_margin(
&win,
win.set_layer_shell_margin(
gtk_layer_shell::Edge::Left,
if pos == BarPosition::Left { gap } else { 0 },
);
gtk_layer_shell::set_margin(
&win,
win.set_layer_shell_margin(
gtk_layer_shell::Edge::Right,
if pos == BarPosition::Right { gap } else { 0 },
);
gtk_layer_shell::set_anchor(
&win,
win.set_anchor(
gtk_layer_shell::Edge::Top,
pos == BarPosition::Top || orientation == Orientation::Vertical,
);
gtk_layer_shell::set_anchor(
&win,
gtk_layer_shell::Edge::Bottom,
pos == BarPosition::Bottom,
);
gtk_layer_shell::set_anchor(
&win,
win.set_anchor(gtk_layer_shell::Edge::Bottom, pos == BarPosition::Bottom);
win.set_anchor(
gtk_layer_shell::Edge::Left,
pos == BarPosition::Left || orientation == Orientation::Horizontal,
);
gtk_layer_shell::set_anchor(
&win,
gtk_layer_shell::Edge::Right,
pos == BarPosition::Right,
);
win.set_anchor(gtk_layer_shell::Edge::Right, pos == BarPosition::Right);
win.connect_leave_notify_event(move |win, ev| {
const THRESHOLD: f64 = 3.0;
@@ -105,7 +99,7 @@ impl Popup {
win.hide();
}
Inhibit(false)
Propagation::Proceed
});
Self {
@@ -121,17 +115,17 @@ impl Popup {
debug!("Registered popup content for #{}", key);
for button in &content.buttons {
let id = get_unique_usize();
let id = Ironbar::unique_id();
button.set_tag("popup-id", id);
}
self.cache.insert(key, (name, content));
self.cache.insert(key, PopupCacheValue { name, content });
}
pub fn show(&mut self, widget_id: usize, button_id: usize) {
self.clear_window();
if let Some((_name, content)) = self.cache.get(&widget_id) {
if let Some(PopupCacheValue { content, .. }) = self.cache.get(&widget_id) {
self.current_widget = Some(widget_id);
content.container.style_context().add_class("popup");
@@ -155,7 +149,7 @@ impl Popup {
pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) {
self.clear_window();
if let Some((_name, content)) = self.cache.get(&widget_id) {
if let Some(PopupCacheValue { content, .. }) = self.cache.get(&widget_id) {
content.container.style_context().add_class("popup");
self.window.add(&content.container);
@@ -223,6 +217,6 @@ impl Popup {
gtk_layer_shell::Edge::Top
};
gtk_layer_shell::set_margin(&self.window, edge, offset as i32);
self.window.set_layer_shell_margin(edge, offset as i32);
}
}

View File

@@ -205,7 +205,7 @@ impl Script {
Ok(output) => callback(output.0, output.1),
Err(err) => error!("{err:?}"),
},
ScriptMode::Watch => match self.spawn().await {
ScriptMode::Watch => match self.spawn() {
Ok(mut rx) => {
while let Some(msg) = rx.recv().await {
callback(msg, true);
@@ -264,7 +264,7 @@ impl Script {
/// Spawns a long-running process.
/// Returns a `mpsc::Receiver` that sends a message
/// every time a new line is written to `stdout` or `stderr`.
pub async fn spawn(&self) -> Result<mpsc::Receiver<OutputStream>> {
pub fn spawn(&self) -> Result<mpsc::Receiver<OutputStream>> {
let mut handle = Command::new("/bin/sh")
.args(["-c", &self.cmd])
.stdout(Stdio::piped())

View File

@@ -1,14 +1,13 @@
use crate::send;
use crate::{glib_recv_mpsc, spawn, try_send};
use color_eyre::{Help, Report};
use glib::Continue;
use gtk::ffi::GTK_STYLE_PROVIDER_PRIORITY_USER;
use gtk::prelude::CssProviderExt;
use gtk::{gdk, gio, CssProvider, StyleContext};
use notify::event::{DataChange, ModifyKind};
use notify::event::ModifyKind;
use notify::{recommended_watcher, Event, EventKind, RecursiveMode, Result, Watcher};
use std::path::PathBuf;
use std::time::Duration;
use tokio::spawn;
use tokio::sync::mpsc;
use tokio::time::sleep;
use tracing::{debug, error, info};
@@ -36,14 +35,20 @@ pub fn load_css(style_path: PathBuf) {
GTK_STYLE_PROVIDER_PRIORITY_USER as u32,
);
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
let (tx, rx) = mpsc::channel(8);
spawn(async move {
let style_path2 = style_path.clone();
let mut watcher = recommended_watcher(move |res: Result<Event>| match res {
Ok(event) if event.kind == EventKind::Modify(ModifyKind::Data(DataChange::Any)) => {
Ok(event) if matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) => {
debug!("{event:?}");
if let Some(path) = event.paths.first() {
send!(tx, path.clone());
if event
.paths
.first()
.map(|p| p == &style_path2)
.unwrap_or_default()
{
try_send!(tx, style_path2.clone());
}
}
Err(e) => error!("Error occurred when watching stylesheet: {:?}", e),
@@ -51,8 +56,10 @@ pub fn load_css(style_path: PathBuf) {
})
.expect("Failed to create CSS file watcher");
let dir_path = style_path.parent().expect("to exist");
watcher
.watch(&style_path, RecursiveMode::NonRecursive)
.watch(dir_path, RecursiveMode::NonRecursive)
.expect("Failed to start CSS file watcher");
debug!("Installed CSS file watcher on '{}'", style_path.display());
@@ -62,19 +69,14 @@ pub fn load_css(style_path: PathBuf) {
}
});
{
rx.attach(None, move |path| {
info!("Reloading CSS");
if let Err(err) = provider
.load_from_file(&gio::File::for_path(path)) {
error!("{:?}", Report::new(err)
.wrap_err("Failed to load CSS")
.suggestion("Check the CSS file for errors")
.suggestion("GTK CSS uses a subset of the full CSS spec and many properties are not available. Ensure you are not using any unsupported property.")
);
}
Continue(true)
});
}
glib_recv_mpsc!(rx, path => {
info!("Reloading CSS");
if let Err(err) = provider.load_from_file(&gio::File::for_path(path)) {
error!("{:?}", Report::new(err)
.wrap_err("Failed to load CSS")
.suggestion("Check the CSS file for errors")
.suggestion("GTK CSS uses a subset of the full CSS spec and many properties are not available. Ensure you are not using any unsupported property.")
);
}
});
}

View File

@@ -1,9 +0,0 @@
use std::sync::atomic::{AtomicUsize, Ordering};
static COUNTER: AtomicUsize = AtomicUsize::new(1);
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
/// This is just an `AtomicUsize` that increments every time this function is called.
pub fn get_unique_usize() -> usize {
COUNTER.fetch_add(1, Ordering::Relaxed)
}