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:
parent
57b57ed002
commit
c702f6fffa
33 changed files with 1081 additions and 1063 deletions
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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")
|
||||
),
|
||||
};
|
||||
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue