mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-04-19 19:34:24 +02:00
Merge branch 'master' into feat/networkmanager
This commit is contained in:
commit
b860f5b603
57 changed files with 2450 additions and 858 deletions
3
.github/scripts/ubuntu_setup.sh
vendored
3
.github/scripts/ubuntu_setup.sh
vendored
|
@ -17,4 +17,5 @@ $SUDO apt-get update && $SUDO apt-get install --assume-yes \
|
|||
libssl-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||
libgtk-3-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||
libgtk-layer-shell-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||
libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH}
|
||||
libpulse-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH} \
|
||||
libluajit-5.1-dev${CROSS_DEB_ARCH:+:$CROSS_DEB_ARCH}
|
||||
|
|
6
.idea/runConfigurations/Clippy__Strict_.xml
generated
6
.idea/runConfigurations/Clippy__Strict_.xml
generated
|
@ -1,15 +1,15 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Clippy (Strict)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="clippy -- -W clippy::pedantic -W clippy::nursery -W clippy::unwrap_used" />
|
||||
<option name="command" value="clippy -- -W clippy::pedantic -W clippy::unwrap_used -A clippy::cast_possible_wrap -A clippy::cast_possible_truncation -A clippy::cast_sign_loss -A clippy::cast_precision_loss -A clippy::cast_lossless -A clippy::module_name_repetitions" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<envs />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="false" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="buildTarget" value="REMOTE" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<envs />
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
|
|
11
.idea/runConfigurations/Run.xml
generated
11
.idea/runConfigurations/Run.xml
generated
|
@ -2,18 +2,17 @@
|
|||
<configuration default="false" name="Run" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="command" value="run --package ironbar --bin ironbar" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||
<envs>
|
||||
<env name="IRONBAR_LOG" value="info" />
|
||||
<env name="IRONBAR_CONFIG" value="examples/test.corn" />
|
||||
</envs>
|
||||
<option name="emulateTerminal" value="true" />
|
||||
<option name="channel" value="DEFAULT" />
|
||||
<option name="requiredFeatures" value="true" />
|
||||
<option name="allFeatures" value="false" />
|
||||
<option name="emulateTerminal" value="false" />
|
||||
<option name="withSudo" value="false" />
|
||||
<option name="buildTarget" value="REMOTE" />
|
||||
<option name="backtrace" value="SHORT" />
|
||||
<envs>
|
||||
<env name="IRONBAR_CONFIG" value="examples/config.json" />
|
||||
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
||||
<env name="RUST_LOG" value="debug" />
|
||||
</envs>
|
||||
<option name="isRedirectInput" value="false" />
|
||||
<option name="redirectInputPath" value="" />
|
||||
<method v="2">
|
||||
|
|
404
Cargo.lock
generated
404
Cargo.lock
generated
|
@ -283,18 +283,18 @@ dependencies = [
|
|||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -306,6 +306,9 @@ name = "bitflags"
|
|||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
|
@ -331,6 +334,16 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.0"
|
||||
|
@ -351,9 +364,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
|||
|
||||
[[package]]
|
||||
name = "cairo-rs"
|
||||
version = "0.18.3"
|
||||
version = "0.18.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f33613627f0dea6a731b0605101fad59ba4f193a52c96c4687728d822605a8a1"
|
||||
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"cairo-sys-rs",
|
||||
|
@ -423,24 +436,22 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.35"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"pure-rust-locales",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.3"
|
||||
version = "4.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "949626d00e063efc93b6dca932419ceb5432f99769911c0b995f7e884c778813"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
@ -460,9 +471,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.3"
|
||||
version = "4.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90239a040c80f5e14809ca132ddc4176ab33d5e17e49691793296e3fcb34d72f"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
|
@ -476,16 +487,6 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.3"
|
||||
|
@ -628,50 +629,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote 1.0.35",
|
||||
"scratch",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.4"
|
||||
|
@ -817,15 +774,6 @@ version = "1.8.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-kinds"
|
||||
version = "0.5.1"
|
||||
|
@ -1270,7 +1218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "72793962ceece3863c2965d7f10c8786323b17c7adea75a515809fa20ab799a5"
|
||||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro-crate 2.0.1",
|
||||
"proc-macro-crate 2.0.0",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote 1.0.35",
|
||||
|
@ -1380,9 +1328,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4"
|
||||
checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
@ -1390,19 +1338,13 @@ dependencies = [
|
|||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.1"
|
||||
|
@ -1582,26 +1524,25 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.56"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1626,16 +1567,6 @@ version = "0.3.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
|
@ -1643,7 +1574,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.1",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1696,6 +1628,7 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
|||
name = "ironbar"
|
||||
version = "0.15.0-pre"
|
||||
dependencies = [
|
||||
"cairo-rs",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"clap",
|
||||
|
@ -1704,13 +1637,14 @@ dependencies = [
|
|||
"dirs",
|
||||
"futures-lite 2.3.0",
|
||||
"futures-signals",
|
||||
"futures-util",
|
||||
"glib",
|
||||
"gtk",
|
||||
"gtk-layer-shell",
|
||||
"hyprland",
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"libpulse-binding",
|
||||
"lua-src",
|
||||
"mlua",
|
||||
"mpd-utils",
|
||||
"mpris",
|
||||
"nix 0.27.1",
|
||||
|
@ -1733,7 +1667,6 @@ dependencies = [
|
|||
"upower_dbus",
|
||||
"walkdir",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
"zbus",
|
||||
]
|
||||
|
@ -1787,11 +1720,12 @@ checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
|
|||
|
||||
[[package]]
|
||||
name = "libcorn"
|
||||
version = "0.9.0"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07283e36c1e48784b5e24e27fd4f6deabd83ebfa05648316dcead505ecbab40f"
|
||||
checksum = "48ada41708c215eb095c72db1ca41913c347f75d382f2fc9e8d1017a22b7671e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"indexmap",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
|
@ -1844,15 +1778,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
|
@ -1867,9 +1792,9 @@ checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
|||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
|
||||
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
|
@ -1884,6 +1809,15 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lua-src"
|
||||
version = "546.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da0daa7eee611a4c30c8f5ee31af55266e26e573971ba9336d2993e2da129b2"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
|
@ -1960,10 +1894,34 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpd-utils"
|
||||
version = "0.2.0"
|
||||
name = "mlua"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7081a86d39a604868a671b0166febc1f31a4c4801d9436ab733f2664baabf8a4"
|
||||
checksum = "868d02cb5eb97761bbf6bd6922c1c7a88b8ea252bbf43bd8350a0bf8497a1fc0"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"mlua-sys",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpd-utils"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fe50e71b3206a46eff95e96549aa60953dc072baffaa04b71415024f8f254d2"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"mpd_client",
|
||||
|
@ -2257,15 +2215,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.7"
|
||||
version = "0.9.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
|
||||
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.2.16",
|
||||
"redox_syscall 0.4.1",
|
||||
"smallvec",
|
||||
"windows-sys 0.45.0",
|
||||
"windows-targets 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2410,11 +2368,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "2.0.1"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97dc5fea232fc28d2f597b37c4876b348a40e33f3b02cc975c8d006d78d94b1a"
|
||||
checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8"
|
||||
dependencies = [
|
||||
"toml_datetime",
|
||||
"toml_edit 0.20.2",
|
||||
]
|
||||
|
||||
|
@ -2551,6 +2508,15 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
|
@ -2608,13 +2574,12 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338"
|
||||
checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.22.0",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
|
@ -2637,7 +2602,6 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
|
@ -2650,13 +2614,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "ron"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300a51053b1cb55c80b7a9fde4120726ddf25ca241a1cbb926626f62fb136bff"
|
||||
checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bitflags 1.3.2",
|
||||
"base64 0.21.0",
|
||||
"bitflags 2.4.0",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2665,6 +2630,12 @@ version = "0.1.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
|
@ -2703,13 +2674,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
|
||||
checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.22.0",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.12"
|
||||
|
@ -2752,12 +2730,6 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.8.2"
|
||||
|
@ -2809,9 +2781,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.114"
|
||||
version = "1.0.115"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -2831,9 +2803,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.3"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
|
||||
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -2852,11 +2824,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.21"
|
||||
version = "0.9.34+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9d684e3ec7de3bf5466b32bd75303ac16f0736426e5a4e0d6e489559ce1249c"
|
||||
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
|
||||
dependencies = [
|
||||
"indexmap 1.9.3",
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
|
@ -3110,27 +3082,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.0.4"
|
||||
|
@ -3140,7 +3091,7 @@ dependencies = [
|
|||
"cfg-expr",
|
||||
"heck 0.4.1",
|
||||
"pkg-config",
|
||||
"toml",
|
||||
"toml 0.7.6",
|
||||
"version-compare",
|
||||
]
|
||||
|
||||
|
@ -3170,15 +3121,6 @@ dependencies = [
|
|||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.58"
|
||||
|
@ -3253,9 +3195,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.36.0"
|
||||
version = "1.37.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931"
|
||||
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
|
@ -3267,6 +3209,7 @@ dependencies = [
|
|||
"signal-hook-registry",
|
||||
"socket2 0.5.5",
|
||||
"tokio-macros",
|
||||
"tracing",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
|
@ -3318,10 +3261,22 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
name = "toml"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
|
||||
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit 0.22.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -3332,11 +3287,11 @@ version = "0.19.14"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
"winnow 0.5.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3345,9 +3300,22 @@ version = "0.20.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
"winnow 0.5.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow 0.6.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3517,12 +3485,6 @@ version = "1.10.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
|
@ -3531,9 +3493,9 @@ checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc"
|
|||
|
||||
[[package]]
|
||||
name = "universal-config"
|
||||
version = "0.4.3"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e41ae277cedae5c3a4d6dbe5d7ad5068cdbcd9e7005de28996cada89c28c776"
|
||||
checksum = "6b01452126ed3a29e5d54cc9f036c541c2ceeb152fb9234fef8a1fc3054b26ca"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"libcorn",
|
||||
|
@ -3542,15 +3504,15 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_yaml",
|
||||
"thiserror",
|
||||
"toml",
|
||||
"toml 0.8.12",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.10"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b"
|
||||
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
|
||||
|
||||
[[package]]
|
||||
name = "upower_dbus"
|
||||
|
@ -3868,12 +3830,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.0",
|
||||
"windows-targets 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4099,10 +4061,19 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
name = "winnow"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
|
@ -4165,6 +4136,7 @@ dependencies = [
|
|||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"winapi",
|
||||
|
|
34
Cargo.toml
34
Cargo.toml
|
@ -11,6 +11,7 @@ keywords = ["gtk", "bar", "wayland", "wlroots", "gtk-layer-shell"]
|
|||
[features]
|
||||
default = [
|
||||
"cli",
|
||||
"cairo",
|
||||
"clipboard",
|
||||
"clock",
|
||||
"config+all",
|
||||
|
@ -46,6 +47,8 @@ http = ["dep:reqwest"]
|
|||
"config+corn" = ["universal-config/corn"]
|
||||
"config+ron" = ["universal-config/ron"]
|
||||
|
||||
cairo = ["lua-src", "mlua", "cairo-rs"]
|
||||
|
||||
clipboard = ["nix"]
|
||||
|
||||
clock = ["chrono"]
|
||||
|
@ -71,7 +74,7 @@ upower = ["upower_dbus", "zbus", "futures-lite"]
|
|||
|
||||
volume = ["libpulse-binding"]
|
||||
|
||||
workspaces = ["futures-util"]
|
||||
workspaces = ["futures-lite"]
|
||||
"workspaces+all" = ["workspaces", "workspaces+sway", "workspaces+hyprland"]
|
||||
"workspaces+sway" = ["workspaces", "swayipc-async"]
|
||||
"workspaces+hyprland" = ["workspaces", "hyprland"]
|
||||
|
@ -81,7 +84,7 @@ workspaces = ["futures-util"]
|
|||
gtk = "0.18.1"
|
||||
gtk-layer-shell = "0.8.0"
|
||||
glib = "0.18.5"
|
||||
tokio = { version = "1.36.0", features = [
|
||||
tokio = { version = "1.37.0", features = [
|
||||
"macros",
|
||||
"rt-multi-thread",
|
||||
"time",
|
||||
|
@ -92,7 +95,7 @@ tokio = { version = "1.36.0", features = [
|
|||
] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
|
||||
tracing-error = "0.2.0"
|
||||
tracing-error = { version = "0.2.0" , default-features = false }
|
||||
tracing-appender = "0.2.3"
|
||||
strip-ansi-escapes = "0.2.0"
|
||||
color-eyre = "0.6.3"
|
||||
|
@ -102,32 +105,36 @@ dirs = "5.0.1"
|
|||
walkdir = "2.5.0"
|
||||
notify = { version = "6.1.1", default-features = false }
|
||||
wayland-client = "0.31.1"
|
||||
wayland-protocols = { version = "0.31.0", features = ["unstable", "client"] }
|
||||
wayland-protocols-wlr = { version = "0.2.0", features = ["client"] }
|
||||
smithay-client-toolkit = { version = "0.18.1", default-features = false, features = [
|
||||
"calloop",
|
||||
] }
|
||||
universal-config = { version = "0.4.3", default_features = false }
|
||||
universal-config = { version = "0.5.0", default_features = false }
|
||||
ctrlc = "3.4.2"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
# cli
|
||||
clap = { version = "4.5.3", optional = true, features = ["derive"] }
|
||||
clap = { version = "4.5.4", optional = true, features = ["derive"] }
|
||||
|
||||
# ipc
|
||||
serde_json = { version = "1.0.114", optional = true }
|
||||
|
||||
# http
|
||||
reqwest = { version = "0.12.2", optional = true }
|
||||
reqwest = { version = "0.12.3", default_features = false, features = ["default-tls", "http2"], optional = true }
|
||||
|
||||
# cairo
|
||||
lua-src = { version = "546.0.2", optional = true }
|
||||
mlua = { version = "0.9.6", optional = true, features = ["luajit"] }
|
||||
cairo-rs = { version = "0.18.5", optional = true, features = ["png"] }
|
||||
|
||||
# clipboard
|
||||
nix = { version = "0.27.1", optional = true, features = ["event"] }
|
||||
|
||||
# clock
|
||||
chrono = { version = "0.4.35", optional = true, features = ["unstable-locales"] }
|
||||
chrono = { version = "0.4.38", optional = true, default_features = false, features = ["clock", "unstable-locales"] }
|
||||
|
||||
# music
|
||||
mpd-utils = { version = "0.2.0", optional = true }
|
||||
mpd-utils = { version = "0.2.1", optional = true }
|
||||
mpris = { version = "2.0.1", optional = true }
|
||||
|
||||
# networkmanager
|
||||
|
@ -137,10 +144,9 @@ futures-signals = { version = "0.3.33", optional = true }
|
|||
sysinfo = { version = "0.29.11", optional = true }
|
||||
|
||||
# tray
|
||||
system-tray = { version = "0.2.0", optional = true }
|
||||
system-tray = { version = "0.2.0", optional = true }
|
||||
|
||||
# upower
|
||||
futures-lite = { version = "2.3.0", optional = true }
|
||||
upower_dbus = { version = "0.3.2", optional = true }
|
||||
|
||||
# volume
|
||||
|
@ -148,11 +154,11 @@ libpulse-binding = { version = "2.28.1", optional = true }
|
|||
|
||||
# workspaces
|
||||
swayipc-async = { version = "2.0.1", optional = true }
|
||||
hyprland = { version = "0.3.13", features = ["silent"], optional = true }
|
||||
futures-util = { version = "0.3.30", optional = true }
|
||||
hyprland = { version = "0.3.13", default_features = false, features = ["listener", "tokio", "silent"], optional = true }
|
||||
|
||||
# shared
|
||||
futures-lite = { version = "2.3.0", optional = true } # workspaces, upower
|
||||
regex = { version = "1.10.4", default-features = false, features = [
|
||||
"std",
|
||||
], optional = true } # music, sys_info
|
||||
zbus = { version = "3.15.2", optional = true } # networkmanager, notifications, upower
|
||||
zbus = { version = "3.15.2", default-features = false, features = ["tokio"], optional = true } # networkmanager, notifications, upower
|
||||
|
|
|
@ -49,8 +49,8 @@ dnf install libpulseaudio-devel
|
|||
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.
|
||||
As of `v0.15.0`, compiling with no features is about 50% faster.
|
||||
On a 3800X, it takes about 45 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.
|
||||
|
@ -77,6 +77,7 @@ cargo build --release --no-default-features \
|
|||
| config+corn | Enables configuration support for [Corn](https://github.com/jakestanger/corn). |
|
||||
| config+ron | Enables configuration support for [Ron](https://github.com/ron-rs/ron). |
|
||||
| **Modules** | |
|
||||
| cairo | Enables the `cairo` module |
|
||||
| clipboard | Enables the `clipboard` module. |
|
||||
| clock | Enables the `clock` module. |
|
||||
| focused | Enables the `focused` module. |
|
||||
|
@ -84,6 +85,7 @@ cargo build --release --no-default-features \
|
|||
| 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. |
|
||||
| notifications | Enables the `notiications` module. |
|
||||
| sys_info | Enables the `sys_info` module. |
|
||||
| tray | Enables the `tray` module. |
|
||||
| upower | Enables the `upower` module. |
|
||||
|
|
|
@ -332,6 +332,7 @@ For information on the `Script` type, and embedding scripts in strings, see [her
|
|||
| `show_if` | [Dynamic Boolean](dynamic-values#dynamic-boolean) | `null` | Polls the script to check its exit code. If exit code is zero, the module is shown. For other codes, it is hidden. |
|
||||
| `transition_type` | `slide_start` or `slide_end` or `crossfade` or `none` | `slide_start` | The transition animation to use when showing/hiding the widget. |
|
||||
| `transition_duration` | `integer` | `250` | The length of the transition animation to use when showing/hiding the widget. |
|
||||
| `disable_popup` | `boolean` | `false` | Prevents the popup from opening on-click for this widget. |
|
||||
|
||||
#### Appearance
|
||||
|
||||
|
|
|
@ -79,7 +79,7 @@ Responds with `ok`.
|
|||
|
||||
### `get`
|
||||
|
||||
Gets an [ironvar](ironvars) value.
|
||||
Gets an [ironvar](ironvars) value.
|
||||
|
||||
Responds with `ok_value` if the value exists, otherwise `error`.
|
||||
|
||||
|
@ -104,6 +104,20 @@ Responds with `ok`.
|
|||
}
|
||||
```
|
||||
|
||||
### list
|
||||
|
||||
Gets a list of all [ironvar](ironvars) values.
|
||||
|
||||
Responds with `ok_value`.
|
||||
|
||||
Each key/value pair is on its own `\n` separated newline. The key and value are separated by a colon and space `: `.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "list"
|
||||
}
|
||||
```
|
||||
|
||||
### `load_css`
|
||||
|
||||
Loads an additional CSS stylesheet, with hot-reloading enabled.
|
||||
|
|
|
@ -20,9 +20,11 @@
|
|||
## Custom
|
||||
|
||||
- [Power Menu](power-menu)
|
||||
- [Weather](weather)
|
||||
|
||||
# Modules
|
||||
|
||||
- [Cairo](cairo)
|
||||
- [Clipboard](clipboard)
|
||||
- [Clock](clock)
|
||||
- [Custom](custom)
|
||||
|
|
468
docs/examples/custom/Weather.md
Normal file
468
docs/examples/custom/Weather.md
Normal file
|
@ -0,0 +1,468 @@
|
|||
Creates a button on the bar which displays the current weather condition and temperature.
|
||||
Clicking the button opens a popup with forecast information for the next few days.
|
||||
|
||||
Weather information is fetched from [wttr.in](https://wttr.in) via an external script.
|
||||
You will need to set up the script to be run as a service.
|
||||
|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"end": [
|
||||
{
|
||||
"type": "custom",
|
||||
"class": "weather",
|
||||
"bar": [
|
||||
{
|
||||
"type": "button",
|
||||
"label": "#weather_current",
|
||||
"on_click": "popup:toggle"
|
||||
}
|
||||
],
|
||||
"popup": [
|
||||
{
|
||||
"type": "box",
|
||||
"orientation": "vertical",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "label",
|
||||
"name": "header",
|
||||
"label": "Forecast"
|
||||
},
|
||||
{
|
||||
"type": "box",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "box",
|
||||
"name": "dates",
|
||||
"orientation": "vertical",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-date",
|
||||
"label": "#weather_date_0"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-date",
|
||||
"label": "#weather_date_1"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-date",
|
||||
"label": "#weather_date_2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "box",
|
||||
"name": "temps",
|
||||
"orientation": "vertical",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "box",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-high",
|
||||
"label": " #weather_high_0"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-avg",
|
||||
"label": " #weather_avg_0"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-low",
|
||||
"label": " #weather_low_0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "box",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-high",
|
||||
"label": " #weather_high_1"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-avg",
|
||||
"label": " #weather_avg_1"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-low",
|
||||
"label": " #weather_low_1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "box",
|
||||
"widgets": [
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-high",
|
||||
"label": " #weather_high_2"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-avg",
|
||||
"label": " #weather_avg_2"
|
||||
},
|
||||
{
|
||||
"type": "label",
|
||||
"class": "weather-low",
|
||||
"label": " #weather_low_2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>TOML</summary>
|
||||
|
||||
```toml
|
||||
[[end]]
|
||||
type = "custom"
|
||||
class = "weather"
|
||||
|
||||
[[end.bar]]
|
||||
type = "button"
|
||||
label = "#weather_current"
|
||||
on_click = "popup:toggle"
|
||||
|
||||
[[end.popup]]
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
|
||||
[[end.popup.widgets]]
|
||||
type = "label"
|
||||
name = "header"
|
||||
label = "Forecast"
|
||||
|
||||
[[end.popup.widgets]]
|
||||
type = "box"
|
||||
|
||||
[[end.popup.widgets.widgets]]
|
||||
type = "box"
|
||||
name = "dates"
|
||||
orientation = "vertical"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-date"
|
||||
label = "#weather_date_0"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-date"
|
||||
label = "#weather_date_1"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-date"
|
||||
label = "#weather_date_2"
|
||||
|
||||
[[end.popup.widgets.widgets]]
|
||||
type = "box"
|
||||
name = "temps"
|
||||
orientation = "vertical"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets]]
|
||||
type = "box"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-high"
|
||||
label = " #weather_high_0"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-avg"
|
||||
label = " #weather_avg_0"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-low"
|
||||
label = " #weather_low_0"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets]]
|
||||
type = "box"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-high"
|
||||
label = " #weather_high_1"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-avg"
|
||||
label = " #weather_avg_1"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-low"
|
||||
label = " #weather_low_1"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets]]
|
||||
type = "box"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-high"
|
||||
label = " #weather_high_2"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-avg"
|
||||
label = " #weather_avg_2"
|
||||
|
||||
[[end.popup.widgets.widgets.widgets.widgets]]
|
||||
type = "label"
|
||||
class = "weather-low"
|
||||
label = " #weather_low_2"
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>YAML</summary>
|
||||
|
||||
```yaml
|
||||
end:
|
||||
- type: custom
|
||||
class: weather
|
||||
bar:
|
||||
- type: button
|
||||
label: '#weather_current'
|
||||
on_click: popup:toggle
|
||||
popup:
|
||||
- type: box
|
||||
orientation: vertical
|
||||
widgets:
|
||||
- type: label
|
||||
name: header
|
||||
label: Forecast
|
||||
- type: box
|
||||
widgets:
|
||||
- type: box
|
||||
name: dates
|
||||
orientation: vertical
|
||||
widgets:
|
||||
- type: label
|
||||
class: weather-date
|
||||
label: '#weather_date_0'
|
||||
- type: label
|
||||
class: weather-date
|
||||
label: '#weather_date_1'
|
||||
- type: label
|
||||
class: weather-date
|
||||
label: '#weather_date_2'
|
||||
- type: box
|
||||
name: temps
|
||||
orientation: vertical
|
||||
widgets:
|
||||
- type: box
|
||||
widgets:
|
||||
- type: label
|
||||
class: weather-high
|
||||
label: ' #weather_high_0'
|
||||
- type: label
|
||||
class: weather-avg
|
||||
label: ' #weather_avg_0'
|
||||
- type: label
|
||||
class: weather-low
|
||||
label: ' #weather_low_0'
|
||||
- type: box
|
||||
widgets:
|
||||
- type: label
|
||||
class: weather-high
|
||||
label: ' #weather_high_1'
|
||||
- type: label
|
||||
class: weather-avg
|
||||
label: ' #weather_avg_1'
|
||||
- type: label
|
||||
class: weather-low
|
||||
label: ' #weather_low_1'
|
||||
- type: box
|
||||
widgets:
|
||||
- type: label
|
||||
class: weather-high
|
||||
label: ' #weather_high_2'
|
||||
- type: label
|
||||
class: weather-avg
|
||||
label: ' #weather_avg_2'
|
||||
- type: label
|
||||
class: weather-low
|
||||
label: ' #weather_low_2'
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Corn</summary>
|
||||
|
||||
|
||||
```corn
|
||||
let {
|
||||
$weather = {
|
||||
type = "custom"
|
||||
class = "weather"
|
||||
|
||||
bar = [ { type = "button" label = "#weather_current" on_click = "popup:toggle" } ]
|
||||
popup = [ {
|
||||
type = "box"
|
||||
orientation = "vertical"
|
||||
|
||||
widgets = [
|
||||
{ type = "label" name = "header" label = "Forecast" }
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "box" name="dates" orientation = "vertical" widgets = [
|
||||
{ type = "label" class="weather-date" label = "#weather_date_0" }
|
||||
{ type = "label" class="weather-date" label = "#weather_date_1" }
|
||||
{ type = "label" class="weather-date" label = "#weather_date_2" }
|
||||
]}
|
||||
{ type = "box" name="temps" orientation = "vertical" widgets = [
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "label" class="weather-high" label = " #weather_high_0" }
|
||||
{ type = "label" class="weather-avg" label = " #weather_avg_0" }
|
||||
{ type = "label" class="weather-low" label = " #weather_low_0" }
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "label" class="weather-high" label = " #weather_high_1" }
|
||||
{ type = "label" class="weather-avg" label = " #weather_avg_1" }
|
||||
{ type = "label" class="weather-low" label = " #weather_low_1" }
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "box"
|
||||
widgets = [
|
||||
{ type = "label" class="weather-high" label = " #weather_high_2" }
|
||||
{ type = "label" class="weather-avg" label = " #weather_avg_2" }
|
||||
{ type = "label" class="weather-low" label = " #weather_low_2" }
|
||||
]
|
||||
}
|
||||
] }
|
||||
]
|
||||
}
|
||||
]
|
||||
} ]
|
||||
}
|
||||
} in {
|
||||
end = [ $weather ]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Script
|
||||
|
||||
Run the following script on a timer. Ensure to fill out your city name.
|
||||
|
||||
```js
|
||||
#!/usr/bin/env zx
|
||||
|
||||
const location = "Canterbury";
|
||||
|
||||
// JS uses Sunday as first day
|
||||
const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
||||
|
||||
// bar logic
|
||||
|
||||
|
||||
const data = await fetch(`https://wttr.in/${location}?format=%c %t|%m %t|%S|%s`)
|
||||
.then(r => r.text());
|
||||
|
||||
const [day, night, sunrise, sunset] = data.replaceAll("+", "").split("|");
|
||||
const [sunriseH, sunriseM, sunriseS] = sunrise.split(":");
|
||||
const [sunsetH, sunsetM, sunsetS] = sunset.split(":");
|
||||
|
||||
const currentTime = new Date();
|
||||
|
||||
const sunriseTime = new Date(currentTime);
|
||||
sunriseTime.setHours(sunriseH);
|
||||
sunriseTime.setMinutes(sunriseM);
|
||||
sunriseTime.setSeconds(sunriseS);
|
||||
|
||||
const sunsetTime = new Date(currentTime);
|
||||
sunsetTime.setHours(sunsetH);
|
||||
sunsetTime.setMinutes(sunsetM);
|
||||
sunsetTime.setSeconds(sunsetS);
|
||||
|
||||
let value = day;
|
||||
if(currentTime < sunriseTime || currentTime > sunsetTime) value = night;
|
||||
|
||||
await $`ironbar set weather_current ${value}`;
|
||||
|
||||
// popup logic
|
||||
|
||||
const forecast = await fetch(`https://wttr.in/${location}?format=j1`).then(r => r.json());
|
||||
|
||||
for (const i in forecast.weather) {
|
||||
const report = forecast.weather[i];
|
||||
|
||||
const day = weekday[new Date(report.date).getDay()];
|
||||
|
||||
await $`ironbar set weather_date_${i} ${day}`;
|
||||
await $`ironbar set weather_avg_${i} ${report.avgtempC.padStart(2, "0")}`;
|
||||
await $`ironbar set weather_high_${i} ${report.maxtempC.padStart(2, "0")}`;
|
||||
await $`ironbar set weather_low_${i} ${report.mintempC.padStart(2, "0")}`;
|
||||
}
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
```css
|
||||
.popup-weather #header {
|
||||
font-size: 1.8em;
|
||||
padding-bottom: 0.4em;
|
||||
margin-bottom: 0.6em;
|
||||
border-bottom: 1px solid @color-border;
|
||||
}
|
||||
|
||||
.popup-weather .weather-date {
|
||||
font-size: 1.5em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.popup-weather .weather-avg {
|
||||
margin-left: 0.5em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
this is a hack to align the different font sizes on left/right
|
||||
you may need to adjust for different fonts
|
||||
*/
|
||||
.popup-weather #temps label {
|
||||
padding-top: 0.2em;
|
||||
margin-bottom: 0.7em;
|
||||
}
|
||||
```
|
215
docs/modules/Cairo.md
Normal file
215
docs/modules/Cairo.md
Normal file
|
@ -0,0 +1,215 @@
|
|||
Allows you to render custom content using the Lua and the Cairo drawing library.
|
||||
This is an advanced feature which provides a powerful escape hatch, allowing you to fetch data and render anything
|
||||
using an embedded scripting environment.
|
||||
|
||||
Scripts are automatically hot-reloaded.
|
||||
|
||||
> [!NOTE]
|
||||
> The Lua engine uses LuaJIT 5.1, and requires the use of a library called `lgi`.
|
||||
> Ensure you have the correct lua-lgi package installed.
|
||||
|
||||

|
||||
|
||||
## Configuration
|
||||
|
||||
> Type: `cairo`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|--------------------|-----------|---------|----------------------------------------------------|
|
||||
| `path` | `string` | `null` | The path to the Lua script to load. |
|
||||
| `frequency` | `float` | `200` | The number of milliseconds between each draw call. |
|
||||
| `width` | `integer` | `42` | The canvas width in pixels. |
|
||||
| `height` | `integer` | `42` | The canvas height in pixels. |
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
|
||||
```json
|
||||
{
|
||||
"center": [
|
||||
{
|
||||
"type": "cairo",
|
||||
"path": ".config/ironbar/clock.lua",
|
||||
"frequency": 100,
|
||||
"width": 300,
|
||||
"height": 300
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>TOML</summary>
|
||||
|
||||
```toml
|
||||
[[center]]
|
||||
type = "cairo"
|
||||
path = ".config/ironbar/clock.lua"
|
||||
frequency = 100
|
||||
width = 300
|
||||
height = 300
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>YAML</summary>
|
||||
|
||||
```yaml
|
||||
center:
|
||||
- type: cairo
|
||||
path: .config/ironbar/clock.lua
|
||||
frequency: 100
|
||||
width: 300
|
||||
height: 300
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Corn</summary>
|
||||
|
||||
```corn
|
||||
let {
|
||||
$config_dir = ".config/ironbar"
|
||||
$cairo = {
|
||||
type = "cairo"
|
||||
path = "$config_dir/clock.lua"
|
||||
frequency = 100
|
||||
width = 300
|
||||
height = 300
|
||||
}
|
||||
} in {
|
||||
center = [ $cairo ]
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Script
|
||||
|
||||
Every script must contain a function called `draw`.
|
||||
This takes a single parameter, which is the Cairo context.
|
||||
|
||||
Outside of this, you can do whatever you like.
|
||||
The full lua `stdlib` is available, and you can load in additional system packages as desired.
|
||||
|
||||
The most basic example, which draws a red square, can be seen below:
|
||||
|
||||
```lua
|
||||
function draw(cr)
|
||||
cr:set_source_rgb(1.0, 0.0, 0.0)
|
||||
cr:paint()
|
||||
end
|
||||
```
|
||||
|
||||
A longer example, used to create the clock in the image at the top of the page, is shown below:
|
||||
|
||||
<details>
|
||||
<summary>Circle clock</summary>
|
||||
|
||||
```lua
|
||||
function get_ms()
|
||||
local ms = tostring(io.popen('date +%s%3N'):read('a')):sub(-4, 9999)
|
||||
return tonumber(ms) / 1000
|
||||
end
|
||||
|
||||
function draw(cr)
|
||||
local center_x = 150
|
||||
local center_y = 150
|
||||
local radius = 130
|
||||
|
||||
local date_table = os.date("*t")
|
||||
|
||||
local hours = date_table["hour"]
|
||||
local minutes = date_table["min"]
|
||||
local seconds = date_table["sec"]
|
||||
local ms = get_ms()
|
||||
|
||||
|
||||
local label_seconds = seconds
|
||||
seconds = seconds + ms
|
||||
|
||||
local hours_str = tostring(hours)
|
||||
if string.len(hours_str) == 1 then
|
||||
hours_str = "0" .. hours_str
|
||||
end
|
||||
|
||||
local minutes_str = tostring(minutes)
|
||||
if string.len(minutes_str) == 1 then
|
||||
minutes_str = "0" .. minutes_str
|
||||
end
|
||||
|
||||
local seconds_str = tostring(label_seconds)
|
||||
if string.len(seconds_str) == 1 then
|
||||
seconds_str = "0" .. seconds_str
|
||||
end
|
||||
|
||||
local font_size = radius / 5.5
|
||||
|
||||
cr:set_source_rgb(1.0, 1.0, 1.0)
|
||||
|
||||
cr:move_to(center_x - font_size * 2.5 + 10, center_y + font_size / 2.5)
|
||||
cr:set_font_size(font_size)
|
||||
cr:show_text(hours_str .. ':' .. minutes_str .. ':' .. seconds_str)
|
||||
cr:stroke()
|
||||
|
||||
if hours > 12 then
|
||||
hours = hours - 12
|
||||
end
|
||||
|
||||
local line_width = radius / 8
|
||||
local start_angle = -math.pi / 2
|
||||
|
||||
local end_angle = start_angle + ((hours + minutes / 60 + seconds / 3600) / 12) * 2 * math.pi
|
||||
cr:set_line_width(line_width)
|
||||
cr:arc(center_x, center_y, radius, start_angle, end_angle)
|
||||
cr:stroke()
|
||||
|
||||
end_angle = start_angle + ((minutes + seconds / 60) / 60) * 2 * math.pi
|
||||
cr:set_line_width(line_width)
|
||||
cr:arc(center_x, center_y, radius * 0.8, start_angle, end_angle)
|
||||
cr:stroke()
|
||||
|
||||
if seconds == 0 then
|
||||
seconds = 60
|
||||
end
|
||||
|
||||
end_angle = start_angle + (seconds / 60) * 2 * math.pi
|
||||
cr:set_line_width(line_width)
|
||||
cr:arc(center_x, center_y, radius * 0.6, start_angle, end_angle)
|
||||
cr:stroke()
|
||||
|
||||
return 0
|
||||
end
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
> [!TIP]
|
||||
> The C documentation for the Cairo context interface can be found [here](https://www.cairographics.org/manual/cairo-cairo-t.html).
|
||||
> The Lua interface provides a slightly friendlier API which restructures things slightly.
|
||||
> The `cairo_` prefix is dropped, and the `cairo_t *cr` parameters are replaced with a namespaced call.
|
||||
> For example, `cairo_paint (cairo_t *cr)` becomes `cr:paint()`
|
||||
|
||||
> [!TIP]
|
||||
> Ironbar's Cairo module has similar functionality to the popular Conky program.
|
||||
> You can often re-use scripts with little work.
|
||||
|
||||
### Initialization
|
||||
|
||||
You can optionally create an `init.lua` file in your config directory.
|
||||
Any code in here will be executed once, on bar startup.
|
||||
|
||||
As variables and functions are global by default in Lua,
|
||||
this provides a mechanism for sharing code between multiple modules.
|
||||
|
||||
## Styling
|
||||
|
||||
| Selector | Description |
|
||||
|----------|-------------------------|
|
||||
| `.cairo` | Cairo widget container. |
|
||||
|
||||
For more information on styling, please see the [styling guide](styling-guide).
|
|
@ -13,6 +13,7 @@ Clicking on the widget opens a popup with the time and a calendar.
|
|||
| `format` | `string` | `%d/%m/%Y %H:%M` | Date/time format string. Pango markup is supported. |
|
||||
| `format_popup` | `string` | `%H:%M:%S` | Date/time format string to display in the popup header. Pango markup is supported. |
|
||||
| `locale` | `string` | `$LC_TIME` or `$LANG` or `'POSIX'` | Locale to use (eg `en_GB`). Defaults to the system language (reading from env var). |
|
||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the time on the clock button. |
|
||||
|
||||
> Detail on available tokens can be found here: <https://docs.rs/chrono/latest/chrono/format/strftime/index.html>
|
||||
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
Allows you to compose custom modules consisting of multiple widgets, including popups.
|
||||
Allows you to compose custom modules consisting of multiple modules and widgets, including popups.
|
||||
Labels can display dynamic content from scripts, and buttons can interact with the bar or execute commands on click.
|
||||
|
||||
The module provides a set of utility widgets, such as containers, labels and buttons.
|
||||
In addition to these, you can also add any native module.
|
||||
Paired with the other custom modules such as Cairo,
|
||||
this provides a powerful declarative interface for constructing your own interfaces.
|
||||
|
||||
If you only intend to run a single script, prefer the [script](script) module,
|
||||
or [label](label) if you only need a single text label.
|
||||
|
||||
|
@ -13,6 +18,11 @@ or [label](label) if you only need a single text label.
|
|||
This module can be quite fiddly to configure as you effectively have to build a tree of widgets by hand.
|
||||
It is well worth looking at the examples.
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|---------|------------------------|------------|------------------------------------------|
|
||||
| `bar` | `(Module or Widget)[]` | `[]` | Modules and widgets to add to the bar. |
|
||||
| `popup` | `(Module or Widget)[]` | `null` | Modules and widgets to add to the popup. |
|
||||
|
||||
### `Widget`
|
||||
|
||||
There are many widget types, each with their own config options.
|
||||
|
@ -36,7 +46,7 @@ A container to place nested widgets inside.
|
|||
| Name | Type | Default | Description |
|
||||
|---------------|------------------------------------------------------------|----------------|-------------------------------------------------------------------|
|
||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Whether child widgets should be horizontally or vertically added. |
|
||||
| `widgets` | `Widget[]` | `[]` | List of widgets to add to this box. |
|
||||
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this box. |
|
||||
|
||||
#### Label
|
||||
|
||||
|
@ -47,6 +57,7 @@ A text label. Pango markup is supported.
|
|||
| Name | Type | Default | Description |
|
||||
|---------|-------------------------------------------------|---------|---------------------------------------------------------------------|
|
||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. |
|
||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the label. |
|
||||
|
||||
#### Button
|
||||
|
||||
|
@ -54,10 +65,12 @@ A clickable button, which can run a command when clicked.
|
|||
|
||||
> Type `button`
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|------------|-------------------------------------------------|---------|---------------------------------------------------------------------|
|
||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. |
|
||||
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
|
||||
| Name | Type | Default | Description |
|
||||
|------------|-------------------------------------------------|---------|--------------------------------------------------------------------------------------------------|
|
||||
| `label` | [Dynamic String](dynamic-values#dynamic-string) | `null` | Widget text label. Pango markup and embedded scripts are supported. Ignored if `widgets` is set. |
|
||||
| `widgets` | `(Module or Widget)[]` | `[]` | List of modules/widgets to add to this button. |
|
||||
| `on_click` | `string [command]` | `null` | Command to execute. More on this [below](#commands). |
|
||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the button. |
|
||||
|
||||
#### Image
|
||||
|
||||
|
@ -197,6 +210,7 @@ to help get your head around what's going on:
|
|||
<button class="power-btn" label="" on_click="!reboot" />
|
||||
</box>
|
||||
<label name="uptime" label="Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}" />
|
||||
<clock disable_popup="true" />
|
||||
</box>
|
||||
</popup>
|
||||
</custom>
|
||||
|
@ -252,6 +266,10 @@ to help get your head around what's going on:
|
|||
"label": "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}",
|
||||
"name": "uptime",
|
||||
"type": "label"
|
||||
},
|
||||
{
|
||||
"type": "clock",
|
||||
"disable_popup": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -309,6 +327,10 @@ type = 'button'
|
|||
label = '''Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}'''
|
||||
name = 'uptime'
|
||||
type = 'label'
|
||||
|
||||
[[end.popup.widgets]]
|
||||
type = 'clock'
|
||||
disable_popup = true
|
||||
```
|
||||
|
||||
</details>
|
||||
|
@ -345,6 +367,8 @@ end:
|
|||
- label: 'Uptime: {{30000:uptime -p | cut -d '' '' -f2-}}'
|
||||
name: uptime
|
||||
type: label
|
||||
- type: clock
|
||||
disable_popup: true
|
||||
type: custom
|
||||
```
|
||||
|
||||
|
@ -370,6 +394,7 @@ let {
|
|||
]
|
||||
}
|
||||
{ type = "label" name = "uptime" label = "Uptime: {{30000:uptime -p | cut -d ' ' -f2-}}" }
|
||||
{ type = "clock" disable_popup = true }
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,8 @@ Pango markup is supported.
|
|||
| `interval.temps` | `integer` | `5` | Seconds between refreshing temperature data |
|
||||
| `interval.disks` | `integer` | `5` | Seconds between refreshing disk data |
|
||||
| `interval.network` | `integer` | `5` | Seconds between refreshing network data |
|
||||
| `orientation` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | Orientation of the labels. |
|
||||
| `direction` | `'horizontal'` or `'vertical'` (shorthand: `'h'` or `'v'`) | `'horizontal'` | How the labels are laid out (not the rotation of an individual label). |
|
||||
|
||||
<details>
|
||||
<summary>JSON</summary>
|
||||
|
|
24
flake.lock
generated
24
flake.lock
generated
|
@ -7,11 +7,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1708794349,
|
||||
"narHash": "sha256-jX+B1VGHT0ruHHL5RwS8L21R6miBn4B6s9iVyUJsJJY=",
|
||||
"lastModified": 1711681752,
|
||||
"narHash": "sha256-LEg6/dmEFxx6Ygti5DO9MOhGNpyB7zdxdWtzv/FCTXk=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "2c94ff9a6fbeb9f3ea0107f28688edbe9c81deaa",
|
||||
"rev": "ada0fb4dcce4561acb1eb17c59b7306d9d4a95f3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -58,11 +58,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1709200309,
|
||||
"narHash": "sha256-lKdtMbhnBNU1lr978T+wEYet3sfIXXgyiDZNEgx8CV8=",
|
||||
"lastModified": 1711715736,
|
||||
"narHash": "sha256-9slQ609YqT9bT/MNX9+5k5jltL9zgpn36DpFB7TkttM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "ebe6e807793e7c9cc59cf81225fdee1a03413811",
|
||||
"rev": "807c549feabce7eddbf259dbdcec9e0600a0660d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -72,11 +72,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1709150264,
|
||||
"narHash": "sha256-HofykKuisObPUfj0E9CJVfaMhawXkYx3G8UIFR/XQ38=",
|
||||
"lastModified": 1711703276,
|
||||
"narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9099616b93301d5cf84274b184a3a5ec69e94e08",
|
||||
"rev": "d8fe5e6c92d0d190646fb9f1056741a229980089",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -102,11 +102,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709172595,
|
||||
"narHash": "sha256-0oYeE5VkhnPA7YBl+0Utq2cYoHcfsEhSGwraCa27Vs8=",
|
||||
"lastModified": 1711851236,
|
||||
"narHash": "sha256-EJ03x3N9ihhonAttkaCrqxb0djDq3URCuDpmVPbNZhA=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "72fa0217f76020ad3aeb2dd9dd72490905b23b6f",
|
||||
"rev": "f258266af947599e8069df1c2e933189270f143a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
374
flake.nix
374
flake.nix
|
@ -1,208 +1,222 @@
|
|||
{
|
||||
description = "Nix Flake for ironbar";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
|
||||
rust-overlay = {
|
||||
url = "github:oxalica/rust-overlay";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
crane = {
|
||||
url = "github:ipetkov/crane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
naersk.url = "github:nix-community/naersk";
|
||||
};
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
rust-overlay,
|
||||
crane,
|
||||
naersk,
|
||||
...
|
||||
}: let
|
||||
inherit (nixpkgs) lib;
|
||||
genSystems = lib.genAttrs [
|
||||
"aarch64-linux"
|
||||
"x86_64-linux"
|
||||
];
|
||||
pkgsFor = system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
overlays = [
|
||||
self.overlays.default
|
||||
rust-overlay.overlays.default
|
||||
];
|
||||
};
|
||||
mkRustToolchain = pkgs:
|
||||
pkgs.rust-bin.stable.latest.default.override {
|
||||
extensions = ["rust-src"];
|
||||
};
|
||||
in {
|
||||
overlays.default = final: prev: let
|
||||
rust = mkRustToolchain final;
|
||||
craneLib = (crane.mkLib final).overrideToolchain rust;
|
||||
naersk' = prev.callPackage naersk {
|
||||
cargo = rust;
|
||||
rustc = rust;
|
||||
};
|
||||
outputs = { self, nixpkgs, rust-overlay, crane, naersk, ... }:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
genSystems = lib.genAttrs [ "aarch64-linux" "x86_64-linux" ];
|
||||
|
||||
pkgsFor = system:
|
||||
import nixpkgs {
|
||||
inherit system;
|
||||
|
||||
overlays = [ self.overlays.default rust-overlay.overlays.default ];
|
||||
};
|
||||
|
||||
mkRustToolchain = pkgs:
|
||||
pkgs.rust-bin.stable.latest.default.override {
|
||||
extensions = [ "rust-src" ];
|
||||
};
|
||||
|
||||
rustPlatform = prev.makeRustPlatform {
|
||||
cargo = rust;
|
||||
rustc = rust;
|
||||
};
|
||||
props = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
builder = "naersk";
|
||||
in {
|
||||
ironbar = let
|
||||
version = props.package.version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
in
|
||||
if builder == "crane"
|
||||
then
|
||||
prev.callPackage ./nix/default.nix {
|
||||
inherit version;
|
||||
inherit rustPlatform;
|
||||
builderName = builder;
|
||||
builder = craneLib;
|
||||
}
|
||||
else if builder == "naersk"
|
||||
then
|
||||
prev.callPackage ./nix/default.nix {
|
||||
inherit version;
|
||||
inherit rustPlatform;
|
||||
builderName = builder;
|
||||
builder = naersk';
|
||||
}
|
||||
else
|
||||
prev.callPackage ./nix/default.nix {
|
||||
inherit version;
|
||||
inherit rustPlatform;
|
||||
builderName = builder;
|
||||
overlays.default = final: prev:
|
||||
let
|
||||
rust = mkRustToolchain final;
|
||||
|
||||
craneLib = (crane.mkLib final).overrideToolchain rust;
|
||||
|
||||
naersk' = prev.callPackage naersk {
|
||||
cargo = rust;
|
||||
rustc = rust;
|
||||
};
|
||||
};
|
||||
packages = genSystems (
|
||||
system: let
|
||||
pkgs = pkgsFor system;
|
||||
in
|
||||
(self.overlays.default pkgs pkgs)
|
||||
// {
|
||||
|
||||
rustPlatform = prev.makeRustPlatform {
|
||||
cargo = rust;
|
||||
rustc = rust;
|
||||
};
|
||||
|
||||
props = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
|
||||
mkDate = longDate:
|
||||
(lib.concatStringsSep "-" [
|
||||
(builtins.substring 0 4 longDate)
|
||||
(builtins.substring 4 2 longDate)
|
||||
(builtins.substring 6 2 longDate)
|
||||
]);
|
||||
|
||||
builder = "naersk";
|
||||
in {
|
||||
ironbar = let
|
||||
version = props.package.version + "+date="
|
||||
+ (mkDate (self.lastModifiedDate or "19700101")) + "_"
|
||||
+ (self.shortRev or "dirty");
|
||||
in if builder == "crane" then
|
||||
prev.callPackage ./nix/default.nix {
|
||||
inherit version;
|
||||
inherit rustPlatform;
|
||||
builderName = builder;
|
||||
builder = craneLib;
|
||||
}
|
||||
else if builder == "naersk" then
|
||||
prev.callPackage ./nix/default.nix {
|
||||
inherit version;
|
||||
inherit rustPlatform;
|
||||
builderName = builder;
|
||||
builder = naersk';
|
||||
}
|
||||
else
|
||||
prev.callPackage ./nix/default.nix {
|
||||
inherit version;
|
||||
inherit rustPlatform;
|
||||
builderName = builder;
|
||||
};
|
||||
};
|
||||
|
||||
packages = genSystems (system:
|
||||
let pkgs = pkgsFor system;
|
||||
in (self.overlays.default pkgs pkgs) // {
|
||||
default = self.packages.${system}.ironbar;
|
||||
}
|
||||
);
|
||||
apps = genSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
in {
|
||||
default = {
|
||||
type = "app";
|
||||
program = "${pkgs.ironbar}/bin/ironbar";
|
||||
};
|
||||
ironbar = {
|
||||
type = "app";
|
||||
program = "${pkgs.ironbar}/bin/ironbar";
|
||||
};
|
||||
});
|
||||
devShells = genSystems (system: let
|
||||
pkgs = pkgsFor system;
|
||||
rust = mkRustToolchain pkgs;
|
||||
in {
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
rust
|
||||
rust-analyzer-unwrapped
|
||||
gcc
|
||||
gtk3
|
||||
gtk-layer-shell
|
||||
pkg-config
|
||||
openssl
|
||||
gdk-pixbuf
|
||||
glib
|
||||
glib-networking
|
||||
shared-mime-info
|
||||
gnome.adwaita-icon-theme
|
||||
hicolor-icon-theme
|
||||
gsettings-desktop-schemas
|
||||
libxkbcommon
|
||||
libpulseaudio
|
||||
];
|
||||
});
|
||||
|
||||
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||
};
|
||||
});
|
||||
homeManagerModules.default = {
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
cfg = config.programs.ironbar;
|
||||
defaultIronbarPackage = self.packages.${pkgs.hostPlatform.system}.default;
|
||||
jsonFormat = pkgs.formats.json {};
|
||||
in {
|
||||
options.programs.ironbar = {
|
||||
enable = lib.mkEnableOption "ironbar status bar";
|
||||
package = lib.mkOption {
|
||||
type = with lib.types; package;
|
||||
default = defaultIronbarPackage;
|
||||
description = "The package for ironbar to use.";
|
||||
};
|
||||
systemd = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = pkgs.stdenv.isLinux;
|
||||
description = "Whether to enable to systemd service for ironbar.";
|
||||
};
|
||||
style = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "The stylesheet to apply to ironbar.";
|
||||
};
|
||||
config = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = {};
|
||||
description = "The config to pass to ironbar.";
|
||||
};
|
||||
features = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.nonEmptyStr;
|
||||
default = [];
|
||||
description = "The features to be used.";
|
||||
};
|
||||
};
|
||||
config = let
|
||||
pkg = cfg.package.override {features = cfg.features;};
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
home.packages = [pkg];
|
||||
xdg.configFile = {
|
||||
"ironbar/config.json" = lib.mkIf (cfg.config != "") {
|
||||
source = jsonFormat.generate "ironbar-config" cfg.config;
|
||||
};
|
||||
"ironbar/style.css" = lib.mkIf (cfg.style != "") {
|
||||
text = cfg.style;
|
||||
};
|
||||
apps = genSystems (system:
|
||||
let pkgs = pkgsFor system;
|
||||
in rec {
|
||||
ironbar = {
|
||||
type = "app";
|
||||
program = "${pkgs.ironbar}/bin/ironbar";
|
||||
};
|
||||
systemd.user.services.ironbar = lib.mkIf cfg.systemd {
|
||||
Unit = {
|
||||
Description = "Systemd service for Ironbar";
|
||||
Requires = ["graphical-session.target"];
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkg}/bin/ironbar";
|
||||
};
|
||||
Install.WantedBy = [
|
||||
(lib.mkIf config.wayland.windowManager.hyprland.systemd.enable "hyprland-session.target")
|
||||
(lib.mkIf config.wayland.windowManager.sway.systemd.enable "sway-session.target")
|
||||
|
||||
default = ironbar;
|
||||
});
|
||||
|
||||
devShells = genSystems (system:
|
||||
let
|
||||
pkgs = pkgsFor system;
|
||||
rust = mkRustToolchain pkgs;
|
||||
|
||||
in {
|
||||
default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
rust
|
||||
rust-analyzer-unwrapped
|
||||
gcc
|
||||
gtk3
|
||||
gtk-layer-shell
|
||||
pkg-config
|
||||
openssl
|
||||
gdk-pixbuf
|
||||
glib
|
||||
glib-networking
|
||||
shared-mime-info
|
||||
gnome.adwaita-icon-theme
|
||||
hicolor-icon-theme
|
||||
gsettings-desktop-schemas
|
||||
libxkbcommon
|
||||
libpulseaudio
|
||||
luajit
|
||||
luajitPackages.lgi
|
||||
];
|
||||
|
||||
RUST_SRC_PATH = "${rust}/lib/rustlib/src/rust/library";
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
homeManagerModules.default = { config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.programs.ironbar;
|
||||
defaultIronbarPackage =
|
||||
self.packages.${pkgs.hostPlatform.system}.default;
|
||||
jsonFormat = pkgs.formats.json { };
|
||||
in {
|
||||
options.programs.ironbar = {
|
||||
enable = lib.mkEnableOption "ironbar status bar";
|
||||
|
||||
package = lib.mkOption {
|
||||
type = with lib.types; package;
|
||||
default = defaultIronbarPackage;
|
||||
description = "The package for ironbar to use.";
|
||||
};
|
||||
|
||||
systemd = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = pkgs.stdenv.isLinux;
|
||||
description = "Whether to enable to systemd service for ironbar.";
|
||||
};
|
||||
|
||||
style = lib.mkOption {
|
||||
type = lib.types.lines;
|
||||
default = "";
|
||||
description = "The stylesheet to apply to ironbar.";
|
||||
};
|
||||
|
||||
config = lib.mkOption {
|
||||
type = jsonFormat.type;
|
||||
default = { };
|
||||
description = "The config to pass to ironbar.";
|
||||
};
|
||||
|
||||
features = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.nonEmptyStr;
|
||||
default = [ ];
|
||||
description = "The features to be used.";
|
||||
};
|
||||
|
||||
};
|
||||
config = let pkg = cfg.package.override { features = cfg.features; };
|
||||
in lib.mkIf cfg.enable {
|
||||
home.packages = [ pkg ];
|
||||
|
||||
xdg.configFile = {
|
||||
"ironbar/config.json" = lib.mkIf (cfg.config != "") {
|
||||
source = jsonFormat.generate "ironbar-config" cfg.config;
|
||||
};
|
||||
|
||||
"ironbar/style.css" =
|
||||
lib.mkIf (cfg.style != "") { text = cfg.style; };
|
||||
};
|
||||
|
||||
systemd.user.services.ironbar = lib.mkIf cfg.systemd {
|
||||
Unit = {
|
||||
Description = "Systemd service for Ironbar";
|
||||
Requires = [ "graphical-session.target" ];
|
||||
};
|
||||
|
||||
Service = {
|
||||
Type = "simple";
|
||||
ExecStart = "${pkg}/bin/ironbar";
|
||||
};
|
||||
|
||||
Install.WantedBy = with config.wayland.windowManager; [
|
||||
(lib.mkIf hyprland.systemd.enable "hyprland-session.target")
|
||||
(lib.mkIf sway.systemd.enable "sway-session.target")
|
||||
(lib.mkIf river.systemd.enable "river-session.target")
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nixConfig = {
|
||||
extra-substituters = ["https://jakestanger.cachix.org"];
|
||||
extra-trusted-public-keys = ["jakestanger.cachix.org-1:VWJE7AWNe5/KOEvCQRxoE8UsI2Xs2nHULJ7TEjYm7mM="];
|
||||
extra-substituters = [ "https://cache.garnix.io" ];
|
||||
extra-trusted-public-keys =
|
||||
[ "cache.garnix.io:CTFPyKSLcx5RMJKfLo5EEPUObbA78b0YQ2DTCJXqr9g=" ];
|
||||
};
|
||||
}
|
||||
|
|
4
lua/draw.lua
Normal file
4
lua/draw.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
function(id, ptr)
|
||||
local cr = __lgi_core.record.new(cairo.Context, ptr)
|
||||
_G['__draw_' .. id](cr)
|
||||
end
|
4
lua/init.lua
Normal file
4
lua/init.lua
Normal file
|
@ -0,0 +1,4 @@
|
|||
local lgi = require('lgi')
|
||||
cairo = lgi.cairo
|
||||
|
||||
__lgi_core = require('lgi.core')
|
123
nix/default.nix
123
nix/default.nix
|
@ -14,6 +14,8 @@
|
|||
libxkbcommon,
|
||||
libpulseaudio,
|
||||
openssl,
|
||||
luajit,
|
||||
luajitPackages,
|
||||
pkg-config,
|
||||
hicolor-icon-theme,
|
||||
rustPlatform,
|
||||
|
@ -23,74 +25,105 @@
|
|||
builderName ? "nix",
|
||||
builder ? {},
|
||||
}: let
|
||||
hasFeature = f: features == [ ] || builtins.elem f features;
|
||||
|
||||
basePkg = rec {
|
||||
inherit version;
|
||||
|
||||
pname = "ironbar";
|
||||
|
||||
src = builtins.path {
|
||||
name = "ironbar";
|
||||
path = lib.cleanSource ../.;
|
||||
};
|
||||
nativeBuildInputs = [pkg-config wrapGAppsHook gobject-introspection];
|
||||
buildInputs = [gtk3 gdk-pixbuf glib gtk-layer-shell glib-networking shared-mime-info gnome.adwaita-icon-theme hicolor-icon-theme gsettings-desktop-schemas libxkbcommon libpulseaudio openssl];
|
||||
propagatedBuildInputs = [
|
||||
gtk3
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
wrapGAppsHook
|
||||
gobject-introspection
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
gtk3
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk-layer-shell
|
||||
glib-networking
|
||||
shared-mime-info
|
||||
gnome.adwaita-icon-theme
|
||||
hicolor-icon-theme
|
||||
gsettings-desktop-schemas
|
||||
libxkbcommon ]
|
||||
++ (if hasFeature "http" then [ openssl ] else [])
|
||||
++ (if hasFeature "volume" then [ libpulseaudio ] else [])
|
||||
++ (if hasFeature "cairo" then [ luajit ] else []);
|
||||
|
||||
propagatedBuildInputs = [ gtk3 ];
|
||||
|
||||
lgi = luajitPackages.lgi;
|
||||
|
||||
gappsWrapperArgs = ''
|
||||
# Thumbnailers
|
||||
--prefix XDG_DATA_DIRS : "${gdk-pixbuf}/share"
|
||||
--prefix XDG_DATA_DIRS : "${librsvg}/share"
|
||||
--prefix XDG_DATA_DIRS : "${webp-pixbuf-loader}/share"
|
||||
--prefix XDG_DATA_DIRS : "${shared-mime-info}/share"
|
||||
|
||||
# gtk-launch
|
||||
--suffix PATH : "${lib.makeBinPath [ gtk3 ]}"
|
||||
''
|
||||
+ (if hasFeature "cairo" then ''
|
||||
--prefix LUA_PATH : "./?.lua;${lgi}/share/lua/5.1/?.lua;${lgi}/share/lua/5.1/?/init.lua;${luajit}/share/lua/5.1/\?.lua;${luajit}/share/lua/5.1/?/init.lua"
|
||||
--prefix LUA_CPATH : "./?.so;${lgi}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/?.so;${luajit}/lib/lua/5.1/loadall.so"
|
||||
'' else "");
|
||||
|
||||
preFixup = ''
|
||||
gappsWrapperArgs+=(
|
||||
# Thumbnailers
|
||||
--prefix XDG_DATA_DIRS : "${gdk-pixbuf}/share"
|
||||
--prefix XDG_DATA_DIRS : "${librsvg}/share"
|
||||
--prefix XDG_DATA_DIRS : "${webp-pixbuf-loader}/share"
|
||||
--prefix XDG_DATA_DIRS : "${shared-mime-info}/share"
|
||||
${gappsWrapperArgs}
|
||||
)
|
||||
'';
|
||||
|
||||
passthru = {
|
||||
updateScript = gnome.updateScript {
|
||||
packageName = pname;
|
||||
attrPath = "gnome.${pname}";
|
||||
};
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://github.com/JakeStanger/ironbar";
|
||||
description = "Customisable gtk-layer-shell wlroots/sway bar written in rust.";
|
||||
description =
|
||||
"Customisable gtk-layer-shell wlroots/sway bar written in rust.";
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "ironbar";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
flags = let
|
||||
noDefault =
|
||||
if features == []
|
||||
then ""
|
||||
else "--no-default-features";
|
||||
featuresStr =
|
||||
if features == []
|
||||
then ""
|
||||
else ''-F "${builtins.concatStringsSep "," features}"'';
|
||||
in [noDefault featuresStr];
|
||||
in
|
||||
if builderName == "naersk"
|
||||
then
|
||||
builder.buildPackage (basePkg
|
||||
// {
|
||||
cargoOptions = old: old ++ flags;
|
||||
})
|
||||
else if builderName == "crane"
|
||||
then
|
||||
builder.buildPackage (basePkg
|
||||
// {
|
||||
cargoExtraArgs = builtins.concatStringsSep " " flags;
|
||||
doCheck = false;
|
||||
})
|
||||
else
|
||||
rustPlatform.buildRustPackage (basePkg
|
||||
// {
|
||||
buildNoDefaultFeatures =
|
||||
if features == []
|
||||
then false
|
||||
else true;
|
||||
buildFeatures = features;
|
||||
cargoDeps = rustPlatform.importCargoLock {lockFile = ../Cargo.lock;};
|
||||
cargoLock.lockFile = ../Cargo.lock;
|
||||
cargoLock.outputHashes."stray-0.1.3" = "sha256-7mvsWZFmPWti9AiX67h6ZlWiVVRZRWIxq3pVaviOUtc=";
|
||||
})
|
||||
noDefault = if features == [ ] then "" else "--no-default-features";
|
||||
|
||||
featuresStr = if features == [ ] then
|
||||
""
|
||||
else
|
||||
''-F "${builtins.concatStringsSep "," features}"'';
|
||||
|
||||
in [ noDefault featuresStr ];
|
||||
in if builderName == "naersk" then
|
||||
builder.buildPackage (basePkg // { cargoBuildOptions = old: old ++ flags; })
|
||||
else if builderName == "crane" then
|
||||
builder.buildPackage (basePkg // {
|
||||
cargoExtraArgs = builtins.concatStringsSep " " flags;
|
||||
doCheck = false;
|
||||
})
|
||||
else
|
||||
rustPlatform.buildRustPackage (basePkg // {
|
||||
buildNoDefaultFeatures = features != [ ];
|
||||
|
||||
buildFeatures = features;
|
||||
cargoDeps = rustPlatform.importCargoLock { lockFile = ../Cargo.lock; };
|
||||
cargoLock.lockFile = ../Cargo.lock;
|
||||
cargoLock.outputHashes."stray-0.1.3" =
|
||||
"sha256-7mvsWZFmPWti9AiX67h6ZlWiVVRZRWIxq3pVaviOUtc=";
|
||||
})
|
||||
|
|
|
@ -9,6 +9,9 @@ pkgs.mkShell {
|
|||
gtk-layer-shell
|
||||
gcc
|
||||
openssl
|
||||
libpulseaudio
|
||||
luajit
|
||||
luajitPackages.lgi
|
||||
];
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
|
|
55
src/bar.rs
55
src/bar.rs
|
@ -1,7 +1,5 @@
|
|||
use crate::config::{BarConfig, BarPosition, MarginConfig, ModuleConfig};
|
||||
use crate::modules::{
|
||||
create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation,
|
||||
};
|
||||
use crate::modules::{BarModuleFactory, ModuleInfo, ModuleLocation};
|
||||
use crate::popup::Popup;
|
||||
use crate::Ironbar;
|
||||
use color_eyre::Result;
|
||||
|
@ -350,57 +348,10 @@ fn add_modules(
|
|||
ironbar: &Rc<Ironbar>,
|
||||
popup: &Rc<Popup>,
|
||||
) -> Result<()> {
|
||||
let orientation = info.bar_position.orientation();
|
||||
|
||||
macro_rules! add_module {
|
||||
($module:expr, $id:expr) => {{
|
||||
let common = $module.common.take().expect("common config to exist");
|
||||
let widget_parts = create_module(
|
||||
*$module,
|
||||
$id,
|
||||
ironbar.clone(),
|
||||
common.name.clone(),
|
||||
&info,
|
||||
&Rc::clone(&popup),
|
||||
)?;
|
||||
set_widget_identifiers(&widget_parts, &common);
|
||||
|
||||
let container = wrap_widget(&widget_parts.widget, common, orientation);
|
||||
content.add(&container);
|
||||
}};
|
||||
}
|
||||
let module_factory = BarModuleFactory::new(ironbar.clone(), popup.clone()).into();
|
||||
|
||||
for config in modules {
|
||||
let id = Ironbar::unique_id();
|
||||
match config {
|
||||
#[cfg(feature = "clipboard")]
|
||||
ModuleConfig::Clipboard(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "clock")]
|
||||
ModuleConfig::Clock(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Custom(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "focused")]
|
||||
ModuleConfig::Focused(mut module) => add_module!(module, id),
|
||||
ModuleConfig::Label(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "launcher")]
|
||||
ModuleConfig::Launcher(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "music")]
|
||||
ModuleConfig::Music(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "networkmanager")]
|
||||
ModuleConfig::NetworkManager(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "notifications")]
|
||||
ModuleConfig::Notifications(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 = "upower")]
|
||||
ModuleConfig::Upower(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "volume")]
|
||||
ModuleConfig::Volume(mut module) => add_module!(module, id),
|
||||
#[cfg(feature = "workspaces")]
|
||||
ModuleConfig::Workspaces(mut module) => add_module!(module, id),
|
||||
}
|
||||
config.create(&module_factory, content, info)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{await_sync, register_client};
|
||||
use crate::{await_sync, register_fallible_client};
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
|
@ -141,4 +141,4 @@ pub trait WorkspaceClient: Debug + Send + Sync {
|
|||
fn subscribe_workspace_change(&self) -> broadcast::Receiver<WorkspaceUpdate>;
|
||||
}
|
||||
|
||||
register_client!(dyn WorkspaceClient, workspaces);
|
||||
register_fallible_client!(dyn WorkspaceClient, workspaces);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||
use crate::{await_sync, send, spawn};
|
||||
use color_eyre::{Report, Result};
|
||||
use futures_util::StreamExt;
|
||||
use futures_lite::StreamExt;
|
||||
use std::sync::Arc;
|
||||
use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent};
|
||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||
|
|
41
src/clients/lua.rs
Normal file
41
src/clients/lua.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use mlua::Lua;
|
||||
use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
use tracing::{debug, error};
|
||||
|
||||
/// Wrapper around Lua instance
|
||||
/// to create a singleton and handle initialization.
|
||||
#[derive(Debug)]
|
||||
pub struct LuaEngine {
|
||||
lua: Lua,
|
||||
}
|
||||
|
||||
impl LuaEngine {
|
||||
pub fn new(config_dir: &Path) -> Self {
|
||||
let lua = unsafe { Lua::unsafe_new() };
|
||||
|
||||
let user_init = config_dir.join("init.lua");
|
||||
if user_init.exists() {
|
||||
debug!("loading user init script");
|
||||
|
||||
if let Err(err) = lua.load(user_init).exec() {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
debug!("loading internal init script");
|
||||
if let Err(err) = lua.load(include_str!("../../lua/init.lua")).exec() {
|
||||
error!("{err:?}");
|
||||
}
|
||||
|
||||
Self { lua }
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for LuaEngine {
|
||||
type Target = Lua;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.lua
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
use crate::Ironbar;
|
||||
use crate::{await_sync, Ironbar};
|
||||
use color_eyre::Result;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub mod clipboard;
|
||||
#[cfg(feature = "workspaces")]
|
||||
pub mod compositor;
|
||||
#[cfg(feature = "cairo")]
|
||||
pub mod lua;
|
||||
#[cfg(feature = "music")]
|
||||
pub mod music;
|
||||
#[cfg(feature = "networkmanager")]
|
||||
|
@ -28,6 +33,8 @@ pub struct Clients {
|
|||
workspaces: Option<Arc<dyn compositor::WorkspaceClient>>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: Option<Arc<clipboard::Client>>,
|
||||
#[cfg(feature = "cairo")]
|
||||
lua: Option<Rc<lua::LuaEngine>>,
|
||||
#[cfg(feature = "music")]
|
||||
music: std::collections::HashMap<music::ClientType, Arc<dyn music::MusicClient>>,
|
||||
#[cfg(feature = "networkmanager")]
|
||||
|
@ -42,6 +49,8 @@ pub struct Clients {
|
|||
volume: Option<Arc<volume::Client>>,
|
||||
}
|
||||
|
||||
pub type ClientResult<T> = Result<Arc<T>>;
|
||||
|
||||
impl Clients {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
|
@ -63,12 +72,23 @@ impl Clients {
|
|||
}
|
||||
|
||||
#[cfg(feature = "workspaces")]
|
||||
pub fn workspaces(&mut self) -> Arc<dyn compositor::WorkspaceClient> {
|
||||
// TODO: Error handling here isn't great - should throw a user-friendly error & exit
|
||||
self.workspaces
|
||||
.get_or_insert_with(|| {
|
||||
compositor::Compositor::create_workspace_client().expect("to be valid compositor")
|
||||
})
|
||||
pub fn workspaces(&mut self) -> ClientResult<dyn compositor::WorkspaceClient> {
|
||||
let client = match &self.workspaces {
|
||||
Some(workspaces) => workspaces.clone(),
|
||||
None => {
|
||||
let client = compositor::Compositor::create_workspace_client()?;
|
||||
self.workspaces.replace(client.clone());
|
||||
client
|
||||
}
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[cfg(feature = "cairo")]
|
||||
pub fn lua(&mut self, config_dir: &Path) -> Rc<lua::LuaEngine> {
|
||||
self.lua
|
||||
.get_or_insert_with(|| Rc::new(lua::LuaEngine::new(config_dir)))
|
||||
.clone()
|
||||
}
|
||||
|
||||
|
@ -88,29 +108,35 @@ impl Clients {
|
|||
}
|
||||
|
||||
#[cfg(feature = "notifications")]
|
||||
pub fn notifications(&mut self) -> Arc<swaync::Client> {
|
||||
self.notifications
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(crate::await_sync(async { swaync::Client::new().await }))
|
||||
})
|
||||
.clone()
|
||||
pub fn notifications(&mut self) -> ClientResult<swaync::Client> {
|
||||
let client = match &self.notifications {
|
||||
Some(client) => client.clone(),
|
||||
None => {
|
||||
let client = await_sync(async { swaync::Client::new().await })?;
|
||||
let client = Arc::new(client);
|
||||
self.notifications.replace(client.clone());
|
||||
client
|
||||
}
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[cfg(feature = "tray")]
|
||||
pub fn tray(&mut self) -> Arc<tray::Client> {
|
||||
// TODO: Error handling here isn't great - should throw a user-friendly error
|
||||
self.tray
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(crate::await_sync(async {
|
||||
let service_name =
|
||||
format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id());
|
||||
pub fn tray(&mut self) -> ClientResult<tray::Client> {
|
||||
let client = match &self.tray {
|
||||
Some(client) => client.clone(),
|
||||
None => {
|
||||
let service_name = format!("{}-{}", env!("CARGO_CRATE_NAME"), Ironbar::unique_id());
|
||||
|
||||
tray::Client::new(&service_name)
|
||||
.await
|
||||
.expect("to be able to start client")
|
||||
}))
|
||||
})
|
||||
.clone()
|
||||
let client = await_sync(async { tray::Client::new(&service_name).await })?;
|
||||
let client = Arc::new(client);
|
||||
self.tray.replace(client.clone());
|
||||
client
|
||||
}
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
#[cfg(feature = "upower")]
|
||||
|
@ -137,6 +163,14 @@ pub trait ProvidesClient<T: ?Sized> {
|
|||
fn provide(&self) -> Arc<T>;
|
||||
}
|
||||
|
||||
/// Types implementing this trait
|
||||
/// indicate that they provide a singleton client instance of type `T`,
|
||||
/// which may fail to be created.
|
||||
pub trait ProvidesFallibleClient<T: ?Sized> {
|
||||
/// Returns a singleton client instance of type `T`.
|
||||
fn try_provide(&self) -> ClientResult<T>;
|
||||
}
|
||||
|
||||
/// Generates a `ProvidesClient` impl block on `WidgetContext`
|
||||
/// for the provided `$ty` (first argument) client type.
|
||||
///
|
||||
|
@ -159,3 +193,26 @@ macro_rules! register_client {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Generates a `ProvidesClient` impl block on `WidgetContext`
|
||||
/// for the provided `$ty` (first argument) client type.
|
||||
///
|
||||
/// The implementation calls `$method` (second argument)
|
||||
/// on the `Clients` struct to obtain the client instance.
|
||||
///
|
||||
/// # Example
|
||||
/// `register_client!(Client, clipboard);`
|
||||
#[macro_export]
|
||||
macro_rules! register_fallible_client {
|
||||
($ty:ty, $method:ident) => {
|
||||
impl<TSend, TReceive> $crate::clients::ProvidesFallibleClient<$ty>
|
||||
for $crate::modules::WidgetContext<TSend, TReceive>
|
||||
where
|
||||
TSend: Clone,
|
||||
{
|
||||
fn try_provide(&self) -> color_eyre::Result<std::sync::Arc<$ty>> {
|
||||
self.ironbar.clients.borrow_mut().$method()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod dbus;
|
||||
|
||||
use crate::{register_client, send, spawn};
|
||||
use crate::{register_fallible_client, send, spawn};
|
||||
use color_eyre::{Report, Result};
|
||||
use dbus::SwayNcProxy;
|
||||
use serde::Deserialize;
|
||||
|
@ -24,9 +24,9 @@ type GetSubscribeData = (bool, bool, u32, bool);
|
|||
impl From<GetSubscribeData> for Event {
|
||||
fn from((dnd, cc_open, count, inhibited): (bool, bool, u32, bool)) -> Self {
|
||||
Self {
|
||||
count,
|
||||
dnd,
|
||||
cc_open,
|
||||
count,
|
||||
inhibited,
|
||||
}
|
||||
}
|
||||
|
@ -40,15 +40,13 @@ pub struct Client {
|
|||
}
|
||||
|
||||
impl Client {
|
||||
pub async fn new() -> Self {
|
||||
let dbus = Box::pin(zbus::Connection::session())
|
||||
.await
|
||||
.expect("failed to create connection to system bus");
|
||||
pub async fn new() -> Result<Self> {
|
||||
let dbus = Box::pin(zbus::Connection::session()).await?;
|
||||
|
||||
let proxy = SwayNcProxy::new(&dbus).await.unwrap();
|
||||
let proxy = SwayNcProxy::new(&dbus).await?;
|
||||
let (tx, rx) = broadcast::channel(8);
|
||||
|
||||
let mut stream = proxy.receive_subscribe_v2().await.unwrap();
|
||||
let mut stream = proxy.receive_subscribe_v2().await?;
|
||||
|
||||
{
|
||||
let tx = tx.clone();
|
||||
|
@ -62,7 +60,7 @@ impl Client {
|
|||
});
|
||||
}
|
||||
|
||||
Self { proxy, tx, _rx: rx }
|
||||
Ok(Self { proxy, tx, _rx: rx })
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
||||
|
@ -85,4 +83,4 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
register_client!(Client, notifications);
|
||||
register_fallible_client!(Client, notifications);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::register_client;
|
||||
use crate::register_fallible_client;
|
||||
pub use system_tray::client::Client;
|
||||
|
||||
register_client!(Client, tray);
|
||||
register_fallible_client!(Client, tray);
|
||||
|
|
|
@ -27,6 +27,8 @@ pub struct CommonConfig {
|
|||
pub on_mouse_exit: Option<ScriptInput>,
|
||||
|
||||
pub tooltip: Option<String>,
|
||||
#[serde(default)]
|
||||
pub disable_popup: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
@ -38,6 +40,34 @@ pub enum TransitionType {
|
|||
SlideEnd,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Clone, Copy)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ModuleOrientation {
|
||||
#[default]
|
||||
#[serde(alias = "h")]
|
||||
Horizontal,
|
||||
#[serde(alias = "v")]
|
||||
Vertical,
|
||||
}
|
||||
|
||||
impl ModuleOrientation {
|
||||
pub const fn to_angle(self) -> f64 {
|
||||
match self {
|
||||
Self::Horizontal => 0.0,
|
||||
Self::Vertical => 90.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ModuleOrientation> for Orientation {
|
||||
fn from(o: ModuleOrientation) -> Self {
|
||||
match o {
|
||||
ModuleOrientation::Horizontal => Self::Horizontal,
|
||||
ModuleOrientation::Vertical => Self::Vertical,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TransitionType {
|
||||
pub const fn to_revealer_transition_type(
|
||||
&self,
|
||||
|
|
|
@ -2,6 +2,8 @@ mod common;
|
|||
mod r#impl;
|
||||
mod truncate;
|
||||
|
||||
#[cfg(feature = "cairo")]
|
||||
use crate::modules::cairo::CairoModule;
|
||||
#[cfg(feature = "clipboard")]
|
||||
use crate::modules::clipboard::ClipboardModule;
|
||||
#[cfg(feature = "clock")]
|
||||
|
@ -29,16 +31,21 @@ use crate::modules::upower::UpowerModule;
|
|||
use crate::modules::volume::VolumeModule;
|
||||
#[cfg(feature = "workspaces")]
|
||||
use crate::modules::workspaces::WorkspacesModule;
|
||||
|
||||
use crate::modules::{AnyModuleFactory, ModuleFactory, ModuleInfo};
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::Result;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use self::common::{CommonConfig, TransitionType};
|
||||
pub use self::common::{CommonConfig, ModuleOrientation, TransitionType};
|
||||
pub use self::truncate::TruncateMode;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum ModuleConfig {
|
||||
#[cfg(feature = "cairo")]
|
||||
Cairo(Box<CairoModule>),
|
||||
#[cfg(feature = "clipboard")]
|
||||
Clipboard(Box<ClipboardModule>),
|
||||
#[cfg(feature = "clock")]
|
||||
|
@ -68,6 +75,51 @@ pub enum ModuleConfig {
|
|||
Workspaces(Box<WorkspacesModule>),
|
||||
}
|
||||
|
||||
impl ModuleConfig {
|
||||
pub fn create(
|
||||
self,
|
||||
module_factory: &AnyModuleFactory,
|
||||
container: >k::Box,
|
||||
info: &ModuleInfo,
|
||||
) -> Result<()> {
|
||||
macro_rules! create {
|
||||
($module:expr) => {
|
||||
module_factory.create(*$module, container, info)
|
||||
};
|
||||
}
|
||||
|
||||
match self {
|
||||
#[cfg(feature = "cairo")]
|
||||
Self::Cairo(module) => create!(module),
|
||||
#[cfg(feature = "clipboard")]
|
||||
Self::Clipboard(module) => create!(module),
|
||||
#[cfg(feature = "clock")]
|
||||
Self::Clock(module) => create!(module),
|
||||
Self::Custom(module) => create!(module),
|
||||
#[cfg(feature = "focused")]
|
||||
Self::Focused(module) => create!(module),
|
||||
Self::Label(module) => create!(module),
|
||||
#[cfg(feature = "launcher")]
|
||||
Self::Launcher(module) => create!(module),
|
||||
#[cfg(feature = "music")]
|
||||
Self::Music(module) => create!(module),
|
||||
#[cfg(feature = "notifications")]
|
||||
Self::Notifications(module) => create!(module),
|
||||
Self::Script(module) => create!(module),
|
||||
#[cfg(feature = "sys_info")]
|
||||
Self::SysInfo(module) => create!(module),
|
||||
#[cfg(feature = "tray")]
|
||||
Self::Tray(module) => create!(module),
|
||||
#[cfg(feature = "upower")]
|
||||
Self::Upower(module) => create!(module),
|
||||
#[cfg(feature = "volume")]
|
||||
Self::Volume(module) => create!(module),
|
||||
#[cfg(feature = "workspaces")]
|
||||
Self::Workspaces(module) => create!(module),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub enum BarEntryConfig {
|
||||
Single(BarConfig),
|
||||
|
|
|
@ -32,6 +32,9 @@ pub enum Command {
|
|||
key: Box<str>,
|
||||
},
|
||||
|
||||
/// Gets the current value of all `ironvar`s.
|
||||
List,
|
||||
|
||||
/// Load an additional CSS stylesheet.
|
||||
/// The sheet is automatically hot-reloaded.
|
||||
LoadCss {
|
||||
|
|
|
@ -129,7 +129,7 @@ impl Ipc {
|
|||
ironbar.reload_config();
|
||||
|
||||
for output in outputs {
|
||||
match crate::load_output_bars(ironbar, application, output) {
|
||||
match crate::load_output_bars(ironbar, application, &output) {
|
||||
Ok(mut bars) => ironbar.bars.borrow_mut().append(&mut bars),
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
|
@ -153,6 +153,20 @@ impl Ipc {
|
|||
None => Response::error("Variable not found"),
|
||||
}
|
||||
}
|
||||
Command::List => {
|
||||
let variable_manager = Ironbar::variable_manager();
|
||||
|
||||
let mut values = read_lock!(variable_manager)
|
||||
.get_all()
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{k}: {}", v.get().unwrap_or_default()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
values.sort();
|
||||
let value = values.join("\n");
|
||||
|
||||
Response::OkValue { value }
|
||||
}
|
||||
Command::LoadCss { path } => {
|
||||
if path.exists() {
|
||||
load_css(path);
|
||||
|
@ -172,7 +186,7 @@ impl Ipc {
|
|||
popup.hide();
|
||||
|
||||
let data = popup
|
||||
.cache
|
||||
.container_cache
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|(_, value)| value.name == name)
|
||||
|
@ -209,7 +223,7 @@ impl Ipc {
|
|||
popup.hide();
|
||||
|
||||
let data = popup
|
||||
.cache
|
||||
.container_cache
|
||||
.borrow()
|
||||
.iter()
|
||||
.find(|(_, value)| value.name == name)
|
||||
|
|
|
@ -46,6 +46,10 @@ impl VariableManager {
|
|||
self.variables.get(key).and_then(IronVar::get)
|
||||
}
|
||||
|
||||
pub fn get_all(&self) -> &HashMap<Box<str>, IronVar> {
|
||||
&self.variables
|
||||
}
|
||||
|
||||
/// Subscribes to an `ironvar`, creating it if it does not exist.
|
||||
/// Any time the var is set, its value is sent on the channel.
|
||||
pub fn subscribe(&mut self, key: Box<str>) -> broadcast::Receiver<Option<String>> {
|
||||
|
@ -66,7 +70,7 @@ impl VariableManager {
|
|||
/// Ironbar dynamic variable representation.
|
||||
/// Interact with them through the `VARIABLE_MANAGER` `VariableManager` singleton.
|
||||
#[derive(Debug)]
|
||||
struct IronVar {
|
||||
pub struct IronVar {
|
||||
value: Option<String>,
|
||||
tx: broadcast::Sender<Option<String>>,
|
||||
_rx: broadcast::Receiver<Option<String>>,
|
||||
|
@ -82,7 +86,7 @@ impl IronVar {
|
|||
|
||||
/// Gets the current variable value.
|
||||
/// Prefer to subscribe to changes where possible.
|
||||
fn get(&self) -> Option<String> {
|
||||
pub fn get(&self) -> Option<String> {
|
||||
self.value.clone()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,31 @@
|
|||
/// Provides implementations of methods required by the `Module` trait
|
||||
/// which cannot be included as part of the trait.
|
||||
///
|
||||
/// This removes the need to add the same boilerplate method definitions
|
||||
/// to every module implementation.
|
||||
///
|
||||
/// # Usage:
|
||||
///
|
||||
/// ```rs
|
||||
/// impl Module for ClockModule {
|
||||
/// type SendMessage = DateTime<Local>;
|
||||
/// type ReceiveMessage = ();
|
||||
///
|
||||
/// module_impl!("clock");
|
||||
/// }
|
||||
#[macro_export]
|
||||
macro_rules! module_impl {
|
||||
($name:literal) => {
|
||||
fn name() -> &'static str {
|
||||
$name
|
||||
}
|
||||
|
||||
fn take_common(&mut self) -> $crate::config::CommonConfig {
|
||||
self.common.take().unwrap_or_default()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Sends a message on an asynchronous `Sender` using `send()`
|
||||
/// Panics if the message cannot be sent.
|
||||
///
|
||||
|
|
57
src/main.rs
57
src/main.rs
|
@ -96,14 +96,18 @@ pub struct Ironbar {
|
|||
bars: Rc<RefCell<Vec<Bar>>>,
|
||||
clients: Rc<RefCell<Clients>>,
|
||||
config: Rc<RefCell<Config>>,
|
||||
config_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Ironbar {
|
||||
fn new() -> Self {
|
||||
let (config, config_dir) = load_config();
|
||||
|
||||
Self {
|
||||
bars: Rc::new(RefCell::new(vec![])),
|
||||
clients: Rc::new(RefCell::new(Clients::new())),
|
||||
config: Rc::new(RefCell::new(load_config())),
|
||||
config: Rc::new(RefCell::new(config)),
|
||||
config_dir,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +196,7 @@ impl Ironbar {
|
|||
while let Ok(event) = rx_outputs.recv().await {
|
||||
match event.event_type {
|
||||
OutputEventType::New => {
|
||||
match load_output_bars(&instance, &app, event.output) {
|
||||
match load_output_bars(&instance, &app, &event.output) {
|
||||
Ok(mut new_bars) => {
|
||||
instance.bars.borrow_mut().append(&mut new_bars);
|
||||
}
|
||||
|
@ -260,7 +264,7 @@ impl Ironbar {
|
|||
/// Note this does *not* reload bars, which must be performed separately.
|
||||
#[cfg(feature = "ipc")]
|
||||
fn reload_config(&self) {
|
||||
self.config.replace(load_config());
|
||||
self.config.replace(load_config().0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,20 +274,37 @@ fn start_ironbar() {
|
|||
}
|
||||
|
||||
/// Loads the config file from disk.
|
||||
fn load_config() -> Config {
|
||||
let mut config = env::var("IRONBAR_CONFIG")
|
||||
.map_or_else(
|
||||
|_| ConfigLoader::new("ironbar").find_and_load(),
|
||||
ConfigLoader::load,
|
||||
)
|
||||
.unwrap_or_else(|err| {
|
||||
error!("Failed to load config: {}", err);
|
||||
warn!("Falling back to the default config");
|
||||
info!("If this is your first time using Ironbar, you should create a config in ~/.config/ironbar/");
|
||||
info!("More info here: https://github.com/JakeStanger/ironbar/wiki/configuration-guide");
|
||||
fn load_config() -> (Config, PathBuf) {
|
||||
let config_path = env::var("IRONBAR_CONFIG");
|
||||
|
||||
Config::default()
|
||||
});
|
||||
let (config, directory) = if let Ok(config_path) = config_path {
|
||||
let path = PathBuf::from(config_path);
|
||||
(
|
||||
ConfigLoader::load(&path),
|
||||
path.parent()
|
||||
.map(PathBuf::from)
|
||||
.ok_or_else(|| Report::msg("Specified path has no parent")),
|
||||
)
|
||||
} else {
|
||||
let config_loader = ConfigLoader::new("ironbar");
|
||||
(
|
||||
config_loader.find_and_load(),
|
||||
config_loader.config_dir().map_err(Report::new),
|
||||
)
|
||||
};
|
||||
|
||||
let mut config = config.unwrap_or_else(|err| {
|
||||
error!("Failed to load config: {}", err);
|
||||
warn!("Falling back to the default config");
|
||||
info!("If this is your first time using Ironbar, you should create a config in ~/.config/ironbar/");
|
||||
info!("More info here: https://github.com/JakeStanger/ironbar/wiki/configuration-guide");
|
||||
|
||||
Config::default()
|
||||
});
|
||||
|
||||
let directory = directory
|
||||
.and_then(|dir| dir.canonicalize().map_err(Report::new))
|
||||
.unwrap_or_else(|_| env::current_dir().expect("to have current working directory"));
|
||||
|
||||
debug!("Loaded config file");
|
||||
|
||||
|
@ -297,7 +318,7 @@ fn load_config() -> Config {
|
|||
}
|
||||
}
|
||||
|
||||
config
|
||||
(config, directory)
|
||||
}
|
||||
|
||||
/// Gets the GDK `Display` instance.
|
||||
|
@ -316,7 +337,7 @@ fn get_display() -> Display {
|
|||
fn load_output_bars(
|
||||
ironbar: &Rc<Ironbar>,
|
||||
app: &Application,
|
||||
output: OutputInfo,
|
||||
output: &OutputInfo,
|
||||
) -> Result<Vec<Bar>> {
|
||||
let Some(monitor_name) = &output.name else {
|
||||
return Err(Report::msg("Output missing monitor name"));
|
||||
|
|
198
src/modules/cairo.rs
Normal file
198
src/modules/cairo.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
use crate::config::CommonConfig;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, module_impl, spawn, try_send};
|
||||
use cairo::{Format, ImageSurface};
|
||||
use glib::translate::IntoGlibPtr;
|
||||
use glib::Propagation;
|
||||
use gtk::prelude::*;
|
||||
use gtk::DrawingArea;
|
||||
use mlua::{Error, Function, LightUserData};
|
||||
use notify::event::ModifyKind;
|
||||
use notify::{recommended_watcher, Event, EventKind, RecursiveMode, Watcher};
|
||||
use serde::Deserialize;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct CairoModule {
|
||||
path: PathBuf,
|
||||
|
||||
#[serde(default = "default_frequency")]
|
||||
frequency: u64,
|
||||
|
||||
#[serde(default = "default_size")]
|
||||
width: u32,
|
||||
#[serde(default = "default_size")]
|
||||
height: u32,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
||||
const fn default_size() -> u32 {
|
||||
42
|
||||
}
|
||||
|
||||
const fn default_frequency() -> u64 {
|
||||
200
|
||||
}
|
||||
|
||||
impl Module<gtk::Box> for CairoModule {
|
||||
type SendMessage = ();
|
||||
type ReceiveMessage = ();
|
||||
|
||||
module_impl!("cairo");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> color_eyre::Result<()>
|
||||
where
|
||||
<Self as Module<gtk::Box>>::SendMessage: Clone,
|
||||
{
|
||||
let path = self.path.to_path_buf();
|
||||
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
let parent = path.parent().expect("to have parent path");
|
||||
|
||||
let mut watcher = recommended_watcher({
|
||||
let path = path.clone();
|
||||
move |res: notify::Result<Event>| match res {
|
||||
Ok(event) if matches!(event.kind, EventKind::Modify(ModifyKind::Data(_))) => {
|
||||
debug!("{event:?}");
|
||||
|
||||
if event.paths.first().is_some_and(|p| p == &path) {
|
||||
try_send!(tx, ModuleUpdateEvent::Update(()));
|
||||
}
|
||||
}
|
||||
Err(e) => error!("Error occurred when watching stylesheet: {:?}", e),
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.expect("Failed to create lua file watcher");
|
||||
|
||||
watcher
|
||||
.watch(parent, RecursiveMode::NonRecursive)
|
||||
.expect("Failed to start lua file watcher");
|
||||
|
||||
// avoid watcher from dropping
|
||||
loop {
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
});
|
||||
|
||||
// Lua needs to run synchronously with the GTK updates,
|
||||
// so the controller does not handle the script engine.
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> color_eyre::Result<ModuleParts<gtk::Box>>
|
||||
where
|
||||
<Self as Module<gtk::Box>>::SendMessage: Clone,
|
||||
{
|
||||
let id = context.id.to_string();
|
||||
|
||||
let container = gtk::Box::new(info.bar_position.orientation(), 0);
|
||||
|
||||
let surface = ImageSurface::create(Format::ARgb32, self.width as i32, self.height as i32)?;
|
||||
|
||||
let area = DrawingArea::new();
|
||||
|
||||
let lua = context
|
||||
.ironbar
|
||||
.clients
|
||||
.borrow_mut()
|
||||
.lua(&context.ironbar.config_dir);
|
||||
|
||||
// this feels kinda dirty,
|
||||
// but it keeps draw functions separate in the global scope
|
||||
let script = fs::read_to_string(&self.path)?
|
||||
.replace("function draw", format!("function __draw_{id}").as_str());
|
||||
lua.load(&script).exec()?;
|
||||
|
||||
{
|
||||
let lua = lua.clone();
|
||||
let id = id.clone();
|
||||
|
||||
let path = self.path.clone();
|
||||
|
||||
area.connect_draw(move |_, cr| {
|
||||
let function: Function = lua
|
||||
.load(include_str!("../../lua/draw.lua"))
|
||||
.eval()
|
||||
.expect("to be valid");
|
||||
|
||||
if let Err(err) = cr.set_source_surface(&surface, 0.0, 0.0) {
|
||||
error!("{err}");
|
||||
return Propagation::Stop;
|
||||
}
|
||||
|
||||
let ptr = unsafe { cr.clone().into_glib_ptr().cast() };
|
||||
|
||||
// mlua needs a valid return type, even if we don't return anything
|
||||
|
||||
if let Err(err) =
|
||||
function.call::<_, Option<bool>>((id.as_str(), LightUserData(ptr)))
|
||||
{
|
||||
match err {
|
||||
Error::RuntimeError(message) => {
|
||||
let message = message.split_once("]:").expect("to exist").1;
|
||||
error!("[lua runtime error] {}:{message}", path.display())
|
||||
}
|
||||
_ => error!("{err}"),
|
||||
}
|
||||
|
||||
return Propagation::Stop;
|
||||
}
|
||||
|
||||
Propagation::Proceed
|
||||
});
|
||||
}
|
||||
|
||||
area.set_size_request(self.width as i32, self.height as i32);
|
||||
container.add(&area);
|
||||
|
||||
glib::spawn_future_local(async move {
|
||||
loop {
|
||||
area.queue_draw();
|
||||
glib::timeout_future(Duration::from_millis(self.frequency)).await;
|
||||
}
|
||||
});
|
||||
|
||||
glib_recv!(context.subscribe(), _ev => {
|
||||
let res = fs::read_to_string(&self.path)
|
||||
.map(|s| s.replace("function draw", format!("function __draw_{id}").as_str()));
|
||||
|
||||
match res {
|
||||
Ok(script) => {
|
||||
match lua.load(&script).exec() {
|
||||
Ok(_) => {},
|
||||
Err(Error::SyntaxError { message, ..}) => {
|
||||
let message = message.split_once("]:").expect("to exist").1;
|
||||
error!("[lua syntax error] {}:{message}", self.path.display())
|
||||
},
|
||||
Err(err) => error!("lua error: {err:?}")
|
||||
}
|
||||
},
|
||||
Err(err) => error!("{err:?}")
|
||||
}
|
||||
});
|
||||
|
||||
Ok(ModuleParts {
|
||||
widget: container,
|
||||
popup: None,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ use crate::image::new_icon_button;
|
|||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
||||
};
|
||||
use crate::{glib_recv, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, spawn, try_send};
|
||||
use glib::Propagation;
|
||||
use gtk::gdk_pixbuf::Pixbuf;
|
||||
use gtk::gio::{Cancellable, MemoryInputStream};
|
||||
|
@ -13,7 +13,6 @@ use gtk::prelude::*;
|
|||
use gtk::{Button, EventBox, Image, Label, Orientation, RadioButton, Widget};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::{debug, error};
|
||||
|
||||
|
@ -65,9 +64,7 @@ impl Module<Button> for ClipboardModule {
|
|||
type SendMessage = ControllerEvent;
|
||||
type ReceiveMessage = UIEvent;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"clipboard"
|
||||
}
|
||||
module_impl!("clipboard");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -78,7 +75,7 @@ impl Module<Button> for ClipboardModule {
|
|||
let max_items = self.max_items;
|
||||
|
||||
let tx = context.tx.clone();
|
||||
let client: Arc<clipboard::Client> = context.client();
|
||||
let client = context.client::<clipboard::Client>();
|
||||
|
||||
// listen to clipboard events
|
||||
spawn(async move {
|
||||
|
@ -137,7 +134,7 @@ impl Module<Button> for ClipboardModule {
|
|||
|
||||
let rx = context.subscribe();
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx, rx, info)
|
||||
.into_popup(context.controller_tx.clone(), rx, context, info)
|
||||
.into_popup_parts(vec![&button]);
|
||||
|
||||
Ok(ModuleParts::new(button, popup))
|
||||
|
@ -147,6 +144,7 @@ impl Module<Button> for ClipboardModule {
|
|||
self,
|
||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
|
|
|
@ -8,12 +8,12 @@ use serde::Deserialize;
|
|||
use tokio::sync::{broadcast, mpsc};
|
||||
use tokio::time::sleep;
|
||||
|
||||
use crate::config::CommonConfig;
|
||||
use crate::config::{CommonConfig, ModuleOrientation};
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
||||
};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn, try_send};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ClockModule {
|
||||
|
@ -31,6 +31,9 @@ pub struct ClockModule {
|
|||
#[serde(default = "default_locale")]
|
||||
locale: String,
|
||||
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
@ -41,6 +44,7 @@ impl Default for ClockModule {
|
|||
format: default_format(),
|
||||
format_popup: default_popup_format(),
|
||||
locale: default_locale(),
|
||||
orientation: ModuleOrientation::Horizontal,
|
||||
common: Some(CommonConfig::default()),
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +75,7 @@ impl Module<Button> for ClockModule {
|
|||
type SendMessage = DateTime<Local>;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
fn name() -> &'static str {
|
||||
"clock"
|
||||
}
|
||||
module_impl!("clock");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -100,7 +102,7 @@ impl Module<Button> for ClockModule {
|
|||
) -> Result<ModuleParts<Button>> {
|
||||
let button = Button::new();
|
||||
let label = Label::builder()
|
||||
.angle(info.bar_position.get_angle())
|
||||
.angle(self.orientation.to_angle())
|
||||
.use_markup(true)
|
||||
.build();
|
||||
button.add(&label);
|
||||
|
@ -120,7 +122,12 @@ impl Module<Button> for ClockModule {
|
|||
});
|
||||
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
|
||||
.into_popup(
|
||||
context.controller_tx.clone(),
|
||||
context.subscribe(),
|
||||
context,
|
||||
info,
|
||||
)
|
||||
.into_popup_parts(vec![&button]);
|
||||
|
||||
Ok(ModuleParts::new(button, popup))
|
||||
|
@ -130,6 +137,7 @@ impl Module<Button> for ClockModule {
|
|||
self,
|
||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box> {
|
||||
let container = gtk::Box::new(Orientation::Vertical, 0);
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
||||
use super::{CustomWidget, CustomWidgetContext};
|
||||
use crate::build;
|
||||
use crate::config::ModuleOrientation;
|
||||
use crate::modules::custom::WidgetConfig;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Orientation;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct BoxWidget {
|
||||
name: Option<String>,
|
||||
class: Option<String>,
|
||||
orientation: Option<String>,
|
||||
orientation: Option<ModuleOrientation>,
|
||||
widgets: Option<Vec<WidgetConfig>>,
|
||||
}
|
||||
|
||||
|
@ -20,9 +20,7 @@ impl CustomWidget for BoxWidget {
|
|||
let container = build!(self, Self::Widget);
|
||||
|
||||
if let Some(orientation) = self.orientation {
|
||||
container.set_orientation(
|
||||
try_get_orientation(&orientation).unwrap_or(Orientation::Horizontal),
|
||||
);
|
||||
container.set_orientation(orientation.into());
|
||||
}
|
||||
|
||||
if let Some(widgets) = self.widgets {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use gtk::prelude::*;
|
||||
use gtk::{Button, Label};
|
||||
use gtk::{Button, Label, Orientation};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::config::ModuleOrientation;
|
||||
use crate::dynamic_value::dynamic_string;
|
||||
use crate::modules::PopupButton;
|
||||
use crate::{build, try_send};
|
||||
|
||||
use super::{CustomWidget, CustomWidgetContext, ExecEvent};
|
||||
use super::{CustomWidget, CustomWidgetContext, ExecEvent, WidgetConfig};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ButtonWidget {
|
||||
|
@ -14,6 +15,9 @@ pub struct ButtonWidget {
|
|||
class: Option<String>,
|
||||
label: Option<String>,
|
||||
on_click: Option<String>,
|
||||
widgets: Option<Vec<WidgetConfig>>,
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
}
|
||||
|
||||
impl CustomWidget for ButtonWidget {
|
||||
|
@ -23,9 +27,20 @@ impl CustomWidget for ButtonWidget {
|
|||
let button = build!(self, Self::Widget);
|
||||
context.popup_buttons.borrow_mut().push(button.clone());
|
||||
|
||||
if let Some(text) = self.label {
|
||||
if let Some(widgets) = self.widgets {
|
||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
|
||||
for widget in widgets {
|
||||
widget.widget.add_to(&container, &context, widget.common);
|
||||
}
|
||||
|
||||
button.add(&container);
|
||||
} else if let Some(text) = self.label {
|
||||
let label = Label::new(None);
|
||||
label.set_use_markup(true);
|
||||
|
||||
label.set_angle(self.orientation.to_angle());
|
||||
|
||||
button.add(&label);
|
||||
|
||||
dynamic_string(&text, move |string| {
|
||||
|
|
|
@ -3,6 +3,7 @@ use gtk::Label;
|
|||
use serde::Deserialize;
|
||||
|
||||
use crate::build;
|
||||
use crate::config::ModuleOrientation;
|
||||
use crate::dynamic_value::dynamic_string;
|
||||
|
||||
use super::{CustomWidget, CustomWidgetContext};
|
||||
|
@ -12,6 +13,8 @@ pub struct LabelWidget {
|
|||
name: Option<String>,
|
||||
class: Option<String>,
|
||||
label: String,
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
}
|
||||
|
||||
impl CustomWidget for LabelWidget {
|
||||
|
@ -20,6 +23,8 @@ impl CustomWidget for LabelWidget {
|
|||
fn into_widget(self, _context: CustomWidgetContext) -> Self::Widget {
|
||||
let label = build!(self, Self::Widget);
|
||||
|
||||
label.set_angle(self.orientation.to_angle());
|
||||
|
||||
label.set_use_markup(true);
|
||||
|
||||
{
|
||||
|
|
|
@ -9,15 +9,16 @@ use self::image::ImageWidget;
|
|||
use self::label::LabelWidget;
|
||||
use self::r#box::BoxWidget;
|
||||
use self::slider::SliderWidget;
|
||||
use crate::config::CommonConfig;
|
||||
use crate::config::{CommonConfig, ModuleConfig};
|
||||
use crate::modules::custom::button::ButtonWidget;
|
||||
use crate::modules::custom::progress::ProgressWidget;
|
||||
use crate::modules::{
|
||||
wrap_widget, Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||
wrap_widget, AnyModuleFactory, BarModuleFactory, Module, ModuleInfo, ModuleParts, ModulePopup,
|
||||
ModuleUpdateEvent, PopupButton, PopupModuleFactory, WidgetContext,
|
||||
};
|
||||
use crate::script::Script;
|
||||
use crate::{send_async, spawn};
|
||||
use color_eyre::{Report, Result};
|
||||
use crate::{module_impl, send_async, spawn};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme, Orientation};
|
||||
use serde::Deserialize;
|
||||
|
@ -40,11 +41,18 @@ pub struct CustomModule {
|
|||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct WidgetConfig {
|
||||
#[serde(flatten)]
|
||||
widget: Widget,
|
||||
widget: WidgetOrModule,
|
||||
#[serde(flatten)]
|
||||
common: CommonConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum WidgetOrModule {
|
||||
Widget(Widget),
|
||||
Module(ModuleConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum Widget {
|
||||
|
@ -58,10 +66,12 @@ pub enum Widget {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct CustomWidgetContext<'a> {
|
||||
info: &'a ModuleInfo<'a>,
|
||||
tx: &'a mpsc::Sender<ExecEvent>,
|
||||
bar_orientation: Orientation,
|
||||
icon_theme: &'a IconTheme,
|
||||
popup_buttons: Rc<RefCell<Vec<Button>>>,
|
||||
module_factory: AnyModuleFactory,
|
||||
}
|
||||
|
||||
trait CustomWidget {
|
||||
|
@ -103,14 +113,16 @@ pub fn set_length<W: WidgetExt>(widget: &W, length: i32, bar_orientation: Orient
|
|||
};
|
||||
}
|
||||
|
||||
/// Attempts to parse an `Orientation` from `String`.
|
||||
/// Will accept `horizontal`, `vertical`, `h` or `v`.
|
||||
/// Ignores case.
|
||||
fn try_get_orientation(orientation: &str) -> Result<Orientation> {
|
||||
match orientation.to_lowercase().as_str() {
|
||||
"horizontal" | "h" => Ok(Orientation::Horizontal),
|
||||
"vertical" | "v" => Ok(Orientation::Vertical),
|
||||
_ => Err(Report::msg("Invalid orientation string in config")),
|
||||
impl WidgetOrModule {
|
||||
fn add_to(self, parent: >k::Box, context: &CustomWidgetContext, common: CommonConfig) {
|
||||
match self {
|
||||
WidgetOrModule::Widget(widget) => widget.add_to(parent, context, common),
|
||||
WidgetOrModule::Module(config) => {
|
||||
if let Err(err) = config.create(&context.module_factory, parent, context.info) {
|
||||
error!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,9 +163,7 @@ impl Module<gtk::Box> for CustomModule {
|
|||
type SendMessage = ();
|
||||
type ReceiveMessage = ExecEvent;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"custom"
|
||||
}
|
||||
module_impl!("custom");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -191,7 +201,7 @@ impl Module<gtk::Box> for CustomModule {
|
|||
|
||||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
mut context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Result<ModuleParts<gtk::Box>> {
|
||||
let orientation = info.bar_position.orientation();
|
||||
|
@ -200,10 +210,13 @@ impl Module<gtk::Box> for CustomModule {
|
|||
let popup_buttons = Rc::new(RefCell::new(Vec::new()));
|
||||
|
||||
let custom_context = CustomWidgetContext {
|
||||
info,
|
||||
tx: &context.controller_tx,
|
||||
bar_orientation: orientation,
|
||||
icon_theme: info.icon_theme,
|
||||
popup_buttons: popup_buttons.clone(),
|
||||
module_factory: BarModuleFactory::new(context.ironbar.clone(), context.popup.clone())
|
||||
.into(),
|
||||
};
|
||||
|
||||
self.bar.clone().into_iter().for_each(|widget| {
|
||||
|
@ -212,8 +225,22 @@ impl Module<gtk::Box> for CustomModule {
|
|||
.add_to(&container, &custom_context, widget.common);
|
||||
});
|
||||
|
||||
for button in popup_buttons.borrow().iter() {
|
||||
button.ensure_popup_id();
|
||||
}
|
||||
|
||||
context.button_id = popup_buttons
|
||||
.borrow()
|
||||
.first()
|
||||
.map_or(usize::MAX, PopupButton::popup_id);
|
||||
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
|
||||
.into_popup(
|
||||
context.controller_tx.clone(),
|
||||
context.subscribe(),
|
||||
context,
|
||||
info,
|
||||
)
|
||||
.into_popup_parts_owned(popup_buttons.take());
|
||||
|
||||
Ok(ModuleParts {
|
||||
|
@ -226,6 +253,7 @@ impl Module<gtk::Box> for CustomModule {
|
|||
self,
|
||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
_rx: broadcast::Receiver<Self::SendMessage>,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
|
@ -235,10 +263,17 @@ impl Module<gtk::Box> for CustomModule {
|
|||
|
||||
if let Some(popup) = self.popup {
|
||||
let custom_context = CustomWidgetContext {
|
||||
info,
|
||||
tx: &tx,
|
||||
bar_orientation: info.bar_position.orientation(),
|
||||
icon_theme: info.icon_theme,
|
||||
popup_buttons: Rc::new(RefCell::new(vec![])),
|
||||
module_factory: PopupModuleFactory::new(
|
||||
context.ironbar,
|
||||
context.popup,
|
||||
context.button_id,
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
|
||||
for widget in popup {
|
||||
|
|
|
@ -4,18 +4,20 @@ use serde::Deserialize;
|
|||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
use crate::config::ModuleOrientation;
|
||||
use crate::dynamic_value::dynamic_string;
|
||||
use crate::modules::custom::set_length;
|
||||
use crate::script::{OutputStream, Script, ScriptInput};
|
||||
use crate::{build, glib_recv_mpsc, spawn, try_send};
|
||||
|
||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext};
|
||||
use super::{CustomWidget, CustomWidgetContext};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct ProgressWidget {
|
||||
name: Option<String>,
|
||||
class: Option<String>,
|
||||
orientation: Option<String>,
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
label: Option<String>,
|
||||
value: Option<ScriptInput>,
|
||||
#[serde(default = "default_max")]
|
||||
|
@ -33,11 +35,7 @@ impl CustomWidget for ProgressWidget {
|
|||
fn into_widget(self, context: CustomWidgetContext) -> Self::Widget {
|
||||
let progress = build!(self, Self::Widget);
|
||||
|
||||
if let Some(orientation) = self.orientation {
|
||||
progress.set_orientation(
|
||||
try_get_orientation(&orientation).unwrap_or(context.bar_orientation),
|
||||
);
|
||||
}
|
||||
progress.set_orientation(self.orientation.into());
|
||||
|
||||
if let Some(length) = self.length {
|
||||
set_length(&progress, length, context.bar_orientation);
|
||||
|
|
|
@ -8,17 +8,19 @@ use serde::Deserialize;
|
|||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
use crate::config::ModuleOrientation;
|
||||
use crate::modules::custom::set_length;
|
||||
use crate::script::{OutputStream, Script, ScriptInput};
|
||||
use crate::{build, glib_recv_mpsc, spawn, try_send};
|
||||
|
||||
use super::{try_get_orientation, CustomWidget, CustomWidgetContext, ExecEvent};
|
||||
use super::{CustomWidget, CustomWidgetContext, ExecEvent};
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct SliderWidget {
|
||||
name: Option<String>,
|
||||
class: Option<String>,
|
||||
orientation: Option<String>,
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
value: Option<ScriptInput>,
|
||||
on_change: Option<String>,
|
||||
#[serde(default = "default_min")]
|
||||
|
@ -45,11 +47,7 @@ impl CustomWidget for SliderWidget {
|
|||
fn into_widget(self, context: CustomWidgetContext) -> Self::Widget {
|
||||
let scale = build!(self, Self::Widget);
|
||||
|
||||
if let Some(orientation) = self.orientation {
|
||||
scale.set_orientation(
|
||||
try_get_orientation(&orientation).unwrap_or(context.bar_orientation),
|
||||
);
|
||||
}
|
||||
scale.set_orientation(self.orientation.into());
|
||||
|
||||
if let Some(length) = self.length {
|
||||
set_length(&scale, length, context.bar_orientation);
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::config::{CommonConfig, TruncateMode};
|
|||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn, try_send};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
|
@ -50,9 +50,7 @@ impl Module<gtk::Box> for FocusedModule {
|
|||
type SendMessage = Option<(String, String)>;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
fn name() -> &'static str {
|
||||
"focused"
|
||||
}
|
||||
module_impl!("focused");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::config::CommonConfig;
|
||||
use crate::dynamic_value::dynamic_string;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, try_send};
|
||||
use crate::{glib_recv, module_impl, try_send};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
|
@ -29,9 +29,7 @@ impl Module<Label> for LabelModule {
|
|||
type SendMessage = String;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
fn name() -> &'static str {
|
||||
"label"
|
||||
}
|
||||
module_impl!("label");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
|
|
@ -7,7 +7,7 @@ use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, Wid
|
|||
use crate::clients::wayland::{self, ToplevelEvent};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::desktop_file::find_desktop_file;
|
||||
use crate::{arc_mut, glib_recv, lock, send_async, spawn, try_send, write_lock};
|
||||
use crate::{arc_mut, glib_recv, lock, module_impl, send_async, spawn, try_send, write_lock};
|
||||
use color_eyre::{Help, Report};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, Orientation};
|
||||
|
@ -80,9 +80,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
type SendMessage = LauncherUpdate;
|
||||
type ReceiveMessage = ItemEvent;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"launcher"
|
||||
}
|
||||
module_impl!("launcher");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -401,7 +399,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
|
||||
let rx = context.subscribe();
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx, rx, info)
|
||||
.into_popup(context.controller_tx.clone(), rx, context, info)
|
||||
.into_popup_parts(vec![]); // since item buttons are dynamic, they pass their geometry directly
|
||||
|
||||
Ok(ModuleParts {
|
||||
|
@ -414,6 +412,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
self,
|
||||
controller_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box> {
|
||||
const MAX_WIDTH: i32 = 250;
|
||||
|
|
|
@ -10,12 +10,14 @@ use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widge
|
|||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::clients::ProvidesClient;
|
||||
use crate::clients::{ClientResult, ProvidesClient, ProvidesFallibleClient};
|
||||
use crate::config::{BarPosition, CommonConfig, TransitionType};
|
||||
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
||||
use crate::popup::Popup;
|
||||
use crate::{glib_recv_mpsc, send, Ironbar};
|
||||
|
||||
#[cfg(feature = "cairo")]
|
||||
pub mod cairo;
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub mod clipboard;
|
||||
/// Displays the current date and time.
|
||||
|
@ -56,6 +58,8 @@ pub enum ModuleLocation {
|
|||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ModuleInfo<'a> {
|
||||
pub app: &'a Application,
|
||||
pub location: ModuleLocation,
|
||||
|
@ -87,10 +91,16 @@ where
|
|||
{
|
||||
pub id: usize,
|
||||
pub ironbar: Rc<Ironbar>,
|
||||
pub popup: Rc<Popup>,
|
||||
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
|
||||
pub update_tx: broadcast::Sender<TSend>,
|
||||
pub controller_tx: mpsc::Sender<TReceive>,
|
||||
|
||||
// TODO: Don't like this - need some serious refactoring to deal with it
|
||||
// This is a hack to be able to pass data from module -> popup creation
|
||||
// for custom widget only.
|
||||
pub button_id: usize,
|
||||
|
||||
_update_rx: broadcast::Receiver<TSend>,
|
||||
}
|
||||
|
||||
|
@ -109,6 +119,13 @@ where
|
|||
ProvidesClient::provide(self)
|
||||
}
|
||||
|
||||
pub fn try_client<T: ?Sized>(&self) -> ClientResult<T>
|
||||
where
|
||||
WidgetContext<TSend, TReceive>: ProvidesFallibleClient<T>,
|
||||
{
|
||||
ProvidesFallibleClient::try_provide(self)
|
||||
}
|
||||
|
||||
/// Subscribes to events sent from this widget.
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<TSend> {
|
||||
self.update_tx.subscribe()
|
||||
|
@ -124,6 +141,32 @@ impl<W: IsA<Widget>> ModuleParts<W> {
|
|||
fn new(widget: W, popup: Option<ModulePopupParts>) -> Self {
|
||||
Self { widget, popup }
|
||||
}
|
||||
|
||||
pub fn setup_identifiers(&self, common: &CommonConfig) {
|
||||
if let Some(ref name) = common.name {
|
||||
self.widget.set_widget_name(name);
|
||||
|
||||
if let Some(ref popup) = self.popup {
|
||||
popup.container.set_widget_name(&format!("popup-{name}"));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref class) = common.class {
|
||||
// gtk counts classes with spaces as the same class
|
||||
for part in class.split(' ') {
|
||||
self.widget.style_context().add_class(part);
|
||||
}
|
||||
|
||||
if let Some(ref popup) = self.popup {
|
||||
for part in class.split(' ') {
|
||||
popup
|
||||
.container
|
||||
.style_context()
|
||||
.add_class(&format!("popup-{part}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -152,11 +195,24 @@ impl ModulePopup for Option<gtk::Box> {
|
|||
}
|
||||
|
||||
pub trait PopupButton {
|
||||
fn ensure_popup_id(&self) -> usize;
|
||||
fn try_popup_id(&self) -> Option<usize>;
|
||||
fn popup_id(&self) -> usize;
|
||||
}
|
||||
|
||||
impl PopupButton for Button {
|
||||
/// Gets the popup ID associated with this button,
|
||||
/// or creates a new one if it does not exist.
|
||||
fn ensure_popup_id(&self) -> usize {
|
||||
if let Some(id) = self.try_popup_id() {
|
||||
id
|
||||
} else {
|
||||
let id = Ironbar::unique_id();
|
||||
self.set_tag("popup-id", id);
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the popup ID associated with this button, if there is one.
|
||||
/// Will return `None` if this is not a popup button.
|
||||
fn try_popup_id(&self) -> Option<usize> {
|
||||
|
@ -203,165 +259,290 @@ where
|
|||
self,
|
||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
_rx: broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
Self: Sized,
|
||||
<Self as Module<W>>::SendMessage: Clone,
|
||||
{
|
||||
None
|
||||
}
|
||||
|
||||
fn take_common(&mut self) -> CommonConfig;
|
||||
}
|
||||
|
||||
/// Creates a module and sets it up.
|
||||
/// This setup includes widget/popup content and event channels.
|
||||
pub fn create_module<TModule, TWidget, TSend, TRec>(
|
||||
module: TModule,
|
||||
id: usize,
|
||||
ironbar: Rc<Ironbar>,
|
||||
name: Option<String>,
|
||||
info: &ModuleInfo,
|
||||
popup: &Rc<Popup>,
|
||||
) -> Result<ModuleParts<TWidget>>
|
||||
where
|
||||
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRec>,
|
||||
TWidget: IsA<Widget>,
|
||||
TSend: Debug + Clone + Send + 'static,
|
||||
{
|
||||
let (ui_tx, ui_rx) = mpsc::channel::<ModuleUpdateEvent<TSend>>(64);
|
||||
let (controller_tx, controller_rx) = mpsc::channel::<TRec>(64);
|
||||
pub trait ModuleFactory {
|
||||
fn create<TModule, TWidget, TSend, TRev>(
|
||||
&self,
|
||||
mut module: TModule,
|
||||
container: >k::Box,
|
||||
info: &ModuleInfo,
|
||||
) -> Result<()>
|
||||
where
|
||||
TModule: Module<TWidget, SendMessage = TSend, ReceiveMessage = TRev>,
|
||||
TWidget: IsA<Widget>,
|
||||
TSend: Debug + Clone + Send + 'static,
|
||||
{
|
||||
let id = Ironbar::unique_id();
|
||||
let common = module.take_common();
|
||||
|
||||
let (tx, rx) = broadcast::channel(64);
|
||||
let (ui_tx, ui_rx) = mpsc::channel::<ModuleUpdateEvent<TSend>>(64);
|
||||
let (controller_tx, controller_rx) = mpsc::channel::<TRev>(64);
|
||||
|
||||
let context = WidgetContext {
|
||||
id,
|
||||
ironbar,
|
||||
tx: ui_tx,
|
||||
update_tx: tx.clone(),
|
||||
controller_tx,
|
||||
_update_rx: rx,
|
||||
};
|
||||
let (tx, rx) = broadcast::channel(64);
|
||||
|
||||
module.spawn_controller(info, &context, controller_rx)?;
|
||||
let context = WidgetContext {
|
||||
id,
|
||||
ironbar: self.ironbar().clone(),
|
||||
popup: self.popup().clone(),
|
||||
tx: ui_tx,
|
||||
update_tx: tx.clone(),
|
||||
controller_tx,
|
||||
_update_rx: rx,
|
||||
button_id: usize::MAX, // hack :(
|
||||
};
|
||||
|
||||
let module_name = TModule::name();
|
||||
let instance_name = name.unwrap_or_else(|| module_name.to_string());
|
||||
module.spawn_controller(info, &context, controller_rx)?;
|
||||
|
||||
let module_parts = module.into_widget(context, info)?;
|
||||
module_parts.widget.add_class("widget");
|
||||
module_parts.widget.add_class(module_name);
|
||||
let module_name = TModule::name();
|
||||
let instance_name = common
|
||||
.name
|
||||
.clone()
|
||||
.unwrap_or_else(|| module_name.to_string());
|
||||
|
||||
if let Some(popup_content) = module_parts.popup.clone() {
|
||||
popup_content
|
||||
.container
|
||||
.style_context()
|
||||
.add_class(&format!("popup-{module_name}"));
|
||||
let module_parts = module.into_widget(context, info)?;
|
||||
module_parts.widget.add_class("widget");
|
||||
module_parts.widget.add_class(module_name);
|
||||
|
||||
popup.register_content(id, instance_name, popup_content);
|
||||
if let Some(popup_content) = module_parts.popup.clone() {
|
||||
popup_content
|
||||
.container
|
||||
.style_context()
|
||||
.add_class(&format!("popup-{module_name}"));
|
||||
|
||||
self.popup()
|
||||
.register_content(id, instance_name, popup_content);
|
||||
}
|
||||
|
||||
self.setup_receiver(tx, ui_rx, module_name, id, common.disable_popup);
|
||||
|
||||
module_parts.setup_identifiers(&common);
|
||||
|
||||
let ev_container = wrap_widget(
|
||||
&module_parts.widget,
|
||||
common,
|
||||
info.bar_position.orientation(),
|
||||
);
|
||||
container.add(&ev_container);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
setup_receiver(tx, ui_rx, popup.clone(), module_name, id);
|
||||
fn setup_receiver<TSend>(
|
||||
&self,
|
||||
tx: broadcast::Sender<TSend>,
|
||||
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
|
||||
name: &'static str,
|
||||
id: usize,
|
||||
disable_popup: bool,
|
||||
) where
|
||||
TSend: Debug + Clone + Send + 'static;
|
||||
|
||||
Ok(module_parts)
|
||||
fn ironbar(&self) -> &Rc<Ironbar>;
|
||||
fn popup(&self) -> &Rc<Popup>;
|
||||
}
|
||||
|
||||
/// Sets up the bridge channel receiver
|
||||
/// to pick up events from the controller, widget or popup.
|
||||
///
|
||||
/// Handles opening/closing popups
|
||||
/// and communicating update messages between controllers and widgets/popups.
|
||||
fn setup_receiver<TSend>(
|
||||
tx: broadcast::Sender<TSend>,
|
||||
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
|
||||
#[derive(Clone)]
|
||||
pub struct BarModuleFactory {
|
||||
ironbar: Rc<Ironbar>,
|
||||
popup: Rc<Popup>,
|
||||
name: &'static str,
|
||||
id: usize,
|
||||
) where
|
||||
TSend: Debug + Clone + Send + 'static,
|
||||
{
|
||||
// some rare cases can cause the popup to incorrectly calculate its size on first open.
|
||||
// we can fix that by just force re-rendering it on its first open.
|
||||
let mut has_popup_opened = false;
|
||||
}
|
||||
|
||||
glib_recv_mpsc!(rx, ev => {
|
||||
match ev {
|
||||
ModuleUpdateEvent::Update(update) => {
|
||||
send!(tx, update);
|
||||
}
|
||||
ModuleUpdateEvent::TogglePopup(button_id) => {
|
||||
debug!("Toggling popup for {} [#{}]", name, id);
|
||||
if popup.is_visible() {
|
||||
popup.hide();
|
||||
} else {
|
||||
popup.show(id, button_id);
|
||||
impl BarModuleFactory {
|
||||
pub fn new(ironbar: Rc<Ironbar>, popup: Rc<Popup>) -> Self {
|
||||
Self { ironbar, popup }
|
||||
}
|
||||
}
|
||||
|
||||
// force re-render on initial open to try and fix size issue
|
||||
if !has_popup_opened {
|
||||
impl ModuleFactory for BarModuleFactory {
|
||||
fn setup_receiver<TSend>(
|
||||
&self,
|
||||
tx: broadcast::Sender<TSend>,
|
||||
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
|
||||
name: &'static str,
|
||||
id: usize,
|
||||
disable_popup: bool,
|
||||
) where
|
||||
TSend: Debug + Clone + Send + 'static,
|
||||
{
|
||||
let popup = self.popup.clone();
|
||||
glib_recv_mpsc!(rx, ev => {
|
||||
match ev {
|
||||
ModuleUpdateEvent::Update(update) => {
|
||||
send!(tx, update);
|
||||
}
|
||||
ModuleUpdateEvent::TogglePopup(button_id) if !disable_popup => {
|
||||
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
|
||||
if popup.is_visible() && popup.current_widget().unwrap_or_default() == id {
|
||||
popup.hide();
|
||||
} else {
|
||||
popup.show(id, button_id);
|
||||
has_popup_opened = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ModuleUpdateEvent::OpenPopup(button_id) => {
|
||||
debug!("Opening popup for {} [#{}]", name, id);
|
||||
popup.hide();
|
||||
popup.show(id, button_id);
|
||||
|
||||
// force re-render on initial open to try and fix size issue
|
||||
if !has_popup_opened {
|
||||
ModuleUpdateEvent::OpenPopup(button_id) if !disable_popup => {
|
||||
debug!("Opening popup for {} [#{}] (button id: {button_id})", name, id);
|
||||
popup.hide();
|
||||
popup.show(id, button_id);
|
||||
has_popup_opened = true;
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "launcher")]
|
||||
ModuleUpdateEvent::OpenPopupAt(geometry) => {
|
||||
debug!("Opening popup for {} [#{}]", name, id);
|
||||
#[cfg(feature = "launcher")]
|
||||
ModuleUpdateEvent::OpenPopupAt(geometry) if !disable_popup => {
|
||||
debug!("Opening popup for {} [#{}]", name, id);
|
||||
|
||||
popup.hide();
|
||||
popup.show_at(id, geometry);
|
||||
|
||||
// force re-render on initial open to try and fix size issue
|
||||
if !has_popup_opened {
|
||||
popup.hide();
|
||||
popup.show_at(id, geometry);
|
||||
has_popup_opened = true;
|
||||
}
|
||||
ModuleUpdateEvent::ClosePopup if !disable_popup => {
|
||||
debug!("Closing popup for {} [#{}]", name, id);
|
||||
popup.hide();
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
ModuleUpdateEvent::ClosePopup => {
|
||||
debug!("Closing popup for {} [#{}]", name, id);
|
||||
popup.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn ironbar(&self) -> &Rc<Ironbar> {
|
||||
&self.ironbar
|
||||
}
|
||||
|
||||
fn popup(&self) -> &Rc<Popup> {
|
||||
&self.popup
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_widget_identifiers<TWidget: IsA<Widget>>(
|
||||
widget_parts: &ModuleParts<TWidget>,
|
||||
common: &CommonConfig,
|
||||
) {
|
||||
if let Some(ref name) = common.name {
|
||||
widget_parts.widget.set_widget_name(name);
|
||||
#[derive(Clone)]
|
||||
pub struct PopupModuleFactory {
|
||||
ironbar: Rc<Ironbar>,
|
||||
popup: Rc<Popup>,
|
||||
button_id: usize,
|
||||
}
|
||||
|
||||
if let Some(ref popup) = widget_parts.popup {
|
||||
popup.container.set_widget_name(&format!("popup-{name}"));
|
||||
impl PopupModuleFactory {
|
||||
pub fn new(ironbar: Rc<Ironbar>, popup: Rc<Popup>, button_id: usize) -> Self {
|
||||
Self {
|
||||
ironbar,
|
||||
popup,
|
||||
button_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleFactory for PopupModuleFactory {
|
||||
fn setup_receiver<TSend>(
|
||||
&self,
|
||||
tx: broadcast::Sender<TSend>,
|
||||
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
|
||||
name: &'static str,
|
||||
id: usize,
|
||||
disable_popup: bool,
|
||||
) where
|
||||
TSend: Debug + Clone + Send + 'static,
|
||||
{
|
||||
let popup = self.popup.clone();
|
||||
let button_id = self.button_id;
|
||||
glib_recv_mpsc!(rx, ev => {
|
||||
match ev {
|
||||
ModuleUpdateEvent::Update(update) => {
|
||||
send!(tx, update);
|
||||
}
|
||||
ModuleUpdateEvent::TogglePopup(_) if !disable_popup => {
|
||||
debug!("Toggling popup for {} [#{}] (button id: {button_id})", name, id);
|
||||
if popup.is_visible() && popup.current_widget().unwrap_or_default() == id {
|
||||
popup.hide();
|
||||
} else {
|
||||
popup.show(id, button_id);
|
||||
}
|
||||
}
|
||||
ModuleUpdateEvent::OpenPopup(_) if !disable_popup => {
|
||||
debug!("Opening popup for {} [#{}] (button id: {button_id})", name, id);
|
||||
popup.hide();
|
||||
popup.show(id, button_id);
|
||||
}
|
||||
#[cfg(feature = "launcher")]
|
||||
ModuleUpdateEvent::OpenPopupAt(geometry) if !disable_popup => {
|
||||
debug!("Opening popup for {} [#{}]", name, id);
|
||||
|
||||
popup.hide();
|
||||
popup.show_at(id, geometry);
|
||||
}
|
||||
ModuleUpdateEvent::ClosePopup if !disable_popup => {
|
||||
debug!("Closing popup for {} [#{}]", name, id);
|
||||
popup.hide();
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn ironbar(&self) -> &Rc<Ironbar> {
|
||||
&self.ironbar
|
||||
}
|
||||
|
||||
fn popup(&self) -> &Rc<Popup> {
|
||||
&self.popup
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AnyModuleFactory {
|
||||
Bar(BarModuleFactory),
|
||||
Popup(PopupModuleFactory),
|
||||
}
|
||||
|
||||
impl ModuleFactory for AnyModuleFactory {
|
||||
fn setup_receiver<TSend>(
|
||||
&self,
|
||||
tx: broadcast::Sender<TSend>,
|
||||
rx: mpsc::Receiver<ModuleUpdateEvent<TSend>>,
|
||||
name: &'static str,
|
||||
id: usize,
|
||||
disable_popup: bool,
|
||||
) where
|
||||
TSend: Debug + Clone + Send + 'static,
|
||||
{
|
||||
match self {
|
||||
AnyModuleFactory::Bar(bar) => bar.setup_receiver(tx, rx, name, id, disable_popup),
|
||||
AnyModuleFactory::Popup(popup) => popup.setup_receiver(tx, rx, name, id, disable_popup),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref class) = common.class {
|
||||
// gtk counts classes with spaces as the same class
|
||||
for part in class.split(' ') {
|
||||
widget_parts.widget.style_context().add_class(part);
|
||||
fn ironbar(&self) -> &Rc<Ironbar> {
|
||||
match self {
|
||||
AnyModuleFactory::Bar(bar) => bar.ironbar(),
|
||||
AnyModuleFactory::Popup(popup) => popup.ironbar(),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref popup) = widget_parts.popup {
|
||||
for part in class.split(' ') {
|
||||
popup
|
||||
.container
|
||||
.style_context()
|
||||
.add_class(&format!("popup-{part}"));
|
||||
}
|
||||
fn popup(&self) -> &Rc<Popup> {
|
||||
match self {
|
||||
AnyModuleFactory::Bar(bar) => bar.popup(),
|
||||
AnyModuleFactory::Popup(popup) => popup.popup(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BarModuleFactory> for AnyModuleFactory {
|
||||
fn from(value: BarModuleFactory) -> Self {
|
||||
Self::Bar(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupModuleFactory> for AnyModuleFactory {
|
||||
fn from(value: PopupModuleFactory) -> Self {
|
||||
Self::Popup(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Takes a widget and adds it into a new `gtk::EventBox`.
|
||||
/// The event box container is returned.
|
||||
pub fn wrap_widget<W: IsA<Widget>>(
|
||||
|
|
|
@ -22,7 +22,7 @@ use crate::modules::PopupButton;
|
|||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||
};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn, try_send};
|
||||
|
||||
pub use self::config::MusicModule;
|
||||
use self::config::PlayerType;
|
||||
|
@ -87,9 +87,7 @@ impl Module<Button> for MusicModule {
|
|||
type SendMessage = ControllerEvent;
|
||||
type ReceiveMessage = PlayerCommand;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"music"
|
||||
}
|
||||
module_impl!("music");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -255,7 +253,7 @@ impl Module<Button> for MusicModule {
|
|||
|
||||
let rx = context.subscribe();
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx, rx, info)
|
||||
.into_popup(context.controller_tx.clone(), rx, context, info)
|
||||
.into_popup_parts(vec![&button]);
|
||||
|
||||
Ok(ModuleParts::new(button, popup))
|
||||
|
@ -265,6 +263,7 @@ impl Module<Button> for MusicModule {
|
|||
self,
|
||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
) -> Option<gtk::Box> {
|
||||
let icon_theme = info.icon_theme;
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::clients::swaync;
|
|||
use crate::config::CommonConfig;
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn, try_send};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Align, Button, Label, Overlay};
|
||||
use serde::Deserialize;
|
||||
|
@ -75,7 +75,7 @@ fn default_icon_open_dnd() -> String {
|
|||
}
|
||||
|
||||
impl Icons {
|
||||
fn icon(&self, value: &swaync::Event) -> &str {
|
||||
fn icon(&self, value: swaync::Event) -> &str {
|
||||
match (value.cc_open, value.count > 0, value.dnd) {
|
||||
(true, _, true) => &self.open_dnd,
|
||||
(true, true, false) => &self.open_some,
|
||||
|
@ -97,9 +97,7 @@ impl Module<Overlay> for NotificationsModule {
|
|||
type SendMessage = swaync::Event;
|
||||
type ReceiveMessage = UiEvent;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"notifications"
|
||||
}
|
||||
module_impl!("notifications");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -110,7 +108,7 @@ impl Module<Overlay> for NotificationsModule {
|
|||
where
|
||||
<Self as Module<Overlay>>::SendMessage: Clone,
|
||||
{
|
||||
let client = context.client::<swaync::Client>();
|
||||
let client = context.try_client::<swaync::Client>()?;
|
||||
|
||||
{
|
||||
let client = client.clone();
|
||||
|
@ -174,7 +172,7 @@ impl Module<Overlay> for NotificationsModule {
|
|||
let button = button.clone();
|
||||
|
||||
glib_recv!(context.subscribe(), ev => {
|
||||
let icon = self.icons.icon(&ev);
|
||||
let icon = self.icons.icon(ev);
|
||||
button.set_label(icon);
|
||||
|
||||
label.set_label(&ev.count.to_string());
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::config::CommonConfig;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::script::{OutputStream, Script, ScriptMode};
|
||||
use crate::{glib_recv, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, spawn, try_send};
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
|
@ -48,9 +48,7 @@ impl Module<Label> for ScriptModule {
|
|||
type SendMessage = String;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
fn name() -> &'static str {
|
||||
"script"
|
||||
}
|
||||
module_impl!("script");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::config::CommonConfig;
|
||||
use crate::config::{CommonConfig, ModuleOrientation};
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, send_async, spawn};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
|
@ -21,6 +21,11 @@ pub struct SysInfoModule {
|
|||
#[serde(default = "Interval::default")]
|
||||
interval: Interval,
|
||||
|
||||
#[serde(default)]
|
||||
orientation: ModuleOrientation,
|
||||
|
||||
direction: Option<ModuleOrientation>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub common: Option<CommonConfig>,
|
||||
}
|
||||
|
@ -116,9 +121,7 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||
type SendMessage = HashMap<String, String>;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
fn name() -> &'static str {
|
||||
"sysinfo"
|
||||
}
|
||||
module_impl!("sysinfo");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -184,11 +187,16 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||
fn into_widget(
|
||||
self,
|
||||
context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
info: &ModuleInfo,
|
||||
_info: &ModuleInfo,
|
||||
) -> Result<ModuleParts<gtk::Box>> {
|
||||
let re = Regex::new(r"\{([^}]+)}")?;
|
||||
|
||||
let container = gtk::Box::new(info.bar_position.orientation(), 10);
|
||||
let layout = match self.direction {
|
||||
Some(orientation) => orientation,
|
||||
None => self.orientation,
|
||||
};
|
||||
|
||||
let container = gtk::Box::new(layout.into(), 10);
|
||||
|
||||
let mut labels = Vec::new();
|
||||
|
||||
|
@ -196,7 +204,7 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||
let label = Label::builder().label(format).use_markup(true).build();
|
||||
|
||||
label.add_class("item");
|
||||
label.set_angle(info.bar_position.get_angle());
|
||||
label.set_angle(self.orientation.to_angle());
|
||||
|
||||
container.add(&label);
|
||||
labels.push(label);
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::clients::tray;
|
|||
use crate::config::CommonConfig;
|
||||
use crate::modules::tray::diff::get_diffs;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, lock, send_async, spawn};
|
||||
use crate::{glib_recv, lock, module_impl, send_async, spawn};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::{prelude::*, PackDirection};
|
||||
use gtk::{IconTheme, MenuBar};
|
||||
|
@ -57,9 +57,7 @@ impl Module<MenuBar> for TrayModule {
|
|||
type SendMessage = Event;
|
||||
type ReceiveMessage = ActivateRequest;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"tray"
|
||||
}
|
||||
module_impl!("tray");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -69,14 +67,14 @@ impl Module<MenuBar> for TrayModule {
|
|||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
|
||||
let client = context.client::<tray::Client>();
|
||||
let client = context.try_client::<tray::Client>()?;
|
||||
let mut tray_rx = client.subscribe();
|
||||
|
||||
let initial_items = lock!(client.items()).clone();
|
||||
|
||||
// listen to tray updates
|
||||
spawn(async move {
|
||||
for (key, (item, menu)) in initial_items.into_iter() {
|
||||
for (key, (item, menu)) in initial_items {
|
||||
send_async!(
|
||||
tx,
|
||||
ModuleUpdateEvent::Update(Event::Add(key.clone(), item.into()))
|
||||
|
@ -91,7 +89,7 @@ impl Module<MenuBar> for TrayModule {
|
|||
}
|
||||
|
||||
while let Ok(message) = tray_rx.recv().await {
|
||||
send_async!(tx, ModuleUpdateEvent::Update(message))
|
||||
send_async!(tx, ModuleUpdateEvent::Update(message));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -161,12 +159,11 @@ fn on_update(
|
|||
let mut menu_item = TrayMenu::new(tx.clone(), address.clone(), *item);
|
||||
container.add(&menu_item.widget);
|
||||
|
||||
match icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
|
||||
Ok(image) => menu_item.set_image(&image),
|
||||
Err(_) => {
|
||||
let label = menu_item.title.clone().unwrap_or(address.clone());
|
||||
menu_item.set_label(&label)
|
||||
}
|
||||
if let Ok(image) = icon::get_image(&menu_item, icon_theme, icon_size, prefer_icons) {
|
||||
menu_item.set_image(&image);
|
||||
} else {
|
||||
let label = menu_item.title.clone().unwrap_or(address.clone());
|
||||
menu_item.set_label(&label);
|
||||
};
|
||||
|
||||
menu_item.widget.show();
|
||||
|
|
|
@ -15,7 +15,7 @@ use crate::modules::PopupButton;
|
|||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||
};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn, try_send};
|
||||
|
||||
const DAY: i64 = 24 * 60 * 60;
|
||||
const HOUR: i64 = 60 * 60;
|
||||
|
@ -54,9 +54,7 @@ impl Module<gtk::Button> for UpowerModule {
|
|||
type SendMessage = UpowerProperties;
|
||||
type ReceiveMessage = ();
|
||||
|
||||
fn name() -> &'static str {
|
||||
"upower"
|
||||
}
|
||||
module_impl!("upower");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -183,7 +181,6 @@ impl Module<gtk::Button> for UpowerModule {
|
|||
try_send!(tx, ModuleUpdateEvent::TogglePopup(button.popup_id()));
|
||||
});
|
||||
|
||||
label.set_angle(info.bar_position.get_angle());
|
||||
let format = self.format.clone();
|
||||
|
||||
let rx = context.subscribe();
|
||||
|
@ -211,7 +208,7 @@ impl Module<gtk::Button> for UpowerModule {
|
|||
|
||||
let rx = context.subscribe();
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx, rx, info)
|
||||
.into_popup(context.controller_tx.clone(), rx, context, info)
|
||||
.into_popup_parts(vec![&button]);
|
||||
|
||||
Ok(ModuleParts::new(button, popup))
|
||||
|
@ -221,6 +218,7 @@ impl Module<gtk::Button> for UpowerModule {
|
|||
self,
|
||||
_tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::gtk_helpers::IronbarGtkExt;
|
|||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, PopupButton, WidgetContext,
|
||||
};
|
||||
use crate::{glib_recv, lock, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, lock, module_impl, send_async, spawn, try_send};
|
||||
use glib::Propagation;
|
||||
use gtk::pango::EllipsizeMode;
|
||||
use gtk::prelude::*;
|
||||
|
@ -99,9 +99,7 @@ impl Module<Button> for VolumeModule {
|
|||
type SendMessage = Event;
|
||||
type ReceiveMessage = Update;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"volume"
|
||||
}
|
||||
module_impl!("volume");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -208,7 +206,12 @@ impl Module<Button> for VolumeModule {
|
|||
}
|
||||
|
||||
let popup = self
|
||||
.into_popup(context.controller_tx.clone(), context.subscribe(), info)
|
||||
.into_popup(
|
||||
context.controller_tx.clone(),
|
||||
context.subscribe(),
|
||||
context,
|
||||
info,
|
||||
)
|
||||
.into_popup_parts(vec![&button]);
|
||||
|
||||
Ok(ModuleParts::new(button, popup))
|
||||
|
@ -218,6 +221,7 @@ impl Module<Button> for VolumeModule {
|
|||
self,
|
||||
tx: mpsc::Sender<Self::ReceiveMessage>,
|
||||
rx: tokio::sync::broadcast::Receiver<Self::SendMessage>,
|
||||
_context: WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_info: &ModuleInfo,
|
||||
) -> Option<gtk::Box>
|
||||
where
|
||||
|
|
|
@ -2,7 +2,7 @@ use crate::clients::compositor::{Visibility, Workspace, WorkspaceClient, Workspa
|
|||
use crate::config::CommonConfig;
|
||||
use crate::image::new_icon_button;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, module_impl, send_async, spawn, try_send};
|
||||
use color_eyre::{Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme};
|
||||
|
@ -144,9 +144,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
type SendMessage = WorkspaceUpdate;
|
||||
type ReceiveMessage = String;
|
||||
|
||||
fn name() -> &'static str {
|
||||
"workspaces"
|
||||
}
|
||||
module_impl!("workspaces");
|
||||
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
|
@ -155,7 +153,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
mut rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
let client = context.ironbar.clients.borrow_mut().workspaces();
|
||||
let client = context.ironbar.clients.borrow_mut().workspaces()?;
|
||||
// Subscribe & send events
|
||||
spawn(async move {
|
||||
let mut srx = client.subscribe_workspace_change();
|
||||
|
@ -168,7 +166,7 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
}
|
||||
});
|
||||
|
||||
let client = context.client::<dyn WorkspaceClient>();
|
||||
let client = context.try_client::<dyn WorkspaceClient>()?;
|
||||
|
||||
// Change workspace focus
|
||||
spawn(async move {
|
||||
|
|
42
src/popup.rs
42
src/popup.rs
|
@ -12,7 +12,7 @@ use tracing::{debug, trace};
|
|||
use crate::config::BarPosition;
|
||||
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
||||
use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton};
|
||||
use crate::Ironbar;
|
||||
use crate::rc_mut;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PopupCacheValue {
|
||||
|
@ -23,7 +23,8 @@ pub struct PopupCacheValue {
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Popup {
|
||||
pub window: ApplicationWindow,
|
||||
pub cache: Rc<RefCell<HashMap<usize, PopupCacheValue>>>,
|
||||
pub container_cache: Rc<RefCell<HashMap<usize, PopupCacheValue>>>,
|
||||
pub button_cache: Rc<RefCell<Vec<Button>>>,
|
||||
monitor: Monitor,
|
||||
pos: BarPosition,
|
||||
current_widget: Rc<RefCell<Option<(usize, usize)>>>,
|
||||
|
@ -106,10 +107,11 @@ impl Popup {
|
|||
|
||||
Self {
|
||||
window: win,
|
||||
cache: Rc::new(RefCell::new(HashMap::new())),
|
||||
container_cache: rc_mut!(HashMap::new()),
|
||||
button_cache: rc_mut!(vec![]),
|
||||
monitor: module_info.monitor.clone(),
|
||||
pos,
|
||||
current_widget: Rc::new(RefCell::new(None)),
|
||||
current_widget: rc_mut!(None),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,8 +119,7 @@ impl Popup {
|
|||
debug!("Registered popup content for #{}", key);
|
||||
|
||||
for button in &content.buttons {
|
||||
let id = Ironbar::unique_id();
|
||||
button.set_tag("popup-id", id);
|
||||
button.ensure_popup_id();
|
||||
}
|
||||
|
||||
let orientation = self.pos.orientation();
|
||||
|
@ -126,7 +127,8 @@ impl Popup {
|
|||
let window = self.window.clone();
|
||||
|
||||
let current_widget = self.current_widget.clone();
|
||||
let cache = self.cache.clone();
|
||||
let cache = self.container_cache.clone();
|
||||
let button_cache = self.button_cache.clone();
|
||||
|
||||
content
|
||||
.container
|
||||
|
@ -135,11 +137,9 @@ impl Popup {
|
|||
trace!("Resized: {}x{}", rect.width(), rect.height());
|
||||
|
||||
if let Some((widget_id, button_id)) = *current_widget.borrow() {
|
||||
if let Some(PopupCacheValue { content, .. }) =
|
||||
cache.borrow().get(&widget_id)
|
||||
{
|
||||
if let Some(PopupCacheValue { .. }) = cache.borrow().get(&widget_id) {
|
||||
Self::set_position(
|
||||
&content.buttons,
|
||||
&button_cache.borrow(),
|
||||
button_id,
|
||||
orientation,
|
||||
&monitor,
|
||||
|
@ -150,7 +150,11 @@ impl Popup {
|
|||
}
|
||||
});
|
||||
|
||||
self.cache
|
||||
self.button_cache
|
||||
.borrow_mut()
|
||||
.append(&mut content.buttons.clone());
|
||||
|
||||
self.container_cache
|
||||
.borrow_mut()
|
||||
.insert(key, PopupCacheValue { name, content });
|
||||
}
|
||||
|
@ -158,16 +162,17 @@ impl Popup {
|
|||
pub fn show(&self, widget_id: usize, button_id: usize) {
|
||||
self.clear_window();
|
||||
|
||||
if let Some(PopupCacheValue { content, .. }) = self.cache.borrow().get(&widget_id) {
|
||||
if let Some(PopupCacheValue { content, .. }) = self.container_cache.borrow().get(&widget_id)
|
||||
{
|
||||
*self.current_widget.borrow_mut() = Some((widget_id, button_id));
|
||||
|
||||
content.container.style_context().add_class("popup");
|
||||
content.container.add_class("popup");
|
||||
self.window.add(&content.container);
|
||||
|
||||
self.window.show();
|
||||
|
||||
Self::set_position(
|
||||
&content.buttons,
|
||||
&self.button_cache.borrow(),
|
||||
button_id,
|
||||
self.pos.orientation(),
|
||||
&self.monitor,
|
||||
|
@ -179,8 +184,9 @@ impl Popup {
|
|||
pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) {
|
||||
self.clear_window();
|
||||
|
||||
if let Some(PopupCacheValue { content, .. }) = self.cache.borrow().get(&widget_id) {
|
||||
content.container.style_context().add_class("popup");
|
||||
if let Some(PopupCacheValue { content, .. }) = self.container_cache.borrow().get(&widget_id)
|
||||
{
|
||||
content.container.add_class("popup");
|
||||
self.window.add(&content.container);
|
||||
|
||||
self.window.show();
|
||||
|
@ -216,7 +222,7 @@ impl Popup {
|
|||
}
|
||||
}
|
||||
|
||||
/// Hides the popover
|
||||
/// Hides the popup
|
||||
pub fn hide(&self) {
|
||||
*self.current_widget.borrow_mut() = None;
|
||||
self.window.hide();
|
||||
|
|
192
test-configs/orientation.corn
Normal file
192
test-configs/orientation.corn
Normal file
|
@ -0,0 +1,192 @@
|
|||
|
||||
{
|
||||
position = "bottom"
|
||||
start = [
|
||||
{
|
||||
type = "clock"
|
||||
format = "%H:%M"
|
||||
}
|
||||
{
|
||||
type = "clock"
|
||||
format = "%H:%M"
|
||||
orientation = "horizontal"
|
||||
}
|
||||
{
|
||||
type = "clock"
|
||||
format = "%H:%M"
|
||||
orientation = "h"
|
||||
}
|
||||
{
|
||||
type = "clock"
|
||||
format = "%H:%M"
|
||||
orientation = "vertical"
|
||||
}
|
||||
{
|
||||
type = "clock"
|
||||
format = "%H:%M"
|
||||
orientation = "v"
|
||||
}
|
||||
{
|
||||
type = "custom"
|
||||
bar = [
|
||||
{
|
||||
type = "label"
|
||||
label = "label"
|
||||
}
|
||||
{
|
||||
type = "label"
|
||||
label = "label"
|
||||
orientation = "horizontal"
|
||||
}
|
||||
{
|
||||
type = "label"
|
||||
label = "label"
|
||||
orientation = "h"
|
||||
}
|
||||
{
|
||||
type = "label"
|
||||
label = "label"
|
||||
orientation = "vertical"
|
||||
}
|
||||
{
|
||||
type = "label"
|
||||
label = "label"
|
||||
orientation = "v"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "custom"
|
||||
bar = [
|
||||
{
|
||||
type = "button"
|
||||
label = "label"
|
||||
}
|
||||
{
|
||||
type = "button"
|
||||
label = "label"
|
||||
orientation = "horizontal"
|
||||
}
|
||||
{
|
||||
type = "button"
|
||||
label = "label"
|
||||
orientation = "h"
|
||||
}
|
||||
{
|
||||
type = "button"
|
||||
label = "label"
|
||||
orientation = "vertical"
|
||||
}
|
||||
{
|
||||
type = "button"
|
||||
label = "label"
|
||||
orientation = "v"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "custom"
|
||||
bar = [
|
||||
{
|
||||
type = "progress"
|
||||
value = "echo 50"
|
||||
}
|
||||
{
|
||||
type = "progress"
|
||||
value = "echo 50"
|
||||
orientation = "horizontal"
|
||||
}
|
||||
{
|
||||
type = "progress"
|
||||
value = "echo 50"
|
||||
orientation = "h"
|
||||
}
|
||||
{
|
||||
type = "progress"
|
||||
value = "echo 50"
|
||||
orientation = "vertical"
|
||||
}
|
||||
{
|
||||
type = "progress"
|
||||
value = "echo 50"
|
||||
orientation = "v"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "custom"
|
||||
bar = [
|
||||
{
|
||||
type = "slider"
|
||||
value = "echo 50"
|
||||
length = 100
|
||||
}
|
||||
{
|
||||
type = "slider"
|
||||
value = "echo 50"
|
||||
length = 100
|
||||
orientation = "horizontal"
|
||||
}
|
||||
{
|
||||
type = "slider"
|
||||
value = "echo 50"
|
||||
length = 100
|
||||
orientation = "h"
|
||||
}
|
||||
{
|
||||
type = "slider"
|
||||
value = "echo 50"
|
||||
length = 100
|
||||
orientation = "vertical"
|
||||
}
|
||||
{
|
||||
type = "slider"
|
||||
value = "echo 50"
|
||||
length = 100
|
||||
orientation = "v"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "sys_info"
|
||||
interval.memory = 30
|
||||
interval.cpu = 1
|
||||
interval.temps = 5
|
||||
interval.disks = 300
|
||||
interval.networks = 3
|
||||
format = [
|
||||
" {cpu_percent}%"
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||
" {uptime}"
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "sys_info"
|
||||
orientation = "vertical"
|
||||
interval.memory = 30
|
||||
interval.cpu = 1
|
||||
interval.temps = 5
|
||||
interval.disks = 300
|
||||
interval.networks = 3
|
||||
format = [
|
||||
" {cpu_percent}%"
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||
" {uptime}"
|
||||
]
|
||||
}
|
||||
{
|
||||
type = "sys_info"
|
||||
layout = "vertical"
|
||||
interval.memory = 30
|
||||
interval.cpu = 1
|
||||
interval.temps = 5
|
||||
interval.disks = 300
|
||||
interval.networks = 3
|
||||
format = [
|
||||
" {cpu_percent}%"
|
||||
" {memory_used} / {memory_total} GB ({memory_percent}%)"
|
||||
" {uptime}"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue