Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40b3b7d80 | ||
|
|
181561fe2a | ||
|
|
7b23e61e7d | ||
|
|
6a39905b43 | ||
|
|
2780d98ee0 | ||
|
|
51d2c2279f | ||
|
|
c347b6c944 | ||
|
|
e83618b1d6 | ||
|
|
90f57d61b9 | ||
|
|
0b9af6bb26 | ||
|
|
11a65d4fbc | ||
|
|
054262365e | ||
|
|
058c8f4228 | ||
|
|
d78d851858 | ||
|
|
db72bc09b4 | ||
|
|
5fb412572f | ||
|
|
400ac00d23 | ||
|
|
80a4b1d177 | ||
|
|
96141d4990 | ||
|
|
b054c17d14 | ||
|
|
3cf9be89fd | ||
|
|
393800aaa2 | ||
|
|
5772711192 | ||
|
|
15f0857859 | ||
|
|
8ba9826cd9 | ||
|
|
07dbf78010 | ||
|
|
97502559b3 | ||
|
|
2b0eb6506a | ||
|
|
012762e102 | ||
|
|
8691824db1 | ||
|
|
ad97550583 | ||
|
|
1ed3220733 |
12
.github/workflows/build.yml
vendored
12
.github/workflows/build.yml
vendored
@@ -23,12 +23,20 @@ jobs:
|
||||
override: true
|
||||
|
||||
- name: Install build deps
|
||||
run: sudo apt install libgtk-3-dev libgtk-layer-shell-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --check
|
||||
|
||||
- name: Clippy
|
||||
- name: Clippy (base features)
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --no-default-features --features config+json
|
||||
|
||||
- name: Clippy (all features)
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
4
.github/workflows/deploy.yml
vendored
4
.github/workflows/deploy.yml
vendored
@@ -18,7 +18,9 @@ jobs:
|
||||
override: true
|
||||
|
||||
- name: Install build deps
|
||||
run: sudo apt install libgtk-3-dev libgtk-layer-shell-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install libgtk-3-dev libgtk-layer-shell-dev
|
||||
|
||||
- name: Update CHANGELOG
|
||||
id: changelog
|
||||
|
||||
40
CHANGELOG.md
40
CHANGELOG.md
@@ -4,6 +4,43 @@ 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.9.0] - 2023-01-28
|
||||
### :boom: BREAKING CHANGES
|
||||
- due to [`fa67d07`](https://github.com/JakeStanger/ironbar/commit/fa67d077b136b109edf6dbaa11a33aebf3e044b4) - mouse event config options *(commit by [@JakeStanger](https://github.com/JakeStanger))*:
|
||||
|
||||
`on_click` is now called `on_click_left` for consistency with new options.
|
||||
|
||||
- due to [`6d8e647`](https://github.com/JakeStanger/ironbar/commit/6d8e647f123e54ba389c5ab2fe908200aa5e4cf6) - mpris support *(commit by [@JakeStanger](https://github.com/JakeStanger))*:
|
||||
|
||||
The `mpd` module has been renamed to `music`. You will need to update the `type` value in your config and add `player_type` to continue using MPD. You will also need to update your styles.
|
||||
|
||||
|
||||
### :sparkles: New Features
|
||||
- [`1dd5863`](https://github.com/JakeStanger/ironbar/commit/1dd586343143bfd501a44c6556719fac9d582d6b) - better surface some config error messages *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`fa67d07`](https://github.com/JakeStanger/ironbar/commit/fa67d077b136b109edf6dbaa11a33aebf3e044b4) - mouse event config options *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`6d8e647`](https://github.com/JakeStanger/ironbar/commit/6d8e647f123e54ba389c5ab2fe908200aa5e4cf6) - mpris support *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`6e5d0c1`](https://github.com/JakeStanger/ironbar/commit/6e5d0c1e8c0b5d7e330608fc835e1e9733f156de) - **workspaces**: hyprland support *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`9ba28fe`](https://github.com/JakeStanger/ironbar/commit/9ba28fe7faf84e06febc2ffea089442f8f5b90a2) - **workspaces**: better ordering *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
|
||||
### :bug: Bug Fixes
|
||||
- [`e1f523c`](https://github.com/JakeStanger/ironbar/commit/e1f523cf2a15b74a5c570dd7440db4c1b476d782) - **music**: popup artist label using wrong name *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`08cfbbc`](https://github.com/JakeStanger/ironbar/commit/08cfbbc2eaf6e74780dd7196efcc15ea6d2e7d12) - **music**: unable to go to prev with mpris *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`0cefcbd`](https://github.com/JakeStanger/ironbar/commit/0cefcbd02b0af518352e35060644f281da249d3e) - **music**: wrong widget name on vol slider *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`90cd078`](https://github.com/JakeStanger/ironbar/commit/90cd078973b23b2291cf156e46729842f33c1806) - **mpd**: stops working if connection lost *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
|
||||
### :recycle: Refactors
|
||||
- [`2c1b292`](https://github.com/JakeStanger/ironbar/commit/2c1b2924d4a103183d3974ac066623a80277a79a) - move most of the horrible `add_module` macro content into proper functions *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`fd2d7e5`](https://github.com/JakeStanger/ironbar/commit/fd2d7e5c7ab8de50c4621b19d07d8b012a451564) - move startup logging code to logging module *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`9d5049d`](https://github.com/JakeStanger/ironbar/commit/9d5049dde01cdb76f4772f8ce8f61a8b5bad3a50) - standardise error messages *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`5e21cbc`](https://github.com/JakeStanger/ironbar/commit/5e21cbcca6cc30d725acdea0f6561cfd6acdcc3c) - macros to reduce repeated code *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`ea2c84d`](https://github.com/JakeStanger/ironbar/commit/ea2c84d1bd15798e32496397c4a6aa42fab39d95) - general code tidy-up *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`0d7ab54`](https://github.com/JakeStanger/ironbar/commit/0d7ab541604691455ed39c73e039ac0635307bc8) - remove redundant clone *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
|
||||
### :memo: Documentation Changes
|
||||
- [`b97f018`](https://github.com/JakeStanger/ironbar/commit/b97f018e81aa55a871a12aa3e1e4b07b1f8eb50f) - update CHANGELOG.md for v0.8.0 [skip ci] *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
- [`c223892`](https://github.com/JakeStanger/ironbar/commit/c223892a57b29ae56431fc585b8cec503f3206c7) - **workspaces**: update for hyprland/new ordering option *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||
|
||||
|
||||
## [v0.8.0] - 2022-11-30
|
||||
### :boom: BREAKING CHANGES
|
||||
- due to [`df77020`](https://github.com/JakeStanger/ironbar/commit/df77020c5277ae9e379bb4fd67c221be5cb20426) - use snake_case for module tokens for consistency *(commit by [@JakeStanger](https://github.com/JakeStanger))*:
|
||||
@@ -156,4 +193,5 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
[v0.5.2]: https://github.com/JakeStanger/ironbar/compare/v0.5.1...v0.5.2
|
||||
[v0.6.0]: https://github.com/JakeStanger/ironbar/compare/v0.5.2...v0.6.0
|
||||
[v0.7.0]: https://github.com/JakeStanger/ironbar/compare/v0.6.0...v0.7.0
|
||||
[v0.8.0]: https://github.com/JakeStanger/ironbar/compare/v0.7.0...v0.8.0
|
||||
[v0.8.0]: https://github.com/JakeStanger/ironbar/compare/v0.7.0...v0.8.0
|
||||
[v0.9.0]: https://github.com/JakeStanger/ironbar/compare/v0.8.0...v0.9.0
|
||||
536
Cargo.lock
generated
536
Cargo.lock
generated
@@ -213,6 +213,12 @@ dependencies = [
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -366,6 +372,16 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
@@ -603,6 +619,15 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-kinds"
|
||||
version = "0.5.1"
|
||||
@@ -688,6 +713,30 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "from_variants"
|
||||
version = "1.0.0"
|
||||
@@ -1054,6 +1103,25 @@ dependencies = [
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@@ -1091,10 +1159,81 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hyprland"
|
||||
version = "0.3.0-alpha.0"
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a04a666b11a405dd7d74dbfb915e7d6f09bc0a52b0715204bf2e54d5572ab935"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyprland"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f76d4dad14a688266c346a9233e8cc5cea0c31a489d8811e3d93176338bafc0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"doc-comment",
|
||||
@@ -1106,6 +1245,8 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -1139,6 +1280,16 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
@@ -1184,11 +1335,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
|
||||
|
||||
[[package]]
|
||||
name = "ironbar"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"async_once",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"color-eyre",
|
||||
"dirs",
|
||||
@@ -1204,6 +1362,7 @@ dependencies = [
|
||||
"mpris",
|
||||
"notify",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
@@ -1213,7 +1372,7 @@ dependencies = [
|
||||
"swayipc-async",
|
||||
"sysinfo",
|
||||
"tokio",
|
||||
"toml",
|
||||
"toml 0.7.0",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-error",
|
||||
@@ -1280,7 +1439,7 @@ dependencies = [
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"toml",
|
||||
"toml 0.5.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1372,6 +1531,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -1437,6 +1602,24 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.2"
|
||||
@@ -1485,6 +1668,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom8"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "5.0.0"
|
||||
@@ -1564,6 +1756,51 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.21",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.0.1"
|
||||
@@ -1647,6 +1884,12 @@ version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.5.1"
|
||||
@@ -1737,7 +1980,7 @@ checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"toml 0.5.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1895,6 +2138,43 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
@@ -1910,6 +2190,12 @@ dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.11"
|
||||
@@ -1925,6 +2211,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
@@ -1937,6 +2232,29 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c4437699b6d34972de58652c68b98cb5b53a4199ab126db8e20ec8ded29a721"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.11.0"
|
||||
@@ -1997,6 +2315,27 @@ dependencies = [
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.14"
|
||||
@@ -2115,9 +2454,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "stray"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e467969fcbf600ebb8d302aa6f2ee6cdf65d2a4c642844632bbdbcaf9c9e2b3e"
|
||||
checksum = "358c1637c5ba4ccf1b6a0698de81454db644866cc426d1abc6d357b2efede511"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"byteorder",
|
||||
@@ -2146,6 +2485,25 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
|
||||
dependencies = [
|
||||
"heck 0.4.0",
|
||||
"proc-macro2",
|
||||
"quote 1.0.21",
|
||||
"rustversion",
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swayipc-async"
|
||||
version = "2.0.1"
|
||||
@@ -2226,7 +2584,7 @@ dependencies = [
|
||||
"cfg-expr",
|
||||
"heck 0.4.0",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"toml 0.5.9",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
@@ -2320,6 +2678,21 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.23.0"
|
||||
@@ -2351,6 +2724,16 @@ dependencies = [
|
||||
"syn 1.0.105",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.11"
|
||||
@@ -2362,6 +2745,20 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.9"
|
||||
@@ -2371,6 +2768,46 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"nom8",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
@@ -2454,6 +2891,12 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
@@ -2476,12 +2919,27 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.10.0"
|
||||
@@ -2506,6 +2964,17 @@ version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
@@ -2518,6 +2987,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
@@ -2574,6 +3049,16 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
@@ -2611,6 +3096,18 @@ dependencies = [
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.83"
|
||||
@@ -2710,6 +3207,16 @@ dependencies = [
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wepoll-ffi"
|
||||
version = "0.1.2"
|
||||
@@ -2807,6 +3314,15 @@ version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xcursor"
|
||||
version = "0.3.4"
|
||||
|
||||
88
Cargo.toml
88
Cargo.toml
@@ -1,15 +1,51 @@
|
||||
[package]
|
||||
name = "ironbar"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
description = "Customisable GTK Layer Shell wlroots/sway bar"
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"http",
|
||||
"config+all",
|
||||
"clock",
|
||||
"music+all",
|
||||
"sys_info",
|
||||
"tray",
|
||||
"workspaces+all"
|
||||
]
|
||||
|
||||
http = ["dep:reqwest"]
|
||||
|
||||
"config+all" = ["config+json", "config+yaml", "config+toml", "config+corn"]
|
||||
"config+json" = ["serde_json"]
|
||||
"config+yaml" = ["serde_yaml"]
|
||||
"config+toml" = ["toml"]
|
||||
"config+corn" = ["libcorn"]
|
||||
|
||||
clock = ["chrono"]
|
||||
|
||||
music = ["regex"]
|
||||
"music+all" = ["music", "music+mpris", "music+mpd"]
|
||||
"music+mpris" = ["music", "mpris"]
|
||||
"music+mpd" = ["music", "mpd_client"]
|
||||
|
||||
sys_info = ["sysinfo", "regex"]
|
||||
|
||||
tray = ["stray"]
|
||||
|
||||
workspaces = ["futures-util"]
|
||||
"workspaces+all" = ["workspaces", "workspaces+sway", "workspaces+hyprland"]
|
||||
"workspaces+sway" = ["workspaces", "swayipc-async"]
|
||||
"workspaces+hyprland" = ["workspaces", "hyprland"]
|
||||
|
||||
[dependencies]
|
||||
# core
|
||||
gtk = "0.16.0"
|
||||
gtk-layer-shell = "0.5.0"
|
||||
glib = "0.16.2"
|
||||
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process"] }
|
||||
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process", "sync", "io-util", "net"] }
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
tracing-error = "0.2.0"
|
||||
@@ -17,25 +53,43 @@ tracing-appender = "0.2.2"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
color-eyre = "0.6.2"
|
||||
serde = { version = "1.0.141", features = ["derive"] }
|
||||
serde_json = "1.0.82"
|
||||
serde_yaml = "0.9.4"
|
||||
toml = "0.5.9"
|
||||
libcorn = "0.6.1"
|
||||
lazy_static = "1.4.0"
|
||||
async_once = "0.2.6"
|
||||
indexmap = "1.9.1"
|
||||
futures-util = "0.3.21"
|
||||
chrono = "0.4.19"
|
||||
regex = { version = "1.6.0", default-features = false, features = ["std"] }
|
||||
stray = { version = "0.1.2" }
|
||||
dirs = "4.0.0"
|
||||
walkdir = "2.3.2"
|
||||
notify = { version = "5.0.0", default-features = false }
|
||||
mpd_client = "1.0.0"
|
||||
mpris = "2.0.0"
|
||||
swayipc-async = { version = "2.0.1" }
|
||||
hyprland = "0.3.0-alpha.0"
|
||||
sysinfo = "0.27.0"
|
||||
wayland-client = "0.29.5"
|
||||
wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] }
|
||||
smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] }
|
||||
lazy_static = "1.4.0"
|
||||
async_once = "0.2.6"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
# http
|
||||
reqwest = { version = "0.11.14", optional = true }
|
||||
|
||||
# config
|
||||
serde_json = { version = "1.0.82", optional = true }
|
||||
serde_yaml = { version = "0.9.4", optional = true }
|
||||
toml = { version = "0.7.0", optional = true }
|
||||
libcorn = { version = "0.6.1", optional = true }
|
||||
|
||||
# clock
|
||||
chrono = { version = "0.4.19", optional = true }
|
||||
|
||||
# music
|
||||
mpd_client = { version = "1.0.0", optional = true }
|
||||
mpris = { version = "2.0.0", optional = true }
|
||||
|
||||
# sys_info
|
||||
sysinfo = { version = "0.27.0", optional = true }
|
||||
|
||||
# tray
|
||||
stray = { version = "0.1.3", optional = true }
|
||||
|
||||
# workspaces
|
||||
swayipc-async = { version = "2.0.1", optional = true }
|
||||
hyprland = { version = "0.3.0", optional = true }
|
||||
futures-util = { version = "0.3.21", optional = true }
|
||||
|
||||
# shared
|
||||
regex = { version = "1.6.0", default-features = false, features = ["std"], optional = true } # music, sys_info
|
||||
17
README.md
17
README.md
@@ -6,8 +6,7 @@ It uses GTK3 and gtk-layer-shell.
|
||||
The bar can be styled to your liking using CSS and hot-loads style changes.
|
||||
For information and examples on styling please see the [wiki](https://github.com/JakeStanger/ironbar/wiki).
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
@@ -29,9 +28,11 @@ yay -S ironbar-git
|
||||
|
||||
### Nix Flake
|
||||
|
||||
A flake is included with the repo which can be used with home-manager.
|
||||
|
||||
#### Example
|
||||
Here is an example nix flake that uses ironbar, this is just a
|
||||
proof of concept, please adapt it to your config
|
||||
|
||||
Here is an example nix flake that uses Ironbar.
|
||||
|
||||
```nix
|
||||
{
|
||||
@@ -67,8 +68,9 @@ proof of concept, please adapt it to your config
|
||||
```
|
||||
|
||||
#### Binary Caching
|
||||
There is also a cachix cache at `https://app.cachix.org/cache/jakestanger`
|
||||
incase you don't want to compile ironbar!
|
||||
|
||||
There is a Cachix cache available at `https://app.cachix.org/cache/jakestanger`
|
||||
in case you don't want to compile Ironbar.
|
||||
|
||||
### Source
|
||||
|
||||
@@ -80,6 +82,9 @@ cargo build --release
|
||||
install target/release/ironbar ~/.local/bin/ironbar
|
||||
```
|
||||
|
||||
By default, all features are enabled.
|
||||
See [here](https://github.com/JakeStanger/ironbar/wiki/compiling) for controlling which features are included.
|
||||
|
||||
[repo](https://github.com/jakestanger/ironbar)
|
||||
|
||||
## Running
|
||||
|
||||
51
docs/Compiling.md
Normal file
51
docs/Compiling.md
Normal file
@@ -0,0 +1,51 @@
|
||||
You can compile Ironbar from source using `cargo`.
|
||||
Just clone the repo and build:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/jakestanger/ironbar.git
|
||||
cd ironbar
|
||||
cargo build --release
|
||||
# change path to wherever you want to install
|
||||
install target/release/ironbar ~/.local/bin/ironbar
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
By default, all features are enabled for convenience. This can result in a significant compile time.
|
||||
If you know you are not going to need all the features, you can compile with only the features you need.
|
||||
|
||||
As of `v0.10.0`, compiling with no features is about 33% faster.
|
||||
On a 3800X, it takes about 60 seconds for no features and 90 seconds for all.
|
||||
This difference is expected to increase as the bar develops.
|
||||
|
||||
Features containing a `+` can be stacked, for example `config+json` and `config+yaml` could both be enabled.
|
||||
|
||||
To build using only specific features, disable default features and pass a comma separated list to `cargo build`:
|
||||
|
||||
```shell
|
||||
cargo build --release --no-default-features \
|
||||
--features http,config+json,clock
|
||||
```
|
||||
|
||||
> ⚠ Make sure you enable at least one `config` feature otherwise you will not be able to start the bar!
|
||||
|
||||
| Feature | Description |
|
||||
|---------------------|-----------------------------------------------------------------------------------|
|
||||
| **Core** | |
|
||||
| http | Enables HTTP features. Currently this includes the ability to load remote images. |
|
||||
| config+all | Enables support for all configuration languages. |
|
||||
| config+json | Enables configuration support for JSON. |
|
||||
| config+yaml | Enables configuration support for YAML. |
|
||||
| config+toml | Enables configuration support for TOML. |
|
||||
| config+corn | Enables configuration support for [Corn](https://github.com/jakestanger.corn). |
|
||||
| **Modules** | |
|
||||
| clock | Enables the `clock` module. |
|
||||
| music+all | Enables the `music` module with support for all player types. |
|
||||
| music+mpris | Enables the `music` module with MPRIS support. |
|
||||
| music+mpd | Enables the `music` module with MPD support. |
|
||||
| sys_info | Enables the `sys_info` module. |
|
||||
| tray | Enables the `tray` module. |
|
||||
| workspaces+all | Enables the `workspaces` module with support for all compositors. |
|
||||
| workspaces+sway | Enables the `workspaces` module with support for Sway. |
|
||||
| workspaces+hyprland | Enables the `workspaces` module with support for Hyprland. |
|
||||
|
||||
@@ -272,6 +272,7 @@ The following table lists each of the top-level bar config options:
|
||||
| `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. |
|
||||
| `icon_theme` | `string` | `null` | Name of the GTK icon theme to use. Leave blank to use default. |
|
||||
| `start` | `Module[]` | `[]` | Array of left or top modules. |
|
||||
| `center` | `Module[]` | `[]` | Array of center modules. |
|
||||
| `end` | `Module[]` | `[]` | Array of right or bottom modules. |
|
||||
|
||||
15
docs/Images.md
Normal file
15
docs/Images.md
Normal file
@@ -0,0 +1,15 @@
|
||||
Ironbar is capable of loading images from multiple sources.
|
||||
In any situation where an option takes text or an icon,
|
||||
you can use a string in any of the following formats, and it will automatically be detected as an image:
|
||||
|
||||
| Source | Example |
|
||||
|-------------------------------|---------------------------------|
|
||||
| GTK icon theme | `icon:firefox` |
|
||||
| Local file | `file:///path/to/file.jpg` |
|
||||
| Remote file (over HTTP/HTTPS) | `https://example.com/image.jpg` |
|
||||
|
||||
Remote images are loaded asynchronously to avoid blocking the UI thread.
|
||||
Be aware this can cause elements to change size upon load if the image is large enough.
|
||||
|
||||
Note that mixing text and images is not supported.
|
||||
Your best option here is to use Nerd Font icons instead.
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
- [Configuration guide](configuration-guide)
|
||||
- [Scripts](scripts)
|
||||
- [Images](images)
|
||||
- [Styling guide](styling-guide)
|
||||
- [Examples](https://github.com/JakeStanger/ironbar/tree/master/examples)
|
||||
|
||||
# Examples
|
||||
|
||||
- [Config](config)
|
||||
- [Stylesheet](stylesheet)
|
||||
- [Stylesheet](https://github.com/JakeStanger/ironbar/blob/master/examples/style.css)
|
||||
|
||||
## Custom
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -1,202 +1,10 @@
|
||||
The below config shows a module of each type being used.
|
||||
The configs linked below show a module of each type being used.
|
||||
|
||||
The Corn format makes heavy use of variables
|
||||
to show how module configs can be easily referenced to improve readability
|
||||
and reduce config length when using multiple bars.
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"start": [
|
||||
{
|
||||
"all_monitors": false,
|
||||
"name_map": {
|
||||
"1": "ﭮ",
|
||||
"2": "",
|
||||
"3": "",
|
||||
"Code": "",
|
||||
"Games": ""
|
||||
},
|
||||
"type": "workspaces"
|
||||
},
|
||||
{
|
||||
"favorites": [
|
||||
"firefox",
|
||||
"discord",
|
||||
"Steam"
|
||||
],
|
||||
"icon_theme": "Paper",
|
||||
"show_icons": true,
|
||||
"show_names": false,
|
||||
"type": "launcher"
|
||||
}
|
||||
],
|
||||
"end": [
|
||||
{
|
||||
"music_dir": "/home/jake/Music",
|
||||
"type": "mpd"
|
||||
},
|
||||
{
|
||||
"host": "chloe:6600",
|
||||
"type": "mpd"
|
||||
},
|
||||
{
|
||||
"path": "/home/jake/bin/phone-battery",
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"format": [
|
||||
"{cpu-percent}% ",
|
||||
"{memory-percent}% "
|
||||
],
|
||||
"type": "sys-info"
|
||||
},
|
||||
{
|
||||
"type": "clock"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>TOML</summary>
|
||||
|
||||
```toml
|
||||
[[start]]
|
||||
all_monitors = false
|
||||
type = 'workspaces'
|
||||
|
||||
[start.name_map]
|
||||
1 = 'ﭮ'
|
||||
2 = ''
|
||||
3 = ''
|
||||
Code = ''
|
||||
Games = ''
|
||||
|
||||
[[start]]
|
||||
icon_theme = 'Paper'
|
||||
show_icons = true
|
||||
show_names = false
|
||||
type = 'launcher'
|
||||
favorites = [
|
||||
'firefox',
|
||||
'discord',
|
||||
'Steam',
|
||||
]
|
||||
|
||||
[[end]]
|
||||
music_dir = '/home/jake/Music'
|
||||
type = 'mpd'
|
||||
|
||||
[[end]]
|
||||
host = 'chloe:6600'
|
||||
type = 'mpd'
|
||||
|
||||
[[end]]
|
||||
path = '/home/jake/bin/phone-battery'
|
||||
type = 'script'
|
||||
|
||||
[[end]]
|
||||
type = 'sys-info'
|
||||
format = [
|
||||
'{cpu-percent}% ',
|
||||
'{memory-percent}% ',
|
||||
]
|
||||
|
||||
[[end]]
|
||||
type = 'clock'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>YAML</summary>
|
||||
|
||||
```yaml
|
||||
---
|
||||
start:
|
||||
- all_monitors: false
|
||||
name_map:
|
||||
"1": ﭮ
|
||||
"2":
|
||||
"3":
|
||||
Code:
|
||||
Games:
|
||||
type: workspaces
|
||||
- favorites:
|
||||
- firefox
|
||||
- discord
|
||||
- Steam
|
||||
icon_theme: Paper
|
||||
show_icons: true
|
||||
show_names: false
|
||||
type: launcher
|
||||
end:
|
||||
- music_dir: /home/jake/Music
|
||||
type: mpd
|
||||
- host: "chloe:6600"
|
||||
type: mpd
|
||||
- path: /home/jake/bin/phone-battery
|
||||
type: script
|
||||
- format:
|
||||
- "{cpu-percent}% "
|
||||
- "{memory-percent}% "
|
||||
type: sys-info
|
||||
- type: clock
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Corn</summary>
|
||||
|
||||
```corn
|
||||
let {
|
||||
$workspaces = {
|
||||
type = "workspaces"
|
||||
all_monitors = false
|
||||
name_map = {
|
||||
1 = "ﭮ"
|
||||
2 = ""
|
||||
3 = ""
|
||||
Games = ""
|
||||
Code = ""
|
||||
}
|
||||
}
|
||||
|
||||
$launcher = {
|
||||
type = "launcher"
|
||||
favorites = ["firefox" "discord" "Steam"]
|
||||
show_names = false
|
||||
show_icons = true
|
||||
icon_theme = "Paper"
|
||||
}
|
||||
|
||||
$mpd_local = { type = "mpd" music_dir = "/home/jake/Music" }
|
||||
$mpd_server = { type = "mpd" host = "chloe:6600" }
|
||||
|
||||
$sys_info = {
|
||||
type = "sys-info"
|
||||
format = ["{cpu-percent}% " "{memory-percent}% "]
|
||||
}
|
||||
|
||||
$tray = { type = "tray" }
|
||||
$clock = { type = "clock" }
|
||||
|
||||
$phone_battery = {
|
||||
type = "script"
|
||||
path = "/home/jake/bin/phone-battery"
|
||||
}
|
||||
|
||||
$start = [ $workspaces $launcher ]
|
||||
$end = [ $mpd_local $mpd_server $phone_battery $sys_info $clock ]
|
||||
}
|
||||
in {
|
||||
start = $start
|
||||
end = $end
|
||||
}
|
||||
```
|
||||
</details>
|
||||
- [JSON](https://github.com/JakeStanger/ironbar/blob/master/examples/config.json)
|
||||
- [TOML](https://github.com/JakeStanger/ironbar/blob/master/examples/config.toml)
|
||||
- [YAML](https://github.com/JakeStanger/ironbar/blob/master/examples/config.yaml)
|
||||
- [Corn](https://github.com/JakeStanger/ironbar/blob/master/examples/config.corn)
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
The below example is a full stylesheet for all modules:
|
||||
|
||||
```css
|
||||
* {
|
||||
/* a nerd font is required to be installed for icons */
|
||||
font-family: Noto Sans Nerd Font, sans-serif;
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
#bar {
|
||||
border-top: 1px solid #424242;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
.container#end > * + * {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.popup {
|
||||
background-color: #2d2d2d;
|
||||
border: 1px solid #424242;
|
||||
}
|
||||
|
||||
#workspaces .item {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#workspaces .item.focused {
|
||||
box-shadow: inset 0 -3px;
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
#workspaces *:not(.focused):hover {
|
||||
box-shadow: inset 0 -3px;
|
||||
}
|
||||
|
||||
#launcher .item {
|
||||
border-radius: 0;
|
||||
background-color: #2d2d2d;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#launcher .item:not(.focused):hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
#launcher .open {
|
||||
border-bottom: 2px solid #6699cc;
|
||||
}
|
||||
|
||||
#launcher .focused {
|
||||
color: white;
|
||||
background-color: black;
|
||||
border-bottom: 4px solid #6699cc;
|
||||
}
|
||||
|
||||
#launcher .urgent {
|
||||
color: white;
|
||||
background-color: #8f0a0a;
|
||||
}
|
||||
|
||||
#script {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#sysinfo {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#tray .item {
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
#mpd {
|
||||
background-color: #2d2d2d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#popup-mpd {
|
||||
color: white;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#popup-mpd #album-art {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#popup-mpd #title .icon, #popup-mpd #title .label {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
#popup-mpd #controls * {
|
||||
border-radius: 0;
|
||||
background-color: #2d2d2d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#popup-mpd #controls *:disabled {
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
#clock {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#popup-clock {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#popup-clock #calendar-clock {
|
||||
color: white;
|
||||
font-size: 2.5em;
|
||||
padding-bottom: 0.1em;
|
||||
}
|
||||
|
||||
#popup-clock #calendar {
|
||||
background-color: #2d2d2d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#popup-clock #calendar .header {
|
||||
padding-top: 1em;
|
||||
border-top: 1px solid #424242;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
#popup-clock #calendar:selected {
|
||||
background-color: #6699cc;
|
||||
}
|
||||
|
||||
#focused {
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
Creates a button on the bar, which opens a popup. The popup contains a header, shutdown button, restart button, and uptime.
|
||||
|
||||

|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -16,9 +16,9 @@ Creates a button on the bar, which opens a popup. The popup contains a header, s
|
||||
{
|
||||
"bar": [
|
||||
{
|
||||
"on_click": "popup:toggle",
|
||||
"label": "",
|
||||
"name": "power-btn",
|
||||
"on_click": "popup:toggle",
|
||||
"type": "button"
|
||||
}
|
||||
],
|
||||
@@ -38,26 +38,27 @@ Creates a button on the bar, which opens a popup. The popup contains a header, s
|
||||
"widgets": [
|
||||
{
|
||||
"class": "power-btn",
|
||||
"on_click": "!shutdown now",
|
||||
"label": "<span font-size='40pt'></span>",
|
||||
"on_click": "!shutdown now",
|
||||
"type": "button"
|
||||
},
|
||||
{
|
||||
"class": "power-btn",
|
||||
"on_click": "!reboot",
|
||||
"label": "<span font-size='40pt'></span>",
|
||||
"on_click": "!reboot",
|
||||
"type": "button"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Up: {{30000:uptime -p | cut -d ' ' -f2-}}",
|
||||
"label": "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}",
|
||||
"name": "uptime",
|
||||
"type": "label"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"tooltip": "Up: {{30000:uptime -p | cut -d ' ' -f2-}}",
|
||||
"type": "custom"
|
||||
}
|
||||
]
|
||||
@@ -75,12 +76,13 @@ type = 'clock'
|
||||
|
||||
[[end]]
|
||||
class = 'power-menu'
|
||||
tooltip = '''Up: {{30000:uptime -p | cut -d ' ' -f2-}}'''
|
||||
type = 'custom'
|
||||
|
||||
[[end.bar]]
|
||||
on_click = 'popup:toggle'
|
||||
label = ''
|
||||
name = 'power-btn'
|
||||
on_click = 'popup:toggle'
|
||||
type = 'button'
|
||||
|
||||
[[end.popup]]
|
||||
@@ -97,18 +99,18 @@ type = 'box'
|
||||
|
||||
[[end.popup.widgets.widgets]]
|
||||
class = 'power-btn'
|
||||
on_click = '!shutdown now'
|
||||
label = '''<span font-size='40pt'></span>'''
|
||||
on_click = '!shutdown now'
|
||||
type = 'button'
|
||||
|
||||
[[end.popup.widgets.widgets]]
|
||||
class = 'power-btn'
|
||||
on_click = '!reboot'
|
||||
label = '''<span font-size='40pt'></span>'''
|
||||
on_click = '!reboot'
|
||||
type = 'button'
|
||||
|
||||
[[end.popup.widgets]]
|
||||
label = '''Up: {{30000:uptime -p | cut -d ' ' -f2-}}'''
|
||||
label = '''Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}'''
|
||||
name = 'uptime'
|
||||
type = 'label'
|
||||
```
|
||||
@@ -121,10 +123,11 @@ type = 'label'
|
||||
```yaml
|
||||
end:
|
||||
- type: clock
|
||||
|
||||
- bar:
|
||||
- on_click: popup:toggle
|
||||
label:
|
||||
- label:
|
||||
name: power-btn
|
||||
on_click: popup:toggle
|
||||
type: button
|
||||
class: power-menu
|
||||
popup:
|
||||
@@ -137,16 +140,17 @@ end:
|
||||
- type: box
|
||||
widgets:
|
||||
- class: power-btn
|
||||
on_click: '!shutdown now'
|
||||
label: <span font-size='40pt'></span>
|
||||
on_click: '!shutdown now'
|
||||
type: button
|
||||
- class: power-btn
|
||||
on_click: '!reboot'
|
||||
label: <span font-size='40pt'></span>
|
||||
on_click: '!reboot'
|
||||
type: button
|
||||
- label: 'Up: {{30000:uptime -p | cut -d '' '' -f2-}}'
|
||||
- label: 'Uptime: {{30000:uptime -p | cut -d '' '' -f2-}}'
|
||||
name: uptime
|
||||
type: label
|
||||
tooltip: 'Up: {{30000:uptime -p | cut -d '' '' -f2-}}'
|
||||
type: custom
|
||||
```
|
||||
|
||||
@@ -157,30 +161,37 @@ end:
|
||||
|
||||
```corn
|
||||
let {
|
||||
$button = { type = "button" name="power-btn" label = "" on_click = "popup:toggle" }
|
||||
|
||||
$popup = {
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
widgets = [
|
||||
{ type = "label" name = "header" label = "Power menu" }
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "button" 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 = "label" name = "uptime" label = "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}" }
|
||||
]
|
||||
}
|
||||
|
||||
$power_menu = {
|
||||
type = "custom"
|
||||
class = "power-menu"
|
||||
|
||||
bar = [ { type = "button" name="power-btn" label = "" on_click = "popup:toggle" } ]
|
||||
bar = [ $button ]
|
||||
popup = [ $popup ]
|
||||
|
||||
popup = [ {
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
widgets = [
|
||||
{ type = "label" name = "header" label = "Power menu" }
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "button" 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 = "label" name = "uptime" label = "Up: {{30000:uptime -p | cut -d ' ' -f2-}}" }
|
||||
]
|
||||
} ]
|
||||
tooltip = "Up: {{30000:uptime -p | cut -d ' ' -f2-}}"
|
||||
}
|
||||
|
||||
$clock = { type = "clock" }
|
||||
} in {
|
||||
end = [ $power_menu ]
|
||||
end = [ $power_menu $clock ]
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
@@ -8,8 +8,8 @@ Clicking on the widget opens a popup with the time and a calendar.
|
||||
|
||||
> Type: `clock`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|----------|--------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Name | Type | Default | Description |
|
||||
|----------|----------|------------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `format` | `string` | `%d/%m/%Y %H:%M` | Date/time format string. Detail on available tokens can be found here: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html> |
|
||||
|
||||
<details>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Allows you to compose custom modules consisting of multiple widgets, including popups.
|
||||
Labels can display dynamic content from scripts, and buttons can interact with the bar or execute commands on click.
|
||||
|
||||

|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -18,15 +18,17 @@ It is well worth looking at the examples.
|
||||
|
||||
### `Widget`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|---------------|------------------------------|--------------|---------------------------------------------------------------------------|
|
||||
| `widget_type` | `box` or `label` or `button` | `null` | Type of GTK widget to create. |
|
||||
| `name` | `string` | `null` | Widget name. |
|
||||
| `class` | `string` | `null` | Widget class name. |
|
||||
| `label` | `string` | `null` | [`label` and `button`] Widget text label. Pango markup supported. |
|
||||
| `on_click` | `string` | `null` | [`button`] Command to execute. More on this [below](#commands). |
|
||||
| `orientation` | `horizontal` or `vertical` | `horizontal` | [`box`] Whether child widgets should be horizontally or vertically added. |
|
||||
| `widgets` | `Widget[]` | `[]` | [`box`] List of widgets to add to this box. |
|
||||
| Name | Type | Default | Description |
|
||||
|---------------|-----------------------------------------|--------------|---------------------------------------------------------------------------|
|
||||
| `widget_type` | `box` or `label` or `button` or `image` | `null` | Type of GTK widget to create. |
|
||||
| `name` | `string` | `null` | Widget name. |
|
||||
| `class` | `string` | `null` | Widget class name. |
|
||||
| `label` | `string` | `null` | [`label` and `button`] Widget text label. Pango markup supported. |
|
||||
| `on_click` | `string` | `null` | [`button`] Command to execute. More on this [below](#commands). |
|
||||
| `src` | `image` | `null` | [`image`] Image source. See [here](images) for information on images. |
|
||||
| `size` | `integer` | `null` | [`image`] Width/height of the image. Aspect ratio is preserved. |
|
||||
| `orientation` | `horizontal` or `vertical` | `horizontal` | [`box`] Whether child widgets should be horizontally or vertically added. |
|
||||
| `widgets` | `Widget[]` | `[]` | [`box`] List of widgets to add to this box. |
|
||||
|
||||
### Labels
|
||||
|
||||
@@ -56,6 +58,8 @@ The following bar commands are supported:
|
||||
- `popup:open`
|
||||
- `popup:close`
|
||||
|
||||
---
|
||||
|
||||
XML is arguably better-suited and easier to read for this sort of markup,
|
||||
but currently is not supported.
|
||||
Nonetheless, it may be worth comparing the examples to the below equivalent
|
||||
|
||||
@@ -7,12 +7,14 @@ Displays the title and/or icon of the currently focused window.
|
||||
|
||||
> Type: `focused`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|--------------|-----------|---------|---------------------------------|
|
||||
| `show_icon` | `boolean` | `true` | Whether to show the app's icon |
|
||||
| `show_title` | `boolean` | `true` | Whether to show the app's title |
|
||||
| `icon_size` | `integer` | `32` | Size of icon in pixels |
|
||||
| `icon_theme` | `string` | `null` | GTK icon theme to use |
|
||||
| Name | Type | Default | Description |
|
||||
|-------------------|---------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `show_icon` | `boolean` | `true` | Whether to show the app's icon |
|
||||
| `show_title` | `boolean` | `true` | Whether to show the app's title |
|
||||
| `icon_size` | `integer` | `32` | Size of icon in pixels |
|
||||
| `truncate` | `start` or `middle` or `end` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||
| `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||
| `truncate.length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
@@ -25,7 +27,7 @@ Displays the title and/or icon of the currently focused window.
|
||||
"show_icon": true,
|
||||
"show_title": true,
|
||||
"icon_size": 32,
|
||||
"icon_theme": "Paper"
|
||||
"truncate": "end"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -43,7 +45,7 @@ type = "focused"
|
||||
show_icon = true
|
||||
show_title = true
|
||||
icon_size = 32
|
||||
icon_theme = "Paper"
|
||||
truncate = "end"
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -57,7 +59,7 @@ end:
|
||||
show_icon: true
|
||||
show_title: true
|
||||
icon_size: 32
|
||||
icon_theme: "Paper"
|
||||
truncate: "end"
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -73,7 +75,7 @@ end:
|
||||
show_icon = true
|
||||
show_title = true
|
||||
icon_size = 32
|
||||
icon_theme = "Paper"
|
||||
truncate = "end"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ Hovering over a program with multiple windows open shows a popup with each windo
|
||||
Clicking an icon/popup item focuses or launches the program.
|
||||
Optionally displays a launchable set of favourites.
|
||||
|
||||

|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -14,7 +14,6 @@ Optionally displays a launchable set of favourites.
|
||||
| `favorites` | `string[]` | `[]` | List of app IDs (or classes) to always show at the start of the launcher |
|
||||
| `show_names` | `boolean` | `false` | Whether to show app names on the button label. Names will still show on tooltips when set to false. |
|
||||
| `show_icons` | `boolean` | `true` | Whether to show app icons on the button. |
|
||||
| `icon_theme` | `string` | `null` | GTK icon theme to use. |
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
@@ -29,8 +28,7 @@ Optionally displays a launchable set of favourites.
|
||||
"discord"
|
||||
],
|
||||
"show_names": false,
|
||||
"show_icons": true,
|
||||
"icon_theme": "Paper"
|
||||
"show_icons": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -49,7 +47,6 @@ type = "launcher"
|
||||
favorites = ["firefox", "discord"]
|
||||
show_names = false
|
||||
show_icons = true
|
||||
icon_theme = "Paper"
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -65,7 +62,6 @@ start:
|
||||
- discord
|
||||
show_names: false
|
||||
show_icons: true
|
||||
icon_theme: "Paper"
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -78,10 +74,9 @@ start:
|
||||
start = [
|
||||
{
|
||||
type = "launcher"
|
||||
favorites = ["firefox" "discord"]
|
||||
favorites = [ "firefox" "discord" ]
|
||||
show_names = false
|
||||
show_icons = true
|
||||
icon_theme = "Paper"
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
@@ -5,21 +5,31 @@ and playback controls.
|
||||
|
||||
in MPRIS mode, the widget will listen to all players and automatically detect/display the active one.
|
||||
|
||||

|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
> Type: `music`
|
||||
|
||||
| | Type | Default | Description |
|
||||
|----------------|------------------|-----------------------------|----------------------------------------------------------------------------------|
|
||||
| `player_type` | `mpris` or `mpd` | `mpris` | Whether to connect to MPRIS players or an MPD server. |
|
||||
| `format` | `string` | `{icon} {title} / {artist}` | Format string for the widget. More info below. |
|
||||
| `icons.play` | `string` | `` | Icon to show when playing. |
|
||||
| `icons.pause` | `string` | `` | Icon to show when paused. |
|
||||
| `icons.volume` | `string` | `墳` | Icon to show under popup volume slider. |
|
||||
| `host` | `string` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. |
|
||||
| `music_dir` | `string` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. |
|
||||
| | Type | Default | Description |
|
||||
|-------------------|---------------------------------------|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `player_type` | `mpris` or `mpd` | `mpris` | Whether to connect to MPRIS players or an MPD server. |
|
||||
| `format` | `string` | `{icon} {title} / {artist}` | Format string for the widget. More info below. |
|
||||
| `truncate` | `start` or `middle` or `end` or `Map` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. Use the long-hand `Map` version if specifying a length. |
|
||||
| `truncate.mode` | `start` or `middle` or `end` | `null` | The location of the ellipses and where to truncate text from. Leave null to avoid truncating. |
|
||||
| `truncate.length` | `integer` | `null` | The maximum number of characters before truncating. Leave blank to let GTK automatically handle. |
|
||||
| `icons.play` | `string/image` | `` | Icon to show when playing. |
|
||||
| `icons.pause` | `string/image` | `` | Icon to show when paused. |
|
||||
| `icons.prev` | `string/image` | `玲` | Icon to show on previous button. |
|
||||
| `icons.next` | `string/image` | `怜` | Icon to show on next button. |
|
||||
| `icons.volume` | `string/image` | `墳` | Icon to show under popup volume slider. |
|
||||
| `icons.track` | `string/image` | `` | Icon to show next to track title. |
|
||||
| `icons.album` | `string/image` | `` | Icon to show next to album name. |
|
||||
| `icons.artist` | `string/image` | `ﴁ` | Icon to show next to artist name. |
|
||||
| `host` | `string/image` | `localhost:6600` | [MPD Only] TCP or Unix socket for the MPD server. |
|
||||
| `music_dir` | `string/image` | `$HOME/Music` | [MPD Only] Path to MPD server's music directory on disc. Required for album art. |
|
||||
|
||||
See [here](images) for information on images.
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
@@ -30,7 +40,8 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
|
||||
{
|
||||
"type": "music",
|
||||
"player_type": "mpd",
|
||||
"format": "{icon} {title} / {artist}",
|
||||
"format": "{title} / {artist}",
|
||||
"truncate": "end",
|
||||
"icons": {
|
||||
"play": "",
|
||||
"pause": ""
|
||||
@@ -50,8 +61,9 @@ in MPRIS mode, the widget will listen to all players and automatically detect/di
|
||||
[[start]]
|
||||
type = "music"
|
||||
player_type = "mpd"
|
||||
format = "{icon} {title} / {artist}"
|
||||
format = "{title} / {artist}"
|
||||
music_dir = "/home/jake/Music"
|
||||
truncate = "end"
|
||||
|
||||
[[start.icons]]
|
||||
play = ""
|
||||
@@ -67,7 +79,8 @@ pause = ""
|
||||
start:
|
||||
- type: "music"
|
||||
player_type: "mpd"
|
||||
format: "{icon} {title} / {artist}"
|
||||
format: "{title} / {artist}"
|
||||
truncate: "end"
|
||||
icons:
|
||||
play: ""
|
||||
pause: ""
|
||||
@@ -85,7 +98,8 @@ start:
|
||||
{
|
||||
type = "music"
|
||||
player_type = "mpd"
|
||||
format = "{icon} {title} / {artist}"
|
||||
format = "{title} / {artist}"
|
||||
truncate = "end"
|
||||
icons.play = ""
|
||||
icons.pause = ""
|
||||
music_dir = "/home/jake/Music"
|
||||
@@ -103,7 +117,6 @@ and will be replaced with values from the currently playing track:
|
||||
|
||||
| Token | Description |
|
||||
|--------------|--------------------------------------|
|
||||
| `{icon}` | Either `icons.play` or `icons.pause` |
|
||||
| `{title}` | Title |
|
||||
| `{album}` | Album name |
|
||||
| `{artist}` | Artist name |
|
||||
@@ -116,24 +129,25 @@ and will be replaced with values from the currently playing track:
|
||||
|
||||
## Styling
|
||||
|
||||
| Selector | Description |
|
||||
|------------------------------------------|------------------------------------------|
|
||||
| `#music` | Tray widget button |
|
||||
| `#popup-music` | Popup box |
|
||||
| `#popup-music #album-art` | Album art image inside popup box |
|
||||
| `#popup-music #title` | Track title container inside popup box |
|
||||
| `#popup-music #title .icon` | Track title icon label inside popup box |
|
||||
| `#popup-music #title .label` | Track title label inside popup box |
|
||||
| `#popup-music #album` | Track album container inside popup box |
|
||||
| `#popup-music #album .icon` | Track album icon label inside popup box |
|
||||
| `#popup-music #album .label` | Track album label inside popup box |
|
||||
| `#popup-music #artist` | Track artist container inside popup box |
|
||||
| `#popup-music #artist .icon` | Track artist icon label inside popup box |
|
||||
| `#popup-music #artist .label` | Track artist label inside popup box |
|
||||
| `#popup-music #controls` | Controls container inside popup box |
|
||||
| `#popup-music #controls #btn-prev` | Previous button inside popup box |
|
||||
| `#popup-music #controls #btn-play-pause` | Play/pause button inside popup box |
|
||||
| `#popup-music #controls #btn-next` | Next button inside popup box |
|
||||
| `#popup-music #volume` | Volume container inside popup box |
|
||||
| `#popup-music #volume #slider` | Volume slider popup box |
|
||||
| `#popup-music #volume .icon` | Volume icon label inside popup box |
|
||||
| Selector | Description |
|
||||
|-------------------------------------|------------------------------------------|
|
||||
| `#music` | Tray widget button |
|
||||
| `#popup-music` | Popup box |
|
||||
| `#popup-music #album-art` | Album art image inside popup box |
|
||||
| `#popup-music #title` | Track title container inside popup box |
|
||||
| `#popup-music #title .icon` | Track title icon label inside popup box |
|
||||
| `#popup-music #title .label` | Track title label inside popup box |
|
||||
| `#popup-music #album` | Track album container inside popup box |
|
||||
| `#popup-music #album .icon` | Track album icon label inside popup box |
|
||||
| `#popup-music #album .label` | Track album label inside popup box |
|
||||
| `#popup-music #artist` | Track artist container inside popup box |
|
||||
| `#popup-music #artist .icon` | Track artist icon label inside popup box |
|
||||
| `#popup-music #artist .label` | Track artist label inside popup box |
|
||||
| `#popup-music #controls` | Controls container inside popup box |
|
||||
| `#popup-music #controls #btn-prev` | Previous button inside popup box |
|
||||
| `#popup-music #controls #btn-play` | Play button inside popup box |
|
||||
| `#popup-music #controls #btn-pause` | Pause button inside popup box |
|
||||
| `#popup-music #controls #btn-next` | Next button inside popup box |
|
||||
| `#popup-music #volume` | Volume container inside popup box |
|
||||
| `#popup-music #volume #slider` | Volume slider popup box |
|
||||
| `#popup-music #volume .icon` | Volume icon label inside popup box |
|
||||
@@ -8,11 +8,11 @@ Shows all current workspaces. Clicking a workspace changes focus to it.
|
||||
|
||||
> Type: `workspaces`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|----------------|---------------------------|----------------|----------------------------------------------------------------------------------------------------------------------|
|
||||
| `name_map` | `Map<string, string>` | `{}` | A map of actual workspace names to their display labels. Workspaces use their actual name if not present in the map. |
|
||||
| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. |
|
||||
| `sort` | `added` or `alphanumeric` | `alphanumeric` | The method used for sorting workspaces. `added` always appends to the end, `alphanumeric` sorts by number/name. |
|
||||
| Name | Type | Default | Description |
|
||||
|----------------|-----------------------------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `name_map` | `Map<string, string/image>` | `{}` | A map of actual workspace names to their display labels/images. Workspaces use their actual name if not present in the map. See [here](images) for information on images. |
|
||||
| `all_monitors` | `boolean` | `false` | Whether to display workspaces from all monitors. When `false`, only shows workspaces on the current monitor. |
|
||||
| `sort` | `added` or `alphanumeric` | `alphanumeric` | The method used for sorting workspaces. `added` always appends to the end, `alphanumeric` sorts by number/name. |
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
@@ -72,15 +72,15 @@ end:
|
||||
|
||||
```corn
|
||||
{
|
||||
end = [
|
||||
{
|
||||
type = "workspaces",
|
||||
name_map.1 = ""
|
||||
name_map.2 = ""
|
||||
name_map.3 = ""
|
||||
all_monitors = false
|
||||
}
|
||||
]
|
||||
end = [
|
||||
{
|
||||
type = "workspaces",
|
||||
name_map.1 = ""
|
||||
name_map.2 = ""
|
||||
name_map.3 = ""
|
||||
all_monitors = false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,19 +4,20 @@ let {
|
||||
all_monitors = false
|
||||
name_map = {
|
||||
1 = "ﭮ"
|
||||
2 = ""
|
||||
2 = "icon:firefox"
|
||||
3 = ""
|
||||
Games = ""
|
||||
Games = "icon:steam"
|
||||
Code = ""
|
||||
}
|
||||
}
|
||||
|
||||
$focused = { type = "focused" }
|
||||
|
||||
$launcher = {
|
||||
type = "launcher"
|
||||
favorites = ["firefox" "discord" "Steam"]
|
||||
show_names = false
|
||||
show_icons = true
|
||||
icon_theme = "Paper"
|
||||
}
|
||||
|
||||
$mpd_local = { type = "mpd" music_dir = "/home/jake/Music" }
|
||||
@@ -24,52 +25,65 @@ let {
|
||||
|
||||
$sys_info = {
|
||||
type = "sys_info"
|
||||
format = ["{cpu_percent}% " "{memory_percent}% "]
|
||||
|
||||
interval.memory = 30
|
||||
interval.cpu = 1
|
||||
interval.temps = 5
|
||||
interval.disks = 300
|
||||
interval.networks = 3
|
||||
|
||||
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}"
|
||||
]
|
||||
}
|
||||
|
||||
$tray = { type = "tray" }
|
||||
$clock = {
|
||||
type = "clock"
|
||||
// show-if = "500:[ $(($(date +%s) % 2)) -eq 0 ]"
|
||||
show_if.cmd = "exit 0"
|
||||
show_if.interval = 500
|
||||
}
|
||||
|
||||
$clock = { type = "clock" }
|
||||
|
||||
$phone_battery = {
|
||||
type = "script"
|
||||
cmd = "/home/jake/bin/phone-battery"
|
||||
|
||||
show_if.cmd = "/home/jake/bin/phone-connected"
|
||||
show_if.interval = 500
|
||||
}
|
||||
|
||||
$log_tail = {
|
||||
type = "script"
|
||||
path = "tail -f /home/jake/.local/share/ironbar/error.log"
|
||||
mode = "watch"
|
||||
// -- begin custom --
|
||||
$button = { type = "button" name="power-btn" label = "" on_click = "popup:toggle" }
|
||||
|
||||
$popup = {
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
widgets = [
|
||||
{ type = "label" name = "header" label = "Power menu" }
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "button" 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 = "label" name = "uptime" label = "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}" }
|
||||
]
|
||||
}
|
||||
|
||||
$power_menu = {
|
||||
type = "custom"
|
||||
class = "power-menu"
|
||||
|
||||
bar = [ { type = "button" name="power-btn" label = "" on_click = "popup:toggle" } ]
|
||||
|
||||
popup = [ {
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
widgets = [
|
||||
{ type = "label" name = "header" label = "Power menu" }
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "button" 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 = "label" name = "uptime" label = "Up: {{30000:uptime -p | cut -d ' ' -f2-}}" }
|
||||
]
|
||||
} ]
|
||||
bar = [ $button ]
|
||||
popup = [ $popup ]
|
||||
|
||||
tooltip = "Up: {{30000:uptime -p | cut -d ' ' -f2-}}"
|
||||
}
|
||||
// -- end custom --
|
||||
|
||||
$left = [ $workspaces $launcher ]
|
||||
$right = [ $mpd_local $mpd_server $phone_battery $sys_info $power_menu $clock ]
|
||||
|
||||
@@ -1,43 +1,133 @@
|
||||
{
|
||||
"anchor_to_edges": true,
|
||||
"end": [
|
||||
{
|
||||
"music_dir": "/home/jake/Music",
|
||||
"player_type": "mpd",
|
||||
"truncate": {
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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-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": [
|
||||
{
|
||||
"bar": [
|
||||
{
|
||||
"size": 32,
|
||||
"src": "file:///path/to/image.jpg",
|
||||
"type": "image"
|
||||
}
|
||||
],
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"all_monitors": false,
|
||||
"name_map": {
|
||||
"1": "ﭮ",
|
||||
"2": "icon:firefox",
|
||||
"3": "",
|
||||
"Code": "",
|
||||
"Games": "icon:steam"
|
||||
},
|
||||
"type": "workspaces"
|
||||
},
|
||||
{
|
||||
"type": "launcher",
|
||||
"icon_theme": "Paper",
|
||||
"favorites": [
|
||||
"firefox",
|
||||
"discord",
|
||||
"Steam"
|
||||
],
|
||||
"show_names": false
|
||||
}
|
||||
],
|
||||
"end": [
|
||||
{
|
||||
"type": "mpd"
|
||||
},
|
||||
{
|
||||
"type": "mpd",
|
||||
"host": "chloe:6600"
|
||||
},
|
||||
{
|
||||
"path": "/home/jake/bin/phone-battery",
|
||||
"type": "script"
|
||||
},
|
||||
{
|
||||
"format": [
|
||||
"{cpu_percent}% ",
|
||||
"{memory_percent}% "
|
||||
],
|
||||
"type": "sys_info"
|
||||
},
|
||||
{
|
||||
"type": "tray"
|
||||
},
|
||||
{
|
||||
"type": "clock"
|
||||
"show_icons": true,
|
||||
"show_names": false,
|
||||
"type": "launcher"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
118
examples/config.toml
Normal file
118
examples/config.toml
Normal file
@@ -0,0 +1,118 @@
|
||||
anchor_to_edges = true
|
||||
icon_theme = 'Paper'
|
||||
position = 'bottom'
|
||||
|
||||
[[end]]
|
||||
music_dir = '/home/jake/Music'
|
||||
player_type = 'mpd'
|
||||
type = 'music'
|
||||
|
||||
[end.truncate]
|
||||
length = 100
|
||||
mode = 'end'
|
||||
|
||||
[[end]]
|
||||
host = 'chloe:6600'
|
||||
player_type = 'mpd'
|
||||
truncate = 'end'
|
||||
type = 'music'
|
||||
|
||||
[[end]]
|
||||
cmd = '/home/jake/bin/phone-battery'
|
||||
type = 'script'
|
||||
|
||||
[end.show_if]
|
||||
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}',
|
||||
]
|
||||
|
||||
[end.interval]
|
||||
cpu = 1
|
||||
disks = 300
|
||||
memory = 30
|
||||
networks = 3
|
||||
temps = 5
|
||||
|
||||
[[end]]
|
||||
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'
|
||||
|
||||
[[end.popup]]
|
||||
orientation = 'vertical'
|
||||
type = 'box'
|
||||
|
||||
[[end.popup.widgets]]
|
||||
label = 'Power menu'
|
||||
name = 'header'
|
||||
type = 'label'
|
||||
|
||||
[[end.popup.widgets]]
|
||||
type = 'box'
|
||||
|
||||
[[end.popup.widgets.widgets]]
|
||||
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'
|
||||
|
||||
[[end.popup.widgets]]
|
||||
label = '''Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}'''
|
||||
name = 'uptime'
|
||||
type = 'label'
|
||||
|
||||
[[end]]
|
||||
type = 'clock'
|
||||
|
||||
[[start]]
|
||||
type = 'custom'
|
||||
|
||||
[[start.bar]]
|
||||
size = 32
|
||||
src = 'file:///path/to/image.jpg'
|
||||
type = 'image'
|
||||
|
||||
[[start]]
|
||||
all_monitors = false
|
||||
type = 'workspaces'
|
||||
|
||||
[start.name_map]
|
||||
1 = 'ﭮ'
|
||||
2 = 'icon:firefox'
|
||||
3 = ''
|
||||
Code = ''
|
||||
Games = 'icon:steam'
|
||||
|
||||
[[start]]
|
||||
show_icons = true
|
||||
show_names = false
|
||||
type = 'launcher'
|
||||
favorites = [
|
||||
'firefox',
|
||||
'discord',
|
||||
'Steam',
|
||||
]
|
||||
|
||||
97
examples/config.yaml
Normal file
97
examples/config.yaml
Normal file
@@ -0,0 +1,97 @@
|
||||
anchor_to_edges: true
|
||||
icon_theme: Paper
|
||||
position: bottom
|
||||
|
||||
start:
|
||||
- bar:
|
||||
- size: 32
|
||||
src: file:///path/to/image.jpg
|
||||
type: image
|
||||
type: custom
|
||||
|
||||
- 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
|
||||
|
||||
end:
|
||||
- music_dir: /home/jake/Music
|
||||
player_type: mpd
|
||||
truncate:
|
||||
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
|
||||
|
||||
- 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-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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
let {
|
||||
$button = { type = "button" name="power-btn" label = "" on_click = "popup:toggle" }
|
||||
|
||||
$popup = {
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
widgets = [
|
||||
{ type = "label" name = "header" label = "Power menu" }
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "button" 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 = "label" name = "uptime" label = "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}" }
|
||||
]
|
||||
}
|
||||
|
||||
$power_menu = {
|
||||
type = "custom"
|
||||
class = "power-menu"
|
||||
|
||||
bar = [ $button ]
|
||||
popup = [ $popup ]
|
||||
}
|
||||
} in {
|
||||
end = [ $power_menu { type = "clock" } ]
|
||||
}
|
||||
@@ -1,31 +1,19 @@
|
||||
* {
|
||||
/* `otf-font-awesome` is required to be installed for icons */
|
||||
font-family: Noto Sans Nerd Font, sans-serif;
|
||||
/* font-family: 'Jetbrains Mono', monospace;*/
|
||||
font-size: 16px;
|
||||
|
||||
/*color: white;*/
|
||||
/*background-color: #2d2d2d;*/
|
||||
/*background-color: red;*/
|
||||
border: none;
|
||||
|
||||
/*opacity: 0.4;*/
|
||||
}
|
||||
|
||||
#bar {
|
||||
border-top: 1px solid #424242;
|
||||
}
|
||||
|
||||
.container {
|
||||
.background, .container {
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
/* test 34543*/
|
||||
|
||||
#right > * + * {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
#workspaces .item {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
@@ -57,7 +45,7 @@
|
||||
|
||||
#launcher .focused {
|
||||
color: white;
|
||||
background-color: black;
|
||||
background-color: #1c1c1c;
|
||||
border-bottom: 4px solid #6699cc;
|
||||
}
|
||||
|
||||
@@ -66,25 +54,54 @@
|
||||
background-color: #8f0a0a;
|
||||
}
|
||||
|
||||
#popup-launcher .popup-item {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
#popup-launcher .popup-item:hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
#popup-launcher .popup-item:not(:first-child) {
|
||||
border-top: 1px solid white;
|
||||
}
|
||||
|
||||
#clock {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#clock:hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
#script {
|
||||
padding-left: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#sysinfo {
|
||||
margin-left: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#sysinfo #item {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
#tray {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#tray .item {
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
#mpd {
|
||||
#music {
|
||||
background-color: #2d2d2d;
|
||||
color: white;
|
||||
}
|
||||
@@ -119,30 +136,77 @@
|
||||
background-color: #6699cc;
|
||||
}
|
||||
|
||||
#popup-mpd {
|
||||
#music:hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
#popup-music {
|
||||
color: white;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
#popup-mpd #album-art {
|
||||
/*border: 1px solid #424242;*/
|
||||
#popup-music #album-art {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#popup-mpd #title .icon, #popup-mpd #title .label {
|
||||
#popup-music #title .icon *, #popup-music #title .label {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
#popup-mpd #controls * {
|
||||
#popup-music #controls * {
|
||||
border-radius: 0;
|
||||
background-color: #2d2d2d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#popup-mpd #controls *:disabled {
|
||||
#popup-music #controls *:disabled {
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
#popup-music #volume > box:last-child label {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
#focused {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.power-menu {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.power-menu #power-btn {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
|
||||
.power-menu #power-btn:hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
.popup-power-menu {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.popup-power-menu #header {
|
||||
color: white;
|
||||
font-size: 1.4em;
|
||||
border-bottom: 1px solid white;
|
||||
padding-bottom: 0.4em;
|
||||
margin-bottom: 0.8em;
|
||||
}
|
||||
|
||||
.popup-power-menu .power-btn {
|
||||
color: white;
|
||||
background-color: #2d2d2d;
|
||||
border: 1px solid white;
|
||||
padding: 0.6em 1em;
|
||||
}
|
||||
|
||||
.popup-power-menu .power-btn + .power-btn {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.popup-power-menu .power-btn:hover {
|
||||
background-color: #1c1c1c;
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
end = [
|
||||
{
|
||||
type = "sys_info"
|
||||
|
||||
interval.memory = 30
|
||||
interval.cpu = 1
|
||||
interval.temps = 5
|
||||
interval.disks = 300
|
||||
interval.networks = 3
|
||||
|
||||
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}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
12
flake.lock
generated
12
flake.lock
generated
@@ -17,11 +17,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1672350804,
|
||||
"narHash": "sha256-jo6zkiCabUBn3ObuKXHGqqORUMH27gYDIFFfLq5P4wg=",
|
||||
"lastModified": 1675115703,
|
||||
"narHash": "sha256-4zetAPSyY0D77x+Ww9QBe8RHn1akvIvHJ/kgg8kGDbk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "677ed08a50931e38382dbef01cba08a8f7eac8f6",
|
||||
"rev": "2caf4ef5005ecc68141ecb4aac271079f7371c44",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -45,11 +45,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1672453260,
|
||||
"narHash": "sha256-ruR2xo30Vn7kY2hAgg2Z2xrCvNePxck6mgR5a8u+zow=",
|
||||
"lastModified": 1675132198,
|
||||
"narHash": "sha256-izOVjdIfdv0OzcfO9rXX0lfGkQn4tdJ0eNm3P3LYo/o=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "176b6fd3dd3d7cea8d22ab1131364a050228d94c",
|
||||
"rev": "48b1403150c3f5a9aeee8bc4c77c8926f29c6501",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
cargoDeps = rustPlatform.importCargoLock {lockFile = ./Cargo.lock;};
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
nativeBuildInputs = with prev; [pkg-config];
|
||||
buildInputs = with prev; [gtk3 gdk-pixbuf gtk-layer-shell libxkbcommon];
|
||||
buildInputs = with prev; [gtk3 gdk-pixbuf gtk-layer-shell libxkbcommon openssl];
|
||||
};
|
||||
};
|
||||
packages = genSystems (
|
||||
@@ -74,6 +74,7 @@
|
||||
gtk3
|
||||
gtk-layer-shell
|
||||
pkg-config
|
||||
openssl
|
||||
];
|
||||
|
||||
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||
|
||||
27
src/bar.rs
27
src/bar.rs
@@ -8,7 +8,7 @@ use crate::{await_sync, read_lock, send, write_lock, Config};
|
||||
use color_eyre::Result;
|
||||
use gtk::gdk::{EventMask, Monitor, ScrollDirection};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Application, ApplicationWindow, EventBox, Orientation, Widget};
|
||||
use gtk::{Application, ApplicationWindow, EventBox, IconTheme, Orientation, Widget};
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc;
|
||||
@@ -140,6 +140,11 @@ fn load_modules(
|
||||
monitor: &Monitor,
|
||||
output_name: &str,
|
||||
) -> Result<()> {
|
||||
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 {
|
||||
@@ -148,6 +153,7 @@ fn load_modules(
|
||||
monitor,
|
||||
output_name,
|
||||
location: $location,
|
||||
icon_theme: &icon_theme,
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -189,15 +195,20 @@ fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: &ModuleInfo
|
||||
|
||||
for (id, config) in modules.into_iter().enumerate() {
|
||||
match config {
|
||||
#[cfg(feature = "clock")]
|
||||
ModuleConfig::Clock(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Script(mut module) => add_module!(module, id),
|
||||
ModuleConfig::SysInfo(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Focused(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Workspaces(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Tray(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Music(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Launcher(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Custom(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Focused(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Launcher(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "music")]
|
||||
ModuleConfig::Music(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Script(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "sys_info")]
|
||||
ModuleConfig::SysInfo(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "tray")]
|
||||
ModuleConfig::Tray(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "workspaces")]
|
||||
ModuleConfig::Workspaces(mut module) => add_module!(module, id),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::{Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||
use crate::error::{ERR_CHANNEL_SEND, ERR_MUTEX_LOCK};
|
||||
use crate::{lock, send};
|
||||
use color_eyre::Result;
|
||||
use hyprland::data::{Workspace as HWorkspace, Workspaces};
|
||||
use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial};
|
||||
use hyprland::event_listener::EventListenerMutable as EventListener;
|
||||
@@ -9,10 +10,9 @@ use lazy_static::lazy_static;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||
use tokio::task::spawn_blocking;
|
||||
use tracing::{error, info};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub struct EventClient {
|
||||
workspaces: Arc<Mutex<Vec<Workspace>>>,
|
||||
workspace_tx: Sender<WorkspaceUpdate>,
|
||||
_workspace_rx: Receiver<WorkspaceUpdate>,
|
||||
}
|
||||
@@ -21,12 +21,7 @@ impl EventClient {
|
||||
fn new() -> Self {
|
||||
let (workspace_tx, workspace_rx) = channel(16);
|
||||
|
||||
let workspaces = Arc::new(Mutex::new(vec![]));
|
||||
// load initial list
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
|
||||
Self {
|
||||
workspaces,
|
||||
workspace_tx,
|
||||
_workspace_rx: workspace_rx,
|
||||
}
|
||||
@@ -35,164 +30,198 @@ impl EventClient {
|
||||
fn listen_workspace_events(&self) {
|
||||
info!("Starting Hyprland event listener");
|
||||
|
||||
let workspaces = self.workspaces.clone();
|
||||
let tx = self.workspace_tx.clone();
|
||||
|
||||
spawn_blocking(move || {
|
||||
let mut event_listener = EventListener::new();
|
||||
|
||||
// we need a lock to ensure events don't run at the same time
|
||||
let lock = Arc::new(Mutex::new(()));
|
||||
|
||||
// cache the active workspace since Hyprland doesn't give us the prev active
|
||||
let active = Self::get_active_workspace().expect("Failed to get active workspace");
|
||||
let active = Arc::new(Mutex::new(Some(active)));
|
||||
|
||||
{
|
||||
let workspaces = workspaces.clone();
|
||||
let tx = tx.clone();
|
||||
let lock = lock.clone();
|
||||
let active = active.clone();
|
||||
|
||||
event_listener.add_workspace_added_handler(move |workspace_type, _state| {
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
let _lock = lock!(lock);
|
||||
debug!("Added workspace: {workspace_type:?}");
|
||||
|
||||
let workspace = Self::get_workspace(&workspaces, workspace_type);
|
||||
workspace.map_or_else(
|
||||
|| error!("Unable to locate workspace"),
|
||||
|workspace| {
|
||||
tx.send(WorkspaceUpdate::Add(workspace))
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
},
|
||||
);
|
||||
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);
|
||||
|
||||
if let Some(workspace) = workspace {
|
||||
send!(tx, WorkspaceUpdate::Add(workspace));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let workspaces = workspaces.clone();
|
||||
let tx = tx.clone();
|
||||
let lock = lock.clone();
|
||||
let active = active.clone();
|
||||
|
||||
event_listener.add_workspace_change_handler(move |workspace_type, _state| {
|
||||
let prev_workspace = Self::get_focused_workspace(&workspaces);
|
||||
let _lock = lock!(lock);
|
||||
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
let mut prev_workspace = lock!(active);
|
||||
|
||||
let workspace = Self::get_workspace(&workspaces, workspace_type);
|
||||
|
||||
if let (Some(prev_workspace), Some(workspace)) = (prev_workspace, workspace) {
|
||||
if prev_workspace.id != workspace.id {
|
||||
tx.send(WorkspaceUpdate::Focus {
|
||||
old: prev_workspace,
|
||||
new: workspace.clone(),
|
||||
})
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
}
|
||||
|
||||
// there may be another type of update so dispatch that regardless of focus change
|
||||
tx.send(WorkspaceUpdate::Update(workspace))
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
} else {
|
||||
error!("Unable to locate workspace");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let workspaces = workspaces.clone();
|
||||
let tx = tx.clone();
|
||||
|
||||
event_listener.add_workspace_destroy_handler(move |workspace_type, _state| {
|
||||
let workspace = Self::get_workspace(&workspaces, workspace_type);
|
||||
workspace.map_or_else(
|
||||
|| error!("Unable to locate workspace"),
|
||||
|workspace| {
|
||||
tx.send(WorkspaceUpdate::Remove(workspace))
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
},
|
||||
debug!(
|
||||
"Received workspace change: {:?} -> {workspace_type:?}",
|
||||
prev_workspace.as_ref().map(|w| &w.id)
|
||||
);
|
||||
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
});
|
||||
}
|
||||
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 workspaces = workspaces.clone();
|
||||
let tx = tx.clone();
|
||||
|
||||
event_listener.add_workspace_moved_handler(move |event_data, _state| {
|
||||
let workspace_type = event_data.1;
|
||||
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
|
||||
let workspace = Self::get_workspace(&workspaces, workspace_type);
|
||||
workspace.map_or_else(
|
||||
|| error!("Unable to locate workspace"),
|
||||
|| {
|
||||
error!("Unable to locate workspace");
|
||||
},
|
||||
|workspace| {
|
||||
tx.send(WorkspaceUpdate::Move(workspace))
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
// there may be another type of update so dispatch that regardless of focus change
|
||||
send!(tx, WorkspaceUpdate::Update(workspace.clone()));
|
||||
if !focused {
|
||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let workspaces = workspaces.clone();
|
||||
let tx = tx.clone();
|
||||
let lock = lock.clone();
|
||||
let active = active.clone();
|
||||
|
||||
event_listener.add_active_monitor_change_handler(move |event_data, _state| {
|
||||
let _lock = lock!(lock);
|
||||
let workspace_type = event_data.1;
|
||||
|
||||
let prev_workspace = Self::get_focused_workspace(&workspaces);
|
||||
let mut prev_workspace = lock!(active);
|
||||
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
debug!(
|
||||
"Received active monitor change: {:?} -> {workspace_type:?}",
|
||||
prev_workspace.as_ref().map(|w| &w.name)
|
||||
);
|
||||
|
||||
let workspace = Self::get_workspace(&workspaces, workspace_type);
|
||||
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);
|
||||
|
||||
if let (Some(prev_workspace), Some(workspace)) = (prev_workspace, workspace) {
|
||||
if prev_workspace.id != workspace.id {
|
||||
tx.send(WorkspaceUpdate::Focus {
|
||||
old: prev_workspace,
|
||||
new: workspace,
|
||||
})
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
}
|
||||
if let (Some(workspace), false) = (workspace, focused) {
|
||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||
} else {
|
||||
error!("Unable to locate workspace");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let tx = tx.clone();
|
||||
let lock = lock.clone();
|
||||
|
||||
event_listener.add_workspace_moved_handler(move |event_data, _state| {
|
||||
let _lock = lock!(lock);
|
||||
let workspace_type = event_data.1;
|
||||
debug!("Received workspace move: {workspace_type:?}");
|
||||
|
||||
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);
|
||||
|
||||
if let Some(workspace) = workspace {
|
||||
send!(tx, WorkspaceUpdate::Move(workspace.clone()));
|
||||
|
||||
if !focused {
|
||||
Self::send_focus_change(&mut prev_workspace, workspace, &tx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
event_listener.add_workspace_destroy_handler(move |workspace_type, _state| {
|
||||
let _lock = lock!(lock);
|
||||
debug!("Received workspace destroy: {workspace_type:?}");
|
||||
|
||||
let name = get_workspace_name(workspace_type);
|
||||
send!(tx, WorkspaceUpdate::Remove(name));
|
||||
});
|
||||
}
|
||||
|
||||
event_listener
|
||||
.start_listener()
|
||||
.expect("Failed to start listener");
|
||||
});
|
||||
}
|
||||
|
||||
fn refresh_workspaces(workspaces: &Mutex<Vec<Workspace>>) {
|
||||
let mut workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK);
|
||||
/// Sends a `WorkspaceUpdate::Focus` event
|
||||
/// and updates the active workspace cache.
|
||||
fn send_focus_change(
|
||||
prev_workspace: &mut Option<Workspace>,
|
||||
workspace: Workspace,
|
||||
tx: &Sender<WorkspaceUpdate>,
|
||||
) {
|
||||
let old = prev_workspace
|
||||
.as_ref()
|
||||
.map(|w| w.name.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let active = HWorkspace::get_active().expect("Failed to get active workspace");
|
||||
let new_workspaces = Workspaces::get()
|
||||
send!(
|
||||
tx,
|
||||
WorkspaceUpdate::Focus {
|
||||
old,
|
||||
new: workspace.name.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> {
|
||||
Workspaces::get()
|
||||
.expect("Failed to get workspaces")
|
||||
.collect()
|
||||
.into_iter()
|
||||
.map(|workspace| Workspace::from((workspace.id == active.id, workspace)));
|
||||
|
||||
workspaces.clear();
|
||||
workspaces.extend(new_workspaces);
|
||||
.find_map(|w| {
|
||||
if w.name == name {
|
||||
Some(Workspace::from((focused, w)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn get_workspace(workspaces: &Mutex<Vec<Workspace>>, id: WorkspaceType) -> Option<Workspace> {
|
||||
let id_string = id_to_string(id);
|
||||
|
||||
let workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK);
|
||||
workspaces
|
||||
.iter()
|
||||
.find(|workspace| workspace.id == id_string)
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn get_focused_workspace(workspaces: &Mutex<Vec<Workspace>>) -> Option<Workspace> {
|
||||
let workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK);
|
||||
workspaces
|
||||
.iter()
|
||||
.find(|workspace| workspace.focused)
|
||||
.cloned()
|
||||
/// Gets the active workspace from the server.
|
||||
fn get_active_workspace() -> Result<Workspace> {
|
||||
let w = HWorkspace::get_active().map(|w| Workspace::from((true, w)))?;
|
||||
Ok(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkspaceClient for EventClient {
|
||||
fn focus(&self, id: String) -> color_eyre::Result<()> {
|
||||
fn focus(&self, id: String) -> Result<()> {
|
||||
Dispatch::call(DispatchType::Workspace(
|
||||
WorkspaceIdentifierWithSpecial::Name(&id),
|
||||
))?;
|
||||
@@ -205,13 +234,16 @@ impl WorkspaceClient for EventClient {
|
||||
{
|
||||
let tx = self.workspace_tx.clone();
|
||||
|
||||
let workspaces = self.workspaces.clone();
|
||||
Self::refresh_workspaces(&workspaces);
|
||||
let active_name = HWorkspace::get_active()
|
||||
.map(|active| active.name)
|
||||
.unwrap_or_default();
|
||||
|
||||
let workspaces = workspaces.lock().expect(ERR_MUTEX_LOCK);
|
||||
let workspaces = Workspaces::get()
|
||||
.expect("Failed to get workspaces")
|
||||
.map(|w| Workspace::from((w.name == active_name, w)))
|
||||
.collect();
|
||||
|
||||
tx.send(WorkspaceUpdate::Init(workspaces.clone()))
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
send!(tx, WorkspaceUpdate::Init(workspaces));
|
||||
}
|
||||
|
||||
rx
|
||||
@@ -230,10 +262,9 @@ pub fn get_client() -> &'static EventClient {
|
||||
&CLIENT
|
||||
}
|
||||
|
||||
fn id_to_string(id: WorkspaceType) -> String {
|
||||
match id {
|
||||
WorkspaceType::Unnamed(id) => id.to_string(),
|
||||
WorkspaceType::Named(name) => name,
|
||||
fn get_workspace_name(name: WorkspaceType) -> String {
|
||||
match name {
|
||||
WorkspaceType::Regular(name) => name,
|
||||
WorkspaceType::Special(name) => name.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
@@ -241,7 +272,7 @@ fn id_to_string(id: WorkspaceType) -> String {
|
||||
impl From<(bool, hyprland::data::Workspace)> for Workspace {
|
||||
fn from((focused, workspace): (bool, hyprland::data::Workspace)) -> Self {
|
||||
Self {
|
||||
id: id_to_string(workspace.id),
|
||||
id: workspace.id.to_string(),
|
||||
name: workspace.name,
|
||||
monitor: workspace.monitor,
|
||||
focused,
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::debug;
|
||||
|
||||
#[cfg(feature = "workspaces+hyprland")]
|
||||
pub mod hyprland;
|
||||
#[cfg(feature = "workspaces+sway")]
|
||||
pub mod sway;
|
||||
|
||||
pub enum Compositor {
|
||||
#[cfg(feature = "workspaces+sway")]
|
||||
Sway,
|
||||
#[cfg(feature = "workspaces+hyprland")]
|
||||
Hyprland,
|
||||
Unsupported,
|
||||
}
|
||||
@@ -18,7 +23,9 @@ impl Display for Compositor {
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
#[cfg(feature = "workspaces+sway")]
|
||||
Self::Sway => "Sway",
|
||||
#[cfg(feature = "workspaces+hyprland")]
|
||||
Self::Hyprland => "Hyprland",
|
||||
Self::Unsupported => "Unsupported",
|
||||
}
|
||||
@@ -31,9 +38,15 @@ impl Compositor {
|
||||
/// This is done by checking system env vars.
|
||||
fn get_current() -> Self {
|
||||
if std::env::var("SWAYSOCK").is_ok() {
|
||||
Self::Sway
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "workspaces+sway")] { Self::Sway }
|
||||
else { tracing::error!("Not compiled with Sway support"); Self::Unsupported }
|
||||
}
|
||||
} else if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
|
||||
Self::Hyprland
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "workspaces+hyprland")] { Self::Hyprland}
|
||||
else { tracing::error!("Not compiled with Hyprland support"); Self::Unsupported }
|
||||
}
|
||||
} else {
|
||||
Self::Unsupported
|
||||
}
|
||||
@@ -44,7 +57,9 @@ impl Compositor {
|
||||
let current = Self::get_current();
|
||||
debug!("Getting workspace client for: {current}");
|
||||
match current {
|
||||
#[cfg(feature = "workspaces+sway")]
|
||||
Self::Sway => Ok(sway::get_sub_client()),
|
||||
#[cfg(feature = "workspaces+hyprland")]
|
||||
Self::Hyprland => Ok(hyprland::get_client()),
|
||||
Self::Unsupported => Err(Report::msg("Unsupported compositor")
|
||||
.note("Currently workspaces are only supported by Sway and Hyprland")),
|
||||
@@ -70,13 +85,13 @@ pub enum WorkspaceUpdate {
|
||||
/// This is re-sent to all subscribers when a new subscription is created.
|
||||
Init(Vec<Workspace>),
|
||||
Add(Workspace),
|
||||
Remove(Workspace),
|
||||
Remove(String),
|
||||
Update(Workspace),
|
||||
Move(Workspace),
|
||||
/// Declares focus moved from the old workspace to the new.
|
||||
Focus {
|
||||
old: Workspace,
|
||||
new: Workspace,
|
||||
old: String,
|
||||
new: String,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::{Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||
use crate::await_sync;
|
||||
use crate::error::ERR_CHANNEL_SEND;
|
||||
use crate::{await_sync, send};
|
||||
use async_once::AsyncOnce;
|
||||
use color_eyre::Report;
|
||||
use futures_util::StreamExt;
|
||||
@@ -75,7 +74,7 @@ impl WorkspaceClient for SwayEventClient {
|
||||
let event =
|
||||
WorkspaceUpdate::Init(workspaces.into_iter().map(Workspace::from).collect());
|
||||
|
||||
tx.send(event).expect(ERR_CHANNEL_SEND);
|
||||
send!(tx, event);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,12 +131,24 @@ impl From<WorkspaceEvent> for WorkspaceUpdate {
|
||||
WorkspaceChange::Init => {
|
||||
Self::Add(event.current.expect("Missing current workspace").into())
|
||||
}
|
||||
WorkspaceChange::Empty => {
|
||||
Self::Remove(event.current.expect("Missing current workspace").into())
|
||||
}
|
||||
WorkspaceChange::Empty => Self::Remove(
|
||||
event
|
||||
.current
|
||||
.expect("Missing current workspace")
|
||||
.name
|
||||
.unwrap_or_default(),
|
||||
),
|
||||
WorkspaceChange::Focus => Self::Focus {
|
||||
old: event.old.expect("Missing old workspace").into(),
|
||||
new: event.current.expect("Missing current workspace").into(),
|
||||
old: event
|
||||
.old
|
||||
.expect("Missing old workspace")
|
||||
.name
|
||||
.unwrap_or_default(),
|
||||
new: event
|
||||
.current
|
||||
.expect("Missing current workspace")
|
||||
.name
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
WorkspaceChange::Move => {
|
||||
Self::Move(event.current.expect("Missing current workspace").into())
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#[cfg(feature = "workspaces")]
|
||||
pub mod compositor;
|
||||
#[cfg(feature = "music")]
|
||||
pub mod music;
|
||||
#[cfg(feature = "tray")]
|
||||
pub mod system_tray;
|
||||
pub mod wayland;
|
||||
|
||||
@@ -4,7 +4,9 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
#[cfg(feature = "music+mpd")]
|
||||
pub mod mpd;
|
||||
#[cfg(feature = "music+mpris")]
|
||||
pub mod mpris;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -22,7 +24,7 @@ pub struct Track {
|
||||
pub disc: Option<u64>,
|
||||
pub genre: Option<String>,
|
||||
pub track: Option<u64>,
|
||||
pub cover_path: Option<PathBuf>,
|
||||
pub cover_path: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -121,12 +121,16 @@ impl MpdClient {
|
||||
fn convert_song(song: &Song, music_dir: &Path) -> Track {
|
||||
let (track, disc) = song.number();
|
||||
|
||||
let cover_path = music_dir.join(
|
||||
song.file_path()
|
||||
.parent()
|
||||
.expect("Song path should not be root")
|
||||
.join("cover.jpg"),
|
||||
);
|
||||
let cover_path = music_dir
|
||||
.join(
|
||||
song.file_path()
|
||||
.parent()
|
||||
.expect("Song path should not be root")
|
||||
.join("cover.jpg"),
|
||||
)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.ok();
|
||||
|
||||
Track {
|
||||
title: song.title().map(std::string::ToString::to_string),
|
||||
@@ -136,7 +140,7 @@ impl MpdClient {
|
||||
genre: try_get_first_tag(song, &Tag::Genre).map(std::string::ToString::to_string),
|
||||
disc: Some(disc),
|
||||
track: Some(track),
|
||||
cover_path: Some(cover_path),
|
||||
cover_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use super::{MusicClient, PlayerUpdate, Status, Track};
|
||||
use crate::clients::music::PlayerState;
|
||||
use crate::error::ERR_MUTEX_LOCK;
|
||||
use crate::{lock, send};
|
||||
use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
@@ -44,7 +43,7 @@ impl Client {
|
||||
.find_all()
|
||||
.expect("Failed to connect to D-Bus");
|
||||
|
||||
let mut players_list_val = players_list.lock().expect(ERR_MUTEX_LOCK);
|
||||
let mut players_list_val = lock!(players_list);
|
||||
for player in players {
|
||||
let identity = player.identity();
|
||||
|
||||
@@ -57,8 +56,7 @@ impl Client {
|
||||
.expect("Failed to connect to D-Bus");
|
||||
|
||||
{
|
||||
let mut current_player =
|
||||
current_player.lock().expect(ERR_MUTEX_LOCK);
|
||||
let mut current_player = lock!(current_player);
|
||||
|
||||
if status == PlaybackStatus::Playing || current_player.is_none() {
|
||||
debug!("Setting active player to '{identity}'");
|
||||
@@ -108,22 +106,19 @@ impl Client {
|
||||
trace!("Received player event from '{identity}': {event:?}");
|
||||
match event {
|
||||
Ok(Event::PlayerShutDown) => {
|
||||
current_player.lock().expect(ERR_MUTEX_LOCK).take();
|
||||
players.lock().expect(ERR_MUTEX_LOCK).remove(identity);
|
||||
lock!(current_player).take();
|
||||
lock!(players).remove(identity);
|
||||
break;
|
||||
}
|
||||
Ok(Event::Playing) => {
|
||||
current_player
|
||||
.lock()
|
||||
.expect(ERR_MUTEX_LOCK)
|
||||
.replace(identity.to_string());
|
||||
lock!(current_player).replace(identity.to_string());
|
||||
|
||||
if let Err(err) = Self::send_update(&player, &tx) {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
Ok(_) => {
|
||||
let current_player = current_player.lock().expect(ERR_MUTEX_LOCK);
|
||||
let current_player = lock!(current_player);
|
||||
let current_player = current_player.as_ref();
|
||||
if let Some(current_player) = current_player {
|
||||
if current_player == identity {
|
||||
@@ -171,15 +166,13 @@ impl Client {
|
||||
let track = Track::from(metadata);
|
||||
|
||||
let player_update = PlayerUpdate::Update(Box::new(Some(track)), status);
|
||||
|
||||
tx.send(player_update)
|
||||
.expect("Failed to send player update");
|
||||
send!(tx, player_update);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_player(&self) -> Option<Player> {
|
||||
let player_name = self.current_player.lock().expect(ERR_MUTEX_LOCK);
|
||||
let player_name = lock!(self.current_player);
|
||||
let player_name = player_name.as_ref();
|
||||
|
||||
player_name.and_then(|player_name| {
|
||||
@@ -266,10 +259,7 @@ impl From<Metadata> for Track {
|
||||
.and_then(mpris::MetadataValue::as_str_array)
|
||||
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
|
||||
track: value.track_number().map(|track| track as u64),
|
||||
cover_path: value
|
||||
.art_url()
|
||||
.map(|path| path.replace("file://", ""))
|
||||
.map(PathBuf::from),
|
||||
cover_path: value.art_url().map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,25 @@
|
||||
use crate::{lock, send};
|
||||
use async_once::AsyncOnce;
|
||||
use color_eyre::Report;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use stray::message::menu::TrayMenu;
|
||||
use stray::message::tray::StatusNotifierItem;
|
||||
use stray::message::{NotifierItemCommand, NotifierItemMessage};
|
||||
use stray::StatusNotifierWatcher;
|
||||
use tokio::spawn;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::debug;
|
||||
use tracing::error;
|
||||
|
||||
type Tray = BTreeMap<String, (Box<StatusNotifierItem>, Option<TrayMenu>)>;
|
||||
|
||||
pub struct TrayEventReceiver {
|
||||
tx: mpsc::Sender<NotifierItemCommand>,
|
||||
b_tx: broadcast::Sender<NotifierItemMessage>,
|
||||
_b_rx: broadcast::Receiver<NotifierItemMessage>,
|
||||
|
||||
tray: Arc<Mutex<Tray>>,
|
||||
}
|
||||
|
||||
impl TrayEventReceiver {
|
||||
@@ -20,19 +30,39 @@ impl TrayEventReceiver {
|
||||
let tray = StatusNotifierWatcher::new(rx).await?;
|
||||
let mut host = tray.create_notifier_host("ironbar").await?;
|
||||
|
||||
let b_tx2 = b_tx.clone();
|
||||
spawn(async move {
|
||||
while let Ok(message) = host.recv().await {
|
||||
b_tx2.send(message)?;
|
||||
}
|
||||
let tray = Arc::new(Mutex::new(BTreeMap::new()));
|
||||
|
||||
Ok::<(), broadcast::error::SendError<NotifierItemMessage>>(())
|
||||
});
|
||||
{
|
||||
let b_tx = b_tx.clone();
|
||||
let tray = tray.clone();
|
||||
|
||||
spawn(async move {
|
||||
while let Ok(message) = host.recv().await {
|
||||
send!(b_tx, message.clone());
|
||||
let mut tray = lock!(tray);
|
||||
match message {
|
||||
NotifierItemMessage::Update {
|
||||
address,
|
||||
item,
|
||||
menu,
|
||||
} => {
|
||||
tray.insert(address, (item, menu));
|
||||
}
|
||||
NotifierItemMessage::Remove { address } => {
|
||||
tray.remove(&address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), broadcast::error::SendError<NotifierItemMessage>>(())
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
tx,
|
||||
b_tx,
|
||||
_b_rx: b_rx,
|
||||
tray,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,7 +72,20 @@ impl TrayEventReceiver {
|
||||
mpsc::Sender<NotifierItemCommand>,
|
||||
broadcast::Receiver<NotifierItemMessage>,
|
||||
) {
|
||||
(self.tx.clone(), self.b_tx.subscribe())
|
||||
let tx = self.tx.clone();
|
||||
let b_rx = self.b_tx.subscribe();
|
||||
|
||||
let tray = lock!(self.tray).clone();
|
||||
for (address, (item, menu)) in tray {
|
||||
let update = NotifierItemMessage::Update {
|
||||
address,
|
||||
item,
|
||||
menu,
|
||||
};
|
||||
send!(self.b_tx, update);
|
||||
}
|
||||
|
||||
(tx, b_rx)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,11 +101,14 @@ lazy_static! {
|
||||
|
||||
let tray = TrayEventReceiver::new().await;
|
||||
|
||||
if tray.is_ok() || retries == MAX_RETRIES {
|
||||
break tray;
|
||||
match tray {
|
||||
Ok(tray) => break Some(tray),
|
||||
Err(err) => error!("{:?}", Report::new(err).wrap_err(format!("Failed to create StatusNotifierWatcher (attempt {retries})")))
|
||||
}
|
||||
|
||||
debug!("Failed to create StatusNotifierWatcher (attempt {retries})");
|
||||
if retries == MAX_RETRIES {
|
||||
break None;
|
||||
}
|
||||
};
|
||||
|
||||
value.expect("Failed to create StatusNotifierWatcher")
|
||||
|
||||
@@ -123,6 +123,9 @@ impl Config {
|
||||
/// and parses it into `Self` based on its extension.
|
||||
fn load_file(path: &Path) -> Result<Self> {
|
||||
let file = fs::read(path).wrap_err("Failed to read config file")?;
|
||||
|
||||
let str = std::str::from_utf8(&file)?;
|
||||
|
||||
let extension = path
|
||||
.extension()
|
||||
.unwrap_or_default()
|
||||
@@ -130,11 +133,16 @@ impl Config {
|
||||
.unwrap_or_default();
|
||||
|
||||
match extension {
|
||||
"json" => serde_json::from_slice(&file).wrap_err("Invalid JSON config"),
|
||||
"toml" => toml::from_slice(&file).wrap_err("Invalid TOML config"),
|
||||
"yaml" | "yml" => serde_yaml::from_slice(&file).wrap_err("Invalid YAML config"),
|
||||
"corn" => libcorn::from_slice(&file).wrap_err("Invalid Corn config"),
|
||||
_ => unreachable!(),
|
||||
#[cfg(feature = "config+json")]
|
||||
"json" => serde_json::from_str(str).wrap_err("Invalid JSON config"),
|
||||
#[cfg(feature = "config+toml")]
|
||||
"toml" => toml::from_str(str).wrap_err("Invalid TOML config"),
|
||||
#[cfg(feature = "config+yaml")]
|
||||
"yaml" | "yml" => serde_yaml::from_str(str).wrap_err("Invalid YAML config"),
|
||||
#[cfg(feature = "config+corn")]
|
||||
"corn" => libcorn::from_str(str).wrap_err("Invalid Corn config"),
|
||||
_ => Err(Report::msg(format!("Unsupported config type: {extension}"))
|
||||
.note("You may need to recompile with support if available")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
mod r#impl;
|
||||
mod truncate;
|
||||
|
||||
#[cfg(feature = "clock")]
|
||||
use crate::modules::clock::ClockModule;
|
||||
use crate::modules::custom::CustomModule;
|
||||
use crate::modules::focused::FocusedModule;
|
||||
use crate::modules::launcher::LauncherModule;
|
||||
#[cfg(feature = "music")]
|
||||
use crate::modules::music::MusicModule;
|
||||
use crate::modules::script::ScriptModule;
|
||||
#[cfg(feature = "sys_info")]
|
||||
use crate::modules::sysinfo::SysInfoModule;
|
||||
#[cfg(feature = "tray")]
|
||||
use crate::modules::tray::TrayModule;
|
||||
#[cfg(feature = "workspaces")]
|
||||
use crate::modules::workspaces::WorkspacesModule;
|
||||
use crate::script::ScriptInput;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use self::truncate::{EllipsizeMode, TruncateMode};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct CommonConfig {
|
||||
pub show_if: Option<ScriptInput>,
|
||||
@@ -29,15 +37,20 @@ pub struct CommonConfig {
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ModuleConfig {
|
||||
#[cfg(feature = "clock")]
|
||||
Clock(ClockModule),
|
||||
Music(MusicModule),
|
||||
Tray(TrayModule),
|
||||
Workspaces(WorkspacesModule),
|
||||
SysInfo(SysInfoModule),
|
||||
Launcher(LauncherModule),
|
||||
Script(ScriptModule),
|
||||
Focused(FocusedModule),
|
||||
Custom(CustomModule),
|
||||
Focused(FocusedModule),
|
||||
Launcher(LauncherModule),
|
||||
#[cfg(feature = "music")]
|
||||
Music(MusicModule),
|
||||
Script(ScriptModule),
|
||||
#[cfg(feature = "sys_info")]
|
||||
SysInfo(SysInfoModule),
|
||||
#[cfg(feature = "tray")]
|
||||
Tray(TrayModule),
|
||||
#[cfg(feature = "workspaces")]
|
||||
Workspaces(WorkspacesModule),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -70,6 +83,9 @@ pub struct Config {
|
||||
#[serde(default = "default_bar_height")]
|
||||
pub height: i32,
|
||||
|
||||
/// GTK icon theme to use.
|
||||
pub icon_theme: Option<String>,
|
||||
|
||||
pub start: Option<Vec<ModuleConfig>>,
|
||||
pub center: Option<Vec<ModuleConfig>>,
|
||||
pub end: Option<Vec<ModuleConfig>>,
|
||||
|
||||
54
src/config/truncate.rs
Normal file
54
src/config/truncate.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use gtk::pango::EllipsizeMode as GtkEllipsizeMode;
|
||||
use gtk::prelude::*;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum EllipsizeMode {
|
||||
Start,
|
||||
Middle,
|
||||
End,
|
||||
}
|
||||
|
||||
impl From<EllipsizeMode> for GtkEllipsizeMode {
|
||||
fn from(value: EllipsizeMode) -> Self {
|
||||
match value {
|
||||
EllipsizeMode::Start => Self::Start,
|
||||
EllipsizeMode::Middle => Self::Middle,
|
||||
EllipsizeMode::End => Self::End,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[serde(untagged)]
|
||||
pub enum TruncateMode {
|
||||
Auto(EllipsizeMode),
|
||||
MaxLength {
|
||||
mode: EllipsizeMode,
|
||||
length: Option<i32>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TruncateMode {
|
||||
const fn mode(&self) -> EllipsizeMode {
|
||||
match self {
|
||||
Self::MaxLength { mode, .. } | Self::Auto(mode) => *mode,
|
||||
}
|
||||
}
|
||||
|
||||
const fn length(&self) -> Option<i32> {
|
||||
match self {
|
||||
Self::Auto(_) => None,
|
||||
Self::MaxLength { length, .. } => *length,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncate_label(&self, label: >k::Label) {
|
||||
label.set_ellipsize(self.mode().into());
|
||||
|
||||
if let Some(max_length) = self.length() {
|
||||
label.set_max_width_chars(max_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
use gtk::gdk_pixbuf::Pixbuf;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{IconLookupFlags, IconTheme};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
@@ -67,7 +64,7 @@ fn parse_desktop_file(path: PathBuf) -> io::Result<HashMap<String, String>> {
|
||||
}
|
||||
|
||||
/// Attempts to get the icon name from the app's `.desktop` file.
|
||||
fn get_desktop_icon_name(app_id: &str) -> Option<String> {
|
||||
pub fn get_desktop_icon_name(app_id: &str) -> Option<String> {
|
||||
find_desktop_file(app_id).and_then(|file| {
|
||||
let map = parse_desktop_file(file);
|
||||
map.map_or(None, |map| {
|
||||
@@ -75,65 +72,3 @@ fn get_desktop_icon_name(app_id: &str) -> Option<String> {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
enum IconLocation {
|
||||
Theme(String),
|
||||
File(PathBuf),
|
||||
}
|
||||
|
||||
/// Attempts to get the location of an icon.
|
||||
///
|
||||
/// Handles icons that are part of a GTK theme, icons specified as path
|
||||
/// and icons for steam games.
|
||||
fn get_icon_location(theme: &IconTheme, app_id: &str, size: i32) -> Option<IconLocation> {
|
||||
let has_icon = theme
|
||||
.lookup_icon(app_id, size, IconLookupFlags::empty())
|
||||
.is_some();
|
||||
|
||||
if has_icon {
|
||||
return Some(IconLocation::Theme(app_id.to_string()));
|
||||
}
|
||||
|
||||
let is_steam_game = app_id.starts_with("steam_app_");
|
||||
if is_steam_game {
|
||||
let steam_id: String = app_id.chars().skip("steam_app_".len()).collect();
|
||||
|
||||
return match dirs::data_dir() {
|
||||
Some(dir) => {
|
||||
let path = dir.join(format!(
|
||||
"icons/hicolor/32x32/apps/steam_icon_{steam_id}.png"
|
||||
));
|
||||
|
||||
return Some(IconLocation::File(path));
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
}
|
||||
|
||||
let icon_name = get_desktop_icon_name(app_id);
|
||||
if let Some(icon_name) = icon_name {
|
||||
let is_path = PathBuf::from(&icon_name).exists();
|
||||
|
||||
return if is_path {
|
||||
Some(IconLocation::File(PathBuf::from(icon_name)))
|
||||
} else {
|
||||
return Some(IconLocation::Theme(icon_name));
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Gets the icon associated with an app.
|
||||
pub fn get_icon(theme: &IconTheme, app_id: &str, size: i32) -> Option<Pixbuf> {
|
||||
let icon_location = get_icon_location(theme, app_id, size);
|
||||
|
||||
match icon_location {
|
||||
Some(IconLocation::Theme(icon_name)) => {
|
||||
let icon = theme.load_icon(&icon_name, size, IconLookupFlags::FORCE_SIZE);
|
||||
icon.map_or(None, |icon| icon)
|
||||
}
|
||||
Some(IconLocation::File(path)) => Pixbuf::from_file_at_scale(path, size, size, true).ok(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
50
src/image/gtk.rs
Normal file
50
src/image/gtk.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use super::ImageProvider;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme, Image, Label, Orientation};
|
||||
use tracing::error;
|
||||
|
||||
#[cfg(any(feature = "music", feature = "workspaces"))]
|
||||
pub fn new_icon_button(input: &str, icon_theme: &IconTheme, size: i32) -> Button {
|
||||
let button = Button::new();
|
||||
|
||||
if ImageProvider::is_definitely_image_input(input) {
|
||||
let image = Image::new();
|
||||
match ImageProvider::parse(input, icon_theme, size)
|
||||
.and_then(|provider| provider.load_into_image(image.clone()))
|
||||
{
|
||||
Ok(_) => {
|
||||
button.set_image(Some(&image));
|
||||
button.set_always_show_image(true);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{err:?}");
|
||||
button.set_label(input);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
button.set_label(input);
|
||||
}
|
||||
|
||||
button
|
||||
}
|
||||
|
||||
#[cfg(feature = "music")]
|
||||
pub fn new_icon_label(input: &str, icon_theme: &IconTheme, size: i32) -> gtk::Box {
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
|
||||
if ImageProvider::is_definitely_image_input(input) {
|
||||
let image = Image::new();
|
||||
container.add(&image);
|
||||
|
||||
if let Err(err) = ImageProvider::parse(input, icon_theme, size)
|
||||
.and_then(|provider| provider.load_into_image(image))
|
||||
{
|
||||
error!("{err:?}");
|
||||
}
|
||||
} else {
|
||||
let label = Label::new(Some(input));
|
||||
container.add(&label);
|
||||
}
|
||||
|
||||
container
|
||||
}
|
||||
7
src/image/mod.rs
Normal file
7
src/image/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[cfg(any(feature = "music", feature = "workspaces"))]
|
||||
mod gtk;
|
||||
mod provider;
|
||||
|
||||
#[cfg(any(feature = "music", feature = "workspaces"))]
|
||||
pub use self::gtk::*;
|
||||
pub use provider::ImageProvider;
|
||||
199
src/image/provider.rs
Normal file
199
src/image/provider.rs
Normal file
@@ -0,0 +1,199 @@
|
||||
use crate::desktop_file::get_desktop_icon_name;
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use gtk::gdk_pixbuf::Pixbuf;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{IconLookupFlags, IconTheme};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
cfg_if!(
|
||||
if #[cfg(feature = "http")] {
|
||||
use crate::send;
|
||||
use gtk::gio::{Cancellable, MemoryInputStream};
|
||||
use tokio::spawn;
|
||||
use tracing::error;
|
||||
}
|
||||
);
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ImageLocation<'a> {
|
||||
Icon {
|
||||
name: String,
|
||||
theme: &'a IconTheme,
|
||||
},
|
||||
Local(PathBuf),
|
||||
Steam(String),
|
||||
#[cfg(feature = "http")]
|
||||
Remote(reqwest::Url),
|
||||
}
|
||||
|
||||
pub struct ImageProvider<'a> {
|
||||
location: ImageLocation<'a>,
|
||||
size: i32,
|
||||
}
|
||||
|
||||
impl<'a> ImageProvider<'a> {
|
||||
/// Attempts to parse the image input to find its location.
|
||||
/// Errors if no valid location type can be found.
|
||||
///
|
||||
/// Note this checks that icons exist in theme, or files exist on disk
|
||||
/// but no other check is performed.
|
||||
pub fn parse(input: &str, theme: &'a IconTheme, size: i32) -> Result<Self> {
|
||||
let location = Self::get_location(input, theme, size)?;
|
||||
Ok(Self { location, size })
|
||||
}
|
||||
|
||||
/// Returns true if the input starts with a prefix
|
||||
/// that is supported by the parser
|
||||
/// (ie the parser would not fallback to checking the input).
|
||||
#[cfg(any(feature = "music", feature = "workspaces"))]
|
||||
pub fn is_definitely_image_input(input: &str) -> bool {
|
||||
input.starts_with("icon:")
|
||||
|| input.starts_with("file://")
|
||||
|| input.starts_with("http://")
|
||||
|| input.starts_with("https://")
|
||||
}
|
||||
|
||||
fn get_location(input: &str, theme: &'a IconTheme, size: i32) -> Result<ImageLocation<'a>> {
|
||||
let (input_type, input_name) = input
|
||||
.split_once(':')
|
||||
.map_or((None, input), |(t, n)| (Some(t), n));
|
||||
|
||||
match input_type {
|
||||
Some(input_type) if input_type == "icon" => Ok(ImageLocation::Icon {
|
||||
name: input_name.to_string(),
|
||||
theme,
|
||||
}),
|
||||
Some(input_type) if input_type == "file" => Ok(ImageLocation::Local(PathBuf::from(
|
||||
input_name[2..].to_string(),
|
||||
))),
|
||||
#[cfg(feature = "http")]
|
||||
Some(input_type) if input_type == "http" || input_type == "https" => {
|
||||
Ok(ImageLocation::Remote(input.parse()?))
|
||||
}
|
||||
None if input.starts_with("steam_app_") => Ok(ImageLocation::Steam(
|
||||
input_name.chars().skip("steam_app_".len()).collect(),
|
||||
)),
|
||||
None if theme
|
||||
.lookup_icon(input, size, IconLookupFlags::empty())
|
||||
.is_some() =>
|
||||
{
|
||||
Ok(ImageLocation::Icon {
|
||||
name: input_name.to_string(),
|
||||
theme,
|
||||
})
|
||||
}
|
||||
Some(input_type) => Err(Report::msg(format!("Unsupported image type: {input_type}"))
|
||||
.note("You may need to recompile with support if available")),
|
||||
None if PathBuf::from(input_name).is_file() => {
|
||||
Ok(ImageLocation::Local(PathBuf::from(input_name)))
|
||||
}
|
||||
None => get_desktop_icon_name(input_name).map_or_else(
|
||||
|| Err(Report::msg("Unknown image type")),
|
||||
|input| Self::get_location(&input, theme, size),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to fetch the image from the location
|
||||
/// and load it into the provided `GTK::Image` widget.
|
||||
pub fn load_into_image(&self, image: gtk::Image) -> Result<()> {
|
||||
// handle remote locations async to avoid blocking UI thread while downloading
|
||||
#[cfg(feature = "http")]
|
||||
if let ImageLocation::Remote(url) = &self.location {
|
||||
let url = url.clone();
|
||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||
|
||||
spawn(async move {
|
||||
let bytes = Self::get_bytes_from_http(url).await;
|
||||
if let Ok(bytes) = bytes {
|
||||
send!(tx, bytes);
|
||||
}
|
||||
});
|
||||
|
||||
{
|
||||
let size = self.size;
|
||||
rx.attach(None, move |bytes| {
|
||||
let stream = MemoryInputStream::from_bytes(&bytes);
|
||||
let pixbuf = Pixbuf::from_stream_at_scale(
|
||||
&stream,
|
||||
size,
|
||||
size,
|
||||
true,
|
||||
Some(&Cancellable::new()),
|
||||
);
|
||||
|
||||
match pixbuf {
|
||||
Ok(pixbuf) => image.set_pixbuf(Some(&pixbuf)),
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
|
||||
Continue(false)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
self.load_into_image_sync(image)?;
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "http"))]
|
||||
self.load_into_image_sync(image)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_into_image_sync(&self, image: gtk::Image) -> Result<()> {
|
||||
let pixbuf = match &self.location {
|
||||
ImageLocation::Icon { name, theme } => self.get_from_icon(name, theme),
|
||||
ImageLocation::Local(path) => self.get_from_file(path),
|
||||
ImageLocation::Steam(steam_id) => self.get_from_steam_id(steam_id),
|
||||
#[cfg(feature = "http")]
|
||||
_ => unreachable!(), // handled above
|
||||
}?;
|
||||
|
||||
image.set_pixbuf(Some(&pixbuf));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Attempts to get a `Pixbuf` from the GTK icon theme.
|
||||
fn get_from_icon(&self, name: &str, theme: &IconTheme) -> Result<Pixbuf> {
|
||||
let pixbuf = match theme.lookup_icon(name, self.size, IconLookupFlags::empty()) {
|
||||
Some(_) => theme.load_icon(name, self.size, IconLookupFlags::FORCE_SIZE),
|
||||
None => Ok(None),
|
||||
}?;
|
||||
|
||||
pixbuf.map_or_else(
|
||||
|| Err(Report::msg("Icon theme does not contain icon '{name}'")),
|
||||
Ok,
|
||||
)
|
||||
}
|
||||
|
||||
/// Attempts to get a `Pixbuf` from a local file.
|
||||
fn get_from_file(&self, path: &Path) -> Result<Pixbuf> {
|
||||
let pixbuf = Pixbuf::from_file_at_scale(path, self.size, self.size, true)?;
|
||||
Ok(pixbuf)
|
||||
}
|
||||
|
||||
/// Attempts to get a `Pixbuf` from a local file,
|
||||
/// using the Steam game ID to look it up.
|
||||
fn get_from_steam_id(&self, steam_id: &str) -> Result<Pixbuf> {
|
||||
// TODO: Can we load this from icon theme with app id `steam_icon_{}`?
|
||||
let path = dirs::data_dir().map_or_else(
|
||||
|| Err(Report::msg("Missing XDG data dir")),
|
||||
|dir| {
|
||||
Ok(dir.join(format!(
|
||||
"icons/hicolor/32x32/apps/steam_icon_{steam_id}.png"
|
||||
)))
|
||||
},
|
||||
)?;
|
||||
|
||||
self.get_from_file(&path)
|
||||
}
|
||||
|
||||
/// Attempts to get `Bytes` from an HTTP resource asynchronously.
|
||||
#[cfg(feature = "http")]
|
||||
async fn get_bytes_from_http(url: reqwest::Url) -> Result<glib::Bytes> {
|
||||
let bytes = reqwest::get(url).await?.bytes().await?;
|
||||
Ok(glib::Bytes::from_owned(bytes))
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,10 @@ mod bar;
|
||||
mod bridge_channel;
|
||||
mod clients;
|
||||
mod config;
|
||||
mod desktop_file;
|
||||
mod dynamic_string;
|
||||
mod error;
|
||||
mod icon;
|
||||
mod image;
|
||||
mod logging;
|
||||
mod macros;
|
||||
mod modules;
|
||||
|
||||
@@ -82,7 +82,7 @@ impl Module<Button> for ClockModule {
|
||||
});
|
||||
}
|
||||
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx);
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
|
||||
|
||||
Ok(ModuleWidget {
|
||||
widget: button,
|
||||
@@ -94,6 +94,7 @@ impl Module<Button> for ClockModule {
|
||||
self,
|
||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: glib::Receiver<Self::SendMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box> {
|
||||
let container = gtk::Box::builder()
|
||||
.orientation(Orientation::Vertical)
|
||||
@@ -119,6 +120,8 @@ impl Module<Button> for ClockModule {
|
||||
});
|
||||
}
|
||||
|
||||
container.show_all();
|
||||
|
||||
Some(container)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::config::CommonConfig;
|
||||
use crate::dynamic_string::DynamicString;
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::popup::{ButtonGeometry, Popup};
|
||||
use crate::script::Script;
|
||||
use crate::{send_async, try_send};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, Label, Orientation};
|
||||
use gtk::{Button, IconTheme, Label, Orientation};
|
||||
use serde::Deserialize;
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
@@ -46,6 +47,8 @@ pub struct Widget {
|
||||
class: Option<String>,
|
||||
on_click: Option<String>,
|
||||
orientation: Option<String>,
|
||||
src: Option<String>,
|
||||
size: Option<i32>,
|
||||
}
|
||||
|
||||
/// Supported GTK widget types
|
||||
@@ -55,20 +58,33 @@ pub enum WidgetType {
|
||||
Box,
|
||||
Label,
|
||||
Button,
|
||||
Image,
|
||||
}
|
||||
|
||||
impl Widget {
|
||||
/// Creates this widget and adds it to the parent container
|
||||
fn add_to(self, parent: >k::Box, tx: Sender<ExecEvent>, bar_orientation: Orientation) {
|
||||
fn add_to(
|
||||
self,
|
||||
parent: >k::Box,
|
||||
tx: Sender<ExecEvent>,
|
||||
bar_orientation: Orientation,
|
||||
icon_theme: &IconTheme,
|
||||
) {
|
||||
match self.widget_type {
|
||||
WidgetType::Box => parent.add(&self.into_box(&tx, bar_orientation)),
|
||||
WidgetType::Box => parent.add(&self.into_box(&tx, bar_orientation, icon_theme)),
|
||||
WidgetType::Label => parent.add(&self.into_label()),
|
||||
WidgetType::Button => parent.add(&self.into_button(tx, bar_orientation)),
|
||||
WidgetType::Image => parent.add(&self.into_image(icon_theme)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `gtk::Box` from this widget
|
||||
fn into_box(self, tx: &Sender<ExecEvent>, bar_orientation: Orientation) -> gtk::Box {
|
||||
fn into_box(
|
||||
self,
|
||||
tx: &Sender<ExecEvent>,
|
||||
bar_orientation: Orientation,
|
||||
icon_theme: &IconTheme,
|
||||
) -> gtk::Box {
|
||||
let mut builder = gtk::Box::builder();
|
||||
|
||||
if let Some(name) = self.name {
|
||||
@@ -87,9 +103,9 @@ impl Widget {
|
||||
}
|
||||
|
||||
if let Some(widgets) = self.widgets {
|
||||
widgets
|
||||
.into_iter()
|
||||
.for_each(|widget| widget.add_to(&container, tx.clone(), bar_orientation));
|
||||
for widget in widgets {
|
||||
widget.add_to(&container, tx.clone(), bar_orientation, icon_theme);
|
||||
}
|
||||
}
|
||||
|
||||
container
|
||||
@@ -157,6 +173,31 @@ impl Widget {
|
||||
|
||||
button
|
||||
}
|
||||
|
||||
fn into_image(self, icon_theme: &IconTheme) -> gtk::Image {
|
||||
let mut builder = gtk::Image::builder();
|
||||
|
||||
if let Some(name) = self.name {
|
||||
builder = builder.name(&name);
|
||||
}
|
||||
|
||||
let gtk_image = builder.build();
|
||||
|
||||
if let Some(src) = self.src {
|
||||
let size = self.size.unwrap_or(32);
|
||||
if let Err(err) = ImageProvider::parse(&src, icon_theme, size)
|
||||
.and_then(|image| image.load_into_image(gtk_image.clone()))
|
||||
{
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(class) = self.class {
|
||||
gtk_image.style_context().add_class(&class);
|
||||
}
|
||||
|
||||
gtk_image
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -217,10 +258,15 @@ impl Module<gtk::Box> for CustomModule {
|
||||
}
|
||||
|
||||
self.bar.clone().into_iter().for_each(|widget| {
|
||||
widget.add_to(&container, context.controller_tx.clone(), orientation);
|
||||
widget.add_to(
|
||||
&container,
|
||||
context.controller_tx.clone(),
|
||||
orientation,
|
||||
info.icon_theme,
|
||||
);
|
||||
});
|
||||
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx);
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
|
||||
|
||||
Ok(ModuleWidget {
|
||||
widget: container,
|
||||
@@ -232,6 +278,7 @@ impl Module<gtk::Box> for CustomModule {
|
||||
self,
|
||||
tx: Sender<Self::ReceiveMessage>,
|
||||
_rx: glib::Receiver<Self::SendMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
Self: Sized,
|
||||
@@ -245,11 +292,18 @@ impl Module<gtk::Box> for CustomModule {
|
||||
}
|
||||
|
||||
if let Some(popup) = self.popup {
|
||||
popup
|
||||
.into_iter()
|
||||
.for_each(|widget| widget.add_to(&container, tx.clone(), Orientation::Horizontal));
|
||||
for widget in popup {
|
||||
widget.add_to(
|
||||
&container,
|
||||
tx.clone(),
|
||||
Orientation::Horizontal,
|
||||
info.icon_theme,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
container.show_all();
|
||||
|
||||
Some(container)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use crate::clients::wayland::{self, ToplevelChange};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::config::{CommonConfig, TruncateMode};
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::{await_sync, icon, read_lock, send_async};
|
||||
use crate::{await_sync, read_lock, send_async};
|
||||
use color_eyre::Result;
|
||||
use glib::Continue;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{IconTheme, Image, Label};
|
||||
use gtk::Label;
|
||||
use serde::Deserialize;
|
||||
use tokio::spawn;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct FocusedModule {
|
||||
@@ -22,8 +24,8 @@ pub struct FocusedModule {
|
||||
/// Icon size in pixels.
|
||||
#[serde(default = "default_icon_size")]
|
||||
icon_size: i32,
|
||||
/// GTK icon theme to use.
|
||||
icon_theme: Option<String>,
|
||||
|
||||
truncate: Option<TruncateMode>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
@@ -91,26 +93,29 @@ impl Module<gtk::Box> for FocusedModule {
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Result<ModuleWidget<gtk::Box>> {
|
||||
let icon_theme = IconTheme::new();
|
||||
|
||||
if let Some(theme) = self.icon_theme {
|
||||
icon_theme.set_custom_theme(Some(&theme));
|
||||
}
|
||||
let icon_theme = info.icon_theme;
|
||||
|
||||
let container = gtk::Box::new(info.bar_position.get_orientation(), 5);
|
||||
|
||||
let icon = Image::builder().name("icon").build();
|
||||
let icon = gtk::Image::builder().name("icon").build();
|
||||
let label = Label::builder().name("label").build();
|
||||
|
||||
if let Some(truncate) = self.truncate {
|
||||
truncate.truncate_label(&label);
|
||||
}
|
||||
|
||||
container.add(&icon);
|
||||
container.add(&label);
|
||||
|
||||
{
|
||||
let icon_theme = icon_theme.clone();
|
||||
context.widget_rx.attach(None, move |(name, id)| {
|
||||
let pixbuf = icon::get_icon(&icon_theme, &id, self.icon_size);
|
||||
|
||||
if self.show_icon {
|
||||
icon.set_pixbuf(pixbuf.as_ref());
|
||||
if let Err(err) = ImageProvider::parse(&id, &icon_theme, self.icon_size)
|
||||
.and_then(|image| image.load_into_image(icon.clone()))
|
||||
{
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_title {
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use super::open_state::OpenState;
|
||||
use crate::clients::wayland::ToplevelInfo;
|
||||
use crate::icon::get_icon;
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
||||
use crate::modules::ModuleUpdateEvent;
|
||||
use crate::popup::Popup;
|
||||
use crate::{read_lock, try_send};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme, Image, Orientation};
|
||||
use gtk::{Button, IconTheme, Orientation};
|
||||
use indexmap::IndexMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::RwLock;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Item {
|
||||
@@ -151,16 +152,24 @@ impl ItemButton {
|
||||
button = button.label(&item.name);
|
||||
}
|
||||
|
||||
if show_icons {
|
||||
let icon = get_icon(icon_theme, &item.app_id, 32);
|
||||
if icon.is_some() {
|
||||
let image = Image::from_pixbuf(icon.as_ref());
|
||||
button = button.image(&image).always_show_image(true);
|
||||
}
|
||||
}
|
||||
|
||||
let button = button.build();
|
||||
|
||||
if show_icons {
|
||||
let gtk_image = gtk::Image::new();
|
||||
let image = ImageProvider::parse(&item.app_id.clone(), icon_theme, 32);
|
||||
match image {
|
||||
Ok(image) => {
|
||||
button.set_image(Some(>k_image));
|
||||
button.set_always_show_image(true);
|
||||
|
||||
if let Err(err) = image.load_into_image(gtk_image) {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
Err(err) => error!("{err:?}"),
|
||||
};
|
||||
}
|
||||
|
||||
let style_context = button.style_context();
|
||||
style_context.add_class("item");
|
||||
|
||||
|
||||
@@ -5,13 +5,13 @@ use self::item::{Item, ItemButton, Window};
|
||||
use self::open_state::OpenState;
|
||||
use crate::clients::wayland::{self, ToplevelChange};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::icon::find_desktop_file;
|
||||
use crate::desktop_file::find_desktop_file;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::{lock, read_lock, try_send, write_lock};
|
||||
use color_eyre::{Help, Report};
|
||||
use glib::Continue;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme, Orientation};
|
||||
use gtk::{Button, Orientation};
|
||||
use indexmap::IndexMap;
|
||||
use serde::Deserialize;
|
||||
use std::process::{Command, Stdio};
|
||||
@@ -33,9 +33,6 @@ pub struct LauncherModule {
|
||||
#[serde(default = "crate::config::default_true")]
|
||||
show_icons: bool,
|
||||
|
||||
/// Name of the GTK icon theme to use.
|
||||
icon_theme: Option<String>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
@@ -297,8 +294,6 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), swayipc_async::Error>(())
|
||||
});
|
||||
|
||||
Ok(())
|
||||
@@ -309,15 +304,15 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> crate::Result<ModuleWidget<gtk::Box>> {
|
||||
let icon_theme = IconTheme::new();
|
||||
if let Some(ref theme) = self.icon_theme {
|
||||
icon_theme.set_custom_theme(Some(theme));
|
||||
}
|
||||
let icon_theme = info.icon_theme;
|
||||
|
||||
let container = gtk::Box::new(info.bar_position.get_orientation(), 0);
|
||||
|
||||
{
|
||||
let container = container.clone();
|
||||
let icon_theme = icon_theme.clone();
|
||||
|
||||
let controller_tx = context.controller_tx.clone();
|
||||
|
||||
let show_names = self.show_names;
|
||||
let show_icons = self.show_icons;
|
||||
@@ -325,7 +320,6 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
|
||||
let mut buttons = IndexMap::<String, ItemButton>::new();
|
||||
|
||||
let controller_tx2 = context.controller_tx.clone();
|
||||
context.widget_rx.attach(None, move |event| {
|
||||
match event {
|
||||
LauncherUpdate::AddItem(item) => {
|
||||
@@ -341,7 +335,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
orientation,
|
||||
&icon_theme,
|
||||
&context.tx,
|
||||
&controller_tx2,
|
||||
&controller_tx,
|
||||
);
|
||||
|
||||
container.add(&button.button);
|
||||
@@ -400,7 +394,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
});
|
||||
}
|
||||
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx);
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
|
||||
Ok(ModuleWidget {
|
||||
widget: container,
|
||||
popup,
|
||||
@@ -411,6 +405,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
self,
|
||||
controller_tx: Sender<Self::ReceiveMessage>,
|
||||
rx: glib::Receiver<Self::SendMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box> {
|
||||
const MAX_WIDTH: i32 = 250;
|
||||
|
||||
|
||||
@@ -4,14 +4,19 @@
|
||||
///
|
||||
/// Clicking the widget opens a popup containing the current time
|
||||
/// with second-level precision and a calendar.
|
||||
#[cfg(feature = "clock")]
|
||||
pub mod clock;
|
||||
pub mod custom;
|
||||
pub mod focused;
|
||||
pub mod launcher;
|
||||
#[cfg(feature = "music")]
|
||||
pub mod music;
|
||||
pub mod script;
|
||||
#[cfg(feature = "sys_info")]
|
||||
pub mod sysinfo;
|
||||
#[cfg(feature = "tray")]
|
||||
pub mod tray;
|
||||
#[cfg(feature = "workspaces")]
|
||||
pub mod workspaces;
|
||||
|
||||
use crate::config::BarPosition;
|
||||
@@ -19,7 +24,7 @@ use crate::popup::ButtonGeometry;
|
||||
use color_eyre::Result;
|
||||
use glib::IsA;
|
||||
use gtk::gdk::Monitor;
|
||||
use gtk::{Application, Widget};
|
||||
use gtk::{Application, IconTheme, Widget};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -34,6 +39,7 @@ pub struct ModuleInfo<'a> {
|
||||
pub bar_position: BarPosition,
|
||||
pub monitor: &'a Monitor,
|
||||
pub output_name: &'a str,
|
||||
pub icon_theme: &'a IconTheme,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -88,6 +94,7 @@ where
|
||||
self,
|
||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
_rx: glib::Receiver<Self::SendMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
Self: Sized,
|
||||
|
||||
140
src/modules/music/config.rs
Normal file
140
src/modules/music/config.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use crate::config::{CommonConfig, TruncateMode};
|
||||
use dirs::{audio_dir, home_dir};
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Icons {
|
||||
/// Icon to display when playing.
|
||||
#[serde(default = "default_icon_play")]
|
||||
pub(crate) play: String,
|
||||
|
||||
/// Icon to display when paused.
|
||||
#[serde(default = "default_icon_pause")]
|
||||
pub(crate) pause: String,
|
||||
|
||||
/// Icon to display for previous button.
|
||||
#[serde(default = "default_icon_prev")]
|
||||
pub(crate) prev: String,
|
||||
|
||||
/// Icon to display for next button.
|
||||
#[serde(default = "default_icon_next")]
|
||||
pub(crate) next: String,
|
||||
|
||||
/// Icon to display under volume slider
|
||||
#[serde(default = "default_icon_volume")]
|
||||
pub(crate) volume: String,
|
||||
|
||||
/// Icon to display nex to track title
|
||||
#[serde(default = "default_icon_track")]
|
||||
pub(crate) track: String,
|
||||
|
||||
/// Icon to display nex to album name
|
||||
#[serde(default = "default_icon_album")]
|
||||
pub(crate) album: String,
|
||||
|
||||
/// Icon to display nex to artist name
|
||||
#[serde(default = "default_icon_artist")]
|
||||
pub(crate) artist: String,
|
||||
}
|
||||
|
||||
impl Default for Icons {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pause: default_icon_pause(),
|
||||
play: default_icon_play(),
|
||||
prev: default_icon_prev(),
|
||||
next: default_icon_next(),
|
||||
volume: default_icon_volume(),
|
||||
track: default_icon_track(),
|
||||
album: default_icon_album(),
|
||||
artist: default_icon_artist(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PlayerType {
|
||||
Mpd,
|
||||
Mpris,
|
||||
}
|
||||
|
||||
impl Default for PlayerType {
|
||||
fn default() -> Self {
|
||||
Self::Mpris
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct MusicModule {
|
||||
/// Type of player to connect to
|
||||
#[serde(default)]
|
||||
pub(crate) player_type: PlayerType,
|
||||
|
||||
/// Format of current song info to display on the bar.
|
||||
#[serde(default = "default_format")]
|
||||
pub(crate) format: String,
|
||||
|
||||
/// Player state icons
|
||||
#[serde(default)]
|
||||
pub(crate) icons: Icons,
|
||||
|
||||
// -- MPD --
|
||||
/// TCP or Unix socket address.
|
||||
#[serde(default = "default_socket")]
|
||||
pub(crate) host: String,
|
||||
/// Path to root of music directory.
|
||||
#[serde(default = "default_music_dir")]
|
||||
pub(crate) music_dir: PathBuf,
|
||||
|
||||
// -- Common --
|
||||
pub(crate) truncate: Option<TruncateMode>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
fn default_socket() -> String {
|
||||
String::from("localhost:6600")
|
||||
}
|
||||
|
||||
fn default_format() -> String {
|
||||
String::from("{title} / {artist}")
|
||||
}
|
||||
|
||||
fn default_icon_play() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
fn default_icon_pause() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
fn default_icon_prev() -> String {
|
||||
String::from("\u{f9ad}")
|
||||
}
|
||||
|
||||
fn default_icon_next() -> String {
|
||||
String::from("\u{f9ac}")
|
||||
}
|
||||
|
||||
fn default_icon_volume() -> String {
|
||||
String::from("墳")
|
||||
}
|
||||
|
||||
fn default_icon_track() -> String {
|
||||
String::from("\u{f886}")
|
||||
}
|
||||
|
||||
fn default_icon_album() -> String {
|
||||
String::from("\u{f524}")
|
||||
}
|
||||
|
||||
fn default_icon_artist() -> String {
|
||||
String::from("\u{fd01}")
|
||||
}
|
||||
|
||||
fn default_music_dir() -> PathBuf {
|
||||
audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default())
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
mod config;
|
||||
|
||||
use crate::clients::music::{self, MusicClient, PlayerState, PlayerUpdate, Status, Track};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::error::ERR_CHANNEL_SEND;
|
||||
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::popup::Popup;
|
||||
use crate::try_send;
|
||||
use crate::{send_async, try_send};
|
||||
use color_eyre::Result;
|
||||
use dirs::{audio_dir, home_dir};
|
||||
use glib::Continue;
|
||||
use gtk::gdk_pixbuf::Pixbuf;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, Image, Label, Orientation, Scale};
|
||||
use gtk::{Button, IconTheme, Label, Orientation, Scale};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -19,6 +17,9 @@ use tokio::spawn;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tracing::error;
|
||||
|
||||
pub use self::config::MusicModule;
|
||||
use self::config::PlayerType;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PlayerCommand {
|
||||
Previous,
|
||||
@@ -28,93 +29,6 @@ pub enum PlayerCommand {
|
||||
Volume(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct Icons {
|
||||
/// Icon to display when playing.
|
||||
#[serde(default = "default_icon_play")]
|
||||
play: String,
|
||||
/// Icon to display when paused.
|
||||
#[serde(default = "default_icon_pause")]
|
||||
pause: String,
|
||||
/// Icon to display under volume slider
|
||||
#[serde(default = "default_icon_volume")]
|
||||
volume: String,
|
||||
}
|
||||
|
||||
impl Default for Icons {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pause: default_icon_pause(),
|
||||
play: default_icon_play(),
|
||||
volume: default_icon_volume(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PlayerType {
|
||||
// Auto,
|
||||
Mpd,
|
||||
Mpris,
|
||||
}
|
||||
|
||||
impl Default for PlayerType {
|
||||
fn default() -> Self {
|
||||
Self::Mpris
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct MusicModule {
|
||||
/// Type of player to connect to
|
||||
#[serde(default)]
|
||||
player_type: PlayerType,
|
||||
|
||||
/// Format of current song info to display on the bar.
|
||||
#[serde(default = "default_format")]
|
||||
format: String,
|
||||
|
||||
/// Player state icons
|
||||
#[serde(default)]
|
||||
icons: Icons,
|
||||
|
||||
// -- MPD --
|
||||
/// TCP or Unix socket address.
|
||||
#[serde(default = "default_socket")]
|
||||
host: String,
|
||||
/// Path to root of music directory.
|
||||
#[serde(default = "default_music_dir")]
|
||||
music_dir: PathBuf,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
fn default_socket() -> String {
|
||||
String::from("localhost:6600")
|
||||
}
|
||||
|
||||
fn default_format() -> String {
|
||||
String::from("{icon} {title} / {artist}")
|
||||
}
|
||||
|
||||
fn default_icon_play() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
fn default_icon_pause() -> String {
|
||||
String::from("")
|
||||
}
|
||||
|
||||
fn default_icon_volume() -> String {
|
||||
String::from("墳")
|
||||
}
|
||||
|
||||
fn default_music_dir() -> PathBuf {
|
||||
audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default())
|
||||
}
|
||||
|
||||
/// Formats a duration given in seconds
|
||||
/// in hh:mm format
|
||||
fn format_time(duration: Duration) -> String {
|
||||
@@ -166,7 +80,6 @@ impl Module<Button> for MusicModule {
|
||||
mut rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let format = self.format.clone();
|
||||
let icons = self.icons.clone();
|
||||
|
||||
let re = Regex::new(r"\{([\w-]+)}")?;
|
||||
let tokens = get_tokens(&re, self.format.as_str());
|
||||
@@ -188,13 +101,8 @@ impl Module<Button> for MusicModule {
|
||||
match update {
|
||||
PlayerUpdate::Update(track, status) => match *track {
|
||||
Some(track) => {
|
||||
let display_string = replace_tokens(
|
||||
format.as_str(),
|
||||
&tokens,
|
||||
&track,
|
||||
&status,
|
||||
&icons,
|
||||
);
|
||||
let display_string =
|
||||
replace_tokens(format.as_str(), &tokens, &track, &status);
|
||||
|
||||
let update = SongUpdate {
|
||||
song: track,
|
||||
@@ -202,14 +110,9 @@ impl Module<Button> for MusicModule {
|
||||
display_string,
|
||||
};
|
||||
|
||||
tx.send(ModuleUpdateEvent::Update(Some(update)))
|
||||
.await
|
||||
.expect(ERR_CHANNEL_SEND);
|
||||
send_async!(tx, ModuleUpdateEvent::Update(Some(update)));
|
||||
}
|
||||
None => tx
|
||||
.send(ModuleUpdateEvent::Update(None))
|
||||
.await
|
||||
.expect(ERR_CHANNEL_SEND),
|
||||
None => send_async!(tx, ModuleUpdateEvent::Update(None)),
|
||||
},
|
||||
PlayerUpdate::Disconnect => break,
|
||||
}
|
||||
@@ -251,9 +154,22 @@ impl Module<Button> for MusicModule {
|
||||
info: &ModuleInfo,
|
||||
) -> Result<ModuleWidget<Button>> {
|
||||
let button = Button::new();
|
||||
let button_contents = gtk::Box::new(Orientation::Horizontal, 5);
|
||||
button.add(&button_contents);
|
||||
|
||||
let icon_play = new_icon_label(&self.icons.play, info.icon_theme, 24);
|
||||
let icon_pause = new_icon_label(&self.icons.pause, info.icon_theme, 24);
|
||||
let label = Label::new(None);
|
||||
|
||||
label.set_angle(info.bar_position.get_angle());
|
||||
button.add(&label);
|
||||
|
||||
if let Some(truncate) = self.truncate {
|
||||
truncate.truncate_label(&label);
|
||||
}
|
||||
|
||||
button_contents.add(&icon_pause);
|
||||
button_contents.add(&icon_play);
|
||||
button_contents.add(&label);
|
||||
|
||||
let orientation = info.bar_position.get_orientation();
|
||||
|
||||
@@ -275,6 +191,21 @@ impl Module<Button> for MusicModule {
|
||||
context.widget_rx.attach(None, move |mut event| {
|
||||
if let Some(event) = event.take() {
|
||||
label.set_label(&event.display_string);
|
||||
|
||||
match event.status.state {
|
||||
PlayerState::Playing => {
|
||||
icon_play.show();
|
||||
icon_pause.hide();
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
icon_pause.show();
|
||||
icon_play.hide();
|
||||
}
|
||||
PlayerState::Stopped => {
|
||||
button.hide();
|
||||
}
|
||||
}
|
||||
|
||||
button.show();
|
||||
} else {
|
||||
button.hide();
|
||||
@@ -285,7 +216,7 @@ impl Module<Button> for MusicModule {
|
||||
});
|
||||
};
|
||||
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx);
|
||||
let popup = self.into_popup(context.controller_tx, context.popup_rx, info);
|
||||
|
||||
Ok(ModuleWidget {
|
||||
widget: button,
|
||||
@@ -297,23 +228,28 @@ impl Module<Button> for MusicModule {
|
||||
self,
|
||||
tx: Sender<Self::ReceiveMessage>,
|
||||
rx: glib::Receiver<Self::SendMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Option<gtk::Box> {
|
||||
let icon_theme = info.icon_theme;
|
||||
|
||||
let container = gtk::Box::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.spacing(10)
|
||||
.name("popup-music")
|
||||
.build();
|
||||
|
||||
let album_image = Image::builder()
|
||||
let album_image = gtk::Image::builder()
|
||||
.width_request(128)
|
||||
.height_request(128)
|
||||
.name("album-art")
|
||||
.build();
|
||||
|
||||
let icons = self.icons;
|
||||
|
||||
let info_box = gtk::Box::new(Orientation::Vertical, 10);
|
||||
let title_label = IconLabel::new("\u{f886}", None);
|
||||
let album_label = IconLabel::new("\u{f524}", None);
|
||||
let artist_label = IconLabel::new("\u{fd01}", None);
|
||||
let title_label = IconLabel::new(&icons.track, None, icon_theme);
|
||||
let album_label = IconLabel::new(&icons.album, None, icon_theme);
|
||||
let artist_label = IconLabel::new(&icons.artist, None, icon_theme);
|
||||
|
||||
title_label.container.set_widget_name("title");
|
||||
album_label.container.set_widget_name("album");
|
||||
@@ -324,12 +260,22 @@ impl Module<Button> for MusicModule {
|
||||
info_box.add(&artist_label.container);
|
||||
|
||||
let controls_box = gtk::Box::builder().name("controls").build();
|
||||
let btn_prev = Button::builder().label("\u{f9ad}").name("btn-prev").build();
|
||||
let btn_play_pause = Button::builder().label("").name("btn-play-pause").build();
|
||||
let btn_next = Button::builder().label("\u{f9ac}").name("btn-next").build();
|
||||
|
||||
let btn_prev = new_icon_button(&icons.prev, icon_theme, 24);
|
||||
btn_prev.set_widget_name("btn-prev");
|
||||
|
||||
let btn_play = new_icon_button(&icons.play, icon_theme, 24);
|
||||
btn_play.set_widget_name("btn-play");
|
||||
|
||||
let btn_pause = new_icon_button(&icons.pause, icon_theme, 24);
|
||||
btn_pause.set_widget_name("btn-pause");
|
||||
|
||||
let btn_next = new_icon_button(&icons.next, icon_theme, 24);
|
||||
btn_next.set_widget_name("btn-next");
|
||||
|
||||
controls_box.add(&btn_prev);
|
||||
controls_box.add(&btn_play_pause);
|
||||
controls_box.add(&btn_play);
|
||||
controls_box.add(&btn_pause);
|
||||
controls_box.add(&btn_next);
|
||||
|
||||
info_box.add(&controls_box);
|
||||
@@ -344,7 +290,7 @@ impl Module<Button> for MusicModule {
|
||||
volume_slider.set_inverted(true);
|
||||
volume_slider.set_widget_name("slider");
|
||||
|
||||
let volume_icon = Label::new(Some(&self.icons.volume));
|
||||
let volume_icon = new_icon_label(&icons.volume, icon_theme, 24);
|
||||
volume_icon.style_context().add_class("icon");
|
||||
|
||||
volume_box.pack_start(&volume_slider, true, true, 0);
|
||||
@@ -359,13 +305,14 @@ impl Module<Button> for MusicModule {
|
||||
try_send!(tx_prev, PlayerCommand::Previous);
|
||||
});
|
||||
|
||||
let tx_toggle = tx.clone();
|
||||
btn_play_pause.connect_clicked(move |button| {
|
||||
if button.style_context().has_class("playing") {
|
||||
try_send!(tx_toggle, PlayerCommand::Pause);
|
||||
} else {
|
||||
try_send!(tx_toggle, PlayerCommand::Play);
|
||||
}
|
||||
let tx_play = tx.clone();
|
||||
btn_play.connect_clicked(move |_| {
|
||||
try_send!(tx_play, PlayerCommand::Play);
|
||||
});
|
||||
|
||||
let tx_pause = tx.clone();
|
||||
btn_pause.connect_clicked(move |_| {
|
||||
try_send!(tx_pause, PlayerCommand::Pause);
|
||||
});
|
||||
|
||||
let tx_next = tx.clone();
|
||||
@@ -382,6 +329,8 @@ impl Module<Button> for MusicModule {
|
||||
container.show_all();
|
||||
|
||||
{
|
||||
let icon_theme = icon_theme.clone();
|
||||
|
||||
let mut prev_cover = None;
|
||||
rx.attach(None, move |update| {
|
||||
if let Some(update) = update {
|
||||
@@ -389,16 +338,22 @@ impl Module<Button> for MusicModule {
|
||||
let new_cover = update.song.cover_path;
|
||||
if prev_cover != new_cover {
|
||||
prev_cover = new_cover.clone();
|
||||
match new_cover.map(|cover_path| {
|
||||
Pixbuf::from_file_at_scale(cover_path, 128, 128, true)
|
||||
}) {
|
||||
Some(Ok(pixbuf)) => album_image.set_from_pixbuf(Some(&pixbuf)),
|
||||
let res = match new_cover
|
||||
.map(|cover_path| ImageProvider::parse(&cover_path, &icon_theme, 128))
|
||||
{
|
||||
Some(Ok(image)) => image.load_into_image(album_image.clone()),
|
||||
Some(Err(err)) => {
|
||||
error!("{:?}", err);
|
||||
album_image.set_from_pixbuf(None);
|
||||
Err(err)
|
||||
}
|
||||
None => {
|
||||
album_image.set_from_pixbuf(None);
|
||||
Ok(())
|
||||
}
|
||||
None => album_image.set_from_pixbuf(None),
|
||||
};
|
||||
if let Err(err) = res {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
title_label
|
||||
@@ -413,23 +368,23 @@ impl Module<Button> for MusicModule {
|
||||
|
||||
match update.status.state {
|
||||
PlayerState::Stopped => {
|
||||
btn_play_pause.set_sensitive(false);
|
||||
btn_pause.hide();
|
||||
btn_play.show();
|
||||
btn_play.set_sensitive(false);
|
||||
}
|
||||
PlayerState::Playing => {
|
||||
btn_play_pause.set_sensitive(true);
|
||||
btn_play_pause.set_label(&self.icons.pause);
|
||||
btn_play.set_sensitive(false);
|
||||
btn_play.hide();
|
||||
|
||||
let style_context = btn_play_pause.style_context();
|
||||
style_context.add_class("playing");
|
||||
style_context.remove_class("paused");
|
||||
btn_pause.set_sensitive(true);
|
||||
btn_pause.show();
|
||||
}
|
||||
PlayerState::Paused => {
|
||||
btn_play_pause.set_sensitive(true);
|
||||
btn_play_pause.set_label(&self.icons.play);
|
||||
btn_pause.set_sensitive(false);
|
||||
btn_pause.hide();
|
||||
|
||||
let style_context = btn_play_pause.style_context();
|
||||
style_context.add_class("paused");
|
||||
style_context.remove_class("playing");
|
||||
btn_play.set_sensitive(true);
|
||||
btn_play.show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,11 +414,10 @@ fn replace_tokens(
|
||||
tokens: &Vec<String>,
|
||||
song: &Track,
|
||||
status: &Status,
|
||||
icons: &Icons,
|
||||
) -> String {
|
||||
let mut compiled_string = format_string.to_string();
|
||||
for token in tokens {
|
||||
let value = get_token_value(song, status, icons, token);
|
||||
let value = get_token_value(song, status, token);
|
||||
compiled_string = compiled_string.replace(format!("{{{token}}}").as_str(), value.as_str());
|
||||
}
|
||||
compiled_string
|
||||
@@ -471,14 +425,8 @@ fn replace_tokens(
|
||||
|
||||
/// Converts a string format token value
|
||||
/// into its respective value.
|
||||
fn get_token_value(song: &Track, status: &Status, icons: &Icons, token: &str) -> String {
|
||||
fn get_token_value(song: &Track, status: &Status, token: &str) -> String {
|
||||
match token {
|
||||
"icon" => match status.state {
|
||||
PlayerState::Stopped => None,
|
||||
PlayerState::Playing => Some(&icons.play),
|
||||
PlayerState::Paused => Some(&icons.pause),
|
||||
}
|
||||
.map(std::string::ToString::to_string),
|
||||
"title" => song.title.clone(),
|
||||
"album" => song.album.clone(),
|
||||
"artist" => song.artist.clone(),
|
||||
@@ -493,17 +441,17 @@ fn get_token_value(song: &Track, status: &Status, icons: &Icons, token: &str) ->
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct IconLabel {
|
||||
label: Label,
|
||||
container: gtk::Box,
|
||||
}
|
||||
|
||||
impl IconLabel {
|
||||
fn new(icon: &str, label: Option<&str>) -> Self {
|
||||
fn new(icon_input: &str, label: Option<&str>, icon_theme: &IconTheme) -> Self {
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
let icon = Label::new(Some(icon));
|
||||
let icon = new_icon_label(icon_input, icon_theme, 32);
|
||||
let label = Label::new(label);
|
||||
|
||||
icon.style_context().add_class("icon");
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::config::CommonConfig;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::script::{OutputStream, Script, ScriptMode};
|
||||
use crate::try_send;
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
@@ -63,8 +64,8 @@ impl Module<Label> for ScriptModule {
|
||||
spawn(async move {
|
||||
script.run(move |(out, _)| match out {
|
||||
OutputStream::Stdout(stdout) => {
|
||||
tx.try_send(ModuleUpdateEvent::Update(stdout))
|
||||
.expect("Failed to send stdout"); }
|
||||
try_send!(tx, ModuleUpdateEvent::Update(stdout));
|
||||
},
|
||||
OutputStream::Stderr(stderr) => {
|
||||
error!("{:?}", Report::msg(stderr)
|
||||
.wrap_err("Watched script error:")
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use crate::clients::compositor::{Compositor, WorkspaceUpdate};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::image::new_icon_button;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
|
||||
use crate::{send_async, try_send};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Button;
|
||||
use gtk::{Button, IconTheme};
|
||||
use serde::Deserialize;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashMap;
|
||||
@@ -49,12 +50,13 @@ fn create_button(
|
||||
name: &str,
|
||||
focused: bool,
|
||||
name_map: &HashMap<String, String>,
|
||||
icon_theme: &IconTheme,
|
||||
tx: &Sender<String>,
|
||||
) -> Button {
|
||||
let button = Button::builder()
|
||||
.label(name_map.get(name).map_or(name, String::as_str))
|
||||
.name(name)
|
||||
.build();
|
||||
let label = name_map.get(name).map_or(name, String::as_str);
|
||||
|
||||
let button = new_icon_button(label, icon_theme, 32);
|
||||
button.set_widget_name(name);
|
||||
|
||||
let style_context = button.style_context();
|
||||
style_context.add_class("item");
|
||||
@@ -154,6 +156,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
{
|
||||
let container = container.clone();
|
||||
let output_name = info.output_name.to_string();
|
||||
let icon_theme = info.icon_theme.clone();
|
||||
|
||||
// keep track of whether init event has fired previously
|
||||
// since it fires for every workspace subscriber
|
||||
@@ -170,6 +173,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
&workspace.name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&icon_theme,
|
||||
&context.controller_tx,
|
||||
);
|
||||
container.add(&item);
|
||||
@@ -187,12 +191,12 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Focus { old, new } => {
|
||||
let old = button_map.get(&old.name);
|
||||
let old = button_map.get(&old);
|
||||
if let Some(old) = old {
|
||||
old.style_context().remove_class("focused");
|
||||
}
|
||||
|
||||
let new = button_map.get(&new.name);
|
||||
let new = button_map.get(&new);
|
||||
if let Some(new) = new {
|
||||
new.style_context().add_class("focused");
|
||||
}
|
||||
@@ -204,6 +208,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
&name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&icon_theme,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
@@ -227,6 +232,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
&name,
|
||||
workspace.focused,
|
||||
&name_map,
|
||||
&icon_theme,
|
||||
&context.controller_tx,
|
||||
);
|
||||
|
||||
@@ -247,7 +253,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
}
|
||||
}
|
||||
WorkspaceUpdate::Remove(workspace) => {
|
||||
let button = button_map.get(&workspace.name);
|
||||
let button = button_map.get(&workspace);
|
||||
if let Some(item) = button {
|
||||
container.remove(item);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ impl Popup {
|
||||
|
||||
/// Shows the popup
|
||||
pub fn show(&self, geometry: ButtonGeometry) {
|
||||
self.window.show_all();
|
||||
self.window.show();
|
||||
self.set_pos(geometry);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user