mirror of
https://github.com/JakeStanger/ironbar.git
synced 2024-11-30 05:24:20 +03:00
Merge pull request #396 from JakeStanger/refactor/clients
Massive Client Refactor
This commit is contained in:
commit
b55fbb3001
165
Cargo.lock
generated
165
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,16 +235,10 @@ 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]]
|
||||
name = "async_once"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82"
|
||||
|
||||
[[package]]
|
||||
name = "atk"
|
||||
version = "0.18.0"
|
||||
@ -478,8 +472,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 +656,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 +674,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 +697,7 @@ dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
@ -715,7 +709,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 +731,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 +754,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
@ -839,7 +833,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 +854,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 +974,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 +1011,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 +1043,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 +1061,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 +1245,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 +1346,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 +1517,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]]
|
||||
@ -1643,22 +1637,20 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
|
||||
name = "ironbar"
|
||||
version = "0.14.0-pre"
|
||||
dependencies = [
|
||||
"async_once",
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
"clap",
|
||||
"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 +1871,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 +2065,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 +2210,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 +2307,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 +2319,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 +2355,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 +2671,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 +2693,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 +2879,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 +2927,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 +3042,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 +3139,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 +3259,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 +3471,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 +3526,7 @@ dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
"syn 1.0.109",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -3544,7 +3549,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 +3560,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 +3653,7 @@ checksum = "fb8e28403665c9f9513202b7e1ed71ec56fde5c107816843fb14057910b2c09c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"quote 1.0.32",
|
||||
"quote 1.0.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4019,7 +4024,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 +4063,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 +4075,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"]
|
||||
|
||||
@ -94,9 +94,6 @@ smithay-client-toolkit = { version = "0.18.0", default-features = false, feature
|
||||
] }
|
||||
universal-config = { version = "0.4.3", default_features = false }
|
||||
ctrlc = "3.4.2"
|
||||
|
||||
lazy_static = "1.4.0"
|
||||
async_once = "0.2.6"
|
||||
cfg-if = "1.0.0"
|
||||
|
||||
# cli
|
||||
@ -115,7 +112,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 +123,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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,8 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Mutex;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use tracing::warn;
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
@ -11,13 +10,15 @@ use crate::lock;
|
||||
|
||||
type DesktopFile = HashMap<String, Vec<String>>;
|
||||
|
||||
lazy_static! {
|
||||
static ref DESKTOP_FILES: Mutex<HashMap<PathBuf, DesktopFile>> =
|
||||
Mutex::new(HashMap::new());
|
||||
fn desktop_files() -> &'static Mutex<HashMap<PathBuf, DesktopFile>> {
|
||||
static DESKTOP_FILES: OnceLock<Mutex<HashMap<PathBuf, DesktopFile>>> = OnceLock::new();
|
||||
DESKTOP_FILES.get_or_init(|| Mutex::new(HashMap::new()))
|
||||
}
|
||||
|
||||
/// These are the keys that in the cache
|
||||
static ref DESKTOP_FILES_LOOK_OUT_KEYS: HashSet<&'static str> =
|
||||
HashSet::from(["Name", "StartupWMClass", "Exec", "Icon"]);
|
||||
fn desktop_files_look_out_keys() -> &'static HashSet<&'static str> {
|
||||
static DESKTOP_FILES_LOOK_OUT_KEYS: OnceLock<HashSet<&'static str>> = OnceLock::new();
|
||||
DESKTOP_FILES_LOOK_OUT_KEYS
|
||||
.get_or_init(|| HashSet::from(["Name", "StartupWMClass", "Exec", "Icon"]))
|
||||
}
|
||||
|
||||
/// Finds directories that should contain `.desktop` files
|
||||
@ -104,7 +105,7 @@ fn find_desktop_file_by_filename(app_id: &str, files: &[PathBuf]) -> Option<Path
|
||||
/// Finds the correct desktop file using the keys in `DESKTOP_FILES_LOOK_OUT_KEYS`
|
||||
fn find_desktop_file_by_filedata(app_id: &str, files: &[PathBuf]) -> Option<PathBuf> {
|
||||
let app_id = &app_id.to_lowercase();
|
||||
let mut desktop_files_cache = lock!(DESKTOP_FILES);
|
||||
let mut desktop_files_cache = lock!(desktop_files());
|
||||
|
||||
let files = files
|
||||
.iter()
|
||||
@ -171,7 +172,7 @@ fn parse_desktop_file(path: &Path) -> Option<DesktopFile> {
|
||||
let key = key.trim();
|
||||
let value = value.trim();
|
||||
|
||||
if DESKTOP_FILES_LOOK_OUT_KEYS.contains(key) {
|
||||
if desktop_files_look_out_keys().contains(key) {
|
||||
Some((key, value))
|
||||
} else {
|
||||
None
|
||||
@ -193,7 +194,7 @@ pub fn get_desktop_icon_name(app_id: &str) -> Option<String> {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut desktop_files_cache = lock!(DESKTOP_FILES);
|
||||
let mut desktop_files_cache = lock!(desktop_files());
|
||||
|
||||
let desktop_file = match desktop_files_cache.get(&path) {
|
||||
Some(desktop_file) => desktop_file,
|
||||
|
@ -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
|
||||
}
|
||||
|
67
src/main.rs
67
src/main.rs
@ -9,7 +9,7 @@ use std::rc::Rc;
|
||||
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
|
||||
#[cfg(feature = "ipc")]
|
||||
use std::sync::RwLock;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::sync::{mpsc, Arc, OnceLock};
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
#[cfg(feature = "cli")]
|
||||
@ -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")]
|
||||
@ -90,26 +89,17 @@ fn run_with_args() {
|
||||
}
|
||||
}
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref RUNTIME: Arc<Runtime> = Arc::new(create_runtime());
|
||||
}
|
||||
|
||||
#[cfg(feature = "ipc")]
|
||||
lazy_static::lazy_static! {
|
||||
static ref VARIABLE_MANAGER: Arc<RwLock<VariableManager>> = arc_rw!(VariableManager::new());
|
||||
}
|
||||
|
||||
#[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 +114,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 +132,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(
|
||||
|| {
|
||||
@ -192,12 +182,14 @@ impl Ironbar {
|
||||
/// Gets the current Tokio runtime.
|
||||
#[must_use]
|
||||
pub fn runtime() -> Arc<Runtime> {
|
||||
RUNTIME.clone()
|
||||
static RUNTIME: OnceLock<Arc<Runtime>> = OnceLock::new();
|
||||
RUNTIME.get_or_init(|| Arc::new(create_runtime())).clone()
|
||||
}
|
||||
|
||||
/// Gets a `usize` ID value that is unique to the entire Ironbar instance.
|
||||
/// This is just a static `AtomicUsize` that increments every time this function is called.
|
||||
pub fn unique_id() -> usize {
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(1);
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@ -205,7 +197,10 @@ impl Ironbar {
|
||||
#[cfg(feature = "ipc")]
|
||||
#[must_use]
|
||||
pub fn variable_manager() -> Arc<RwLock<VariableManager>> {
|
||||
VARIABLE_MANAGER.clone()
|
||||
static VARIABLE_MANAGER: OnceLock<Arc<RwLock<VariableManager>>> = OnceLock::new();
|
||||
VARIABLE_MANAGER
|
||||
.get_or_init(|| arc_rw!(VariableManager::new()))
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Gets a clone of a bar by its unique name.
|
||||
@ -228,7 +223,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 +259,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 +272,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 +313,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 +374,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,
|
||||
@ -72,22 +72,22 @@ impl Module<Button> for ClipboardModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> color_eyre::Result<()> {
|
||||
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)
|
||||
}
|
||||
@ -107,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),
|
||||
@ -171,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);
|
||||
|
||||
|
@ -78,9 +78,10 @@ impl Module<Button> for ClockModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
loop {
|
||||
let date = Local::now();
|
||||
|
@ -158,9 +158,10 @@ impl Module<gtk::Box> for CustomModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
while let Some(event) = rx.recv().await {
|
||||
if event.cmd.starts_with('!') {
|
||||
|
@ -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)]
|
||||
@ -57,21 +57,17 @@ impl Module<gtk::Box> for FocusedModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
_rx: Receiver<Self::ReceiveMessage>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
spawn(async move {
|
||||
let (mut wlrx, handles) = {
|
||||
let wl = wayland::get_client();
|
||||
let wl = lock!(wl);
|
||||
wl.subscribe_toplevels()
|
||||
};
|
||||
let tx = context.tx.clone();
|
||||
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!(
|
||||
@ -82,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!(
|
||||
@ -98,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));
|
||||
|
@ -36,9 +36,10 @@ impl Module<Label> for LabelModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let tx = context.tx.clone();
|
||||
dynamic_string(&self.label, move |string| {
|
||||
try_send!(tx, ModuleUpdateEvent::Update(string));
|
||||
});
|
||||
|
@ -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::*;
|
||||
@ -90,7 +87,7 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> crate::Result<()> {
|
||||
let items = self
|
||||
@ -111,28 +108,27 @@ impl Module<gtk::Box> for LauncherModule {
|
||||
let items = arc_mut!(items);
|
||||
|
||||
let items2 = Arc::clone(&items);
|
||||
let tx2 = tx.clone();
|
||||
|
||||
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()
|
||||
}
|
||||
@ -162,7 +177,7 @@ where
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()>
|
||||
where
|
||||
@ -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>>,
|
||||
@ -208,16 +224,17 @@ where
|
||||
|
||||
let (tx, rx) = broadcast::channel(64);
|
||||
|
||||
module.spawn_controller(info, ui_tx.clone(), controller_rx)?;
|
||||
|
||||
let context = WidgetContext {
|
||||
id,
|
||||
ironbar,
|
||||
tx: ui_tx,
|
||||
update_tx: tx.clone(),
|
||||
controller_tx,
|
||||
_update_rx: rx,
|
||||
};
|
||||
|
||||
module.spawn_controller(info, &context, controller_rx)?;
|
||||
|
||||
let module_name = TModule::name();
|
||||
let instance_name = name.unwrap_or_else(|| module_name.to_string());
|
||||
|
||||
|
@ -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 {
|
||||
@ -90,7 +94,7 @@ impl Module<Button> for MusicModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
mut rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let format = self.format.clone();
|
||||
@ -98,18 +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 {
|
||||
@ -142,7 +149,6 @@ impl Module<Button> for MusicModule {
|
||||
progress_tick
|
||||
))
|
||||
),
|
||||
PlayerUpdate::Disconnect => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,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(),
|
||||
|
@ -6,7 +6,7 @@ use color_eyre::{Help, Report, Result};
|
||||
use gtk::prelude::*;
|
||||
use gtk::Label;
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::mpsc;
|
||||
use tracing::error;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
@ -55,11 +55,12 @@ impl Module<Label> for ScriptModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
_rx: Receiver<Self::ReceiveMessage>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let script: Script = self.into();
|
||||
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
script.run(None, move |out, _| match out {
|
||||
OutputStream::Stdout(stdout) => {
|
||||
|
@ -11,7 +11,6 @@ use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use sysinfo::{ComponentExt, CpuExt, DiskExt, NetworkExt, RefreshKind, System, SystemExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
@ -124,8 +123,8 @@ impl Module<gtk::Box> for SysInfoModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
_rx: Receiver<Self::ReceiveMessage>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_rx: mpsc::Receiver<Self::ReceiveMessage>,
|
||||
) -> Result<()> {
|
||||
let interval = self.interval;
|
||||
|
||||
@ -159,6 +158,7 @@ impl Module<gtk::Box> for SysInfoModule {
|
||||
spawn_refresh!(RefreshType::Network, networks);
|
||||
spawn_refresh!(RefreshType::System, system);
|
||||
|
||||
let tx = context.tx.clone();
|
||||
spawn(async move {
|
||||
let mut format_info = HashMap::new();
|
||||
|
||||
|
@ -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;
|
||||
@ -168,10 +168,13 @@ impl Module<MenuBar> for TrayModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
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;
|
||||
@ -61,12 +61,14 @@ impl Module<gtk::Button> for UpowerModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: mpsc::Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
_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");
|
||||
}
|
||||
|
||||
{
|
||||
@ -151,16 +151,14 @@ impl Module<gtk::Box> for WorkspacesModule {
|
||||
fn spawn_controller(
|
||||
&self,
|
||||
_info: &ModuleInfo,
|
||||
tx: Sender<ModuleUpdateEvent<Self::SendMessage>>,
|
||||
context: &WidgetContext<Self::SendMessage, Self::ReceiveMessage>,
|
||||
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");
|
||||
|
||||
@ -170,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…
Reference in New Issue
Block a user