Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b801751bda | ||
|
|
ee67b3be28 | ||
|
|
6442667961 | ||
|
|
68574d4327 | ||
|
|
6871126bd8 | ||
|
|
481adfcaa4 | ||
|
|
64650fbf3a | ||
|
|
a35d25520c | ||
|
|
78e30b39fe | ||
|
|
b81927e3a5 | ||
|
|
5d319e91f2 | ||
|
|
015dcd3204 | ||
|
|
1e38719996 | ||
|
|
6dcae66570 | ||
|
|
649b0efb19 | ||
|
|
023c2fb118 | ||
|
|
ea57f5e18d | ||
|
|
53142d1bea | ||
|
|
7e0f2cad1c | ||
|
|
1d7c3772e4 | ||
|
|
f2ee2dfe7a | ||
|
|
ab8f7ecfc8 | ||
|
|
917838c98c | ||
|
|
5ec46b2a2a |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: Rust
|
name: Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
5
.github/workflows/deploy.yml
vendored
5
.github/workflows/deploy.yml
vendored
@@ -17,6 +17,9 @@ jobs:
|
|||||||
toolchain: stable
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
|
- name: Install build deps
|
||||||
|
run: sudo apt install libgtk-3-dev libgtk-layer-shell-dev
|
||||||
|
|
||||||
- name: Update CHANGELOG
|
- name: Update CHANGELOG
|
||||||
id: changelog
|
id: changelog
|
||||||
uses: Requarks/changelog-action@v1
|
uses: Requarks/changelog-action@v1
|
||||||
@@ -36,7 +39,7 @@ jobs:
|
|||||||
- name: Commit CHANGELOG.md
|
- name: Commit CHANGELOG.md
|
||||||
uses: stefanzweifel/git-auto-commit-action@v4
|
uses: stefanzweifel/git-auto-commit-action@v4
|
||||||
with:
|
with:
|
||||||
branch: main
|
branch: master
|
||||||
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
|
commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]'
|
||||||
file_pattern: CHANGELOG.md
|
file_pattern: CHANGELOG.md
|
||||||
|
|
||||||
|
|||||||
2
.idea/runConfigurations/Clippy__Strict_.xml
generated
2
.idea/runConfigurations/Clippy__Strict_.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Clippy (Strict)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<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 -W clippy::expect_used" />
|
<option name="command" value="clippy -- -W clippy::pedantic -W clippy::nursery -W clippy::unwrap_used" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<option name="channel" value="DEFAULT" />
|
<option name="channel" value="DEFAULT" />
|
||||||
<option name="requiredFeatures" value="false" />
|
<option name="requiredFeatures" value="false" />
|
||||||
|
|||||||
17
.idea/runConfigurations/Format.xml
generated
Normal file
17
.idea/runConfigurations/Format.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Format" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
|
<option name="command" value="fmt" />
|
||||||
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
|
<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 />
|
||||||
|
<option name="isRedirectInput" value="false" />
|
||||||
|
<option name="redirectInputPath" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
1
.idea/runConfigurations/Run.xml
generated
1
.idea/runConfigurations/Run.xml
generated
@@ -12,6 +12,7 @@
|
|||||||
<envs>
|
<envs>
|
||||||
<env name="IRONBAR_CONFIG" value="examples/config.json" />
|
<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="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
||||||
|
<env name="RUST_LOG" value="trace" />
|
||||||
</envs>
|
</envs>
|
||||||
<option name="isRedirectInput" value="false" />
|
<option name="isRedirectInput" value="false" />
|
||||||
<option name="redirectInputPath" value="" />
|
<option name="redirectInputPath" value="" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run (Debug)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
<configuration default="false" name="Run (GTK Debug)" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||||
<option name="command" value="run --package ironbar --bin ironbar" />
|
<option name="command" value="run --package ironbar --bin ironbar" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
|
||||||
<option name="channel" value="DEFAULT" />
|
<option name="channel" value="DEFAULT" />
|
||||||
5
.idea/runConfigurations/Run__Live_Config_.xml
generated
5
.idea/runConfigurations/Run__Live_Config_.xml
generated
@@ -9,7 +9,10 @@
|
|||||||
<option name="withSudo" value="false" />
|
<option name="withSudo" value="false" />
|
||||||
<option name="buildTarget" value="REMOTE" />
|
<option name="buildTarget" value="REMOTE" />
|
||||||
<option name="backtrace" value="SHORT" />
|
<option name="backtrace" value="SHORT" />
|
||||||
<envs />
|
<envs>
|
||||||
|
<env name="PATH" value="/usr/local/bin:/usr/bin:$USER_HOME$/.local/share/npm/bin" />
|
||||||
|
<env name="RUST_LOG" value="trace" />
|
||||||
|
</envs>
|
||||||
<option name="isRedirectInput" value="false" />
|
<option name="isRedirectInput" value="false" />
|
||||||
<option name="redirectInputPath" value="" />
|
<option name="redirectInputPath" value="" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
|
|||||||
42
CHANGELOG.md
Normal file
42
CHANGELOG.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [v0.5.1] - 2022-09-06
|
||||||
|
### :bug: Bug Fixes
|
||||||
|
- [`b81927e`](https://github.com/JakeStanger/ironbar/commit/b81927e3a57808188e31419695a36aa4ea3f2830) - **launcher**: opening new instances when focused/urgent *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
- [`a35d255`](https://github.com/JakeStanger/ironbar/commit/a35d25520cd3fd235cdc77ec6209d88499ca3639) - **launcher**: item state changes not handled correctly *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
### :wrench: Chores
|
||||||
|
- [`481adfc`](https://github.com/JakeStanger/ironbar/commit/481adfcaa41c0d3a1ba7d61edb68db49d959c78f) - **intellij**: update run configs *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
- [`6871126`](https://github.com/JakeStanger/ironbar/commit/6871126bd8def89ccbf2934180d615e781ec32c7) - **release**: v0.5.1 *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.5.0] - 2022-08-25
|
||||||
|
### :sparkles: New Features
|
||||||
|
- [`1e38719`](https://github.com/JakeStanger/ironbar/commit/1e387199962b81caeb40ffbd99a956f24abdf4e3) - introduce logging in some areas *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
### :bug: Bug Fixes
|
||||||
|
- [`023c2fb`](https://github.com/JakeStanger/ironbar/commit/023c2fb118f46f3592f1dfe1a6704014c062ab3f) - **workspaces**: not listening to move event *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
- [`6dcae66`](https://github.com/JakeStanger/ironbar/commit/6dcae66570cf5434e077ec823cded33771b4239c) - avoid creating loads of sway/mpd clients *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
### :wrench: Chores
|
||||||
|
- [`015dcd3`](https://github.com/JakeStanger/ironbar/commit/015dcd3204dfa6a1ebcef1b4f3b345ed733fee2f) - **release**: v0.5.0 *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
|
||||||
|
## [v0.4.0] - 2022-08-22
|
||||||
|
### :sparkles: New Features
|
||||||
|
- [`ab8f7ec`](https://github.com/JakeStanger/ironbar/commit/ab8f7ecfc8fa4b96fce78518af75794641950140) - logging support and proper error handling *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
### :bug: Bug Fixes
|
||||||
|
- [`f2ee2df`](https://github.com/JakeStanger/ironbar/commit/f2ee2dfe7a0f5575d0c3ec09644ca990b088cd85) - error when using with `swaybar_command` *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
### :wrench: Chores
|
||||||
|
- [`1d7c377`](https://github.com/JakeStanger/ironbar/commit/1d7c3772e4b97c7198043cb55fe9c71695a211ab) - **release**: v0.4.0 *(commit by [@JakeStanger](https://github.com/JakeStanger))*
|
||||||
|
|
||||||
|
|
||||||
|
[v0.4.0]: https://github.com/JakeStanger/ironbar/compare/v0.3.0...v0.4.0
|
||||||
|
[v0.5.0]: https://github.com/JakeStanger/ironbar/compare/v0.4.0...v0.5.0
|
||||||
|
[v0.5.1]: https://github.com/JakeStanger/ironbar/compare/v0.5.0...v0.5.1
|
||||||
540
Cargo.lock
generated
540
Cargo.lock
generated
@@ -2,6 +2,21 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -22,12 +37,27 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ansi_term"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.58"
|
version = "1.0.58"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
|
checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayvec"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -133,7 +163,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -142,6 +172,21 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.66"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -244,8 +289,8 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"time",
|
"time 0.1.44",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -287,6 +332,33 @@ dependencies = [
|
|||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-eyre"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"color-spantrace",
|
||||||
|
"eyre",
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color-spantrace"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"owo-colors",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-error",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -295,7 +367,7 @@ checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"atty",
|
"atty",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -315,17 +387,18 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cornfig"
|
name = "cornfig"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7d9e72be8e5eb3eb96acb65d30ddab369be7a268408d8e25cc3a110ad8bf1bf"
|
checksum = "0b6981753b68f7642c3737b302cd37dee779189fcdad975a69d6a7bb165f134e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml 0.8.26",
|
"serde_yaml",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -464,7 +537,7 @@ checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"redox_users",
|
"redox_users",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -500,6 +573,16 @@ version = "2.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "eyre"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
|
||||||
|
dependencies = [
|
||||||
|
"indenter",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -537,41 +620,15 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fsevent"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5ab7d1bd1bd33cc98b0889831b72da23c0aa4df9cec7e0702f46ecea04b35db6"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"fsevent-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fsevent-sys"
|
name = "fsevent-sys"
|
||||||
version = "2.0.1"
|
version = "4.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f41b048a94555da0f42f1d632e2e19510084fb8e303b0daa2816e733fb3644a0"
|
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuchsia-zircon"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"fuchsia-zircon-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuchsia-zircon-sys"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
@@ -737,6 +794,12 @@ dependencies = [
|
|||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gio"
|
name = "gio"
|
||||||
version = "0.15.12"
|
version = "0.15.12"
|
||||||
@@ -764,7 +827,7 @@ dependencies = [
|
|||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -929,15 +992,6 @@ dependencies = [
|
|||||||
"syn 1.0.98",
|
"syn 1.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
|
||||||
dependencies = [
|
|
||||||
"ahash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
@@ -983,6 +1037,12 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indenter"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
@@ -990,14 +1050,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.7.1"
|
version = "0.9.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f"
|
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"inotify-sys",
|
"inotify-sys",
|
||||||
@@ -1022,37 +1082,36 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iovec"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.3.0"
|
version = "0.5.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"color-eyre",
|
||||||
"cornfig",
|
"cornfig",
|
||||||
|
"crossbeam-channel 0.5.6",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"glib",
|
"glib",
|
||||||
"gtk",
|
"gtk",
|
||||||
"gtk-layer-shell",
|
"gtk-layer-shell",
|
||||||
"ksway",
|
"ksway",
|
||||||
|
"lazy_static",
|
||||||
"mpd_client",
|
"mpd_client",
|
||||||
"notify",
|
"notify",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml 0.9.4",
|
"serde_yaml",
|
||||||
"stray",
|
"stray",
|
||||||
|
"strip-ansi-escapes",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
|
"tracing-error",
|
||||||
|
"tracing-subscriber",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1072,13 +1131,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kernel32-sys"
|
name = "kqueue"
|
||||||
version = "0.2.2"
|
version = "1.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
checksum = "4d6112e8f37b59803ac47a42d14f1f3a59bbf72fc6857ffc5be455e28a691f8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.2.8",
|
"kqueue-sys",
|
||||||
"winapi-build",
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kqueue-sys"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8367585489f01bc55dd27404dcf56b95e6da061a256a666ab23be9ba96a2e587"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1104,24 +1173,12 @@ version = "1.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazycell"
|
|
||||||
version = "1.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.126"
|
version = "0.2.126"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linked-hash-map"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
@@ -1141,6 +1198,15 @@ dependencies = [
|
|||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchers"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||||
|
dependencies = [
|
||||||
|
"regex-automata",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1163,22 +1229,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "miniz_oxide"
|
||||||
version = "0.6.23"
|
version = "0.5.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
|
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"adler",
|
||||||
"fuchsia-zircon",
|
|
||||||
"fuchsia-zircon-sys",
|
|
||||||
"iovec",
|
|
||||||
"kernel32-sys",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"miow",
|
|
||||||
"net2",
|
|
||||||
"slab",
|
|
||||||
"winapi 0.2.8",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1193,39 +1249,13 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mio-extras"
|
|
||||||
version = "2.0.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
|
|
||||||
dependencies = [
|
|
||||||
"lazycell",
|
|
||||||
"log",
|
|
||||||
"mio 0.6.23",
|
|
||||||
"slab",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "miow"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
|
|
||||||
dependencies = [
|
|
||||||
"kernel32-sys",
|
|
||||||
"net2",
|
|
||||||
"winapi 0.2.8",
|
|
||||||
"ws2_32-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mpd_client"
|
name = "mpd_client"
|
||||||
version = "0.7.5"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1137104369b60c7dab080f7a46d5e1dae8ba1edd9003c41964ffa46dec14226c"
|
checksum = "ab5ddb4e7f7f0323823dcadfb17cb8b4d25d7ebcfee20779a814091d5b6dec95"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
|
||||||
"futures-core",
|
|
||||||
"mpd_protocol",
|
"mpd_protocol",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1233,28 +1263,17 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mpd_protocol"
|
name = "mpd_protocol"
|
||||||
version = "0.13.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18039a2cd7aa60ebadfe3e759053188def0aef4c036842e645c9ed4490c8ebd3"
|
checksum = "afcc158275b88361fed416b6efe013286b8fe0c8929bbd569504e5992f638693"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
"bytes",
|
"bytes",
|
||||||
"hashbrown 0.11.2",
|
|
||||||
"nom",
|
"nom",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "net2"
|
|
||||||
version = "0.2.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if 0.1.10",
|
|
||||||
"libc",
|
|
||||||
"winapi 0.3.9",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.23.1"
|
version = "0.23.1"
|
||||||
@@ -1280,20 +1299,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "notify"
|
name = "notify"
|
||||||
version = "4.0.17"
|
version = "5.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae03c8c853dba7bfd23e571ff0cff7bc9dceb40a4cd684cd1681824183f45257"
|
checksum = "ed2c66da08abae1c024c01d635253e402341b4060a12e99b31c7594063bf490a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
"crossbeam-channel 0.5.6",
|
||||||
"filetime",
|
"filetime",
|
||||||
"fsevent",
|
|
||||||
"fsevent-sys",
|
"fsevent-sys",
|
||||||
"inotify",
|
"inotify",
|
||||||
|
"kqueue",
|
||||||
"libc",
|
"libc",
|
||||||
"mio 0.6.23",
|
"mio",
|
||||||
"mio-extras",
|
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1302,7 +1321,7 @@ version = "0.3.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
|
checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1345,6 +1364,24 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.13.0"
|
version = "1.13.0"
|
||||||
@@ -1367,6 +1404,12 @@ version = "6.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
|
checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "owo-colors"
|
||||||
|
version = "3.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pango"
|
name = "pango"
|
||||||
version = "0.15.10"
|
version = "0.15.10"
|
||||||
@@ -1423,9 +1466,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8"
|
checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
@@ -1433,9 +1476,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_derive"
|
name = "pest_derive"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91"
|
checksum = "905708f7f674518498c1f8d644481440f476d39ca6ecae83319bba7c6c12da91"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_generator",
|
"pest_generator",
|
||||||
@@ -1443,9 +1486,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_generator"
|
name = "pest_generator"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0"
|
checksum = "5803d8284a629cc999094ecd630f55e91b561a1d1ba75e233b00ae13b91a69ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
@@ -1456,9 +1499,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_meta"
|
name = "pest_meta"
|
||||||
version = "2.2.1"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe"
|
checksum = "1538eb784f07615c6d9a8ab061089c6c54a344c5b4301db51990ca1c241e8c04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pest",
|
"pest",
|
||||||
@@ -1644,6 +1687,15 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||||
|
dependencies = [
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.27"
|
version = "0.6.27"
|
||||||
@@ -1656,9 +1708,15 @@ version = "0.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -1775,21 +1833,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_yaml"
|
name = "serde_yaml"
|
||||||
version = "0.8.26"
|
version = "0.9.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
|
checksum = "89f31df3f50926cdf2855da5fd8812295c34752cb20438dae42a67f79e021ac3"
|
||||||
dependencies = [
|
|
||||||
"indexmap",
|
|
||||||
"ryu",
|
|
||||||
"serde",
|
|
||||||
"yaml-rust",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serde_yaml"
|
|
||||||
version = "0.9.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "79b7c9017c64a49806c6e8df8ef99b92446d09c92457f85f91835b01a8064ae0"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"itoa",
|
"itoa",
|
||||||
@@ -1824,6 +1870,15 @@ version = "1.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.7"
|
version = "0.4.7"
|
||||||
@@ -1846,7 +1901,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1871,6 +1926,15 @@ dependencies = [
|
|||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strip-ansi-escapes"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8"
|
||||||
|
dependencies = [
|
||||||
|
"vte",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -1901,9 +1965,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.25.1"
|
version = "0.26.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "373e4bc9213f734126e2be3e2e118dbc9b909c37487d8d755822bc90f70ae62a"
|
checksum = "4ae2421f3e16b3afd4aa692d23b83d0ba42ee9b0081d5deeb7d21428d7195fb1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
@@ -1911,7 +1975,7 @@ dependencies = [
|
|||||||
"ntapi",
|
"ntapi",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rayon",
|
"rayon",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1938,7 +2002,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"remove_dir_all",
|
"remove_dir_all",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1993,7 +2057,18 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"libc",
|
||||||
|
"num_threads",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2006,13 +2081,13 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio 0.8.4",
|
"mio",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2058,6 +2133,17 @@ dependencies = [
|
|||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel 0.5.6",
|
||||||
|
"time 0.3.13",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.22"
|
version = "0.1.22"
|
||||||
@@ -2076,6 +2162,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
|
checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-error"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||||
|
dependencies = [
|
||||||
|
"tracing",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"log",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b"
|
||||||
|
dependencies = [
|
||||||
|
"ansi_term",
|
||||||
|
"matchers",
|
||||||
|
"once_cell",
|
||||||
|
"regex",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2097,7 +2223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d"
|
checksum = "ce65604324d3cce9b966701489fbd0cf318cb1f7bd9dd07ac9a4ee6fb791930d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2118,6 +2244,18 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0"
|
checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2130,6 +2268,27 @@ version = "0.9.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte"
|
||||||
|
version = "0.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"utf8parse",
|
||||||
|
"vte_generate_state_changes",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vte_generate_state_changes"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 1.0.42",
|
||||||
|
"quote 1.0.20",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "waker-fn"
|
name = "waker-fn"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -2143,7 +2302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"same-file",
|
"same-file",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -2159,12 +2318,6 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi"
|
|
||||||
version = "0.2.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -2175,12 +2328,6 @@ dependencies = [
|
|||||||
"winapi-x86_64-pc-windows-gnu",
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "winapi-build"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-i686-pc-windows-gnu"
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -2193,7 +2340,7 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2245,25 +2392,6 @@ version = "0.36.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ws2_32-sys"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
|
||||||
dependencies = [
|
|
||||||
"winapi 0.2.8",
|
|
||||||
"winapi-build",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yaml-rust"
|
|
||||||
version = "0.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
|
||||||
dependencies = [
|
|
||||||
"linked-hash-map",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
@@ -2298,7 +2426,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uds_windows",
|
"uds_windows",
|
||||||
"winapi 0.3.9",
|
"winapi",
|
||||||
"zbus_macros",
|
"zbus_macros",
|
||||||
"zbus_names",
|
"zbus_names",
|
||||||
"zvariant",
|
"zvariant",
|
||||||
|
|||||||
23
Cargo.toml
23
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "ironbar"
|
name = "ironbar"
|
||||||
version = "0.3.0"
|
version = "0.5.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Customisable wlroots/sway bar"
|
description = "Customisable wlroots/sway bar"
|
||||||
@@ -11,19 +11,28 @@ description = "Customisable wlroots/sway bar"
|
|||||||
gtk = "0.15.5"
|
gtk = "0.15.5"
|
||||||
gtk-layer-shell = "0.4.1"
|
gtk-layer-shell = "0.4.1"
|
||||||
glib = "0.15.12"
|
glib = "0.15.12"
|
||||||
stray = "0.1.1"
|
|
||||||
tokio = { version = "1.20.1", features = ["macros", "rt-multi-thread", "time"] }
|
tokio = { version = "1.20.1", features = ["macros", "rt-multi-thread", "time"] }
|
||||||
|
tracing = "0.1.36"
|
||||||
|
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
|
||||||
|
tracing-error = "0.2.0"
|
||||||
|
tracing-appender = "0.2.2"
|
||||||
|
strip-ansi-escapes = "0.1.1"
|
||||||
|
color-eyre = "0.6.2"
|
||||||
futures-util = "0.3.21"
|
futures-util = "0.3.21"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
serde = { version = "1.0.141", features = ["derive"] }
|
serde = { version = "1.0.141", features = ["derive"] }
|
||||||
serde_json = "1.0.82"
|
serde_json = "1.0.82"
|
||||||
serde_yaml = "0.9.4"
|
serde_yaml = "0.9.4"
|
||||||
toml = "0.5.9"
|
toml = "0.5.9"
|
||||||
cornfig = "0.2.0"
|
cornfig = "0.3.0"
|
||||||
mpd_client = "0.7.5"
|
lazy_static = "1.4.0"
|
||||||
regex = "1.6.0"
|
regex = "1.6.0"
|
||||||
ksway = "0.1.0"
|
stray = "0.1.1"
|
||||||
sysinfo = "0.25.1"
|
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
walkdir = "2.3.2"
|
walkdir = "2.3.2"
|
||||||
notify = "4.0.17"
|
notify = "5.0.0"
|
||||||
|
mpd_client = "1.0.0"
|
||||||
|
ksway = "0.1.0"
|
||||||
|
sysinfo = "0.26.2"
|
||||||
|
# required for wrapping ksway
|
||||||
|
crossbeam-channel = "0.5.6"
|
||||||
47
README.md
47
README.md
@@ -28,6 +28,18 @@ yay -S ironbar-git
|
|||||||
|
|
||||||
[aur package](https://aur.archlinux.org/packages/ironbar-git)
|
[aur package](https://aur.archlinux.org/packages/ironbar-git)
|
||||||
|
|
||||||
|
### Source
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/jakestanger/ironbar.git
|
||||||
|
cd ironbar
|
||||||
|
cargo build --release
|
||||||
|
# change path to wherever you want to install
|
||||||
|
install target/release/ironbar ~/.local/bin/ironbar
|
||||||
|
```
|
||||||
|
|
||||||
|
[repo](https://github.com/jakestanger/ironbar)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Ironbar gives a lot of flexibility when configuring, including multiple file formats
|
Ironbar gives a lot of flexibility when configuring, including multiple file formats
|
||||||
@@ -44,37 +56,22 @@ A full styling guide can be found [here](https://github.com/JakeStanger/ironbar/
|
|||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
This project is in very early stages:
|
This project is in alpha, but should be usable.
|
||||||
|
Everything that is implemented works and should be documented.
|
||||||
|
Proper error handling is in place so things should either fail gracefully with detail, or not fail at all.
|
||||||
|
|
||||||
- Error handling is barely implemented - expect crashes
|
There is currently room for lots more modules, and lots more configuration options for the existing modules.
|
||||||
- There will be bugs!
|
The current configuration schema is not set in stone and breaking changes could come along at any point;
|
||||||
- Lots of modules need more configuration options
|
until the project matures I am more interested in ease of use than backwards compatibility.
|
||||||
- There's room for lots of modules
|
|
||||||
- The code is messy and quite prototypal in places
|
|
||||||
- Config options aren't set in stone - expect breaking changes
|
|
||||||
- Documentation is probably missing in lots of places
|
|
||||||
|
|
||||||
That said, it will be *actively developed* as I am using it on my daily driver.
|
A few bugs do exist, and I am sure there are plenty more to be found.
|
||||||
|
|
||||||
|
The project will be *actively developed* as I am using it on my daily driver.
|
||||||
Bugs will be fixed, features will be added, code will be refactored.
|
Bugs will be fixed, features will be added, code will be refactored.
|
||||||
|
|
||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
|
|
||||||
I welcome contributions of any kind with open arms. That said, please do stick to some basics:
|
Please check [here](https://github.com/JakeStanger/ironbar/blob/master/CONTRIBUTING.md).
|
||||||
|
|
||||||
- For code contributions:
|
|
||||||
- Fix any `cargo clippy` warnings, using at least the default configuration.
|
|
||||||
- Make sure your code is formatted using `cargo fmt`.
|
|
||||||
- Keep any documentation up to date.
|
|
||||||
- I won't enforce it, but preferably stick to [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) messages.
|
|
||||||
|
|
||||||
|
|
||||||
- For PRs:
|
|
||||||
- Please open an issue or discussion beforehand.
|
|
||||||
I'll accept most contributions, but it's best to make sure you're not working on something that won't get accepted :)
|
|
||||||
|
|
||||||
|
|
||||||
- For issues:
|
|
||||||
- Please provide as much information as you can - share your config, any logs, steps to reproduce...
|
|
||||||
|
|
||||||
## Acknowledgements
|
## Acknowledgements
|
||||||
|
|
||||||
|
|||||||
38
src/bar.rs
38
src/bar.rs
@@ -1,11 +1,20 @@
|
|||||||
use crate::config::{BarPosition, ModuleConfig};
|
use crate::config::{BarPosition, ModuleConfig};
|
||||||
use crate::modules::{Module, ModuleInfo, ModuleLocation};
|
use crate::modules::{Module, ModuleInfo, ModuleLocation};
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
use color_eyre::Result;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Application, ApplicationWindow, Orientation};
|
use gtk::{Application, ApplicationWindow, Orientation};
|
||||||
|
use tracing::{debug, info};
|
||||||
|
|
||||||
pub fn create_bar(app: &Application, monitor: &Monitor, monitor_name: &str, config: Config) {
|
/// Creates a new window for a bar,
|
||||||
|
/// sets it up and adds its widgets.
|
||||||
|
pub fn create_bar(
|
||||||
|
app: &Application,
|
||||||
|
monitor: &Monitor,
|
||||||
|
monitor_name: &str,
|
||||||
|
config: Config,
|
||||||
|
) -> Result<()> {
|
||||||
let win = ApplicationWindow::builder().application(app).build();
|
let win = ApplicationWindow::builder().application(app).build();
|
||||||
|
|
||||||
setup_layer_shell(&win, monitor, &config.position);
|
setup_layer_shell(&win, monitor, &config.position);
|
||||||
@@ -31,17 +40,22 @@ pub fn create_bar(app: &Application, monitor: &Monitor, monitor_name: &str, conf
|
|||||||
content.set_center_widget(Some(¢er));
|
content.set_center_widget(Some(¢er));
|
||||||
content.pack_end(&right, false, false, 0);
|
content.pack_end(&right, false, false, 0);
|
||||||
|
|
||||||
load_modules(&left, ¢er, &right, app, config, monitor, monitor_name);
|
load_modules(&left, ¢er, &right, app, config, monitor, monitor_name)?;
|
||||||
win.add(&content);
|
win.add(&content);
|
||||||
|
|
||||||
win.connect_destroy_event(|_, _| {
|
win.connect_destroy_event(|_, _| {
|
||||||
|
info!("Shutting down");
|
||||||
gtk::main_quit();
|
gtk::main_quit();
|
||||||
Inhibit(false)
|
Inhibit(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debug!("Showing bar");
|
||||||
win.show_all();
|
win.show_all();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads the configured modules onto a bar.
|
||||||
fn load_modules(
|
fn load_modules(
|
||||||
left: >k::Box,
|
left: >k::Box,
|
||||||
center: >k::Box,
|
center: >k::Box,
|
||||||
@@ -50,7 +64,7 @@ fn load_modules(
|
|||||||
config: Config,
|
config: Config,
|
||||||
monitor: &Monitor,
|
monitor: &Monitor,
|
||||||
output_name: &str,
|
output_name: &str,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if let Some(modules) = config.left {
|
if let Some(modules) = config.left {
|
||||||
let info = ModuleInfo {
|
let info = ModuleInfo {
|
||||||
app,
|
app,
|
||||||
@@ -60,7 +74,7 @@ fn load_modules(
|
|||||||
output_name,
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(left, modules, &info);
|
add_modules(left, modules, &info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(modules) = config.center {
|
if let Some(modules) = config.center {
|
||||||
@@ -72,7 +86,7 @@ fn load_modules(
|
|||||||
output_name,
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(center, modules, &info);
|
add_modules(center, modules, &info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(modules) = config.right {
|
if let Some(modules) = config.right {
|
||||||
@@ -84,16 +98,21 @@ fn load_modules(
|
|||||||
output_name,
|
output_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
add_modules(right, modules, &info);
|
add_modules(right, modules, &info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: &ModuleInfo) {
|
/// Adds modules into a provided GTK box,
|
||||||
|
/// which should be one of its left, center or right containers.
|
||||||
|
fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: &ModuleInfo) -> Result<()> {
|
||||||
macro_rules! add_module {
|
macro_rules! add_module {
|
||||||
($module:expr, $name:literal) => {{
|
($module:expr, $name:literal) => {{
|
||||||
let widget = $module.into_widget(&info);
|
let widget = $module.into_widget(&info)?;
|
||||||
widget.set_widget_name($name);
|
widget.set_widget_name($name);
|
||||||
content.add(&widget);
|
content.add(&widget);
|
||||||
|
debug!("Added module of type {}", $name);
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,8 +128,11 @@ fn add_modules(content: >k::Box, modules: Vec<ModuleConfig>, info: &ModuleInfo
|
|||||||
ModuleConfig::Focused(module) => add_module!(module, "focused"),
|
ModuleConfig::Focused(module) => add_module!(module, "focused"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets up GTK layer shell for a provided aplication window.
|
||||||
fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor, position: &BarPosition) {
|
fn setup_layer_shell(win: &ApplicationWindow, monitor: &Monitor, position: &BarPosition) {
|
||||||
gtk_layer_shell::init_for_window(win);
|
gtk_layer_shell::init_for_window(win);
|
||||||
gtk_layer_shell::set_monitor(win, monitor);
|
gtk_layer_shell::set_monitor(win, monitor);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub struct Collection<TKey, TData> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
||||||
|
/// Creates a new empty collection.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
keys: vec![],
|
keys: vec![],
|
||||||
@@ -18,6 +19,7 @@ impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts a new key/value pair at the end of the collection.
|
||||||
pub fn insert(&mut self, key: TKey, value: TData) {
|
pub fn insert(&mut self, key: TKey, value: TData) {
|
||||||
self.keys.push(key);
|
self.keys.push(key);
|
||||||
self.values.push(value);
|
self.values.push(value);
|
||||||
@@ -25,6 +27,8 @@ impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
|||||||
assert_eq!(self.keys.len(), self.values.len());
|
assert_eq!(self.keys.len(), self.values.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a reference of the value for the specified key
|
||||||
|
/// if it exists in the collection.
|
||||||
pub fn get(&self, key: &TKey) -> Option<&TData> {
|
pub fn get(&self, key: &TKey) -> Option<&TData> {
|
||||||
let index = self.keys.iter().position(|k| k == key);
|
let index = self.keys.iter().position(|k| k == key);
|
||||||
match index {
|
match index {
|
||||||
@@ -33,6 +37,8 @@ impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference for the value with the specified key
|
||||||
|
/// if it exists in the collection.
|
||||||
pub fn get_mut(&mut self, key: &TKey) -> Option<&mut TData> {
|
pub fn get_mut(&mut self, key: &TKey) -> Option<&mut TData> {
|
||||||
let index = self.keys.iter().position(|k| k == key);
|
let index = self.keys.iter().position(|k| k == key);
|
||||||
match index {
|
match index {
|
||||||
@@ -41,6 +47,9 @@ impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes the key/value from the collection
|
||||||
|
/// if it exists
|
||||||
|
/// and returns the removed value.
|
||||||
pub fn remove(&mut self, key: &TKey) -> Option<TData> {
|
pub fn remove(&mut self, key: &TKey) -> Option<TData> {
|
||||||
assert_eq!(self.keys.len(), self.values.len());
|
assert_eq!(self.keys.len(), self.values.len());
|
||||||
|
|
||||||
@@ -53,26 +62,32 @@ impl<TKey: PartialEq, TData> Collection<TKey, TData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the length of the collection.
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.keys.len()
|
self.keys.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a reference to the first value in the collection.
|
||||||
pub fn first(&self) -> Option<&TData> {
|
pub fn first(&self) -> Option<&TData> {
|
||||||
self.values.first()
|
self.values.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the values as a slice.
|
||||||
pub fn as_slice(&self) -> &[TData] {
|
pub fn as_slice(&self) -> &[TData] {
|
||||||
self.values.as_slice()
|
self.values.as_slice()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the collection is empty.
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.keys.is_empty()
|
self.keys.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets an iterator for the collection.
|
||||||
pub fn iter(&self) -> Iter<'_, TData> {
|
pub fn iter(&self) -> Iter<'_, TData> {
|
||||||
self.values.iter()
|
self.values.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable iterator for the collection
|
||||||
pub fn iter_mut(&mut self) -> IterMut<'_, TData> {
|
pub fn iter_mut(&mut self) -> IterMut<'_, TData> {
|
||||||
self.values.iter_mut()
|
self.values.iter_mut()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ use crate::modules::script::ScriptModule;
|
|||||||
use crate::modules::sysinfo::SysInfoModule;
|
use crate::modules::sysinfo::SysInfoModule;
|
||||||
use crate::modules::tray::TrayModule;
|
use crate::modules::tray::TrayModule;
|
||||||
use crate::modules::workspaces::WorkspacesModule;
|
use crate::modules::workspaces::WorkspacesModule;
|
||||||
|
use color_eyre::eyre::{Context, ContextCompat};
|
||||||
|
use color_eyre::{eyre, Help, Report};
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
|
use eyre::Result;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -68,49 +71,74 @@ const fn default_bar_height() -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load() -> Option<Self> {
|
/// Attempts to load the config file from file,
|
||||||
if let Ok(config_path) = env::var("IRONBAR_CONFIG") {
|
/// parse it and return a new instance of `Self`.
|
||||||
|
pub fn load() -> Result<Self> {
|
||||||
|
let config_path = if let Ok(config_path) = env::var("IRONBAR_CONFIG") {
|
||||||
let path = PathBuf::from(config_path);
|
let path = PathBuf::from(config_path);
|
||||||
Self::load_file(
|
if path.exists() {
|
||||||
&path,
|
Ok(path)
|
||||||
path.extension()
|
} else {
|
||||||
.unwrap_or_default()
|
Err(Report::msg("Specified config file does not exist")
|
||||||
.to_str()
|
.note("Config file was specified using `IRONBAR_CONFIG` environment variable"))
|
||||||
.unwrap_or_default(),
|
}
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
let config_dir = config_dir().expect("Failed to locate user config dir");
|
Self::try_find_config()
|
||||||
|
}?;
|
||||||
|
|
||||||
let extensions = vec!["json", "toml", "yaml", "yml", "corn"];
|
Self::load_file(&config_path)
|
||||||
|
}
|
||||||
|
|
||||||
extensions.into_iter().find_map(|extension| {
|
/// Attempts to discover the location of the config file
|
||||||
let full_path = config_dir
|
/// by checking each valid format's extension.
|
||||||
.join("ironbar")
|
///
|
||||||
.join(format!("config.{extension}"));
|
/// Returns the path of the first valid match, if any.
|
||||||
|
fn try_find_config() -> Result<PathBuf> {
|
||||||
|
let config_dir = config_dir().wrap_err("Failed to locate user config dir")?;
|
||||||
|
|
||||||
Self::load_file(&full_path, extension)
|
let extensions = vec!["json", "toml", "yaml", "yml", "corn"];
|
||||||
})
|
|
||||||
|
let file = extensions.into_iter().find_map(|extension| {
|
||||||
|
let full_path = config_dir
|
||||||
|
.join("ironbar")
|
||||||
|
.join(format!("config.{extension}"));
|
||||||
|
|
||||||
|
if Path::exists(&full_path) {
|
||||||
|
Some(full_path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
match file {
|
||||||
|
Some(file) => Ok(file),
|
||||||
|
None => Err(Report::msg("Could not find config file")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_file(path: &Path, extension: &str) -> Option<Self> {
|
/// Loads the config file at the specified path
|
||||||
if path.exists() {
|
/// and parses it into `Self` based on its extension.
|
||||||
let file = fs::read(path).expect("Failed to read config file");
|
fn load_file(path: &Path) -> Result<Self> {
|
||||||
Some(match extension {
|
let file = fs::read(path).wrap_err("Failed to read config file")?;
|
||||||
"json" => serde_json::from_slice(&file).expect("Invalid JSON config"),
|
let extension = path
|
||||||
"toml" => toml::from_slice(&file).expect("Invalid TOML config"),
|
.extension()
|
||||||
"yaml" | "yml" => serde_yaml::from_slice(&file).expect("Invalid YAML config"),
|
.unwrap_or_default()
|
||||||
"corn" => {
|
.to_str()
|
||||||
// corn doesn't support deserialization yet
|
.unwrap_or_default();
|
||||||
// so serialize the interpreted result then deserialize that
|
|
||||||
let file = String::from_utf8(file).expect("Config file contains invalid UTF-8");
|
match extension {
|
||||||
let config = cornfig::parse(&file).expect("Invalid corn config").value;
|
"json" => serde_json::from_slice(&file).wrap_err("Invalid JSON config"),
|
||||||
serde_json::from_str(&serde_json::to_string(&config).unwrap()).unwrap()
|
"toml" => toml::from_slice(&file).wrap_err("Invalid TOML config"),
|
||||||
}
|
"yaml" | "yml" => serde_yaml::from_slice(&file).wrap_err("Invalid YAML config"),
|
||||||
_ => unreachable!(),
|
"corn" => {
|
||||||
})
|
// corn doesn't support deserialization yet
|
||||||
} else {
|
// so serialize the interpreted result then deserialize that
|
||||||
None
|
let file =
|
||||||
|
String::from_utf8(file).wrap_err("Config file contains invalid UTF-8")?;
|
||||||
|
let config = cornfig::parse(&file).wrap_err("Invalid corn config")?.value;
|
||||||
|
Ok(serde_json::from_str(&serde_json::to_string(&config)?)?)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/icon.rs
25
src/icon.rs
@@ -58,9 +58,7 @@ fn parse_desktop_file(path: PathBuf) -> io::Result<HashMap<String, String>> {
|
|||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
for line in lines.flatten() {
|
for line in lines.flatten() {
|
||||||
let is_pair = line.contains('=');
|
if let Some((key, value)) = line.split_once('=') {
|
||||||
if is_pair {
|
|
||||||
let (key, value) = line.split_once('=').unwrap();
|
|
||||||
map.insert(key.to_string(), value.to_string());
|
map.insert(key.to_string(), value.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,6 +86,10 @@ enum IconLocation {
|
|||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempts to get the location of an icon.
|
||||||
|
///
|
||||||
|
/// Handles icons that are part of a GTK theme, icons specified as path
|
||||||
|
/// and icons for steam games.
|
||||||
fn get_icon_location(theme: &IconTheme, app_id: &str, size: i32) -> Option<IconLocation> {
|
fn get_icon_location(theme: &IconTheme, app_id: &str, size: i32) -> Option<IconLocation> {
|
||||||
let has_icon = theme
|
let has_icon = theme
|
||||||
.lookup_icon(app_id, size, IconLookupFlags::empty())
|
.lookup_icon(app_id, size, IconLookupFlags::empty())
|
||||||
@@ -100,13 +102,18 @@ fn get_icon_location(theme: &IconTheme, app_id: &str, size: i32) -> Option<IconL
|
|||||||
let is_steam_game = app_id.starts_with("steam_app_");
|
let is_steam_game = app_id.starts_with("steam_app_");
|
||||||
if is_steam_game {
|
if is_steam_game {
|
||||||
let steam_id: String = app_id.chars().skip("steam_app_".len()).collect();
|
let steam_id: String = app_id.chars().skip("steam_app_".len()).collect();
|
||||||
let home_dir = dirs::data_dir().unwrap();
|
|
||||||
let path = home_dir.join(format!(
|
|
||||||
"icons/hicolor/32x32/apps/steam_icon_{}.png",
|
|
||||||
steam_id
|
|
||||||
));
|
|
||||||
|
|
||||||
return Some(IconLocation::File(path));
|
return match dirs::data_dir() {
|
||||||
|
Some(dir) => {
|
||||||
|
let path = dir.join(format!(
|
||||||
|
"icons/hicolor/32x32/apps/steam_icon_{}.png",
|
||||||
|
steam_id
|
||||||
|
));
|
||||||
|
|
||||||
|
return Some(IconLocation::File(path));
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let icon_name = get_desktop_icon_name(app_id);
|
let icon_name = get_desktop_icon_name(app_id);
|
||||||
|
|||||||
57
src/logging.rs
Normal file
57
src/logging.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use color_eyre::Result;
|
||||||
|
use dirs::data_dir;
|
||||||
|
use std::env;
|
||||||
|
use strip_ansi_escapes::Writer;
|
||||||
|
use tracing_appender::non_blocking::{NonBlocking, WorkerGuard};
|
||||||
|
use tracing_error::ErrorLayer;
|
||||||
|
use tracing_subscriber::fmt::{Layer, MakeWriter};
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
use tracing_subscriber::{fmt, EnvFilter};
|
||||||
|
|
||||||
|
struct MakeFileWriter {
|
||||||
|
file_writer: NonBlocking,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MakeFileWriter {
|
||||||
|
const fn new(file_writer: NonBlocking) -> Self {
|
||||||
|
Self { file_writer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MakeWriter<'a> for MakeFileWriter {
|
||||||
|
type Writer = Writer<NonBlocking>;
|
||||||
|
|
||||||
|
fn make_writer(&'a self) -> Self::Writer {
|
||||||
|
Writer::new(self.file_writer.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Installs tracing into the current application.
|
||||||
|
///
|
||||||
|
/// The returned `WorkerGuard` must remain in scope
|
||||||
|
/// for the lifetime of the application for logging to file to work.
|
||||||
|
pub fn install_tracing() -> Result<WorkerGuard> {
|
||||||
|
let fmt_layer = fmt::layer().with_target(true);
|
||||||
|
let filter_layer = EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?;
|
||||||
|
let file_filter_layer =
|
||||||
|
EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("warn"))?;
|
||||||
|
|
||||||
|
let log_path = data_dir().unwrap_or(env::current_dir()?).join("ironbar");
|
||||||
|
|
||||||
|
let appender = tracing_appender::rolling::never(log_path, "error.log");
|
||||||
|
let (file_writer, guard) = tracing_appender::non_blocking(appender);
|
||||||
|
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(filter_layer)
|
||||||
|
.with(fmt_layer)
|
||||||
|
.with(ErrorLayer::default())
|
||||||
|
.with(
|
||||||
|
Layer::default()
|
||||||
|
.with_writer(MakeFileWriter::new(file_writer))
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_filter(file_filter_layer),
|
||||||
|
)
|
||||||
|
.init();
|
||||||
|
|
||||||
|
Ok(guard)
|
||||||
|
}
|
||||||
166
src/main.rs
166
src/main.rs
@@ -2,6 +2,7 @@ mod bar;
|
|||||||
mod collection;
|
mod collection;
|
||||||
mod config;
|
mod config;
|
||||||
mod icon;
|
mod icon;
|
||||||
|
mod logging;
|
||||||
mod modules;
|
mod modules;
|
||||||
mod popup;
|
mod popup;
|
||||||
mod style;
|
mod style;
|
||||||
@@ -10,74 +11,137 @@ mod sway;
|
|||||||
use crate::bar::create_bar;
|
use crate::bar::create_bar;
|
||||||
use crate::config::{Config, MonitorConfig};
|
use crate::config::{Config, MonitorConfig};
|
||||||
use crate::style::load_css;
|
use crate::style::load_css;
|
||||||
use crate::sway::SwayOutput;
|
use crate::sway::{get_client, SwayOutput};
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use color_eyre::Report;
|
||||||
use dirs::config_dir;
|
use dirs::config_dir;
|
||||||
|
use gtk::gdk::Display;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{gdk, Application};
|
use gtk::Application;
|
||||||
use ksway::client::Client;
|
|
||||||
use ksway::IpcCommand;
|
use ksway::IpcCommand;
|
||||||
|
use std::env;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
use crate::logging::install_tracing;
|
||||||
|
use tracing::{debug, error, info};
|
||||||
|
|
||||||
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<()> {
|
||||||
|
// Disable backtraces by default
|
||||||
|
if env::var("RUST_LIB_BACKTRACE").is_err() {
|
||||||
|
env::set_var("RUST_LIB_BACKTRACE", "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep guard in scope
|
||||||
|
// otherwise file logging drops
|
||||||
|
let _guard = install_tracing()?;
|
||||||
|
|
||||||
|
color_eyre::install()?;
|
||||||
|
|
||||||
|
info!("Ironbar version {}", VERSION);
|
||||||
|
info!("Starting application");
|
||||||
|
|
||||||
let app = Application::builder()
|
let app = Application::builder()
|
||||||
.application_id("dev.jstanger.waylandbar")
|
.application_id("dev.jstanger.ironbar")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let mut sway_client = Client::connect().expect("Failed to connect to Sway IPC");
|
|
||||||
let outputs = sway_client
|
|
||||||
.ipc(IpcCommand::GetOutputs)
|
|
||||||
.expect("Failed to get Sway outputs");
|
|
||||||
let outputs = serde_json::from_slice::<Vec<SwayOutput>>(&outputs)
|
|
||||||
.expect("Failed to deserialize outputs message from Sway IPC");
|
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
let config = Config::load().unwrap_or_default();
|
let display = match Display::default() {
|
||||||
|
Some(display) => display,
|
||||||
|
None => {
|
||||||
|
let report = Report::msg("Failed to get default GTK display");
|
||||||
|
error!("{:?}", report);
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Better logging (https://crates.io/crates/tracing)
|
let config = match Config::load() {
|
||||||
// TODO: error handling (https://crates.io/crates/color-eyre)
|
Ok(config) => config,
|
||||||
|
Err(err) => {
|
||||||
|
error!("{:?}", err);
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
debug!("Loaded config file");
|
||||||
|
|
||||||
// TODO: Embedded Deno/lua - build custom modules via script???
|
if let Err(err) = create_bars(app, &display, &config) {
|
||||||
|
error!("{:?}", err);
|
||||||
let display = gdk::Display::default().expect("Failed to get default GDK display");
|
exit(2);
|
||||||
let num_monitors = display.n_monitors();
|
|
||||||
|
|
||||||
for i in 0..num_monitors {
|
|
||||||
let monitor = display.monitor(i).unwrap();
|
|
||||||
let monitor_name = &outputs
|
|
||||||
.get(i as usize)
|
|
||||||
.expect("GTK monitor output differs from Sway's")
|
|
||||||
.name;
|
|
||||||
|
|
||||||
config.monitors.as_ref().map_or_else(
|
|
||||||
|| {
|
|
||||||
create_bar(app, &monitor, monitor_name, config.clone());
|
|
||||||
},
|
|
||||||
|config| {
|
|
||||||
let config = config.get(monitor_name);
|
|
||||||
match &config {
|
|
||||||
Some(MonitorConfig::Single(config)) => {
|
|
||||||
create_bar(app, &monitor, monitor_name, config.clone());
|
|
||||||
}
|
|
||||||
Some(MonitorConfig::Multiple(configs)) => {
|
|
||||||
for config in configs {
|
|
||||||
create_bar(app, &monitor, monitor_name, config.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let style_path = config_dir()
|
debug!("Created bars");
|
||||||
.expect("Failed to locate user config dir")
|
|
||||||
.join("ironbar")
|
let style_path = match config_dir() {
|
||||||
.join("style.css");
|
Some(dir) => dir.join("ironbar").join("style.css"),
|
||||||
|
None => {
|
||||||
|
let report = Report::msg("Failed to locate user config dir");
|
||||||
|
error!("{:?}", report);
|
||||||
|
exit(3);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if style_path.exists() {
|
if style_path.exists() {
|
||||||
load_css(style_path);
|
load_css(style_path);
|
||||||
|
debug!("Loaded CSS watcher file");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.run();
|
// Ignore CLI args
|
||||||
|
// Some are provided by swaybar_config but not currently supported
|
||||||
|
app.run_with_args(&Vec::<&str>::new());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates each of the bars across each of the (configured) outputs.
|
||||||
|
fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<()> {
|
||||||
|
let outputs = {
|
||||||
|
let sway = get_client();
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
|
||||||
|
let outputs = sway.ipc(IpcCommand::GetOutputs);
|
||||||
|
|
||||||
|
match outputs {
|
||||||
|
Ok(outputs) => Ok(outputs),
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
let outputs = serde_json::from_slice::<Vec<SwayOutput>>(&outputs)?;
|
||||||
|
|
||||||
|
debug!("Received {} outputs from Sway IPC", outputs.len());
|
||||||
|
|
||||||
|
let num_monitors = display.n_monitors();
|
||||||
|
|
||||||
|
for i in 0..num_monitors {
|
||||||
|
let monitor = display.monitor(i).ok_or_else(|| Report::msg("GTK and Sway are reporting a different number of outputs - this is a severe bug and should never happen"))?;
|
||||||
|
let monitor_name = &outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?.name;
|
||||||
|
|
||||||
|
info!("Creating bar on '{}'", monitor_name);
|
||||||
|
|
||||||
|
// TODO: Could we use an Arc<Config> here to avoid cloning?
|
||||||
|
config.monitors.as_ref().map_or_else(
|
||||||
|
|| create_bar(app, &monitor, monitor_name, config.clone()),
|
||||||
|
|config| {
|
||||||
|
let config = config.get(monitor_name);
|
||||||
|
match &config {
|
||||||
|
Some(MonitorConfig::Single(config)) => {
|
||||||
|
create_bar(app, &monitor, monitor_name, config.clone())
|
||||||
|
}
|
||||||
|
Some(MonitorConfig::Multiple(configs)) => {
|
||||||
|
for config in configs {
|
||||||
|
create_bar(app, &monitor, monitor_name, config.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod popup;
|
|||||||
use self::popup::Popup;
|
use self::popup::Popup;
|
||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
|
use color_eyre::Result;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Orientation};
|
use gtk::{Button, Orientation};
|
||||||
@@ -26,7 +27,7 @@ fn default_format() -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<Button> for ClockModule {
|
impl Module<Button> for ClockModule {
|
||||||
fn into_widget(self, info: &ModuleInfo) -> Button {
|
fn into_widget(self, info: &ModuleInfo) -> Result<Button> {
|
||||||
let button = Button::new();
|
let button = Button::new();
|
||||||
|
|
||||||
let popup = Popup::new(
|
let popup = Popup::new(
|
||||||
@@ -51,7 +52,8 @@ impl Module<Button> for ClockModule {
|
|||||||
let date = Local::now();
|
let date = Local::now();
|
||||||
let date_string = format!("{}", date.format(format));
|
let date_string = format!("{}", date.format(format));
|
||||||
|
|
||||||
tx.send(date_string).unwrap();
|
tx.send(date_string).expect("Failed to send date string");
|
||||||
|
|
||||||
sleep(tokio::time::Duration::from_millis(500)).await;
|
sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -64,6 +66,6 @@ impl Module<Button> for ClockModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
button
|
Ok(button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ impl Popup {
|
|||||||
let date = Local::now();
|
let date = Local::now();
|
||||||
let date_string = format!("{}", date.format(format));
|
let date_string = format!("{}", date.format(format));
|
||||||
|
|
||||||
tx.send(date_string).unwrap();
|
tx.send(date_string).expect("Failed to send date string");
|
||||||
|
|
||||||
sleep(tokio::time::Duration::from_millis(500)).await;
|
sleep(tokio::time::Duration::from_millis(500)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
use crate::icon;
|
use crate::icon;
|
||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use crate::sway::node::get_open_windows;
|
use crate::sway::get_client;
|
||||||
use crate::sway::WindowEvent;
|
use color_eyre::Result;
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{IconTheme, Image, Label, Orientation};
|
use gtk::{IconTheme, Image, Label, Orientation};
|
||||||
use ksway::{Client, IpcEvent};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct FocusedModule {
|
pub struct FocusedModule {
|
||||||
|
/// Whether to show icon on the bar.
|
||||||
#[serde(default = "crate::config::default_true")]
|
#[serde(default = "crate::config::default_true")]
|
||||||
show_icon: bool,
|
show_icon: bool,
|
||||||
|
/// Whether to show app name on the bar.
|
||||||
#[serde(default = "crate::config::default_true")]
|
#[serde(default = "crate::config::default_true")]
|
||||||
show_title: bool,
|
show_title: bool,
|
||||||
|
|
||||||
|
/// Icon size in pixels.
|
||||||
#[serde(default = "default_icon_size")]
|
#[serde(default = "default_icon_size")]
|
||||||
icon_size: i32,
|
icon_size: i32,
|
||||||
|
/// GTK icon theme to use.
|
||||||
icon_theme: Option<String>,
|
icon_theme: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +29,7 @@ const fn default_icon_size() -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<gtk::Box> for FocusedModule {
|
impl Module<gtk::Box> for FocusedModule {
|
||||||
fn into_widget(self, _info: &ModuleInfo) -> gtk::Box {
|
fn into_widget(self, _info: &ModuleInfo) -> Result<gtk::Box> {
|
||||||
let icon_theme = IconTheme::new();
|
let icon_theme = IconTheme::new();
|
||||||
|
|
||||||
if let Some(theme) = self.icon_theme {
|
if let Some(theme) = self.icon_theme {
|
||||||
@@ -41,23 +44,28 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
container.add(&icon);
|
container.add(&icon);
|
||||||
container.add(&label);
|
container.add(&label);
|
||||||
|
|
||||||
let mut sway = Client::connect().unwrap();
|
|
||||||
|
|
||||||
let srx = sway.subscribe(vec![IpcEvent::Window]).unwrap();
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
let focused = get_open_windows(&mut sway)
|
let focused = {
|
||||||
.into_iter()
|
let sway = get_client();
|
||||||
.find(|node| node.focused);
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
sway.get_open_windows()?
|
||||||
|
.into_iter()
|
||||||
|
.find(|node| node.focused)
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(focused) = focused {
|
if let Some(focused) = focused {
|
||||||
tx.send(focused).unwrap();
|
tx.send(focused)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn_blocking(move || loop {
|
spawn_blocking(move || {
|
||||||
while let Ok((_, payload)) = srx.try_recv() {
|
let srx = {
|
||||||
let payload: WindowEvent = serde_json::from_slice(&payload).unwrap();
|
let sway = get_client();
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
sway.subscribe_window()
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Ok(payload) = srx.recv() {
|
||||||
let update = match payload.change.as_str() {
|
let update = match payload.change.as_str() {
|
||||||
"focus" => true,
|
"focus" => true,
|
||||||
"title" => payload.container.focused,
|
"title" => payload.container.focused,
|
||||||
@@ -65,10 +73,10 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if update {
|
if update {
|
||||||
tx.send(payload.container).unwrap();
|
tx.send(payload.container)
|
||||||
|
.expect("Failed to sendf focus update");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sway.poll().unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -89,6 +97,6 @@ impl Module<gtk::Box> for FocusedModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
container
|
Ok(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
use crate::collection::Collection;
|
use crate::collection::Collection;
|
||||||
use crate::icon::{find_desktop_file, get_icon};
|
use crate::icon::{find_desktop_file, get_icon};
|
||||||
|
use crate::modules::launcher::open_state::OpenState;
|
||||||
use crate::modules::launcher::popup::Popup;
|
use crate::modules::launcher::popup::Popup;
|
||||||
use crate::modules::launcher::FocusEvent;
|
use crate::modules::launcher::FocusEvent;
|
||||||
use crate::sway::SwayNode;
|
use crate::sway::SwayNode;
|
||||||
|
use crate::Report;
|
||||||
|
use color_eyre::Help;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, IconTheme, Image};
|
use gtk::{Button, IconTheme, Image};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LauncherItem {
|
pub struct LauncherItem {
|
||||||
pub app_id: String,
|
pub app_id: String,
|
||||||
pub favorite: bool,
|
pub favorite: bool,
|
||||||
pub windows: Rc<Mutex<Collection<i32, LauncherWindow>>>,
|
pub windows: Rc<RwLock<Collection<i32, LauncherWindow>>>,
|
||||||
pub state: Arc<RwLock<State>>,
|
pub state: Arc<RwLock<State>>,
|
||||||
pub button: Button,
|
pub button: Button,
|
||||||
}
|
}
|
||||||
@@ -24,14 +28,13 @@ pub struct LauncherItem {
|
|||||||
pub struct LauncherWindow {
|
pub struct LauncherWindow {
|
||||||
pub con_id: i32,
|
pub con_id: i32,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
pub open_state: OpenState,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub is_xwayland: bool,
|
pub is_xwayland: bool,
|
||||||
pub open: bool,
|
pub open_state: OpenState,
|
||||||
pub focused: bool,
|
|
||||||
pub urgent: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -49,16 +52,14 @@ impl LauncherItem {
|
|||||||
button.style_context().add_class("item");
|
button.style_context().add_class("item");
|
||||||
|
|
||||||
let state = State {
|
let state = State {
|
||||||
open: false,
|
open_state: OpenState::Closed,
|
||||||
focused: false,
|
|
||||||
urgent: false,
|
|
||||||
is_xwayland: false,
|
is_xwayland: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = Self {
|
let item = Self {
|
||||||
app_id,
|
app_id,
|
||||||
favorite,
|
favorite,
|
||||||
windows: Rc::new(Mutex::new(Collection::new())),
|
windows: Rc::new(RwLock::new(Collection::new())),
|
||||||
state: Arc::new(RwLock::new(state)),
|
state: Arc::new(RwLock::new(state)),
|
||||||
button,
|
button,
|
||||||
};
|
};
|
||||||
@@ -76,20 +77,19 @@ impl LauncherItem {
|
|||||||
LauncherWindow {
|
LauncherWindow {
|
||||||
con_id: node.id,
|
con_id: node.id,
|
||||||
name: node.name.clone(),
|
name: node.name.clone(),
|
||||||
|
open_state: OpenState::from_node(node),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
let state = State {
|
let state = State {
|
||||||
open: true,
|
open_state: OpenState::from_node(node),
|
||||||
focused: node.focused,
|
|
||||||
urgent: node.urgent,
|
|
||||||
is_xwayland: node.is_xwayland(),
|
is_xwayland: node.is_xwayland(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = Self {
|
let item = Self {
|
||||||
app_id: node.get_id().to_string(),
|
app_id: node.get_id().to_string(),
|
||||||
favorite: false,
|
favorite: false,
|
||||||
windows: Rc::new(Mutex::new(windows)),
|
windows: Rc::new(RwLock::new(windows)),
|
||||||
state: Arc::new(RwLock::new(state)),
|
state: Arc::new(RwLock::new(state)),
|
||||||
button,
|
button,
|
||||||
};
|
};
|
||||||
@@ -101,10 +101,17 @@ impl LauncherItem {
|
|||||||
fn configure_button(&self, config: &ButtonConfig) {
|
fn configure_button(&self, config: &ButtonConfig) {
|
||||||
let button = &self.button;
|
let button = &self.button;
|
||||||
|
|
||||||
let windows = self.windows.lock().unwrap();
|
let windows = self
|
||||||
|
.windows
|
||||||
|
.read()
|
||||||
|
.expect("Failed to get read lock on windows");
|
||||||
|
|
||||||
let name = if windows.len() == 1 {
|
let name = if windows.len() == 1 {
|
||||||
windows.first().unwrap().name.as_ref()
|
windows
|
||||||
|
.first()
|
||||||
|
.expect("Failed to get first window")
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
} else {
|
} else {
|
||||||
Some(&self.app_id)
|
Some(&self.app_id)
|
||||||
};
|
};
|
||||||
@@ -129,21 +136,31 @@ impl LauncherItem {
|
|||||||
let (focus_tx, mut focus_rx) = mpsc::channel(32);
|
let (focus_tx, mut focus_rx) = mpsc::channel(32);
|
||||||
|
|
||||||
button.connect_clicked(move |_| {
|
button.connect_clicked(move |_| {
|
||||||
let state = state.read().unwrap();
|
let state = state.read().expect("Failed to get read lock on state");
|
||||||
if state.open {
|
if state.open_state.is_open() {
|
||||||
focus_tx.try_send(()).unwrap();
|
focus_tx.try_send(()).expect("Failed to send focus event");
|
||||||
} else {
|
} else {
|
||||||
// attempt to find desktop file and launch
|
// attempt to find desktop file and launch
|
||||||
match find_desktop_file(&app_id) {
|
match find_desktop_file(&app_id) {
|
||||||
Some(file) => {
|
Some(file) => {
|
||||||
Command::new("gtk-launch")
|
if let Err(err) = Command::new("gtk-launch")
|
||||||
.arg(file.file_name().unwrap())
|
.arg(
|
||||||
|
file.file_name()
|
||||||
|
.expect("File segment missing from path to desktop file"),
|
||||||
|
)
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.spawn()
|
.spawn()
|
||||||
.unwrap();
|
{
|
||||||
|
error!(
|
||||||
|
"{:?}",
|
||||||
|
Report::new(err)
|
||||||
|
.wrap_err("Failed to run gtk-launch command.")
|
||||||
|
.suggestion("Perhaps the desktop file is invalid?")
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => (),
|
None => error!("Could not find desktop file for {}", app_id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -153,15 +170,15 @@ impl LauncherItem {
|
|||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while focus_rx.recv().await == Some(()) {
|
while focus_rx.recv().await == Some(()) {
|
||||||
let state = state.read().unwrap();
|
let state = state.read().expect("Failed to get read lock on state");
|
||||||
if state.is_xwayland {
|
if state.is_xwayland {
|
||||||
tx_click
|
tx_click
|
||||||
.try_send(FocusEvent::Class(app_id.clone()))
|
.try_send(FocusEvent::Class(app_id.clone()))
|
||||||
.unwrap();
|
.expect("Failed to send focus event");
|
||||||
} else {
|
} else {
|
||||||
tx_click
|
tx_click
|
||||||
.try_send(FocusEvent::AppId(app_id.clone()))
|
.try_send(FocusEvent::AppId(app_id.clone()))
|
||||||
.unwrap();
|
.expect("Failed to send focus event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -172,7 +189,7 @@ impl LauncherItem {
|
|||||||
let tx_hover = config.tx.clone();
|
let tx_hover = config.tx.clone();
|
||||||
|
|
||||||
button.connect_enter_notify_event(move |button, _| {
|
button.connect_enter_notify_event(move |button, _| {
|
||||||
let windows = windows.lock().unwrap();
|
let windows = windows.read().expect("Failed to get read lock on windows");
|
||||||
if windows.len() > 1 {
|
if windows.len() > 1 {
|
||||||
popup.set_windows(windows.as_slice(), &tx_hover);
|
popup.set_windows(windows.as_slice(), &tx_hover);
|
||||||
popup.show(button);
|
popup.show(button);
|
||||||
@@ -196,7 +213,7 @@ impl LauncherItem {
|
|||||||
let style = button.style_context();
|
let style = button.style_context();
|
||||||
|
|
||||||
style.add_class("launcher-item");
|
style.add_class("launcher-item");
|
||||||
self.update_button_classes(&self.state.read().unwrap());
|
self.update_button_classes(&self.state.read().expect("Failed to get read lock on state"));
|
||||||
|
|
||||||
button.show_all();
|
button.show_all();
|
||||||
}
|
}
|
||||||
@@ -223,22 +240,53 @@ impl LauncherItem {
|
|||||||
style.remove_class("favorite");
|
style.remove_class("favorite");
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.open {
|
if state.open_state.is_open() {
|
||||||
style.add_class("open");
|
style.add_class("open");
|
||||||
} else {
|
} else {
|
||||||
style.remove_class("open");
|
style.remove_class("open");
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.focused {
|
if state.open_state.is_focused() {
|
||||||
style.add_class("focused");
|
style.add_class("focused");
|
||||||
} else {
|
} else {
|
||||||
style.remove_class("focused");
|
style.remove_class("focused");
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.urgent {
|
if state.open_state.is_urgent() {
|
||||||
style.add_class("urgent");
|
style.add_class("urgent");
|
||||||
} else {
|
} else {
|
||||||
style.remove_class("urgent");
|
style.remove_class("urgent");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the open state for a specific window on the item
|
||||||
|
/// and updates the item state based on all its windows.
|
||||||
|
pub fn set_window_open_state(&self, window_id: i32, new_state: OpenState, state: &mut State) {
|
||||||
|
let mut windows = self
|
||||||
|
.windows
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on windows");
|
||||||
|
|
||||||
|
let window = windows.iter_mut().find(|w| w.con_id == window_id);
|
||||||
|
if let Some(window) = window {
|
||||||
|
window.open_state = new_state;
|
||||||
|
|
||||||
|
state.open_state =
|
||||||
|
OpenState::merge_states(windows.iter().map(|w| &w.open_state).collect());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the open state on the item and all its windows.
|
||||||
|
/// This overrides the existing open states.
|
||||||
|
pub fn set_open_state(&self, new_state: OpenState, state: &mut State) {
|
||||||
|
state.open_state = new_state;
|
||||||
|
let mut windows = self
|
||||||
|
.windows
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on windows");
|
||||||
|
|
||||||
|
windows
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|window| window.open_state = new_state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,36 @@
|
|||||||
mod item;
|
mod item;
|
||||||
|
mod open_state;
|
||||||
mod popup;
|
mod popup;
|
||||||
|
|
||||||
use crate::collection::Collection;
|
use crate::collection::Collection;
|
||||||
use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow};
|
use crate::modules::launcher::item::{ButtonConfig, LauncherItem, LauncherWindow};
|
||||||
|
use crate::modules::launcher::open_state::OpenState;
|
||||||
use crate::modules::launcher::popup::Popup;
|
use crate::modules::launcher::popup::Popup;
|
||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use crate::sway::node::get_open_windows;
|
use crate::sway::{get_client, SwayNode};
|
||||||
use crate::sway::{SwayNode, WindowEvent};
|
use color_eyre::{Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{IconTheme, Orientation};
|
use gtk::{IconTheme, Orientation};
|
||||||
use ksway::{Client, IpcEvent};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct LauncherModule {
|
pub struct LauncherModule {
|
||||||
|
/// List of app IDs (or classes) to always show regardles of open state,
|
||||||
|
/// in the order specified.
|
||||||
favorites: Option<Vec<String>>,
|
favorites: Option<Vec<String>>,
|
||||||
|
/// Whether to show application names on the bar.
|
||||||
#[serde(default = "crate::config::default_false")]
|
#[serde(default = "crate::config::default_false")]
|
||||||
show_names: bool,
|
show_names: bool,
|
||||||
|
/// Whether to show application icons on the bar.
|
||||||
#[serde(default = "crate::config::default_true")]
|
#[serde(default = "crate::config::default_true")]
|
||||||
show_icons: bool,
|
show_icons: bool,
|
||||||
|
|
||||||
|
/// Name of the GTK icon theme to use.
|
||||||
icon_theme: Option<String>,
|
icon_theme: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,29 +75,37 @@ impl Launcher {
|
|||||||
/// Adds a new window to the launcher.
|
/// Adds a new window to the launcher.
|
||||||
/// This gets added to an existing group
|
/// This gets added to an existing group
|
||||||
/// if an instance of the program is already open.
|
/// if an instance of the program is already open.
|
||||||
fn add_window(&mut self, window: SwayNode) {
|
fn add_window(&mut self, node: SwayNode) {
|
||||||
let id = window.get_id().to_string();
|
let id = node.get_id().to_string();
|
||||||
|
|
||||||
|
debug!("Adding window with ID {}", id);
|
||||||
|
|
||||||
if let Some(item) = self.items.get_mut(&id) {
|
if let Some(item) = self.items.get_mut(&id) {
|
||||||
let mut state = item.state.write().unwrap();
|
let mut state = item
|
||||||
state.open = true;
|
.state
|
||||||
state.focused = window.focused || state.focused;
|
.write()
|
||||||
state.urgent = window.urgent || state.urgent;
|
.expect("Failed to get write lock on state");
|
||||||
state.is_xwayland = window.is_xwayland();
|
let new_open_state = OpenState::from_node(&node);
|
||||||
|
state.open_state = OpenState::merge_states(vec![&state.open_state, &new_open_state]);
|
||||||
|
state.is_xwayland = node.is_xwayland();
|
||||||
|
|
||||||
item.update_button_classes(&state);
|
item.update_button_classes(&state);
|
||||||
|
|
||||||
let mut windows = item.windows.lock().unwrap();
|
let mut windows = item
|
||||||
|
.windows
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on windows");
|
||||||
|
|
||||||
windows.insert(
|
windows.insert(
|
||||||
window.id,
|
node.id,
|
||||||
LauncherWindow {
|
LauncherWindow {
|
||||||
con_id: window.id,
|
con_id: node.id,
|
||||||
name: window.name,
|
name: node.name,
|
||||||
|
open_state: new_open_state,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let item = LauncherItem::from_node(&window, &self.button_config);
|
let item = LauncherItem::from_node(&node, &self.button_config);
|
||||||
|
|
||||||
self.container.add(&item.button);
|
self.container.add(&item.button);
|
||||||
self.items.insert(id, item);
|
self.items.insert(id, item);
|
||||||
@@ -103,17 +118,21 @@ impl Launcher {
|
|||||||
fn remove_window(&mut self, window: &SwayNode) {
|
fn remove_window(&mut self, window: &SwayNode) {
|
||||||
let id = window.get_id().to_string();
|
let id = window.get_id().to_string();
|
||||||
|
|
||||||
|
debug!("Removing window with ID {}", id);
|
||||||
|
|
||||||
let item = self.items.get_mut(&id);
|
let item = self.items.get_mut(&id);
|
||||||
|
|
||||||
let remove = if let Some(item) = item {
|
let remove = if let Some(item) = item {
|
||||||
let windows = Rc::clone(&item.windows);
|
let windows = Rc::clone(&item.windows);
|
||||||
let mut windows = windows.lock().unwrap();
|
let mut windows = windows
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on windows");
|
||||||
|
|
||||||
windows.remove(&window.id);
|
windows.remove(&window.id);
|
||||||
|
|
||||||
if windows.is_empty() {
|
if windows.is_empty() {
|
||||||
let mut state = item.state.write().unwrap();
|
let mut state = item.state.write().expect("Failed to get lock on windows");
|
||||||
state.open = false;
|
state.open_state = OpenState::Closed;
|
||||||
item.update_button_classes(&state);
|
item.update_button_classes(&state);
|
||||||
|
|
||||||
if item.favorite {
|
if item.favorite {
|
||||||
@@ -134,63 +153,100 @@ impl Launcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_window_focused(&mut self, window: &SwayNode) {
|
/// Unfocuses the currently focused window
|
||||||
let id = window.get_id().to_string();
|
/// and focuses the newly focused one.
|
||||||
|
fn set_window_focused(&mut self, node: &SwayNode) {
|
||||||
|
let id = node.get_id().to_string();
|
||||||
|
|
||||||
let currently_focused = self
|
debug!("Setting window with ID {} focused", id);
|
||||||
.items
|
|
||||||
.iter_mut()
|
let prev_focused = self.items.iter_mut().find(|item| {
|
||||||
.find(|item| item.state.read().unwrap().focused);
|
item.state
|
||||||
if let Some(currently_focused) = currently_focused {
|
.read()
|
||||||
let mut state = currently_focused.state.write().unwrap();
|
.expect("Failed to get read lock on state")
|
||||||
state.focused = false;
|
.open_state
|
||||||
currently_focused.update_button_classes(&state);
|
.is_focused()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(prev_focused) = prev_focused {
|
||||||
|
let mut state = prev_focused
|
||||||
|
.state
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on state");
|
||||||
|
|
||||||
|
// if a window from the same item took focus,
|
||||||
|
// we don't need to unfocus the item.
|
||||||
|
if prev_focused.app_id != id {
|
||||||
|
prev_focused.set_open_state(OpenState::open(), &mut state);
|
||||||
|
prev_focused.update_button_classes(&state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let item = self.items.get_mut(&id);
|
let item = self.items.get_mut(&id);
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
let mut state = item.state.write().unwrap();
|
let mut state = item
|
||||||
state.focused = true;
|
.state
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on state");
|
||||||
|
item.set_window_open_state(node.id, OpenState::focused(), &mut state);
|
||||||
item.update_button_classes(&state);
|
item.update_button_classes(&state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Updates the window title for the given node.
|
||||||
fn set_window_title(&mut self, window: SwayNode) {
|
fn set_window_title(&mut self, window: SwayNode) {
|
||||||
let id = window.get_id().to_string();
|
let id = window.get_id().to_string();
|
||||||
let item = self.items.get_mut(&id);
|
let item = self.items.get_mut(&id);
|
||||||
|
|
||||||
|
debug!("Updating title for window with ID {}", id);
|
||||||
|
|
||||||
if let (Some(item), Some(name)) = (item, window.name) {
|
if let (Some(item), Some(name)) = (item, window.name) {
|
||||||
let mut windows = item.windows.lock().unwrap();
|
let mut windows = item
|
||||||
|
.windows
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on windows");
|
||||||
if windows.len() == 1 {
|
if windows.len() == 1 {
|
||||||
item.set_title(&name, &self.button_config);
|
item.set_title(&name, &self.button_config);
|
||||||
|
} else if let Some(window) = windows.get_mut(&window.id) {
|
||||||
|
window.name = Some(name);
|
||||||
} else {
|
} else {
|
||||||
windows.get_mut(&window.id).unwrap().name = Some(name);
|
// This should never happen
|
||||||
|
// But makes more sense to wipe title than keep old one in case of error
|
||||||
|
item.set_title("", &self.button_config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_window_urgent(&mut self, window: &SwayNode) {
|
/// Updates the window urgency based on the given node.
|
||||||
let id = window.get_id().to_string();
|
fn set_window_urgent(&mut self, node: &SwayNode) {
|
||||||
|
let id = node.get_id().to_string();
|
||||||
let item = self.items.get_mut(&id);
|
let item = self.items.get_mut(&id);
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
"Setting urgency to {} for window with ID {}",
|
||||||
|
node.urgent, id
|
||||||
|
);
|
||||||
|
|
||||||
if let Some(item) = item {
|
if let Some(item) = item {
|
||||||
let mut state = item.state.write().unwrap();
|
let mut state = item
|
||||||
state.urgent = window.urgent;
|
.state
|
||||||
|
.write()
|
||||||
|
.expect("Failed to get write lock on state");
|
||||||
|
|
||||||
|
item.set_window_open_state(node.id, OpenState::urgent(node.urgent), &mut state);
|
||||||
item.update_button_classes(&state);
|
item.update_button_classes(&state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module<gtk::Box> for LauncherModule {
|
impl Module<gtk::Box> for LauncherModule {
|
||||||
fn into_widget(self, info: &ModuleInfo) -> gtk::Box {
|
fn into_widget(self, info: &ModuleInfo) -> Result<gtk::Box> {
|
||||||
let icon_theme = IconTheme::new();
|
let icon_theme = IconTheme::new();
|
||||||
|
|
||||||
if let Some(theme) = self.icon_theme {
|
if let Some(theme) = self.icon_theme {
|
||||||
icon_theme.set_custom_theme(Some(&theme));
|
icon_theme.set_custom_theme(Some(&theme));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut sway = Client::connect().unwrap();
|
|
||||||
|
|
||||||
let popup = Popup::new(
|
let popup = Popup::new(
|
||||||
"popup-launcher",
|
"popup-launcher",
|
||||||
info.app,
|
info.app,
|
||||||
@@ -216,22 +272,29 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
button_config,
|
button_config,
|
||||||
);
|
);
|
||||||
|
|
||||||
let open_windows = get_open_windows(&mut sway);
|
let open_windows = {
|
||||||
|
let sway = get_client();
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
sway.get_open_windows()
|
||||||
|
}?;
|
||||||
|
|
||||||
for window in open_windows {
|
for window in open_windows {
|
||||||
launcher.add_window(window);
|
launcher.add_window(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
let srx = sway.subscribe(vec![IpcEvent::Window]).unwrap();
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn_blocking(move || loop {
|
spawn_blocking(move || {
|
||||||
while let Ok((_, payload)) = srx.try_recv() {
|
let srx = {
|
||||||
let payload: WindowEvent = serde_json::from_slice(&payload).unwrap();
|
let sway = get_client();
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
sway.subscribe_window()
|
||||||
|
};
|
||||||
|
|
||||||
tx.send(payload).unwrap();
|
while let Ok(payload) = srx.recv() {
|
||||||
|
tx.send(payload)
|
||||||
|
.expect("Failed to send window event payload");
|
||||||
}
|
}
|
||||||
sway.poll().unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -250,18 +313,21 @@ impl Module<gtk::Box> for LauncherModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut sway = Client::connect().unwrap();
|
let sway = get_client();
|
||||||
|
|
||||||
while let Some(event) = ui_rx.recv().await {
|
while let Some(event) = ui_rx.recv().await {
|
||||||
let selector = match event {
|
let selector = match event {
|
||||||
FocusEvent::AppId(app_id) => format!("[app_id={}]", app_id),
|
FocusEvent::AppId(app_id) => format!("[app_id={}]", app_id),
|
||||||
FocusEvent::Class(class) => format!("[class={}]", class),
|
FocusEvent::Class(class) => format!("[class={}]", class),
|
||||||
FocusEvent::ConId(id) => format!("[con_id={}]", id),
|
FocusEvent::ConId(id) => format!("[con_id={}]", id),
|
||||||
};
|
};
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
sway.run(format!("{} focus", selector)).unwrap();
|
sway.run(format!("{} focus", selector))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok::<(), Report>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
container
|
Ok(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
74
src/modules/launcher/open_state.rs
Normal file
74
src/modules/launcher/open_state.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use crate::sway::SwayNode;
|
||||||
|
|
||||||
|
/// Open state for a launcher item, or item window.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
|
||||||
|
pub enum OpenState {
|
||||||
|
Closed,
|
||||||
|
Open { focused: bool, urgent: bool },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpenState {
|
||||||
|
/// Creates from `SwayNode`
|
||||||
|
pub const fn from_node(node: &SwayNode) -> Self {
|
||||||
|
Self::Open {
|
||||||
|
focused: node.focused,
|
||||||
|
urgent: node.urgent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates open without focused/urgent
|
||||||
|
pub const fn open() -> Self {
|
||||||
|
Self::Open {
|
||||||
|
focused: false,
|
||||||
|
urgent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates open with focused
|
||||||
|
pub const fn focused() -> Self {
|
||||||
|
Self::Open {
|
||||||
|
focused: true,
|
||||||
|
urgent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates open with urgent
|
||||||
|
pub const fn urgent(urgent: bool) -> Self {
|
||||||
|
Self::Open {
|
||||||
|
focused: false,
|
||||||
|
urgent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if open
|
||||||
|
pub fn is_open(self) -> bool {
|
||||||
|
self != Self::Closed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if open with focus
|
||||||
|
pub const fn is_focused(self) -> bool {
|
||||||
|
matches!(self, Self::Open { focused: true, .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// check if open with urgent
|
||||||
|
pub const fn is_urgent(self) -> bool {
|
||||||
|
matches!(self, Self::Open { urgent: true, .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Merges states together to produce a single state.
|
||||||
|
/// This is effectively an OR operation,
|
||||||
|
/// so sets state to open and flags to true if any state is open
|
||||||
|
/// or any instance of the flag is true.
|
||||||
|
pub fn merge_states(states: Vec<&Self>) -> Self {
|
||||||
|
states.iter().fold(Self::Closed, |merged, current| {
|
||||||
|
if merged.is_open() || current.is_open() {
|
||||||
|
Self::Open {
|
||||||
|
focused: merged.is_focused() || current.is_focused(),
|
||||||
|
urgent: merged.is_urgent() || current.is_urgent(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self::Closed
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,8 @@ impl Popup {
|
|||||||
let window = self.window.clone();
|
let window = self.window.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
button.connect_clicked(move |_| {
|
button.connect_clicked(move |_| {
|
||||||
tx.try_send(FocusEvent::ConId(con_id)).unwrap();
|
tx.try_send(FocusEvent::ConId(con_id))
|
||||||
|
.expect("Failed to send focus event");
|
||||||
window.hide();
|
window.hide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ pub mod tray;
|
|||||||
pub mod workspaces;
|
pub mod workspaces;
|
||||||
|
|
||||||
use crate::config::BarPosition;
|
use crate::config::BarPosition;
|
||||||
|
use color_eyre::Result;
|
||||||
/// Shamelessly stolen from here:
|
/// Shamelessly stolen from here:
|
||||||
/// <https://github.com/zeroeightysix/rustbar/blob/master/src/modules/module.rs>
|
/// <https://github.com/zeroeightysix/rustbar/blob/master/src/modules/module.rs>
|
||||||
use glib::IsA;
|
use glib::IsA;
|
||||||
use gtk::gdk::Monitor;
|
use gtk::gdk::Monitor;
|
||||||
use gtk::{Application, Widget};
|
use gtk::{Application, Widget};
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serde_json::Value;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum ModuleLocation {
|
pub enum ModuleLocation {
|
||||||
@@ -43,12 +42,5 @@ where
|
|||||||
{
|
{
|
||||||
/// Consumes the module config
|
/// Consumes the module config
|
||||||
/// and produces a GTK widget of type `W`
|
/// and produces a GTK widget of type `W`
|
||||||
fn into_widget(self, info: &ModuleInfo) -> W;
|
fn into_widget(self, info: &ModuleInfo) -> Result<W>;
|
||||||
|
|
||||||
fn from_value(v: &Value) -> Box<Self>
|
|
||||||
where
|
|
||||||
Self: DeserializeOwned,
|
|
||||||
{
|
|
||||||
serde_json::from_value(v.clone()).unwrap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,90 @@
|
|||||||
use mpd_client::commands::responses::Status;
|
use lazy_static::lazy_static;
|
||||||
use mpd_client::raw::MpdProtocolError;
|
use mpd_client::client::Connection;
|
||||||
use mpd_client::{Client, Connection};
|
use mpd_client::protocol::MpdProtocolError;
|
||||||
|
use mpd_client::responses::Status;
|
||||||
|
use mpd_client::Client;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::net::{TcpStream, UnixStream};
|
use tokio::net::{TcpStream, UnixStream};
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
fn is_unix_socket(host: &String) -> bool {
|
lazy_static! {
|
||||||
PathBuf::from(host).is_file()
|
static ref CLIENTS: Arc<Mutex<HashMap<String, Arc<Client>>>> =
|
||||||
|
Arc::new(Mutex::new(HashMap::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_connection(host: &String) -> Result<Connection, MpdProtocolError> {
|
pub async fn get_connection(host: &str) -> Option<Arc<Client>> {
|
||||||
if is_unix_socket(host) {
|
let mut clients = CLIENTS.lock().await;
|
||||||
connect_unix(host).await
|
|
||||||
} else {
|
match clients.get(host) {
|
||||||
connect_tcp(host).await
|
Some(client) => Some(Arc::clone(client)),
|
||||||
|
None => {
|
||||||
|
let client = wait_for_connection(host, Duration::from_secs(5), None).await?;
|
||||||
|
let client = Arc::new(client);
|
||||||
|
clients.insert(host.to_string(), Arc::clone(&client));
|
||||||
|
Some(client)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_unix(host: &String) -> Result<Connection, MpdProtocolError> {
|
async fn wait_for_connection(
|
||||||
let connection = UnixStream::connect(host)
|
host: &str,
|
||||||
.await
|
interval: Duration,
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to unix socket: {}", host));
|
max_retries: Option<usize>,
|
||||||
|
) -> Option<Client> {
|
||||||
|
let mut retries = 0;
|
||||||
|
let max_retries = max_retries.unwrap_or(usize::MAX);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if retries == max_retries {
|
||||||
|
break None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(conn) = try_get_mpd_conn(host).await {
|
||||||
|
break Some(conn.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
retries += 1;
|
||||||
|
sleep(interval).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cycles through each MPD host and
|
||||||
|
/// returns the first one which connects,
|
||||||
|
/// or none if there are none
|
||||||
|
async fn try_get_mpd_conn(host: &str) -> Option<Connection> {
|
||||||
|
let connection = if is_unix_socket(host) {
|
||||||
|
connect_unix(host).await
|
||||||
|
} else {
|
||||||
|
connect_tcp(host).await
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_unix_socket(host: &str) -> bool {
|
||||||
|
PathBuf::from(host).is_file()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn connect_unix(host: &str) -> Result<Connection, MpdProtocolError> {
|
||||||
|
let connection = UnixStream::connect(host).await?;
|
||||||
Client::connect(connection).await
|
Client::connect(connection).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn connect_tcp(host: &String) -> Result<Connection, MpdProtocolError> {
|
async fn connect_tcp(host: &str) -> Result<Connection, MpdProtocolError> {
|
||||||
let connection = TcpStream::connect(host)
|
let connection = TcpStream::connect(host).await?;
|
||||||
.await
|
|
||||||
.unwrap_or_else(|_| panic!("Error connecting to unix socket: {}", host));
|
|
||||||
|
|
||||||
Client::connect(connection).await
|
Client::connect(connection).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// Gets MPD server status.
|
|
||||||
// /// Panics on error.
|
|
||||||
// pub async fn get_status(client: &Client) -> Status {
|
|
||||||
// client
|
|
||||||
// .command(commands::Status)
|
|
||||||
// .await
|
|
||||||
// .expect("Failed to get MPD server status")
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// Gets the duration of the current song
|
/// Gets the duration of the current song
|
||||||
pub fn get_duration(status: &Status) -> u64 {
|
pub fn get_duration(status: &Status) -> Option<u64> {
|
||||||
status
|
status.duration.map(|duration| duration.as_secs())
|
||||||
.duration
|
|
||||||
.expect("Failed to get duration from MPD status")
|
|
||||||
.as_secs()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the elapsed time of the current song
|
/// Gets the elapsed time of the current song
|
||||||
pub fn get_elapsed(status: &Status) -> u64 {
|
pub fn get_elapsed(status: &Status) -> Option<u64> {
|
||||||
status
|
status.elapsed.map(|duration| duration.as_secs())
|
||||||
.elapsed
|
|
||||||
.expect("Failed to get elapsed time from MPD status")
|
|
||||||
.as_secs()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,30 +5,39 @@ use self::popup::Popup;
|
|||||||
use crate::modules::mpd::client::{get_connection, get_duration, get_elapsed};
|
use crate::modules::mpd::client::{get_connection, get_duration, get_elapsed};
|
||||||
use crate::modules::mpd::popup::{MpdPopup, PopupEvent};
|
use crate::modules::mpd::popup::{MpdPopup, PopupEvent};
|
||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use dirs::home_dir;
|
use color_eyre::Result;
|
||||||
|
use dirs::{audio_dir, home_dir};
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Orientation};
|
use gtk::{Button, Orientation};
|
||||||
use mpd_client::commands::responses::{PlayState, Song, Status};
|
use mpd_client::commands;
|
||||||
use mpd_client::{commands, Tag};
|
use mpd_client::responses::{PlayState, Song, Status};
|
||||||
|
use mpd_client::tag::Tag;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct MpdModule {
|
pub struct MpdModule {
|
||||||
|
/// TCP or Unix socket address.
|
||||||
#[serde(default = "default_socket")]
|
#[serde(default = "default_socket")]
|
||||||
host: String,
|
host: String,
|
||||||
|
/// Format of current song info to display on the bar.
|
||||||
#[serde(default = "default_format")]
|
#[serde(default = "default_format")]
|
||||||
format: String,
|
format: String,
|
||||||
|
/// Icon to display when playing.
|
||||||
#[serde(default = "default_icon_play")]
|
#[serde(default = "default_icon_play")]
|
||||||
icon_play: Option<String>,
|
icon_play: Option<String>,
|
||||||
|
/// Icon to display when paused.
|
||||||
#[serde(default = "default_icon_pause")]
|
#[serde(default = "default_icon_pause")]
|
||||||
icon_pause: Option<String>,
|
icon_pause: Option<String>,
|
||||||
|
|
||||||
|
/// Path to root of music directory.
|
||||||
#[serde(default = "default_music_dir")]
|
#[serde(default = "default_music_dir")]
|
||||||
music_dir: PathBuf,
|
music_dir: PathBuf,
|
||||||
}
|
}
|
||||||
@@ -41,16 +50,18 @@ fn default_format() -> String {
|
|||||||
String::from("{icon} {title} / {artist}")
|
String::from("{icon} {title} / {artist}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn default_icon_play() -> Option<String> {
|
fn default_icon_play() -> Option<String> {
|
||||||
Some(String::from(""))
|
Some(String::from(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
fn default_icon_pause() -> Option<String> {
|
fn default_icon_pause() -> Option<String> {
|
||||||
Some(String::from(""))
|
Some(String::from(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_music_dir() -> PathBuf {
|
fn default_music_dir() -> PathBuf {
|
||||||
home_dir().unwrap().join("Music")
|
audio_dir().unwrap_or_else(|| home_dir().map(|dir| dir.join("Music")).unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to read the first value for a tag
|
/// Attempts to read the first value for a tag
|
||||||
@@ -84,8 +95,8 @@ enum Event {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<Button> for MpdModule {
|
impl Module<Button> for MpdModule {
|
||||||
fn into_widget(self, info: &ModuleInfo) -> Button {
|
fn into_widget(self, info: &ModuleInfo) -> Result<Button> {
|
||||||
let re = Regex::new(r"\{([\w-]+)}").unwrap();
|
let re = Regex::new(r"\{([\w-]+)}")?;
|
||||||
let tokens = get_tokens(&re, self.format.as_str());
|
let tokens = get_tokens(&re, self.format.as_str());
|
||||||
|
|
||||||
let button = Button::new();
|
let button = Button::new();
|
||||||
@@ -107,13 +118,17 @@ impl Module<Button> for MpdModule {
|
|||||||
let music_dir = self.music_dir.clone();
|
let music_dir = self.music_dir.clone();
|
||||||
|
|
||||||
button.connect_clicked(move |_| {
|
button.connect_clicked(move |_| {
|
||||||
click_tx.send(Event::Open).unwrap();
|
click_tx
|
||||||
|
.send(Event::Open)
|
||||||
|
.expect("Failed to send popup open event");
|
||||||
});
|
});
|
||||||
|
|
||||||
let host = self.host.clone();
|
let host = self.host.clone();
|
||||||
let host2 = self.host.clone();
|
let host2 = self.host.clone();
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let (client, _) = get_connection(&host).await.unwrap(); // TODO: Handle connecting properly
|
let client = get_connection(&host)
|
||||||
|
.await
|
||||||
|
.expect("Unexpected error when trying to connect to MPD server");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let current_song = client.command(commands::CurrentSong).await;
|
let current_song = client.command(commands::CurrentSong).await;
|
||||||
@@ -125,32 +140,38 @@ impl Module<Button> for MpdModule {
|
|||||||
.await;
|
.await;
|
||||||
|
|
||||||
tx.send(Event::Update(Box::new(Some((song.song, status, string)))))
|
tx.send(Event::Update(Box::new(Some((song.song, status, string)))))
|
||||||
.unwrap();
|
.expect("Failed to send update event");
|
||||||
} else {
|
} else {
|
||||||
tx.send(Event::Update(Box::new(None))).unwrap();
|
tx.send(Event::Update(Box::new(None)))
|
||||||
|
.expect("Failed to send update event");
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(tokio::time::Duration::from_secs(1)).await;
|
sleep(Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let (client, _) = get_connection(&host2).await.unwrap(); // TODO: Handle connecting properly
|
let client = get_connection(&host2)
|
||||||
|
.await
|
||||||
|
.expect("Unexpected error when trying to connect to MPD server");
|
||||||
|
|
||||||
while let Some(event) = ui_rx.recv().await {
|
while let Some(event) = ui_rx.recv().await {
|
||||||
match event {
|
let res = match event {
|
||||||
PopupEvent::Previous => client.command(commands::Previous).await,
|
PopupEvent::Previous => client.command(commands::Previous).await,
|
||||||
PopupEvent::Toggle => {
|
PopupEvent::Toggle => match client.command(commands::Status).await {
|
||||||
let status = client.command(commands::Status).await.unwrap();
|
Ok(status) => match status.state {
|
||||||
match status.state {
|
|
||||||
PlayState::Playing => client.command(commands::SetPause(true)).await,
|
PlayState::Playing => client.command(commands::SetPause(true)).await,
|
||||||
PlayState::Paused => client.command(commands::SetPause(false)).await,
|
PlayState::Paused => client.command(commands::SetPause(false)).await,
|
||||||
PlayState::Stopped => Ok(()),
|
PlayState::Stopped => Ok(()),
|
||||||
}
|
},
|
||||||
}
|
Err(err) => Err(err),
|
||||||
|
},
|
||||||
PopupEvent::Next => client.command(commands::Next).await,
|
PopupEvent::Next => client.command(commands::Next).await,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = res {
|
||||||
|
error!("Failed to send command to MPD server: {:?}", err);
|
||||||
}
|
}
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -178,7 +199,7 @@ impl Module<Button> for MpdModule {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
button
|
Ok(button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,9 +241,10 @@ impl MpdModule {
|
|||||||
"disc" => try_get_first_tag(song.tags.get(&Tag::Disc)),
|
"disc" => try_get_first_tag(song.tags.get(&Tag::Disc)),
|
||||||
"genre" => try_get_first_tag(song.tags.get(&Tag::Genre)),
|
"genre" => try_get_first_tag(song.tags.get(&Tag::Genre)),
|
||||||
"track" => try_get_first_tag(song.tags.get(&Tag::Track)),
|
"track" => try_get_first_tag(song.tags.get(&Tag::Track)),
|
||||||
"duration" => return format_time(get_duration(status)),
|
"duration" => return get_duration(status).map(format_time).unwrap_or_default(),
|
||||||
"elapsed" => return format_time(get_elapsed(status)),
|
|
||||||
_ => return token.to_string(),
|
"elapsed" => return get_elapsed(status).map(format_time).unwrap_or_default(),
|
||||||
|
_ => Some(token),
|
||||||
};
|
};
|
||||||
s.unwrap_or_default().to_string()
|
s.unwrap_or_default().to_string()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ pub use crate::popup::Popup;
|
|||||||
use gtk::gdk_pixbuf::Pixbuf;
|
use gtk::gdk_pixbuf::Pixbuf;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Image, Label, Orientation};
|
use gtk::{Button, Image, Label, Orientation};
|
||||||
use mpd_client::commands::responses::{PlayState, Song, Status};
|
use mpd_client::responses::{PlayState, Song, Status};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@@ -90,17 +90,23 @@ impl MpdPopup {
|
|||||||
|
|
||||||
let tx_prev = tx.clone();
|
let tx_prev = tx.clone();
|
||||||
btn_prev.connect_clicked(move |_| {
|
btn_prev.connect_clicked(move |_| {
|
||||||
tx_prev.try_send(PopupEvent::Previous).unwrap();
|
tx_prev
|
||||||
|
.try_send(PopupEvent::Previous)
|
||||||
|
.expect("Failed to send prev track message");
|
||||||
});
|
});
|
||||||
|
|
||||||
let tx_toggle = tx.clone();
|
let tx_toggle = tx.clone();
|
||||||
btn_play_pause.connect_clicked(move |_| {
|
btn_play_pause.connect_clicked(move |_| {
|
||||||
tx_toggle.try_send(PopupEvent::Toggle).unwrap();
|
tx_toggle
|
||||||
|
.try_send(PopupEvent::Toggle)
|
||||||
|
.expect("Failed to send play/pause track message");
|
||||||
});
|
});
|
||||||
|
|
||||||
let tx_next = tx;
|
let tx_next = tx;
|
||||||
btn_next.connect_clicked(move |_| {
|
btn_next.connect_clicked(move |_| {
|
||||||
tx_next.try_send(PopupEvent::Next).unwrap();
|
tx_next
|
||||||
|
.try_send(PopupEvent::Next)
|
||||||
|
.expect("Failed to send next track message");
|
||||||
});
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -121,7 +127,12 @@ impl MpdPopup {
|
|||||||
|
|
||||||
// only update art when album changes
|
// only update art when album changes
|
||||||
if prev_album != curr_album {
|
if prev_album != curr_album {
|
||||||
let cover_path = path.join(song.file_path().parent().unwrap().join("cover.jpg"));
|
let cover_path = path.join(
|
||||||
|
song.file_path()
|
||||||
|
.parent()
|
||||||
|
.expect("Song path should not be root")
|
||||||
|
.join("cover.jpg"),
|
||||||
|
);
|
||||||
|
|
||||||
if let Ok(pixbuf) = Pixbuf::from_file_at_scale(cover_path, 128, 128, true) {
|
if let Ok(pixbuf) = Pixbuf::from_file_at_scale(cover_path, 128, 128, true) {
|
||||||
self.cover.set_from_pixbuf(Some(&pixbuf));
|
self.cover.set_from_pixbuf(Some(&pixbuf));
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
|
use color_eyre::{eyre::Report, eyre::Result, eyre::WrapErr, Section};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::Label;
|
use gtk::Label;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct ScriptModule {
|
pub struct ScriptModule {
|
||||||
|
/// Path to script to execute.
|
||||||
path: String,
|
path: String,
|
||||||
|
/// Time in milliseconds between executions.
|
||||||
#[serde(default = "default_interval")]
|
#[serde(default = "default_interval")]
|
||||||
interval: u64,
|
interval: u64,
|
||||||
}
|
}
|
||||||
@@ -19,19 +23,15 @@ const fn default_interval() -> u64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<Label> for ScriptModule {
|
impl Module<Label> for ScriptModule {
|
||||||
fn into_widget(self, _info: &ModuleInfo) -> Label {
|
fn into_widget(self, _info: &ModuleInfo) -> Result<Label> {
|
||||||
let label = Label::builder().use_markup(true).build();
|
let label = Label::builder().use_markup(true).build();
|
||||||
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
loop {
|
loop {
|
||||||
let output = Command::new("sh").arg("-c").arg(&self.path).output();
|
match self.run_script() {
|
||||||
if let Ok(output) = output {
|
Ok(stdout) => tx.send(stdout).expect("Failed to send stdout"),
|
||||||
let stdout = String::from_utf8(output.stdout)
|
Err(err) => error!("{:?}", err),
|
||||||
.map(|output| output.trim().to_string())
|
|
||||||
.expect("Script output not valid UTF-8");
|
|
||||||
|
|
||||||
tx.send(stdout).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sleep(tokio::time::Duration::from_millis(self.interval)).await;
|
sleep(tokio::time::Duration::from_millis(self.interval)).await;
|
||||||
@@ -46,6 +46,34 @@ impl Module<Label> for ScriptModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
label
|
Ok(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScriptModule {
|
||||||
|
#[instrument]
|
||||||
|
fn run_script(&self) -> Result<String> {
|
||||||
|
let output = Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(&self.path)
|
||||||
|
.output()
|
||||||
|
.wrap_err("Failed to get script output")?;
|
||||||
|
|
||||||
|
if output.status.success() {
|
||||||
|
let stdout = String::from_utf8(output.stdout)
|
||||||
|
.map(|output| output.trim().to_string())
|
||||||
|
.wrap_err("Script stdout not valid UTF-8")?;
|
||||||
|
|
||||||
|
Ok(stdout)
|
||||||
|
} else {
|
||||||
|
let stderr = String::from_utf8(output.stderr)
|
||||||
|
.map(|output| output.trim().to_string())
|
||||||
|
.wrap_err("Script stderr not valid UTF-8")?;
|
||||||
|
|
||||||
|
Err(Report::msg(stderr)
|
||||||
|
.wrap_err("Script returned non-zero error code")
|
||||||
|
.suggestion("Check the path to your script")
|
||||||
|
.suggestion("Check the script for errors"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
|
use color_eyre::Result;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Label, Orientation};
|
use gtk::{Label, Orientation};
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
@@ -10,12 +11,13 @@ use tokio::time::sleep;
|
|||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct SysInfoModule {
|
pub struct SysInfoModule {
|
||||||
|
/// List of formatting strings.
|
||||||
format: Vec<String>,
|
format: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module<gtk::Box> for SysInfoModule {
|
impl Module<gtk::Box> for SysInfoModule {
|
||||||
fn into_widget(self, _info: &ModuleInfo) -> gtk::Box {
|
fn into_widget(self, _info: &ModuleInfo) -> Result<gtk::Box> {
|
||||||
let re = Regex::new(r"\{([\w-]+)}").unwrap();
|
let re = Regex::new(r"\{([\w-]+)}")?;
|
||||||
|
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 10);
|
let container = gtk::Box::new(Orientation::Horizontal, 10);
|
||||||
|
|
||||||
@@ -46,7 +48,8 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||||||
format_info.insert("memory-percent", format!("{:0>2.0}", memory_percent));
|
format_info.insert("memory-percent", format!("{:0>2.0}", memory_percent));
|
||||||
format_info.insert("cpu-percent", format!("{:0>2.0}", cpu_percent));
|
format_info.insert("cpu-percent", format!("{:0>2.0}", cpu_percent));
|
||||||
|
|
||||||
tx.send(format_info).unwrap();
|
tx.send(format_info)
|
||||||
|
.expect("Failed to send system info map");
|
||||||
|
|
||||||
sleep(tokio::time::Duration::from_secs(1)).await;
|
sleep(tokio::time::Duration::from_secs(1)).await;
|
||||||
}
|
}
|
||||||
@@ -69,6 +72,6 @@ impl Module<gtk::Box> for SysInfoModule {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
container
|
Ok(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
|
use color_eyre::Result;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{IconLookupFlags, IconTheme, Image, Menu, MenuBar, MenuItem, SeparatorMenuItem};
|
use gtk::{IconLookupFlags, IconTheme, Image, Menu, MenuBar, MenuItem, SeparatorMenuItem};
|
||||||
@@ -26,10 +27,11 @@ fn get_icon(item: &StatusNotifierItem) -> Option<Image> {
|
|||||||
item.icon_theme_path.as_ref().and_then(|path| {
|
item.icon_theme_path.as_ref().and_then(|path| {
|
||||||
let theme = IconTheme::new();
|
let theme = IconTheme::new();
|
||||||
theme.append_search_path(&path);
|
theme.append_search_path(&path);
|
||||||
let icon_name = item.icon_name.as_ref().unwrap();
|
|
||||||
let icon_info = theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
|
|
||||||
|
|
||||||
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
|
item.icon_name.as_ref().and_then(|icon_name| {
|
||||||
|
let icon_info = theme.lookup_icon(icon_name, 16, IconLookupFlags::empty());
|
||||||
|
icon_info.map(|icon_info| Image::from_pixbuf(icon_info.load_icon().ok().as_ref()))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +40,8 @@ fn get_icon(item: &StatusNotifierItem) -> Option<Image> {
|
|||||||
fn get_menu_items(
|
fn get_menu_items(
|
||||||
menu: &[MenuItemInfo],
|
menu: &[MenuItemInfo],
|
||||||
tx: &mpsc::Sender<NotifierItemCommand>,
|
tx: &mpsc::Sender<NotifierItemCommand>,
|
||||||
id: String,
|
id: &str,
|
||||||
path: String,
|
path: &str,
|
||||||
) -> Vec<MenuItem> {
|
) -> Vec<MenuItem> {
|
||||||
menu.iter()
|
menu.iter()
|
||||||
.map(|item_info| {
|
.map(|item_info| {
|
||||||
@@ -53,7 +55,7 @@ fn get_menu_items(
|
|||||||
|
|
||||||
if !item_info.submenu.is_empty() {
|
if !item_info.submenu.is_empty() {
|
||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
get_menu_items(&item_info.submenu, &tx.clone(), id.clone(), path.clone())
|
get_menu_items(&item_info.submenu, &tx.clone(), id, path)
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|item| menu.add(item));
|
.for_each(|item| menu.add(item));
|
||||||
|
|
||||||
@@ -63,8 +65,8 @@ fn get_menu_items(
|
|||||||
let item = builder.build();
|
let item = builder.build();
|
||||||
|
|
||||||
let info = item_info.clone();
|
let info = item_info.clone();
|
||||||
let id = id.clone();
|
let id = id.to_string();
|
||||||
let path = path.clone();
|
let path = path.to_string();
|
||||||
|
|
||||||
{
|
{
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
@@ -74,7 +76,7 @@ fn get_menu_items(
|
|||||||
menu_path: path.clone(),
|
menu_path: path.clone(),
|
||||||
notifier_address: id.clone(),
|
notifier_address: id.clone(),
|
||||||
})
|
})
|
||||||
.unwrap();
|
.expect("Failed to send menu item clicked event");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ fn get_menu_items(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<MenuBar> for TrayModule {
|
impl Module<MenuBar> for TrayModule {
|
||||||
fn into_widget(self, _info: &ModuleInfo) -> MenuBar {
|
fn into_widget(self, _info: &ModuleInfo) -> Result<MenuBar> {
|
||||||
let container = MenuBar::new();
|
let container = MenuBar::new();
|
||||||
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
@@ -107,10 +109,11 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
menu,
|
menu,
|
||||||
} => {
|
} => {
|
||||||
tx.send(TrayUpdate::Update(id, Box::new(item), menu))
|
tx.send(TrayUpdate::Update(id, Box::new(item), menu))
|
||||||
.unwrap();
|
.expect("Failed to send tray update event");
|
||||||
}
|
}
|
||||||
NotifierItemMessage::Remove { address: id } => {
|
NotifierItemMessage::Remove { address: id } => {
|
||||||
tx.send(TrayUpdate::Remove(id)).unwrap();
|
tx.send(TrayUpdate::Remove(id))
|
||||||
|
.expect("Failed to send tray remove event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,13 +141,11 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
menu_item
|
menu_item
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(menu_opts) = menu {
|
if let (Some(menu_opts), Some(menu_path)) = (menu, item.menu) {
|
||||||
let menu_path = item.menu.as_ref().unwrap().to_string();
|
|
||||||
|
|
||||||
let submenus = menu_opts.submenus;
|
let submenus = menu_opts.submenus;
|
||||||
if !submenus.is_empty() {
|
if !submenus.is_empty() {
|
||||||
let menu = Menu::new();
|
let menu = Menu::new();
|
||||||
get_menu_items(&submenus, &ui_tx.clone(), id.clone(), menu_path)
|
get_menu_items(&submenus, &ui_tx.clone(), &id, &menu_path)
|
||||||
.iter()
|
.iter()
|
||||||
.for_each(|item| menu.add(item));
|
.for_each(|item| menu.add(item));
|
||||||
menu_item.set_submenu(Some(&menu));
|
menu_item.set_submenu(Some(&menu));
|
||||||
@@ -154,8 +155,9 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
widgets.insert(id, menu_item);
|
widgets.insert(id, menu_item);
|
||||||
}
|
}
|
||||||
TrayUpdate::Remove(id) => {
|
TrayUpdate::Remove(id) => {
|
||||||
let widget = widgets.get(&id).unwrap();
|
if let Some(widget) = widgets.get(&id) {
|
||||||
container.remove(widget);
|
container.remove(widget);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -163,6 +165,6 @@ impl Module<MenuBar> for TrayModule {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
container
|
Ok(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
use crate::modules::{Module, ModuleInfo};
|
use crate::modules::{Module, ModuleInfo};
|
||||||
use crate::sway::{Workspace, WorkspaceEvent};
|
use crate::sway::{get_client, Workspace};
|
||||||
|
use color_eyre::{Report, Result};
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{Button, Orientation};
|
use gtk::{Button, Orientation};
|
||||||
use ksway::client::Client;
|
use ksway::IpcCommand;
|
||||||
use ksway::{IpcCommand, IpcEvent};
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Clone)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct WorkspacesModule {
|
pub struct WorkspacesModule {
|
||||||
|
/// Map of actual workspace names to custom names.
|
||||||
name_map: Option<HashMap<String, String>>,
|
name_map: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
/// Whether to display icons for all monitors.
|
||||||
#[serde(default = "crate::config::default_false")]
|
#[serde(default = "crate::config::default_false")]
|
||||||
all_monitors: bool,
|
all_monitors: bool,
|
||||||
}
|
}
|
||||||
@@ -34,7 +37,10 @@ impl Workspace {
|
|||||||
{
|
{
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let name = self.name.clone();
|
let name = self.name.clone();
|
||||||
button.connect_clicked(move |_item| tx.try_send(name.clone()).unwrap());
|
button.connect_clicked(move |_item| {
|
||||||
|
tx.try_send(name.clone())
|
||||||
|
.expect("Failed to send workspace click event");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
button
|
button
|
||||||
@@ -42,18 +48,20 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Module<gtk::Box> for WorkspacesModule {
|
impl Module<gtk::Box> for WorkspacesModule {
|
||||||
fn into_widget(self, info: &ModuleInfo) -> gtk::Box {
|
fn into_widget(self, info: &ModuleInfo) -> Result<gtk::Box> {
|
||||||
let mut sway = Client::connect().unwrap();
|
|
||||||
|
|
||||||
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
let container = gtk::Box::new(Orientation::Horizontal, 0);
|
||||||
|
|
||||||
let workspaces = {
|
let workspaces = {
|
||||||
let raw = sway.ipc(IpcCommand::GetWorkspaces).unwrap();
|
trace!("Getting current workspaces");
|
||||||
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw).unwrap();
|
let sway = get_client();
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
let raw = sway.ipc(IpcCommand::GetWorkspaces)?;
|
||||||
|
let workspaces = serde_json::from_slice::<Vec<Workspace>>(&raw)?;
|
||||||
|
|
||||||
if self.all_monitors {
|
if self.all_monitors {
|
||||||
workspaces
|
workspaces
|
||||||
} else {
|
} else {
|
||||||
|
trace!("Filtering workspaces to current monitor only");
|
||||||
workspaces
|
workspaces
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|workspace| workspace.output == info.output_name)
|
.filter(|workspace| workspace.output == info.output_name)
|
||||||
@@ -67,53 +75,80 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
|
|
||||||
let (ui_tx, mut ui_rx) = mpsc::channel(32);
|
let (ui_tx, mut ui_rx) = mpsc::channel(32);
|
||||||
|
|
||||||
|
trace!("Creating workspace buttons");
|
||||||
for workspace in workspaces {
|
for workspace in workspaces {
|
||||||
let item = workspace.as_button(&name_map, &ui_tx);
|
let item = workspace.as_button(&name_map, &ui_tx);
|
||||||
container.add(&item);
|
container.add(&item);
|
||||||
button_map.insert(workspace.name, item);
|
button_map.insert(workspace.name, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
let srx = sway.subscribe(vec![IpcEvent::Workspace]).unwrap();
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn_blocking(move || loop {
|
spawn_blocking(move || {
|
||||||
while let Ok((_, payload)) = srx.try_recv() {
|
trace!("Starting workspace event listener task");
|
||||||
let payload: WorkspaceEvent = serde_json::from_slice(&payload).unwrap();
|
let srx = {
|
||||||
tx.send(payload).unwrap();
|
let sway = get_client();
|
||||||
|
let mut sway = sway.lock().expect("Failed to get lock on Sway IPC client");
|
||||||
|
|
||||||
|
sway.subscribe_workspace()
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Ok(payload) = srx.recv() {
|
||||||
|
tx.send(payload).expect("Failed to send workspace event");
|
||||||
}
|
}
|
||||||
sway.poll().unwrap();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
|
trace!("Setting up sway event handler");
|
||||||
let menubar = container.clone();
|
let menubar = container.clone();
|
||||||
let output_name = info.output_name.to_string();
|
let output_name = info.output_name.to_string();
|
||||||
rx.attach(None, move |event| {
|
rx.attach(None, move |event| {
|
||||||
|
debug!("Received workspace event {:?}", event);
|
||||||
match event.change.as_str() {
|
match event.change.as_str() {
|
||||||
"focus" => {
|
"focus" => {
|
||||||
let old = event.old.unwrap();
|
let old = event.old.and_then(|old| button_map.get(&old.name));
|
||||||
if let Some(old_button) = button_map.get(&old.name) {
|
if let Some(old) = old {
|
||||||
old_button.style_context().remove_class("focused");
|
old.style_context().remove_class("focused");
|
||||||
}
|
}
|
||||||
|
|
||||||
let new = event.current.unwrap();
|
let new = event.current.and_then(|new| button_map.get(&new.name));
|
||||||
if let Some(new_button) = button_map.get(&new.name) {
|
if let Some(new) = new {
|
||||||
new_button.style_context().add_class("focused");
|
new.style_context().add_class("focused");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!("{:?} {:?}", old, new);
|
||||||
}
|
}
|
||||||
"init" => {
|
"init" => {
|
||||||
let workspace = event.current.unwrap();
|
if let Some(workspace) = event.current {
|
||||||
if self.all_monitors || workspace.output == output_name {
|
if self.all_monitors || workspace.output == output_name {
|
||||||
let item = workspace.as_button(&name_map, &ui_tx);
|
let item = workspace.as_button(&name_map, &ui_tx);
|
||||||
|
|
||||||
item.show();
|
item.show();
|
||||||
menubar.add(&item);
|
menubar.add(&item);
|
||||||
button_map.insert(workspace.name, item);
|
button_map.insert(workspace.name, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"move" => {
|
||||||
|
if let Some(workspace) = event.current {
|
||||||
|
if !self.all_monitors {
|
||||||
|
if workspace.output == output_name {
|
||||||
|
let item = workspace.as_button(&name_map, &ui_tx);
|
||||||
|
|
||||||
|
item.show();
|
||||||
|
menubar.add(&item);
|
||||||
|
button_map.insert(workspace.name, item);
|
||||||
|
} else if let Some(item) = button_map.get(&workspace.name) {
|
||||||
|
menubar.remove(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"empty" => {
|
"empty" => {
|
||||||
let current = event.current.unwrap();
|
if let Some(workspace) = event.current {
|
||||||
if let Some(item) = button_map.get(¤t.name) {
|
if let Some(item) = button_map.get(&workspace.name) {
|
||||||
menubar.remove(item);
|
menubar.remove(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -124,12 +159,18 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut sway = Client::connect().unwrap();
|
trace!("Setting up UI event handler");
|
||||||
|
let sway = get_client();
|
||||||
while let Some(name) = ui_rx.recv().await {
|
while let Some(name) = ui_rx.recv().await {
|
||||||
sway.run(format!("workspace {}", name)).unwrap();
|
let mut sway = sway
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get write lock on Sway IPC client");
|
||||||
|
sway.run(format!("workspace {}", name))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok::<(), Report>(())
|
||||||
});
|
});
|
||||||
|
|
||||||
container
|
Ok(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ pub struct Popup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Popup {
|
impl Popup {
|
||||||
|
/// Creates a new popup window.
|
||||||
|
/// This includes setting up gtk-layer-shell
|
||||||
|
/// and an empty `gtk::Box` container.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
name: &str,
|
name: &str,
|
||||||
app: &Application,
|
app: &Application,
|
||||||
@@ -107,9 +110,10 @@ impl Popup {
|
|||||||
let screen_width = self.monitor.workarea().width();
|
let screen_width = self.monitor.workarea().width();
|
||||||
let popup_width = self.window.allocated_width();
|
let popup_width = self.window.allocated_width();
|
||||||
|
|
||||||
|
let top_level = button.toplevel().expect("Failed to get top-level widget");
|
||||||
let (widget_x, _) = button
|
let (widget_x, _) = button
|
||||||
.translate_coordinates(&button.toplevel().unwrap(), 0, 0)
|
.translate_coordinates(&top_level, 0, 0)
|
||||||
.unwrap();
|
.unwrap_or((0, 0));
|
||||||
|
|
||||||
let widget_center = f64::from(widget_x) + f64::from(widget_width) / 2.0;
|
let widget_center = f64::from(widget_x) + f64::from(widget_width) / 2.0;
|
||||||
|
|
||||||
|
|||||||
69
src/style.rs
69
src/style.rs
@@ -1,45 +1,66 @@
|
|||||||
|
use color_eyre::{Help, Report};
|
||||||
use glib::Continue;
|
use glib::Continue;
|
||||||
use gtk::prelude::CssProviderExt;
|
use gtk::prelude::CssProviderExt;
|
||||||
use gtk::{gdk, gio, CssProvider, StyleContext};
|
use gtk::{gdk, gio, CssProvider, StyleContext};
|
||||||
use notify::{DebouncedEvent, RecursiveMode, Watcher};
|
use notify::{Event, RecursiveMode, Result, Watcher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
/// Attempts to load CSS file at the given path
|
||||||
|
/// and attach if to the current GTK application.
|
||||||
|
///
|
||||||
|
/// Installs a file watcher and reloads CSS when
|
||||||
|
/// write changes are detected on the file.
|
||||||
pub fn load_css(style_path: PathBuf) {
|
pub fn load_css(style_path: PathBuf) {
|
||||||
let provider = CssProvider::new();
|
let provider = CssProvider::new();
|
||||||
provider
|
|
||||||
.load_from_file(&gio::File::for_path(&style_path))
|
|
||||||
.expect("Couldn't load custom style");
|
|
||||||
StyleContext::add_provider_for_screen(
|
|
||||||
&gdk::Screen::default().expect("Couldn't get default GDK screen"),
|
|
||||||
&provider,
|
|
||||||
800,
|
|
||||||
);
|
|
||||||
|
|
||||||
let (watcher_tx, watcher_rx) = mpsc::channel::<DebouncedEvent>();
|
if let Err(err) = provider.load_from_file(&gio::File::for_path(&style_path)) {
|
||||||
|
error!("{:?}", Report::new(err)
|
||||||
|
.wrap_err("Failed to load CSS")
|
||||||
|
.suggestion("Check the CSS file for errors")
|
||||||
|
.suggestion("GTK CSS uses a subset of the full CSS spec and many properties are not available. Ensure you are not using any unsupported property.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let screen = gdk::Screen::default().expect("Failed to get default GTK screen");
|
||||||
|
StyleContext::add_provider_for_screen(&screen, &provider, 800);
|
||||||
|
|
||||||
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
let mut watcher = notify::watcher(watcher_tx, Duration::from_millis(500)).unwrap();
|
match notify::recommended_watcher(move |res: Result<Event>| match res {
|
||||||
watcher
|
Ok(event) => {
|
||||||
.watch(&style_path, RecursiveMode::NonRecursive)
|
if let Some(path) = event.paths.first() {
|
||||||
.unwrap();
|
tx.send(path.clone())
|
||||||
|
.expect("Failed to send style changed message");
|
||||||
loop {
|
}
|
||||||
if let Ok(DebouncedEvent::Write(path)) = watcher_rx.recv() {
|
|
||||||
tx.send(path).unwrap();
|
|
||||||
}
|
}
|
||||||
|
Err(e) => error!("Error occurred when watching stylesheet: {:?}", e),
|
||||||
|
}) {
|
||||||
|
Ok(mut watcher) => {
|
||||||
|
watcher
|
||||||
|
.watch(&style_path, RecursiveMode::NonRecursive)
|
||||||
|
.expect("Unexpected error when attempting to watch CSS");
|
||||||
|
}
|
||||||
|
Err(err) => error!(
|
||||||
|
"{:?}",
|
||||||
|
Report::new(err).wrap_err("Failed to start CSS watcher")
|
||||||
|
),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
rx.attach(None, move |path| {
|
rx.attach(None, move |path| {
|
||||||
println!("Reloading CSS");
|
info!("Reloading CSS");
|
||||||
provider
|
if let Err(err) = provider
|
||||||
.load_from_file(&gio::File::for_path(path))
|
.load_from_file(&gio::File::for_path(path)) {
|
||||||
.expect("Couldn't load custom style");
|
error!("{:?}", Report::new(err)
|
||||||
|
.wrap_err("Failed to load CSS")
|
||||||
|
.suggestion("Check the CSS file for errors")
|
||||||
|
.suggestion("GTK CSS uses a subset of the full CSS spec and many properties are not available. Ensure you are not using any unsupported property.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|||||||
187
src/sway/mod.rs
187
src/sway/mod.rs
@@ -1,15 +1,22 @@
|
|||||||
|
use color_eyre::{Report, Result};
|
||||||
|
use crossbeam_channel::Receiver;
|
||||||
|
use ksway::{Error, IpcCommand, IpcEvent};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use tokio::spawn;
|
||||||
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
pub mod node;
|
pub mod node;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct WorkspaceEvent {
|
pub struct WorkspaceEvent {
|
||||||
pub change: String,
|
pub change: String,
|
||||||
pub old: Option<Workspace>,
|
pub old: Option<Workspace>,
|
||||||
pub current: Option<Workspace>,
|
pub current: Option<Workspace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct Workspace {
|
pub struct Workspace {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
@@ -17,13 +24,13 @@ pub struct Workspace {
|
|||||||
pub output: String,
|
pub output: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct WindowEvent {
|
pub struct WindowEvent {
|
||||||
pub change: String,
|
pub change: String,
|
||||||
pub container: SwayNode,
|
pub container: SwayNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct SwayNode {
|
pub struct SwayNode {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub node_type: String,
|
pub node_type: String,
|
||||||
@@ -38,12 +45,180 @@ pub struct SwayNode {
|
|||||||
pub window_properties: Option<WindowProperties>,
|
pub window_properties: Option<WindowProperties>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub struct WindowProperties {
|
pub struct WindowProperties {
|
||||||
pub class: String,
|
pub class: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct SwayOutput {
|
pub struct SwayOutput {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Broadcaster<T> = Arc<Mutex<UnboundedBroadcast<T>>>;
|
||||||
|
|
||||||
|
pub struct SwayClient {
|
||||||
|
client: ksway::Client,
|
||||||
|
|
||||||
|
workspace_bc: Broadcaster<WorkspaceEvent>,
|
||||||
|
window_bc: Broadcaster<WindowEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SwayClient {
|
||||||
|
fn connect() -> Result<Self> {
|
||||||
|
let client = match ksway::Client::connect() {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(err) => Err(get_client_error(err)),
|
||||||
|
}?;
|
||||||
|
info!("Sway IPC client connected");
|
||||||
|
|
||||||
|
let workspace_bc = Arc::new(Mutex::new(UnboundedBroadcast::new()));
|
||||||
|
let window_bc = Arc::new(Mutex::new(UnboundedBroadcast::new()));
|
||||||
|
|
||||||
|
let workspace_bc2 = workspace_bc.clone();
|
||||||
|
let window_bc2 = window_bc.clone();
|
||||||
|
spawn(async move {
|
||||||
|
let mut sub_client = match ksway::Client::connect() {
|
||||||
|
Ok(client) => Ok(client),
|
||||||
|
Err(err) => Err(get_client_error(err)),
|
||||||
|
}
|
||||||
|
.expect("Failed to connect to Sway IPC server");
|
||||||
|
info!("Sway IPC subscription client connected");
|
||||||
|
|
||||||
|
let event_types = vec![IpcEvent::Window, IpcEvent::Workspace];
|
||||||
|
let rx = match sub_client.subscribe(event_types) {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(err) => Err(get_client_error(err)),
|
||||||
|
}
|
||||||
|
.expect("Failed to subscribe to Sway IPC server");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
while let Ok((ev_type, payload)) = rx.try_recv() {
|
||||||
|
debug!("Received sway event {:?}", ev_type);
|
||||||
|
match ev_type {
|
||||||
|
IpcEvent::Workspace => {
|
||||||
|
let json = serde_json::from_slice::<WorkspaceEvent>(&payload).expect(
|
||||||
|
"Received invalid workspace event payload from Sway IPC server",
|
||||||
|
);
|
||||||
|
workspace_bc
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get lock on workspace event bus")
|
||||||
|
.send(json)
|
||||||
|
.expect("Failed to broadcast workspace event");
|
||||||
|
}
|
||||||
|
IpcEvent::Window => {
|
||||||
|
let json = serde_json::from_slice::<WindowEvent>(&payload).expect(
|
||||||
|
"Received invalid window event payload from Sway IPC server",
|
||||||
|
);
|
||||||
|
window_bc
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get lock on window event bus")
|
||||||
|
.send(json)
|
||||||
|
.expect("Failed to broadcast window event");
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match sub_client.poll() {
|
||||||
|
Ok(()) => Ok(()),
|
||||||
|
Err(err) => Err(get_client_error(err)),
|
||||||
|
}
|
||||||
|
.expect("Failed to poll Sway IPC client");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client,
|
||||||
|
workspace_bc: workspace_bc2,
|
||||||
|
window_bc: window_bc2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ipc(&mut self, command: IpcCommand) -> Result<Vec<u8>> {
|
||||||
|
debug!("Sending command: {:?}", command);
|
||||||
|
match self.client.ipc(command) {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(err) => Err(get_client_error(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn run(&mut self, cmd: String) -> Result<Vec<u8>> {
|
||||||
|
debug!("Sending command: {}", cmd);
|
||||||
|
match self.client.run(cmd) {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(err) => Err(get_client_error(err)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_workspace(&mut self) -> Receiver<WorkspaceEvent> {
|
||||||
|
trace!("Adding new workspace subscriber");
|
||||||
|
self.workspace_bc
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get lock on workspace event bus")
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe_window(&mut self) -> Receiver<WindowEvent> {
|
||||||
|
trace!("Adding new window subscriber");
|
||||||
|
self.window_bc
|
||||||
|
.lock()
|
||||||
|
.expect("Failed to get lock on window event bus")
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets an error report from a `ksway` error enum variant
|
||||||
|
pub fn get_client_error(error: Error) -> Report {
|
||||||
|
match error {
|
||||||
|
Error::SockPathNotFound => Report::msg("Sway socket path not found"),
|
||||||
|
Error::SubscriptionError => Report::msg("Sway IPC subscription error"),
|
||||||
|
Error::AlreadySubscribed => Report::msg("Already subscribed to Sway IPC server"),
|
||||||
|
Error::Io(err) => Report::new(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref CLIENT: Arc<Mutex<SwayClient>> = {
|
||||||
|
let client = SwayClient::connect();
|
||||||
|
match client {
|
||||||
|
Ok(client) => Arc::new(Mutex::new(client)),
|
||||||
|
Err(err) => panic!("{:?}", err),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_client() -> Arc<Mutex<SwayClient>> {
|
||||||
|
Arc::clone(&CLIENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crossbeam channel wrapper
|
||||||
|
/// which sends messages to all receivers.
|
||||||
|
pub struct UnboundedBroadcast<T> {
|
||||||
|
channels: Vec<crossbeam_channel::Sender<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static + Clone + Send + Sync> UnboundedBroadcast<T> {
|
||||||
|
/// Creates a new broadcaster.
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { channels: vec![] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new sender/receiver pair.
|
||||||
|
/// The sender is stored locally and the receiver is returned.
|
||||||
|
pub fn subscribe(&mut self) -> Receiver<T> {
|
||||||
|
let (tx, rx) = crossbeam_channel::unbounded();
|
||||||
|
|
||||||
|
self.channels.push(tx);
|
||||||
|
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempts to send a messsge to all receivers.
|
||||||
|
pub fn send(&self, message: T) -> Result<(), crossbeam_channel::SendError<T>> {
|
||||||
|
for c in &self.channels {
|
||||||
|
c.send(message.clone())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
use crate::sway::SwayNode;
|
use crate::sway::{SwayClient, SwayNode};
|
||||||
use ksway::{Client, IpcCommand};
|
use color_eyre::Result;
|
||||||
|
use ksway::IpcCommand;
|
||||||
|
|
||||||
impl SwayNode {
|
impl SwayNode {
|
||||||
|
/// Gets either the `app_id` or `class`
|
||||||
|
/// depending on whether this is a native Wayland
|
||||||
|
/// or xwayland application.
|
||||||
pub fn get_id(&self) -> &str {
|
pub fn get_id(&self) -> &str {
|
||||||
self.app_id.as_ref().map_or_else(
|
self.app_id.as_ref().map_or_else(
|
||||||
|| {
|
|| {
|
||||||
&self
|
self.window_properties
|
||||||
.window_properties
|
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("cannot find node name")
|
.expect("Cannot find node window properties")
|
||||||
.class
|
.class
|
||||||
|
.as_ref()
|
||||||
|
.expect("Cannot find node name")
|
||||||
},
|
},
|
||||||
|app_id| app_id,
|
|app_id| app_id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether this application
|
||||||
|
/// is running under xwayland.
|
||||||
pub fn is_xwayland(&self) -> bool {
|
pub fn is_xwayland(&self) -> bool {
|
||||||
self.shell == Some(String::from("xwayland"))
|
self.shell == Some(String::from("xwayland"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Recursively checks the provided node for any child application nodes.
|
||||||
|
/// Returns a list of any found application nodes.
|
||||||
fn check_node(node: SwayNode, window_nodes: &mut Vec<SwayNode>) {
|
fn check_node(node: SwayNode, window_nodes: &mut Vec<SwayNode>) {
|
||||||
if node.name.is_some() && (node.node_type == "con" || node.node_type == "floating_con") {
|
if node.name.is_some() && (node.node_type == "con" || node.node_type == "floating_con") {
|
||||||
window_nodes.push(node);
|
window_nodes.push(node);
|
||||||
@@ -34,12 +43,15 @@ fn check_node(node: SwayNode, window_nodes: &mut Vec<SwayNode>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_open_windows(sway: &mut Client) -> Vec<SwayNode> {
|
impl SwayClient {
|
||||||
let raw = sway.ipc(IpcCommand::GetTree).unwrap();
|
/// Gets a flat vector of all currently open windows.
|
||||||
let root_node = serde_json::from_slice::<SwayNode>(&raw).unwrap();
|
pub fn get_open_windows(&mut self) -> Result<Vec<SwayNode>> {
|
||||||
|
let root_node = self.ipc(IpcCommand::GetTree)?;
|
||||||
|
let root_node = serde_json::from_slice(&root_node)?;
|
||||||
|
|
||||||
let mut window_nodes = vec![];
|
let mut window_nodes = vec![];
|
||||||
check_node(root_node, &mut window_nodes);
|
check_node(root_node, &mut window_nodes);
|
||||||
|
|
||||||
window_nodes
|
Ok(window_nodes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user