1 Commits

Author SHA1 Message Date
Jake Stanger
3fad8c6a16 wip output events rework 2023-09-25 22:52:17 +01:00
6 changed files with 318 additions and 118 deletions

145
src/cached_broadcast.rs Normal file
View File

@@ -0,0 +1,145 @@
use crate::{arc_rw, read_lock, send_async, write_lock};
use std::fmt::Debug;
use std::ops::Deref;
use std::sync::{Arc, RwLock};
// use std::thread::sleep;
use std::time::Duration;
use tokio::spawn;
use tokio::sync::mpsc;
use tokio::task::spawn_blocking;
use tokio::time::sleep;
use tracing::trace;
pub trait Cacheable: Debug + Clone + Send + Sync {
type Key: Debug + Clone + Send + Sync + Eq;
fn get_key(&self) -> Self::Key;
}
pub type Sender<T> = mpsc::Sender<Event<T>>;
pub type Receiver<T> = mpsc::Receiver<Event<T>>;
pub struct CachedBroadcastChannel<T>
where
T: Cacheable,
{
capacity: usize,
data: Vec<T>,
channels: Arc<RwLock<Vec<mpsc::Sender<Event<T>>>>>,
base_tx: mpsc::Sender<Event<T>>,
}
#[derive(Debug, Clone)]
pub enum Event<T>
where
T: Cacheable,
{
Add(T),
Remove(T::Key),
Replace(T::Key, T),
}
impl<T> CachedBroadcastChannel<T>
where
T: Cacheable + 'static,
{
pub fn new(capacity: usize) -> Self {
let (tx, rx) = mpsc::channel::<Event<T>>(capacity);
let mut rx = DropDetector(rx);
// spawn_blocking(move || loop {
// let ev = rx.0.try_recv();
// println!("{ev:?}");
// sleep(Duration::from_secs(1))
// });
let channels = arc_rw!(Vec::<Sender<T>>::new());
let channels = Arc::clone(&channels);
spawn(async move {
println!("hello");
while let Some(event) = rx.0.recv().await {
println!("ev");
// trace!("{event:?}");
// let iter = read_lock!(channels).clone().into_iter();
// for channel in iter {
// send_async!(channel, event.clone());
// }
}
println!("goodbye");
});
Self {
capacity,
data: vec![],
channels,
base_tx: tx,
}
}
pub async fn send(&mut self, event: Event<T>) {
match event.clone() {
Event::Add(data) => {
self.data.push(data);
}
Event::Remove(key) => {
let Some(index) = self.data.iter().position(|t| t.get_key() == key) else {
return;
};
self.data.remove(index);
}
Event::Replace(key, data) => {
let Some(index) = self.data.iter().position(|t| t.get_key() == key) else {
return;
};
let _ = std::mem::replace(&mut self.data[index], data);
}
}
send_async!(self.base_tx, event);
// let mut closed = vec![];
// for (i, channel) in read_lock!(self.channels).iter().enumerate() {
// if channel.is_closed() {
// closed.push(i);
// } else {
// send_async!(channel, event.clone());
// }
// }
//
// for channel in closed.into_iter().rev() {
// write_lock!(self.channels).remove(channel);
// }
}
pub fn sender(&self) -> mpsc::Sender<Event<T>> {
self.base_tx.clone()
}
pub fn receiver(&mut self) -> mpsc::Receiver<Event<T>> {
let (tx, rx) = mpsc::channel(self.capacity);
write_lock!(self.channels).push(tx);
rx
}
pub fn data(&self) -> &Vec<T> {
&self.data
}
}
#[derive(Debug)]
struct DropDetector<T>(T);
impl<T> Drop for DropDetector<T> {
fn drop(&mut self) {
println!("DROPPED")
}
}
impl<T: Cacheable> Drop for CachedBroadcastChannel<T> {
fn drop(&mut self) {
println!("Channel DROPPED")
}
}

View File

