mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-04-19 11:24:24 +02:00
refactor: major client code changes
This does away with `lazy_static` singletons for all the clients, instead putting them all inside a `Clients` struct on the `Ironbar` struct. Client code has been refactored in places to accommodate this, and module code has been updated to get the clients the new way. The Wayland client has been re-written from the ground up to remove a lot of the needless complications, provide a nicer interface and reduce some duplicate data. The MPD music client has been overhauled to use the `mpd_utils` crate, which simplifies the code within Ironbar and should offer more robustness and better recovery when connection is lost to the server. The launcher module in particular has been affected by the refactor.
This commit is contained in:
parent
57b57ed002
commit
c702f6fffa
33 changed files with 1081 additions and 1063 deletions
158
Cargo.lock
generated
158
Cargo.lock
generated
|
@ -218,8 +218,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -235,8 +235,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -478,8 +478,8 @@ checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
|||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -662,9 +662,9 @@ dependencies = [
|
|||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"scratch",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -680,8 +680,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -703,7 +703,7 @@ dependencies = [
|
|||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
@ -715,7 +715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
|
@ -737,7 +737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
|
@ -760,7 +760,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
|||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
@ -839,7 +839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "4e40a16955681d469ab3da85aaa6b42ff656b3c67b52e1d8d3dd36afe97fd462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
|
@ -860,8 +860,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -980,15 +980,15 @@ checksum = "55a5e644a80e6d96b2b4910fa7993301d7b7926c045b475b62202b20a36ce69e"
|
|||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -1017,9 +1017,9 @@ checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
|||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.28"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
@ -1049,9 +1049,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeee267a1883f7ebef3700f262d2d54de95dfaf38189015a74fdc4e0c7ad8143"
|
||||
checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
|
||||
dependencies = [
|
||||
"fastrand 2.0.1",
|
||||
"futures-core",
|
||||
|
@ -1067,8 +1067,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1251,8 +1251,8 @@ dependencies = [
|
|||
"proc-macro-crate 2.0.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1352,8 +1352,8 @@ dependencies = [
|
|||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1523,8 +1523,8 @@ version = "0.3.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c941d3d52e979612af8cb94e8de49000c7fada2014a7791d173ab41339f4e4eb"
|
||||
dependencies = [
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1650,15 +1650,14 @@ dependencies = [
|
|||
"color-eyre",
|
||||
"ctrlc",
|
||||
"dirs",
|
||||
"futures-lite 2.1.0",
|
||||
"futures-lite 2.2.0",
|
||||
"futures-util",
|
||||
"glib",
|
||||
"gtk",
|
||||
"gtk-layer-shell",
|
||||
"hyprland",
|
||||
"indexmap 2.1.0",
|
||||
"lazy_static",
|
||||
"mpd_client",
|
||||
"mpd-utils",
|
||||
"mpris",
|
||||
"nix 0.27.1",
|
||||
"notify",
|
||||
|
@ -1879,6 +1878,19 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpd-utils"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7081a86d39a604868a671b0166febc1f31a4c4801d9436ab733f2664baabf8a4"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"mpd_client",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mpd_client"
|
||||
version = "1.3.0"
|
||||
|
@ -2060,8 +2072,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2205,8 +2217,8 @@ dependencies = [
|
|||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2302,7 +2314,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
|||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
@ -2314,15 +2326,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
version = "1.0.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
|
||||
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
@ -2350,9 +2362,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.32"
|
||||
version = "1.0.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -2666,8 +2678,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2688,8 +2700,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2874,9 +2886,9 @@ checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059"
|
|||
dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"rustversion",
|
||||
"syn 2.0.28",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2922,18 +2934,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.28"
|
||||
version = "2.0.48"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
|
||||
checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
|
@ -3037,22 +3049,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.40"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
||||
checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.40"
|
||||
version = "1.0.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
||||
checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3134,8 +3146,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3254,8 +3266,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"syn 2.0.28",
|
||||
"quote 1.0.35",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3466,7 +3478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3521,7 +3533,7 @@ dependencies = [
|
|||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
@ -3544,7 +3556,7 @@ version = "0.2.84"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||
dependencies = [
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
|
@ -3555,7 +3567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
|
@ -3648,7 +3660,7 @@ checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4019,7 +4031,7 @@ checksum = "41d1794a946878c0e807f55a397187c11fc7a038ba5d868e7db4f3bd7760bc9d"
|
|||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
"zvariant_utils",
|
||||
|
@ -4058,7 +4070,7 @@ checksum = "934d7a7dfc310d6ee06c87ffe88ef4eca7d3e37bb251dece2ef93da8f17d8ecd"
|
|||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
@ -4070,6 +4082,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
|
|
@ -48,7 +48,7 @@ clock = ["chrono"]
|
|||
music = ["regex"]
|
||||
"music+all" = ["music", "music+mpris", "music+mpd"]
|
||||
"music+mpris" = ["music", "mpris"]
|
||||
"music+mpd" = ["music", "mpd_client"]
|
||||
"music+mpd" = ["music", "mpd-utils"]
|
||||
|
||||
sys_info = ["sysinfo", "regex"]
|
||||
|
||||
|
@ -115,7 +115,7 @@ nix = { version = "0.27.1", optional = true, features = ["event"] }
|
|||
chrono = { version = "0.4.31", optional = true, features = ["unstable-locales"] }
|
||||
|
||||
# music
|
||||
mpd_client = { version = "1.3.0", optional = true }
|
||||
mpd-utils = { version = "0.2.0", optional = true }
|
||||
mpris = { version = "2.0.1", optional = true }
|
||||
|
||||
# sys_info
|
||||
|
@ -126,7 +126,7 @@ system-tray = { version = "0.1.4", optional = true }
|
|||
|
||||
# upower
|
||||
upower_dbus = { version = "0.3.2", optional = true }
|
||||
futures-lite = { version = "2.1.0", optional = true }
|
||||
futures-lite = { version = "2.2.0", optional = true }
|
||||
zbus = { version = "3.14.1", optional = true }
|
||||
|
||||
# workspaces
|
||||
|
|
21
src/bar.rs
21
src/bar.rs
|
@ -27,6 +27,8 @@ pub struct Bar {
|
|||
monitor_name: String,
|
||||
position: BarPosition,
|
||||
|
||||
ironbar: Rc<Ironbar>,
|
||||
|
||||
window: ApplicationWindow,
|
||||
|
||||
content: gtk::Box,
|
||||
|
@ -39,7 +41,12 @@ pub struct Bar {
|
|||
}
|
||||
|
||||
impl Bar {
|
||||
pub fn new(app: &Application, monitor_name: String, config: Config) -> Self {
|
||||
pub fn new(
|
||||
app: &Application,
|
||||
monitor_name: String,
|
||||
config: Config,
|
||||
ironbar: Rc<Ironbar>,
|
||||
) -> Self {
|
||||
let window = ApplicationWindow::builder()
|
||||
.application(app)
|
||||
.type_(WindowType::Toplevel)
|
||||
|
@ -90,6 +97,7 @@ impl Bar {
|
|||
name,
|
||||
monitor_name,
|
||||
position,
|
||||
ironbar,
|
||||
window,
|
||||
content,
|
||||
start,
|
||||
|
@ -263,17 +271,17 @@ impl Bar {
|
|||
|
||||
if let Some(modules) = config.start {
|
||||
let info = info!(ModuleLocation::Left);
|
||||
add_modules(&self.start, modules, &info, &popup)?;
|
||||
add_modules(&self.start, modules, &info, &self.ironbar, &popup)?;
|
||||
}
|
||||
|
||||
if let Some(modules) = config.center {
|
||||
let info = info!(ModuleLocation::Center);
|
||||
add_modules(&self.center, modules, &info, &popup)?;
|
||||
add_modules(&self.center, modules, &info, &self.ironbar, &popup)?;
|
||||
}
|
||||
|
||||
if let Some(modules) = config.end {
|
||||
let info = info!(ModuleLocation::Right);
|
||||
add_modules(&self.end, modules, &info, &popup)?;
|
||||
add_modules(&self.end, modules, &info, &self.ironbar, &popup)?;
|
||||
}
|
||||
|
||||
let result = BarLoadResult { popup };
|
||||
|
@ -333,6 +341,7 @@ fn add_modules(
|
|||
content: >k::Box,
|
||||
modules: Vec<ModuleConfig>,
|
||||
info: &ModuleInfo,
|
||||
ironbar: &Rc<Ironbar>,
|
||||
popup: &Rc<RefCell<Popup>>,
|
||||
) -> Result<()> {
|
||||
let orientation = info.bar_position.get_orientation();
|
||||
|
@ -343,6 +352,7 @@ fn add_modules(
|
|||
let widget_parts = create_module(
|
||||
*$module,
|
||||
$id,
|
||||
ironbar.clone(),
|
||||
common.name.clone(),
|
||||
&info,
|
||||
&Rc::clone(&popup),
|
||||
|
@ -387,7 +397,8 @@ pub fn create_bar(
|
|||
monitor: &Monitor,
|
||||
monitor_name: String,
|
||||
config: Config,
|
||||
ironbar: Rc<Ironbar>,
|
||||
) -> Result<Bar> {
|
||||
let bar = Bar::new(app, monitor_name, config);
|
||||
let bar = Bar::new(app, monitor_name, config, ironbar);
|
||||
bar.init(monitor)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
use super::wayland::{self, ClipboardItem};
|
||||
use crate::{arc_mut, lock, spawn, try_send};
|
||||
use crate::{arc_mut, lock, register_client, spawn, try_send};
|
||||
use indexmap::map::Iter;
|
||||
use indexmap::IndexMap;
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ClipboardEvent {
|
||||
Add(Arc<ClipboardItem>),
|
||||
Add(ClipboardItem),
|
||||
Remove(usize),
|
||||
Activate(usize),
|
||||
}
|
||||
|
@ -18,13 +17,16 @@ type EventSender = mpsc::Sender<ClipboardEvent>;
|
|||
|
||||
/// Clipboard client singleton,
|
||||
/// to ensure bars don't duplicate requests to the compositor.
|
||||
pub struct ClipboardClient {
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
wayland: Arc<wayland::Client>,
|
||||
|
||||
senders: Arc<Mutex<Vec<(EventSender, usize)>>>,
|
||||
cache: Arc<Mutex<ClipboardCache>>,
|
||||
}
|
||||
|
||||
impl ClipboardClient {
|
||||
fn new() -> Self {
|
||||
impl Client {
|
||||
pub(crate) fn new(wl: Arc<wayland::Client>) -> Self {
|
||||
trace!("Initializing clipboard client");
|
||||
|
||||
let senders = arc_mut!(Vec::<(EventSender, usize)>::new());
|
||||
|
@ -34,13 +36,11 @@ impl ClipboardClient {
|
|||
{
|
||||
let senders = senders.clone();
|
||||
let cache = cache.clone();
|
||||
let wl = wl.clone();
|
||||
|
||||
spawn(async move {
|
||||
let (mut rx, item) = {
|
||||
let wl = wayland::get_client();
|
||||
let wl = lock!(wl);
|
||||
wl.subscribe_clipboard()
|
||||
};
|
||||
let item = wl.clipboard_item();
|
||||
let mut rx = wl.subscribe_clipboard();
|
||||
|
||||
if let Some(item) = item {
|
||||
let senders = lock!(senders);
|
||||
|
@ -91,7 +91,11 @@ impl ClipboardClient {
|
|||
});
|
||||
}
|
||||
|
||||
Self { senders, cache }
|
||||
Self {
|
||||
wayland: wl,
|
||||
senders,
|
||||
cache,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, cache_size: usize) -> mpsc::Receiver<ClipboardEvent> {
|
||||
|
@ -120,9 +124,7 @@ impl ClipboardClient {
|
|||
};
|
||||
|
||||
if let Some(item) = item {
|
||||
let wl = wayland::get_client();
|
||||
let wl = lock!(wl);
|
||||
wl.copy_to_clipboard(item);
|
||||
self.wayland.copy_to_clipboard(item);
|
||||
}
|
||||
|
||||
let senders = lock!(self.senders);
|
||||
|
@ -150,7 +152,7 @@ impl ClipboardClient {
|
|||
/// at different times.
|
||||
#[derive(Debug)]
|
||||
struct ClipboardCache {
|
||||
cache: IndexMap<usize, (Arc<ClipboardItem>, usize)>,
|
||||
cache: IndexMap<usize, (ClipboardItem, usize)>,
|
||||
}
|
||||
|
||||
impl ClipboardCache {
|
||||
|
@ -162,12 +164,12 @@ impl ClipboardCache {
|
|||
}
|
||||
|
||||
/// Gets the entry with key `id` from the cache.
|
||||
fn get(&self, id: usize) -> Option<Arc<ClipboardItem>> {
|
||||
fn get(&self, id: usize) -> Option<ClipboardItem> {
|
||||
self.cache.get(&id).map(|(item, _)| item).cloned()
|
||||
}
|
||||
|
||||
/// Inserts an entry with `ref_count` initial references.
|
||||
fn insert(&mut self, item: Arc<ClipboardItem>, ref_count: usize) -> Option<Arc<ClipboardItem>> {
|
||||
fn insert(&mut self, item: ClipboardItem, ref_count: usize) -> Option<ClipboardItem> {
|
||||
self.cache
|
||||
.insert(item.id, (item, ref_count))
|
||||
.map(|(item, _)| item)
|
||||
|
@ -175,7 +177,7 @@ impl ClipboardCache {
|
|||
|
||||
/// Removes the entry with key `id`.
|
||||
/// This ignores references.
|
||||
fn remove(&mut self, id: usize) -> Option<Arc<ClipboardItem>> {
|
||||
fn remove(&mut self, id: usize) -> Option<ClipboardItem> {
|
||||
self.cache.shift_remove(&id).map(|(item, _)| item)
|
||||
}
|
||||
|
||||
|
@ -224,15 +226,9 @@ impl ClipboardCache {
|
|||
self.cache.len()
|
||||
}
|
||||
|
||||
fn iter(&self) -> Iter<'_, usize, (Arc<ClipboardItem>, usize)> {
|
||||
fn iter(&self) -> Iter<'_, usize, (ClipboardItem, usize)> {
|
||||
self.cache.iter()
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: ClipboardClient = ClipboardClient::new();
|
||||
}
|
||||
|
||||
pub fn get_client() -> &'static ClipboardClient {
|
||||
&CLIENT
|
||||
}
|
||||
register_client!(Client, clipboard);
|
||||
|
|
|
@ -6,23 +6,26 @@ use hyprland::dispatch::{Dispatch, DispatchType, WorkspaceIdentifierWithSpecial}
|
|||
use hyprland::event_listener::EventListener;
|
||||
use hyprland::prelude::*;
|
||||
use hyprland::shared::{HyprDataVec, WorkspaceType};
|
||||
use lazy_static::lazy_static;
|
||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub struct EventClient {
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
workspace_tx: Sender<WorkspaceUpdate>,
|
||||
_workspace_rx: Receiver<WorkspaceUpdate>,
|
||||
}
|
||||
|
||||
impl EventClient {
|
||||
fn new() -> Self {
|
||||
impl Client {
|
||||
pub(crate) fn new() -> Self {
|
||||
let (workspace_tx, workspace_rx) = channel(16);
|
||||
|
||||
Self {
|
||||
let instance = Self {
|
||||
workspace_tx,
|
||||
_workspace_rx: workspace_rx,
|
||||
}
|
||||
};
|
||||
|
||||
instance.listen_workspace_events();
|
||||
instance
|
||||
}
|
||||
|
||||
fn listen_workspace_events(&self) {
|
||||
|
@ -203,7 +206,7 @@ impl EventClient {
|
|||
}
|
||||
}
|
||||
|
||||
impl WorkspaceClient for EventClient {
|
||||
impl WorkspaceClient for Client {
|
||||
fn focus(&self, id: String) -> Result<()> {
|
||||
let identifier = match id.parse::<i32>() {
|
||||
Ok(inum) => WorkspaceIdentifierWithSpecial::Id(inum),
|
||||
|
@ -239,18 +242,6 @@ impl WorkspaceClient for EventClient {
|
|||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: EventClient = {
|
||||
let client = EventClient::new();
|
||||
client.listen_workspace_events();
|
||||
client
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_client() -> &'static EventClient {
|
||||
&CLIENT
|
||||
}
|
||||
|
||||
fn get_workspace_name(name: WorkspaceType) -> String {
|
||||
match name {
|
||||
WorkspaceType::Regular(name) => name,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use crate::{await_sync, register_client};
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::{Help, Report, Result};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::debug;
|
||||
|
||||
|
@ -44,7 +46,7 @@ impl Compositor {
|
|||
}
|
||||
} else if std::env::var("HYPRLAND_INSTANCE_SIGNATURE").is_ok() {
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "workspaces+hyprland")] { Self::Hyprland}
|
||||
if #[cfg(feature = "workspaces+hyprland")] { Self::Hyprland }
|
||||
else { tracing::error!("Not compiled with Hyprland support"); Self::Unsupported }
|
||||
}
|
||||
} else {
|
||||
|
@ -52,15 +54,17 @@ impl Compositor {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the workspace client for the current compositor
|
||||
pub fn get_workspace_client() -> Result<&'static (dyn WorkspaceClient + Send)> {
|
||||
/// Creates a new instance of
|
||||
/// the workspace client for the current compositor.
|
||||
pub fn create_workspace_client() -> Result<Arc<dyn WorkspaceClient + Send + Sync>> {
|
||||
let current = Self::get_current();
|
||||
debug!("Getting workspace client for: {current}");
|
||||
match current {
|
||||
#[cfg(feature = "workspaces+sway")]
|
||||
Self::Sway => Ok(sway::get_sub_client()),
|
||||
Self::Sway => await_sync(async { sway::Client::new().await })
|
||||
.map(|client| Arc::new(client) as Arc<dyn WorkspaceClient + Send + Sync>),
|
||||
#[cfg(feature = "workspaces+hyprland")]
|
||||
Self::Hyprland => Ok(hyprland::get_client()),
|
||||
Self::Hyprland => Ok(Arc::new(hyprland::Client::new())),
|
||||
Self::Unsupported => Err(Report::msg("Unsupported compositor")
|
||||
.note("Currently workspaces are only supported by Sway and Hyprland")),
|
||||
}
|
||||
|
@ -129,10 +133,12 @@ pub enum WorkspaceUpdate {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
pub trait WorkspaceClient {
|
||||
pub trait WorkspaceClient: Debug + Send + Sync {
|
||||
/// Requests the workspace with this name is focused.
|
||||
fn focus(&self, name: String) -> Result<()>;
|
||||
|
||||
/// Creates a new to workspace event receiver.
|
||||
fn subscribe_workspace_change(&self) -> broadcast::Receiver<WorkspaceUpdate>;
|
||||
}
|
||||
|
||||
register_client!(dyn WorkspaceClient, workspaces);
|
||||
|
|
|
@ -1,32 +1,35 @@
|
|||
use super::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||
use crate::{await_sync, send, spawn};
|
||||
use async_once::AsyncOnce;
|
||||
use color_eyre::Report;
|
||||
use color_eyre::{Report, Result};
|
||||
use futures_util::StreamExt;
|
||||
use lazy_static::lazy_static;
|
||||
use std::sync::Arc;
|
||||
use swayipc_async::{Connection, Event, EventType, Node, WorkspaceChange, WorkspaceEvent};
|
||||
use tokio::sync::broadcast::{channel, Receiver, Sender};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{info, trace};
|
||||
|
||||
pub struct SwayEventClient {
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
client: Arc<Mutex<Connection>>,
|
||||
workspace_tx: Sender<WorkspaceUpdate>,
|
||||
_workspace_rx: Receiver<WorkspaceUpdate>,
|
||||
}
|
||||
|
||||
impl SwayEventClient {
|
||||
fn new() -> Self {
|
||||
impl Client {
|
||||
pub(crate) async fn new() -> Result<Self> {
|
||||
// Avoid using `arc_mut!` here because we need tokio Mutex.
|
||||
let client = Arc::new(Mutex::new(Connection::new().await?));
|
||||
info!("Sway IPC subscription client connected");
|
||||
|
||||
let (workspace_tx, workspace_rx) = channel(16);
|
||||
|
||||
{
|
||||
// create 2nd client as subscription takes ownership
|
||||
let client = Connection::new().await?;
|
||||
let workspace_tx = workspace_tx.clone();
|
||||
|
||||
spawn(async move {
|
||||
let client = Connection::new().await?;
|
||||
info!("Sway IPC subscription client connected");
|
||||
|
||||
let event_types = [EventType::Workspace];
|
||||
|
||||
let mut events = client.subscribe(event_types).await?;
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
|
@ -43,18 +46,18 @@ impl SwayEventClient {
|
|||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
Ok(Self {
|
||||
client,
|
||||
workspace_tx,
|
||||
_workspace_rx: workspace_rx,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WorkspaceClient for SwayEventClient {
|
||||
fn focus(&self, id: String) -> color_eyre::Result<()> {
|
||||
impl WorkspaceClient for Client {
|
||||
fn focus(&self, id: String) -> Result<()> {
|
||||
await_sync(async move {
|
||||
let client = get_client().await;
|
||||
let mut client = client.lock().await;
|
||||
let mut client = self.client.lock().await;
|
||||
client.run_command(format!("workspace {id}")).await
|
||||
})?;
|
||||
Ok(())
|
||||
|
@ -65,14 +68,12 @@ impl WorkspaceClient for SwayEventClient {
|
|||
|
||||
{
|
||||
let tx = self.workspace_tx.clone();
|
||||
await_sync(async {
|
||||
let client = get_client().await;
|
||||
let mut client = client.lock().await;
|
||||
let client = self.client.clone();
|
||||
|
||||
await_sync(async {
|
||||
let mut client = client.lock().await;
|
||||
let workspaces = client.get_workspaces().await.expect("to get workspaces");
|
||||
|
||||
let workspaces = client
|
||||
.get_workspaces()
|
||||
.await
|
||||
.expect("Failed to get workspaces");
|
||||
let event =
|
||||
WorkspaceUpdate::Init(workspaces.into_iter().map(Workspace::from).collect());
|
||||
|
||||
|
@ -84,27 +85,6 @@ impl WorkspaceClient for SwayEventClient {
|
|||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: AsyncOnce<Arc<Mutex<Connection>>> = AsyncOnce::new(async {
|
||||
let client = Connection::new()
|
||||
.await
|
||||
.expect("Failed to connect to Sway socket");
|
||||
Arc::new(Mutex::new(client))
|
||||
});
|
||||
static ref SUB_CLIENT: SwayEventClient = SwayEventClient::new();
|
||||
}
|
||||
|
||||
/// Gets the sway IPC client
|
||||
async fn get_client() -> Arc<Mutex<Connection>> {
|
||||
let client = CLIENT.get().await;
|
||||
Arc::clone(client)
|
||||
}
|
||||
|
||||
/// Gets the sway IPC event subscription client
|
||||
pub fn get_sub_client() -> &'static SwayEventClient {
|
||||
&SUB_CLIENT
|
||||
}
|
||||
|
||||
impl From<Node> for Workspace {
|
||||
fn from(node: Node) -> Self {
|
||||
let visibility = Visibility::from(&node);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub mod clipboard;
|
||||
#[cfg(feature = "workspaces")]
|
||||
|
@ -9,3 +11,109 @@ pub mod system_tray;
|
|||
#[cfg(feature = "upower")]
|
||||
pub mod upower;
|
||||
pub mod wayland;
|
||||
|
||||
/// Singleton wrapper consisting of
|
||||
/// all the singleton client types used by modules.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Clients {
|
||||
wayland: Option<Arc<wayland::Client>>,
|
||||
#[cfg(feature = "workspaces")]
|
||||
workspaces: Option<Arc<dyn compositor::WorkspaceClient>>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: Option<Arc<clipboard::Client>>,
|
||||
#[cfg(feature = "music")]
|
||||
music: std::collections::HashMap<music::ClientType, Arc<dyn music::MusicClient>>,
|
||||
#[cfg(feature = "tray")]
|
||||
tray: Option<Arc<system_tray::TrayEventReceiver>>,
|
||||
#[cfg(feature = "upower")]
|
||||
upower: Option<Arc<zbus::fdo::PropertiesProxy<'static>>>,
|
||||
}
|
||||
|
||||
impl Clients {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn wayland(&mut self) -> Arc<wayland::Client> {
|
||||
self.wayland
|
||||
.get_or_insert_with(|| Arc::new(wayland::Client::new()))
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub fn clipboard(&mut self) -> Arc<clipboard::Client> {
|
||||
let wayland = self.wayland();
|
||||
|
||||
self.clipboard
|
||||
.get_or_insert_with(|| Arc::new(clipboard::Client::new(wayland)))
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "workspaces")]
|
||||
pub fn workspaces(&mut self) -> Arc<dyn compositor::WorkspaceClient> {
|
||||
// TODO: Error handling here isn't great - should throw a user-friendly error & exit
|
||||
self.workspaces
|
||||
.get_or_insert_with(|| {
|
||||
compositor::Compositor::create_workspace_client().expect("to be valid compositor")
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "music")]
|
||||
pub fn music(&mut self, client_type: music::ClientType) -> Arc<dyn music::MusicClient> {
|
||||
self.music
|
||||
.entry(client_type.clone())
|
||||
.or_insert_with(|| music::create_client(client_type))
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "tray")]
|
||||
pub fn tray(&mut self) -> Arc<system_tray::TrayEventReceiver> {
|
||||
self.tray
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(crate::await_sync(async {
|
||||
system_tray::create_client().await
|
||||
}))
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "upower")]
|
||||
pub fn upower(&mut self) -> Arc<zbus::fdo::PropertiesProxy<'static>> {
|
||||
self.upower
|
||||
.get_or_insert_with(|| {
|
||||
crate::await_sync(async { upower::create_display_proxy().await })
|
||||
})
|
||||
.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Types implementing this trait
|
||||
/// indicate that they provide a singleton client instance of type `T`.
|
||||
pub trait ProvidesClient<T: ?Sized> {
|
||||
/// Returns a singleton client instance of type `T`.
|
||||
fn provide(&self) -> Arc<T>;
|
||||
}
|
||||
|
||||
/// Generates a `ProvidesClient` impl block on `WidgetContext`
|
||||
/// for the provided `$ty` (first argument) client type.
|
||||
///
|
||||
/// The implementation calls `$method` (second argument)
|
||||
/// on the `Clients` struct to obtain the client instance.
|
||||
///
|
||||
/// # Example
|
||||
/// `register_client!(Client, clipboard);`
|
||||
#[macro_export]
|
||||
macro_rules! register_client {
|
||||
($ty:ty, $method:ident) => {
|
||||
impl<TSend, TReceive> $crate::clients::ProvidesClient<$ty>
|
||||
for $crate::modules::WidgetContext<TSend, TReceive>
|
||||
where
|
||||
TSend: Clone,
|
||||
{
|
||||
fn provide(&self) -> Arc<$ty> {
|
||||
self.ironbar.clients.borrow_mut().$method()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use color_eyre::Result;
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
@ -19,8 +20,6 @@ pub enum PlayerUpdate {
|
|||
/// Triggered at regular intervals while a track is playing.
|
||||
/// Used to keep track of the progress through the current track.
|
||||
ProgressTick(ProgressTick),
|
||||
/// Triggered when the client disconnects from the player.
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -56,7 +55,7 @@ pub struct ProgressTick {
|
|||
pub elapsed: Option<Duration>,
|
||||
}
|
||||
|
||||
pub trait MusicClient {
|
||||
pub trait MusicClient: Debug + Send + Sync {
|
||||
fn play(&self) -> Result<()>;
|
||||
fn pause(&self) -> Result<()>;
|
||||
fn next(&self) -> Result<()>;
|
||||
|
@ -68,18 +67,15 @@ pub trait MusicClient {
|
|||
fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate>;
|
||||
}
|
||||
|
||||
pub enum ClientType<'a> {
|
||||
Mpd { host: &'a str, music_dir: PathBuf },
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum ClientType {
|
||||
Mpd { host: String, music_dir: PathBuf },
|
||||
Mpris,
|
||||
}
|
||||
|
||||
pub async fn get_client(client_type: ClientType<'_>) -> Box<Arc<dyn MusicClient>> {
|
||||
pub fn create_client(client_type: ClientType) -> Arc<dyn MusicClient> {
|
||||
match client_type {
|
||||
ClientType::Mpd { host, music_dir } => Box::new(
|
||||
mpd::get_client(host, music_dir)
|
||||
.await
|
||||
.expect("Failed to connect to MPD client"),
|
||||
),
|
||||
ClientType::Mpris => Box::new(mpris::get_client()),
|
||||
ClientType::Mpd { host, music_dir } => Arc::new(mpd::Client::new(host, music_dir)),
|
||||
ClientType::Mpris => Arc::new(mpris::Client::new()),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,91 +1,72 @@
|
|||
use super::{
|
||||
MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track, TICK_INTERVAL_MS,
|
||||
};
|
||||
use crate::{await_sync, send, spawn};
|
||||
use crate::{await_sync, send, spawn, Ironbar};
|
||||
use color_eyre::Report;
|
||||
use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use mpd_client::client::{Connection, ConnectionEvent, Subsystem};
|
||||
use mpd_client::commands::SeekMode;
|
||||
use mpd_client::protocol::MpdProtocolError;
|
||||
use mpd_client::client::{ConnectionEvent, Subsystem};
|
||||
use mpd_client::commands::{self, SeekMode};
|
||||
use mpd_client::responses::{PlayState, Song};
|
||||
use mpd_client::tag::Tag;
|
||||
use mpd_client::{commands, Client};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::os::unix::fs::FileTypeExt;
|
||||
use mpd_utils::{mpd_client, PersistentClient};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::net::{TcpStream, UnixStream};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::debug;
|
||||
|
||||
lazy_static! {
|
||||
static ref CONNECTIONS: Arc<Mutex<HashMap<String, Arc<MpdClient>>>> =
|
||||
Arc::new(Mutex::new(HashMap::new()));
|
||||
macro_rules! command {
|
||||
($self:ident, $command:expr) => {
|
||||
await_sync(async move { $self.client.command($command).await.map_err(Report::new) })
|
||||
};
|
||||
}
|
||||
|
||||
pub struct MpdClient {
|
||||
client: Client,
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
client: Arc<PersistentClient>,
|
||||
music_dir: PathBuf,
|
||||
tx: broadcast::Sender<PlayerUpdate>,
|
||||
_rx: broadcast::Receiver<PlayerUpdate>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MpdConnectionError {
|
||||
MaxRetries,
|
||||
ProtocolError(MpdProtocolError),
|
||||
}
|
||||
impl Client {
|
||||
pub fn new(host: String, music_dir: PathBuf) -> Self {
|
||||
let client = Arc::new(PersistentClient::new(host, Duration::from_secs(5)));
|
||||
let mut client_rx = client.subscribe();
|
||||
|
||||
impl Display for MpdConnectionError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MaxRetries => write!(f, "Reached max retries"),
|
||||
Self::ProtocolError(e) => write!(f, "{e:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
let (tx, rx) = broadcast::channel(32);
|
||||
|
||||
impl std::error::Error for MpdConnectionError {}
|
||||
|
||||
impl MpdClient {
|
||||
async fn new(host: &str, music_dir: PathBuf) -> Result<Self, MpdConnectionError> {
|
||||
debug!("Creating new MPD connection to {}", host);
|
||||
|
||||
let (client, mut state_changes) =
|
||||
wait_for_connection(host, Duration::from_secs(5), None).await?;
|
||||
|
||||
let (tx, rx) = broadcast::channel(16);
|
||||
let _guard = Ironbar::runtime().enter();
|
||||
client.init();
|
||||
|
||||
{
|
||||
let music_dir = music_dir.clone();
|
||||
let tx = tx.clone();
|
||||
let client = client.clone();
|
||||
let music_dir = music_dir.clone();
|
||||
|
||||
spawn(async move {
|
||||
while let Some(change) = state_changes.next().await {
|
||||
debug!("Received state change: {:?}", change);
|
||||
Self::send_update(&client, &tx, &music_dir)
|
||||
.await
|
||||
.expect("Failed to send update");
|
||||
|
||||
while let Ok(change) = client_rx.recv().await {
|
||||
debug!("Received state change: {change:?}");
|
||||
if let ConnectionEvent::SubsystemChange(
|
||||
Subsystem::Player | Subsystem::Queue | Subsystem::Mixer,
|
||||
) = change
|
||||
) = *change
|
||||
{
|
||||
Self::send_update(&client, &tx, &music_dir)
|
||||
.await
|
||||
.expect("Failed to send update");
|
||||
}
|
||||
}
|
||||
|
||||
Ok::<(), broadcast::error::SendError<(Option<Track>, Status)>>(())
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let client = client.clone();
|
||||
let tx = tx.clone();
|
||||
let client = client.clone();
|
||||
|
||||
spawn(async move {
|
||||
loop {
|
||||
|
@ -95,16 +76,16 @@ impl MpdClient {
|
|||
});
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
client,
|
||||
music_dir,
|
||||
tx,
|
||||
music_dir,
|
||||
_rx: rx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_update(
|
||||
client: &Client,
|
||||
client: &PersistentClient,
|
||||
tx: &broadcast::Sender<PlayerUpdate>,
|
||||
music_dir: &Path,
|
||||
) -> Result<(), broadcast::error::SendError<PlayerUpdate>> {
|
||||
|
@ -112,7 +93,7 @@ impl MpdClient {
|
|||
let status = client.command(commands::Status).await;
|
||||
|
||||
if let (Ok(current_song), Ok(status)) = (current_song, status) {
|
||||
let track = current_song.map(|s| Self::convert_song(&s.song, music_dir));
|
||||
let track = current_song.map(|s| convert_song(&s.song, music_dir));
|
||||
let status = Status::from(status);
|
||||
|
||||
let update = PlayerUpdate::Update(Box::new(track), status);
|
||||
|
@ -122,7 +103,7 @@ impl MpdClient {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_tick_update(client: &Client, tx: &broadcast::Sender<PlayerUpdate>) {
|
||||
async fn send_tick_update(client: &PersistentClient, tx: &broadcast::Sender<PlayerUpdate>) {
|
||||
let status = client.command(commands::Status).await;
|
||||
|
||||
if let Ok(status) = status {
|
||||
|
@ -136,183 +117,70 @@ impl MpdClient {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_connected(&self) -> bool {
|
||||
!self.client.is_connection_closed()
|
||||
}
|
||||
|
||||
fn send_disconnect_update(&self) -> Result<(), broadcast::error::SendError<PlayerUpdate>> {
|
||||
info!("Connection to MPD server lost");
|
||||
self.tx.send(PlayerUpdate::Disconnect)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert_song(song: &Song, music_dir: &Path) -> Track {
|
||||
let (track, disc) = song.number();
|
||||
|
||||
let cover_path = music_dir
|
||||
.join(
|
||||
song.file_path()
|
||||
.parent()
|
||||
.expect("Song path should not be root")
|
||||
.join("cover.jpg"),
|
||||
)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.ok();
|
||||
|
||||
Track {
|
||||
title: song.title().map(std::string::ToString::to_string),
|
||||
album: song.album().map(std::string::ToString::to_string),
|
||||
artist: Some(song.artists().join(", ")),
|
||||
date: try_get_first_tag(song, &Tag::Date).map(std::string::ToString::to_string),
|
||||
genre: try_get_first_tag(song, &Tag::Genre).map(std::string::ToString::to_string),
|
||||
disc: Some(disc),
|
||||
track: Some(track),
|
||||
cover_path,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! async_command {
|
||||
($client:expr, $command:expr) => {
|
||||
await_sync(async {
|
||||
$client
|
||||
.command($command)
|
||||
.await
|
||||
.unwrap_or_else(|err| error!("Failed to send command: {err:?}"))
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
impl MusicClient for MpdClient {
|
||||
impl MusicClient for Client {
|
||||
fn play(&self) -> Result<()> {
|
||||
async_command!(self.client, commands::SetPause(false));
|
||||
Ok(())
|
||||
command!(self, commands::SetPause(false))
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<()> {
|
||||
async_command!(self.client, commands::SetPause(true));
|
||||
Ok(())
|
||||
command!(self, commands::SetPause(true))
|
||||
}
|
||||
|
||||
fn next(&self) -> Result<()> {
|
||||
async_command!(self.client, commands::Next);
|
||||
Ok(())
|
||||
command!(self, commands::Next)
|
||||
}
|
||||
|
||||
fn prev(&self) -> Result<()> {
|
||||
async_command!(self.client, commands::Previous);
|
||||
Ok(())
|
||||
command!(self, commands::Previous)
|
||||
}
|
||||
|
||||
fn set_volume_percent(&self, vol: u8) -> Result<()> {
|
||||
async_command!(self.client, commands::SetVolume(vol));
|
||||
Ok(())
|
||||
command!(self, commands::SetVolume(vol))
|
||||
}
|
||||
|
||||
fn seek(&self, duration: Duration) -> Result<()> {
|
||||
async_command!(self.client, commands::Seek(SeekMode::Absolute(duration)));
|
||||
Ok(())
|
||||
command!(self, commands::Seek(SeekMode::Absolute(duration)))
|
||||
}
|
||||
|
||||
fn subscribe_change(&self) -> broadcast::Receiver<PlayerUpdate> {
|
||||
let rx = self.tx.subscribe();
|
||||
await_sync(async {
|
||||
await_sync(async move {
|
||||
Self::send_update(&self.client, &self.tx, &self.music_dir)
|
||||
.await
|
||||
.expect("Failed to send player update");
|
||||
.expect("to be able to send update");
|
||||
});
|
||||
rx
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_client(
|
||||
host: &str,
|
||||
music_dir: PathBuf,
|
||||
) -> Result<Arc<MpdClient>, MpdConnectionError> {
|
||||
let mut connections = CONNECTIONS.lock().await;
|
||||
match connections.get(host) {
|
||||
None => {
|
||||
let client = MpdClient::new(host, music_dir).await?;
|
||||
let client = Arc::new(client);
|
||||
connections.insert(host.to_string(), Arc::clone(&client));
|
||||
Ok(client)
|
||||
}
|
||||
Some(client) => {
|
||||
if client.is_connected() {
|
||||
Ok(Arc::clone(client))
|
||||
} else {
|
||||
client
|
||||
.send_disconnect_update()
|
||||
.expect("Failed to send disconnect update");
|
||||
fn convert_song(song: &Song, music_dir: &Path) -> Track {
|
||||
let (track, disc) = song.number();
|
||||
|
||||
let client = MpdClient::new(host, music_dir).await?;
|
||||
let client = Arc::new(client);
|
||||
connections.insert(host.to_string(), Arc::clone(&client));
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
let cover_path = music_dir
|
||||
.join(
|
||||
song.file_path()
|
||||
.parent()
|
||||
.expect("Song path should not be root")
|
||||
.join("cover.jpg"),
|
||||
)
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.ok();
|
||||
|
||||
Track {
|
||||
title: song.title().map(ToString::to_string),
|
||||
album: song.album().map(ToString::to_string),
|
||||
artist: Some(song.artists().join(", ")),
|
||||
date: try_get_first_tag(song, &Tag::Date).map(ToString::to_string),
|
||||
genre: try_get_first_tag(song, &Tag::Genre).map(ToString::to_string),
|
||||
disc: Some(disc),
|
||||
track: Some(track),
|
||||
cover_path,
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_connection(
|
||||
host: &str,
|
||||
interval: Duration,
|
||||
max_retries: Option<usize>,
|
||||
) -> Result<Connection, MpdConnectionError> {
|
||||
let mut retries = 0;
|
||||
let max_retries = max_retries.unwrap_or(usize::MAX);
|
||||
|
||||
loop {
|
||||
if retries == max_retries {
|
||||
break Err(MpdConnectionError::MaxRetries);
|
||||
}
|
||||
|
||||
retries += 1;
|
||||
|
||||
match try_get_mpd_conn(host).await {
|
||||
Ok(conn) => break Ok(conn),
|
||||
Err(err) => {
|
||||
if retries == max_retries {
|
||||
break Err(MpdConnectionError::ProtocolError(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) -> Result<Connection, MpdProtocolError> {
|
||||
if is_unix_socket(host) {
|
||||
connect_unix(host).await
|
||||
} else {
|
||||
connect_tcp(host).await
|
||||
}
|
||||
}
|
||||
|
||||
fn is_unix_socket(host: &str) -> bool {
|
||||
let path = PathBuf::from(host);
|
||||
path.exists()
|
||||
&& path
|
||||
.metadata()
|
||||
.map_or(false, |metadata| metadata.file_type().is_socket())
|
||||
}
|
||||
|
||||
async fn connect_unix(host: &str) -> Result<Connection, MpdProtocolError> {
|
||||
let connection = UnixStream::connect(host).await?;
|
||||
Client::connect(connection).await
|
||||
}
|
||||
|
||||
async fn connect_tcp(host: &str) -> Result<Connection, MpdProtocolError> {
|
||||
let connection = TcpStream::connect(host).await?;
|
||||
Client::connect(connection).await
|
||||
}
|
||||
|
||||
/// Attempts to read the first value for a tag
|
||||
/// (since the MPD client returns a vector of tags, or None)
|
||||
pub fn try_get_first_tag<'a>(song: &'a Song, tag: &'a Tag) -> Option<&'a str> {
|
||||
|
|
|
@ -2,20 +2,16 @@ use super::{MusicClient, PlayerState, PlayerUpdate, Status, Track, TICK_INTERVAL
|
|||
use crate::clients::music::ProgressTick;
|
||||
use crate::{arc_mut, lock, send, spawn_blocking};
|
||||
use color_eyre::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder};
|
||||
use std::cmp;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
use std::{cmp, string};
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: Arc<Client> = Arc::new(Client::new());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
current_player: Arc<Mutex<Option<String>>>,
|
||||
tx: broadcast::Sender<PlayerUpdate>,
|
||||
|
@ -23,7 +19,7 @@ pub struct Client {
|
|||
}
|
||||
|
||||
impl Client {
|
||||
fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
let (tx, rx) = broadcast::channel(32);
|
||||
|
||||
let current_player = arc_mut!(None);
|
||||
|
@ -289,10 +285,6 @@ impl MusicClient for Client {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_client() -> Arc<Client> {
|
||||
CLIENT.clone()
|
||||
}
|
||||
|
||||
impl From<Metadata> for Track {
|
||||
fn from(value: Metadata) -> Self {
|
||||
const KEY_DATE: &str = "xesam:contentCreated";
|
||||
|
@ -301,11 +293,11 @@ impl From<Metadata> for Track {
|
|||
Self {
|
||||
title: value
|
||||
.title()
|
||||
.map(std::string::ToString::to_string)
|
||||
.map(ToString::to_string)
|
||||
.and_then(replace_empty_none),
|
||||
album: value
|
||||
.album_name()
|
||||
.map(std::string::ToString::to_string)
|
||||
.map(ToString::to_string)
|
||||
.and_then(replace_empty_none),
|
||||
artist: value
|
||||
.artists()
|
||||
|
@ -314,14 +306,14 @@ impl From<Metadata> for Track {
|
|||
date: value
|
||||
.get(KEY_DATE)
|
||||
.and_then(mpris::MetadataValue::as_string)
|
||||
.map(std::string::ToString::to_string),
|
||||
.map(ToString::to_string),
|
||||
disc: value.disc_number().map(|disc| disc as u64),
|
||||
genre: value
|
||||
.get(KEY_GENRE)
|
||||
.and_then(mpris::MetadataValue::as_str_array)
|
||||
.and_then(|arr| arr.first().map(|val| (*val).to_string())),
|
||||
track: value.track_number().map(|track| track as u64),
|
||||
cover_path: value.art_url().map(string::ToString::to_string),
|
||||
cover_path: value.art_url().map(ToString::to_string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
use crate::{arc_mut, lock, send, spawn, Ironbar};
|
||||
use async_once::AsyncOnce;
|
||||
use crate::{arc_mut, lock, register_client, send, spawn, Ironbar};
|
||||
use color_eyre::Report;
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use system_tray::message::menu::TrayMenu;
|
||||
|
@ -13,6 +11,7 @@ use tracing::{debug, error, trace};
|
|||
|
||||
type Tray = BTreeMap<String, (Box<StatusNotifierItem>, Option<TrayMenu>)>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TrayEventReceiver {
|
||||
tx: mpsc::Sender<NotifierItemCommand>,
|
||||
b_tx: broadcast::Sender<NotifierItemMessage>,
|
||||
|
@ -39,7 +38,7 @@ impl TrayEventReceiver {
|
|||
|
||||
spawn(async move {
|
||||
while let Ok(message) = host.recv().await {
|
||||
trace!("Received message: {message:?} ");
|
||||
trace!("Received message: {message:?}");
|
||||
|
||||
send!(b_tx, message.clone());
|
||||
let mut tray = lock!(tray);
|
||||
|
@ -94,32 +93,35 @@ impl TrayEventReceiver {
|
|||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: AsyncOnce<TrayEventReceiver> = AsyncOnce::new(async {
|
||||
const MAX_RETRIES: i32 = 10;
|
||||
/// Attempts to create a new `TrayEventReceiver` instance,
|
||||
/// retrying a maximum of 10 times before panicking the thread.
|
||||
pub async fn create_client() -> TrayEventReceiver {
|
||||
const MAX_RETRIES: i32 = 10;
|
||||
|
||||
// sometimes this can fail
|
||||
let mut retries = 0;
|
||||
// sometimes this can fail
|
||||
let mut retries = 0;
|
||||
|
||||
let value = loop {
|
||||
retries += 1;
|
||||
let value = loop {
|
||||
retries += 1;
|
||||
|
||||
let tray = Box::pin(TrayEventReceiver::new()).await;
|
||||
let tray = Box::pin(TrayEventReceiver::new()).await;
|
||||
|
||||
match tray {
|
||||
Ok(tray) => break Some(tray),
|
||||
Err(err) => error!("{:?}", Report::new(err).wrap_err(format!("Failed to create StatusNotifierWatcher (attempt {retries})")))
|
||||
}
|
||||
match tray {
|
||||
Ok(tray) => break Some(tray),
|
||||
Err(err) => error!(
|
||||
"{:?}",
|
||||
Report::new(err).wrap_err(format!(
|
||||
"Failed to create StatusNotifierWatcher (attempt {retries})"
|
||||
))
|
||||
),
|
||||
}
|
||||
|
||||
if retries == MAX_RETRIES {
|
||||
break None;
|
||||
}
|
||||
};
|
||||
if retries == MAX_RETRIES {
|
||||
break None;
|
||||
}
|
||||
};
|
||||
|
||||
value.expect("Failed to create StatusNotifierWatcher")
|
||||
});
|
||||
value.expect("Failed to create StatusNotifierWatcher")
|
||||
}
|
||||
|
||||
pub async fn get_tray_event_client() -> &'static TrayEventReceiver {
|
||||
CLIENT.get().await
|
||||
}
|
||||
register_client!(TrayEventReceiver, tray);
|
||||
|
|
|
@ -1,40 +1,35 @@
|
|||
use async_once::AsyncOnce;
|
||||
use lazy_static::lazy_static;
|
||||
use crate::register_client;
|
||||
use std::sync::Arc;
|
||||
use upower_dbus::UPowerProxy;
|
||||
use zbus::fdo::PropertiesProxy;
|
||||
|
||||
lazy_static! {
|
||||
static ref DISPLAY_PROXY: AsyncOnce<Arc<PropertiesProxy<'static>>> = AsyncOnce::new(async {
|
||||
let dbus = Box::pin(zbus::Connection::system())
|
||||
.await
|
||||
.expect("failed to create connection to system bus");
|
||||
pub async fn create_display_proxy() -> Arc<PropertiesProxy<'static>> {
|
||||
let dbus = Box::pin(zbus::Connection::system())
|
||||
.await
|
||||
.expect("failed to create connection to system bus");
|
||||
|
||||
let device_proxy = UPowerProxy::new(&dbus)
|
||||
.await
|
||||
.expect("failed to create upower proxy");
|
||||
let device_proxy = UPowerProxy::new(&dbus)
|
||||
.await
|
||||
.expect("failed to create upower proxy");
|
||||
|
||||
let display_device = device_proxy
|
||||
.get_display_device()
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("failed to get display device for {device_proxy:?}"));
|
||||
let display_device = device_proxy
|
||||
.get_display_device()
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("failed to get display device for {device_proxy:?}"));
|
||||
|
||||
let path = display_device.path().to_owned();
|
||||
let path = display_device.path().to_owned();
|
||||
|
||||
let proxy = PropertiesProxy::builder(&dbus)
|
||||
.destination("org.freedesktop.UPower")
|
||||
.expect("failed to set proxy destination address")
|
||||
.path(path)
|
||||
.expect("failed to set proxy path")
|
||||
.cache_properties(zbus::CacheProperties::No)
|
||||
.build()
|
||||
.await
|
||||
.expect("failed to build proxy");
|
||||
let proxy = PropertiesProxy::builder(&dbus)
|
||||
.destination("org.freedesktop.UPower")
|
||||
.expect("failed to set proxy destination address")
|
||||
.path(path)
|
||||
.expect("failed to set proxy path")
|
||||
.cache_properties(zbus::CacheProperties::No)
|
||||
.build()
|
||||
.await
|
||||
.expect("failed to build proxy");
|
||||
|
||||
Arc::new(proxy)
|
||||
});
|
||||
Arc::new(proxy)
|
||||
}
|
||||
|
||||
pub async fn get_display_proxy() -> &'static PropertiesProxy<'static> {
|
||||
DISPLAY_PROXY.get().await
|
||||
}
|
||||
register_client!(PropertiesProxy<'static>, upower);
|
||||
|
|
|
@ -1,269 +0,0 @@
|
|||
use super::wlr_foreign_toplevel::handle::ToplevelHandle;
|
||||
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||
use super::wlr_foreign_toplevel::ToplevelEvent;
|
||||
use super::Environment;
|
||||
use crate::error::ERR_CHANNEL_RECV;
|
||||
use crate::{send, spawn_blocking};
|
||||
use cfg_if::cfg_if;
|
||||
use color_eyre::Report;
|
||||
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
||||
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
|
||||
use smithay_client_toolkit::reexports::calloop::EventLoop;
|
||||
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
|
||||
use smithay_client_toolkit::registry::RegistryState;
|
||||
use smithay_client_toolkit::seat::SeatState;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::mpsc;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::globals::registry_queue_init;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::Connection;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
use super::ClipboardItem;
|
||||
use super::wlr_data_control::manager::DataControlDeviceManagerState;
|
||||
use crate::lock;
|
||||
use std::sync::Arc;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
/// Sends a request for all the outputs.
|
||||
/// These are then sent on the `output` channel.
|
||||
Outputs,
|
||||
/// Sends a request for all the seats.
|
||||
/// These are then sent ont the `seat` channel.
|
||||
Seats,
|
||||
/// Sends a request for all the toplevels.
|
||||
/// These are then sent on the `toplevel_init` channel.
|
||||
Toplevels,
|
||||
/// Sends a request for the current clipboard item.
|
||||
/// This is then sent on the `clipboard_init` channel.
|
||||
#[cfg(feature = "clipboard")]
|
||||
Clipboard,
|
||||
/// Copies the value to the clipboard
|
||||
#[cfg(feature = "clipboard")]
|
||||
CopyToClipboard(Arc<ClipboardItem>),
|
||||
/// Forces a dispatch, flushing any currently queued events
|
||||
Roundtrip,
|
||||
}
|
||||
|
||||
pub struct WaylandClient {
|
||||
// External channels
|
||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
_clipboard_rx: broadcast::Receiver<Arc<ClipboardItem>>,
|
||||
|
||||
// Internal channels
|
||||
toplevel_init_rx: mpsc::Receiver<HashMap<usize, ToplevelHandle>>,
|
||||
output_rx: mpsc::Receiver<Vec<OutputInfo>>,
|
||||
seat_rx: mpsc::Receiver<Vec<WlSeat>>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_init_rx: mpsc::Receiver<Option<Arc<ClipboardItem>>>,
|
||||
|
||||
request_tx: Sender<Request>,
|
||||
}
|
||||
|
||||
impl WaylandClient {
|
||||
pub(super) fn new() -> Self {
|
||||
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
|
||||
|
||||
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
|
||||
#[cfg(feature = "clipboard")]
|
||||
let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel();
|
||||
let (output_tx, output_rx) = mpsc::channel();
|
||||
let (seat_tx, seat_rx) = mpsc::channel();
|
||||
|
||||
let toplevel_tx2 = toplevel_tx.clone();
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
let (clipboard_tx, clipboard_rx) = broadcast::channel(32);
|
||||
let clipboard_tx2 = clipboard_tx.clone();
|
||||
}
|
||||
}
|
||||
|
||||
let (ev_tx, ev_rx) = channel::<Request>();
|
||||
|
||||
// `queue` is not `Send` so we need to handle everything inside the task
|
||||
spawn_blocking(move || {
|
||||
let toplevel_tx = toplevel_tx2;
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_tx = clipboard_tx2;
|
||||
|
||||
let conn =
|
||||
Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
|
||||
let (globals, queue) =
|
||||
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
|
||||
|
||||
let qh = queue.handle();
|
||||
let mut event_loop =
|
||||
EventLoop::<Environment>::try_new().expect("Failed to create new event loop");
|
||||
|
||||
WaylandSource::new(conn, queue)
|
||||
.insert(event_loop.handle())
|
||||
.expect("Failed to insert Wayland event queue into event loop");
|
||||
|
||||
let loop_handle = event_loop.handle();
|
||||
|
||||
// Initialize the registry handling
|
||||
// so other parts of Smithay's client toolkit may bind globals.
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
|
||||
let output_delegate = OutputState::new(&globals, &qh);
|
||||
let seat_delegate = SeatState::new(&globals, &qh);
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let data_control_device_manager_delegate =
|
||||
DataControlDeviceManagerState::bind(&globals, &qh)
|
||||
.expect("data device manager is not available");
|
||||
|
||||
let foreign_toplevel_manager_delegate = ToplevelManagerState::bind(&globals, &qh)
|
||||
.expect("foreign toplevel manager is not available");
|
||||
|
||||
let mut env = Environment {
|
||||
registry_state,
|
||||
output_state: output_delegate,
|
||||
seat_state: seat_delegate,
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_device_manager_state: data_control_device_manager_delegate,
|
||||
foreign_toplevel_manager_state: foreign_toplevel_manager_delegate,
|
||||
seats: vec![],
|
||||
handles: HashMap::new(),
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: crate::arc_mut!(None),
|
||||
toplevel_tx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_devices: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
selection_offers: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
copy_paste_sources: vec![],
|
||||
loop_handle: event_loop.handle(),
|
||||
};
|
||||
|
||||
loop_handle
|
||||
.insert_source(ev_rx, move |event, _metadata, env| {
|
||||
trace!("{event:?}");
|
||||
match event {
|
||||
Event::Msg(Request::Roundtrip) => debug!("Received refresh event"),
|
||||
Event::Msg(Request::Outputs) => {
|
||||
trace!("Received get outputs request");
|
||||
|
||||
send!(output_tx, env.output_info());
|
||||
}
|
||||
Event::Msg(Request::Seats) => {
|
||||
trace!("Receive get seats request");
|
||||
send!(seat_tx, env.seats.clone());
|
||||
}
|
||||
Event::Msg(Request::Toplevels) => {
|
||||
trace!("Receive get toplevels request");
|
||||
send!(toplevel_init_tx, env.handles.clone());
|
||||
}
|
||||
#[cfg(feature = "clipboard")]
|
||||
Event::Msg(Request::Clipboard) => {
|
||||
trace!("Receive get clipboard requests");
|
||||
let clipboard = lock!(env.clipboard).clone();
|
||||
send!(clipboard_init_tx, clipboard);
|
||||
}
|
||||
#[cfg(feature = "clipboard")]
|
||||
Event::Msg(Request::CopyToClipboard(value)) => {
|
||||
env.copy_to_clipboard(value, &qh);
|
||||
}
|
||||
Event::Closed => panic!("Channel unexpectedly closed"),
|
||||
}
|
||||
})
|
||||
.expect("Failed to insert channel into event queue");
|
||||
|
||||
loop {
|
||||
trace!("Dispatching event loop");
|
||||
if let Err(err) = event_loop.dispatch(None, &mut env) {
|
||||
error!(
|
||||
"{:?}",
|
||||
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
toplevel_tx,
|
||||
_toplevel_rx: toplevel_rx,
|
||||
toplevel_init_rx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_init_rx,
|
||||
output_rx,
|
||||
seat_rx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx,
|
||||
#[cfg(feature = "clipboard")]
|
||||
_clipboard_rx: clipboard_rx,
|
||||
request_tx: ev_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe_toplevels(
|
||||
&self,
|
||||
) -> (
|
||||
broadcast::Receiver<ToplevelEvent>,
|
||||
HashMap<usize, ToplevelHandle>,
|
||||
) {
|
||||
let rx = self.toplevel_tx.subscribe();
|
||||
|
||||
let receiver = &self.toplevel_init_rx;
|
||||
send!(self.request_tx, Request::Toplevels);
|
||||
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
|
||||
|
||||
(rx, data)
|
||||
}
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub fn subscribe_clipboard(
|
||||
&self,
|
||||
) -> (
|
||||
broadcast::Receiver<Arc<ClipboardItem>>,
|
||||
Option<Arc<ClipboardItem>>,
|
||||
) {
|
||||
let rx = self.clipboard_tx.subscribe();
|
||||
|
||||
let receiver = &self.clipboard_init_rx;
|
||||
send!(self.request_tx, Request::Clipboard);
|
||||
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
|
||||
|
||||
(rx, data)
|
||||
}
|
||||
|
||||
/// Force a roundtrip on the wayland connection,
|
||||
/// flushing any queued events and immediately receiving any new ones.
|
||||
pub fn roundtrip(&self) {
|
||||
trace!("Sending roundtrip request");
|
||||
send!(self.request_tx, Request::Roundtrip);
|
||||
}
|
||||
|
||||
pub fn get_outputs(&self) -> Vec<OutputInfo> {
|
||||
trace!("Sending get outputs request");
|
||||
|
||||
send!(self.request_tx, Request::Outputs);
|
||||
self.output_rx.recv().expect(ERR_CHANNEL_RECV)
|
||||
}
|
||||
|
||||
pub fn get_seats(&self) -> Vec<WlSeat> {
|
||||
trace!("Sending get seats request");
|
||||
|
||||
send!(self.request_tx, Request::Seats);
|
||||
self.seat_rx.recv().expect(ERR_CHANNEL_RECV)
|
||||
}
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
|
||||
send!(self.request_tx, Request::CopyToClipboard(item));
|
||||
}
|
||||
}
|
|
@ -1,28 +1,37 @@
|
|||
mod client;
|
||||
|
||||
mod macros;
|
||||
mod wl_output;
|
||||
mod wl_seat;
|
||||
mod wlr_foreign_toplevel;
|
||||
|
||||
use self::wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||
use crate::{arc_mut, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
|
||||
use crate::error::ERR_CHANNEL_RECV;
|
||||
use crate::{
|
||||
arc_mut, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager, lock,
|
||||
register_client, send, Ironbar,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use calloop_channel::Event::Msg;
|
||||
use cfg_if::cfg_if;
|
||||
use lazy_static::lazy_static;
|
||||
use smithay_client_toolkit::output::OutputState;
|
||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||
use color_eyre::Report;
|
||||
use smithay_client_toolkit::output::{OutputInfo, OutputState};
|
||||
use smithay_client_toolkit::reexports::calloop::channel as calloop_channel;
|
||||
use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle};
|
||||
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
|
||||
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
||||
use smithay_client_toolkit::seat::SeatState;
|
||||
use smithay_client_toolkit::{
|
||||
delegate_output, delegate_registry, delegate_seat, registry_handlers,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::globals::registry_queue_init;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
|
||||
pub use self::client::WaylandClient;
|
||||
pub use self::wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
||||
use wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||
|
||||
pub use wl_output::OutputEvent;
|
||||
pub use wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
|
@ -36,6 +45,7 @@ cfg_if! {
|
|||
|
||||
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataControlDeviceEntry {
|
||||
seat: WlSeat,
|
||||
device: DataControlDevice,
|
||||
|
@ -43,35 +53,162 @@ cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Environment {
|
||||
pub registry_state: RegistryState,
|
||||
pub output_state: OutputState,
|
||||
pub seat_state: SeatState,
|
||||
pub foreign_toplevel_manager_state: ToplevelManagerState,
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Output(OutputEvent),
|
||||
Toplevel(ToplevelEvent),
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub data_control_device_manager_state: DataControlDeviceManagerState,
|
||||
pub loop_handle: LoopHandle<'static, Self>,
|
||||
|
||||
pub seats: Vec<WlSeat>,
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub data_control_devices: Vec<DataControlDeviceEntry>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub selection_offers: Vec<SelectionOfferItem>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub copy_paste_sources: Vec<CopyPasteSource>,
|
||||
|
||||
pub handles: HashMap<usize, ToplevelHandle>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
|
||||
|
||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||
Clipboard(ClipboardItem),
|
||||
}
|
||||
|
||||
// Now we need to say we are delegating the responsibility of output related events for our application data
|
||||
// type to the requisite delegate.
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
Roundtrip,
|
||||
|
||||
OutputInfoAll,
|
||||
|
||||
ToplevelInfoAll,
|
||||
ToplevelFocus(usize),
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
CopyToClipboard(ClipboardItem),
|
||||
#[cfg(feature = "clipboard")]
|
||||
ClipboardItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Response {
|
||||
/// An empty success response
|
||||
Ok,
|
||||
|
||||
OutputInfo(Option<OutputInfo>),
|
||||
OutputInfoAll(Vec<OutputInfo>),
|
||||
|
||||
ToplevelInfo(Option<ToplevelInfo>),
|
||||
ToplevelInfoAll(Vec<ToplevelInfo>),
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
ClipboardItem(Option<ClipboardItem>),
|
||||
|
||||
Seat(WlSeat),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BroadcastChannel<T>(broadcast::Sender<T>, Arc<Mutex<broadcast::Receiver<T>>>);
|
||||
|
||||
impl<T> From<(broadcast::Sender<T>, broadcast::Receiver<T>)> for BroadcastChannel<T> {
|
||||
fn from(value: (broadcast::Sender<T>, broadcast::Receiver<T>)) -> Self {
|
||||
BroadcastChannel(value.0, arc_mut!(value.1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
tx: calloop_channel::Sender<Request>,
|
||||
rx: Arc<Mutex<std::sync::mpsc::Receiver<Response>>>,
|
||||
|
||||
output_channel: BroadcastChannel<OutputEvent>,
|
||||
toplevel_channel: BroadcastChannel<ToplevelEvent>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_channel: BroadcastChannel<ClipboardItem>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub(crate) fn new() -> Self {
|
||||
let (event_tx, mut event_rx) = mpsc::channel(32);
|
||||
|
||||
let (request_tx, request_rx) = calloop_channel::channel();
|
||||
let (response_tx, response_rx) = std::sync::mpsc::channel();
|
||||
|
||||
let output_channel = broadcast::channel(32);
|
||||
let toplevel_channel = broadcast::channel(32);
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_channel = broadcast::channel(32);
|
||||
|
||||
Ironbar::runtime().spawn_blocking(move || {
|
||||
Environment::spawn(event_tx, request_rx, response_tx);
|
||||
});
|
||||
|
||||
// listen to events
|
||||
{
|
||||
let output_tx = output_channel.0.clone();
|
||||
let toplevel_tx = toplevel_channel.0.clone();
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_tx = clipboard_channel.0.clone();
|
||||
|
||||
let rt = Ironbar::runtime();
|
||||
rt.spawn(async move {
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
match event {
|
||||
Event::Output(event) => send!(output_tx, event),
|
||||
Event::Toplevel(event) => send!(toplevel_tx, event),
|
||||
#[cfg(feature = "clipboard")]
|
||||
Event::Clipboard(item) => send!(clipboard_tx, item),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
tx: request_tx,
|
||||
rx: arc_mut!(response_rx),
|
||||
|
||||
output_channel: output_channel.into(),
|
||||
toplevel_channel: toplevel_channel.into(),
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_channel: clipboard_channel.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a request to the environment event loop,
|
||||
/// and returns the response.
|
||||
fn send_request(&self, request: Request) -> Response {
|
||||
send!(self.tx, request);
|
||||
lock!(self.rx).recv().expect(ERR_CHANNEL_RECV)
|
||||
}
|
||||
|
||||
/// Sends a round-trip request to the client,
|
||||
/// forcing it to send/receive any events in the queue.
|
||||
pub(crate) fn roundtrip(&self) -> Response {
|
||||
self.send_request(Request::Roundtrip)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Environment {
|
||||
registry_state: RegistryState,
|
||||
output_state: OutputState,
|
||||
seat_state: SeatState,
|
||||
|
||||
queue_handle: QueueHandle<Self>,
|
||||
loop_handle: LoopHandle<'static, Self>,
|
||||
|
||||
event_tx: mpsc::Sender<Event>,
|
||||
response_tx: std::sync::mpsc::Sender<Response>,
|
||||
|
||||
// local state
|
||||
handles: Vec<ToplevelHandle>,
|
||||
|
||||
// -- clipboard --
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_device_manager_state: DataControlDeviceManagerState,
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_devices: Vec<DataControlDeviceEntry>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
copy_paste_sources: Vec<CopyPasteSource>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
selection_offers: Vec<SelectionOfferItem>,
|
||||
|
||||
// local state
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: Arc<Mutex<Option<ClipboardItem>>>,
|
||||
}
|
||||
|
||||
delegate_registry!(Environment);
|
||||
|
||||
delegate_output!(Environment);
|
||||
delegate_seat!(Environment);
|
||||
|
||||
|
@ -82,21 +219,128 @@ cfg_if! {
|
|||
if #[cfg(feature = "clipboard")] {
|
||||
delegate_data_control_device_manager!(Environment);
|
||||
delegate_data_control_device!(Environment);
|
||||
delegate_data_control_source!(Environment);
|
||||
delegate_data_control_offer!(Environment);
|
||||
delegate_data_control_source!(Environment);
|
||||
}
|
||||
}
|
||||
|
||||
// In order for our delegate to know of the existence of globals, we need to implement registry
|
||||
// handling for the program. This trait will forward events to the RegistryHandler trait
|
||||
// implementations.
|
||||
delegate_registry!(Environment);
|
||||
impl Environment {
|
||||
pub fn spawn(
|
||||
event_tx: mpsc::Sender<Event>,
|
||||
request_rx: calloop_channel::Channel<Request>,
|
||||
response_tx: std::sync::mpsc::Sender<Response>,
|
||||
) {
|
||||
let conn = Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
|
||||
let (globals, queue) =
|
||||
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
|
||||
|
||||
let qh = queue.handle();
|
||||
let mut event_loop = EventLoop::<Self>::try_new().expect("Failed to create new event loop");
|
||||
|
||||
WaylandSource::new(conn, queue)
|
||||
.insert(event_loop.handle())
|
||||
.expect("Failed to insert Wayland event queue into event loop");
|
||||
|
||||
let loop_handle = event_loop.handle();
|
||||
|
||||
// Initialize the registry handling
|
||||
// so other parts of Smithay's client toolkit may bind globals.
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
|
||||
let output_state = OutputState::new(&globals, &qh);
|
||||
let seat_state = SeatState::new(&globals, &qh);
|
||||
ToplevelManagerState::bind(&globals, &qh)
|
||||
.expect("to bind to wlr_foreign_toplevel_manager global");
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let data_control_device_manager_state = DataControlDeviceManagerState::bind(&globals, &qh)
|
||||
.expect("to bind to wlr_data_control_device_manager global");
|
||||
|
||||
let mut env = Self {
|
||||
registry_state,
|
||||
output_state,
|
||||
seat_state,
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_device_manager_state,
|
||||
queue_handle: qh,
|
||||
loop_handle: loop_handle.clone(),
|
||||
event_tx,
|
||||
response_tx,
|
||||
handles: vec![],
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_devices: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
copy_paste_sources: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
selection_offers: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: arc_mut!(None),
|
||||
};
|
||||
|
||||
loop_handle
|
||||
.insert_source(request_rx, Self::on_request)
|
||||
.expect("to be able to insert source");
|
||||
|
||||
loop {
|
||||
trace!("Dispatching event loop");
|
||||
if let Err(err) = event_loop.dispatch(None, &mut env) {
|
||||
error!(
|
||||
"{:?}",
|
||||
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes a request from the client
|
||||
/// and sends the response.
|
||||
fn on_request(event: calloop_channel::Event<Request>, _metadata: &mut (), env: &mut Self) {
|
||||
trace!("Request: {event:?}");
|
||||
|
||||
match event {
|
||||
Msg(Request::Roundtrip) => {
|
||||
debug!("received roundtrip request");
|
||||
send!(env.response_tx, Response::Ok);
|
||||
}
|
||||
Msg(Request::OutputInfoAll) => {
|
||||
let infos = env.output_info_all();
|
||||
send!(env.response_tx, Response::OutputInfoAll(infos));
|
||||
}
|
||||
Msg(Request::ToplevelInfoAll) => {
|
||||
let infos = env
|
||||
.handles
|
||||
.iter()
|
||||
.filter_map(ToplevelHandle::info)
|
||||
.collect();
|
||||
send!(env.response_tx, Response::ToplevelInfoAll(infos));
|
||||
}
|
||||
Msg(Request::ToplevelFocus(id)) => {
|
||||
let handle = env
|
||||
.handles
|
||||
.iter()
|
||||
.find(|handle| handle.info().map_or(false, |info| info.id == id));
|
||||
if let Some(handle) = handle {
|
||||
let seat = env.default_seat();
|
||||
handle.focus(&seat);
|
||||
}
|
||||
send!(env.response_tx, Response::Ok);
|
||||
}
|
||||
#[cfg(feature = "clipboard")]
|
||||
Msg(Request::CopyToClipboard(item)) => {
|
||||
env.copy_to_clipboard(item);
|
||||
send!(env.response_tx, Response::Ok);
|
||||
}
|
||||
#[cfg(feature = "clipboard")]
|
||||
Msg(Request::ClipboardItem) => {
|
||||
let item = lock!(env.clipboard).clone();
|
||||
send!(env.response_tx, Response::ClipboardItem(item));
|
||||
}
|
||||
calloop_channel::Event::Closed => error!("request channel unexpectedly closed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In order for delegate_registry to work, our application data type needs to provide a way for the
|
||||
// implementation to access the registry state.
|
||||
//
|
||||
// We also need to indicate which delegates will get told about globals being created. We specify
|
||||
// the types of the delegates inside the array.
|
||||
impl ProvidesRegistryState for Environment {
|
||||
fn registry(&mut self) -> &mut RegistryState {
|
||||
&mut self.registry_state
|
||||
|
@ -104,10 +348,4 @@ impl ProvidesRegistryState for Environment {
|
|||
registry_handlers![OutputState, SeatState];
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: Arc<Mutex<WaylandClient>> = arc_mut!(WaylandClient::new());
|
||||
}
|
||||
|
||||
pub fn get_client() -> Arc<Mutex<WaylandClient>> {
|
||||
CLIENT.clone()
|
||||
}
|
||||
register_client!(Client, wayland);
|
||||
|
|
|
@ -1,11 +1,41 @@
|
|||
use super::Environment;
|
||||
use super::{Client, Environment, Event, Request, Response};
|
||||
use crate::try_send;
|
||||
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
|
||||
use tracing::debug;
|
||||
use wayland_client::protocol::wl_output;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, error};
|
||||
use wayland_client::protocol::wl_output::WlOutput;
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OutputEvent {
|
||||
output: OutputInfo,
|
||||
event_type: OutputEventType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum OutputEventType {
|
||||
New,
|
||||
Update,
|
||||
Destroyed,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Gets the information for all outputs.
|
||||
pub fn output_info_all(&self) -> Vec<OutputInfo> {
|
||||
match self.send_request(Request::OutputInfoAll) {
|
||||
Response::OutputInfoAll(info) => info,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to events from outputs.
|
||||
pub fn subscribe_outputs(&self) -> broadcast::Receiver<OutputEvent> {
|
||||
self.output_channel.0.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn output_info(&mut self) -> Vec<OutputInfo> {
|
||||
pub fn output_info_all(&mut self) -> Vec<OutputInfo> {
|
||||
self.output_state
|
||||
.outputs()
|
||||
.filter_map(|output| self.output_state.info(&output))
|
||||
|
@ -27,29 +57,48 @@ impl OutputHandler for Environment {
|
|||
// Then there exist these functions that indicate the lifecycle of an output.
|
||||
// These will be called as appropriate by the delegate implementation.
|
||||
|
||||
fn new_output(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
) {
|
||||
fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
||||
debug!("Handler received new output");
|
||||
if let Some(info) = self.output_state.info(&output) {
|
||||
try_send!(
|
||||
self.event_tx,
|
||||
Event::Output(OutputEvent {
|
||||
output: info,
|
||||
event_type: OutputEventType::New
|
||||
})
|
||||
);
|
||||
} else {
|
||||
error!("Output is missing information!");
|
||||
}
|
||||
}
|
||||
|
||||
fn update_output(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
) {
|
||||
fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
||||
debug!("Handle received output update");
|
||||
if let Some(info) = self.output_state.info(&output) {
|
||||
try_send!(
|
||||
self.event_tx,
|
||||
Event::Output(OutputEvent {
|
||||
output: info,
|
||||
event_type: OutputEventType::Update
|
||||
})
|
||||
);
|
||||
} else {
|
||||
error!("Output is missing information!");
|
||||
}
|
||||
}
|
||||
|
||||
fn output_destroyed(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_output: wl_output::WlOutput,
|
||||
) {
|
||||
fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
|
||||
debug!("Handle received output destruction");
|
||||
if let Some(info) = self.output_state.info(&output) {
|
||||
try_send!(
|
||||
self.event_tx,
|
||||
Event::Output(OutputEvent {
|
||||
output: info,
|
||||
event_type: OutputEventType::Destroyed
|
||||
})
|
||||
);
|
||||
} else {
|
||||
error!("Output is missing information!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,24 +1,30 @@
|
|||
use super::Environment;
|
||||
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
|
||||
use tracing::debug;
|
||||
use wayland_client::protocol::wl_seat;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
|
||||
impl Environment {
|
||||
/// Gets the default seat.
|
||||
pub(crate) fn default_seat(&self) -> WlSeat {
|
||||
self.seat_state.seats().next().expect("one seat to exist")
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandler for Environment {
|
||||
fn seat_state(&mut self) -> &mut SeatState {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
|
||||
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _seat: WlSeat) {
|
||||
debug!("Handler received new seat");
|
||||
self.seats.push(seat);
|
||||
}
|
||||
|
||||
fn new_capability(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
seat: wl_seat::WlSeat,
|
||||
seat: WlSeat,
|
||||
_: Capability,
|
||||
) {
|
||||
debug!("Handler received new capability");
|
||||
|
@ -39,25 +45,22 @@ impl SeatHandler for Environment {
|
|||
device: data_control_device,
|
||||
});
|
||||
}
|
||||
|
||||
if !self.seats.iter().any(|s| s == &seat) {
|
||||
self.seats.push(seat);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_capability(
|
||||
&mut self,
|
||||
_: &Connection,
|
||||
_: &QueueHandle<Self>,
|
||||
_: wl_seat::WlSeat,
|
||||
seat: WlSeat,
|
||||
_: Capability,
|
||||
) {
|
||||
debug!("Handler received capability removal");
|
||||
// Not applicable
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
self.data_control_devices.retain(|entry| entry.seat != seat);
|
||||
}
|
||||
|
||||
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
|
||||
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _seat: WlSeat) {
|
||||
debug!("Handler received seat removal");
|
||||
self.seats.retain(|s| s != &seat);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use wayland_protocols_wlr::data_control::v1::client::{
|
|||
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataControlDevice {
|
||||
pub device: ZwlrDataControlDeviceV1,
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use wayland_protocols_wlr::data_control::v1::client::{
|
|||
zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataControlDeviceManagerState<V = DataControlOfferData> {
|
||||
manager: ZwlrDataControlManagerV1,
|
||||
_phantom: PhantomData<V>,
|
||||
|
|
|
@ -6,8 +6,8 @@ pub mod source;
|
|||
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
|
||||
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
|
||||
use self::source::DataControlSourceHandler;
|
||||
use crate::clients::wayland::Environment;
|
||||
use crate::{lock, send, Ironbar};
|
||||
use super::{Client, Environment, Event, Request, Response};
|
||||
use crate::{lock, try_send, Ironbar};
|
||||
use device::DataControlDevice;
|
||||
use glib::Bytes;
|
||||
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
|
||||
|
@ -21,22 +21,28 @@ use std::io::{ErrorKind, Read, Write};
|
|||
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
|
||||
use std::sync::Arc;
|
||||
use std::{fs, io};
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
|
||||
|
||||
const INTERNAL_MIME_TYPE: &str = "x-ironbar-internal";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SelectionOfferItem {
|
||||
offer: SelectionOffer,
|
||||
token: Option<RegistrationToken>,
|
||||
}
|
||||
|
||||
/// Represents a value which can be read/written
|
||||
/// to/from the system clipboard and surrounding metadata.
|
||||
///
|
||||
/// Can be cheaply cloned.
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct ClipboardItem {
|
||||
pub id: usize,
|
||||
pub value: ClipboardValue,
|
||||
pub mime_type: String,
|
||||
pub value: Arc<ClipboardValue>,
|
||||
pub mime_type: Arc<str>,
|
||||
}
|
||||
|
||||
impl PartialEq<Self> for ClipboardItem {
|
||||
|
@ -108,24 +114,60 @@ impl MimeType {
|
|||
}
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn copy_to_clipboard(&mut self, item: Arc<ClipboardItem>, qh: &QueueHandle<Self>) {
|
||||
debug!("Copying item to clipboard: {item:?}");
|
||||
|
||||
// TODO: Proper device tracking
|
||||
let device = self.data_control_devices.first();
|
||||
if let Some(device) = device {
|
||||
let source = self
|
||||
.data_control_device_manager_state
|
||||
.create_copy_paste_source(qh, [INTERNAL_MIME_TYPE, item.mime_type.as_str()]);
|
||||
|
||||
source.set_selection(&device.device);
|
||||
self.copy_paste_sources.push(source);
|
||||
|
||||
lock!(self.clipboard).replace(item);
|
||||
impl Client {
|
||||
/// Gets the current clipboard item,
|
||||
/// if this exists and Ironbar has record of it.
|
||||
pub fn clipboard_item(&self) -> Option<ClipboardItem> {
|
||||
match self.send_request(Request::ClipboardItem) {
|
||||
Response::ClipboardItem(item) => item,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies the provided value to the system clipboard.
|
||||
pub fn copy_to_clipboard(&self, item: ClipboardItem) {
|
||||
match self.send_request(Request::CopyToClipboard(item)) {
|
||||
Response::Ok => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to the system clipboard,
|
||||
/// receiving all new copied items.
|
||||
pub fn subscribe_clipboard(&self) -> broadcast::Receiver<ClipboardItem> {
|
||||
self.clipboard_channel.0.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
/// Creates a new copy/paste source on the
|
||||
/// seat's data control device.
|
||||
///
|
||||
/// This provides it as an offer,
|
||||
/// which the compositor will then treat as the current copied value.
|
||||
pub fn copy_to_clipboard(&mut self, item: ClipboardItem) {
|
||||
debug!("Copying item to clipboard: {item:?}");
|
||||
|
||||
let seat = self.default_seat();
|
||||
let Some(device) = self
|
||||
.data_control_devices
|
||||
.iter()
|
||||
.find(|entry| entry.seat == seat)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let source = self
|
||||
.data_control_device_manager_state
|
||||
.create_copy_paste_source(&self.queue_handle, [INTERNAL_MIME_TYPE, &item.mime_type]);
|
||||
|
||||
source.set_selection(&device.device);
|
||||
self.copy_paste_sources.push(source);
|
||||
|
||||
lock!(self.clipboard).replace(item);
|
||||
}
|
||||
|
||||
/// Reads an offer file handle into a new `ClipboardItem`.
|
||||
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
|
||||
let value = match mime_type.category {
|
||||
MimeTypeCategory::Text => {
|
||||
|
@ -145,13 +187,15 @@ impl Environment {
|
|||
|
||||
Ok(ClipboardItem {
|
||||
id: Ironbar::unique_id(),
|
||||
value,
|
||||
mime_type: mime_type.value.clone(),
|
||||
value: Arc::new(value),
|
||||
mime_type: mime_type.value.clone().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DataControlDeviceHandler for Environment {
|
||||
/// Called when an offer for a new value is received
|
||||
/// (ie something has copied to the clipboard)
|
||||
fn selection(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
|
@ -175,15 +219,16 @@ impl DataControlDeviceHandler for Environment {
|
|||
.last_mut()
|
||||
.expect("Failed to get current offer");
|
||||
|
||||
// clear prev
|
||||
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
|
||||
lock!(self.clipboard).take();
|
||||
// send an event so the clipboard module is aware it's changed
|
||||
send!(
|
||||
self.clipboard_tx,
|
||||
Arc::new(ClipboardItem {
|
||||
try_send!(
|
||||
self.event_tx,
|
||||
Event::Clipboard(ClipboardItem {
|
||||
id: usize::MAX,
|
||||
mime_type: String::new(),
|
||||
value: ClipboardValue::Other
|
||||
mime_type: String::new().into(),
|
||||
value: Arc::new(ClipboardValue::Other)
|
||||
})
|
||||
);
|
||||
return;
|
||||
|
@ -192,7 +237,7 @@ impl DataControlDeviceHandler for Environment {
|
|||
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
|
||||
let offer_clone = cur_offer.offer.clone();
|
||||
|
||||
let tx = self.clipboard_tx.clone();
|
||||
let tx = self.event_tx.clone();
|
||||
let clipboard = self.clipboard.clone();
|
||||
|
||||
let token =
|
||||
|
@ -207,9 +252,8 @@ impl DataControlDeviceHandler for Environment {
|
|||
|
||||
match Self::read_file(&mime_type, file.get_mut()) {
|
||||
Ok(item) => {
|
||||
let item = Arc::new(item);
|
||||
lock!(clipboard).replace(item.clone());
|
||||
send!(tx, item);
|
||||
try_send!(tx, Event::Clipboard(item));
|
||||
}
|
||||
Err(err) => error!("{err:?}"),
|
||||
}
|
||||
|
@ -255,6 +299,8 @@ impl DataControlSourceHandler for Environment {
|
|||
debug!("Accepted mime type: {mime:?}");
|
||||
}
|
||||
|
||||
/// Writes the current clipboard item to 'paste' it
|
||||
/// upon request from a compositor client.
|
||||
fn send_request(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
|
@ -274,12 +320,12 @@ impl DataControlSourceHandler for Environment {
|
|||
{
|
||||
trace!("Source found, writing to file");
|
||||
|
||||
let mut bytes = match &item.value {
|
||||
let mut bytes = match item.value.as_ref() {
|
||||
ClipboardValue::Text(text) => text.as_bytes(),
|
||||
ClipboardValue::Image(bytes) => bytes.as_ref(),
|
||||
ClipboardValue::Other => panic!(
|
||||
"{:?}",
|
||||
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type",)
|
||||
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type")
|
||||
),
|
||||
};
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ use wayland_protocols_wlr::foreign_toplevel::v1::client::{
|
|||
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ToplevelManagerState<V = ToplevelHandleData> {
|
||||
manager: ZwlrForeignToplevelManagerV1,
|
||||
_phantom: PhantomData<V>,
|
||||
|
@ -31,12 +32,7 @@ impl ToplevelManagerState {
|
|||
|
||||
pub trait ToplevelManagerHandler: Sized {
|
||||
/// Advertises a new toplevel.
|
||||
fn toplevel(
|
||||
&mut self,
|
||||
conn: &Connection,
|
||||
qh: &QueueHandle<Self>,
|
||||
manager: ToplevelManagerState,
|
||||
);
|
||||
fn toplevel(&mut self, conn: &Connection, qh: &QueueHandle<Self>);
|
||||
}
|
||||
|
||||
impl ProvidesBoundGlobal<ZwlrForeignToplevelManagerV1, 3> for ToplevelManagerState {
|
||||
|
@ -60,7 +56,7 @@ where
|
|||
|
||||
fn event(
|
||||
state: &mut D,
|
||||
toplevel_manager: &ZwlrForeignToplevelManagerV1,
|
||||
_toplevel_manager: &ZwlrForeignToplevelManagerV1,
|
||||
event: Event,
|
||||
_data: &GlobalData,
|
||||
conn: &Connection,
|
||||
|
@ -68,14 +64,7 @@ where
|
|||
) {
|
||||
match event {
|
||||
Event::Toplevel { toplevel: _ } => {
|
||||
state.toplevel(
|
||||
conn,
|
||||
qhandle,
|
||||
ToplevelManagerState {
|
||||
manager: toplevel_manager.clone(),
|
||||
_phantom: PhantomData,
|
||||
},
|
||||
);
|
||||
state.toplevel(conn, qhandle);
|
||||
}
|
||||
Event::Finished => {
|
||||
warn!("Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues.");
|
||||
|
|
|
@ -2,41 +2,62 @@ pub mod handle;
|
|||
pub mod manager;
|
||||
|
||||
use self::handle::ToplevelHandleHandler;
|
||||
use self::manager::{ToplevelManagerHandler, ToplevelManagerState};
|
||||
use crate::clients::wayland::Environment;
|
||||
use self::manager::ToplevelManagerHandler;
|
||||
use super::{Client, Environment, Event, Request, Response};
|
||||
use crate::try_send;
|
||||
use tokio::sync::broadcast;
|
||||
use tracing::{debug, error, trace};
|
||||
use wayland_client::{Connection, QueueHandle};
|
||||
|
||||
use crate::send;
|
||||
pub use handle::{ToplevelHandle, ToplevelInfo};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ToplevelEvent {
|
||||
New(ToplevelHandle),
|
||||
Update(ToplevelHandle),
|
||||
Remove(ToplevelHandle),
|
||||
New(ToplevelInfo),
|
||||
Update(ToplevelInfo),
|
||||
Remove(ToplevelInfo),
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Gets the information for all currently open toplevels (windows)
|
||||
pub fn toplevel_info_all(&self) -> Vec<ToplevelInfo> {
|
||||
match self.send_request(Request::ToplevelInfoAll) {
|
||||
Response::ToplevelInfoAll(infos) => infos,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Focuses the toplevel with the provided ID.
|
||||
pub fn toplevel_focus(&self, handle_id: usize) {
|
||||
match self.send_request(Request::ToplevelFocus(handle_id)) {
|
||||
Response::Ok => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Subscribes to events from toplevels.
|
||||
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
|
||||
self.toplevel_channel.0.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToplevelManagerHandler for Environment {
|
||||
fn toplevel(
|
||||
&mut self,
|
||||
_conn: &Connection,
|
||||
_qh: &QueueHandle<Self>,
|
||||
_manager: ToplevelManagerState,
|
||||
) {
|
||||
fn toplevel(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>) {
|
||||
debug!("Manager received new handle");
|
||||
}
|
||||
}
|
||||
|
||||
impl ToplevelHandleHandler for Environment {
|
||||
fn new_handle(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, handle: ToplevelHandle) {
|
||||
trace!("Handler received new handle");
|
||||
debug!("Handler received new handle");
|
||||
|
||||
match handle.info() {
|
||||
Some(info) => {
|
||||
trace!("Adding new handle: {info:?}");
|
||||
self.handles.insert(info.id, handle.clone());
|
||||
send!(self.toplevel_tx, ToplevelEvent::New(handle));
|
||||
self.handles.push(handle.clone());
|
||||
if let Some(info) = handle.info() {
|
||||
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::New(info)));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Handle is missing information!");
|
||||
|
@ -55,8 +76,10 @@ impl ToplevelHandleHandler for Environment {
|
|||
match handle.info() {
|
||||
Some(info) => {
|
||||
trace!("Updating handle: {info:?}");
|
||||
self.handles.insert(info.id, handle.clone());
|
||||
send!(self.toplevel_tx, ToplevelEvent::Update(handle));
|
||||
self.handles.push(handle.clone());
|
||||
if let Some(info) = handle.info() {
|
||||
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Update(info)));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Handle is missing information!");
|
||||
|
@ -71,14 +94,10 @@ impl ToplevelHandleHandler for Environment {
|
|||
handle: ToplevelHandle,
|
||||
) {
|
||||
debug!("Handler received handle close");
|
||||
match handle.info() {
|
||||
Some(info) => {
|
||||
self.handles.remove(&info.id);
|
||||
send!(self.toplevel_tx, ToplevelEvent::Remove(handle));
|
||||
}
|
||||
None => {
|
||||
error!("Handle is missing information!");
|
||||
}
|
||||
|
||||
self.handles.retain(|h| h != &handle);
|
||||
if let Some(info) = handle.info() {
|
||||
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Remove(info)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,7 +104,11 @@ impl Ipc {
|
|||
/// Takes an input command, runs it and returns with the appropriate response.
|
||||
///
|
||||
/// This runs on the main thread, allowing commands to interact with GTK.
|
||||
fn handle_command(command: Command, application: &Application, ironbar: &Ironbar) -> Response {
|
||||
fn handle_command(
|
||||
command: Command,
|
||||
application: &Application,
|
||||
ironbar: &Rc<Ironbar>,
|
||||
) -> Response {
|
||||
match command {
|
||||
Command::Inspect => {
|
||||
gtk::Window::set_interactive_debugging(true);
|
||||
|
@ -117,7 +121,7 @@ impl Ipc {
|
|||
window.close();
|
||||
}
|
||||
|
||||
*ironbar.bars.borrow_mut() = crate::load_interface(application);
|
||||
*ironbar.bars.borrow_mut() = crate::load_interface(application, ironbar.clone());
|
||||
|
||||
Response::Ok
|
||||
}
|
||||
|
|
45
src/main.rs
45
src/main.rs
|
@ -26,9 +26,8 @@ use tokio::task::{block_in_place, JoinHandle};
|
|||
use tracing::{debug, error, info, warn};
|
||||
use universal_config::ConfigLoader;
|
||||
|
||||
use clients::wayland;
|
||||
|
||||
use crate::bar::{create_bar, Bar};
|
||||
use crate::clients::Clients;
|
||||
use crate::config::{Config, MonitorConfig};
|
||||
use crate::error::ExitCode;
|
||||
#[cfg(feature = "ipc")]
|
||||
|
@ -104,12 +103,14 @@ lazy_static::lazy_static! {
|
|||
#[derive(Debug)]
|
||||
pub struct Ironbar {
|
||||
bars: Rc<RefCell<Vec<Bar>>>,
|
||||
clients: Rc<RefCell<Clients>>,
|
||||
}
|
||||
|
||||
impl Ironbar {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
bars: Rc::new(RefCell::new(vec![])),
|
||||
clients: Rc::new(RefCell::new(Clients::new())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,8 +125,8 @@ impl Ironbar {
|
|||
let instance = Rc::new(self);
|
||||
|
||||
// force start wayland client ahead of ui
|
||||
let wl = wayland::get_client();
|
||||
lock!(wl).roundtrip();
|
||||
let wl = instance.clients.borrow_mut().wayland();
|
||||
wl.roundtrip();
|
||||
|
||||
app.connect_activate(move |app| {
|
||||
if running.load(Ordering::Relaxed) {
|
||||
|
@ -142,7 +143,7 @@ impl Ironbar {
|
|||
}
|
||||
}
|
||||
|
||||
*instance.bars.borrow_mut() = load_interface(app);
|
||||
*instance.bars.borrow_mut() = load_interface(app, instance.clone());
|
||||
|
||||
let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|
||||
|| {
|
||||
|
@ -228,7 +229,7 @@ fn start_ironbar() {
|
|||
}
|
||||
|
||||
/// Loads the Ironbar config and interface.
|
||||
pub fn load_interface(app: &Application) -> Vec<Bar> {
|
||||
pub fn load_interface(app: &Application, ironbar: Rc<Ironbar>) -> Vec<Bar> {
|
||||
let display = Display::default().map_or_else(
|
||||
|| {
|
||||
let report = Report::msg("Failed to get default GTK display");
|
||||
|
@ -264,7 +265,7 @@ pub fn load_interface(app: &Application) -> Vec<Bar> {
|
|||
}
|
||||
}
|
||||
|
||||
match create_bars(app, &display, &config) {
|
||||
match create_bars(app, &display, &config, &ironbar) {
|
||||
Ok(bars) => {
|
||||
debug!("Created {} bars", bars.len());
|
||||
bars
|
||||
|
@ -277,9 +278,14 @@ pub fn load_interface(app: &Application) -> Vec<Bar> {
|
|||
}
|
||||
|
||||
/// Creates each of the bars across each of the (configured) outputs.
|
||||
fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<Vec<Bar>> {
|
||||
let wl = wayland::get_client();
|
||||
let outputs = lock!(wl).get_outputs();
|
||||
fn create_bars(
|
||||
app: &Application,
|
||||
display: &Display,
|
||||
config: &Config,
|
||||
ironbar: &Rc<Ironbar>,
|
||||
) -> Result<Vec<Bar>> {
|
||||
let wl = ironbar.clients.borrow_mut().wayland();
|
||||
let outputs = wl.output_info_all();
|
||||
|
||||
debug!("Received {} outputs from Wayland", outputs.len());
|
||||
debug!("Outputs: {:?}", outputs);
|
||||
|
@ -313,17 +319,27 @@ fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<
|
|||
&monitor,
|
||||
monitor_name.to_string(),
|
||||
config.clone(),
|
||||
ironbar.clone(),
|
||||
)?]
|
||||
}
|
||||
Some(MonitorConfig::Multiple(configs)) => configs
|
||||
.iter()
|
||||
.map(|config| create_bar(app, &monitor, monitor_name.to_string(), config.clone()))
|
||||
.map(|config| {
|
||||
create_bar(
|
||||
app,
|
||||
&monitor,
|
||||
monitor_name.to_string(),
|
||||
config.clone(),
|
||||
ironbar.clone(),
|
||||
)
|
||||
})
|
||||
.collect::<Result<_>>()?,
|
||||
None if show_default_bar => vec![create_bar(
|
||||
app,
|
||||
&monitor,
|
||||
monitor_name.to_string(),
|
||||
config.clone(),
|
||||
ironbar.clone(),
|
||||
)?],
|
||||
None => vec![],
|
||||
};
|
||||
|
@ -364,11 +380,8 @@ where
|
|||
/// This is not an `async` operation
|
||||
/// so can be used outside of an async function.
|
||||
///
|
||||
/// Do note it must be called from within a Tokio runtime still.
|
||||
///
|
||||
/// Use sparingly! Prefer async functions wherever possible.
|
||||
///
|
||||
/// TODO: remove all instances of this once async trait funcs are stable
|
||||
/// Use sparingly, as this risks blocking the UI thread!
|
||||
/// Prefer async functions wherever possible.
|
||||
pub fn await_sync<F: Future>(f: F) -> F::Output {
|
||||
block_in_place(|| Ironbar::runtime().block_on(f))
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ const fn default_max_items() -> usize {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ControllerEvent {
|
||||
Add(usize, Arc<ClipboardItem>),
|
||||
Add(usize, ClipboardItem),
|
||||
Remove(usize),
|
||||
Activate(usize),
|
||||
Deactivate,
|
||||
|
@ -78,17 +78,16 @@ impl Module<Button> for ClipboardModule {
|
|||
let max_items = self.max_items;
|
||||
|
||||
let tx = context.tx.clone();
|
||||
let client: Arc<clipboard::Client> = context.client();
|
||||
|
||||
// listen to clipboard events
|
||||
spawn(async move {
|
||||
let mut rx = {
|
||||
let client = clipboard::get_client();
|
||||
client.subscribe(max_items)
|
||||
};
|
||||
let mut rx = client.subscribe(max_items);
|
||||
|
||||
while let Some(event) = rx.recv().await {
|
||||
match event {
|
||||
ClipboardEvent::Add(item) => {
|
||||
let msg = match &item.value {
|
||||
let msg = match item.value.as_ref() {
|
||||
ClipboardValue::Other => {
|
||||
ModuleUpdateEvent::Update(ControllerEvent::Deactivate)
|
||||
}
|
||||
|
@ -108,10 +107,11 @@ impl Module<Button> for ClipboardModule {
|
|||
error!("Clipboard client unexpectedly closed");
|
||||
});
|
||||
|
||||
let client = context.client::<clipboard::Client>();
|
||||
|
||||
// listen to ui events
|
||||
spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let client = clipboard::get_client();
|
||||
match event {
|
||||
UIEvent::Copy(id) => client.copy(id),
|
||||
UIEvent::Remove(id) => client.remove(id),
|
||||
|
@ -172,7 +172,7 @@ impl Module<Button> for ClipboardModule {
|
|||
let row = gtk::Box::new(Orientation::Horizontal, 0);
|
||||
row.style_context().add_class("item");
|
||||
|
||||
let button = match &item.value {
|
||||
let button = match item.value.as_ref() {
|
||||
ClipboardValue::Text(value) => {
|
||||
let button = RadioButton::from_widget(&hidden_option);
|
||||
|
||||
|
|
|
@ -3,12 +3,12 @@ use crate::config::{CommonConfig, TruncateMode};
|
|||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{glib_recv, lock, send_async, spawn, try_send};
|
||||
use crate::{glib_recv, send_async, spawn, try_send};
|
||||
use color_eyre::Result;
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::debug;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
|
@ -58,21 +58,16 @@ impl Module<gtk::Box> for FocusedModule {
|
|||
&self,
|
||||
_info: &ModuleInfo,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: Receiver<Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
let (mut wlrx, handles) = {
|
||||
let wl = wayland::get_client();
|
||||
let wl = lock!(wl);
|
||||
wl.subscribe_toplevels()
|
||||
};
|
||||
let wl = context.client::<wayland::Client>();
|
||||
|
||||
let focused = handles.values().find_map(|handle| {
|
||||
handle
|
||||
.info()
|
||||
.and_then(|info| if info.focused { Some(info) } else { None })
|
||||
});
|
||||
spawn(async move {
|
||||
let mut wlrx = wl.subscribe_toplevels();
|
||||
let handles = wl.toplevel_info_all();
|
||||
|
||||
let focused = handles.into_iter().find(|info| info.focused);
|
||||
|
||||
if let Some(focused) = focused {
|
||||
try_send!(
|
||||
|
@ -83,9 +78,7 @@ impl Module<gtk::Box> for FocusedModule {
|
|||
|
||||
while let Ok(event) = wlrx.recv().await {
|
||||
match event {
|
||||
ToplevelEvent::Update(handle) => {
|
||||
let info = handle.info().unwrap_or_default();
|
||||
|
||||
ToplevelEvent::Update(info) => {
|
||||
if info.focused {
|
||||
debug!("Changing focus");
|
||||
send_async!(
|
||||
|
@ -99,8 +92,7 @@ impl Module<gtk::Box> for FocusedModule {
|
|||
send_async!(tx, ModuleUpdateEvent::Update(None));
|
||||
}
|
||||
}
|
||||
ToplevelEvent::Remove(handle) => {
|
||||
let info = handle.info().unwrap_or_default();
|
||||
ToplevelEvent::Remove(info) => {
|
||||
if info.focused {
|
||||
debug!("Clearing focus");
|
||||
send_async!(tx, ModuleUpdateEvent::Update(None));
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use super::open_state::OpenState;
|
||||
use crate::clients::wayland::ToplevelHandle;
|
||||
use crate::clients::wayland::ToplevelInfo;
|
||||
use crate::config::BarPosition;
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::image::ImageProvider;
|
||||
use crate::modules::launcher::{ItemEvent, LauncherUpdate};
|
||||
use crate::modules::ModuleUpdateEvent;
|
||||
use crate::{read_lock, try_send};
|
||||
use color_eyre::{Report, Result};
|
||||
use glib::Propagation;
|
||||
use gtk::prelude::*;
|
||||
use gtk::{Button, IconTheme};
|
||||
|
@ -15,7 +14,6 @@ use std::rc::Rc;
|
|||
use std::sync::RwLock;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::error;
|
||||
use wayland_client::protocol::wl_seat::WlSeat;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Item {
|
||||
|
@ -38,30 +36,24 @@ impl Item {
|
|||
}
|
||||
|
||||
/// Merges the provided node into this launcher item
|
||||
pub fn merge_toplevel(&mut self, handle: ToplevelHandle) -> Result<Window> {
|
||||
let info = handle
|
||||
.info()
|
||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||
|
||||
pub fn merge_toplevel(&mut self, info: ToplevelInfo) -> Window {
|
||||
let id = info.id;
|
||||
|
||||
if self.windows.is_empty() {
|
||||
self.name = info.title;
|
||||
self.name = info.title.clone();
|
||||
}
|
||||
|
||||
let window = Window::try_from(handle)?;
|
||||
let window = Window::from(info);
|
||||
self.windows.insert(id, window.clone());
|
||||
|
||||
self.recalculate_open_state();
|
||||
|
||||
Ok(window)
|
||||
window
|
||||
}
|
||||
|
||||
pub fn unmerge_toplevel(&mut self, handle: &ToplevelHandle) {
|
||||
if let Some(info) = handle.info() {
|
||||
self.windows.remove(&info.id);
|
||||
self.recalculate_open_state();
|
||||
}
|
||||
pub fn unmerge_toplevel(&mut self, info: &ToplevelInfo) {
|
||||
self.windows.remove(&info.id);
|
||||
self.recalculate_open_state();
|
||||
}
|
||||
|
||||
pub fn set_window_name(&mut self, window_id: usize, name: String) {
|
||||
|
@ -97,29 +89,24 @@ impl Item {
|
|||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ToplevelHandle> for Item {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(handle: ToplevelHandle) -> std::result::Result<Self, Self::Error> {
|
||||
let info = handle
|
||||
.info()
|
||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||
|
||||
impl From<ToplevelInfo> for Item {
|
||||
fn from(info: ToplevelInfo) -> Self {
|
||||
let id = info.id;
|
||||
let name = info.title.clone();
|
||||
let app_id = info.app_id.clone();
|
||||
let open_state = OpenState::from(&info);
|
||||
|
||||
let mut windows = IndexMap::new();
|
||||
let window = Window::try_from(handle)?;
|
||||
windows.insert(info.id, window);
|
||||
let window = Window::from(info);
|
||||
windows.insert(id, window);
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
app_id,
|
||||
favorite: false,
|
||||
open_state,
|
||||
windows,
|
||||
name,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,30 +115,17 @@ pub struct Window {
|
|||
pub id: usize,
|
||||
pub name: String,
|
||||
pub open_state: OpenState,
|
||||
handle: ToplevelHandle,
|
||||
}
|
||||
|
||||
impl TryFrom<ToplevelHandle> for Window {
|
||||
type Error = Report;
|
||||
|
||||
fn try_from(handle: ToplevelHandle) -> Result<Self, Self::Error> {
|
||||
let info = handle
|
||||
.info()
|
||||
.ok_or_else(|| Report::msg("Toplevel is missing associated info"))?;
|
||||
impl From<ToplevelInfo> for Window {
|
||||
fn from(info: ToplevelInfo) -> Self {
|
||||
let open_state = OpenState::from(&info);
|
||||
|
||||
Ok(Self {
|
||||
Self {
|
||||
id: info.id,
|
||||
name: info.title,
|
||||
open_state,
|
||||
handle,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn focus(&self, seat: &WlSeat) {
|
||||
self.handle.focus(seat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,12 @@
|
|||
mod item;
|
||||
mod open_state;
|
||||
|
||||
use self::item::{Item, ItemButton, Window};
|
||||
use self::item::{AppearanceOptions, Item, ItemButton, Window};
|
||||
use self::open_state::OpenState;
|
||||
use super::{Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::clients::wayland::{self, ToplevelEvent};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::desktop_file::find_desktop_file;
|
||||
use crate::modules::launcher::item::AppearanceOptions;
|
||||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||
};
|
||||
use crate::{arc_mut, glib_recv, lock, send_async, spawn, try_send, write_lock};
|
||||
use color_eyre::{Help, Report};
|
||||
use gtk::prelude::*;
|
||||
|
@ -112,27 +109,26 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
|
||||
let items2 = Arc::clone(&items);
|
||||
|
||||
let tx = context.tx.clone();
|
||||
let tx2 = context.tx.clone();
|
||||
|
||||
let wl = context.client::<wayland::Client>();
|
||||
spawn(async move {
|
||||
let items = items2;
|
||||
let tx = tx2;
|
||||
|
||||
let (mut wlrx, handles) = {
|
||||
let wl = wayland::get_client();
|
||||
let wl = lock!(wl);
|
||||
wl.subscribe_toplevels()
|
||||
};
|
||||
|
||||
for handle in handles.values() {
|
||||
let Some(info) = handle.info() else { continue };
|
||||
let mut wlrx = wl.subscribe_toplevels();
|
||||
let handles = wl.toplevel_info_all();
|
||||
|
||||
for info in handles {
|
||||
let mut items = lock!(items);
|
||||
let item = items.get_mut(&info.app_id);
|
||||
match item {
|
||||
Some(item) => {
|
||||
item.merge_toplevel(handle.clone())?;
|
||||
item.merge_toplevel(info.clone());
|
||||
}
|
||||
None => {
|
||||
items.insert(info.app_id.clone(), Item::try_from(handle.clone())?);
|
||||
items.insert(info.app_id.clone(), Item::from(info.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,22 +150,22 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
trace!("event: {:?}", event);
|
||||
|
||||
match event {
|
||||
ToplevelEvent::New(handle) => {
|
||||
let Some(info) = handle.info() else { continue };
|
||||
ToplevelEvent::New(info) => {
|
||||
let app_id = info.app_id.clone();
|
||||
|
||||
let new_item = {
|
||||
let mut items = lock!(items);
|
||||
let item = items.get_mut(&info.app_id);
|
||||
match item {
|
||||
None => {
|
||||
let item: Item = handle.try_into()?;
|
||||
let item: Item = info.into();
|
||||
|
||||
items.insert(info.app_id.clone(), item.clone());
|
||||
items.insert(app_id.clone(), item.clone());
|
||||
|
||||
ItemOrWindow::Item(item)
|
||||
}
|
||||
Some(item) => {
|
||||
let window = item.merge_toplevel(handle)?;
|
||||
let window = item.merge_toplevel(info);
|
||||
ItemOrWindow::Window(window)
|
||||
}
|
||||
}
|
||||
|
@ -180,14 +176,11 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
send_update(LauncherUpdate::AddItem(item)).await
|
||||
}
|
||||
ItemOrWindow::Window(window) => {
|
||||
send_update(LauncherUpdate::AddWindow(info.app_id.clone(), window))
|
||||
.await
|
||||
send_update(LauncherUpdate::AddWindow(app_id, window)).await
|
||||
}
|
||||
}?;
|
||||
}
|
||||
ToplevelEvent::Update(handle) => {
|
||||
let Some(info) = handle.info() else { continue };
|
||||
|
||||
ToplevelEvent::Update(info) => {
|
||||
if let Some(item) = lock!(items).get_mut(&info.app_id) {
|
||||
item.set_window_focused(info.id, info.focused);
|
||||
item.set_window_name(info.id, info.title.clone());
|
||||
|
@ -202,15 +195,13 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
))
|
||||
.await?;
|
||||
}
|
||||
ToplevelEvent::Remove(handle) => {
|
||||
let Some(info) = handle.info() else { continue };
|
||||
|
||||
ToplevelEvent::Remove(info) => {
|
||||
let remove_item = {
|
||||
let mut items = lock!(items);
|
||||
let item = items.get_mut(&info.app_id);
|
||||
match item {
|
||||
Some(item) => {
|
||||
item.unmerge_toplevel(&handle);
|
||||
item.unmerge_toplevel(&info);
|
||||
|
||||
if item.windows.is_empty() {
|
||||
items.remove(&info.app_id);
|
||||
|
@ -245,6 +236,7 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
});
|
||||
|
||||
// listen to ui events
|
||||
let wl = context.client::<wayland::Client>();
|
||||
spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
if let ItemEvent::OpenItem(app_id) = event {
|
||||
|
@ -272,37 +264,29 @@ impl Module<gtk::Box> for LauncherModule {
|
|||
} else {
|
||||
send_async!(tx, ModuleUpdateEvent::ClosePopup);
|
||||
|
||||
let wl = wayland::get_client();
|
||||
let items = lock!(items);
|
||||
|
||||
let id = match event {
|
||||
ItemEvent::FocusItem(app_id) => items.get(&app_id).and_then(|item| {
|
||||
item.windows
|
||||
.iter()
|
||||
.find(|(_, win)| !win.open_state.is_focused())
|
||||
.or_else(|| item.windows.first())
|
||||
.map(|(_, win)| win.id)
|
||||
}),
|
||||
ItemEvent::FocusItem(app_id) => {
|
||||
lock!(items).get(&app_id).and_then(|item| {
|
||||
item.windows
|
||||
.iter()
|
||||
.find(|(_, win)| !win.open_state.is_focused())
|
||||
.or_else(|| item.windows.first())
|
||||
.map(|(_, win)| win.id)
|
||||
})
|
||||
}
|
||||
ItemEvent::FocusWindow(id) => Some(id),
|
||||
ItemEvent::OpenItem(_) => unreachable!(),
|
||||
};
|
||||
|
||||
if let Some(id) = id {
|
||||
if let Some(window) =
|
||||
items.iter().find_map(|(_, item)| item.windows.get(&id))
|
||||
if let Some(window) = lock!(items)
|
||||
.iter()
|
||||
.find_map(|(_, item)| item.windows.get(&id))
|
||||
{
|
||||
debug!("Focusing window {id}: {}", window.name);
|
||||
|
||||
let seat = lock!(wl)
|
||||
.get_seats()
|
||||
.pop()
|
||||
.expect("Failed to get Wayland seat");
|
||||
window.focus(&seat);
|
||||
wl.toplevel_focus(window.id);
|
||||
}
|
||||
}
|
||||
|
||||
// roundtrip to immediately send activate event
|
||||
lock!(wl).roundtrip();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::cell::RefCell;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use color_eyre::Result;
|
||||
use glib::IsA;
|
||||
|
@ -10,10 +11,11 @@ use gtk::{Application, Button, EventBox, IconTheme, Orientation, Revealer, Widge
|
|||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::debug;
|
||||
|
||||
use crate::clients::ProvidesClient;
|
||||
use crate::config::{BarPosition, CommonConfig, TransitionType};
|
||||
use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry};
|
||||
use crate::popup::Popup;
|
||||
use crate::{glib_recv_mpsc, send};
|
||||
use crate::{glib_recv_mpsc, send, Ironbar};
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub mod clipboard;
|
||||
|
@ -76,6 +78,7 @@ where
|
|||
TSend: Clone,
|
||||
{
|
||||
pub id: usize,
|
||||
pub ironbar: Rc<Ironbar>,
|
||||
pub tx: mpsc::Sender<ModuleUpdateEvent<TSend>>,
|
||||
pub update_tx: broadcast::Sender<TSend>,
|
||||
pub controller_tx: mpsc::Sender<TReceive>,
|
||||
|
@ -87,6 +90,18 @@ impl<TSend, TReceive> WidgetContext<TSend, TReceive>
|
|||
where
|
||||
TSend: Clone,
|
||||
{
|
||||
/// Gets client `T` from the context.
|
||||
///
|
||||
/// This is a shorthand to avoid needing to go through
|
||||
/// `context.ironbar.clients`.
|
||||
pub fn client<T: ?Sized>(&self) -> Arc<T>
|
||||
where
|
||||
WidgetContext<TSend, TReceive>: ProvidesClient<T>,
|
||||
{
|
||||
ProvidesClient::provide(self)
|
||||
}
|
||||
|
||||
/// Subscribes to events sent from this widget.
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<TSend> {
|
||||
self.update_tx.subscribe()
|
||||
}
|
||||
|
@ -194,6 +209,7 @@ where
|
|||
pub fn create_module<TModule, TWidget, TSend, TRec>(
|
||||
module: TModule,
|
||||
id: usize,
|
||||
ironbar: Rc<Ironbar>,
|
||||
name: Option<String>,
|
||||
info: &ModuleInfo,
|
||||
popup: &Rc<RefCell<Popup>>,
|
||||
|
@ -210,6 +226,7 @@ where
|
|||
|
||||
let context = WidgetContext {
|
||||
id,
|
||||
ironbar,
|
||||
tx: ui_tx,
|
||||
update_tx: tx.clone(),
|
||||
controller_tx,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::cell::RefMut;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
@ -14,6 +15,7 @@ use tracing::error;
|
|||
use crate::clients::music::{
|
||||
self, MusicClient, PlayerState, PlayerUpdate, ProgressTick, Status, Track,
|
||||
};
|
||||
use crate::clients::Clients;
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::image::{new_icon_button, new_icon_label, ImageProvider};
|
||||
use crate::modules::PopupButton;
|
||||
|
@ -67,16 +69,18 @@ pub struct SongUpdate {
|
|||
display_string: String,
|
||||
}
|
||||
|
||||
async fn get_client(
|
||||
fn get_client(
|
||||
mut clients: RefMut<'_, Clients>,
|
||||
player_type: PlayerType,
|
||||
host: &str,
|
||||
host: String,
|
||||
music_dir: PathBuf,
|
||||
) -> Box<Arc<dyn MusicClient>> {
|
||||
match player_type {
|
||||
PlayerType::Mpd => music::get_client(music::ClientType::Mpd { host, music_dir }),
|
||||
PlayerType::Mpris => music::get_client(music::ClientType::Mpris {}),
|
||||
}
|
||||
.await
|
||||
) -> Arc<dyn MusicClient> {
|
||||
let client_type = match player_type {
|
||||
PlayerType::Mpd => music::ClientType::Mpd { host, music_dir },
|
||||
PlayerType::Mpris => music::ClientType::Mpris,
|
||||
};
|
||||
|
||||
clients.music(client_type)
|
||||
}
|
||||
|
||||
impl Module<Button> for MusicModule {
|
||||
|
@ -98,19 +102,21 @@ impl Module<Button> for MusicModule {
|
|||
let re = Regex::new(r"\{([\w-]+)}")?;
|
||||
let tokens = get_tokens(&re, self.format.as_str());
|
||||
|
||||
let client = get_client(
|
||||
context.ironbar.clients.borrow_mut(),
|
||||
self.player_type,
|
||||
self.host.clone(),
|
||||
self.music_dir.clone(),
|
||||
);
|
||||
|
||||
// receive player updates
|
||||
{
|
||||
let player_type = self.player_type;
|
||||
let host = self.host.clone();
|
||||
let music_dir = self.music_dir.clone();
|
||||
let tx = context.tx.clone();
|
||||
let client = client.clone();
|
||||
|
||||
spawn(async move {
|
||||
loop {
|
||||
let mut rx = {
|
||||
let client = get_client(player_type, &host, music_dir.clone()).await;
|
||||
client.subscribe_change()
|
||||
};
|
||||
let mut rx = client.subscribe_change();
|
||||
|
||||
while let Ok(update) = rx.recv().await {
|
||||
match update {
|
||||
|
@ -143,7 +149,6 @@ impl Module<Button> for MusicModule {
|
|||
progress_tick
|
||||
))
|
||||
),
|
||||
PlayerUpdate::Disconnect => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -152,13 +157,8 @@ impl Module<Button> for MusicModule {
|
|||
|
||||
// listen to ui events
|
||||
{
|
||||
let player_type = self.player_type;
|
||||
let host = self.host.clone();
|
||||
let music_dir = self.music_dir.clone();
|
||||
|
||||
spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
let client = get_client(player_type, &host, music_dir.clone()).await;
|
||||
let res = match event {
|
||||
PlayerCommand::Previous => client.prev(),
|
||||
PlayerCommand::Play => client.play(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::clients::system_tray::get_tray_event_client;
|
||||
use crate::clients::system_tray::TrayEventReceiver;
|
||||
use crate::config::CommonConfig;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
use crate::{await_sync, glib_recv, spawn, try_send};
|
||||
use crate::{glib_recv, spawn, try_send};
|
||||
use color_eyre::Result;
|
||||
use glib::ffi::g_strfreev;
|
||||
use glib::translate::ToGlibPtr;
|
||||
|
@ -171,8 +171,10 @@ impl Module<MenuBar> for TrayModule {
|
|||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
mut rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let client = await_sync(async { get_tray_event_client().await });
|
||||
let tx = context.tx.clone();
|
||||
|
||||
let client = context.client::<TrayEventReceiver>();
|
||||
|
||||
let (tray_tx, mut tray_rx) = client.subscribe();
|
||||
|
||||
// listen to tray updates
|
||||
|
|
|
@ -6,8 +6,8 @@ use serde::Deserialize;
|
|||
use tokio::sync::{broadcast, mpsc};
|
||||
use upower_dbus::BatteryState;
|
||||
use zbus;
|
||||
use zbus::fdo::PropertiesProxy;
|
||||
|
||||
use crate::clients::upower::get_display_proxy;
|
||||
use crate::config::CommonConfig;
|
||||
use crate::gtk_helpers::IronbarGtkExt;
|
||||
use crate::image::ImageProvider;
|
||||
|
@ -15,7 +15,7 @@ use crate::modules::PopupButton;
|
|||
use crate::modules::{
|
||||
Module, ModuleInfo, ModuleParts, ModulePopup, ModuleUpdateEvent, WidgetContext,
|
||||
};
|
||||
use crate::{await_sync, error, glib_recv, send_async, spawn, try_send};
|
||||
use crate::{error, glib_recv, send_async, spawn, try_send};
|
||||
|
||||
const DAY: i64 = 24 * 60 * 60;
|
||||
const HOUR: i64 = 60 * 60;
|
||||
|
@ -65,9 +65,10 @@ impl Module<gtk::Button> for UpowerModule {
|
|||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
|
||||
let display_proxy = context.client::<PropertiesProxy>();
|
||||
|
||||
spawn(async move {
|
||||
// await_sync due to strange "higher-ranked lifetime error"
|
||||
let display_proxy = await_sync(async move { get_display_proxy().await });
|
||||
let mut prop_changed_stream = display_proxy.receive_properties_changed().await?;
|
||||
|
||||
let device_interface_name =
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::clients::compositor::{Compositor, Visibility, Workspace, WorkspaceUpdate};
|
||||
use crate::clients::compositor::{Visibility, Workspace, WorkspaceClient, WorkspaceUpdate};
|
||||
use crate::config::CommonConfig;
|
||||
use crate::image::new_icon_button;
|
||||
use crate::modules::{Module, ModuleInfo, ModuleParts, ModuleUpdateEvent, WidgetContext};
|
||||
|
@ -98,7 +98,7 @@ fn create_button(
|
|||
}
|
||||
|
||||
if !visibility.is_visible() {
|
||||
style_context.add_class("inactive")
|
||||
style_context.add_class("inactive");
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -155,13 +155,10 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
mut rx: Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
let client = context.ironbar.clients.borrow_mut().workspaces();
|
||||
// Subscribe & send events
|
||||
spawn(async move {
|
||||
let mut srx = {
|
||||
let client =
|
||||
Compositor::get_workspace_client().expect("Failed to get workspace client");
|
||||
client.subscribe_workspace_change()
|
||||
};
|
||||
let mut srx = client.subscribe_workspace_change();
|
||||
|
||||
trace!("Set up workspace subscription");
|
||||
|
||||
|
@ -171,13 +168,13 @@ impl Module<gtk::Box> for WorkspacesModule {
|
|||
}
|
||||
});
|
||||
|
||||
let client = context.client::<dyn WorkspaceClient>();
|
||||
|
||||
// Change workspace focus
|
||||
spawn(async move {
|
||||
trace!("Setting up UI event handler");
|
||||
|
||||
while let Some(name) = rx.recv().await {
|
||||
let client =
|
||||
Compositor::get_workspace_client().expect("Failed to get workspace client");
|
||||
client.focus(name)?;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue