1
0
Fork 0
mirror of https://github.com/Zedfrigg/ironbar.git synced 2025-08-18 07:11: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:
Jake Stanger 2024-01-07 23:50:10 +00:00
parent 57b57ed002
commit c702f6fffa
No known key found for this signature in database
GPG key ID: C51FC8F9CB0BEA61
33 changed files with 1081 additions and 1063 deletions

View file

@ -1,269 +0,0 @@
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::error::ERR_CHANNEL_RECV;
use crate::{send, spawn_blocking};
use cfg_if::cfg_if;
use color_eyre::Report;
use smithay_client_toolkit::output::{OutputInfo, OutputState};
use smithay_client_toolkit::reexports::calloop::channel::{channel, Event, Sender};
use smithay_client_toolkit::reexports::calloop::EventLoop;
use smithay_client_toolkit::reexports::calloop_wayland_source::WaylandSource;
use smithay_client_toolkit::registry::RegistryState;
use smithay_client_toolkit::seat::SeatState;
use std::collections::HashMap;
use std::sync::mpsc;
use tokio::sync::broadcast;
use tracing::{debug, error, trace};
use wayland_client::globals::registry_queue_init;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::Connection;
cfg_if! {
if #[cfg(feature = "clipboard")] {
use super::ClipboardItem;
use super::wlr_data_control::manager::DataControlDeviceManagerState;
use crate::lock;
use std::sync::Arc;
}
}
#[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.
Seats,
/// Sends a request for all the toplevels.
/// These are then sent on the `toplevel_init` channel.
Toplevels,
/// Sends a request for the current clipboard item.
/// This is then sent on the `clipboard_init` channel.
#[cfg(feature = "clipboard")]
Clipboard,
/// Copies the value to the clipboard
#[cfg(feature = "clipboard")]
CopyToClipboard(Arc<ClipboardItem>),
/// Forces a dispatch, flushing any currently queued events
Roundtrip,
}
pub struct WaylandClient {
// External channels
toplevel_tx: broadcast::Sender<ToplevelEvent>,
_toplevel_rx: broadcast::Receiver<ToplevelEvent>,
#[cfg(feature = "clipboard")]
clipboard_tx: broadcast::Sender<Arc<ClipboardItem>>,
#[cfg(feature = "clipboard")]
_clipboard_rx: broadcast::Receiver<Arc<ClipboardItem>>,
// 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>>>,
request_tx: Sender<Request>,
}
impl WaylandClient {
pub(super) fn new() -> Self {
let (toplevel_tx, toplevel_rx) = broadcast::channel(32);
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();
cfg_if! {
if #[cfg(feature = "clipboard")] {
let (clipboard_tx, clipboard_rx) = broadcast::channel(32);
let clipboard_tx2 = clipboard_tx.clone();
}
}
let (ev_tx, ev_rx) = channel::<Request>();
// `queue` is not `Send` so we need to handle everything inside the task
spawn_blocking(move || {
let toplevel_tx = toplevel_tx2;
#[cfg(feature = "clipboard")]
let clipboard_tx = clipboard_tx2;
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::<Environment>::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_delegate = OutputState::new(&globals, &qh);
let seat_delegate = SeatState::new(&globals, &qh);
#[cfg(feature = "clipboard")]
let data_control_device_manager_delegate =
DataControlDeviceManagerState::bind(&globals, &qh)
.expect("data device manager is not available");
let foreign_toplevel_manager_delegate = ToplevelManagerState::bind(&globals, &qh)
.expect("foreign toplevel manager is not available");
let mut env = Environment {
registry_state,
output_state: output_delegate,
seat_state: seat_delegate,
#[cfg(feature = "clipboard")]
data_control_device_manager_state: data_control_device_manager_delegate,
foreign_toplevel_manager_state: foreign_toplevel_manager_delegate,
seats: vec![],
handles: HashMap::new(),
#[cfg(feature = "clipboard")]
clipboard: crate::arc_mut!(None),
toplevel_tx,
#[cfg(feature = "clipboard")]
clipboard_tx,
#[cfg(feature = "clipboard")]
data_control_devices: vec![],
#[cfg(feature = "clipboard")]
selection_offers: vec![],
#[cfg(feature = "clipboard")]
copy_paste_sources: vec![],
loop_handle: event_loop.handle(),
};
loop_handle
.insert_source(ev_rx, move |event, _metadata, env| {
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());
}
Event::Msg(Request::Toplevels) => {
trace!("Receive get toplevels request");
send!(toplevel_init_tx, env.handles.clone());
}
#[cfg(feature = "clipboard")]
Event::Msg(Request::Clipboard) => {
trace!("Receive get clipboard requests");
let clipboard = lock!(env.clipboard).clone();
send!(clipboard_init_tx, clipboard);
}
#[cfg(feature = "clipboard")]
Event::Msg(Request::CopyToClipboard(value)) => {
env.copy_to_clipboard(value, &qh);
}
Event::Closed => panic!("Channel unexpectedly closed"),
}
})
.expect("Failed to insert channel into event queue");
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")
);
}
}
});
Self {
toplevel_tx,
_toplevel_rx: toplevel_rx,
toplevel_init_rx,
#[cfg(feature = "clipboard")]
clipboard_init_rx,
output_rx,
seat_rx,
#[cfg(feature = "clipboard")]
clipboard_tx,
#[cfg(feature = "clipboard")]
_clipboard_rx: clipboard_rx,
request_tx: ev_tx,
}
}
pub fn subscribe_toplevels(
&self,
) -> (
broadcast::Receiver<ToplevelEvent>,
HashMap<usize, ToplevelHandle>,
) {
let rx = self.toplevel_tx.subscribe();
let receiver = &self.toplevel_init_rx;
send!(self.request_tx, Request::Toplevels);
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
(rx, data)
}
#[cfg(feature = "clipboard")]
pub fn subscribe_clipboard(
&self,
) -> (
broadcast::Receiver<Arc<ClipboardItem>>,
Option<Arc<ClipboardItem>>,
) {
let rx = self.clipboard_tx.subscribe();
let receiver = &self.clipboard_init_rx;
send!(self.request_tx, Request::Clipboard);
let data = receiver.recv().expect(ERR_CHANNEL_RECV);
(rx, data)
}
/// Force a roundtrip on the wayland connection,
/// flushing any queued events and immediately receiving any new ones.
pub fn roundtrip(&self) {
trace!("Sending roundtrip request");
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)
}
pub fn get_seats(&self) -> Vec<WlSeat> {
trace!("Sending get seats request");
send!(self.request_tx, Request::Seats);
self.seat_rx.recv().expect(ERR_CHANNEL_RECV)
}
#[cfg(feature = "clipboard")]
pub fn copy_to_clipboard(&self, item: Arc<ClipboardItem>) {
send!(self.request_tx, Request::CopyToClipboard(item));
}
}

View file

@ -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);

View file

@ -1,11 +1,41 @@
use super::Environment;
use super::{Client, Environment, Event, Request, Response};
use crate::try_send;
use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState};
use tracing::debug;
use wayland_client::protocol::wl_output;
use tokio::sync::broadcast;
use tracing::{debug, error};
use wayland_client::protocol::wl_output::WlOutput;
use wayland_client::{Connection, QueueHandle};
#[derive(Debug, Clone)]
pub struct OutputEvent {
output: OutputInfo,
event_type: OutputEventType,
}
#[derive(Debug, Clone, Copy)]
pub enum OutputEventType {
New,
Update,
Destroyed,
}
impl Client {
/// Gets the information for all outputs.
pub fn output_info_all(&self) -> Vec<OutputInfo> {
match self.send_request(Request::OutputInfoAll) {
Response::OutputInfoAll(info) => info,
_ => unreachable!(),
}
}
/// Subscribes to events from outputs.
pub fn subscribe_outputs(&self) -> broadcast::Receiver<OutputEvent> {
self.output_channel.0.subscribe()
}
}
impl Environment {
pub fn output_info(&mut self) -> Vec<OutputInfo> {
pub fn output_info_all(&mut self) -> Vec<OutputInfo> {
self.output_state
.outputs()
.filter_map(|output| self.output_state.info(&output))
@ -27,29 +57,48 @@ impl OutputHandler for Environment {
// Then there exist these functions that indicate the lifecycle of an output.
// These will be called as appropriate by the delegate implementation.
fn new_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
fn new_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
debug!("Handler received new output");
if let Some(info) = self.output_state.info(&output) {
try_send!(
self.event_tx,
Event::Output(OutputEvent {
output: info,
event_type: OutputEventType::New
})
);
} else {
error!("Output is missing information!");
}
}
fn update_output(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
fn update_output(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
debug!("Handle received output update");
if let Some(info) = self.output_state.info(&output) {
try_send!(
self.event_tx,
Event::Output(OutputEvent {
output: info,
event_type: OutputEventType::Update
})
);
} else {
error!("Output is missing information!");
}
}
fn output_destroyed(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_output: wl_output::WlOutput,
) {
fn output_destroyed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, output: WlOutput) {
debug!("Handle received output destruction");
if let Some(info) = self.output_state.info(&output) {
try_send!(
self.event_tx,
Event::Output(OutputEvent {
output: info,
event_type: OutputEventType::Destroyed
})
);
} else {
error!("Output is missing information!");
}
}
}

View file

@ -1,24 +1,30 @@
use super::Environment;
use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState};
use tracing::debug;
use wayland_client::protocol::wl_seat;
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::{Connection, QueueHandle};
impl Environment {
/// Gets the default seat.
pub(crate) fn default_seat(&self) -> WlSeat {
self.seat_state.seats().next().expect("one seat to exist")
}
}
impl SeatHandler for Environment {
fn seat_state(&mut self) -> &mut SeatState {
&mut self.seat_state
}
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _seat: WlSeat) {
debug!("Handler received new seat");
self.seats.push(seat);
}
fn new_capability(
&mut self,
_: &Connection,
qh: &QueueHandle<Self>,
seat: wl_seat::WlSeat,
seat: WlSeat,
_: Capability,
) {
debug!("Handler received new capability");
@ -39,25 +45,22 @@ impl SeatHandler for Environment {
device: data_control_device,
});
}
if !self.seats.iter().any(|s| s == &seat) {
self.seats.push(seat);
}
}
fn remove_capability(
&mut self,
_: &Connection,
_: &QueueHandle<Self>,
_: wl_seat::WlSeat,
seat: WlSeat,
_: Capability,
) {
debug!("Handler received capability removal");
// Not applicable
#[cfg(feature = "clipboard")]
self.data_control_devices.retain(|entry| entry.seat != seat);
}
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _seat: WlSeat) {
debug!("Handler received seat removal");
self.seats.retain(|s| s != &seat);
}
}