@@ -2,8 +2,9 @@ use super::wlr_foreign_toplevel::handle::ToplevelHandle;
use super::wlr_foreign_toplevel::manager::ToplevelManagerState;
use super::wlr_foreign_toplevel::ToplevelEvent;
use super::Environment;
use crate::cached_broadcast::CachedBroadcastChannel;
use crate::error::ERR_CHANNEL_RECV;
use crate::send;
use crate::{cached_broadcast, send};
use cfg_if::cfg_if;
use color_eyre::Report;
use smithay_client_toolkit::output::{OutputInfo, OutputState};
@@ -31,11 +32,8 @@ cfg_if! {
#[derive(Debug)]
pub enum Request {
/// Sends a request for all the outputs.
/// These are then sent on the `output` channel.
Outputs,
/// Sends a request for all the seats.
/// These are then sent ont the `seat` channel.
/// These are then sent on the `seat` channel.
Seats,
/// Sends a request for all the toplevels.
/// These are then sent on the `toplevel_init` channel.
@@ -53,8 +51,10 @@ pub enum Request {
pub struct WaylandClient {
// External channels
output_channel: CachedBroadcastChannel<OutputInfo>,
toplevel_tx: broadcast::Sender<ToplevelEvent>,
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
#[cfg(feature = "clipboard")]
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
#[cfg(feature = "clipboard")]
@@ -62,7 +62,6 @@ pub struct WaylandClient {
// Internal channels
toplevel_init_rx: mpsc::Receiver<HashMap<usize, ToplevelHandle>>,
output_rx: mpsc::Receiver<Vec<OutputInfo>>,
seat_rx: mpsc::Receiver<Vec<WlSeat>>,
#[cfg(feature = "clipboard")]
clipboard_init_rx: mpsc::Receiver<Option<Arc<ClipboardItem>>>,
@@ -74,10 +73,14 @@ impl WaylandClient {
pub(super) fn new() -> Self {
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
let mut output_channel = CachedBroadcastChannel::new(8);
let output_tx = output_channel.sender();
let tx2 = output_tx.clone();
let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel();
#[cfg(feature = "clipboard")]
let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel();
let (output_tx, output_rx) = mpsc::channel();
let (seat_tx, seat_rx) = mpsc::channel();
let toplevel_tx2 = toplevel_tx.clone();
@@ -99,6 +102,7 @@ impl WaylandClient {
let conn =
Connection::connect_to_env().expect("Failed to connect to Wayland compositor");
let (globals, queue) =
registry_queue_init(&conn).expect("Failed to retrieve Wayland globals");
@@ -139,6 +143,7 @@ impl WaylandClient {
handles: HashMap::new(),
#[cfg(feature = "clipboard")]
clipboard: crate::arc_mut!(None),
output_tx,
toplevel_tx,
#[cfg(feature = "clipboard")]
clipboard_tx,
@@ -156,11 +161,6 @@ impl WaylandClient {
trace!("{event:?}");
match event {
Event::Msg(Request::Roundtrip) => debug!("Received refresh event"),
Event::Msg(Request::Outputs) => {
trace!("Received get outputs request");
send!(output_tx, env.output_info());
}
Event::Msg(Request::Seats) => {
trace!("Receive get seats request");
send!(seat_tx, env.seats.clone());
@@ -196,12 +196,12 @@ impl WaylandClient {
});
Self {
output_channel,
toplevel_tx,
_toplevel_rx: toplevel_rx,
toplevel_init_rx,
#[cfg(feature = "clipboard")]
clipboard_init_rx,
output_rx,
seat_rx,
#[cfg(feature = "clipboard")]
clipboard_tx,
@@ -242,6 +242,10 @@ impl WaylandClient {
(rx, data)
}
pub fn subscribe_outputs(&mut self) -> cached_broadcast::Receiver<OutputInfo> {
self.output_channel.receiver()
}
/// Force a roundtrip on the wayland connection,
/// flushing any queued events and immediately receiving any new ones.
pub fn roundtrip(&self) {
@@ -249,11 +253,13 @@ impl WaylandClient {
send!(self.request_tx, Request::Roundtrip);
}
pub fn get_outputs(&self) -> Vec<OutputInfo> {
trace!("Sending get outputs request");
send!(self.request_tx, Request::Outputs);
self.output_rx.recv().expect(ERR_CHANNEL_RECV)
/// Gets a list of all outputs.
///
/// This should only be used in a scenario
/// where you need a snapshot of outputs at the current time.
/// Prefer to listen to output events with `subscribe_output` where possible.
pub fn get_outputs(&self) -> &Vec<OutputInfo> {
self.output_channel.data()
}
pub fn get_seats(&self) -> Vec<WlSeat> {

View File

@@ -6,10 +6,10 @@ mod wl_seat;
mod wlr_foreign_toplevel;
use self::wlr_foreign_toplevel::manager::ToplevelManagerState;
use crate::{arc_mut, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
use crate::{arc_mut, cached_broadcast, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager};
use cfg_if::cfg_if;
use lazy_static::lazy_static;
use smithay_client_toolkit::output::OutputState;
use smithay_client_toolkit::output::{OutputInfo, OutputState};
use smithay_client_toolkit::reexports::calloop::LoopHandle;
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
use smithay_client_toolkit::seat::SeatState;
@@ -65,6 +65,7 @@ pub struct Environment {
#[cfg(feature = "clipboard")]
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
output_tx: cached_broadcast::Sender<OutputInfo>,
toplevel_tx: broadcast::Sender<ToplevelEvent>,
#[cfg(feature = "clipboard")]
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,

View File

@@ -1,4 +1,6 @@
use super::Environment;
use crate::cached_broadcast::Cacheable;
use crate::{cached_broadcast, try_send};
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
use tracing::debug;
use wayland_client::protocol::wl_output;
@@ -31,9 +33,12 @@ impl OutputHandler for Environment {
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
output: wl_output::WlOutput,
) {
debug!("Handler received new output");
if let Some(info) = self.output_state.info(&output) {
try_send!(self.output_tx, cached_broadcast::Event::Add(info));
};
}
fn update_output(
@@ -42,14 +47,26 @@ impl OutputHandler for Environment {
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
debug!("Handle received output update");
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
output: wl_output::WlOutput,
) {
debug!("Handle received output destruction");
if let Some(info) = self.output_state.info(&output) {
try_send!(self.output_tx, cached_broadcast::Event::Remove(info.id));
};
}
}
impl Cacheable for OutputInfo {
type Key = u32;
fn get_key(&self) -> Self::Key {
self.id
}
}

View File

@@ -18,7 +18,7 @@ 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::{await_sync, read_lock, send_async, try_send, write_lock, GlobalState};
use super::Ipc;
@@ -123,14 +123,7 @@ impl Ipc {
Response::Ok
}
Command::Reload => {
info!("Closing existing bars");
let windows = application.windows();
for window in windows {
window.close();
}
crate::load_interface(application, global_state);
await_sync(async move { crate::reload(application, global_state).await }).unwrap();
Response::Ok
}
Command::Set { key, value } => {

View File

@@ -14,10 +14,12 @@ use clap::Parser;
use color_eyre::eyre::Result;
use color_eyre::Report;
use dirs::config_dir;
use gtk::gdk::Display;
use gtk::gdk::{Display, Monitor};
use gtk::prelude::*;
use gtk::Application;
use smithay_client_toolkit::output::OutputInfo;
use tokio::runtime::Handle;
use tokio::spawn;
use tokio::task::{block_in_place, spawn_blocking};
use tracing::{debug, error, info, warn};
use universal_config::ConfigLoader;
@@ -25,6 +27,8 @@ use universal_config::ConfigLoader;
use clients::wayland;
use crate::bar::create_bar;
use crate::bridge_channel::BridgeChannel;
use crate::cached_broadcast::Event;
use crate::config::{Config, MonitorConfig};
use crate::error::ExitCode;
use crate::global_state::GlobalState;
@@ -32,6 +36,7 @@ use crate::style::load_css;
mod bar;
mod bridge_channel;
mod cached_broadcast;
#[cfg(feature = "cli")]
mod cli;
mod clients;
@@ -84,19 +89,22 @@ async fn run_with_args(global_state: Rc<RefCell<GlobalState>>) {
Err(err) => error!("{err:?}"),
};
}
None => start_ironbar(global_state),
None => start_ironbar(global_state).await,
}
}
fn start_ironbar(global_state: Rc<RefCell<GlobalState>>) {
async fn start_ironbar(global_state: Rc<RefCell<GlobalState>>) {
info!("Ironbar version {}", VERSION);
info!("Starting application");
let app = Application::builder().application_id(GTK_APP_ID).build();
let _ = wayland::get_client(); // force-init
let output_bridge = BridgeChannel::<Event<OutputInfo>>::new();
let output_tx = output_bridge.create_sender();
let running = Rc::new(Cell::new(false));
let global_state2 = global_state.clone();
app.connect_activate(move |app| {
if running.get() {
info!("Ironbar already running, returning");
@@ -107,13 +115,11 @@ fn start_ironbar(global_state: Rc<RefCell<GlobalState>>) {
cfg_if! {
if #[cfg(feature = "ipc")] {
let ipc = ipc::Ipc::new(global_state.clone());
let ipc = ipc::Ipc::new(global_state2.clone());
ipc.start(app);
}
}
load_interface(app, &global_state);
let style_path = env::var("IRONBAR_CSS").ok().map_or_else(
|| {
config_dir().map_or_else(
@@ -147,27 +153,131 @@ fn start_ironbar(global_state: Rc<RefCell<GlobalState>>) {
exit(0);
});
ctrlc::set_handler(move || tx.send(()).expect("Could not send signal on channel."))
.expect("Error setting Ctrl-C handler");
let wc = wayland::get_client();
let output_tx = output_tx.clone();
let mut output_rx = lock!(wc).subscribe_outputs();
spawn(async move {
while let Some(event) = output_rx.recv().await {
try_send!(output_tx.clone(), event);
}
});
ctrlc::set_handler(move || send!(tx, ())).expect("Error setting Ctrl-C handler");
});
let config = load_config();
{
let app = app.clone();
let global_state = global_state.clone();
output_bridge.recv(move |event: cached_broadcast::Event<_>| {
let display = get_display();
match event {
Event::Add(output) => {
debug!("Adding bar(s) for monitor {:?}", &output.name);
create_bars_for_monitor(&app, &display, &output, config.clone(), &global_state)
.unwrap();
}
// TODO: Implement
Event::Remove(_) => {}
Event::Replace(_, _) => {}
}
Continue(true)
});
}
// Ignore CLI args
// Some are provided by swaybar_config but not currently supported
app.run_with_args(&Vec::<&str>::new());
}
/// Loads the Ironbar config and interface.
pub fn load_interface(app: &Application, global_state: &Rc<RefCell<GlobalState>>) {
let display = Display::default().map_or_else(
/// Closes all current bars and entirely reloads Ironbar.
/// This re-reads the config file.
pub async fn reload(app: &Application, global_state: &Rc<RefCell<GlobalState>>) -> Result<()> {
info!("Closing existing bars");
let windows = app.windows();
for window in windows {
window.close();
}
let config = load_config();
let wl = wayland::get_client();
let wl = lock!(wl);
let outputs = wl.get_outputs();
let display = get_display();
for output in outputs.iter() {
create_bars_for_monitor(app, &display, output, config.clone(), global_state)?;
}
Ok(())
}
fn create_bars_for_monitor(
app: &Application,
display: &Display,
output: &OutputInfo,
config: Config,
global_state: &Rc<RefCell<GlobalState>>,
) -> Result<()> {
let Some(monitor_name) = &output.name else {
return Ok(());
};
let monitor = match get_monitor(&monitor_name, display) {
Ok(monitor) => monitor,
Err(err) => return Err(err),
};
let Some(monitor_config) = config.get_monitor_config(&monitor_name) else {
return Ok(());
};
match monitor_config {
MonitorConfig::Single(config) => {
create_bar(&app, &monitor, &monitor_name, config, global_state)
}
MonitorConfig::Multiple(configs) => configs
.into_iter()
.map(|config| create_bar(&app, &monitor, &monitor_name, config, global_state))
.collect(),
}
}
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,
);
)
}
let mut config = env::var("IRONBAR_CONFIG")
fn get_monitor(name: &str, display: &Display) -> Result<Monitor> {
let wl = wayland::get_client();
let wl = lock!(wl);
let outputs = wl.get_outputs();
let monitor = (0..display.n_monitors()).into_iter().find_map(|i| {
let monitor = display.monitor(i)?;
let output = outputs.get(i as usize)?;
let is_match = output.name.as_ref().map(|n| n == name).unwrap_or_default();
if is_match {
Some(monitor)
} else {
None
}
});
monitor.ok_or_else(|| Report::msg(error::ERR_OUTPUTS))
}
fn load_config() -> Config {
let config = env::var("IRONBAR_CONFIG")
.map_or_else(
|_| ConfigLoader::new("ironbar").find_and_load(),
ConfigLoader::load,
@@ -182,79 +292,7 @@ pub fn load_interface(app: &Application, global_state: &Rc<RefCell<GlobalState>>
});
debug!("Loaded config file");
#[cfg(feature = "ipc")]
if let Some(ironvars) = config.ironvar_defaults.take() {
let variable_manager = ironvar::get_variable_manager();
for (k, v) in ironvars {
if write_lock!(variable_manager).set(k.clone(), v).is_err() {
warn!("Ignoring invalid ironvar: '{k}'");
}
}
}
if let Err(err) = create_bars(app, &display, &config, global_state) {
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<RefCell<GlobalState>>,
) -> Result<()> {
let wl = wayland::get_client();
let outputs = lock!(wl).get_outputs();
debug!("Received {} outputs from Wayland", outputs.len());
debug!("Outputs: {:?}", outputs);
let num_monitors = display.n_monitors();
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;
};
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)?;
}
Ok(())
}
_ => Ok(()),
}
},
)?;
}
Ok(())
config
}
/// Blocks on a `Future` until it resolves.