mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-07-01 18:51:04 +02:00
Merge pull request #405 from JakeStanger/feat/dpms-support
feat: load bars on monitor when it connects
This commit is contained in:
commit
e737177ab0
6 changed files with 139 additions and 96 deletions
|
@ -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 { .. } => {
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
132
src/main.rs
132
src/main.rs
|
@ -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 {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue