From b188bc714614406935d8bb88a719adab2dfce32f Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Tue, 27 Sep 2022 20:24:16 +0100 Subject: [PATCH 1/6] feat: initial support for running outside sway Progress is being tracked in #18. Currently the workspaces, focused and launcher modules are not supported. --- Cargo.lock | 87 +++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 ++- src/main.rs | 18 +++-------- src/wayland.rs | 49 ++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+), 15 deletions(-) create mode 100644 src/wayland.rs diff --git a/Cargo.lock b/Cargo.lock index d31563b..33ca2ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,6 +594,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + [[package]] name = "either" version = "1.7.0" @@ -1133,6 +1139,8 @@ dependencies = [ "tracing-error", "tracing-subscriber", "walkdir", + "wayland-client", + "wayland-protocols", ] [[package]] @@ -1281,6 +1289,18 @@ dependencies = [ "memoffset", ] +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if", + "libc", + "memoffset", +] + [[package]] name = "nom" version = "7.1.1" @@ -2280,6 +2300,65 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wayland-client" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +dependencies = [ + "bitflags", + "downcast-rs", + "libc", + "nix 0.24.2", + "wayland-commons", + "wayland-scanner", + "wayland-sys", +] + +[[package]] +name = "wayland-commons" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" +dependencies = [ + "nix 0.24.2", + "once_cell", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-protocols" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +dependencies = [ + "bitflags", + "wayland-client", + "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +dependencies = [ + "proc-macro2", + "quote", + "xml-rs", +] + +[[package]] +name = "wayland-sys" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +dependencies = [ + "pkg-config", +] + [[package]] name = "wepoll-ffi" version = "0.1.2" @@ -2363,6 +2442,12 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + [[package]] name = "zbus" version = "2.3.2" @@ -2386,7 +2471,7 @@ dependencies = [ "futures-util", "hex", "lazy_static", - "nix", + "nix 0.23.1", "once_cell", "ordered-stream", "rand", diff --git a/Cargo.toml b/Cargo.toml index b4863ad..9d7d189 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,4 +35,6 @@ walkdir = "2.3.2" notify = "5.0.0" mpd_client = "1.0.0" swayipc-async = { git = "https://github.com/JakeStanger/swayipc-rs.git", branch = "feat/derive-clone" } -sysinfo = "0.26.2" \ No newline at end of file +sysinfo = "0.26.2" +wayland-client = "0.29.5" +wayland-protocols = { version = "0.29.5", features=["unstable_protocols", "client"] } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 419ea78..da391cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod modules; mod popup; mod style; mod sway; +mod wayland; use crate::bar::create_bar; use crate::config::{Config, MonitorConfig}; @@ -100,25 +101,16 @@ async fn main() -> Result<()> { /// Creates each of the bars across each of the (configured) outputs. async fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<()> { - let outputs = { - let sway = get_client().await; - let mut sway = sway.lock().await; + let outputs = wayland::get_output_names(); - let outputs = sway.get_outputs().await; - - match outputs { - Ok(outputs) => Ok(outputs), - Err(err) => Err(err), - } - }?; - - debug!("Received {} outputs from Sway IPC", outputs.len()); + debug!("Received {} outputs from Wayland", outputs.len()); + debug!("Output names: {:?}", outputs); let num_monitors = display.n_monitors(); for i in 0..num_monitors { let monitor = display.monitor(i).ok_or_else(|| Report::msg("GTK and Sway are reporting a different number of outputs - this is a severe bug and should never happen"))?; - let monitor_name = &outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?.name; + let monitor_name = outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?; info!("Creating bar on '{}'", monitor_name); diff --git a/src/wayland.rs b/src/wayland.rs new file mode 100644 index 0000000..6159038 --- /dev/null +++ b/src/wayland.rs @@ -0,0 +1,49 @@ +use std::cell::RefCell; +use std::rc::Rc; +use wayland_client::protocol::wl_output::{self, Event}; +use wayland_client::{global_filter, Display as WlDisplay, GlobalManager, Main}; + +pub fn get_output_names() -> Vec { + // Connect to the server + let display = WlDisplay::connect_to_env().unwrap(); + + let mut event_queue = display.create_event_queue(); + + let attached_display = (*display).clone().attach(event_queue.token()); + + let outputs = Rc::new(RefCell::new(Vec::::new())); + + let _globals = { + let outputs = outputs.clone(); + GlobalManager::new_with_cb(&attached_display, { + global_filter!([ + wl_output::WlOutput, + 4, + move |output: Main, _: DispatchData| { + let outputs = outputs.clone(); + + output.quick_assign(move |_, event, _| match event { + Event::Name { name: title } => { + let outputs = outputs.clone(); + outputs.as_ref().borrow_mut().push(title); + } + _ => {} + }) + } + ]) + }) + }; + + // A roundtrip synchronization to make sure the server received our registry + // creation and sent us the global list + event_queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .unwrap(); + + // for some reason we need to call this twice? + event_queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .unwrap(); + + outputs.take() +} From 324f00cdf9200e3e3ecedfa68ab4c99b170242e2 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Tue, 4 Oct 2022 23:26:07 +0100 Subject: [PATCH 2/6] feat: wlroots-agnostic support for `focused` module --- Cargo.lock | 110 ++++++++++++++++++++- Cargo.toml | 5 +- src/main.rs | 12 ++- src/modules/focused.rs | 61 +++++------- src/wayland.rs | 49 ---------- src/wayland/client.rs | 108 +++++++++++++++++++++ src/wayland/mod.rs | 54 +++++++++++ src/wayland/toplevel.rs | 126 ++++++++++++++++++++++++ src/wayland/toplevel_manager.rs | 166 ++++++++++++++++++++++++++++++++ 9 files changed, 597 insertions(+), 94 deletions(-) delete mode 100644 src/wayland.rs create mode 100644 src/wayland/client.rs create mode 100644 src/wayland/mod.rs create mode 100644 src/wayland/toplevel.rs create mode 100644 src/wayland/toplevel_manager.rs diff --git a/Cargo.lock b/Cargo.lock index 33ca2ed..8e0a49b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,19 @@ dependencies = [ "system-deps", ] +[[package]] +name = "calloop" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22a6a8f622f797120d452c630b0ab12e1331a1a753e2039ce7868d4ac77b4ee" +dependencies = [ + "log", + "nix 0.24.2", + "slotmap", + "thiserror", + "vec_map", +] + [[package]] name = "cc" version = "1.0.73" @@ -594,6 +607,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -1128,6 +1150,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", + "smithay-client-toolkit", "stray", "strip-ansi-escapes", "swayipc-async", @@ -1181,6 +1204,16 @@ version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -1215,6 +1248,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memmap2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.6.5" @@ -1740,6 +1782,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1863,12 +1911,40 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +[[package]] +name = "smithay-client-toolkit" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +dependencies = [ + "bitflags", + "calloop", + "dlib", + "lazy_static", + "log", + "memmap2", + "nix 0.24.2", + "pkg-config", + "wayland-client", + "wayland-cursor", + "wayland-protocols", +] + [[package]] name = "socket2" version = "0.4.4" @@ -2061,9 +2137,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.21.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89797afd69d206ccd11fb0ea560a44bbb87731d020670e79416d442919257d42" +checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" dependencies = [ "autocfg", "bytes", @@ -2071,7 +2147,6 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", "pin-project-lite", "socket2", "tokio-macros", @@ -2238,6 +2313,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version-compare" version = "0.1.0" @@ -2310,6 +2391,7 @@ dependencies = [ "downcast-rs", "libc", "nix 0.24.2", + "scoped-tls", "wayland-commons", "wayland-scanner", "wayland-sys", @@ -2327,6 +2409,17 @@ dependencies = [ "wayland-sys", ] +[[package]] +name = "wayland-cursor" +version = "0.29.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +dependencies = [ + "nix 0.24.2", + "wayland-client", + "xcursor", +] + [[package]] name = "wayland-protocols" version = "0.29.5" @@ -2356,6 +2449,8 @@ version = "0.29.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" dependencies = [ + "dlib", + "lazy_static", "pkg-config", ] @@ -2442,6 +2537,15 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "xcursor" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463705a63313cd4301184381c5e8042f0a7e9b4bb63653f216311d4ae74690b7" +dependencies = [ + "nom", +] + [[package]] name = "xml-rs" version = "0.8.4" diff --git a/Cargo.toml b/Cargo.toml index 9d7d189..dd909f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ derive_builder = "0.11.2" gtk = "0.15.5" gtk-layer-shell = "0.4.1" glib = "0.15.12" -tokio = { version = "1.21.0", features = ["macros", "rt-multi-thread", "time"] } +tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time"] } tracing = "0.1.36" tracing-subscriber = { version = "0.3.15", features = ["env-filter"] } tracing-error = "0.2.0" @@ -37,4 +37,5 @@ mpd_client = "1.0.0" swayipc-async = { git = "https://github.com/JakeStanger/swayipc-rs.git", branch = "feat/derive-clone" } sysinfo = "0.26.2" wayland-client = "0.29.5" -wayland-protocols = { version = "0.29.5", features=["unstable_protocols", "client"] } \ No newline at end of file +wayland-protocols = { version = "0.29.5", features=["unstable_protocols", "client"] } +smithay-client-toolkit = "0.16.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index da391cb..08a95ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,6 +28,7 @@ use tokio::task::block_in_place; use crate::logging::install_tracing; use tracing::{debug, error, info}; +use wayland::WaylandClient; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -47,6 +48,8 @@ async fn main() -> Result<()> { info!("Ironbar version {}", VERSION); info!("Starting application"); + let wayland_client = wayland::get_client().await; + let app = Application::builder() .application_id("dev.jstanger.ironbar") .build(); @@ -70,7 +73,7 @@ async fn main() -> Result<()> { }; debug!("Loaded config file"); - if let Err(err) = await_sync(create_bars(app, &display, &config)) { + if let Err(err) = await_sync(create_bars(app, &display, wayland_client, &config)) { error!("{:?}", err); exit(2); } @@ -100,8 +103,8 @@ async fn main() -> Result<()> { } /// Creates each of the bars across each of the (configured) outputs. -async fn create_bars(app: &Application, display: &Display, config: &Config) -> Result<()> { - let outputs = wayland::get_output_names(); +async fn create_bars(app: &Application, display: &Display, wl: &WaylandClient, config: &Config) -> Result<()> { + let outputs = wl.outputs.as_slice(); debug!("Received {} outputs from Wayland", outputs.len()); debug!("Output names: {:?}", outputs); @@ -110,7 +113,8 @@ async fn create_bars(app: &Application, display: &Display, config: &Config) -> R for i in 0..num_monitors { let monitor = display.monitor(i).ok_or_else(|| Report::msg("GTK and Sway are reporting a different number of outputs - this is a severe bug and should never happen"))?; - let monitor_name = outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?; + let output = outputs.get(i as usize).ok_or_else(|| Report::msg("GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"))?; + let monitor_name = &output.name; info!("Creating bar on '{}'", monitor_name); diff --git a/src/modules/focused.rs b/src/modules/focused.rs index e1cc5f7..4d5c8d6 100644 --- a/src/modules/focused.rs +++ b/src/modules/focused.rs @@ -1,16 +1,13 @@ use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; -use crate::sway::node::{get_node_id, get_open_windows}; -use crate::sway::{get_client, get_sub_client}; -use crate::{await_sync, icon}; +use crate::wayland::{ToplevelChange}; +use crate::{await_sync, icon, wayland}; use color_eyre::Result; use glib::Continue; use gtk::prelude::*; use gtk::{IconTheme, Image, Label, Orientation}; use serde::Deserialize; -use swayipc_async::WindowChange; use tokio::spawn; use tokio::sync::mpsc::{Receiver, Sender}; -use tracing::trace; #[derive(Debug, Deserialize, Clone)] pub struct FocusedModule { @@ -43,50 +40,42 @@ impl Module for FocusedModule { _rx: Receiver, ) -> Result<()> { let focused = await_sync(async { - let sway = get_client().await; - let mut sway = sway.lock().await; - get_open_windows(&mut sway) - .await - .expect("Failed to get open windows") - .into_iter() - .find(|node| node.focused) + let wl = wayland::get_client().await; + let toplevels = wl + .toplevels + .read() + .expect("Failed to get read lock on toplevels") + .clone(); + + toplevels.into_iter().find(|top| top.active) }); - if let Some(node) = focused { - let id = get_node_id(&node); - let name = node.name.as_deref().unwrap_or(id); - + if let Some(top) = focused { tx.try_send(ModuleUpdateEvent::Update(( - name.to_string(), - id.to_string(), + top.title.clone(), + top.app_id )))?; } spawn(async move { - let mut srx = { - let sway = get_sub_client(); - sway.subscribe_window() + let mut wlrx = { + let wl = wayland::get_client().await; + wl.subscribe_toplevels() }; - trace!("Set up Sway window subscription"); - - while let Ok(payload) = srx.recv().await { - let update = match payload.change { - WindowChange::Focus => true, - WindowChange::Title => payload.container.focused, - _ => false, + while let Ok(event) = wlrx.recv().await { + let update = match event.change { + ToplevelChange::Focus(focus) => focus, + ToplevelChange::Title(_) => event.toplevel.active, + _ => false }; if update { - let node = payload.container; - - let id = get_node_id(&node); - let name = node.name.as_deref().unwrap_or(id); - - tx.try_send(ModuleUpdateEvent::Update(( - name.to_string(), - id.to_string(), + tx.send(ModuleUpdateEvent::Update(( + event.toplevel.title, + event.toplevel.app_id, ))) + .await .expect("Failed to send focus update"); } } diff --git a/src/wayland.rs b/src/wayland.rs deleted file mode 100644 index 6159038..0000000 --- a/src/wayland.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; -use wayland_client::protocol::wl_output::{self, Event}; -use wayland_client::{global_filter, Display as WlDisplay, GlobalManager, Main}; - -pub fn get_output_names() -> Vec { - // Connect to the server - let display = WlDisplay::connect_to_env().unwrap(); - - let mut event_queue = display.create_event_queue(); - - let attached_display = (*display).clone().attach(event_queue.token()); - - let outputs = Rc::new(RefCell::new(Vec::::new())); - - let _globals = { - let outputs = outputs.clone(); - GlobalManager::new_with_cb(&attached_display, { - global_filter!([ - wl_output::WlOutput, - 4, - move |output: Main, _: DispatchData| { - let outputs = outputs.clone(); - - output.quick_assign(move |_, event, _| match event { - Event::Name { name: title } => { - let outputs = outputs.clone(); - outputs.as_ref().borrow_mut().push(title); - } - _ => {} - }) - } - ]) - }) - }; - - // A roundtrip synchronization to make sure the server received our registry - // creation and sent us the global list - event_queue - .sync_roundtrip(&mut (), |_, _, _| unreachable!()) - .unwrap(); - - // for some reason we need to call this twice? - event_queue - .sync_roundtrip(&mut (), |_, _, _| unreachable!()) - .unwrap(); - - outputs.take() -} diff --git a/src/wayland/client.rs b/src/wayland/client.rs new file mode 100644 index 0000000..fb6207b --- /dev/null +++ b/src/wayland/client.rs @@ -0,0 +1,108 @@ +use std::sync::{Arc, RwLock}; +use super::{Env, ToplevelHandler}; +use crate::wayland::toplevel_manager::listen_for_toplevels; +use smithay_client_toolkit::environment::Environment; +use smithay_client_toolkit::output::{with_output_info, OutputInfo}; +use smithay_client_toolkit::reexports::calloop; +use smithay_client_toolkit::{new_default_environment, WaylandSource}; +use tokio::sync::{broadcast, oneshot}; +use tokio::task::spawn_blocking; +use tracing::{trace}; +use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1; +use crate::collection::Collection; +use crate::wayland::toplevel::{ToplevelEvent, ToplevelInfo}; +use crate::wayland::ToplevelChange; + +pub struct WaylandClient { + pub outputs: Vec, + pub toplevels: Arc>>, + toplevel_tx: broadcast::Sender, + _toplevel_rx: broadcast::Receiver, +} + +impl WaylandClient { + pub(super) async fn new() -> Self { + let (output_tx, output_rx) = oneshot::channel(); + let (toplevel_tx, toplevel_rx) = broadcast::channel(32); + + let toplevel_tx2 = toplevel_tx.clone(); + + let toplevels = Arc::new(RwLock::new(Collection::new())); + let toplevels2 = toplevels.clone(); + + // `queue` is not send so we need to handle everything inside the task + spawn_blocking(move || { + let (env, _display, queue) = + new_default_environment!(Env, fields = [toplevel: ToplevelHandler::init()]) + .expect("Failed to connect to Wayland compositor"); + + let outputs = Self::get_outputs(&env); + output_tx + .send(outputs) + .expect("Failed to send outputs out of task"); + + let _toplevel_manager = env.require_global::(); + + let _listener = listen_for_toplevels(env, move |_handle, event, _ddata| { + trace!("Received toplevel event: {:?}", event); + + if event.change != ToplevelChange::Close { + toplevels2 + .write() + .expect("Failed to get write lock on toplevels") + .insert(event.toplevel.app_id.clone(), event.toplevel.clone()); + } else { + toplevels2 + .write() + .expect("Failed to get write lock on toplevels") + .remove(&event.toplevel.app_id); + } + + toplevel_tx2 + .send(event) + .expect("Failed to send toplevel event"); + }); + + let mut event_loop = calloop::EventLoop::<()>::try_new().unwrap(); + WaylandSource::new(queue) + .quick_insert(event_loop.handle()) + .unwrap(); + + loop { + event_loop.dispatch(None, &mut ()).unwrap(); + } + }); + + let outputs = output_rx + .await + .expect("Failed to receive outputs from task"); + + // spawn(async move { + // println!("start"); + // while let Ok(ev) = toplevel_rx.recv().await { + // println!("recv {:?}", ev) + // } + // println!("stop"); + // }); + + Self { + outputs, + toplevels, + toplevel_tx, + _toplevel_rx: toplevel_rx, + } + } + + pub fn subscribe_toplevels(&self) -> broadcast::Receiver { + self.toplevel_tx.subscribe() + } + + fn get_outputs(env: &Environment) -> Vec { + let outputs = env.get_all_outputs(); + + outputs + .iter() + .filter_map(|output| with_output_info(output, |info| info.clone())) + .collect() + } +} diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs new file mode 100644 index 0000000..c89ea67 --- /dev/null +++ b/src/wayland/mod.rs @@ -0,0 +1,54 @@ +mod client; +mod toplevel; +mod toplevel_manager; + +extern crate smithay_client_toolkit as sctk; + +use self::toplevel_manager::ToplevelHandler; +pub use crate::wayland::toplevel::{ToplevelChange, ToplevelEvent, ToplevelInfo}; +use crate::wayland::toplevel_manager::{ToplevelHandling, ToplevelStatusListener}; +use async_once::AsyncOnce; +use lazy_static::lazy_static; +use wayland_client::{Attached, DispatchData, Interface}; +use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ + zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, + zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, +}; + +pub use client::WaylandClient; + +/// A utility for lazy-loading globals. +/// Taken from smithay_client_toolkit where it's not exposed +#[derive(Debug)] +enum LazyGlobal { + Unknown, + Seen { id: u32, version: u32 }, + Bound(Attached), +} + +sctk::default_environment!(Env, + fields = [ + toplevel: ToplevelHandler + ], + singles = [ + ZwlrForeignToplevelManagerV1 => toplevel + ], +); + +impl ToplevelHandling for Env { + fn listen(&mut self, f: F) -> ToplevelStatusListener + where + F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static, + { + self.toplevel.listen(f) + } +} + +lazy_static! { + static ref CLIENT: AsyncOnce = + AsyncOnce::new(async { WaylandClient::new().await }); +} + +pub async fn get_client() -> &'static WaylandClient { + CLIENT.get().await +} diff --git a/src/wayland/toplevel.rs b/src/wayland/toplevel.rs new file mode 100644 index 0000000..e3d4485 --- /dev/null +++ b/src/wayland/toplevel.rs @@ -0,0 +1,126 @@ +use std::collections::HashSet; +use std::sync::{Arc, RwLock}; +use wayland_client::{DispatchData, Main}; +use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1}; + +const STATE_ACTIVE: u32 = 2; +const STATE_FULLSCREEN: u32 = 3; + +#[derive(Debug, Clone, Default)] +pub struct ToplevelInfo { + pub app_id: String, + pub title: String, + pub active: bool, + pub fullscreen: bool, + + ready: bool, +} + +pub struct Toplevel; + +#[derive(Debug, Clone)] +pub struct ToplevelEvent { + pub toplevel: ToplevelInfo, + pub change: ToplevelChange, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ToplevelChange { + New, + Close, + Title(String), + Focus(bool), + Fullscreen(bool), +} + +fn toplevel_implem(event: Event, info: &mut ToplevelInfo, implem: &mut F, ddata: DispatchData) +where + F: FnMut(ToplevelEvent, DispatchData), +{ + let change = match event { + Event::AppId { app_id } => { + info.app_id = app_id; + None + } + Event::Title { title } => { + info.title = title.clone(); + + if info.ready { + Some(ToplevelChange::Title(title)) + } else { + None + } + } + Event::State { state } => { + // state is received as a `Vec` where every 4 bytes make up a `u32` + // the u32 then represents a value in the `State` enum. + assert_eq!(state.len() % 4, 0); + + let state = (0..state.len() / 4) + .map(|i| { + let slice: [u8; 4] = state[i * 4..i * 4 + 4] + .try_into() + .expect("Received invalid state length"); + u32::from_le_bytes(slice) + }) + .collect::>(); + + let new_active = state.contains(&STATE_ACTIVE); + let new_fullscreen = state.contains(&STATE_FULLSCREEN); + + let change = if info.ready && new_active != info.active { + Some(ToplevelChange::Focus(new_active)) + } else if info.ready && new_fullscreen != info.fullscreen { + Some(ToplevelChange::Fullscreen(new_fullscreen)) + } else { + None + }; + + info.active = new_active; + info.fullscreen = new_fullscreen; + + change + } + Event::Closed => Some(ToplevelChange::Close), + Event::OutputEnter { output: _ } => None, + Event::OutputLeave { output: _ } => None, + Event::Parent { parent: _ } => None, + Event::Done => { + assert_ne!(info.app_id, ""); + if !info.ready { + info.ready = true; + Some(ToplevelChange::New) + } else { + None + } + } + _ => unreachable!(), + }; + + if let Some(change) = change { + let event = ToplevelEvent { + change, + toplevel: info.clone(), + }; + + implem(event, ddata); + } +} + +impl Toplevel { + pub fn init(handle: Main, mut callback: F) -> Self + where + F: FnMut(ToplevelEvent, DispatchData) + 'static, + { + let inner = Arc::new(RwLock::new(ToplevelInfo::default())); + + handle.quick_assign(move |_handle, event, ddata| { + let mut inner = inner + .write() + .expect("Failed to get write lock on toplevel inner state"); + toplevel_implem(event, &mut *inner, &mut callback, ddata); + }); + + Self + } +} diff --git a/src/wayland/toplevel_manager.rs b/src/wayland/toplevel_manager.rs new file mode 100644 index 0000000..ce8e9c2 --- /dev/null +++ b/src/wayland/toplevel_manager.rs @@ -0,0 +1,166 @@ +use crate::wayland::toplevel::{Toplevel, ToplevelEvent}; +use crate::wayland::LazyGlobal; +use smithay_client_toolkit::environment::{Environment, GlobalHandler}; +use std::cell::RefCell; +use std::rc; +use std::rc::Rc; +use tracing::warn; +use wayland_client::protocol::wl_registry::WlRegistry; +use wayland_client::{Attached, DispatchData}; +use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ + zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, + zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1}, +}; + + +struct ToplevelHandlerInner { + manager: LazyGlobal, + registry: Option>, + toplevels: Vec, +} + +impl ToplevelHandlerInner { + fn new() -> Self { + let toplevels = vec![]; + + Self { + registry: None, + manager: LazyGlobal::Unknown, + toplevels, + } + } +} + +pub struct ToplevelHandler { + inner: Rc>, + status_listeners: Rc>>>>, +} + +impl ToplevelHandler { + pub fn init() -> Self { + let inner = Rc::new(RefCell::new(ToplevelHandlerInner::new())); + + Self { + inner, + status_listeners: Rc::new(RefCell::new(Vec::new())), + } + } +} + +impl GlobalHandler for ToplevelHandler { + fn created( + &mut self, + registry: Attached, + id: u32, + version: u32, + _ddata: DispatchData, + ) { + let mut inner = RefCell::borrow_mut(&self.inner); + if inner.registry.is_none() { + inner.registry = Some(registry); + } + if let LazyGlobal::Unknown = inner.manager { + inner.manager = LazyGlobal::Seen { id, version } + } else { + warn!( + "Compositor advertised zwlr_foreign_toplevel_manager_v1 multiple times, ignoring." + ) + } + } + + fn get(&self) -> Option> { + let mut inner = RefCell::borrow_mut(&self.inner); + + match inner.manager { + LazyGlobal::Bound(ref mgr) => Some(mgr.clone()), + LazyGlobal::Unknown => None, + LazyGlobal::Seen { id, version } => { + let registry = inner.registry.as_ref().expect("Failed to get registry"); + // current max protocol version = 3 + let version = std::cmp::min(version, 3); + let manager = registry.bind::(version, id); + + { + let inner = self.inner.clone(); + let status_listeners = self.status_listeners.clone(); + + manager.quick_assign(move |_, event, _ddata| { + let mut inner = RefCell::borrow_mut(&inner); + let status_listeners = status_listeners.clone(); + + match event { + zwlr_foreign_toplevel_manager_v1::Event::Toplevel { + toplevel: handle, + } => { + let toplevel = Toplevel::init(handle.clone(), move |event, ddata| { + notify_status_listeners( + &handle, + event, + ddata, + &status_listeners, + ); + }); + + inner.toplevels.push(toplevel); + } + zwlr_foreign_toplevel_manager_v1::Event::Finished => {} + _ => unreachable!(), + } + }); + } + + inner.manager = LazyGlobal::Bound((*manager).clone()); + Some((*manager).clone()) + } + } + } +} + +type ToplevelStatusCallback = + dyn FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static; + +/// Notifies the callbacks of an event on the toplevel +fn notify_status_listeners( + toplevel: &ZwlrForeignToplevelHandleV1, + event: ToplevelEvent, + mut ddata: DispatchData, + listeners: &RefCell>>>, +) { + listeners.borrow_mut().retain(|lst| { + if let Some(cb) = rc::Weak::upgrade(lst) { + (cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow()); + true + } else { + false + } + }) +} + +pub struct ToplevelStatusListener { + _cb: Rc>, +} + +pub trait ToplevelHandling { + fn listen(&mut self, f: F) -> ToplevelStatusListener + where + F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static; +} + +impl ToplevelHandling for ToplevelHandler { + fn listen(&mut self, f: F) -> ToplevelStatusListener + where + F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static, + { + let rc = Rc::new(RefCell::new(f)) as Rc<_>; + self.status_listeners.borrow_mut().push(Rc::downgrade(&rc)); + ToplevelStatusListener { _cb: rc } + } +} + +pub fn listen_for_toplevels(env: Environment, f: F) -> ToplevelStatusListener +where + E: ToplevelHandling, + F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static, +{ + env.with_inner(move |inner| ToplevelHandling::listen(inner, f)) +} From bb4fe7f7f58fa2a6d0a2259bd9442700d2c884f7 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Tue, 4 Oct 2022 23:26:26 +0100 Subject: [PATCH 3/6] docs(readme): credit smithay client toolkit Could not have got the Wayland stuff working without. --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b5a080f..caff548 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Ironbar -Ironbar is a customisable and feature-rich bar targeting the Sway compositor, written in Rust. +Ironbar is a customisable and feature-rich bar targeting the Sway compositor, written in Rust. It uses GTK3 and gtk-layer-shell. -The bar can be styled to your liking using CSS and hot-loads style changes. +The bar can be styled to your liking using CSS and hot-loads style changes. For information and examples on styling please see the [wiki](https://github.com/JakeStanger/ironbar/wiki). ![Screenshot of fully configured bar with MPD widget open](https://user-images.githubusercontent.com/5057870/184539623-92d56a44-a659-49a9-91f9-5cdc453e5dfb.png) @@ -42,29 +42,30 @@ install target/release/ironbar ~/.local/bin/ironbar ## Configuration -Ironbar gives a lot of flexibility when configuring, including multiple file formats -and options for scaling complexity: you can use a single config across all monitors, -or configure different/multiple bars per monitor. +Ironbar gives a lot of flexibility when configuring, including multiple file formats +and options for scaling complexity: you can use a single config across all monitors, +or configure different/multiple bars per monitor. A full configuration guide can be found [here](https://github.com/JakeStanger/ironbar/wiki/configuration-guide). ## Styling -To get started, create a stylesheet at `.config/ironbar/style.css`. Changes will be hot-reloaded every time you save the file. +To get started, create a stylesheet at `.config/ironbar/style.css`. Changes will be hot-reloaded every time you save the +file. A full styling guide can be found [here](https://github.com/JakeStanger/ironbar/wiki/styling-guide). ## Project Status -This project is in alpha, but should be usable. -Everything that is implemented works and should be documented. +This project is in alpha, but should be usable. +Everything that is implemented works and should be documented. Proper error handling is in place so things should either fail gracefully with detail, or not fail at all. There is currently room for lots more modules, and lots more configuration options for the existing modules. The current configuration schema is not set in stone and breaking changes could come along at any point; until the project matures I am more interested in ease of use than backwards compatibility. -A few bugs do exist, and I am sure there are plenty more to be found. +A few bugs do exist, and I am sure there are plenty more to be found. The project will be *actively developed* as I am using it on my daily driver. Bugs will be fixed, features will be added, code will be refactored. @@ -77,3 +78,4 @@ Please check [here](https://github.com/JakeStanger/ironbar/blob/master/CONTRIBUT - [Waybar](https://github.com/Alexays/Waybar) - A lot of the initial inspiration, and a pretty great bar. - [Rustbar](https://github.com/zeroeightysix/rustbar) - Served as a good demo for writing a basic GTK bar in Rust +- [Smithay Client Toolkit](https://github.com/Smithay/client-toolkit) - Essential in being able to communicate to Wayland \ No newline at end of file From b1c66b9117cf8a10350cdb857a5267a1a72ad914 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Mon, 10 Oct 2022 20:15:24 +0100 Subject: [PATCH 4/6] feat: wlroots-agnostic support for `launcher` module --- src/main.rs | 1 - src/modules/focused.rs | 4 +- src/modules/launcher/item.rs | 89 +++-------- src/modules/launcher/mod.rs | 238 +++++++++++++---------------- src/modules/launcher/open_state.rs | 30 +--- src/wayland/client.rs | 43 +++--- src/wayland/toplevel.rs | 14 +- 7 files changed, 172 insertions(+), 247 deletions(-) diff --git a/src/main.rs b/src/main.rs index 08a95ca..113f1e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ mod wayland; use crate::bar::create_bar; use crate::config::{Config, MonitorConfig}; use crate::style::load_css; -use crate::sway::get_client; use color_eyre::eyre::Result; use color_eyre::Report; use dirs::config_dir; diff --git a/src/modules/focused.rs b/src/modules/focused.rs index 4d5c8d6..2f4a6bc 100644 --- a/src/modules/focused.rs +++ b/src/modules/focused.rs @@ -47,10 +47,10 @@ impl Module for FocusedModule { .expect("Failed to get read lock on toplevels") .clone(); - toplevels.into_iter().find(|top| top.active) + toplevels.into_iter().find(|(top, _)| top.active) }); - if let Some(top) = focused { + if let Some((top, _)) = focused { tx.try_send(ModuleUpdateEvent::Update(( top.title.clone(), top.app_id diff --git a/src/modules/launcher/item.rs b/src/modules/launcher/item.rs index 99964a2..7986982 100644 --- a/src/modules/launcher/item.rs +++ b/src/modules/launcher/item.rs @@ -4,22 +4,20 @@ use crate::icon::get_icon; use crate::modules::launcher::{ItemEvent, LauncherUpdate}; use crate::modules::ModuleUpdateEvent; use crate::popup::Popup; -use crate::sway::node::{get_node_id, is_node_xwayland}; use gtk::prelude::*; use gtk::{Button, IconTheme, Image}; use std::rc::Rc; use std::sync::RwLock; -use swayipc_async::Node; use tokio::sync::mpsc::Sender; +use crate::wayland::ToplevelInfo; #[derive(Debug, Clone)] pub struct Item { pub app_id: String, pub favorite: bool, pub open_state: OpenState, - pub windows: Collection, - pub name: Option, - pub is_xwayland: bool, + pub windows: Collection, + pub name: String, } impl Item { @@ -29,21 +27,18 @@ impl Item { favorite, open_state, windows: Collection::new(), - name: None, - is_xwayland: false, + name: String::new(), } } /// Merges the provided node into this launcher item - pub fn merge_node(&mut self, node: Node) -> Window { + pub fn merge_toplevel(&mut self, node: ToplevelInfo) -> Window { let id = node.id; if self.windows.is_empty() { - self.name = node.name.clone(); + self.name = node.title.clone(); } - self.is_xwayland = self.is_xwayland || is_node_xwayland(&node); - let window: Window = node.into(); self.windows.insert(id, window.clone()); @@ -52,16 +47,12 @@ impl Item { window } - pub fn unmerge_node(&mut self, node: &Node) { + pub fn unmerge_toplevel(&mut self, node: &ToplevelInfo) { self.windows.remove(&node.id); self.recalculate_open_state(); } - pub fn get_name(&self) -> &str { - self.name.as_ref().unwrap_or(&self.app_id) - } - - pub fn set_window_name(&mut self, window_id: i64, name: Option) { + pub fn set_window_name(&mut self, window_id: usize, name: String) { if let Some(window) = self.windows.get_mut(&window_id) { if let OpenState::Open { focused: true, .. } = window.open_state { self.name = name.clone(); @@ -71,23 +62,7 @@ impl Item { } } - pub fn set_unfocused(&mut self) { - let focused = self - .windows - .iter_mut() - .find(|window| window.open_state.is_focused()); - - if let Some(focused) = focused { - focused.open_state = OpenState::Open { - focused: false, - urgent: focused.open_state.is_urgent(), - }; - - self.recalculate_open_state(); - } - } - - pub fn set_window_focused(&mut self, window_id: i64, focused: bool) { + pub fn set_window_focused(&mut self, window_id: usize, focused: bool) { if let Some(window) = self.windows.get_mut(&window_id) { window.open_state = OpenState::merge_states(&[&window.open_state, &OpenState::focused(focused)]); @@ -96,15 +71,6 @@ impl Item { } } - pub fn set_window_urgent(&mut self, window_id: i64, urgent: bool) { - if let Some(window) = self.windows.get_mut(&window_id) { - window.open_state = - OpenState::merge_states(&[&window.open_state, &OpenState::urgent(urgent)]); - - self.recalculate_open_state(); - } - } - /// Sets this item's open state /// to the merged result of its windows' open states fn recalculate_open_state(&mut self) { @@ -119,16 +85,14 @@ impl Item { } } -impl From for Item { - fn from(node: Node) -> Self { - let app_id = get_node_id(&node).to_string(); - let open_state = OpenState::from_node(&node); - let name = node.name.clone(); - - let is_xwayland = is_node_xwayland(&node); +impl From for Item { + fn from(toplevel: ToplevelInfo) -> Self { + let open_state = OpenState::from_toplevel(&toplevel); + let name = toplevel.title.clone(); + let app_id = toplevel.app_id.clone(); let mut windows = Collection::new(); - windows.insert(node.id, node.into()); + windows.insert(toplevel.id, toplevel.into()); Self { app_id, @@ -136,25 +100,24 @@ impl From for Item { open_state, windows, name, - is_xwayland, } } } #[derive(Clone, Debug)] pub struct Window { - pub id: i64, - pub name: Option, + pub id: usize, + pub name: String, pub open_state: OpenState, } -impl From for Window { - fn from(node: Node) -> Self { - let open_state = OpenState::from_node(&node); +impl From for Window { + fn from(node: ToplevelInfo) -> Self { + let open_state = OpenState::from_toplevel(&node); Self { id: node.id, - name: node.name, + name: node.title, open_state, } } @@ -183,7 +146,7 @@ impl ItemButton { let mut button = Button::builder(); if show_names { - button = button.label(item.get_name()); + button = button.label(&item.name); } if show_icons { @@ -208,9 +171,6 @@ impl ItemButton { if item.open_state.is_focused() { style_context.add_class("focused"); } - if item.open_state.is_urgent() { - style_context.add_class("urgent"); - } { let app_id = item.app_id.clone(); @@ -274,7 +234,6 @@ impl ItemButton { if !open { self.set_focused(false); - self.set_urgent(false); } } @@ -282,10 +241,6 @@ impl ItemButton { self.update_class("focused", focused); } - pub fn set_urgent(&self, urgent: bool) { - self.update_class("urgent", urgent); - } - /// Adds or removes a class to the button based on `toggle`. fn update_class(&self, class: &str, toggle: bool) { let style_context = self.button.style_context(); diff --git a/src/modules/launcher/mod.rs b/src/modules/launcher/mod.rs index a14b563..61d8486 100644 --- a/src/modules/launcher/mod.rs +++ b/src/modules/launcher/mod.rs @@ -1,14 +1,13 @@ mod item; mod open_state; +use self::item::{Item, ItemButton, Window}; +use self::open_state::OpenState; use crate::collection::Collection; use crate::icon::find_desktop_file; -use crate::modules::launcher::item::{Item, ItemButton, Window}; -use crate::modules::launcher::open_state::OpenState; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; -use crate::sway::get_sub_client; -use crate::sway::node::{get_node_id, get_open_windows}; -use crate::{await_sync, get_client}; +use crate::wayland; +use crate::wayland::ToplevelChange; use color_eyre::{Help, Report}; use glib::Continue; use gtk::prelude::*; @@ -16,7 +15,6 @@ use gtk::{Button, IconTheme, Orientation}; use serde::Deserialize; use std::process::{Command, Stdio}; use std::sync::{Arc, Mutex}; -use swayipc_async::WindowChange; use tokio::spawn; use tokio::sync::mpsc; use tokio::sync::mpsc::{Receiver, Sender}; @@ -47,13 +45,11 @@ pub enum LauncherUpdate { /// Removes item with `app_id` RemoveItem(String), /// Removes window from item with `app_id`. - RemoveWindow(String, i64), + RemoveWindow(String, usize), /// Sets title for `app_id` - Title(String, i64, Option), - /// Focuses first `app_id`, unfocuses second `app_id` (if present) - Focus(String, Option), - /// Marks the item with `app_id` as urgent or not urgent - Urgent(String, bool), + Title(String, usize, String), + /// Marks the item with `app_id` as focused or not focused + Focus(String, bool), /// Declares the item with `app_id` has been hovered over Hover(String), } @@ -61,7 +57,7 @@ pub enum LauncherUpdate { #[derive(Debug)] pub enum ItemEvent { FocusItem(String), - FocusWindow(i64), + FocusWindow(usize), OpenItem(String), } @@ -100,69 +96,73 @@ impl Module for LauncherModule { let items = Arc::new(Mutex::new(items)); - let open_windows = await_sync(async { - let sway = get_client().await; - let mut sway = sway.lock().await; - get_open_windows(&mut sway).await - })?; - { - let mut items = items.lock().expect("Failed to get lock on items"); - for window in open_windows { - let id = get_node_id(&window).to_string(); + let items = Arc::clone(&items); + let tx = tx.clone(); + spawn(async move { + let wl = wayland::get_client().await; + let open_windows = wl + .toplevels + .read() + .expect("Failed to get read lock on toplevels"); - let item = items.get_mut(&id); - match item { - Some(item) => { - item.merge_node(window); - } - None => { - items.insert(id, window.into()); + let mut items = items.lock().expect("Failed to get lock on items"); + + for (window, _) in open_windows.clone().into_iter() { + let item = items.get_mut(&window.app_id); + match item { + Some(item) => { + item.merge_toplevel(window); + } + None => { + items.insert(window.app_id.clone(), window.into()); + } } } - } - let items = items.iter(); - for item in items { - tx.try_send(ModuleUpdateEvent::Update(LauncherUpdate::AddItem( - item.clone(), - )))?; - } + let items = items.iter(); + for item in items { + tx.try_send(ModuleUpdateEvent::Update(LauncherUpdate::AddItem( + item.clone(), + )))?; + } + + Ok::<(), Report>(()) + }); } let items2 = Arc::clone(&items); spawn(async move { let items = items2; - let mut srx = { - let sway = get_sub_client(); - sway.subscribe_window() + let mut wlrx = { + let wl = wayland::get_client().await; + wl.subscribe_toplevels() }; - while let Ok(event) = srx.recv().await { + let send_update = |update: LauncherUpdate| tx.send(ModuleUpdateEvent::Update(update)); + + while let Ok(event) = wlrx.recv().await { trace!("event: {:?}", event); - let window = event.container; - let id = get_node_id(&window).to_string(); - - let send_update = - |update: LauncherUpdate| tx.send(ModuleUpdateEvent::Update(update)); + let window = event.toplevel; + let app_id = window.app_id.clone(); let items = || items.lock().expect("Failed to get lock on items"); match event.change { - WindowChange::New => { + ToplevelChange::New => { let new_item = { let mut items = items(); - match items.get_mut(&id) { + match items.get_mut(&app_id) { None => { let item: Item = window.into(); - items.insert(id.clone(), item.clone()); + items.insert(app_id.clone(), item.clone()); ItemOrWindow::Item(item) } Some(item) => { - let window = item.merge_node(window); + let window = item.merge_toplevel(window); ItemOrWindow::Window(window) } } @@ -173,19 +173,19 @@ impl Module for LauncherModule { send_update(LauncherUpdate::AddItem(item)).await } ItemOrWindow::Window(window) => { - send_update(LauncherUpdate::AddWindow(id, window)).await + send_update(LauncherUpdate::AddWindow(app_id, window)).await } }?; } - WindowChange::Close => { + ToplevelChange::Close => { let remove_item = { let mut items = items(); - match items.get_mut(&id) { + match items.get_mut(&app_id) { Some(item) => { - item.unmerge_node(&window); + item.unmerge_toplevel(&window); if item.windows.is_empty() { - items.remove(&id); + items.remove(&app_id); Some(ItemOrWindowId::Item) } else { Some(ItemOrWindowId::Window) @@ -197,58 +197,47 @@ impl Module for LauncherModule { match remove_item { Some(ItemOrWindowId::Item) => { - send_update(LauncherUpdate::RemoveItem(id)).await?; + send_update(LauncherUpdate::RemoveItem(app_id)).await?; } Some(ItemOrWindowId::Window) => { - send_update(LauncherUpdate::RemoveWindow(id, window.id)).await?; + send_update(LauncherUpdate::RemoveWindow(app_id, window.id)) + .await?; } None => {} }; } - WindowChange::Focus => { - let prev_id = { - let mut items = items(); + ToplevelChange::Focus(focused) => { + let update_title = if focused { + if let Some(item) = items().get_mut(&app_id) { + item.set_window_focused(window.id, true); - let prev_focused = - items.iter_mut().find(|item| item.open_state.is_focused()); - if let Some(prev_focused) = prev_focused { - prev_focused.set_unfocused(); - Some(prev_focused.app_id.to_string()) + // might be switching focus between windows of same app + if item.windows.len() > 1 { + item.set_window_name(window.id, window.title.clone()); + true + } else { + false + } } else { - None + false } + } else { + false }; - let mut update_title = false; - if let Some(item) = items().get_mut(&id) { - item.set_window_focused(window.id, true); - - // might be switching focus between windows of same app - if item.windows.len() > 1 { - item.set_window_name(window.id, window.name.clone()); - update_title = true; - } - } - - send_update(LauncherUpdate::Focus(id.clone(), prev_id)).await?; + send_update(LauncherUpdate::Focus(app_id.clone(), focused)).await?; if update_title { - send_update(LauncherUpdate::Title(id, window.id, window.name)).await?; + send_update(LauncherUpdate::Title(app_id, window.id, window.title)) + .await?; } } - WindowChange::Title => { - if let Some(item) = items().get_mut(&id) { - item.set_window_name(window.id, window.name.clone()); + ToplevelChange::Title(title) => { + if let Some(item) = items().get_mut(&app_id) { + item.set_window_name(window.id, title.clone()); } - send_update(LauncherUpdate::Title(id, window.id, window.name)).await?; - } - WindowChange::Urgent => { - if let Some(item) = items().get_mut(&id) { - item.set_window_urgent(window.id, window.urgent); - } - - send_update(LauncherUpdate::Urgent(id, window.urgent)).await?; + send_update(LauncherUpdate::Title(app_id, window.id, title)).await?; } _ => {} } @@ -259,8 +248,6 @@ impl Module for LauncherModule { // listen to ui events spawn(async move { - let sway = get_client().await; - while let Some(event) = rx.recv().await { trace!("{:?}", event); @@ -287,25 +274,26 @@ impl Module for LauncherModule { }, ); } else { - let selector = { - let items = items.lock().expect("Failed to get lock on items"); + let wl = wayland::get_client().await; + let items = items.lock().expect("Failed to get lock on items"); - match event { - ItemEvent::FocusItem(app_id) => items.get(&app_id).map(|item| { - if item.is_xwayland { - format!("[class={}]", app_id) - } else { - format!("[app_id={}]", app_id) - } - }), - ItemEvent::FocusWindow(con_id) => Some(format!("[con_id={}]", con_id)), - ItemEvent::OpenItem(_) => unreachable!(), - } + let id = match event { + ItemEvent::FocusItem(app_id) => items + .get(&app_id) + .and_then(|item| item.windows.first().map(|win| win.id)), + ItemEvent::FocusWindow(id) => Some(id), + ItemEvent::OpenItem(_) => unreachable!(), }; - if let Some(selector) = selector { - let mut sway = sway.lock().await; - sway.run_command(format!("{} focus", selector)).await?; + if let Some(id) = id { + let toplevels = wl + .toplevels + .read() + .expect("Failed to get read lock on toplevels"); + let seat = wl.seats.first().unwrap(); + if let Some((_top, handle)) = toplevels.get(&id) { + handle.activate(seat); + }; } } } @@ -391,20 +379,11 @@ impl Module for LauncherModule { menu_state.num_windows -= 1; } } - LauncherUpdate::Focus(new, prev) => { - debug!( - "Changing focus to item with id {} (removing from {:?})", - new, prev - ); + LauncherUpdate::Focus(app_id, focus) => { + debug!("Changing focus to {} on item with id {}", focus, app_id); - if let Some(prev) = prev { - if let Some(button) = buttons.get(&prev) { - button.set_focused(false); - } - } - - if let Some(button) = buttons.get(&new) { - button.set_focused(true); + if let Some(button) = buttons.get(&app_id) { + button.set_focused(focus); } } LauncherUpdate::Title(app_id, _, name) => { @@ -412,17 +391,10 @@ impl Module for LauncherModule { if show_names { if let Some(button) = buttons.get(&app_id) { - button.button.set_label(&name.unwrap_or_default()); + button.button.set_label(&name); } } } - LauncherUpdate::Urgent(app_id, urgent) => { - debug!("Updating urgency for item with id {}: {}", app_id, urgent); - - if let Some(button) = buttons.get(&app_id) { - button.set_urgent(urgent); - } - } LauncherUpdate::Hover(_) => {} }; @@ -444,7 +416,7 @@ impl Module for LauncherModule { ) -> Option { let container = gtk::Box::new(Orientation::Vertical, 0); - let mut buttons = Collection::>::new(); + let mut buttons = Collection::>::new(); { let container = container.clone(); @@ -458,7 +430,7 @@ impl Module for LauncherModule { .into_iter() .map(|win| { let button = Button::builder() - .label(win.name.as_ref().unwrap_or(&String::new())) + .label(&win.name) .height_request(40) .width_request(100) .build(); @@ -484,7 +456,7 @@ impl Module for LauncherModule { LauncherUpdate::AddWindow(app_id, win) => { if let Some(buttons) = buttons.get_mut(&app_id) { let button = Button::builder() - .label(win.name.as_ref().unwrap_or(&String::new())) + .label(&win.name) .height_request(40) .width_request(100) .build(); @@ -512,9 +484,7 @@ impl Module for LauncherModule { LauncherUpdate::Title(app_id, win_id, title) => { if let Some(buttons) = buttons.get_mut(&app_id) { if let Some(button) = buttons.get(&win_id) { - if let Some(title) = title { - button.set_label(&title); - } + button.set_label(&title); } } } diff --git a/src/modules/launcher/open_state.rs b/src/modules/launcher/open_state.rs index cabd7c1..e355059 100644 --- a/src/modules/launcher/open_state.rs +++ b/src/modules/launcher/open_state.rs @@ -1,35 +1,23 @@ -use swayipc_async::Node; +use crate::wayland::ToplevelInfo; /// Open state for a launcher item, or item window. #[derive(Debug, Clone, Eq, PartialEq, Copy)] pub enum OpenState { Closed, - Open { focused: bool, urgent: bool }, + Open { focused: bool }, } impl OpenState { /// Creates from `SwayNode` - pub const fn from_node(node: &Node) -> Self { + pub const fn from_toplevel(toplevel: &ToplevelInfo) -> Self { Self::Open { - focused: node.focused, - urgent: node.urgent, + focused: toplevel.active, } } /// Creates open with focused pub const fn focused(focused: bool) -> Self { - Self::Open { - focused, - urgent: false, - } - } - - /// Creates open with urgent - pub const fn urgent(urgent: bool) -> Self { - Self::Open { - focused: false, - urgent, - } + Self::Open { focused } } /// Checks if open @@ -39,12 +27,7 @@ impl OpenState { /// Checks if open with focus pub const fn is_focused(self) -> bool { - matches!(self, Self::Open { focused: true, .. }) - } - - /// check if open with urgent - pub const fn is_urgent(self) -> bool { - matches!(self, Self::Open { urgent: true, .. }) + matches!(self, Self::Open { focused: true }) } /// Merges states together to produce a single state. @@ -56,7 +39,6 @@ impl OpenState { if merged.is_open() || current.is_open() { Self::Open { focused: merged.is_focused() || current.is_focused(), - urgent: merged.is_urgent() || current.is_urgent(), } } else { Self::Closed diff --git a/src/wayland/client.rs b/src/wayland/client.rs index fb6207b..36d05f4 100644 --- a/src/wayland/client.rs +++ b/src/wayland/client.rs @@ -1,21 +1,27 @@ -use std::sync::{Arc, RwLock}; use super::{Env, ToplevelHandler}; +use crate::collection::Collection; +use crate::wayland::toplevel::{ToplevelEvent, ToplevelInfo}; use crate::wayland::toplevel_manager::listen_for_toplevels; +use crate::wayland::ToplevelChange; use smithay_client_toolkit::environment::Environment; use smithay_client_toolkit::output::{with_output_info, OutputInfo}; use smithay_client_toolkit::reexports::calloop; use smithay_client_toolkit::{new_default_environment, WaylandSource}; +use std::sync::{Arc, RwLock}; +use std::time::Duration; use tokio::sync::{broadcast, oneshot}; use tokio::task::spawn_blocking; -use tracing::{trace}; -use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1; -use crate::collection::Collection; -use crate::wayland::toplevel::{ToplevelEvent, ToplevelInfo}; -use crate::wayland::ToplevelChange; +use tracing::trace; +use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ + zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, + zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, +}; +use wayland_client::protocol::wl_seat::WlSeat; pub struct WaylandClient { pub outputs: Vec, - pub toplevels: Arc>>, + pub seats: Vec, + pub toplevels: Arc>>, toplevel_tx: broadcast::Sender, _toplevel_rx: broadcast::Receiver, } @@ -23,6 +29,7 @@ pub struct WaylandClient { impl WaylandClient { pub(super) async fn new() -> Self { let (output_tx, output_rx) = oneshot::channel(); + let (seat_tx, seat_rx) = oneshot::channel(); let (toplevel_tx, toplevel_rx) = broadcast::channel(32); let toplevel_tx2 = toplevel_tx.clone(); @@ -41,21 +48,24 @@ impl WaylandClient { .send(outputs) .expect("Failed to send outputs out of task"); + let seats = env.get_all_seats(); + seat_tx.send(seats.into_iter().map(|seat| seat.detach()).collect::>()).expect("Failed to send seats out of task"); + let _toplevel_manager = env.require_global::(); - let _listener = listen_for_toplevels(env, move |_handle, event, _ddata| { + let _listener = listen_for_toplevels(env, move |handle, event, _ddata| { trace!("Received toplevel event: {:?}", event); if event.change != ToplevelChange::Close { toplevels2 .write() .expect("Failed to get write lock on toplevels") - .insert(event.toplevel.app_id.clone(), event.toplevel.clone()); + .insert(event.toplevel.id, (event.toplevel.clone(), handle)); } else { toplevels2 .write() .expect("Failed to get write lock on toplevels") - .remove(&event.toplevel.app_id); + .remove(&event.toplevel.id); } toplevel_tx2 @@ -69,7 +79,9 @@ impl WaylandClient { .unwrap(); loop { - event_loop.dispatch(None, &mut ()).unwrap(); + // TODO: Avoid need for duration here - can we force some event when sending requests? + event_loop.dispatch(Duration::from_millis(50), &mut ()).unwrap(); + event_loop. } }); @@ -77,16 +89,11 @@ impl WaylandClient { .await .expect("Failed to receive outputs from task"); - // spawn(async move { - // println!("start"); - // while let Ok(ev) = toplevel_rx.recv().await { - // println!("recv {:?}", ev) - // } - // println!("stop"); - // }); + let seats = seat_rx.await.expect("Failed to receive seats from task"); Self { outputs, + seats, toplevels, toplevel_tx, _toplevel_rx: toplevel_rx, diff --git a/src/wayland/toplevel.rs b/src/wayland/toplevel.rs index e3d4485..0117ebb 100644 --- a/src/wayland/toplevel.rs +++ b/src/wayland/toplevel.rs @@ -1,13 +1,18 @@ use std::collections::HashSet; use std::sync::{Arc, RwLock}; +use std::sync::atomic::{AtomicUsize, Ordering}; use wayland_client::{DispatchData, Main}; use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1}; const STATE_ACTIVE: u32 = 2; const STATE_FULLSCREEN: u32 = 3; +static COUNTER: AtomicUsize = AtomicUsize::new(1); +fn get_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } + #[derive(Debug, Clone, Default)] pub struct ToplevelInfo { + pub id: usize, pub app_id: String, pub title: String, pub active: bool, @@ -16,6 +21,13 @@ pub struct ToplevelInfo { ready: bool, } +impl ToplevelInfo { + fn new() -> Self { + let id = get_id(); + Self { id, ..Default::default() } + } +} + pub struct Toplevel; #[derive(Debug, Clone)] @@ -112,7 +124,7 @@ impl Toplevel { where F: FnMut(ToplevelEvent, DispatchData) + 'static, { - let inner = Arc::new(RwLock::new(ToplevelInfo::default())); + let inner = Arc::new(RwLock::new(ToplevelInfo::new())); handle.quick_assign(move |_handle, event, ddata| { let mut inner = inner From 5ce50b0987812a1ade2d1262e8d7df6916cfc39a Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Mon, 10 Oct 2022 21:59:44 +0100 Subject: [PATCH 5/6] refactor: tidy and format --- src/main.rs | 9 ++++-- src/modules/focused.rs | 9 ++---- src/modules/launcher/item.rs | 2 +- src/modules/launcher/mod.rs | 12 ++++---- src/{sway/mod.rs => sway.rs} | 28 +++--------------- src/sway/node.rs | 50 --------------------------------- src/wayland/client.rs | 36 +++++++++++++++--------- src/wayland/mod.rs | 2 +- src/wayland/toplevel.rs | 19 ++++++++----- src/wayland/toplevel_manager.rs | 32 ++++++++++----------- 10 files changed, 73 insertions(+), 126 deletions(-) rename src/{sway/mod.rs => sway.rs} (67%) delete mode 100644 src/sway/node.rs diff --git a/src/main.rs b/src/main.rs index 113f1e3..108013d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,7 +72,7 @@ async fn main() -> Result<()> { }; debug!("Loaded config file"); - if let Err(err) = await_sync(create_bars(app, &display, wayland_client, &config)) { + if let Err(err) = create_bars(app, &display, wayland_client, &config) { error!("{:?}", err); exit(2); } @@ -102,7 +102,12 @@ async fn main() -> Result<()> { } /// Creates each of the bars across each of the (configured) outputs. -async fn create_bars(app: &Application, display: &Display, wl: &WaylandClient, config: &Config) -> Result<()> { +fn create_bars( + app: &Application, + display: &Display, + wl: &WaylandClient, + config: &Config, +) -> Result<()> { let outputs = wl.outputs.as_slice(); debug!("Received {} outputs from Wayland", outputs.len()); diff --git a/src/modules/focused.rs b/src/modules/focused.rs index 2f4a6bc..39e577d 100644 --- a/src/modules/focused.rs +++ b/src/modules/focused.rs @@ -1,5 +1,5 @@ use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; -use crate::wayland::{ToplevelChange}; +use crate::wayland::ToplevelChange; use crate::{await_sync, icon, wayland}; use color_eyre::Result; use glib::Continue; @@ -51,10 +51,7 @@ impl Module for FocusedModule { }); if let Some((top, _)) = focused { - tx.try_send(ModuleUpdateEvent::Update(( - top.title.clone(), - top.app_id - )))?; + tx.try_send(ModuleUpdateEvent::Update((top.title.clone(), top.app_id)))?; } spawn(async move { @@ -67,7 +64,7 @@ impl Module for FocusedModule { let update = match event.change { ToplevelChange::Focus(focus) => focus, ToplevelChange::Title(_) => event.toplevel.active, - _ => false + _ => false, }; if update { diff --git a/src/modules/launcher/item.rs b/src/modules/launcher/item.rs index 7986982..0e4fdd9 100644 --- a/src/modules/launcher/item.rs +++ b/src/modules/launcher/item.rs @@ -4,12 +4,12 @@ use crate::icon::get_icon; use crate::modules::launcher::{ItemEvent, LauncherUpdate}; use crate::modules::ModuleUpdateEvent; use crate::popup::Popup; +use crate::wayland::ToplevelInfo; use gtk::prelude::*; use gtk::{Button, IconTheme, Image}; use std::rc::Rc; use std::sync::RwLock; use tokio::sync::mpsc::Sender; -use crate::wayland::ToplevelInfo; #[derive(Debug, Clone)] pub struct Item { diff --git a/src/modules/launcher/mod.rs b/src/modules/launcher/mod.rs index 61d8486..5070bb8 100644 --- a/src/modules/launcher/mod.rs +++ b/src/modules/launcher/mod.rs @@ -108,7 +108,7 @@ impl Module for LauncherModule { let mut items = items.lock().expect("Failed to get lock on items"); - for (window, _) in open_windows.clone().into_iter() { + for (window, _) in open_windows.clone() { let item = items.get_mut(&window.app_id); match item { Some(item) => { @@ -154,7 +154,8 @@ impl Module for LauncherModule { ToplevelChange::New => { let new_item = { let mut items = items(); - match items.get_mut(&app_id) { + let item = items.get_mut(&app_id); + match item { None => { let item: Item = window.into(); items.insert(app_id.clone(), item.clone()); @@ -180,7 +181,8 @@ impl Module for LauncherModule { ToplevelChange::Close => { let remove_item = { let mut items = items(); - match items.get_mut(&app_id) { + let item = items.get_mut(&app_id); + match item { Some(item) => { item.unmerge_toplevel(&window); @@ -239,7 +241,7 @@ impl Module for LauncherModule { send_update(LauncherUpdate::Title(app_id, window.id, title)).await?; } - _ => {} + ToplevelChange::Fullscreen(_) => {} } } @@ -290,7 +292,7 @@ impl Module for LauncherModule { .toplevels .read() .expect("Failed to get read lock on toplevels"); - let seat = wl.seats.first().unwrap(); + let seat = wl.seats.first().expect("Failed to get Wayland seat"); if let Some((_top, handle)) = toplevels.get(&id) { handle.activate(seat); }; diff --git a/src/sway/mod.rs b/src/sway.rs similarity index 67% rename from src/sway/mod.rs rename to src/sway.rs index e1c4f74..073ba47 100644 --- a/src/sway/mod.rs +++ b/src/sway.rs @@ -3,50 +3,37 @@ use color_eyre::Report; use futures_util::StreamExt; use lazy_static::lazy_static; use std::sync::Arc; -use swayipc_async::{Connection, Event, EventType, WindowEvent, WorkspaceEvent}; +use swayipc_async::{Connection, Event, EventType, WorkspaceEvent}; use tokio::spawn; use tokio::sync::broadcast::{channel, Receiver, Sender}; use tokio::sync::Mutex; use tracing::{info, trace}; -pub mod node; - pub struct SwayEventClient { workspace_tx: Sender>, _workspace_rx: Receiver>, - window_tx: Sender>, - _window_rx: Receiver>, } impl SwayEventClient { fn new() -> Self { let (workspace_tx, workspace_rx) = channel(16); - let (window_tx, window_rx) = channel(16); let workspace_tx2 = workspace_tx.clone(); - let window_tx2 = window_tx.clone(); spawn(async move { let workspace_tx = workspace_tx2; - let window_tx = window_tx2; let client = Connection::new().await?; info!("Sway IPC subscription client connected"); - let event_types = [EventType::Window, EventType::Workspace]; + let event_types = [EventType::Workspace]; let mut events = client.subscribe(event_types).await?; while let Some(event) = events.next().await { trace!("event: {:?}", event); - match event? { - Event::Workspace(ev) => { - workspace_tx.send(ev)?; - } - Event::Window(ev) => { - window_tx.send(ev)?; - } - _ => {} + if let Event::Workspace(ev) = event? { + workspace_tx.send(ev)?; }; } @@ -56,8 +43,6 @@ impl SwayEventClient { Self { workspace_tx, _workspace_rx: workspace_rx, - window_tx, - _window_rx: window_rx, } } @@ -65,11 +50,6 @@ impl SwayEventClient { pub fn subscribe_workspace(&self) -> Receiver> { self.workspace_tx.subscribe() } - - /// Gets an event receiver for window events - pub fn subscribe_window(&self) -> Receiver> { - self.window_tx.subscribe() - } } lazy_static! { diff --git a/src/sway/node.rs b/src/sway/node.rs deleted file mode 100644 index 6796170..0000000 --- a/src/sway/node.rs +++ /dev/null @@ -1,50 +0,0 @@ -use color_eyre::Result; -use swayipc_async::{Connection, Node, NodeType, ShellType}; - -pub fn get_node_id(node: &Node) -> &str { - node.app_id.as_ref().map_or_else( - || { - node.window_properties - .as_ref() - .expect("Cannot find node window properties") - .class - .as_ref() - .expect("Cannot find node name") - }, - |app_id| app_id, - ) -} - -/// Checks whether this application -/// is running under xwayland. -pub fn is_node_xwayland(node: &Node) -> bool { - node.shell == Some(ShellType::Xwayland) -} - -/// Recursively checks the provided node for any child application nodes. -/// Returns a list of any found application nodes. -fn check_node(node: Node, window_nodes: &mut Vec) { - if node.name.is_some() - && (node.node_type == NodeType::Con || node.node_type == NodeType::FloatingCon) - { - window_nodes.push(node); - } else { - node.nodes.into_iter().for_each(|node| { - check_node(node, window_nodes); - }); - - node.floating_nodes.into_iter().for_each(|node| { - check_node(node, window_nodes); - }); - } -} - -/// Gets a flat vector of all currently open windows. -pub async fn get_open_windows(client: &mut Connection) -> Result> { - let root_node = client.get_tree().await?; - - let mut window_nodes = vec![]; - check_node(root_node, &mut window_nodes); - - Ok(window_nodes) -} diff --git a/src/wayland/client.rs b/src/wayland/client.rs index 36d05f4..9763d9e 100644 --- a/src/wayland/client.rs +++ b/src/wayland/client.rs @@ -12,11 +12,11 @@ use std::time::Duration; use tokio::sync::{broadcast, oneshot}; use tokio::task::spawn_blocking; use tracing::trace; +use wayland_client::protocol::wl_seat::WlSeat; use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, }; -use wayland_client::protocol::wl_seat::WlSeat; pub struct WaylandClient { pub outputs: Vec, @@ -30,6 +30,7 @@ impl WaylandClient { pub(super) async fn new() -> Self { let (output_tx, output_rx) = oneshot::channel(); let (seat_tx, seat_rx) = oneshot::channel(); + let (toplevel_tx, toplevel_rx) = broadcast::channel(32); let toplevel_tx2 = toplevel_tx.clone(); @@ -49,23 +50,30 @@ impl WaylandClient { .expect("Failed to send outputs out of task"); let seats = env.get_all_seats(); - seat_tx.send(seats.into_iter().map(|seat| seat.detach()).collect::>()).expect("Failed to send seats out of task"); + seat_tx + .send( + seats + .into_iter() + .map(|seat| seat.detach()) + .collect::>(), + ) + .expect("Failed to send seats out of task"); let _toplevel_manager = env.require_global::(); let _listener = listen_for_toplevels(env, move |handle, event, _ddata| { trace!("Received toplevel event: {:?}", event); - if event.change != ToplevelChange::Close { - toplevels2 - .write() - .expect("Failed to get write lock on toplevels") - .insert(event.toplevel.id, (event.toplevel.clone(), handle)); - } else { + if event.change == ToplevelChange::Close { toplevels2 .write() .expect("Failed to get write lock on toplevels") .remove(&event.toplevel.id); + } else { + toplevels2 + .write() + .expect("Failed to get write lock on toplevels") + .insert(event.toplevel.id, (event.toplevel.clone(), handle)); } toplevel_tx2 @@ -73,15 +81,17 @@ impl WaylandClient { .expect("Failed to send toplevel event"); }); - let mut event_loop = calloop::EventLoop::<()>::try_new().unwrap(); + let mut event_loop = + calloop::EventLoop::<()>::try_new().expect("Failed to create new event loop"); WaylandSource::new(queue) .quick_insert(event_loop.handle()) - .unwrap(); + .expect("Failed to insert event loop into wayland event queue"); loop { // TODO: Avoid need for duration here - can we force some event when sending requests? - event_loop.dispatch(Duration::from_millis(50), &mut ()).unwrap(); - event_loop. + event_loop + .dispatch(Duration::from_millis(50), &mut ()) + .expect("Failed to dispatch pending wayland events"); } }); @@ -109,7 +119,7 @@ impl WaylandClient { outputs .iter() - .filter_map(|output| with_output_info(output, |info| info.clone())) + .filter_map(|output| with_output_info(output, Clone::clone)) .collect() } } diff --git a/src/wayland/mod.rs b/src/wayland/mod.rs index c89ea67..fdd0357 100644 --- a/src/wayland/mod.rs +++ b/src/wayland/mod.rs @@ -18,7 +18,7 @@ use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ pub use client::WaylandClient; /// A utility for lazy-loading globals. -/// Taken from smithay_client_toolkit where it's not exposed +/// Taken from `smithay_client_toolkit` where it's not exposed #[derive(Debug)] enum LazyGlobal { Unknown, diff --git a/src/wayland/toplevel.rs b/src/wayland/toplevel.rs index 0117ebb..d02d534 100644 --- a/src/wayland/toplevel.rs +++ b/src/wayland/toplevel.rs @@ -8,7 +8,9 @@ const STATE_ACTIVE: u32 = 2; const STATE_FULLSCREEN: u32 = 3; static COUNTER: AtomicUsize = AtomicUsize::new(1); -fn get_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } +fn get_id() -> usize { + COUNTER.fetch_add(1, Ordering::Relaxed) +} #[derive(Debug, Clone, Default)] pub struct ToplevelInfo { @@ -24,7 +26,10 @@ pub struct ToplevelInfo { impl ToplevelInfo { fn new() -> Self { let id = get_id(); - Self { id, ..Default::default() } + Self { + id, + ..Default::default() + } } } @@ -99,11 +104,11 @@ where Event::Parent { parent: _ } => None, Event::Done => { assert_ne!(info.app_id, ""); - if !info.ready { + if info.ready { + None + } else { info.ready = true; Some(ToplevelChange::New) - } else { - None } } _ => unreachable!(), @@ -120,7 +125,7 @@ where } impl Toplevel { - pub fn init(handle: Main, mut callback: F) -> Self + pub fn init(handle: &Main, mut callback: F) -> Self where F: FnMut(ToplevelEvent, DispatchData) + 'static, { @@ -130,7 +135,7 @@ impl Toplevel { let mut inner = inner .write() .expect("Failed to get write lock on toplevel inner state"); - toplevel_implem(event, &mut *inner, &mut callback, ddata); + toplevel_implem(event, &mut inner, &mut callback, ddata); }); Self diff --git a/src/wayland/toplevel_manager.rs b/src/wayland/toplevel_manager.rs index ce8e9c2..97d3a39 100644 --- a/src/wayland/toplevel_manager.rs +++ b/src/wayland/toplevel_manager.rs @@ -12,7 +12,6 @@ use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1}, }; - struct ToplevelHandlerInner { manager: LazyGlobal, registry: Option>, @@ -20,7 +19,7 @@ struct ToplevelHandlerInner { } impl ToplevelHandlerInner { - fn new() -> Self { + const fn new() -> Self { let toplevels = vec![]; Self { @@ -64,7 +63,7 @@ impl GlobalHandler for ToplevelHandler { } else { warn!( "Compositor advertised zwlr_foreign_toplevel_manager_v1 multiple times, ignoring." - ) + ); } } @@ -92,14 +91,15 @@ impl GlobalHandler for ToplevelHandler { zwlr_foreign_toplevel_manager_v1::Event::Toplevel { toplevel: handle, } => { - let toplevel = Toplevel::init(handle.clone(), move |event, ddata| { - notify_status_listeners( - &handle, - event, - ddata, - &status_listeners, - ); - }); + let toplevel = + Toplevel::init(&handle.clone(), move |event, ddata| { + notify_status_listeners( + &handle, + &event, + ddata, + &status_listeners, + ); + }); inner.toplevels.push(toplevel); } @@ -122,18 +122,16 @@ type ToplevelStatusCallback = /// Notifies the callbacks of an event on the toplevel fn notify_status_listeners( toplevel: &ZwlrForeignToplevelHandleV1, - event: ToplevelEvent, + event: &ToplevelEvent, mut ddata: DispatchData, listeners: &RefCell>>>, ) { listeners.borrow_mut().retain(|lst| { - if let Some(cb) = rc::Weak::upgrade(lst) { + rc::Weak::upgrade(lst).map_or(false, |cb| { (cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow()); true - } else { - false - } - }) + }) + }); } pub struct ToplevelStatusListener { From 994d0f580b4d1b6ff750839652a7f06149743172 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Fri, 14 Oct 2022 22:14:04 +0100 Subject: [PATCH 6/6] docs(readme): update references to sway --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index caff548..f369f6f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Ironbar -Ironbar is a customisable and feature-rich bar targeting the Sway compositor, written in Rust. +Ironbar is a customisable and feature-rich bar targeting wlroots compositors, written in Rust. It uses GTK3 and gtk-layer-shell. The bar can be styled to your liking using CSS and hot-loads style changes.