Merge pull request #396 from JakeStanger/refactor/clients

Massive Client Refactor
This commit is contained in:
Jake Stanger 2024-01-09 23:39:30 +00:00 committed by GitHub
commit b55fbb3001
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1131 additions and 1118 deletions

165
Cargo.lock generated
View File

@ -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",
]

View File

@ -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

View File

@ -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: &gtk::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)
}

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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()
}
}
};
}

View File

@ -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()),
}
}

View File

@ -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> {

View File

@ -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),
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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));
}
}

View File

@ -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);

View File

@ -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!");
}
}
}

View File

@ -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);
}
}

View File

@ -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,
}

View File

@ -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>,

View File

@ -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")
),
};

View File

@ -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.");

View File

@ -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)));
}
}
}

View File

@ -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,

View 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
}

View File

@ -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))
}

View File

@ -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);

View File

@ -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();

View File

@ -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('!') {

View File

@ -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));

View File

@ -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));
});

View File

@ -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);
}
}
}

View File

@ -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();
}
}
});

View File

@ -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());

View File

@ -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(),

View File

@ -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) => {

View File

@ -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();

View File

@ -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

View File

@ -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 =

View File

@ -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)?;
}