diff --git a/src/bar.rs b/src/bar.rs index 7ef66a4..57cd3f9 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -3,8 +3,7 @@ use crate::modules::{ create_module, set_widget_identifiers, wrap_widget, ModuleInfo, ModuleLocation, }; use crate::popup::Popup; -use crate::unique_id::get_unique_usize; -use crate::{Config, GlobalState}; +use crate::{Config, Ironbar}; use color_eyre::Result; use gtk::gdk::Monitor; use gtk::prelude::*; @@ -13,129 +12,228 @@ use std::cell::RefCell; use std::rc::Rc; use tracing::{debug, info}; -/// Creates a new window for a bar, -/// sets it up and adds its widgets. -pub fn create_bar( - app: &Application, - monitor: &Monitor, - monitor_name: &str, - config: Config, - global_state: &Rc>, -) -> Result<()> { - let win = ApplicationWindow::builder().application(app).build(); - let bar_name = config - .name - .clone() - .unwrap_or_else(|| format!("bar-{}", get_unique_usize())); - - win.set_widget_name(&bar_name); - info!("Creating bar {}", bar_name); - - setup_layer_shell( - &win, - monitor, - config.position, - config.anchor_to_edges, - config.margin, - ); - - let orientation = config.position.get_orientation(); - - let content = gtk::Box::builder() - .orientation(orientation) - .spacing(0) - .hexpand(false) - .name("bar"); - - let content = if orientation == Orientation::Horizontal { - content.height_request(config.height) - } else { - content.width_request(config.height) - } - .build(); - - content.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)); - content.pack_end(&end, false, false, 0); - - let load_result = load_modules(&start, ¢er, &end, app, config, monitor, monitor_name)?; - global_state - .borrow_mut() - .popups_mut() - .insert(bar_name.into(), load_result.popup); - - win.add(&content); - - win.connect_destroy_event(|_, _| { - info!("Shutting down"); - gtk::main_quit(); - Inhibit(false) - }); - - 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(); - content.show(); - win.show(); - - Ok(()) +#[derive(Debug, Clone)] +enum Inner { + New { config: Option }, + Loaded { popup: Rc> }, } -/// Sets up GTK layer shell for a provided application window. -fn setup_layer_shell( - win: &ApplicationWindow, - monitor: &Monitor, +#[derive(Debug, Clone)] +pub struct Bar { + name: String, + monitor_name: String, position: BarPosition, - anchor_to_edges: bool, - margin: MarginConfig, -) { - gtk_layer_shell::init_for_window(win); - gtk_layer_shell::set_monitor(win, monitor); - gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top); - gtk_layer_shell::auto_exclusive_zone_enable(win); - gtk_layer_shell::set_namespace(win, env!("CARGO_PKG_NAME")); - gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Top, margin.top); - gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Bottom, margin.bottom); - gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, margin.left); - gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, margin.right); + window: ApplicationWindow, - let bar_orientation = position.get_orientation(); + content: gtk::Box, - gtk_layer_shell::set_anchor( - win, - gtk_layer_shell::Edge::Top, - position == BarPosition::Top - || (bar_orientation == Orientation::Vertical && anchor_to_edges), - ); - gtk_layer_shell::set_anchor( - win, - gtk_layer_shell::Edge::Bottom, - position == BarPosition::Bottom - || (bar_orientation == Orientation::Vertical && anchor_to_edges), - ); - gtk_layer_shell::set_anchor( - win, - gtk_layer_shell::Edge::Left, - position == BarPosition::Left - || (bar_orientation == Orientation::Horizontal && anchor_to_edges), - ); - gtk_layer_shell::set_anchor( - win, - gtk_layer_shell::Edge::Right, - position == BarPosition::Right - || (bar_orientation == Orientation::Horizontal && anchor_to_edges), - ); + start: gtk::Box, + center: gtk::Box, + end: gtk::Box, + + inner: Inner, +} + +impl Bar { + pub fn new(app: &Application, monitor_name: String, config: Config) -> Self { + let window = ApplicationWindow::builder().application(app).build(); + let name = config + .name + .clone() + .unwrap_or_else(|| format!("bar-{}", Ironbar::unique_id())); + + window.set_widget_name(&name); + + let position = config.position; + let orientation = position.get_orientation(); + + let content = gtk::Box::builder() + .orientation(orientation) + .spacing(0) + .hexpand(false) + .name("bar"); + + let content = if orientation == Orientation::Horizontal { + content.height_request(config.height) + } else { + content.width_request(config.height) + } + .build(); + + content.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)); + content.pack_end(&end, false, false, 0); + + window.add(&content); + + window.connect_destroy_event(|_, _| { + info!("Shutting down"); + gtk::main_quit(); + Inhibit(false) + }); + + Bar { + name, + monitor_name, + position, + window, + content, + start, + center, + end, + inner: Inner::New { + config: Some(config), + }, + } + } + + pub fn init(mut self, monitor: &Monitor) -> Result { + let Inner::New { ref mut config } = self.inner else { + return Ok(self); + }; + + let Some(config) = config.take() else { + return Ok(self); + }; + + info!( + "Initializing bar '{}' on '{}'", + self.name, self.monitor_name + ); + + self.setup_layer_shell(config.anchor_to_edges, config.margin, monitor); + + let load_result = self.load_modules(config, monitor)?; + + self.show(); + + self.inner = Inner::Loaded { + popup: load_result.popup, + }; + Ok(self) + } + + /// Sets up GTK layer shell for a provided application window. + fn setup_layer_shell(&self, anchor_to_edges: bool, margin: MarginConfig, monitor: &Monitor) { + let win = &self.window; + let position = self.position; + + gtk_layer_shell::init_for_window(win); + gtk_layer_shell::set_monitor(win, monitor); + gtk_layer_shell::set_layer(win, gtk_layer_shell::Layer::Top); + gtk_layer_shell::auto_exclusive_zone_enable(win); + gtk_layer_shell::set_namespace(win, env!("CARGO_PKG_NAME")); + + gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Top, margin.top); + gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Bottom, margin.bottom); + gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Left, margin.left); + gtk_layer_shell::set_margin(win, gtk_layer_shell::Edge::Right, margin.right); + + let bar_orientation = position.get_orientation(); + + gtk_layer_shell::set_anchor( + win, + gtk_layer_shell::Edge::Top, + position == BarPosition::Top + || (bar_orientation == Orientation::Vertical && anchor_to_edges), + ); + gtk_layer_shell::set_anchor( + win, + gtk_layer_shell::Edge::Bottom, + position == BarPosition::Bottom + || (bar_orientation == Orientation::Vertical && anchor_to_edges), + ); + gtk_layer_shell::set_anchor( + win, + gtk_layer_shell::Edge::Left, + position == BarPosition::Left + || (bar_orientation == Orientation::Horizontal && anchor_to_edges), + ); + gtk_layer_shell::set_anchor( + win, + gtk_layer_shell::Edge::Right, + position == BarPosition::Right + || (bar_orientation == Orientation::Horizontal && anchor_to_edges), + ); + } + + /// Loads the configured modules onto a bar. + fn load_modules(&self, config: Config, monitor: &Monitor) -> Result { + let icon_theme = IconTheme::new(); + if let Some(ref theme) = config.icon_theme { + icon_theme.set_custom_theme(Some(theme)); + } + + let app = &self.window.application().expect("to exist"); + + macro_rules! info { + ($location:expr) => { + ModuleInfo { + app, + bar_position: config.position, + monitor, + output_name: &self.monitor_name, + location: $location, + icon_theme: &icon_theme, + } + }; + } + + // popup ignores module location so can bodge this for now + let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap); + let popup = Rc::new(RefCell::new(popup)); + + if let Some(modules) = config.start { + let info = info!(ModuleLocation::Left); + add_modules(&self.start, modules, &info, &popup)?; + } + + if let Some(modules) = config.center { + let info = info!(ModuleLocation::Center); + add_modules(&self.center, modules, &info, &popup)?; + } + + if let Some(modules) = config.end { + let info = info!(ModuleLocation::Right); + add_modules(&self.end, modules, &info, &popup)?; + } + + let result = BarLoadResult { popup }; + + Ok(result) + } + + fn show(&self) { + debug!("Showing bar: {}", self.name); + + // show each box but do not use `show_all`. + // this ensures `show_if` option works as intended. + self.start.show(); + self.center.show(); + self.end.show(); + self.content.show(); + self.window.show(); + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn popup(&self) -> Rc> { + match &self.inner { + Inner::New { .. } => { + panic!("Attempted to get popup of uninitialized bar. This is a serious bug!") + } + Inner::Loaded { popup } => popup.clone(), + } + } } /// Creates a `gtk::Box` container to place widgets inside. @@ -155,58 +253,6 @@ struct BarLoadResult { popup: Rc>, } -/// 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 icon_theme = IconTheme::new(); - if let Some(ref theme) = config.icon_theme { - icon_theme.set_custom_theme(Some(theme)); - } - - macro_rules! info { - ($location:expr) => { - ModuleInfo { - app, - bar_position: config.position, - monitor, - output_name, - location: $location, - icon_theme: &icon_theme, - } - }; - } - - // popup ignores module location so can bodge this for now - let popup = Popup::new(&info!(ModuleLocation::Left), config.popup_gap); - let popup = Rc::new(RefCell::new(popup)); - - if let Some(modules) = config.start { - let info = info!(ModuleLocation::Left); - add_modules(left, modules, &info, &popup)?; - } - - if let Some(modules) = config.center { - let info = info!(ModuleLocation::Center); - add_modules(center, modules, &info, &popup)?; - } - - if let Some(modules) = config.end { - let info = info!(ModuleLocation::Right); - add_modules(right, modules, &info, &popup)?; - } - - let result = BarLoadResult { popup }; - - Ok(result) -} - /// Adds modules into a provided GTK box, /// which should be one of its left, center or right containers. fn add_modules( @@ -235,7 +281,7 @@ fn add_modules( } for config in modules { - let id = get_unique_usize(); + let id = Ironbar::unique_id(); match config { #[cfg(feature = "clipboard")] ModuleConfig::Clipboard(mut module) => add_module!(module, id), @@ -261,3 +307,13 @@ fn add_modules( Ok(()) } + +pub fn create_bar( + app: &Application, + monitor: &Monitor, + monitor_name: String, + config: Config, +) -> Result { + let bar = Bar::new(app, monitor_name, config); + bar.init(monitor) +} diff --git a/src/cli/mod.rs b/src/cli.rs similarity index 100% rename from src/cli/mod.rs rename to src/cli.rs diff --git a/src/clients/system_tray.rs b/src/clients/system_tray.rs index 7948191..5978338 100644 --- a/src/clients/system_tray.rs +++ b/src/clients/system_tray.rs @@ -1,5 +1,4 @@ -use crate::unique_id::get_unique_usize; -use crate::{arc_mut, lock, send}; +use crate::{arc_mut, lock, send, Ironbar}; use async_once::AsyncOnce; use color_eyre::Report; use lazy_static::lazy_static; @@ -25,7 +24,7 @@ pub struct TrayEventReceiver { impl TrayEventReceiver { async fn new() -> system_tray::error::Result { - let id = format!("ironbar-{}", get_unique_usize()); + let id = format!("ironbar-{}", Ironbar::unique_id()); let (tx, rx) = mpsc::channel(16); let (b_tx, b_rx) = broadcast::channel(16); diff --git a/src/clients/wayland/wlr_data_control/mod.rs b/src/clients/wayland/wlr_data_control/mod.rs index 926869d..ef88772 100644 --- a/src/clients/wayland/wlr_data_control/mod.rs +++ b/src/clients/wayland/wlr_data_control/mod.rs @@ -7,8 +7,7 @@ use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler}; use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer}; use self::source::DataControlSourceHandler; use crate::clients::wayland::Environment; -use crate::unique_id::get_unique_usize; -use crate::{lock, send}; +use crate::{lock, send, Ironbar}; use device::DataControlDevice; use glib::Bytes; use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ}; @@ -145,7 +144,7 @@ impl Environment { }; Ok(ClipboardItem { - id: get_unique_usize(), + id: Ironbar::unique_id(), value, mime_type: mime_type.value.clone(), }) diff --git a/src/clients/wayland/wlr_foreign_toplevel/handle.rs b/src/clients/wayland/wlr_foreign_toplevel/handle.rs index 08ac93c..1cf1a14 100644 --- a/src/clients/wayland/wlr_foreign_toplevel/handle.rs +++ b/src/clients/wayland/wlr_foreign_toplevel/handle.rs @@ -1,6 +1,5 @@ use super::manager::ToplevelManagerState; -use crate::lock; -use crate::unique_id::get_unique_usize; +use crate::{lock, Ironbar}; use std::collections::HashSet; use std::sync::{Arc, Mutex}; use tracing::trace; @@ -68,7 +67,7 @@ pub struct ToplevelInfo { impl Default for ToplevelInfo { fn default() -> Self { Self { - id: get_unique_usize(), + id: Ironbar::unique_id(), app_id: String::new(), title: String::new(), fullscreen: false, diff --git a/src/dynamic_value/dynamic_bool.rs b/src/dynamic_value/dynamic_bool.rs index 094408f..2afde47 100644 --- a/src/dynamic_value/dynamic_bool.rs +++ b/src/dynamic_value/dynamic_bool.rs @@ -1,7 +1,7 @@ -#[cfg(feature = "ipc")] -use crate::ironvar::get_variable_manager; use crate::script::Script; use crate::send; +#[cfg(feature = "ipc")] +use crate::Ironbar; use cfg_if::cfg_if; use glib::Continue; use serde::Deserialize; @@ -55,7 +55,7 @@ impl DynamicBool { } #[cfg(feature = "ipc")] DynamicBool::Variable(variable) => { - let variable_manager = get_variable_manager(); + let variable_manager = Ironbar::variable_manager(); let variable_name = variable[1..].into(); // remove hash let mut rx = crate::write_lock!(variable_manager).subscribe(variable_name); diff --git a/src/dynamic_value/dynamic_string.rs b/src/dynamic_value/dynamic_string.rs index 19def7b..cdfbd5c 100644 --- a/src/dynamic_value/dynamic_string.rs +++ b/src/dynamic_value/dynamic_string.rs @@ -1,6 +1,6 @@ -#[cfg(feature = "ipc")] -use crate::ironvar::get_variable_manager; use crate::script::{OutputStream, Script}; +#[cfg(feature = "ipc")] +use crate::Ironbar; use crate::{arc_mut, lock, send}; use gtk::prelude::*; use tokio::spawn; @@ -72,7 +72,7 @@ where lock!(label_parts).push(String::new()); spawn(async move { - let variable_manager = get_variable_manager(); + let variable_manager = Ironbar::variable_manager(); let mut rx = crate::write_lock!(variable_manager).subscribe(name); while let Ok(value) = rx.recv().await { diff --git a/src/global_state.rs b/src/global_state.rs deleted file mode 100644 index d022b35..0000000 --- a/src/global_state.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::popup::Popup; -use std::cell::{RefCell, RefMut}; -use std::collections::HashMap; -use std::rc::Rc; - -/// Global application state shared across all bars. -/// -/// Data that needs to be accessed from anywhere -/// that is not otherwise accessible should be placed on here. -#[derive(Debug)] -pub struct GlobalState { - popups: HashMap, Rc>>, -} - -impl GlobalState { - pub(crate) fn new() -> Self { - Self { - popups: HashMap::new(), - } - } - - pub fn popups(&self) -> &HashMap, Rc>> { - &self.popups - } - - pub fn popups_mut(&mut self) -> &mut HashMap, Rc>> { - &mut self.popups - } - - pub fn with_popup_mut(&self, monitor_name: &str, f: F) -> Option - where - F: FnOnce(RefMut) -> T, - { - let popup = self.popups().get(monitor_name); - - if let Some(popup) = popup { - let popup = popup.borrow_mut(); - Some(f(popup)) - } else { - None - } - } -} diff --git a/src/ipc/mod.rs b/src/ipc/mod.rs index d5d707c..efb6ce9 100644 --- a/src/ipc/mod.rs +++ b/src/ipc/mod.rs @@ -3,25 +3,21 @@ pub mod commands; pub mod responses; mod server; -use std::cell::RefCell; use std::path::{Path, PathBuf}; -use std::rc::Rc; use tracing::warn; -use crate::GlobalState; pub use commands::Command; pub use responses::Response; #[derive(Debug)] pub struct Ipc { path: PathBuf, - global_state: Rc>, } impl Ipc { /// Creates a new IPC instance. /// This can be used as both a server and client. - pub fn new(global_state: Rc>) -> Self { + pub fn new() -> Self { let ipc_socket_file = std::env::var("XDG_RUNTIME_DIR") .map_or_else(|_| PathBuf::from("/tmp"), PathBuf::from) .join("ironbar-ipc.sock"); @@ -32,7 +28,6 @@ impl Ipc { Self { path: ipc_socket_file, - global_state, } } diff --git a/src/ipc/server.rs b/src/ipc/server.rs index fb3c291..78721f4 100644 --- a/src/ipc/server.rs +++ b/src/ipc/server.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::fs; use std::path::Path; use std::rc::Rc; @@ -15,10 +14,9 @@ use tracing::{debug, error, info, warn}; use crate::bridge_channel::BridgeChannel; use crate::ipc::{Command, Response}; -use crate::ironvar::get_variable_manager; use crate::modules::PopupButton; use crate::style::load_css; -use crate::{read_lock, send_async, try_send, write_lock, GlobalState}; +use crate::{read_lock, send_async, try_send, write_lock, Ironbar}; use super::Ipc; @@ -26,7 +24,7 @@ impl Ipc { /// Starts the IPC server on its socket. /// /// Once started, the server will begin accepting connections. - pub fn start(&self, application: &Application) { + pub fn start(&self, application: &Application, ironbar: Rc) { let bridge = BridgeChannel::::new(); let cmd_tx = bridge.create_sender(); let (res_tx, mut res_rx) = mpsc::channel(32); @@ -70,9 +68,8 @@ impl Ipc { }); let application = application.clone(); - let global_state = self.global_state.clone(); bridge.recv(move |command| { - let res = Self::handle_command(command, &application, &global_state); + let res = Self::handle_command(command, &application, ironbar.clone()); try_send!(res_tx, res); Continue(true) }); @@ -115,7 +112,7 @@ impl Ipc { fn handle_command( command: Command, application: &Application, - global_state: &Rc>, + ironbar: Rc, ) -> Response { match command { Command::Inspect => { @@ -129,12 +126,12 @@ impl Ipc { window.close(); } - crate::load_interface(application, global_state); + *ironbar.bars.borrow_mut() = crate::load_interface(application); Response::Ok } Command::Set { key, value } => { - let variable_manager = get_variable_manager(); + let variable_manager = Ironbar::variable_manager(); let mut variable_manager = write_lock!(variable_manager); match variable_manager.set(key, value) { Ok(()) => Response::Ok, @@ -142,7 +139,7 @@ impl Ipc { } } Command::Get { key } => { - let variable_manager = get_variable_manager(); + let variable_manager = Ironbar::variable_manager(); let value = read_lock!(variable_manager).get(&key); match value { Some(value) => Response::OkValue { value }, @@ -158,68 +155,85 @@ impl Ipc { } } Command::TogglePopup { bar_name, name } => { - let global_state = global_state.borrow(); - let response = global_state.with_popup_mut(&bar_name, |mut popup| { - let current_widget = popup.current_widget(); - popup.hide(); + let bar = ironbar.bar_by_name(&bar_name); - let data = popup - .cache - .iter() - .find(|(_, (module_name, _))| module_name == &name) - .map(|module| (module, module.1 .1.buttons.first())); + match bar { + Some(bar) => { + let popup = bar.popup(); + let current_widget = popup.borrow().current_widget(); - match data { - Some(((&id, _), Some(button))) if current_widget != Some(id) => { - let button_id = button.popup_id(); - popup.show(id, button_id); + popup.borrow_mut().hide(); - Response::Ok + let data = popup + .borrow() + .cache + .iter() + .find(|(_, value)| value.name == name) + .map(|(id, value)| (*id, value.content.buttons.first().cloned())); + + match data { + Some((id, Some(button))) if current_widget != Some(id) => { + let button_id = button.popup_id(); + let mut popup = popup.borrow_mut(); + + if popup.is_visible() { + popup.hide(); + } else { + popup.show(id, button_id); + } + + Response::Ok + } + Some((_, None)) => Response::error("Module has no popup functionality"), + Some(_) => Response::Ok, + None => Response::error("Invalid module name"), } - Some((_, None)) => Response::error("Module has no popup functionality"), - Some(_) => Response::Ok, - None => Response::error("Invalid module name"), } - }); - - response.unwrap_or_else(|| Response::error("Invalid monitor name")) + None => Response::error("Invalid bar name"), + } } Command::OpenPopup { bar_name, name } => { - let global_state = global_state.borrow(); - let response = global_state.with_popup_mut(&bar_name, |mut popup| { - // only one popup per bar, so hide if open for another widget - popup.hide(); + let bar = ironbar.bar_by_name(&bar_name); - let data = popup - .cache - .iter() - .find(|(_, (module_name, _))| module_name == &name) - .map(|module| (module, module.1 .1.buttons.first())); + match bar { + Some(bar) => { + let popup = bar.popup(); - match data { - Some(((&id, _), Some(button))) => { - let button_id = button.popup_id(); - popup.show(id, button_id); + // only one popup per bar, so hide if open for another widget + popup.borrow_mut().hide(); - Response::Ok + let data = popup + .borrow() + .cache + .iter() + .find(|(_, value)| value.name == name) + .map(|(id, value)| (*id, value.content.buttons.first().cloned())); + + match data { + Some((id, Some(button))) => { + let button_id = button.popup_id(); + popup.borrow_mut().show(id, button_id); + + Response::Ok + } + Some((_, None)) => Response::error("Module has no popup functionality"), + None => Response::error("Invalid module name"), } - Some((_, None)) => Response::error("Module has no popup functionality"), - None => Response::error("Invalid module name"), } - }); - - response.unwrap_or_else(|| Response::error("Invalid monitor name")) + None => Response::error("Invalid bar name"), + } } Command::ClosePopup { bar_name } => { - let global_state = global_state.borrow(); - let popup_found = global_state - .with_popup_mut(&bar_name, |mut popup| popup.hide()) - .is_some(); + let bar = ironbar.bar_by_name(&bar_name); - if popup_found { - Response::Ok - } else { - Response::error("Invalid monitor name") + match bar { + Some(bar) => { + let popup = bar.popup(); + popup.borrow_mut().hide(); + + Response::Ok + } + None => Response::error("Invalid bar name"), } } Command::Ping => Response::Ok, diff --git a/src/ironvar.rs b/src/ironvar.rs index a83f07f..5b1db21 100644 --- a/src/ironvar.rs +++ b/src/ironvar.rs @@ -1,25 +1,21 @@ #![doc = include_str!("../docs/Ironvars.md")] -use crate::{arc_rw, send}; +use crate::send; use color_eyre::{Report, Result}; -use lazy_static::lazy_static; use std::collections::HashMap; -use std::sync::{Arc, RwLock}; use tokio::sync::broadcast; -lazy_static! { - static ref VARIABLE_MANAGER: Arc> = arc_rw!(VariableManager::new()); -} - -pub fn get_variable_manager() -> Arc> { - VARIABLE_MANAGER.clone() -} - /// Global singleton manager for `IronVar` variables. pub struct VariableManager { variables: HashMap, IronVar>, } +impl Default for VariableManager { + fn default() -> Self { + Self::new() + } +} + impl VariableManager { pub fn new() -> Self { Self { diff --git a/src/main.rs b/src/main.rs index 4c47b28..5d3747d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,15 @@ #![doc = include_str!("../README.md")] -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::env; use std::future::Future; use std::path::PathBuf; use std::process::exit; use std::rc::Rc; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::mpsc; +#[cfg(feature = "ipc")] +use std::sync::{Arc, RwLock}; use cfg_if::cfg_if; #[cfg(feature = "cli")] @@ -14,9 +17,11 @@ use clap::Parser; use color_eyre::eyre::Result; use color_eyre::Report; use dirs::config_dir; +use glib::PropertySet; use gtk::gdk::Display; use gtk::prelude::*; use gtk::Application; +use lazy_static::lazy_static; use tokio::runtime::Handle; use tokio::task::{block_in_place, spawn_blocking}; use tracing::{debug, error, info, warn}; @@ -24,10 +29,11 @@ use universal_config::ConfigLoader; use clients::wayland; -use crate::bar::create_bar; +use crate::bar::{create_bar, Bar}; use crate::config::{Config, MonitorConfig}; use crate::error::ExitCode; -use crate::global_state::GlobalState; +#[cfg(feature = "ipc")] +use crate::ironvar::VariableManager; use crate::style::load_css; mod bar; @@ -39,7 +45,6 @@ mod config; mod desktop_file; mod dynamic_value; mod error; -mod global_state; mod gtk_helpers; mod image; #[cfg(feature = "ipc")] @@ -52,7 +57,6 @@ mod modules; mod popup; mod script; mod style; -mod unique_id; const GTK_APP_ID: &str = "dev.jstanger.ironbar"; const VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -61,103 +65,160 @@ const VERSION: &str = env!("CARGO_PKG_VERSION"); async fn main() { let _guard = logging::install_logging(); - let global_state = Rc::new(RefCell::new(GlobalState::new())); - cfg_if! { if #[cfg(feature = "cli")] { - run_with_args(global_state).await; + run_with_args().await; } else { - start_ironbar(global_state); + start_ironbar(); } } } #[cfg(feature = "cli")] -async fn run_with_args(global_state: Rc>) { +async fn run_with_args() { let args = cli::Args::parse(); match args.command { Some(command) => { - let ipc = ipc::Ipc::new(global_state); + let ipc = ipc::Ipc::new(); match ipc.send(command).await { Ok(res) => cli::handle_response(res), Err(err) => error!("{err:?}"), }; } - None => start_ironbar(global_state), + None => start_ironbar(), } } -fn start_ironbar(global_state: Rc>) { - info!("Ironbar version {}", VERSION); - info!("Starting application"); +static COUNTER: AtomicUsize = AtomicUsize::new(1); - let app = Application::builder().application_id(GTK_APP_ID).build(); - let _ = wayland::get_client(); // force-init +#[cfg(feature = "ipc")] +lazy_static! { + static ref VARIABLE_MANAGER: Arc> = arc_rw!(VariableManager::new()); +} - let running = Rc::new(Cell::new(false)); +#[derive(Debug)] +pub struct Ironbar { + bars: Rc>>, +} - app.connect_activate(move |app| { - if running.get() { - info!("Ironbar already running, returning"); - return; +impl Ironbar { + fn new() -> Self { + Self { + bars: Rc::new(RefCell::new(vec![])), } + } - running.set(true); + fn start(self) { + info!("Ironbar version {}", VERSION); + info!("Starting application"); - cfg_if! { - if #[cfg(feature = "ipc")] { - let ipc = ipc::Ipc::new(global_state.clone()); - ipc.start(app); + let app = Application::builder().application_id(GTK_APP_ID).build(); + + let running = AtomicBool::new(false); + + let instance = Rc::new(self); + + // force start wayland client ahead of ui + let wl = wayland::get_client(); + lock!(wl).roundtrip(); + + app.connect_activate(move |app| { + if running.load(Ordering::Relaxed) { + info!("Ironbar already running, returning"); + return; } - } - load_interface(app, &global_state); + running.set(true); - let style_path = env::var("IRONBAR_CSS").ok().map_or_else( - || { - config_dir().map_or_else( - || { - let report = Report::msg("Failed to locate user config dir"); - error!("{:?}", report); - exit(ExitCode::CreateBars as i32); - }, - |dir| dir.join("ironbar").join("style.css"), - ) - }, - PathBuf::from, - ); + cfg_if! { + if #[cfg(feature = "ipc")] { + let ipc = ipc::Ipc::new(); + ipc.start(app, instance.clone()); + } + } - if style_path.exists() { - load_css(style_path); - } + *instance.bars.borrow_mut() = load_interface(app); - let (tx, rx) = mpsc::channel(); + let style_path = env::var("IRONBAR_CSS").ok().map_or_else( + || { + config_dir().map_or_else( + || { + let report = Report::msg("Failed to locate user config dir"); + error!("{:?}", report); + exit(ExitCode::CreateBars as i32); + }, + |dir| dir.join("ironbar").join("style.css"), + ) + }, + PathBuf::from, + ); - #[cfg(feature = "ipc")] - let ipc_path = ipc.path().to_path_buf(); - spawn_blocking(move || { - rx.recv().expect("to receive from channel"); + if style_path.exists() { + load_css(style_path); + } - info!("Shutting down"); + let (tx, rx) = mpsc::channel(); #[cfg(feature = "ipc")] - ipc::Ipc::shutdown(ipc_path); + let ipc_path = ipc.path().to_path_buf(); + spawn_blocking(move || { + rx.recv().expect("to receive from channel"); - exit(0); + info!("Shutting down"); + + #[cfg(feature = "ipc")] + ipc::Ipc::shutdown(ipc_path); + + exit(0); + }); + + ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) + .expect("Error setting Ctrl-C handler"); + + // TODO: Start wayland client - listen for outputs + // All bar loading should happen as an event response to this }); - ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel.")) - .expect("Error setting Ctrl-C handler"); - }); + // Ignore CLI args + // Some are provided by swaybar_config but not currently supported + app.run_with_args(&Vec::<&str>::new()); + } - // Ignore CLI args - // Some are provided by swaybar_config but not currently supported - app.run_with_args(&Vec::<&str>::new()); + /// Gets a `usize` ID value that is unique to the entire Ironbar instance. + /// This is just a static `AtomicUsize` that increments every time this function is called. + pub fn unique_id() -> usize { + COUNTER.fetch_add(1, Ordering::Relaxed) + } + + /// Gets the `Ironvar` manager singleton. + #[cfg(feature = "ipc")] + #[must_use] + pub fn variable_manager() -> Arc> { + VARIABLE_MANAGER.clone() + } + + /// Gets a clone of a bar by its unique name. + /// + /// Since the bar contains mostly GTK objects, + /// the clone is cheap enough to not worry about. + #[must_use] + pub fn bar_by_name(&self, name: &str) -> Option { + self.bars + .borrow() + .iter() + .find(|&bar| bar.name() == name) + .cloned() + } +} + +fn start_ironbar() { + let ironbar = Ironbar::new(); + ironbar.start(); } /// Loads the Ironbar config and interface. -pub fn load_interface(app: &Application, global_state: &Rc>) { +pub fn load_interface(app: &Application) -> Vec { let display = Display::default().map_or_else( || { let report = Report::msg("Failed to get default GTK display"); @@ -185,7 +246,7 @@ pub fn load_interface(app: &Application, global_state: &Rc> #[cfg(feature = "ipc")] if let Some(ironvars) = config.ironvar_defaults.take() { - let variable_manager = ironvar::get_variable_manager(); + let variable_manager = Ironbar::variable_manager(); for (k, v) in ironvars { if write_lock!(variable_manager).set(k.clone(), v).is_err() { warn!("Ignoring invalid ironvar: '{k}'"); @@ -193,21 +254,20 @@ pub fn load_interface(app: &Application, global_state: &Rc> } } - if let Err(err) = create_bars(app, &display, &config, global_state) { - error!("{:?}", err); - exit(ExitCode::CreateBars as i32); + match create_bars(app, &display, &config) { + Ok(bars) => { + debug!("Created {} bars", bars.len()); + bars + } + Err(err) => { + error!("{:?}", err); + exit(ExitCode::CreateBars as i32); + } } - - debug!("Created bars"); } /// Creates each of the bars across each of the (configured) outputs. -fn create_bars( - app: &Application, - display: &Display, - config: &Config, - global_state: &Rc>, -) -> Result<()> { +fn create_bars(app: &Application, display: &Display, config: &Config) -> Result> { let wl = wayland::get_client(); let outputs = lock!(wl).get_outputs(); @@ -216,6 +276,7 @@ fn create_bars( let num_monitors = display.n_monitors(); + let mut all_bars = vec![]; for i in 0..num_monitors { let monitor = display .monitor(i) @@ -228,33 +289,35 @@ fn create_bars( continue; }; - config.monitors.as_ref().map_or_else( - || { - info!("Creating bar on '{}'", monitor_name); - create_bar(app, &monitor, monitor_name, config.clone(), global_state) - }, - |config| { - let config = config.get(monitor_name); - match &config { - Some(MonitorConfig::Single(config)) => { - info!("Creating bar on '{}'", monitor_name); - create_bar(app, &monitor, monitor_name, config.clone(), global_state) - } - Some(MonitorConfig::Multiple(configs)) => { - for config in configs { - info!("Creating bar on '{}'", monitor_name); - create_bar(app, &monitor, monitor_name, config.clone(), global_state)?; - } + let mut bars = match config + .monitors + .as_ref() + .and_then(|config| config.get(monitor_name)) + { + Some(MonitorConfig::Single(config)) => { + vec![create_bar( + app, + &monitor, + monitor_name.to_string(), + config.clone(), + )?] + } + Some(MonitorConfig::Multiple(configs)) => configs + .iter() + .map(|config| create_bar(app, &monitor, monitor_name.to_string(), config.clone())) + .collect::>()?, + None => vec![create_bar( + app, + &monitor, + monitor_name.to_string(), + config.clone(), + )?], + }; - Ok(()) - } - _ => Ok(()), - } - }, - )?; + all_bars.append(&mut bars); } - Ok(()) + Ok(all_bars) } /// Blocks on a `Future` until it resolves. diff --git a/src/popup.rs b/src/popup.rs index 882d3e3..530ddda 100644 --- a/src/popup.rs +++ b/src/popup.rs @@ -8,12 +8,18 @@ use tracing::debug; use crate::config::BarPosition; use crate::gtk_helpers::{IronbarGtkExt, WidgetGeometry}; use crate::modules::{ModuleInfo, ModulePopupParts, PopupButton}; -use crate::unique_id::get_unique_usize; +use crate::Ironbar; + +#[derive(Debug, Clone)] +pub struct PopupCacheValue { + pub name: String, + pub content: ModulePopupParts, +} #[derive(Debug, Clone)] pub struct Popup { pub window: ApplicationWindow, - pub cache: HashMap, + pub cache: HashMap, monitor: Monitor, pos: BarPosition, current_widget: Option, @@ -121,17 +127,17 @@ impl Popup { debug!("Registered popup content for #{}", key); for button in &content.buttons { - let id = get_unique_usize(); + let id = Ironbar::unique_id(); button.set_tag("popup-id", id); } - self.cache.insert(key, (name, content)); + self.cache.insert(key, PopupCacheValue { name, content }); } pub fn show(&mut self, widget_id: usize, button_id: usize) { self.clear_window(); - if let Some((_name, content)) = self.cache.get(&widget_id) { + if let Some(PopupCacheValue { content, .. }) = self.cache.get(&widget_id) { self.current_widget = Some(widget_id); content.container.style_context().add_class("popup"); @@ -155,7 +161,7 @@ impl Popup { pub fn show_at(&self, widget_id: usize, geometry: WidgetGeometry) { self.clear_window(); - if let Some((_name, content)) = self.cache.get(&widget_id) { + if let Some(PopupCacheValue { content, .. }) = self.cache.get(&widget_id) { content.container.style_context().add_class("popup"); self.window.add(&content.container); diff --git a/src/unique_id.rs b/src/unique_id.rs deleted file mode 100644 index beeddea..0000000 --- a/src/unique_id.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; - -static COUNTER: AtomicUsize = AtomicUsize::new(1); - -/// Gets a `usize` ID value that is unique to the entire Ironbar instance. -/// This is just an `AtomicUsize` that increments every time this function is called. -pub fn get_unique_usize() -> usize { - COUNTER.fetch_add(1, Ordering::Relaxed) -}