View file

@ -12,6 +12,7 @@ use wayland_protocols_wlr::data_control::v1::client::{
zwlr_data_control_offer_v1::ZwlrDataControlOfferV1,
};
#[derive(Debug)]
pub struct DataControlDevice {
pub device: ZwlrDataControlDeviceV1,
}

View file

@ -14,6 +14,7 @@ use wayland_protocols_wlr::data_control::v1::client::{
zwlr_data_control_source_v1::ZwlrDataControlSourceV1,
};
#[derive(Debug)]
pub struct DataControlDeviceManagerState<V = DataControlOfferData> {
manager: ZwlrDataControlManagerV1,
_phantom: PhantomData<V>,

View file

@ -6,8 +6,8 @@ pub mod source;
use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler};
use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer};
use self::source::DataControlSourceHandler;
use crate::clients::wayland::Environment;
use crate::{lock, send, Ironbar};
use super::{Client, Environment, Event, Request, Response};
use crate::{lock, try_send, Ironbar};
use device::DataControlDevice;
use glib::Bytes;
use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ};
@ -21,22 +21,28 @@ use std::io::{ErrorKind, Read, Write};
use std::os::fd::{AsRawFd, OwnedFd, RawFd};
use std::sync::Arc;
use std::{fs, io};
use tokio::sync::broadcast;
use tracing::{debug, error, trace};
use wayland_client::{Connection, QueueHandle};
use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1;
const INTERNAL_MIME_TYPE: &str = "x-ironbar-internal";
#[derive(Debug)]
pub struct SelectionOfferItem {
offer: SelectionOffer,
token: Option<RegistrationToken>,
}
/// Represents a value which can be read/written
/// to/from the system clipboard and surrounding metadata.
///
/// Can be cheaply cloned.
#[derive(Debug, Clone, Eq)]
pub struct ClipboardItem {
pub id: usize,
pub value: ClipboardValue,
pub mime_type: String,
pub value: Arc<ClipboardValue>,
pub mime_type: Arc<str>,
}
impl PartialEq<Self> for ClipboardItem {
@ -108,24 +114,60 @@ impl MimeType {
}
}
impl Environment {
pub fn copy_to_clipboard(&mut self, item: Arc<ClipboardItem>, qh: &QueueHandle<Self>) {
debug!("Copying item to clipboard: {item:?}");
// TODO: Proper device tracking
let device = self.data_control_devices.first();
if let Some(device) = device {
let source = self
.data_control_device_manager_state
.create_copy_paste_source(qh, [INTERNAL_MIME_TYPE, item.mime_type.as_str()]);
source.set_selection(&device.device);
self.copy_paste_sources.push(source);
lock!(self.clipboard).replace(item);
impl Client {
/// Gets the current clipboard item,
/// if this exists and Ironbar has record of it.
pub fn clipboard_item(&self) -> Option<ClipboardItem> {
match self.send_request(Request::ClipboardItem) {
Response::ClipboardItem(item) => item,
_ => unreachable!(),
}
}
/// Copies the provided value to the system clipboard.
pub fn copy_to_clipboard(&self, item: ClipboardItem) {
match self.send_request(Request::CopyToClipboard(item)) {
Response::Ok => (),
_ => unreachable!(),
}
}
/// Subscribes to the system clipboard,
/// receiving all new copied items.
pub fn subscribe_clipboard(&self) -> broadcast::Receiver<ClipboardItem> {
self.clipboard_channel.0.subscribe()
}
}
impl Environment {
/// Creates a new copy/paste source on the
/// seat's data control device.
///
/// This provides it as an offer,
/// which the compositor will then treat as the current copied value.
pub fn copy_to_clipboard(&mut self, item: ClipboardItem) {
debug!("Copying item to clipboard: {item:?}");
let seat = self.default_seat();
let Some(device) = self
.data_control_devices
.iter()
.find(|entry| entry.seat == seat)
else {
return;
};
let source = self
.data_control_device_manager_state
.create_copy_paste_source(&self.queue_handle, [INTERNAL_MIME_TYPE, &item.mime_type]);
source.set_selection(&device.device);
self.copy_paste_sources.push(source);
lock!(self.clipboard).replace(item);
}
/// Reads an offer file handle into a new `ClipboardItem`.
fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result<ClipboardItem> {
let value = match mime_type.category {
MimeTypeCategory::Text => {
@ -145,13 +187,15 @@ impl Environment {
Ok(ClipboardItem {
id: Ironbar::unique_id(),
value,
mime_type: mime_type.value.clone(),
value: Arc::new(value),
mime_type: mime_type.value.clone().into(),
})
}
}
impl DataControlDeviceHandler for Environment {
/// Called when an offer for a new value is received
/// (ie something has copied to the clipboard)
fn selection(
&mut self,
_conn: &Connection,
@ -175,15 +219,16 @@ impl DataControlDeviceHandler for Environment {
.last_mut()
.expect("Failed to get current offer");
// clear prev
let Some(mime_type) = MimeType::parse_multiple(&mime_types) else {
lock!(self.clipboard).take();
// send an event so the clipboard module is aware it's changed
send!(
self.clipboard_tx,
Arc::new(ClipboardItem {
try_send!(
self.event_tx,
Event::Clipboard(ClipboardItem {
id: usize::MAX,
mime_type: String::new(),
value: ClipboardValue::Other
mime_type: String::new().into(),
value: Arc::new(ClipboardValue::Other)
})
);
return;
@ -192,7 +237,7 @@ impl DataControlDeviceHandler for Environment {
if let Ok(read_pipe) = cur_offer.offer.receive(mime_type.value.clone()) {
let offer_clone = cur_offer.offer.clone();
let tx = self.clipboard_tx.clone();
let tx = self.event_tx.clone();
let clipboard = self.clipboard.clone();
let token =
@ -207,9 +252,8 @@ impl DataControlDeviceHandler for Environment {
match Self::read_file(&mime_type, file.get_mut()) {
Ok(item) => {
let item = Arc::new(item);
lock!(clipboard).replace(item.clone());
send!(tx, item);
try_send!(tx, Event::Clipboard(item));
}
Err(err) => error!("{err:?}"),
}
@ -255,6 +299,8 @@ impl DataControlSourceHandler for Environment {
debug!("Accepted mime type: {mime:?}");
}
/// Writes the current clipboard item to 'paste' it
/// upon request from a compositor client.
fn send_request(
&mut self,
_conn: &Connection,
@ -274,12 +320,12 @@ impl DataControlSourceHandler for Environment {
{
trace!("Source found, writing to file");
let mut bytes = match &item.value {
let mut bytes = match item.value.as_ref() {
ClipboardValue::Text(text) => text.as_bytes(),
ClipboardValue::Image(bytes) => bytes.as_ref(),
ClipboardValue::Other => panic!(
"{:?}",
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type",)
io::Error::new(ErrorKind::Other, "Attempted to copy unsupported mime type")
),
};

View file

@ -10,6 +10,7 @@ use wayland_protocols_wlr::foreign_toplevel::v1::client::{
zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1},
};
#[derive(Debug)]
pub struct ToplevelManagerState<V = ToplevelHandleData> {
manager: ZwlrForeignToplevelManagerV1,
_phantom: PhantomData<V>,
@ -31,12 +32,7 @@ impl ToplevelManagerState {
pub trait ToplevelManagerHandler: Sized {
/// Advertises a new toplevel.
fn toplevel(
&mut self,
conn: &Connection,
qh: &QueueHandle<Self>,
manager: ToplevelManagerState,
);
fn toplevel(&mut self, conn: &Connection, qh: &QueueHandle<Self>);
}
impl ProvidesBoundGlobal<ZwlrForeignToplevelManagerV1, 3> for ToplevelManagerState {
@ -60,7 +56,7 @@ where
fn event(
state: &mut D,
toplevel_manager: &ZwlrForeignToplevelManagerV1,
_toplevel_manager: &ZwlrForeignToplevelManagerV1,
event: Event,
_data: &GlobalData,
conn: &Connection,
@ -68,14 +64,7 @@ where
) {
match event {
Event::Toplevel { toplevel: _ } => {
state.toplevel(
conn,
qhandle,
ToplevelManagerState {
manager: toplevel_manager.clone(),
_phantom: PhantomData,
},
);
state.toplevel(conn, qhandle);
}
Event::Finished => {
warn!("Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues.");

View file

@ -2,41 +2,62 @@ pub mod handle;
pub mod manager;
use self::handle::ToplevelHandleHandler;
use self::manager::{ToplevelManagerHandler, ToplevelManagerState};
use crate::clients::wayland::Environment;
use self::manager::ToplevelManagerHandler;
use super::{Client, Environment, Event, Request, Response};
use crate::try_send;
use tokio::sync::broadcast;
use tracing::{debug, error, trace};
use wayland_client::{Connection, QueueHandle};
use crate::send;
pub use handle::{ToplevelHandle, ToplevelInfo};
#[derive(Debug, Clone)]
pub enum ToplevelEvent {
New(ToplevelHandle),
Update(ToplevelHandle),
Remove(ToplevelHandle),
New(ToplevelInfo),
Update(ToplevelInfo),
Remove(ToplevelInfo),
}
impl Client {
/// Gets the information for all currently open toplevels (windows)
pub fn toplevel_info_all(&self) -> Vec<ToplevelInfo> {
match self.send_request(Request::ToplevelInfoAll) {
Response::ToplevelInfoAll(infos) => infos,
_ => unreachable!(),
}
}
/// Focuses the toplevel with the provided ID.
pub fn toplevel_focus(&self, handle_id: usize) {
match self.send_request(Request::ToplevelFocus(handle_id)) {
Response::Ok => (),
_ => unreachable!(),
}
}
/// Subscribes to events from toplevels.
pub fn subscribe_toplevels(&self) -> broadcast::Receiver<ToplevelEvent> {
self.toplevel_channel.0.subscribe()
}
}
impl ToplevelManagerHandler for Environment {
fn toplevel(
&mut self,
_conn: &Connection,
_qh: &QueueHandle<Self>,
_manager: ToplevelManagerState,
) {
fn toplevel(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>) {
debug!("Manager received new handle");
}
}
impl ToplevelHandleHandler for Environment {
fn new_handle(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, handle: ToplevelHandle) {
trace!("Handler received new handle");
debug!("Handler received new handle");
match handle.info() {
Some(info) => {
trace!("Adding new handle: {info:?}");
self.handles.insert(info.id, handle.clone());
send!(self.toplevel_tx, ToplevelEvent::New(handle));
self.handles.push(handle.clone());
if let Some(info) = handle.info() {
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::New(info)));
}
}
None => {
error!("Handle is missing information!");
@ -55,8 +76,10 @@ impl ToplevelHandleHandler for Environment {
match handle.info() {
Some(info) => {
trace!("Updating handle: {info:?}");
self.handles.insert(info.id, handle.clone());
send!(self.toplevel_tx, ToplevelEvent::Update(handle));
self.handles.push(handle.clone());
if let Some(info) = handle.info() {
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Update(info)));
}
}
None => {
error!("Handle is missing information!");
@ -71,14 +94,10 @@ impl ToplevelHandleHandler for Environment {
handle: ToplevelHandle,
) {
debug!("Handler received handle close");
match handle.info() {
Some(info) => {
self.handles.remove(&info.id);
send!(self.toplevel_tx, ToplevelEvent::Remove(handle));
}
None => {
error!("Handle is missing information!");
}
self.handles.retain(|h| h != &handle);
if let Some(info) = handle.info() {
try_send!(self.event_tx, Event::Toplevel(ToplevelEvent::Remove(info)));
}
}
}