Skip to content

Commit

Permalink
feat: load bars on monitor when it connects
Browse files Browse the repository at this point in the history
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
  • Loading branch information
JakeStanger committed Jan 11, 2024
1 parent 3dc198e commit 16869e8
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 93 deletions.
5 changes: 5 additions & 0 deletions src/bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RefCell<Popup>> {
match &self.inner {
Inner::New { .. } => {
Expand Down
6 changes: 1 addition & 5 deletions src/clients/wayland/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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! {
Expand Down Expand Up @@ -81,16 +81,12 @@ pub enum Response {
/// An empty success response
Ok,

OutputInfo(Option<OutputInfo>),
OutputInfoAll(Vec<OutputInfo>),

ToplevelInfo(Option<ToplevelInfo>),
ToplevelInfoAll(Vec<ToplevelInfo>),

#[cfg(feature = "clipboard")]
ClipboardItem(Option<ClipboardItem>),

Seat(WlSeat),
}

#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions src/clients/wayland/wl_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
2 changes: 0 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Check warning on line 12 in src/error.rs

View workflow job for this annotation

GitHub Actions / clippy

constant `ERR_WAYLAND_DATA` is never used

warning: constant `ERR_WAYLAND_DATA` is never used --> src/error.rs:12:11 | 12 | pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object"; | ^^^^^^^^^^^^^^^^
12 changes: 11 additions & 1 deletion src/ipc/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,17 @@ impl Ipc {
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
}
Expand Down
191 changes: 108 additions & 83 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -93,13 +95,15 @@ fn run_with_args() {
pub struct Ironbar {
bars: Rc<RefCell<Vec<Bar>>>,
clients: Rc<RefCell<Clients>>,
config: Rc<RefCell<Config>>,
}

impl Ironbar {
fn new() -> Self {
Self {
bars: Rc::new(RefCell::new(vec![])),
clients: Rc::new(RefCell::new(Clients::new())),
config: Rc::new(RefCell::new(load_config())),
}
}

Expand All @@ -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| {
Expand All @@ -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(
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -215,24 +255,21 @@ 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.
fn reload_config(&self) {

Check warning on line 261 in src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

method `reload_config` is never used

warning: method `reload_config` is never used --> src/main.rs:261:8 | 101 | impl Ironbar { | ------------ method in this implementation ... 261 | fn reload_config(&self) { | ^^^^^^^^^^^^^ | = note: `#[warn(dead_code)]` on by default
self.config.replace(load_config());
}
}

fn start_ironbar() {
let ironbar = Ironbar::new();
ironbar.start();
}

/// Loads the Ironbar config and interface.
pub fn load_interface(app: &Application, ironbar: Rc<Ironbar>) -> Vec<Bar> {
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")

Check warning on line 273 in src/main.rs

View workflow job for this annotation

GitHub Actions / clippy

variable does not need to be mutable

warning: variable does not need to be mutable --> src/main.rs:273:9 | 273 | let mut config = env::var("IRONBAR_CONFIG") | ----^^^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default
.map_or_else(
|_| ConfigLoader::new("ironbar").find_and_load(),
Expand All @@ -259,89 +296,77 @@ pub fn load_interface(app: &Application, ironbar: Rc<Ironbar>) -> Vec<Bar> {
}
}

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<Ironbar>,
app: &Application,
output: OutputInfo,
) -> Result<Vec<Bar>> {
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::<Result<_>>()?,
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::<Result<_>>()?,
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 {
Expand Down

0 comments on commit 16869e8

Please sign in to comment.