From 8371a92204185a78a0ea597462a8dd5112774554 Mon Sep 17 00:00:00 2001 From: Jake Stanger Date: Thu, 11 Jan 2024 23:22:18 +0000 Subject: [PATCH] feat: load bars on monitor when it connects Finally, Ironbar will respond to events of monitors being (dis)connected, and will create bars when a monitor connects. This means at last - resolves #291 yaay --- src/bar.rs | 5 + src/clients/wayland/mod.rs | 15 ++- src/clients/wayland/wl_output.rs | 9 +- src/error.rs | 2 - src/ipc/server.rs | 14 ++- src/main.rs | 192 ++++++++++++++++++------------- 6 files changed, 140 insertions(+), 97 deletions(-) diff --git a/src/bar.rs b/src/bar.rs index 2efa218a..1c78fd7d 100644 --- a/src/bar.rs +++ b/src/bar.rs @@ -308,6 +308,11 @@ impl Bar { &self.name } + /// The name of the output the bar is displayed on. + pub fn monitor_name(&self) -> &str { + &self.monitor_name + } + pub fn popup(&self) -> Rc> { match &self.inner { Inner::New { .. } => { diff --git a/src/clients/wayland/mod.rs b/src/clients/wayland/mod.rs index 86746e39..94dbbf9e 100644 --- a/src/clients/wayland/mod.rs +++ b/src/clients/wayland/mod.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex}; use calloop_channel::Event::Msg; use cfg_if::cfg_if; use color_eyre::Report; -use smithay_client_toolkit::output::{OutputInfo, OutputState}; +use smithay_client_toolkit::output::OutputState; use smithay_client_toolkit::reexports::calloop::channel as calloop_channel; use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle}; use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource; @@ -25,12 +25,11 @@ use smithay_client_toolkit::{ use tokio::sync::{broadcast, mpsc}; use tracing::{debug, error, trace}; use wayland_client::globals::registry_queue_init; -use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::{Connection, QueueHandle}; use wlr_foreign_toplevel::manager::ToplevelManagerState; -pub use wl_output::OutputEvent; +pub use wl_output::{OutputEvent, OutputEventType}; pub use wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo}; cfg_if! { @@ -42,6 +41,7 @@ cfg_if! { use self::wlr_data_control::manager::DataControlDeviceManagerState; use self::wlr_data_control::source::CopyPasteSource; use self::wlr_data_control::SelectionOfferItem; + use wayland_client::protocol::wl_seat::WlSeat; pub use wlr_data_control::{ClipboardItem, ClipboardValue}; @@ -65,6 +65,7 @@ pub enum Event { pub enum Request { Roundtrip, + #[cfg(feature = "ipc")] OutputInfoAll, ToplevelInfoAll, @@ -81,16 +82,13 @@ pub enum Response { /// An empty success response Ok, - OutputInfo(Option), - OutputInfoAll(Vec), + #[cfg(feature = "ipc")] + OutputInfoAll(Vec), - ToplevelInfo(Option), ToplevelInfoAll(Vec), #[cfg(feature = "clipboard")] ClipboardItem(Option), - - Seat(WlSeat), } #[derive(Debug)] @@ -303,6 +301,7 @@ impl Environment { debug!("received roundtrip request"); send!(env.response_tx, Response::Ok); } + #[cfg(feature = "ipc")] Msg(Request::OutputInfoAll) => { let infos = env.output_info_all(); send!(env.response_tx, Response::OutputInfoAll(infos)); diff --git a/src/clients/wayland/wl_output.rs b/src/clients/wayland/wl_output.rs index b162b6d2..4e236c68 100644 --- a/src/clients/wayland/wl_output.rs +++ b/src/clients/wayland/wl_output.rs @@ -1,4 +1,4 @@ -use super::{Client, Environment, Event, Request, Response}; +use super::{Client, Environment, Event}; use crate::try_send; use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState}; use tokio::sync::broadcast; @@ -8,8 +8,8 @@ use wayland_client::{Connection, QueueHandle}; #[derive(Debug, Clone)] pub struct OutputEvent { - output: OutputInfo, - event_type: OutputEventType, + pub output: OutputInfo, + pub event_type: OutputEventType, } #[derive(Debug, Clone, Copy)] @@ -21,7 +21,9 @@ pub enum OutputEventType { impl Client { /// Gets the information for all outputs. + #[cfg(feature = "ipc")] pub fn output_info_all(&self) -> Vec { + use super::{Request, Response}; match self.send_request(Request::OutputInfoAll) { Response::OutputInfoAll(info) => info, _ => unreachable!(), @@ -35,6 +37,7 @@ impl Client { } impl Environment { + #[cfg(feature = "ipc")] pub fn output_info_all(&mut self) -> Vec { self.output_state .outputs() diff --git a/src/error.rs b/src/error.rs index 002e1a1a..7ceb6b37 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,11 +4,9 @@ pub enum ExitCode { CreateBars = 2, } -pub const ERR_OUTPUTS: &str = "GTK and Wayland are reporting a different set of outputs - this is a severe bug and should never happen"; pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex"; pub const ERR_READ_LOCK: &str = "Failed to get read lock"; pub const ERR_WRITE_LOCK: &str = "Failed to get write lock"; pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel"; pub const ERR_CHANNEL_RECV: &str = "Failed to receive message from channel"; - pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object"; diff --git a/src/ipc/server.rs b/src/ipc/server.rs index 40f0c48d..4d30ee4d 100644 --- a/src/ipc/server.rs +++ b/src/ipc/server.rs @@ -116,12 +116,24 @@ impl Ipc { } Command::Reload => { info!("Closing existing bars"); + ironbar.bars.borrow_mut().clear(); + let windows = application.windows(); for window in windows { window.close(); } - *ironbar.bars.borrow_mut() = crate::load_interface(application, ironbar.clone()); + let wl = ironbar.clients.borrow_mut().wayland(); + let outputs = wl.output_info_all(); + + ironbar.reload_config(); + + for output in outputs { + match crate::load_output_bars(ironbar, application, output) { + Ok(mut bars) => ironbar.bars.borrow_mut().append(&mut bars), + Err(err) => error!("{err:?}"), + } + } Response::Ok } diff --git a/src/main.rs b/src/main.rs index 1c6a6907..ed84080a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,12 +21,14 @@ use glib::PropertySet; use gtk::gdk::Display; use gtk::prelude::*; use gtk::Application; +use smithay_client_toolkit::output::OutputInfo; use tokio::runtime::Runtime; use tokio::task::{block_in_place, JoinHandle}; use tracing::{debug, error, info, warn}; use universal_config::ConfigLoader; use crate::bar::{create_bar, Bar}; +use crate::clients::wayland::OutputEventType; use crate::clients::Clients; use crate::config::{Config, MonitorConfig}; use crate::error::ExitCode; @@ -93,6 +95,7 @@ fn run_with_args() { pub struct Ironbar { bars: Rc>>, clients: Rc>, + config: Rc>, } impl Ironbar { @@ -100,6 +103,7 @@ impl Ironbar { Self { bars: Rc::new(RefCell::new(vec![])), clients: Rc::new(RefCell::new(Clients::new())), + config: Rc::new(RefCell::new(load_config())), } } @@ -111,10 +115,15 @@ impl Ironbar { let running = AtomicBool::new(false); + // cannot use `oneshot` as `connect_activate` is not `FnOnce`. + let (activate_tx, activate_rx) = mpsc::channel(); + let instance = Rc::new(self); + let instance2 = instance.clone(); // force start wayland client ahead of ui let wl = instance.clients.borrow_mut().wayland(); + let mut rx_outputs = wl.subscribe_outputs(); wl.roundtrip(); app.connect_activate(move |app| { @@ -132,8 +141,6 @@ impl Ironbar { } } - *instance.bars.borrow_mut() = load_interface(app, instance.clone()); - let style_path = env::var("IRONBAR_CSS").ok().map_or_else( || { config_dir().map_or_else( @@ -170,10 +177,43 @@ impl Ironbar { 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 + let hold = app.hold(); + send!(activate_tx, hold); }); + { + let instance = instance2; + let app = app.clone(); + + glib::spawn_future_local(async move { + let _hold = activate_rx.recv().expect("to receive activation signal"); + debug!("Received activation signal, initialising bars"); + + while let Ok(event) = rx_outputs.recv().await { + match event.event_type { + OutputEventType::New => { + match load_output_bars(&instance, &app, event.output) { + Ok(mut new_bars) => { + instance.bars.borrow_mut().append(&mut new_bars) + } + Err(err) => error!("{err:?}"), + } + } + OutputEventType::Destroyed => { + let Some(name) = event.output.name else { + continue; + }; + instance + .bars + .borrow_mut() + .retain(|bar| bar.monitor_name() != name); + } + OutputEventType::Update => {} + } + } + }); + } + // Ignore CLI args // Some are provided by swaybar_config but not currently supported app.run_with_args(&Vec::<&str>::new()); @@ -215,6 +255,13 @@ impl Ironbar { .find(|&bar| bar.name() == name) .cloned() } + + /// Re-reads the config file from disk and replaces the active config. + /// Note this does *not* reload bars, which must be performed separately. + #[cfg(feature = "ipc")] + fn reload_config(&self) { + self.config.replace(load_config()); + } } fn start_ironbar() { @@ -222,17 +269,8 @@ fn start_ironbar() { ironbar.start(); } -/// Loads the Ironbar config and interface. -pub fn load_interface(app: &Application, ironbar: Rc) -> Vec { - let display = Display::default().map_or_else( - || { - let report = Report::msg("Failed to get default GTK display"); - error!("{:?}", report); - exit(ExitCode::GtkDisplay as i32) - }, - |display| display, - ); - +/// Loads the config file from disk. +fn load_config() -> Config { let mut config = env::var("IRONBAR_CONFIG") .map_or_else( |_| ConfigLoader::new("ironbar").find_and_load(), @@ -259,89 +297,77 @@ pub fn load_interface(app: &Application, ironbar: Rc) -> Vec { } } - match create_bars(app, &display, &config, &ironbar) { - Ok(bars) => { - debug!("Created {} bars", bars.len()); - bars - } - Err(err) => { - error!("{:?}", err); - exit(ExitCode::CreateBars as i32); - } - } + config } -/// Creates each of the bars across each of the (configured) outputs. -fn create_bars( - app: &Application, - display: &Display, - config: &Config, +/// Gets the GDK `Display` instance. +fn get_display() -> Display { + Display::default().map_or_else( + || { + let report = Report::msg("Failed to get default GTK display"); + error!("{:?}", report); + exit(ExitCode::GtkDisplay as i32) + }, + |display| display, + ) +} + +/// Loads all the bars associated with an output. +fn load_output_bars( ironbar: &Rc, + app: &Application, + output: OutputInfo, ) -> Result> { - let wl = ironbar.clients.borrow_mut().wayland(); - let outputs = wl.output_info_all(); + let Some(monitor_name) = &output.name else { + return Err(Report::msg("Output missing monitor name")); + }; - debug!("Received {} outputs from Wayland", outputs.len()); - debug!("Outputs: {:?}", outputs); + let config = ironbar.config.borrow(); + let display = get_display(); - let num_monitors = display.n_monitors(); + let pos = output.logical_position.unwrap_or_default(); + let monitor = display.monitor_at_point(pos.0, pos.1).unwrap(); let show_default_bar = config.start.is_some() || config.center.is_some() || config.end.is_some(); - let mut all_bars = vec![]; - for i in 0..num_monitors { - let monitor = display - .monitor(i) - .ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?; - let output = outputs - .get(i as usize) - .ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?; - - let Some(monitor_name) = &output.name else { - continue; - }; - - 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(), - ironbar.clone(), - )?] - } - Some(MonitorConfig::Multiple(configs)) => configs - .iter() - .map(|config| { - create_bar( - app, - &monitor, - monitor_name.to_string(), - config.clone(), - ironbar.clone(), - ) - }) - .collect::>()?, - None if show_default_bar => vec![create_bar( + let 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(), ironbar.clone(), - )?], - None => vec![], - }; - - all_bars.append(&mut bars); - } - - Ok(all_bars) + )?] + } + Some(MonitorConfig::Multiple(configs)) => configs + .iter() + .map(|config| { + create_bar( + app, + &monitor, + monitor_name.to_string(), + config.clone(), + ironbar.clone(), + ) + }) + .collect::>()?, + None if show_default_bar => vec![create_bar( + app, + &monitor, + monitor_name.to_string(), + config.clone(), + ironbar.clone(), + )?], + None => vec![], + }; + + Ok(bars) } fn create_runtime() -> Runtime {