mirror of
https://github.com/Zedfrigg/ironbar.git
synced 2025-08-17 14:51:04 +02:00
refactor: major client code changes
This does away with `lazy_static` singletons for all the clients, instead putting them all inside a `Clients` struct on the `Ironbar` struct. Client code has been refactored in places to accommodate this, and module code has been updated to get the clients the new way. The Wayland client has been re-written from the ground up to remove a lot of the needless complications, provide a nicer interface and reduce some duplicate data. The MPD music client has been overhauled to use the `mpd_utils` crate, which simplifies the code within Ironbar and should offer more robustness and better recovery when connection is lost to the server. The launcher module in particular has been affected by the refactor.
This commit is contained in:
parent
57b57ed002
commit
c702f6fffa
33 changed files with 1081 additions and 1063 deletions
|
@ -1,28 +1,37 @@
|
|||
mod client;
|
||||
|
||||
mod macros;
|
||||
mod wl_output;
|
||||
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::error::ERR_CHANNEL_RECV;
|
||||
use crate::{
|
||||
arc_mut, delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager, lock,
|
||||
register_client, send, Ironbar,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use calloop_channel::Event::Msg;
|
||||
use cfg_if::cfg_if;
|
||||
use lazy_static::lazy_static;
|
||||
use smithay_client_toolkit::output::OutputState;
|
||||
use smithay_client_toolkit::reexports::calloop::LoopHandle;
|
||||
use color_eyre::Report;
|
||||
use smithay_client_toolkit::output::{OutputInfo, 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;
|
||||
use smithay_client_toolkit::registry::{ProvidesRegistryState, RegistryState};
|
||||
use smithay_client_toolkit::seat::SeatState;
|
||||
use smithay_client_toolkit::{
|
||||
delegate_output, delegate_registry, delegate_seat, registry_handlers,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tokio::sync::broadcast;
|
||||
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};
|
||||
|
||||
pub use self::client::WaylandClient;
|
||||
pub use self::wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
||||
use wlr_foreign_toplevel::manager::ToplevelManagerState;
|
||||
|
||||
pub use wl_output::OutputEvent;
|
||||
pub use wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "clipboard")] {
|
||||
|
@ -36,6 +45,7 @@ cfg_if! {
|
|||
|
||||
pub use wlr_data_control::{ClipboardItem, ClipboardValue};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataControlDeviceEntry {
|
||||
seat: WlSeat,
|
||||
device: DataControlDevice,
|
||||
|
@ -43,35 +53,162 @@ cfg_if! {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Environment {
|
||||
pub registry_state: RegistryState,
|
||||
pub output_state: OutputState,
|
||||
pub seat_state: SeatState,
|
||||
pub foreign_toplevel_manager_state: ToplevelManagerState,
|
||||
#[derive(Debug)]
|
||||
pub enum Event {
|
||||
Output(OutputEvent),
|
||||
Toplevel(ToplevelEvent),
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub data_control_device_manager_state: DataControlDeviceManagerState,
|
||||
pub loop_handle: LoopHandle<'static, Self>,
|
||||
|
||||
pub seats: Vec<WlSeat>,
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub data_control_devices: Vec<DataControlDeviceEntry>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub selection_offers: Vec<SelectionOfferItem>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
pub copy_paste_sources: Vec<CopyPasteSource>,
|
||||
|
||||
pub handles: HashMap<usize, ToplevelHandle>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: Arc<Mutex<Option<Arc<ClipboardItem>>>>,
|
||||
|
||||
toplevel_tx: broadcast::Sender<ToplevelEvent>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
|
||||
Clipboard(ClipboardItem),
|
||||
}
|
||||
|
||||
// Now we need to say we are delegating the responsibility of output related events for our application data
|
||||
// type to the requisite delegate.
|
||||
#[derive(Debug)]
|
||||
pub enum Request {
|
||||
Roundtrip,
|
||||
|
||||
OutputInfoAll,
|
||||
|
||||
ToplevelInfoAll,
|
||||
ToplevelFocus(usize),
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
CopyToClipboard(ClipboardItem),
|
||||
#[cfg(feature = "clipboard")]
|
||||
ClipboardItem,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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)]
|
||||
struct BroadcastChannel<T>(broadcast::Sender<T>, Arc<Mutex<broadcast::Receiver<T>>>);
|
||||
|
||||
impl<T> From<(broadcast::Sender<T>, broadcast::Receiver<T>)> for BroadcastChannel<T> {
|
||||
fn from(value: (broadcast::Sender<T>, broadcast::Receiver<T>)) -> Self {
|
||||
BroadcastChannel(value.0, arc_mut!(value.1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Client {
|
||||
tx: calloop_channel::Sender<Request>,
|
||||
rx: Arc<Mutex<std::sync::mpsc::Receiver<Response>>>,
|
||||
|
||||
output_channel: BroadcastChannel<OutputEvent>,
|
||||
toplevel_channel: BroadcastChannel<ToplevelEvent>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_channel: BroadcastChannel<ClipboardItem>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub(crate) fn new() -> Self {
|
||||
let (event_tx, mut event_rx) = mpsc::channel(32);
|
||||
|
||||
let (request_tx, request_rx) = calloop_channel::channel();
|
||||
let (response_tx, response_rx) = std::sync::mpsc::channel();
|
||||
|
||||
let output_channel = broadcast::channel(32);
|
||||
let toplevel_channel = broadcast::channel(32);
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_channel = broadcast::channel(32);
|
||||
|
||||
Ironbar::runtime().spawn_blocking(move || {
|
||||
Environment::spawn(event_tx, request_rx, response_tx);
|
||||
});
|
||||
|
||||
// listen to events
|
||||
{
|
||||
let output_tx = output_channel.0.clone();
|
||||
let toplevel_tx = toplevel_channel.0.clone();
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let clipboard_tx = clipboard_channel.0.clone();
|
||||
|
||||
let rt = Ironbar::runtime();
|
||||
rt.spawn(async move {
|
||||
while let Some(event) = event_rx.recv().await {
|
||||
match event {
|
||||
Event::Output(event) => send!(output_tx, event),
|
||||
Event::Toplevel(event) => send!(toplevel_tx, event),
|
||||
#[cfg(feature = "clipboard")]
|
||||
Event::Clipboard(item) => send!(clipboard_tx, item),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Self {
|
||||
tx: request_tx,
|
||||
rx: arc_mut!(response_rx),
|
||||
|
||||
output_channel: output_channel.into(),
|
||||
toplevel_channel: toplevel_channel.into(),
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard_channel: clipboard_channel.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sends a request to the environment event loop,
|
||||
/// and returns the response.
|
||||
fn send_request(&self, request: Request) -> Response {
|
||||
send!(self.tx, request);
|
||||
lock!(self.rx).recv().expect(ERR_CHANNEL_RECV)
|
||||
}
|
||||
|
||||
/// Sends a round-trip request to the client,
|
||||
/// forcing it to send/receive any events in the queue.
|
||||
pub(crate) fn roundtrip(&self) -> Response {
|
||||
self.send_request(Request::Roundtrip)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Environment {
|
||||
registry_state: RegistryState,
|
||||
output_state: OutputState,
|
||||
seat_state: SeatState,
|
||||
|
||||
queue_handle: QueueHandle<Self>,
|
||||
loop_handle: LoopHandle<'static, Self>,
|
||||
|
||||
event_tx: mpsc::Sender<Event>,
|
||||
response_tx: std::sync::mpsc::Sender<Response>,
|
||||
|
||||
// local state
|
||||
handles: Vec<ToplevelHandle>,
|
||||
|
||||
// -- clipboard --
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_device_manager_state: DataControlDeviceManagerState,
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_devices: Vec<DataControlDeviceEntry>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
copy_paste_sources: Vec<CopyPasteSource>,
|
||||
#[cfg(feature = "clipboard")]
|
||||
selection_offers: Vec<SelectionOfferItem>,
|
||||
|
||||
// local state
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: Arc<Mutex<Option<ClipboardItem>>>,
|
||||
}
|
||||
|
||||
delegate_registry!(Environment);
|
||||
|
||||
delegate_output!(Environment);
|
||||
delegate_seat!(Environment);
|
||||
|
||||
|
@ -82,21 +219,128 @@ cfg_if! {
|
|||
if #[cfg(feature = "clipboard")] {
|
||||
delegate_data_control_device_manager!(Environment);
|
||||
delegate_data_control_device!(Environment);
|
||||
delegate_data_control_source!(Environment);
|
||||
delegate_data_control_offer!(Environment);
|
||||
delegate_data_control_source!(Environment);
|
||||
}
|
||||
}
|
||||
|
||||
// In order for our delegate to know of the existence of globals, we need to implement registry
|
||||
// handling for the program. This trait will forward events to the RegistryHandler trait
|
||||
// implementations.
|
||||
delegate_registry!(Environment);
|
||||
impl Environment {
|
||||
pub fn spawn(
|
||||
event_tx: mpsc::Sender<Event>,
|
||||
request_rx: calloop_channel::Channel<Request>,
|
||||
response_tx: std::sync::mpsc::Sender<Response>,
|
||||
) {
|
||||
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");
|
||||
|
||||
let qh = queue.handle();
|
||||
let mut event_loop = EventLoop::<Self>::try_new().expect("Failed to create new event loop");
|
||||
|
||||
WaylandSource::new(conn, queue)
|
||||
.insert(event_loop.handle())
|
||||
.expect("Failed to insert Wayland event queue into event loop");
|
||||
|
||||
let loop_handle = event_loop.handle();
|
||||
|
||||
// Initialize the registry handling
|
||||
// so other parts of Smithay's client toolkit may bind globals.
|
||||
let registry_state = RegistryState::new(&globals);
|
||||
|
||||
let output_state = OutputState::new(&globals, &qh);
|
||||
let seat_state = SeatState::new(&globals, &qh);
|
||||
ToplevelManagerState::bind(&globals, &qh)
|
||||
.expect("to bind to wlr_foreign_toplevel_manager global");
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
let data_control_device_manager_state = DataControlDeviceManagerState::bind(&globals, &qh)
|
||||
.expect("to bind to wlr_data_control_device_manager global");
|
||||
|
||||
let mut env = Self {
|
||||
registry_state,
|
||||
output_state,
|
||||
seat_state,
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_device_manager_state,
|
||||
queue_handle: qh,
|
||||
loop_handle: loop_handle.clone(),
|
||||
event_tx,
|
||||
response_tx,
|
||||
handles: vec![],
|
||||
|
||||
#[cfg(feature = "clipboard")]
|
||||
data_control_devices: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
copy_paste_sources: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
selection_offers: vec![],
|
||||
#[cfg(feature = "clipboard")]
|
||||
clipboard: arc_mut!(None),
|
||||
};
|
||||
|
||||
loop_handle
|
||||
.insert_source(request_rx, Self::on_request)
|
||||
.expect("to be able to insert source");
|
||||
|
||||
loop {
|
||||
trace!("Dispatching event loop");
|
||||
if let Err(err) = event_loop.dispatch(None, &mut env) {
|
||||
error!(
|
||||
"{:?}",
|
||||
Report::new(err).wrap_err("Failed to dispatch pending wayland events")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes a request from the client
|
||||
/// and sends the response.
|
||||
fn on_request(event: calloop_channel::Event<Request>, _metadata: &mut (), env: &mut Self) {
|
||||
trace!("Request: {event:?}");
|
||||
|
||||
match event {
|
||||
Msg(Request::Roundtrip) => {
|
||||
debug!("received roundtrip request");
|
||||
send!(env.response_tx, Response::Ok);
|
||||
}
|
||||
Msg(Request::OutputInfoAll) => {
|
||||
let infos = env.output_info_all();
|
||||
send!(env.response_tx, Response::OutputInfoAll(infos));
|
||||
}
|
||||
Msg(Request::ToplevelInfoAll) => {
|
||||
let infos = env
|
||||
.handles
|
||||
.iter()
|
||||
.filter_map(ToplevelHandle::info)
|
||||
.collect();
|
||||
send!(env.response_tx, Response::ToplevelInfoAll(infos));
|
||||
}
|
||||
Msg(Request::ToplevelFocus(id)) => {
|
||||
let handle = env
|
||||
.handles
|
||||
.iter()
|
||||
.find(|handle| handle.info().map_or(false, |info| info.id == id));
|
||||
if let Some(handle) = handle {
|
||||
let seat = env.default_seat();
|
||||
handle.focus(&seat);
|
||||
}
|
||||
send!(env.response_tx, Response::Ok);
|
||||
}
|
||||
#[cfg(feature = "clipboard")]
|
||||
Msg(Request::CopyToClipboard(item)) => {
|
||||
env.copy_to_clipboard(item);
|
||||
send!(env.response_tx, Response::Ok);
|
||||
}
|
||||
#[cfg(feature = "clipboard")]
|
||||
Msg(Request::ClipboardItem) => {
|
||||
let item = lock!(env.clipboard).clone();
|
||||
send!(env.response_tx, Response::ClipboardItem(item));
|
||||
}
|
||||
calloop_channel::Event::Closed => error!("request channel unexpectedly closed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In order for delegate_registry to work, our application data type needs to provide a way for the
|
||||
// implementation to access the registry state.
|
||||
//
|
||||
// We also need to indicate which delegates will get told about globals being created. We specify
|
||||
// the types of the delegates inside the array.
|
||||
impl ProvidesRegistryState for Environment {
|
||||
fn registry(&mut self) -> &mut RegistryState {
|
||||
&mut self.registry_state
|
||||
|
@ -104,10 +348,4 @@ impl ProvidesRegistryState for Environment {
|
|||
registry_handlers![OutputState, SeatState];
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CLIENT: Arc<Mutex<WaylandClient>> = arc_mut!(WaylandClient::new());
|
||||
}
|
||||
|
||||
pub fn get_client() -> Arc<Mutex<WaylandClient>> {
|
||||
CLIENT.clone()
|
||||
}
|
||||
register_client!(Client, wayland);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue