From 2c1b2924d4a103183d3974ac066623a80277a79a Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Sun, 4 Dec 2022 23:23:22 +0000 Subject: [PATCH 1/6] refactor: move most of the horrible `add_module` macro content into proper functions --- Cargo.lock | 85 ------ Cargo.toml | 1 - src/bar.rs | 505 +++++++++++++++++--------------- src/clients/wayland/toplevel.rs | 8 +- src/main.rs | 2 + src/modules/clock.rs | 6 +- src/modules/custom.rs | 6 +- src/modules/focused.rs | 6 +- src/modules/launcher/mod.rs | 6 +- src/modules/mod.rs | 6 +- src/modules/mpd.rs | 6 +- src/modules/script.rs | 6 +- src/modules/sysinfo.rs | 6 +- src/modules/tray.rs | 6 +- src/modules/workspaces.rs | 6 +- 15 files changed, 317 insertions(+), 344 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f388b0a..a990d2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,41 +493,6 @@ dependencies = [ "syn", ] -[[package]] -name = "darling" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" -dependencies = [ - "darling_core", - "quote", - "syn", -] - [[package]] name = "derivative" version = "2.2.0" @@ -539,37 +504,6 @@ dependencies = [ "syn", ] -[[package]] -name = "derive_builder" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07adf7be193b71cc36b193d0f5fe60b918a3a9db4dad0449f57bcfd519704a3" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" -dependencies = [ - "derive_builder_core", - "syn", -] - [[package]] name = "digest" version = "0.10.6" @@ -689,12 +623,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "futures-channel" version = "0.3.25" @@ -1091,12 +1019,6 @@ dependencies = [ "cxx-build", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "indenter" version = "0.3.3" @@ -1149,7 +1071,6 @@ dependencies = [ "async_once", "chrono", "color-eyre", - "derive_builder", "dirs", "futures-util", "glib", @@ -2064,12 +1985,6 @@ dependencies = [ "vte", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "swayipc-async" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index 18ae797..97ef7b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ license = "MIT" description = "Customisable GTK Layer Shell wlroots/sway bar" [dependencies] -derive_builder = "0.11.2" gtk = "0.16.0" gtk-layer-shell = "0.5.0" glib = "0.16.2" diff --git a/src/bar.rs b/src/bar.rs index 48b178a..d81b293 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -1,23 +1,15 @@ use crate::bridge_channel::BridgeChannel; -use crate::config::{BarPosition, ModuleConfig}; +use crate::config::{BarPosition, CommonConfig, ModuleConfig}; use crate::dynamic_string::DynamicString; -use crate::modules::custom::ExecEvent; -use crate::modules::launcher::{ItemEvent, LauncherUpdate}; -use crate::modules::mpd::{PlayerCommand, SongUpdate}; -use crate::modules::workspaces::WorkspaceUpdate; -use crate::modules::{Module, ModuleInfoBuilder, ModuleLocation, ModuleUpdateEvent, WidgetContext}; +use crate::modules::{Module, ModuleInfo, ModuleLocation, ModuleUpdateEvent, WidgetContext}; use crate::popup::Popup; use crate::script::{OutputStream, Script}; use crate::{await_sync, Config}; -use chrono::{DateTime, Local}; use color_eyre::Result; use gtk::gdk::Monitor; use gtk::prelude::*; -use gtk::{Application, ApplicationWindow, Orientation}; -use std::collections::HashMap; +use gtk::{Application, ApplicationWindow, EventBox, Orientation, Widget}; use std::sync::{Arc, RwLock}; -use stray::message::NotifierItemCommand; -use stray::NotifierItemMessage; use tokio::spawn; use tokio::sync::mpsc; use tracing::{debug, error, info, trace}; @@ -49,26 +41,11 @@ pub fn create_bar( } .build(); - let start = gtk::Box::builder() - .orientation(orientation) - .spacing(0) - .name("start") - .build(); - let center = gtk::Box::builder() - .orientation(orientation) - .spacing(0) - .name("center") - .build(); - let end = gtk::Box::builder() - .orientation(orientation) - .spacing(0) - .name("end") - .build(); - content.style_context().add_class("container"); - start.style_context().add_class("container"); - center.style_context().add_class("container"); - end.style_context().add_class("container"); + + let start = create_container("start", orientation); + let center = create_container("center", orientation); + let end = create_container("end", orientation); content.add(&start); content.set_center_widget(Some(¢er)); @@ -84,6 +61,9 @@ pub fn create_bar( }); debug!("Showing bar"); + + // show each box but do not use `show_all`. + // this ensures `show_if` option works as intended. start.show(); center.show(); end.show(); @@ -93,218 +73,6 @@ pub fn create_bar( Ok(()) } -/// Loads the configured modules onto a bar. -fn load_modules( - left: >k::Box, - center: >k::Box, - right: >k::Box, - app: &Application, - config: Config, - monitor: &Monitor, - output_name: &str, -) -> Result<()> { - let mut info_builder = ModuleInfoBuilder::default(); - let info_builder = info_builder - .app(app) - .bar_position(config.position) - .monitor(monitor) - .output_name(output_name); - - if let Some(modules) = config.start { - let info_builder = info_builder.location(ModuleLocation::Left); - - add_modules(left, modules, info_builder)?; - } - - if let Some(modules) = config.center { - let info_builder = info_builder.location(ModuleLocation::Center); - - add_modules(center, modules, info_builder)?; - } - - if let Some(modules) = config.end { - let info_builder = info_builder.location(ModuleLocation::Right); - - add_modules(right, modules, info_builder)?; - } - - Ok(()) -} - -/// Adds modules into a provided GTK box, -/// which should be one of its left, center or right containers. -fn add_modules( - content: >k::Box, - modules: Vec, - info_builder: &mut ModuleInfoBuilder, -) -> Result<()> { - let base_popup_info = info_builder.module_name("").build()?; - let popup = Popup::new(&base_popup_info); - let popup = Arc::new(RwLock::new(popup)); - - macro_rules! add_module { - ($module:expr, $id:expr, $name:literal, $send_message:ty, $receive_message:ty) => { - let info = info_builder.module_name($name).build()?; - - let (w_tx, w_rx) = glib::MainContext::channel::<$send_message>(glib::PRIORITY_DEFAULT); - let (p_tx, p_rx) = glib::MainContext::channel::<$send_message>(glib::PRIORITY_DEFAULT); - - let channel = BridgeChannel::>::new(); - let (ui_tx, ui_rx) = mpsc::channel::<$receive_message>(16); - - $module.spawn_controller(&info, channel.create_sender(), ui_rx)?; - - let context = WidgetContext { - id: $id, - widget_rx: w_rx, - popup_rx: p_rx, - tx: channel.create_sender(), - controller_tx: ui_tx, - }; - - let common = $module.common.clone(); - - let widget = $module.into_widget(context, &info)?; - - let container = gtk::EventBox::new(); - container.add(&widget.widget); - - content.add(&container); - widget.widget.set_widget_name(info.module_name); - - if let Some(show_if) = common.show_if { - let script = Script::new_polling(show_if); - let container = container.clone(); - - let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); - - spawn(async move { - script - .run(|(_, success)| { - tx.send(success) - .expect("Failed to send widget visibility toggle message"); - }) - .await; - }); - - rx.attach(None, move |success| { - if success { - container.show_all() - } else { - container.hide() - }; - Continue(true) - }); - } else { - container.show_all(); - } - - if let Some(on_click) = common.on_click { - let script = Script::new_polling(on_click); - container.connect_button_press_event(move |_, _| { - trace!("Running on-click script"); - match await_sync(async { script.get_output().await }) { - Ok((OutputStream::Stderr(out), _)) => error!("{out}"), - Err(err) => error!("{err:?}"), - _ => {} - } - Inhibit(false) - }); - } - - if let Some(tooltip) = common.tooltip { - DynamicString::new(&tooltip, move |string| { - container.set_tooltip_text(Some(&string)); - Continue(true) - }); - } - - let has_popup = widget.popup.is_some(); - if let Some(popup_content) = widget.popup { - popup - .write() - .expect("Failed to get write lock on popup") - .register_content($id, popup_content); - } - - let popup2 = Arc::clone(&popup); - channel.recv(move |ev| { - let popup = popup2.clone(); - match ev { - ModuleUpdateEvent::Update(update) => { - if has_popup { - p_tx.send(update.clone()) - .expect("Failed to send update to popup"); - } - - w_tx.send(update).expect("Failed to send update to module"); - } - ModuleUpdateEvent::TogglePopup(geometry) => { - debug!("Toggling popup for {} [#{}]", $name, $id); - let popup = popup.read().expect("Failed to get read lock on popup"); - if popup.is_visible() { - popup.hide() - } else { - popup.show_content($id); - popup.show(geometry); - } - } - ModuleUpdateEvent::OpenPopup(geometry) => { - debug!("Opening popup for {} [#{}]", $name, $id); - - let popup = popup.read().expect("Failed to get read lock on popup"); - popup.hide(); - popup.show_content($id); - popup.show(geometry); - } - ModuleUpdateEvent::ClosePopup => { - debug!("Closing popup for {} [#{}]", $name, $id); - - let popup = popup.read().expect("Failed to get read lock on popup"); - popup.hide(); - } - } - - Continue(true) - }); - }; - } - - for (id, config) in modules.into_iter().enumerate() { - match config { - ModuleConfig::Clock(module) => { - add_module!(module, id, "clock", DateTime, ()); - } - ModuleConfig::Script(module) => { - add_module!(module, id, "script", String, ()); - } - ModuleConfig::SysInfo(module) => { - add_module!(module, id, "sysinfo", HashMap, ()); - } - ModuleConfig::Focused(module) => { - add_module!(module, id, "focused", (String, String), ()); - } - ModuleConfig::Workspaces(module) => { - add_module!(module, id, "workspaces", WorkspaceUpdate, String); - } - ModuleConfig::Tray(module) => { - add_module!(module, id, "tray", NotifierItemMessage, NotifierItemCommand); - } - ModuleConfig::Mpd(module) => { - add_module!(module, id, "mpd", Option, PlayerCommand); - } - ModuleConfig::Launcher(module) => { - add_module!(module, id, "launcher", LauncherUpdate, ItemEvent); - } - ModuleConfig::Custom(module) => { - add_module!(module, id, "custom", (), ExecEvent); - } - } - } - - Ok(()) -} - /// Sets up GTK layer shell for a provided application window. fn setup_layer_shell( win: &ApplicationWindow, @@ -349,3 +117,256 @@ fn setup_layer_shell( || (bar_orientation == Orientation::Horizontal && anchor_to_edges), ); } + +/// Creates a `gtk::Box` container to place widgets inside. +fn create_container(name: &str, orientation: Orientation) -> gtk::Box { + let container = gtk::Box::builder() + .orientation(orientation) + .spacing(0) + .name(name) + .build(); + + container.style_context().add_class("container"); + container +} + +/// Loads the configured modules onto a bar. +fn load_modules( + left: >k::Box, + center: >k::Box, + right: >k::Box, + app: &Application, + config: Config, + monitor: &Monitor, + output_name: &str, +) -> Result<()> { + macro_rules! info { + ($location:expr) => { + ModuleInfo { + app, + bar_position: config.position, + monitor, + output_name, + location: $location, + } + }; + } + + if let Some(modules) = config.start { + let info = info!(ModuleLocation::Left); + add_modules(left, modules, &info)?; + } + + if let Some(modules) = config.center { + let info = info!(ModuleLocation::Center); + add_modules(center, modules, &info)?; + } + + if let Some(modules) = config.end { + let info = info!(ModuleLocation::Right); + add_modules(right, modules, &info)?; + } + + Ok(()) +} + +/// Adds modules into a provided GTK box, +/// which should be one of its left, center or right containers. +fn add_modules(content: >k::Box, modules: Vec, info: &ModuleInfo) -> Result<()> { + let popup = Popup::new(info); + let popup = Arc::new(RwLock::new(popup)); + + macro_rules! add_module { + ($module:expr, $id:expr) => {{ + let common = $module.common.take().expect("Common config did not exist"); + let widget = create_module($module, $id, &info, &Arc::clone(&popup))?; + + let container = wrap_widget(&widget); + content.add(&container); + setup_module_common_options(container, common); + }}; + } + + for (id, config) in modules.into_iter().enumerate() { + match config { + ModuleConfig::Clock(mut module) => add_module!(module, id), + ModuleConfig::Script(mut module) => add_module!(module, id), + ModuleConfig::SysInfo(mut module) => add_module!(module, id), + ModuleConfig::Focused(mut module) => add_module!(module, id), + ModuleConfig::Workspaces(mut module) => add_module!(module, id), + ModuleConfig::Tray(mut module) => add_module!(module, id), + ModuleConfig::Mpd(mut module) => add_module!(module, id), + ModuleConfig::Launcher(mut module) => add_module!(module, id), + ModuleConfig::Custom(mut module) => add_module!(module, id), + } + } + + Ok(()) +} + +/// Creates a module and sets it up. +/// This setup includes widget/popup content and event channels. +fn create_module( + module: TModule, + id: usize, + info: &ModuleInfo, + popup: &Arc>, +) -> Result +where + TModule: Module, + TWidget: IsA, + TSend: Clone + Send + 'static, +{ + let (w_tx, w_rx) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); + let (p_tx, p_rx) = glib::MainContext::channel::(glib::PRIORITY_DEFAULT); + + let channel = BridgeChannel::>::new(); + let (ui_tx, ui_rx) = mpsc::channel::(16); + + module.spawn_controller(info, channel.create_sender(), ui_rx)?; + + let context = WidgetContext { + id, + widget_rx: w_rx, + popup_rx: p_rx, + tx: channel.create_sender(), + controller_tx: ui_tx, + }; + + let name = TModule::name(); + + let module_parts = module.into_widget(context, info)?; + module_parts.widget.set_widget_name(name); + + let mut has_popup = false; + if let Some(popup_content) = module_parts.popup { + register_popup_content(popup, id, popup_content); + has_popup = true; + } + + setup_receiver(channel, w_tx, p_tx, popup.clone(), name, id, has_popup); + + Ok(module_parts.widget) +} + +/// Registers the popup content with the popup. +fn register_popup_content(popup: &Arc>, id: usize, popup_content: gtk::Box) { + popup + .write() + .expect("Failed to get write lock on popup") + .register_content(id, popup_content); +} + +/// Sets up the bridge channel receiver +/// to pick up events from the controller, widget or popup. +/// +/// Handles opening/closing popups +/// and communicating update messages between controllers and widgets/popups. +fn setup_receiver( + channel: BridgeChannel>, + w_tx: glib::Sender, + p_tx: glib::Sender, + popup: Arc>, + name: &'static str, + id: usize, + has_popup: bool, +) where + TSend: Clone + Send + 'static, +{ + channel.recv(move |ev| { + match ev { + ModuleUpdateEvent::Update(update) => { + if has_popup { + p_tx.send(update.clone()) + .expect("Failed to send update to popup"); + } + + w_tx.send(update).expect("Failed to send update to widget"); + } + ModuleUpdateEvent::TogglePopup(geometry) => { + debug!("Toggling popup for {} [#{}]", name, id); + let popup = popup.read().expect("Failed to get read lock on popup"); + if popup.is_visible() { + popup.hide(); + } else { + popup.show_content(id); + popup.show(geometry); + } + } + ModuleUpdateEvent::OpenPopup(geometry) => { + debug!("Opening popup for {} [#{}]", name, id); + + let popup = popup.read().expect("Failed to get read lock on popup"); + popup.hide(); + popup.show_content(id); + popup.show(geometry); + } + ModuleUpdateEvent::ClosePopup => { + debug!("Closing popup for {} [#{}]", name, id); + + let popup = popup.read().expect("Failed to get read lock on popup"); + popup.hide(); + } + } + + Continue(true) + }); +} + +/// Takes a widget and adds it into a new `gtk::EventBox`. +/// The event box container is returned. +fn wrap_widget>(widget: &W) -> EventBox { + let container = EventBox::new(); + container.add(widget); + container +} + +/// Configures the module's container according to the common config options. +fn setup_module_common_options(container: EventBox, common: CommonConfig) { + common.show_if.map_or_else( + || { + container.show_all(); + }, + |show_if| { + let script = Script::new_polling(show_if); + let container = container.clone(); + let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + spawn(async move { + script + .run(|(_, success)| { + tx.send(success) + .expect("Failed to send widget visibility toggle message"); + }) + .await; + }); + rx.attach(None, move |success| { + if success { + container.show_all(); + } else { + container.hide(); + }; + Continue(true) + }); + }, + ); + + if let Some(on_click) = common.on_click { + let script = Script::new_polling(on_click); + container.connect_button_press_event(move |_, _| { + trace!("Running on-click script"); + match await_sync(async { script.get_output().await }) { + Ok((OutputStream::Stderr(out), _)) => error!("{out}"), + Err(err) => error!("{err:?}"), + _ => {} + } + Inhibit(false) + }); + } + + if let Some(tooltip) = common.tooltip { + DynamicString::new(&tooltip, move |string| { + container.set_tooltip_text(Some(&string)); + Continue(true) + }); + } +} diff --git a/src/clients/wayland/toplevel.rs b/src/clients/wayland/toplevel.rs index b878726..5559db1 100644 --- a/src/clients/wayland/toplevel.rs +++ b/src/clients/wayland/toplevel.rs @@ -9,6 +9,7 @@ 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) } @@ -108,9 +109,9 @@ where None } } - Event::OutputEnter { output: _ } => None, - Event::OutputLeave { output: _ } => None, - Event::Parent { parent: _ } => None, + Event::OutputEnter { output: _ } + | Event::OutputLeave { output: _ } + | Event::Parent { parent: _ } => None, Event::Done => { if info.ready || info.app_id.is_empty() { None @@ -119,6 +120,7 @@ where Some(ToplevelChange::New) } } + _ => unreachable!(), }; diff --git a/src/main.rs b/src/main.rs index 3b7011c..b6f22ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -178,6 +178,8 @@ fn create_bars( /// 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 pub fn await_sync(f: F) -> F::Output { block_in_place(|| Handle::current().block_on(f)) } diff --git a/src/modules/clock.rs b/src/modules/clock.rs index 5b59698..49da2d4 100644 --- a/src/modules/clock.rs +++ b/src/modules/clock.rs @@ -22,7 +22,7 @@ pub struct ClockModule { format: String, #[serde(flatten)] - pub common: CommonConfig, + pub common: Option, } fn default_format() -> String { @@ -33,6 +33,10 @@ impl Module