1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-07-01 18:51:04 +02:00

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
This commit is contained in:
Jake Stanger 2024-01-11 23:22:18 +00:00
parent 3dc198ee41
commit 8371a92204
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
6 changed files with 139 additions and 96 deletions

View file

@ -308,6 +308,11 @@ impl Bar {
&self.name &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<RefCell<Popup>> { pub fn popup(&self) -> Rc<RefCell<Popup>> {
match &self.inner { match &self.inner {
Inner::New { .. } => { Inner::New { .. } => {

View file

@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex};
use calloop_channel::Event::Msg; use calloop_channel::Event::Msg;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use color_eyre::Report; 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::channel as calloop_channel;
use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle}; use smithay_client_toolkit::reexports::calloop::{EventLoop, LoopHandle};
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource; use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
@ -25,12 +25,11 @@ use smithay_client_toolkit::{
use tokio::sync::{broadcast, mpsc}; use tokio::sync::{broadcast, mpsc};
use tracing::{debug, error, trace}; use tracing::{debug, error, trace};
use wayland_client::globals::registry_queue_init; use wayland_client::globals::registry_queue_init;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::{Connection, QueueHandle}; use wayland_client::{Connection, QueueHandle};
use wlr_foreign_toplevel::manager::ToplevelManagerState; 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}; pub use wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
cfg_if! { cfg_if! {
@ -42,6 +41,7 @@ cfg_if! {
use self::wlr_data_control::manager::DataControlDeviceManagerState; use self::wlr_data_control::manager::DataControlDeviceManagerState;
use self::wlr_data_control::source::CopyPasteSource; use self::wlr_data_control::source::CopyPasteSource;
use self::wlr_data_control::SelectionOfferItem; use self::wlr_data_control::SelectionOfferItem;
use wayland_client::protocol::wl_seat::WlSeat;
pub use wlr_data_control::{ClipboardItem, ClipboardValue}; pub use wlr_data_control::{ClipboardItem, ClipboardValue};
@ -65,6 +65,7 @@ pub enum Event {
pub enum Request { pub enum Request {
Roundtrip, Roundtrip,
#[cfg(feature = "ipc")]
OutputInfoAll, OutputInfoAll,
ToplevelInfoAll, ToplevelInfoAll,
@ -81,16 +82,13 @@ pub enum Response {
/// An empty success response /// An empty success response
Ok, Ok,
OutputInfo(Option<OutputInfo>), #[cfg(feature = "ipc")]
OutputInfoAll(Vec<OutputInfo>), OutputInfoAll(Vec<smithay_client_toolkit::output::OutputInfo>),
ToplevelInfo(Option<ToplevelInfo>),
ToplevelInfoAll(Vec<ToplevelInfo>), ToplevelInfoAll(Vec<ToplevelInfo>),
#[cfg(feature = "clipboard")] #[cfg(feature = "clipboard")]
ClipboardItem(Option<ClipboardItem>), ClipboardItem(Option<ClipboardItem>),
Seat(WlSeat),
} }
#[derive(Debug)] #[derive(Debug)]
@ -303,6 +301,7 @@ impl Environment {
debug!("received roundtrip request"); debug!("received roundtrip request");
send!(env.response_tx, Response::Ok); send!(env.response_tx, Response::Ok);
} }
#[cfg(feature = "ipc")]
Msg(Request::OutputInfoAll) => { Msg(Request::OutputInfoAll) => {
let infos = env.output_info_all(); let infos = env.output_info_all();
send!(env.response_tx, Response::OutputInfoAll(infos)); send!(env.response_tx, Response::OutputInfoAll(infos));

View file

@ -1,4 +1,4 @@
use super::{Client, Environment, Event, Request, Response}; use super::{Client, Environment, Event};
use crate::try_send; use crate::try_send;
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState}; use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
use tokio::sync::broadcast; use tokio::sync::broadcast;
@ -8,8 +8,8 @@ use wayland_client::{Connection, QueueHandle};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct OutputEvent { pub struct OutputEvent {
output: OutputInfo, pub output: OutputInfo,
event_type: OutputEventType, pub event_type: OutputEventType,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -21,7 +21,9 @@ pub enum OutputEventType {
impl Client { impl Client {
/// Gets the information for all outputs. /// Gets the information for all outputs.
#[cfg(feature = "ipc")]
pub fn output_info_all(&self) -> Vec<OutputInfo> { pub fn output_info_all(&self) -> Vec<OutputInfo> {
use super::{Request, Response};
match self.send_request(Request::OutputInfoAll) { match self.send_request(Request::OutputInfoAll) {
Response::OutputInfoAll(info) => info, Response::OutputInfoAll(info) => info,
_ => unreachable!(), _ => unreachable!(),
@ -35,6 +37,7 @@ impl Client {
} }
impl Environment { impl Environment {
#[cfg(feature = "ipc")]
pub fn output_info_all(&mut self) -> Vec<OutputInfo> { pub fn output_info_all(&mut self) -> Vec<OutputInfo> {
self.output_state self.output_state
.outputs() .outputs()

View file

@ -4,11 +4,9 @@ pub enum ExitCode {
CreateBars = 2, 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_MUTEX_LOCK: &str = "Failed to get lock on Mutex";
pub const ERR_READ_LOCK: &str = "Failed to get read lock"; 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_WRITE_LOCK: &str = "Failed to get write lock";
pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel"; 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_CHANNEL_RECV: &str = "Failed to receive message from channel";
pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object"; pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object";

View file

@ -116,12 +116,24 @@ impl Ipc {
} }
Command::Reload => { Command::Reload => {
info!("Closing existing bars"); info!("Closing existing bars");
ironbar.bars.borrow_mut().clear();
let windows = application.windows(); let windows = application.windows();
for window in windows { for window in windows {
window.close(); 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 Response::Ok
} }

View file

@ -21,12 +21,14 @@ use glib::PropertySet;
use gtk::gdk::Display; use gtk::gdk::Display;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Application; use gtk::Application;
use smithay_client_toolkit::output::OutputInfo;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::task::{block_in_place, JoinHandle}; use tokio::task::{block_in_place, JoinHandle};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
use universal_config::ConfigLoader; use universal_config::ConfigLoader;
use crate::bar::{create_bar, Bar}; use crate::bar::{create_bar, Bar};
use crate::clients::wayland::OutputEventType;
use crate::clients::Clients; use crate::clients::Clients;
use crate::config::{Config, MonitorConfig}; use crate::config::{Config, MonitorConfig};
use crate::error::ExitCode; use crate::error::ExitCode;
@ -93,6 +95,7 @@ fn run_with_args() {
pub struct Ironbar { pub struct Ironbar {
bars: Rc<RefCell<Vec<Bar>>>, bars: Rc<RefCell<Vec<Bar>>>,
clients: Rc<RefCell<Clients>>, clients: Rc<RefCell<Clients>>,
config: Rc<RefCell<Config>>,
} }
impl Ironbar { impl Ironbar {
@ -100,6 +103,7 @@ impl Ironbar {
Self { Self {
bars: Rc::new(RefCell::new(vec![])), bars: Rc::new(RefCell::new(vec![])),
clients: Rc::new(RefCell::new(Clients::new())), 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); 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 instance = Rc::new(self);
let instance2 = instance.clone();
// force start wayland client ahead of ui // force start wayland client ahead of ui
let wl = instance.clients.borrow_mut().wayland(); let wl = instance.clients.borrow_mut().wayland();
let mut rx_outputs = wl.subscribe_outputs();
wl.roundtrip(); wl.roundtrip();
app.connect_activate(move |app| { 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( let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|| { || {
config_dir().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.")) ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
.expect("Error setting Ctrl-C handler"); .expect("Error setting Ctrl-C handler");
// TODO: Start wayland client - listen for outputs let hold = app.hold();
// All bar loading should happen as an event response to this 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 // Ignore CLI args
// Some are provided by swaybar_config but not currently supported // Some are provided by swaybar_config but not currently supported
app.run_with_args(&Vec::<&str>::new()); app.run_with_args(&Vec::<&str>::new());
@ -215,6 +255,13 @@ impl Ironbar {
.find(|&bar| bar.name() == name) .find(|&bar| bar.name() == name)
.cloned() .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() { fn start_ironbar() {
@ -222,17 +269,8 @@ fn start_ironbar() {
ironbar.start(); ironbar.start();
} }
/// Loads the Ironbar config and interface. /// Loads the config file from disk.
pub fn load_interface(app: &Application, ironbar: Rc<Ironbar>) -> Vec<Bar> { fn load_config() -> Config {
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,
);
let mut config = env::var("IRONBAR_CONFIG") let mut config = env::var("IRONBAR_CONFIG")
.map_or_else( .map_or_else(
|_| ConfigLoader::new("ironbar").find_and_load(), |_| ConfigLoader::new("ironbar").find_and_load(),
@ -259,50 +297,41 @@ pub fn load_interface(app: &Application, ironbar: Rc<Ironbar>) -> Vec<Bar> {
} }
} }
match create_bars(app, &display, &config, &ironbar) { config
Ok(bars) => {
debug!("Created {} bars", bars.len());
bars
}
Err(err) => {
error!("{:?}", err);
exit(ExitCode::CreateBars as i32);
}
}
} }
/// Creates each of the bars across each of the (configured) outputs. /// Gets the GDK `Display` instance.
fn create_bars( fn get_display() -> Display {
app: &Application, Display::default().map_or_else(
display: &Display, || {
config: &Config, 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<Ironbar>, ironbar: &Rc<Ironbar>,
app: &Application,
output: OutputInfo,
) -> Result<Vec<Bar>> { ) -> Result<Vec<Bar>> {
let wl = ironbar.clients.borrow_mut().wayland(); let Some(monitor_name) = &output.name else {
let outputs = wl.output_info_all(); return Err(Report::msg("Output missing monitor name"));
};
debug!("Received {} outputs from Wayland", outputs.len()); let config = ironbar.config.borrow();
debug!("Outputs: {:?}", outputs); 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 = let show_default_bar =
config.start.is_some() || config.center.is_some() || config.end.is_some(); config.start.is_some() || config.center.is_some() || config.end.is_some();
let mut all_bars = vec![]; let bars = match config
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 .monitors
.as_ref() .as_ref()
.and_then(|config| config.get(monitor_name)) .and_then(|config| config.get(monitor_name))
@ -338,10 +367,7 @@ fn create_bars(
None => vec![], None => vec![],
}; };
all_bars.append(&mut bars); Ok(bars)
}
Ok(all_bars)
} }
fn create_runtime() -> Runtime { fn create_runtime() -> Runtime {