diff --git a/Cargo.lock b/Cargo.lock index 8f0ebba..555dc72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1555,6 +1555,7 @@ dependencies = [ "walkdir", "wayland-client", "wayland-protocols", + "wayland-protocols-wlr", "zbus 3.11.1", ] @@ -1824,18 +1825,6 @@ dependencies = [ "memoffset 0.6.5", ] -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - [[package]] name = "nix" version = "0.25.1" @@ -2222,6 +2211,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-xml" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "0.3.15" @@ -2451,6 +2449,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.1.0" @@ -2649,9 +2653,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smithay-client-toolkit" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454" +checksum = "e1476c3d89bb67079264b88aaf4f14358353318397e083b7c4e8c14517f55de7" dependencies = [ "bitflags 1.3.2", "calloop", @@ -2659,11 +2663,14 @@ dependencies = [ "lazy_static", "log", "memmap2", - "nix 0.24.3", - "pkg-config", + "nix 0.26.2", + "thiserror", + "wayland-backend", "wayland-client", "wayland-cursor", "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", ] [[package]] @@ -3408,72 +3415,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] -name = "wayland-client" -version = "0.29.5" +name = "wayland-backend" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" +checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" dependencies = [ - "bitflags 1.3.2", + "cc", "downcast-rs", - "libc", - "nix 0.24.3", - "wayland-commons", - "wayland-scanner", - "wayland-sys", -] - -[[package]] -name = "wayland-commons" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" -dependencies = [ - "nix 0.24.3", - "once_cell", + "io-lifetimes", + "nix 0.26.2", + "scoped-tls", "smallvec", "wayland-sys", ] [[package]] -name = "wayland-cursor" -version = "0.29.5" +name = "wayland-client" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" +checksum = "85bde68449abab1a808e5227b6e295f4ae3680911eb7711b4a2cb90141edb780" dependencies = [ - "nix 0.24.3", + "bitflags 1.3.2", + "calloop", + "nix 0.26.2", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-cursor" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0c3a0d5b4b688b07b0442362d3ed6bf04724fcc16cd69ab6285b90dbc487aa" +dependencies = [ + "nix 0.26.2", "wayland-client", "xcursor", ] [[package]] name = "wayland-protocols" -version = "0.29.5" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" +checksum = "7fefbeb8a360abe67ab7c2efe1d297a1a50ee011f5460791bc18870c26bb84e2" dependencies = [ "bitflags 1.3.2", + "wayland-backend", "wayland-client", - "wayland-commons", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" +dependencies = [ + "bitflags 1.3.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", "wayland-scanner", ] [[package]] name = "wayland-scanner" -version = "0.29.5" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" +checksum = "4834c14b3edf1d9986c83ca79b1e7e3afbe9874c7c144702f6467063259ce45d" dependencies = [ "proc-macro2", + "quick-xml", "quote 1.0.26", - "xml-rs", ] [[package]] name = "wayland-sys" -version = "0.29.5" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" +checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" dependencies = [ + "dlib", + "log", "pkg-config", ] @@ -3701,12 +3724,6 @@ dependencies = [ "nom", ] -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - [[package]] name = "zbus" version = "2.3.2" diff --git a/Cargo.toml b/Cargo.toml index 0260ad6..8fc3986 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,9 +62,10 @@ indexmap = "1.9.1" dirs = "5.0.0" walkdir = "2.3.2" notify = { version = "5.0.0", default-features = false } -wayland-client = "0.29.5" -wayland-protocols = { version = "0.29.5", features = ["unstable_protocols", "client"] } -smithay-client-toolkit = { version = "0.16.0", default-features = false, features = ["calloop"] } +wayland-client = "0.30.0" +wayland-protocols = { version = "0.30.0", features = ["unstable", "client"] } +wayland-protocols-wlr = { version = "0.1.0", features = ["client"] } +smithay-client-toolkit = { version = "0.17.0", default-features = false, features = ["calloop"] } universal-config = { version = "0.4.0", default_features = false } lazy_static = "1.4.0" @@ -75,7 +76,7 @@ cfg-if = "1.0.0" reqwest = { version = "0.11.14", optional = true } # clipboard -nix = { version = "0.26.2", optional = true } +nix = { version = "0.26.2", optional = true, features = ["event"] } # clock chrono = { version = "0.4.19", optional = true } diff --git a/src/clients/clipboard.rs b/src/clients/clipboard.rs index 68bbe59..cadbae1 100644 --- a/src/clients/clipboard.rs +++ b/src/clients/clipboard.rs @@ -6,7 +6,7 @@ use lazy_static::lazy_static; use std::sync::{Arc, Mutex}; use tokio::spawn; use tokio::sync::mpsc; -use tracing::debug; +use tracing::{debug, trace}; #[derive(Debug)] pub enum ClipboardEvent { @@ -26,6 +26,8 @@ pub struct ClipboardClient { impl ClipboardClient { fn new() -> Self { + trace!("Initializing clipboard client"); + let senders = Arc::new(Mutex::new(Vec::<(EventSender, usize)>::new())); let cache = Arc::new(Mutex::new(ClipboardCache::new())); @@ -35,11 +37,21 @@ impl ClipboardClient { let cache = cache.clone(); spawn(async move { - let mut rx = { + let (mut rx, item) = { let wl = wayland::get_client().await; wl.subscribe_clipboard() }; + if let Some(item) = item { + let senders = lock!(senders); + let iter = senders.iter(); + for (tx, _) in iter { + try_send!(tx, ClipboardEvent::Add(item.clone())); + } + + lock!(cache).insert(item, senders.len()); + } + while let Ok(item) = rx.recv().await { debug!("Received clipboard item (ID: {})", item.id); @@ -59,7 +71,6 @@ impl ClipboardClient { let iter = senders.iter(); for (tx, sender_cache_size) in iter { if cache_size == *sender_cache_size { - // let mut cache = lock!(cache); let removed_id = lock!(cache) .remove_ref_first() .expect("Clipboard cache unexpectedly empty"); @@ -83,18 +94,11 @@ impl ClipboardClient { Self { senders, cache } } - pub async fn subscribe(&self, cache_size: usize) -> mpsc::Receiver { + pub fn subscribe(&self, cache_size: usize) -> mpsc::Receiver { let (tx, rx) = mpsc::channel(16); - let wl = wayland::get_client().await; - wl.roundtrip(); - { - let mut cache = lock!(self.cache); - - if let Some(item) = wl.get_clipboard() { - cache.insert_or_inc_ref(item); - } + let cache = lock!(self.cache); let iter = cache.iter(); for (_, (item, _)) in iter { @@ -102,10 +106,7 @@ impl ClipboardClient { } } - { - let mut senders = lock!(self.senders); - senders.push((tx, cache_size)); - } + lock!(self.senders).push((tx, cache_size)); rx } @@ -171,13 +172,6 @@ impl ClipboardCache { .map(|(item, _)| item) } - /// Inserts an entry with `ref_count` initial references, - /// or increments the `ref_count` by 1 if it already exists. - fn insert_or_inc_ref(&mut self, item: Arc) { - let mut item = self.cache.entry(item.id).or_insert((item, 0)); - item.1 += 1; - } - /// Removes the entry with key `id`. /// This ignores references. fn remove(&mut self, id: usize) -> Option> { diff --git a/src/clients/music/mpris.rs b/src/clients/music/mpris.rs index 6f56d21..289ffc7 100644 --- a/src/clients/music/mpris.rs +++ b/src/clients/music/mpris.rs @@ -5,6 +5,7 @@ use color_eyre::Result; use lazy_static::lazy_static; use mpris::{DBusError, Event, Metadata, PlaybackStatus, Player, PlayerFinder}; use std::collections::HashSet; +use std::string; use std::sync::{Arc, Mutex}; use std::thread::sleep; use std::time::Duration; @@ -259,7 +260,7 @@ impl From for Track { .and_then(mpris::MetadataValue::as_str_array) .and_then(|arr| arr.first().map(|val| (*val).to_string())), track: value.track_number().map(|track| track as u64), - cover_path: value.art_url().map(|s| s.to_string()), + cover_path: value.art_url().map(string::ToString::to_string), } } } diff --git a/src/clients/wayland/client.rs b/src/clients/wayland/client.rs index 11adc02..9f506fb 100644 --- a/src/clients/wayland/client.rs +++ b/src/clients/wayland/client.rs @@ -1,79 +1,90 @@ -use super::wlr_foreign_toplevel::{ - handle::{ToplevelEvent, ToplevelInfo}, - manager::listen_for_toplevels, -}; -use super::{DData, Env, ToplevelHandler}; -use crate::{error as err, send}; +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; use cfg_if::cfg_if; use color_eyre::Report; -use indexmap::IndexMap; -use smithay_client_toolkit::environment::Environment; -use smithay_client_toolkit::output::{with_output_info, OutputInfo}; +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::WaylandSource; +use smithay_client_toolkit::registry::RegistryState; +use smithay_client_toolkit::seat::SeatState; use std::collections::HashMap; -use std::sync::{Arc, RwLock}; -use tokio::sync::{broadcast, oneshot}; +use std::sync::mpsc; +use tokio::sync::broadcast; use tokio::task::spawn_blocking; -use tracing::{debug, error}; +use tracing::{debug, error, trace}; +use wayland_client::globals::registry_queue_init; use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::{ConnectError, Display, EventQueue}; -use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ - zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, - zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1, -}; +use wayland_client::{Connection, WaylandSource}; cfg_if! { if #[cfg(feature = "clipboard")] { - use super::{ClipboardItem}; - use super::wlr_data_control::manager::{listen_to_devices, DataControlDeviceHandler}; - use crate::{read_lock, write_lock}; - use tokio::spawn; + use super::ClipboardItem; + use super::wlr_data_control::manager::DataControlDeviceManagerState; + use crate::lock; + use std::sync::{Arc, Mutex}; } } #[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), /// Forces a dispatch, flushing any currently queued events - Refresh, + Roundtrip, } pub struct WaylandClient { - pub outputs: Vec, - pub seats: Vec, - - pub toplevels: Arc>>, + // External channels toplevel_tx: broadcast::Sender, _toplevel_rx: broadcast::Receiver, - #[cfg(feature = "clipboard")] clipboard_tx: broadcast::Sender>, #[cfg(feature = "clipboard")] - clipboard: Arc>>>, + _clipboard_rx: broadcast::Receiver>, + + // Internal channels + toplevel_init_rx: mpsc::Receiver>, + output_rx: mpsc::Receiver>, + seat_rx: mpsc::Receiver>, + #[cfg(feature = "clipboard")] + clipboard_init_rx: mpsc::Receiver>>, request_tx: Sender, } impl WaylandClient { pub(super) async fn new() -> Self { - let (output_tx, output_rx) = oneshot::channel(); - let (seat_tx, seat_rx) = oneshot::channel(); - let (toplevel_tx, toplevel_rx) = broadcast::channel(32); - let toplevels = Arc::new(RwLock::new(IndexMap::new())); - let toplevels2 = toplevels.clone(); + let (toplevel_init_tx, toplevel_init_rx) = mpsc::channel(); + #[cfg(feature = "clipboard")] + let (clipboard_init_tx, clipboard_init_rx) = mpsc::channel(); + let (output_tx, output_rx) = mpsc::channel(); + let (seat_tx, seat_rx) = mpsc::channel(); let toplevel_tx2 = toplevel_tx.clone(); cfg_if! { if #[cfg(feature = "clipboard")] { - let (clipboard_tx, mut clipboard_rx) = broadcast::channel(32); - let clipboard = Arc::new(RwLock::new(None)); + let (clipboard_tx, clipboard_rx) = broadcast::channel(32); let clipboard_tx2 = clipboard_tx.clone(); } } @@ -82,85 +93,100 @@ impl WaylandClient { // `queue` is not `Send` so we need to handle everything inside the task spawn_blocking(move || { - let toplevels = toplevels2; let toplevel_tx = toplevel_tx2; + #[cfg(feature = "clipboard")] + let clipboard_tx = clipboard_tx2; - let (env, _display, queue) = - Self::new_environment().expect("Failed to connect to Wayland compositor"); + 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::::try_new().expect("Failed to create new event loop"); + EventLoop::::try_new().expect("Failed to create new event loop"); + WaylandSource::new(queue) - .quick_insert(event_loop.handle()) + .expect("Failed to create Wayland source from queue") + .insert(event_loop.handle()) .expect("Failed to insert Wayland event queue into event loop"); - let outputs = Self::get_outputs(&env); - send!(output_tx, outputs); + let loop_handle = event_loop.handle(); - let seats = env.get_all_seats(); + // 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); - // TODO: Actually handle seats properly #[cfg(feature = "clipboard")] - let default_seat = seats[0].detach(); + let data_control_device_manager_delegate = + DataControlDeviceManagerState::bind(&globals, &qh) + .expect("data device manager is not available"); - send!( - seat_tx, - seats - .into_iter() - .map(|seat| seat.detach()) - .collect::>() - ); + let foreign_toplevel_manager_delegate = ToplevelManagerState::bind(&globals, &qh) + .expect("foreign toplevel manager is not available"); - let handle = event_loop.handle(); - handle - .insert_source(ev_rx, move |event, _metadata, ddata| { - // let env = &ddata.env; + 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: Arc::new(Mutex::new(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::Refresh) => debug!("Received refresh 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)) => { - super::wlr_data_control::copy_to_clipboard( - &ddata.env, - &default_seat, - &value, - ) - .expect("Failed to copy to clipboard"); + env.copy_to_clipboard(value, &qh); } Event::Closed => panic!("Channel unexpectedly closed"), } }) .expect("Failed to insert channel into event queue"); - let _toplevel_manager = env.require_global::(); - - let _toplevel_listener = listen_for_toplevels(&env, move |handle, event, _ddata| { - super::wlr_foreign_toplevel::update_toplevels( - &toplevels, - handle, - event, - &toplevel_tx, - ); - }); - - cfg_if! { - if #[cfg(feature = "clipboard")] { - let clipboard_tx = clipboard_tx2; - let handle = event_loop.handle(); - - let _offer_listener = listen_to_devices(&env, move |_seat, event, ddata| { - debug!("Received clipboard event"); - super::wlr_data_control::receive_offer(event, &handle, clipboard_tx.clone(), ddata); - }); - } - } - - let mut data = DData { - env, - offer_tokens: HashMap::new(), - }; - loop { - if let Err(err) = event_loop.dispatch(None, &mut data) { + 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") @@ -169,119 +195,76 @@ impl WaylandClient { } }); - // keep track of current clipboard item - #[cfg(feature = "clipboard")] - { - let clipboard = clipboard.clone(); - spawn(async move { - while let Ok(item) = clipboard_rx.recv().await { - let mut clipboard = write_lock!(clipboard); - clipboard.replace(item); - } - }); - } - - let outputs = output_rx.await.expect(err::ERR_CHANNEL_RECV); - - let seats = seat_rx.await.expect(err::ERR_CHANNEL_RECV); - Self { - outputs, - seats, - #[cfg(feature = "clipboard")] - clipboard, - toplevels, 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 { - self.toplevel_tx.subscribe() + pub fn subscribe_toplevels( + &self, + ) -> ( + broadcast::Receiver, + HashMap, + ) { + 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> { - self.clipboard_tx.subscribe() + pub fn subscribe_clipboard( + &self, + ) -> ( + broadcast::Receiver>, + Option>, + ) { + 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) { - send!(self.request_tx, Request::Refresh); + trace!("Sending roundtrip request"); + send!(self.request_tx, Request::Roundtrip); } - #[cfg(feature = "clipboard")] - pub fn get_clipboard(&self) -> Option> { - let clipboard = read_lock!(self.clipboard); - clipboard.as_ref().cloned() + pub fn get_outputs(&self) -> Vec { + 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 { + 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) { send!(self.request_tx, Request::CopyToClipboard(item)); } - - fn get_outputs(env: &Environment) -> Vec { - let outputs = env.get_all_outputs(); - - outputs - .iter() - .filter_map(|output| with_output_info(output, Clone::clone)) - .collect() - } - - fn new_environment() -> Result<(Environment, Display, EventQueue), ConnectError> { - Display::connect_to_env().and_then(|display| { - let mut queue = display.create_event_queue(); - let ret = { - let mut sctk_seats = smithay_client_toolkit::seat::SeatHandler::new(); - let sctk_data_device_manager = - smithay_client_toolkit::data_device::DataDeviceHandler::init(&mut sctk_seats); - - #[cfg(feature = "clipboard")] - let data_control_device = DataControlDeviceHandler::init(&mut sctk_seats); - - let sctk_primary_selection_manager = - smithay_client_toolkit::primary_selection::PrimarySelectionHandler::init( - &mut sctk_seats, - ); - - let display = ::smithay_client_toolkit::reexports::client::Proxy::clone(&display); - let env = Environment::new( - &display.attach(queue.token()), - &mut queue, - Env { - sctk_compositor: smithay_client_toolkit::environment::SimpleGlobal::new(), - sctk_subcompositor: smithay_client_toolkit::environment::SimpleGlobal::new( - ), - sctk_shm: smithay_client_toolkit::shm::ShmHandler::new(), - sctk_outputs: smithay_client_toolkit::output::OutputHandler::new(), - sctk_seats, - sctk_data_device_manager, - sctk_primary_selection_manager, - toplevel: ToplevelHandler::init(), - #[cfg(feature = "clipboard")] - data_control_device, - }, - ); - - if let Ok(env) = env.as_ref() { - let _psm = env.get_primary_selection_manager(); - } - - env - }; - match ret { - Ok(env) => Ok((env, display, queue)), - Err(_e) => display.protocol_error().map_or_else( - || Err(ConnectError::NoCompositorListening), - |perr| { - panic!("[SCTK] A protocol error occured during initial setup: {perr}"); - }, - ), - } - }) - } } diff --git a/src/clients/wayland/macros.rs b/src/clients/wayland/macros.rs new file mode 100644 index 0000000..9325d2a --- /dev/null +++ b/src/clients/wayland/macros.rs @@ -0,0 +1,101 @@ +/// It is necessary to store macros in a separate file due to a compilation error. +/// I believe this stems from the feature flags. +/// Related issue: https://github.com/rust-lang/rust/issues/81066 + +// --- Data Control Device --- \\ + +#[macro_export] +macro_rules! delegate_data_control_device_manager { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1: smithay_client_toolkit::globals::GlobalData + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; +} + +#[macro_export] +macro_rules! delegate_data_control_device { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $udata, + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1: $crate::clients::wayland::wlr_data_control::device::DataControlDeviceData + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; +} + +#[macro_export] +macro_rules! delegate_data_control_offer { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $udata, + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::ZwlrDataControlOfferV1: $crate::clients::wayland::wlr_data_control::offer::DataControlOfferData + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; +} + +#[macro_export] +macro_rules! delegate_data_control_source { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $udata, + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1: $crate::clients::wayland::wlr_data_control::source::DataControlSourceData + ] => $crate::clients::wayland::wlr_data_control::manager::DataControlDeviceManagerState + ); + }; +} + +// --- Foreign Toplevel --- \\ + +#[macro_export] +macro_rules! delegate_foreign_toplevel_manager { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1: smithay_client_toolkit::globals::GlobalData + ] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState + ); + }; +} + +#[macro_export] +macro_rules! delegate_foreign_toplevel_handle { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty, udata: [$($udata: ty),*$(,)?]) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: $udata, + ] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState + ); + }; + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + wayland_client::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: + [ + wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1: $crate::clients::wayland::wlr_foreign_toplevel::handle::ToplevelHandleData + ] => $crate::clients::wayland::wlr_foreign_toplevel::manager::ToplevelManagerState + ); + }; +} diff --git a/src/clients/wayland/mod.rs b/src/clients/wayland/mod.rs index 0f567d9..a6ef58a 100644 --- a/src/clients/wayland/mod.rs +++ b/src/clients/wayland/mod.rs @@ -1,70 +1,110 @@ mod client; +mod macros; +mod wl_output; +mod wl_seat; mod wlr_foreign_toplevel; -use std::collections::HashMap; +use self::wlr_foreign_toplevel::manager::ToplevelManagerState; +use crate::{delegate_foreign_toplevel_handle, delegate_foreign_toplevel_manager}; use async_once::AsyncOnce; -use lazy_static::lazy_static; -use std::fmt::Debug; use cfg_if::cfg_if; -use smithay_client_toolkit::default_environment; -use smithay_client_toolkit::environment::Environment; -use smithay_client_toolkit::reexports::calloop::RegistrationToken; -use wayland_client::{Attached, Interface}; -use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_manager_v1::ZwlrForeignToplevelManagerV1; -pub use wlr_foreign_toplevel::handle::{ToplevelChange, ToplevelEvent, ToplevelInfo}; -use wlr_foreign_toplevel::manager::{ToplevelHandler}; +use lazy_static::lazy_static; +use smithay_client_toolkit::output::OutputState; +use smithay_client_toolkit::reexports::calloop::LoopHandle; +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 tokio::sync::broadcast; +use wayland_client::protocol::wl_seat::WlSeat; -pub use client::WaylandClient; +pub use self::client::WaylandClient; +pub use self::wlr_foreign_toplevel::{ToplevelEvent, ToplevelHandle, ToplevelInfo}; cfg_if! { if #[cfg(feature = "clipboard")] { mod wlr_data_control; - use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1; - use wlr_data_control::manager::DataControlDeviceHandler; + use crate::{delegate_data_control_device, delegate_data_control_device_manager, delegate_data_control_offer, delegate_data_control_source}; + use self::wlr_data_control::device::DataControlDevice; + use self::wlr_data_control::manager::DataControlDeviceManagerState; + use self::wlr_data_control::source::CopyPasteSource; + use self::wlr_data_control::SelectionOfferItem; + use std::sync::{Arc, Mutex}; + pub use wlr_data_control::{ClipboardItem, ClipboardValue}; + + pub struct DataControlDeviceEntry { + seat: WlSeat, + device: DataControlDevice, + } } } -/// A utility for lazy-loading globals. -/// Taken from `smithay_client_toolkit` where it's not exposed -#[derive(Debug)] -enum LazyGlobal { - Unknown, - Seen { id: u32, version: u32 }, - Bound(Attached), +pub struct Environment { + pub registry_state: RegistryState, + pub output_state: OutputState, + pub seat_state: SeatState, + pub foreign_toplevel_manager_state: ToplevelManagerState, + #[cfg(feature = "clipboard")] + pub data_control_device_manager_state: DataControlDeviceManagerState, + pub loop_handle: LoopHandle<'static, Self>, + + pub seats: Vec, + + #[cfg(feature = "clipboard")] + pub data_control_devices: Vec, + #[cfg(feature = "clipboard")] + pub selection_offers: Vec, + #[cfg(feature = "clipboard")] + pub copy_paste_sources: Vec, + + pub handles: HashMap, + #[cfg(feature = "clipboard")] + clipboard: Arc>>>, + + toplevel_tx: broadcast::Sender, + #[cfg(feature = "clipboard")] + clipboard_tx: broadcast::Sender>, } -pub struct DData { - env: Environment, - offer_tokens: HashMap, -} +// Now we need to say we are delegating the responsibility of output related events for our application data +// type to the requisite delegate. +delegate_output!(Environment); +delegate_seat!(Environment); + +delegate_foreign_toplevel_manager!(Environment); +delegate_foreign_toplevel_handle!(Environment); cfg_if! { if #[cfg(feature = "clipboard")] { - default_environment!(Env, - fields = [ - toplevel: ToplevelHandler, - data_control_device: DataControlDeviceHandler - ], - singles = [ - ZwlrForeignToplevelManagerV1 => toplevel, - ZwlrDataControlManagerV1 => data_control_device - ], - ); - } else { - default_environment!(Env, - fields = [ - toplevel: ToplevelHandler, - ], - singles = [ - ZwlrForeignToplevelManagerV1 => toplevel, - ], - ); + delegate_data_control_device_manager!(Environment); + delegate_data_control_device!(Environment); + delegate_data_control_source!(Environment); + delegate_data_control_offer!(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); + +// 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 + } + registry_handlers![OutputState, SeatState]; +} + lazy_static! { static ref CLIENT: AsyncOnce = AsyncOnce::new(async { WaylandClient::new().await }); diff --git a/src/clients/wayland/wl_output.rs b/src/clients/wayland/wl_output.rs new file mode 100644 index 0000000..1b3af8d --- /dev/null +++ b/src/clients/wayland/wl_output.rs @@ -0,0 +1,55 @@ +use super::Environment; +use smithay_client_toolkit::output::{OutputHandler, OutputInfo, OutputState}; +use tracing::debug; +use wayland_client::protocol::wl_output; +use wayland_client::{Connection, QueueHandle}; + +impl Environment { + pub fn output_info(&mut self) -> Vec { + self.output_state + .outputs() + .filter_map(|output| self.output_state.info(&output)) + .collect() + } +} + +// In order to use OutputDelegate, we must implement this trait to indicate when something has happened to an +// output and to provide an instance of the output state to the delegate when dispatching events. +impl OutputHandler for Environment { + // First we need to provide a way to access the delegate. + // + // This is needed because delegate implementations for handling events use the application data type in + // their function signatures. This allows the implementation to access an instance of the type. + fn output_state(&mut self) -> &mut OutputState { + &mut self.output_state + } + + // 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, + _output: wl_output::WlOutput, + ) { + debug!("Handler received new output"); + } + + fn update_output( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + } + + fn output_destroyed( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _output: wl_output::WlOutput, + ) { + debug!("Handle received output destruction"); + } +} diff --git a/src/clients/wayland/wl_seat.rs b/src/clients/wayland/wl_seat.rs new file mode 100644 index 0000000..ee36892 --- /dev/null +++ b/src/clients/wayland/wl_seat.rs @@ -0,0 +1,63 @@ +use super::Environment; +use smithay_client_toolkit::seat::{Capability, SeatHandler, SeatState}; +use tracing::debug; +use wayland_client::protocol::wl_seat; +use wayland_client::{Connection, QueueHandle}; + +impl SeatHandler for Environment { + fn seat_state(&mut self) -> &mut SeatState { + &mut self.seat_state + } + + fn new_seat(&mut self, _: &Connection, _: &QueueHandle, seat: wl_seat::WlSeat) { + debug!("Handler received new seat"); + self.seats.push(seat); + } + + fn new_capability( + &mut self, + _: &Connection, + qh: &QueueHandle, + seat: wl_seat::WlSeat, + _: Capability, + ) { + debug!("Handler received new capability"); + + #[cfg(feature = "clipboard")] + if !self + .data_control_devices + .iter_mut() + .any(|entry| entry.seat == seat) + { + debug!("Adding new data control device"); + // create the data device here for this seat + let data_control_device_manager = &self.data_control_device_manager_state; + let data_control_device = data_control_device_manager.get_data_device(qh, &seat); + self.data_control_devices + .push(super::DataControlDeviceEntry { + seat: seat.clone(), + device: data_control_device, + }); + } + + if !self.seats.iter().any(|s| s == &seat) { + self.seats.push(seat); + } + } + + fn remove_capability( + &mut self, + _: &Connection, + _: &QueueHandle, + _: wl_seat::WlSeat, + _: Capability, + ) { + debug!("Handler received capability removal"); + // Not applicable + } + + fn remove_seat(&mut self, _: &Connection, _: &QueueHandle, seat: wl_seat::WlSeat) { + debug!("Handler received seat removal"); + self.seats.retain(|s| s != &seat); + } +} diff --git a/src/clients/wayland/wlr_data_control/device.rs b/src/clients/wayland/wlr_data_control/device.rs index 493eec6..8135a86 100644 --- a/src/clients/wayland/wlr_data_control/device.rs +++ b/src/clients/wayland/wlr_data_control/device.rs @@ -1,88 +1,166 @@ -use super::offer::DataControlOffer; -use super::source::DataControlSource; +use super::manager::DataControlDeviceManagerState; +use super::offer::{ + DataControlOfferData, DataControlOfferDataExt, DataControlOfferHandler, SelectionOffer, +}; +use crate::error::ERR_WAYLAND_DATA; use crate::lock; use std::sync::{Arc, Mutex}; -use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::{Attached, DispatchData, Main}; -use wayland_protocols::wlr::unstable::data_control::v1::client::{ +use tracing::warn; +use wayland_client::{event_created_child, Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols_wlr::data_control::v1::client::{ zwlr_data_control_device_v1::{Event, ZwlrDataControlDeviceV1}, - zwlr_data_control_manager_v1::ZwlrDataControlManagerV1, zwlr_data_control_offer_v1::ZwlrDataControlOfferV1, }; -#[derive(Debug)] -struct Inner { - offer: Option>, -} - -impl Inner { - fn new_offer(&mut self, offer: &Main) { - self.offer.replace(Arc::new(DataControlOffer::new(offer))); - } -} - -#[derive(Debug, Clone)] -pub struct DataControlDeviceEvent(pub Arc); - -fn data_control_device_implem( - event: Event, - inner: &mut Inner, - implem: &mut F, - ddata: DispatchData, -) where - F: FnMut(DataControlDeviceEvent, DispatchData), -{ - match event { - Event::DataOffer { id } => { - inner.new_offer(&id); - } - Event::Selection { id: Some(offer) } => { - let inner_offer = inner - .offer - .clone() - .expect("Offer should exist at this stage"); - if offer == inner_offer.offer { - implem(DataControlDeviceEvent(inner_offer), ddata); - } - } - _ => {} - } -} - pub struct DataControlDevice { - device: ZwlrDataControlDeviceV1, - _inner: Arc>, + pub device: ZwlrDataControlDeviceV1, } -impl DataControlDevice { - pub fn init_for_seat( - manager: &Attached, - seat: &WlSeat, - mut callback: F, - ) -> Self - where - F: FnMut(DataControlDeviceEvent, DispatchData) + 'static, - { - let inner = Arc::new(Mutex::new(Inner { offer: None })); +#[derive(Debug, Default)] +pub struct DataControlDeviceInner { + /// the active selection offer and its data + selection_offer: Arc>>, + /// the active undetermined offers and their data + pub undetermined_offers: Arc>>, +} - let device = manager.get_data_device(seat); +#[derive(Debug, Default)] +pub struct DataControlDeviceData { + pub(super) inner: Arc>, +} - { - let inner = inner.clone(); - device.quick_assign(move |_handle, event, ddata| { - let mut inner = lock!(inner); - data_control_device_implem(event, &mut inner, &mut callback, ddata); - }); - } +pub trait DataControlDeviceDataExt: Send + Sync { + type DataControlOfferInner: DataControlOfferDataExt + Send + Sync + 'static; - Self { - device: device.detach(), - _inner: inner, - } + fn data_control_device_data(&self) -> &DataControlDeviceData; + + fn selection_mime_types(&self) -> Vec { + let inner = self.data_control_device_data(); + lock!(lock!(inner.inner).selection_offer) + .as_ref() + .map(|offer| { + let data = offer + .data::() + .expect(ERR_WAYLAND_DATA); + data.mime_types() + }) + .unwrap_or_default() } - pub fn set_selection(&self, source: &Option) { - self.device - .set_selection(source.as_ref().map(|s| &s.source)); + /// Get the active selection offer if it exists. + fn selection_offer(&self) -> Option { + let inner = self.data_control_device_data(); + lock!(lock!(inner.inner).selection_offer) + .as_ref() + .and_then(|offer| { + let data = offer + .data::() + .expect(ERR_WAYLAND_DATA); + data.as_selection_offer() + }) + } +} + +impl DataControlDeviceDataExt for DataControlDevice { + type DataControlOfferInner = DataControlOfferData; + fn data_control_device_data(&self) -> &DataControlDeviceData { + self.device.data().expect(ERR_WAYLAND_DATA) + } +} + +impl DataControlDeviceDataExt for DataControlDeviceData { + type DataControlOfferInner = DataControlOfferData; + fn data_control_device_data(&self) -> &DataControlDeviceData { + self + } +} + +/// Handler trait for `DataDevice` events. +/// +/// The functions defined in this trait are called as `DataDevice` events are received from the compositor. +pub trait DataControlDeviceHandler: Sized { + /// Advertises a new selection. + fn selection( + &mut self, + conn: &Connection, + qh: &QueueHandle, + data_device: DataControlDevice, + ); +} + +impl Dispatch for DataControlDeviceManagerState +where + D: Dispatch + + Dispatch + + DataControlDeviceHandler + + DataControlOfferHandler + + 'static, + U: DataControlDeviceDataExt, + V: DataControlOfferDataExt + Default + 'static + Send + Sync, +{ + event_created_child!(D, ZwlrDataControlDeviceV1, [ + 0 => (ZwlrDataControlOfferV1, V::default()) + ]); + + fn event( + state: &mut D, + data_device: &ZwlrDataControlDeviceV1, + event: Event, + data: &U, + conn: &Connection, + qh: &QueueHandle, + ) { + let data = data.data_control_device_data(); + let inner = lock!(data.inner); + + match event { + Event::DataOffer { id } => { + // XXX Drop done here to prevent Mutex deadlocks.S + + lock!(inner.undetermined_offers).push(id.clone()); + let data = id + .data::() + .expect(ERR_WAYLAND_DATA) + .data_control_offer_data(); + data.init_undetermined_offer(&id); + + // Append the data offer to our list of offers. + drop(inner); + } + Event::Selection { id } => { + let mut selection_offer = lock!(inner.selection_offer); + + if let Some(offer) = id { + let mut undetermined = lock!(inner.undetermined_offers); + if let Some(i) = undetermined.iter().position(|o| o == &offer) { + undetermined.remove(i); + } + drop(undetermined); + + let data = offer + .data::() + .expect(ERR_WAYLAND_DATA) + .data_control_offer_data(); + data.to_selection_offer(); + // XXX Drop done here to prevent Mutex deadlocks. + *selection_offer = Some(offer.clone()); + drop(selection_offer); + drop(inner); + state.selection( + conn, + qh, + DataControlDevice { + device: data_device.clone(), + }, + ); + } else { + *selection_offer = None; + } + } + Event::Finished => { + warn!("Data control offer is no longer valid, but has not been dropped by client. This could cause clipboard issues."); + } + _ => {} + } } } diff --git a/src/clients/wayland/wlr_data_control/manager.rs b/src/clients/wayland/wlr_data_control/manager.rs index fcd239a..a2a2ac4 100644 --- a/src/clients/wayland/wlr_data_control/manager.rs +++ b/src/clients/wayland/wlr_data_control/manager.rs @@ -1,253 +1,132 @@ -use super::device::{DataControlDevice, DataControlDeviceEvent}; -use super::source::DataControlSource; -use smithay_client_toolkit::data_device::WritePipe; -use smithay_client_toolkit::environment::{Environment, GlobalHandler}; -use smithay_client_toolkit::seat::{SeatHandling, SeatListener}; -use smithay_client_toolkit::MissingGlobal; -use std::cell::RefCell; -use std::rc::{self, Rc}; -use tracing::warn; -use wayland_client::protocol::wl_registry::WlRegistry; +use super::device::{DataControlDevice, DataControlDeviceData, DataControlDeviceDataExt}; +use super::offer::DataControlOfferData; +use super::source::{CopyPasteSource, DataControlSourceData, DataControlSourceDataExt}; +use smithay_client_toolkit::error::GlobalError; +use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal}; +use std::marker::PhantomData; +use tracing::debug; +use wayland_client::globals::{BindError, GlobalList}; use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::{Attached, DispatchData}; -use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_manager_v1::ZwlrDataControlManagerV1; +use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols_wlr::data_control::v1::client::{ + zwlr_data_control_device_v1::ZwlrDataControlDeviceV1, + zwlr_data_control_manager_v1::ZwlrDataControlManagerV1, + zwlr_data_control_source_v1::ZwlrDataControlSourceV1, +}; -enum DataControlDeviceHandlerInner { - Ready { - manager: Attached, - devices: Vec<(WlSeat, DataControlDevice)>, - status_listeners: Rc>>>>, - }, - Pending { - seats: Vec, - status_listeners: Rc>>>>, - }, +pub struct DataControlDeviceManagerState { + manager: ZwlrDataControlManagerV1, + _phantom: PhantomData, } -impl DataControlDeviceHandlerInner { - fn init_manager(&mut self, manager: Attached) { - let (seats, status_listeners) = if let Self::Pending { - seats, - status_listeners, - } = self - { - (std::mem::take(seats), status_listeners.clone()) - } else { - warn!("Ignoring second zwlr_data_control_manager_v1"); - return; - }; - - let mut devices = Vec::new(); - - for seat in seats { - let my_seat = seat.clone(); - let status_listeners = status_listeners.clone(); - let device = - DataControlDevice::init_for_seat(&manager, &seat, move |event, dispatch_data| { - notify_status_listeners(&my_seat, &event, dispatch_data, &status_listeners); - }); - devices.push((seat.clone(), device)); - } - - *self = Self::Ready { +impl DataControlDeviceManagerState { + pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result + where + State: Dispatch + 'static, + { + let manager = globals.bind(qh, 1..=2, GlobalData)?; + debug!("Bound to ZwlDataControlManagerV1 global"); + Ok(Self { manager, - devices, - status_listeners, - }; - } - - fn get_manager(&self) -> Option> { - match self { - Self::Ready { manager, .. } => Some(manager.clone()), - Self::Pending { .. } => None, - } - } - - fn new_seat(&mut self, seat: &WlSeat) { - match self { - Self::Ready { - manager, - devices, - status_listeners, - } => { - if devices.iter().any(|(s, _)| s == seat) { - // the seat already exists, nothing to do - return; - } - let my_seat = seat.clone(); - let status_listeners = status_listeners.clone(); - let device = - DataControlDevice::init_for_seat(manager, seat, move |event, dispatch_data| { - notify_status_listeners(&my_seat, &event, dispatch_data, &status_listeners); - }); - devices.push((seat.clone(), device)); - } - Self::Pending { seats, .. } => { - seats.push(seat.clone()); - } - } - } - - fn remove_seat(&mut self, seat: &WlSeat) { - match self { - Self::Ready { devices, .. } => devices.retain(|(s, _)| s != seat), - Self::Pending { seats, .. } => seats.retain(|s| s != seat), - } - } - - fn create_source(&self, mime_types: Vec, callback: F) -> Option - where - F: FnMut(String, WritePipe, DispatchData) + 'static, - { - match self { - Self::Ready { manager, .. } => { - let source = DataControlSource::new(manager, mime_types, callback); - Some(source) - } - Self::Pending { .. } => None, - } - } - - fn with_device(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal> - where - F: FnOnce(&DataControlDevice), - { - match self { - Self::Ready { devices, .. } => { - let device = devices - .iter() - .find_map(|(s, device)| if s == seat { Some(device) } else { None }); - - device.map_or(Err(MissingGlobal), |device| { - f(device); - Ok(()) - }) - } - Self::Pending { .. } => Err(MissingGlobal), - } - } -} - -pub struct DataControlDeviceHandler { - inner: Rc>, - status_listeners: Rc>>>>, - _seat_listener: SeatListener, -} - -impl DataControlDeviceHandler { - pub fn init(seat_handler: &mut S) -> Self - where - S: SeatHandling, - { - let status_listeners = Rc::new(RefCell::new(Vec::new())); - - let inner = Rc::new(RefCell::new(DataControlDeviceHandlerInner::Pending { - seats: Vec::new(), - status_listeners: status_listeners.clone(), - })); - - let seat_inner = inner.clone(); - let seat_listener = seat_handler.listen(move |seat, seat_data, _| { - if seat_data.defunct { - seat_inner.borrow_mut().remove_seat(&seat); - } else { - seat_inner.borrow_mut().new_seat(&seat); - } - }); - - Self { - inner, - _seat_listener: seat_listener, - status_listeners, - } - } -} - -impl GlobalHandler for DataControlDeviceHandler { - fn created( - &mut self, - registry: Attached, - id: u32, - version: u32, - _ddata: DispatchData, - ) { - // data control manager is supported until version 2 - let version = std::cmp::min(version, 2); - - let manager = registry.bind::(version, id); - self.inner.borrow_mut().init_manager((*manager).clone()); - } - - fn get(&self) -> Option> { - RefCell::borrow(&self.inner).get_manager() - } -} - -type DataControlDeviceStatusCallback = - dyn FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static; - -/// Notifies the callbacks of an event on the data device -fn notify_status_listeners( - seat: &WlSeat, - event: &DataControlDeviceEvent, - mut ddata: DispatchData, - listeners: &RefCell>>>, -) { - listeners.borrow_mut().retain(|lst| { - rc::Weak::upgrade(lst).map_or(false, |cb| { - (cb.borrow_mut())(seat.clone(), event.clone(), ddata.reborrow()); - true + _phantom: PhantomData, }) - }); -} - -pub struct DataControlDeviceStatusListener { - _cb: Rc>, -} - -pub trait DataControlDeviceHandling { - fn listen(&mut self, f: F) -> DataControlDeviceStatusListener - where - F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static; - - fn with_data_control_device(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal> - where - F: FnOnce(&DataControlDevice); - - fn create_source(&self, mime_types: Vec, callback: F) -> Option - where - F: FnMut(String, WritePipe, DispatchData) + 'static; -} - -impl DataControlDeviceHandling for DataControlDeviceHandler { - fn listen(&mut self, f: F) -> DataControlDeviceStatusListener - where - F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static, - { - let rc = Rc::new(RefCell::new(f)) as Rc<_>; - self.status_listeners.borrow_mut().push(Rc::downgrade(&rc)); - DataControlDeviceStatusListener { _cb: rc } } - fn with_data_control_device(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal> + /// creates a data source for copy paste + pub fn create_copy_paste_source<'s, D, I>( + &self, + qh: &QueueHandle, + mime_types: I, + ) -> CopyPasteSource where - F: FnOnce(&DataControlDevice), + D: Dispatch + 'static, + I: IntoIterator, { - RefCell::borrow(&self.inner).with_device(seat, f) + CopyPasteSource { + inner: self.create_data_control_source(qh, mime_types), + } } - fn create_source(&self, mime_types: Vec, callback: F) -> Option + /// creates a data source + fn create_data_control_source<'s, D, I>( + &self, + qh: &QueueHandle, + mime_types: I, + ) -> ZwlrDataControlSourceV1 where - F: FnMut(String, WritePipe, DispatchData) + 'static, + D: Dispatch + 'static, + I: IntoIterator, { - RefCell::borrow(&self.inner).create_source(mime_types, callback) + let source = + self.create_data_control_source_with_data(qh, DataControlSourceData::default()); + + for mime in mime_types { + source.offer(mime.to_string()); + } + + source + } + + /// create a new data source for a given seat with some user data + pub fn create_data_control_source_with_data( + &self, + qh: &QueueHandle, + data: U, + ) -> ZwlrDataControlSourceV1 + where + D: Dispatch + 'static, + U: DataControlSourceDataExt + 'static, + { + self.manager.create_data_source(qh, data) + } + + /// create a new data device for a given seat + pub fn get_data_device(&self, qh: &QueueHandle, seat: &WlSeat) -> DataControlDevice + where + D: Dispatch + 'static, + { + DataControlDevice { + device: self.get_data_control_device_with_data( + qh, + seat, + DataControlDeviceData::default(), + ), + } + } + + /// create a new data device for a given seat with some user data + pub fn get_data_control_device_with_data( + &self, + qh: &QueueHandle, + seat: &WlSeat, + data: U, + ) -> ZwlrDataControlDeviceV1 + where + D: Dispatch + 'static, + U: DataControlDeviceDataExt + 'static, + { + self.manager.get_data_device(seat, qh, data) } } -pub fn listen_to_devices(env: &Environment, f: F) -> DataControlDeviceStatusListener +impl ProvidesBoundGlobal for DataControlDeviceManagerState { + fn bound_global(&self) -> Result { + Ok(self.manager.clone()) + } +} + +impl Dispatch for DataControlDeviceManagerState where - E: DataControlDeviceHandling, - F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static, + D: Dispatch, { - env.with_inner(move |inner| DataControlDeviceHandling::listen(inner, f)) + fn event( + _state: &mut D, + _proxy: &ZwlrDataControlManagerV1, + _event: ::Event, + _data: &GlobalData, + _conn: &Connection, + _qhandle: &QueueHandle, + ) { + unreachable!() + } } diff --git a/src/clients/wayland/wlr_data_control/mod.rs b/src/clients/wayland/wlr_data_control/mod.rs index bc28e14..fe9034a 100644 --- a/src/clients/wayland/wlr_data_control/mod.rs +++ b/src/clients/wayland/wlr_data_control/mod.rs @@ -3,28 +3,28 @@ pub mod manager; pub mod offer; pub mod source; -use super::Env; -use crate::clients::wayland::DData; -use crate::send; -use color_eyre::Report; -use device::{DataControlDevice, DataControlDeviceEvent}; +use self::device::{DataControlDeviceDataExt, DataControlDeviceHandler}; +use self::offer::{DataControlDeviceOffer, DataControlOfferHandler, SelectionOffer}; +use self::source::DataControlSourceHandler; +use crate::clients::wayland::Environment; +use crate::{lock, send}; +use device::DataControlDevice; use glib::Bytes; -use manager::{DataControlDeviceHandling, DataControlDeviceStatusListener}; -use smithay_client_toolkit::data_device::WritePipe; -use smithay_client_toolkit::environment::Environment; -use smithay_client_toolkit::reexports::calloop::LoopHandle; -use smithay_client_toolkit::MissingGlobal; -use source::DataControlSource; +use nix::fcntl::{fcntl, F_GETPIPE_SZ, F_SETPIPE_SZ}; +use nix::sys::epoll::{epoll_create, epoll_ctl, epoll_wait, EpollEvent, EpollFlags, EpollOp}; +use smithay_client_toolkit::data_device_manager::WritePipe; +use smithay_client_toolkit::reexports::calloop::RegistrationToken; +use std::cmp::min; +use std::fmt::{Debug, Formatter}; use std::fs::File; -use std::io; -use std::io::{Read, Write}; +use std::io::{ErrorKind, Read, Write}; +use std::os::fd::{AsRawFd, OwnedFd, RawFd}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; -use std::time::UNIX_EPOCH; -use tokio::sync::broadcast; +use std::{fs, io}; use tracing::{debug, error, trace}; -use wayland_client::protocol::wl_seat::WlSeat; -use wayland_client::DispatchData; +use wayland_client::{Connection, QueueHandle}; +use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::ZwlrDataControlSourceV1; static COUNTER: AtomicUsize = AtomicUsize::new(1); @@ -34,6 +34,11 @@ fn get_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } +pub struct SelectionOfferItem { + offer: SelectionOffer, + token: Option, +} + #[derive(Debug, Clone, Eq)] pub struct ClipboardItem { pub id: usize, @@ -47,77 +52,27 @@ impl PartialEq for ClipboardItem { } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub enum ClipboardValue { Text(String), Image(Bytes), Other, } -impl DataControlDeviceHandling for Env { - fn listen(&mut self, f: F) -> DataControlDeviceStatusListener - where - F: FnMut(WlSeat, DataControlDeviceEvent, DispatchData) + 'static, - { - self.data_control_device.listen(f) - } - - fn with_data_control_device(&self, seat: &WlSeat, f: F) -> Result<(), MissingGlobal> - where - F: FnOnce(&DataControlDevice), - { - self.data_control_device.with_data_control_device(seat, f) - } - - fn create_source(&self, mime_types: Vec, callback: F) -> Option - where - F: FnMut(String, WritePipe, DispatchData) + 'static, - { - self.data_control_device.create_source(mime_types, callback) - } -} - -pub fn copy_to_clipboard( - env: &Environment, - seat: &WlSeat, - item: &ClipboardItem, -) -> Result<(), MissingGlobal> -where - E: DataControlDeviceHandling, -{ - debug!("Copying item with id {} [{}]", item.id, item.mime_type); - trace!("Copying: {item:?}"); - - let item = item.clone(); - - env.with_inner(|env| { - let mime_types = vec![INTERNAL_MIME_TYPE.to_string(), item.mime_type]; - let source = env.create_source(mime_types, move |mime_type, mut pipe, _ddata| { - debug!( - "Triggering source callback for item with id {} [{}]", - item.id, mime_type - ); - - // FIXME: Not working for large (buffered) values in xwayland - let bytes = match &item.value { - ClipboardValue::Text(text) => text.as_bytes(), - ClipboardValue::Image(bytes) => bytes.as_ref(), - ClipboardValue::Other => panic!( - "{:?}", - io::Error::new( - io::ErrorKind::Other, - "Attempted to copy unsupported mime type", - ) - ), - }; - - if let Err(err) = pipe.write_all(bytes) { - error!("{err:?}"); +impl Debug for ClipboardValue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Text(text) => text.clone(), + Self::Image(bytes) => { + format!("[{} Bytes]", bytes.len()) + } + Self::Other => "[Unknown]".to_string(), } - }); - - env.with_data_control_device(seat, |device| device.set_selection(&source)) - }) + ) + } } #[derive(Debug)] @@ -133,126 +88,308 @@ enum MimeTypeCategory { } impl MimeType { - fn parse(mime_types: &[String]) -> Option { - mime_types - .iter() - .map(|s| s.to_lowercase()) - .find_map(|mime_type| match mime_type.as_str() { - "text" - | "string" - | "utf8_string" - | "text/plain" - | "text/plain;charset=utf-8" - | "text/plain;charset=iso-8859-1" - | "text/plain;charset=us-ascii" - | "text/plain;charset=unicode" => Some(Self { - value: mime_type, - category: MimeTypeCategory::Text, - }), - "image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp" - | "image/x-bmp" | "image/icon" => Some(Self { - value: mime_type, - category: MimeTypeCategory::Image, - }), - _ => None, - }) + fn parse(mime_type: &str) -> Option { + match mime_type.to_lowercase().as_str() { + "text" + | "string" + | "utf8_string" + | "text/plain" + | "text/plain;charset=utf-8" + | "text/plain;charset=iso-8859-1" + | "text/plain;charset=us-ascii" + | "text/plain;charset=unicode" => Some(Self { + value: mime_type.to_string(), + category: MimeTypeCategory::Text, + }), + "image/png" | "image/jpg" | "image/jpeg" | "image/tiff" | "image/bmp" + | "image/x-bmp" | "image/icon" => Some(Self { + value: mime_type.to_string(), + category: MimeTypeCategory::Image, + }), + _ => None, + } + } + + fn parse_multiple(mime_types: &[String]) -> Option { + mime_types.iter().find_map(|mime| Self::parse(mime)) } } -pub fn receive_offer( - event: DataControlDeviceEvent, - handle: &LoopHandle, - tx: broadcast::Sender>, - mut ddata: DispatchData, -) { - let timestamp = std::time::SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Could not get epoch, system time is probably very wrong") - .as_nanos(); +impl Environment { + pub fn copy_to_clipboard(&mut self, item: Arc, qh: &QueueHandle) { + debug!("Copying item to clipboard: {item:?}"); - let offer = event.0; + // 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()]); - let ddata = ddata - .get::() - .expect("Expected dispatch data to exist"); + source.set_selection(&device.device); + self.copy_paste_sources.push(source); - let handle2 = handle.clone(); + lock!(self.clipboard).replace(item); + } + } - let res = offer.with_mime_types(|mime_types| { - debug!("Offer mime types: {mime_types:?}"); + fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result { + let value = match mime_type.category { + MimeTypeCategory::Text => { + let mut txt = String::new(); + file.read_to_string(&mut txt)?; + + ClipboardValue::Text(txt) + } + MimeTypeCategory::Image => { + let mut bytes = vec![]; + file.read_to_end(&mut bytes)?; + let bytes = Bytes::from(&bytes); + + ClipboardValue::Image(bytes) + } + }; + + Ok(ClipboardItem { + id: get_id(), + value, + mime_type: mime_type.value.clone(), + }) + } +} + +impl DataControlDeviceHandler for Environment { + fn selection( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + data_device: DataControlDevice, + ) { + debug!("Handler received selection event"); + + let mime_types = data_device.selection_mime_types(); if mime_types.contains(&INTERNAL_MIME_TYPE.to_string()) { - debug!("Skipping value provided by bar"); - return Ok(()); + return; } - let mime_type = MimeType::parse(mime_types); - debug!("Detected mime type: {mime_type:?}"); + if let Some(offer) = data_device.selection_offer() { + self.selection_offers + .push(SelectionOfferItem { offer, token: None }); - match mime_type { - Some(mime_type) => { - debug!("[{timestamp}] Sending clipboard read request ({mime_type:?})"); - let read_pipe = offer.receive(mime_type.value.clone())?; - let source = handle.insert_source(read_pipe, move |(), file, ddata| { - debug!( - "[{timestamp}] Reading clipboard contents ({:?})", - &mime_type.category - ); - match read_file(&mime_type, file) { - Ok(item) => { - send!(tx, Arc::new(item)); - } - Err(err) => error!("{err:?}"), - } + let cur_offer = self + .selection_offers + .last_mut() + .expect("Failed to get current offer"); - if let Some(src) = ddata.offer_tokens.remove(×tamp) { - handle2.remove(src); - } - })?; - - ddata.offer_tokens.insert(timestamp, source); - } - None => { + 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!( - tx, + self.clipboard_tx, Arc::new(ClipboardItem { id: usize::MAX, mime_type: String::new(), value: ClipboardValue::Other }) ); + return; + }; + + 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 clipboard = self.clipboard.clone(); + + let token = self + .loop_handle + .insert_source(read_pipe, move |_, file, state| { + let item = state + .selection_offers + .iter() + .position(|o| o.offer == offer_clone) + .map(|p| state.selection_offers.remove(p)) + .expect("Failed to find selection offer item"); + + match Self::read_file(&mime_type, file) { + Ok(item) => { + let item = Arc::new(item); + lock!(clipboard).replace(item.clone()); + send!(tx, item); + } + Err(err) => error!("{err:?}"), + } + + state + .loop_handle + .remove(item.token.expect("Missing item token")); + }); + + match token { + Ok(token) => { + cur_offer.token.replace(token); + } + Err(err) => error!("{err:?}"), + } } } - - Ok::<(), Report>(()) - }); - - if let Err(err) = res { - error!("{err:?}"); } } -fn read_file(mime_type: &MimeType, file: &mut File) -> io::Result { - let value = match mime_type.category { - MimeTypeCategory::Text => { - let mut txt = String::new(); - file.read_to_string(&mut txt)?; +impl DataControlOfferHandler for Environment { + fn offer( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _offer: &mut DataControlDeviceOffer, + _mime_type: String, + ) { + debug!("Handler received offer"); + } +} - ClipboardValue::Text(txt) - } - MimeTypeCategory::Image => { - let mut bytes = vec![]; - file.read_to_end(&mut bytes)?; - let bytes = Bytes::from(&bytes); +impl DataControlSourceHandler for Environment { + fn accept_mime( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _source: &ZwlrDataControlSourceV1, + mime: Option, + ) { + debug!("Accepted mime type: {mime:?}"); + } - ClipboardValue::Image(bytes) + fn send_request( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + source: &ZwlrDataControlSourceV1, + mime: String, + write_pipe: WritePipe, + ) { + debug!("Handler received source send request event ({mime})"); + + if let Some(item) = lock!(self.clipboard).clone() { + let fd = OwnedFd::from(write_pipe); + if self + .copy_paste_sources + .iter_mut() + .any(|s| s.inner() == source && MimeType::parse(&mime).is_some()) + { + trace!("Source found, writing to file"); + + let mut bytes = match &item.value { + 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",) + ), + }; + + let pipe_size = set_pipe_size(fd.as_raw_fd(), bytes.len()) + .expect("Failed to increase pipe size"); + let mut file = File::from(fd.try_clone().expect("Failed to clone fd")); + + trace!("Num bytes: {}", bytes.len()); + + let mut events = (0..16).map(|_| EpollEvent::empty()).collect::>(); + let mut epoll_event = EpollEvent::new(EpollFlags::EPOLLOUT, 0); + + let epoll_fd = epoll_create().unwrap(); + epoll_ctl( + epoll_fd, + EpollOp::EpollCtlAdd, + fd.as_raw_fd(), + &mut epoll_event, + ) + .unwrap(); + + while !bytes.is_empty() { + let chunk = &bytes[..min(pipe_size as usize, bytes.len())]; + + trace!("Writing {} bytes ({} remain)", chunk.len(), bytes.len()); + + epoll_wait(epoll_fd, &mut events, 100).expect("Failed to wait to epoll"); + + match file.write(chunk) { + Ok(_) => bytes = &bytes[chunk.len()..], + Err(err) => { + error!("{err:?}"); + break; + } + } + } + + // for chunk in bytes.chunks(pipe_size as usize) { + // trace!("Writing chunk"); + // file.write(chunk).expect("Failed to write chunk to buffer"); + // file.flush().expect("Failed to flush to file"); + // } + + // match file.write_vectored(&bytes.chunks(pipe_size as usize).map(IoSlice::new).collect::>()) { + // Ok(_) => debug!("Copied item"), + // Err(err) => error!("{err:?}"), + // } + + // match file.write_all(bytes) { + // Ok(_) => debug!("Copied item"), + // Err(err) => error!("{err:?}"), + // } + } else { + error!("Failed to find source"); + } } + } + + fn cancelled( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + source: &ZwlrDataControlSourceV1, + ) { + debug!("Handler received source cancelled event"); + + self.copy_paste_sources + .iter() + .position(|s| s.inner() == source) + .map(|pos| self.copy_paste_sources.remove(pos)); + source.destroy(); + } +} + +/// Attempts to increase the fd pipe size to the requested number of bytes. +/// The kernel will automatically round this up to the nearest page size. +/// If the requested size is larger than the kernel max (normally 1MB), +/// it will be clamped at this. +/// +/// Returns the new size if succeeded +fn set_pipe_size(fd: RawFd, size: usize) -> io::Result { + // clamp size at kernel max + let max_pipe_size = fs::read_to_string("/proc/sys/fs/pipe-max-size") + .expect("Failed to find pipe-max-size virtual kernel file") + .trim() + .parse::() + .expect("Failed to parse pipe-max-size contents"); + + let size = min(size, max_pipe_size); + + let curr_size = fcntl(fd, F_GETPIPE_SZ)? as usize; + + trace!("Current pipe size: {curr_size}"); + + let new_size = if size > curr_size { + trace!("Requesting pipe size increase to (at least): {size}"); + let res = fcntl(fd, F_SETPIPE_SZ(size as i32))?; + trace!("New pipe size: {res}"); + if res < size as i32 { + return Err(io::Error::last_os_error()); + } + res + } else { + size as i32 }; - Ok(ClipboardItem { - id: get_id(), - value, - mime_type: mime_type.value.clone(), - }) + Ok(new_size) } diff --git a/src/clients/wayland/wlr_data_control/offer.rs b/src/clients/wayland/wlr_data_control/offer.rs index ba9dd2b..6c721ae 100644 --- a/src/clients/wayland/wlr_data_control/offer.rs +++ b/src/clients/wayland/wlr_data_control/offer.rs @@ -1,74 +1,183 @@ +use super::manager::DataControlDeviceManagerState; use crate::lock; use nix::fcntl::OFlag; use nix::unistd::{close, pipe2}; -use smithay_client_toolkit::data_device::ReadPipe; -use std::io; +use smithay_client_toolkit::data_device_manager::data_offer::DataOfferError; +use smithay_client_toolkit::data_device_manager::ReadPipe; +use std::ops::DerefMut; use std::os::fd::FromRawFd; use std::sync::{Arc, Mutex}; -use tracing::warn; -use wayland_client::Main; -use wayland_protocols::wlr::unstable::data_control::v1::client::zwlr_data_control_offer_v1::{ +use tracing::{debug, warn}; +use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_offer_v1::{ Event, ZwlrDataControlOfferV1, }; #[derive(Debug, Clone)] -struct Inner { - mime_types: Vec, +pub struct UndeterminedOffer { + pub(crate) data_offer: Option, +} + +impl PartialEq for UndeterminedOffer { + fn eq(&self, other: &Self) -> bool { + self.data_offer == other.data_offer + } } #[derive(Debug, Clone)] -pub struct DataControlOffer { - inner: Arc>, - pub(crate) offer: ZwlrDataControlOfferV1, +pub struct SelectionOffer { + pub data_offer: ZwlrDataControlOfferV1, } -impl DataControlOffer { - pub(crate) fn new(offer: &Main) -> Self { - let inner = Arc::new(Mutex::new(Inner { - mime_types: Vec::new(), - })); - - { - let inner = inner.clone(); - - offer.quick_assign(move |_, event, _| { - let mut inner = lock!(inner); - if let Event::Offer { mime_type } = event { - inner.mime_types.push(mime_type); - } - }); - } - - Self { - offer: offer.detach(), - inner, - } - } - - pub fn with_mime_types(&self, f: F) -> T - where - F: FnOnce(&[String]) -> T, - { - let inner = lock!(self.inner); - f(&inner.mime_types) - } - - pub fn receive(&self, mime_type: String) -> io::Result { - // create a pipe - let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?; - - self.offer.receive(mime_type, writefd); - - if let Err(err) = close(writefd) { - warn!("Failed to close write pipe: {}", err); - } - - Ok(unsafe { FromRawFd::from_raw_fd(readfd) }) +impl PartialEq for SelectionOffer { + fn eq(&self, other: &Self) -> bool { + self.data_offer == other.data_offer } } -impl Drop for DataControlOffer { - fn drop(&mut self) { - self.offer.destroy(); +impl SelectionOffer { + pub fn receive(&self, mime_type: String) -> Result { + receive(&self.data_offer, mime_type).map_err(DataOfferError::Io) } } + +#[derive(Debug, Clone, PartialEq)] +pub enum DataControlDeviceOffer { + Selection(SelectionOffer), + Undetermined(UndeterminedOffer), +} + +impl Default for DataControlDeviceOffer { + fn default() -> Self { + Self::Undetermined(UndeterminedOffer { data_offer: None }) + } +} + +#[derive(Debug, Default)] +pub struct DataControlOfferData { + pub(crate) inner: Arc>, +} + +#[derive(Debug, Default)] +pub struct DataControlDeviceOfferInner { + pub(crate) offer: DataControlDeviceOffer, + pub(crate) mime_types: Vec, +} + +impl DataControlOfferData { + pub(crate) fn push_mime_type(&self, mime_type: String) { + lock!(self.inner).mime_types.push(mime_type); + } + + pub(crate) fn to_selection_offer(&self) { + let mut inner = lock!(self.inner); + match &mut inner.deref_mut().offer { + DataControlDeviceOffer::Selection(_) => {} + DataControlDeviceOffer::Undetermined(o) => { + inner.offer = DataControlDeviceOffer::Selection(SelectionOffer { + data_offer: o.data_offer.clone().expect("Missing current data offer"), + }); + } + } + } + + pub(crate) fn init_undetermined_offer(&self, offer: &ZwlrDataControlOfferV1) { + let mut inner = lock!(self.inner); + match &mut inner.deref_mut().offer { + DataControlDeviceOffer::Selection(_) => { + inner.offer = DataControlDeviceOffer::Undetermined(UndeterminedOffer { + data_offer: Some(offer.clone()), + }); + } + DataControlDeviceOffer::Undetermined(o) => { + o.data_offer = Some(offer.clone()); + } + } + } +} + +pub trait DataControlOfferDataExt { + fn data_control_offer_data(&self) -> &DataControlOfferData; + fn mime_types(&self) -> Vec; + fn as_selection_offer(&self) -> Option; +} + +impl DataControlOfferDataExt for DataControlOfferData { + fn data_control_offer_data(&self) -> &DataControlOfferData { + self + } + + fn mime_types(&self) -> Vec { + lock!(self.inner).mime_types.clone() + } + + fn as_selection_offer(&self) -> Option { + match &lock!(self.inner).offer { + DataControlDeviceOffer::Selection(o) => Some(o.clone()), + DataControlDeviceOffer::Undetermined(_) => None, + } + } +} + +/// Handler trait for `DataOffer` events. +/// +/// The functions defined in this trait are called as `DataOffer` events are received from the compositor. +pub trait DataControlOfferHandler: Sized { + // Called for each mime type the data offer advertises. + fn offer( + &mut self, + conn: &Connection, + qh: &QueueHandle, + offer: &mut DataControlDeviceOffer, + mime_type: String, + ); +} + +impl Dispatch for DataControlDeviceManagerState +where + D: Dispatch + DataControlOfferHandler, + U: DataControlOfferDataExt, +{ + fn event( + state: &mut D, + _offer: &ZwlrDataControlOfferV1, + event: ::Event, + data: &U, + conn: &Connection, + qh: &QueueHandle, + ) { + let data = data.data_control_offer_data(); + + if let Event::Offer { mime_type } = event { + debug!("Adding new offer with type '{mime_type}'"); + data.push_mime_type(mime_type.clone()); + state.offer(conn, qh, &mut lock!(data.inner).offer, mime_type); + } + } +} + +/// Request to receive the data of a given mime type. +/// +/// You can do this several times, as a reaction to motion of +/// the dnd cursor, or to inspect the data in order to choose your +/// response. +/// +/// Note that you should *not* read the contents right away in a +/// blocking way, as you may deadlock your application doing so. +/// At least make sure you flush your events to the server before +/// doing so. +/// +/// Fails if too many file descriptors were already open and a pipe +/// could not be created. +pub fn receive(offer: &ZwlrDataControlOfferV1, mime_type: String) -> std::io::Result { + // create a pipe + let (readfd, writefd) = pipe2(OFlag::O_CLOEXEC)?; + + offer.receive(mime_type, writefd); + + if let Err(err) = close(writefd) { + warn!("Failed to close write pipe: {}", err); + } + + Ok(unsafe { FromRawFd::from_raw_fd(readfd) }) +} diff --git a/src/clients/wayland/wlr_data_control/source.rs b/src/clients/wayland/wlr_data_control/source.rs index 305b46a..a184d8d 100644 --- a/src/clients/wayland/wlr_data_control/source.rs +++ b/src/clients/wayland/wlr_data_control/source.rs @@ -1,54 +1,101 @@ -use smithay_client_toolkit::data_device::WritePipe; -use std::os::fd::FromRawFd; -use wayland_client::{Attached, DispatchData}; -use wayland_protocols::wlr::unstable::data_control::v1::client::{ - zwlr_data_control_manager_v1::ZwlrDataControlManagerV1, - zwlr_data_control_source_v1::{Event, ZwlrDataControlSourceV1}, +use super::device::DataControlDevice; +use super::manager::DataControlDeviceManagerState; +use smithay_client_toolkit::data_device_manager::WritePipe; +use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols_wlr::data_control::v1::client::zwlr_data_control_source_v1::{ + Event, ZwlrDataControlSourceV1, }; -fn data_control_source_impl( - source: &ZwlrDataControlSourceV1, - event: Event, - implem: &mut F, - ddata: DispatchData, -) where - F: FnMut(String, WritePipe, DispatchData), +#[derive(Debug, Default)] +pub struct DataControlSourceData {} + +pub trait DataControlSourceDataExt: Send + Sync { + fn data_source_data(&self) -> &DataControlSourceData; +} + +impl DataControlSourceDataExt for DataControlSourceData { + fn data_source_data(&self) -> &DataControlSourceData { + self + } +} + +/// Handler trait for `DataSource` events. +/// +/// The functions defined in this trait are called as `DataSource` events are received from the compositor. +pub trait DataControlSourceHandler: Sized { + /// This may be called multiple times, once for each accepted mime type from the destination, if any. + fn accept_mime( + &mut self, + conn: &Connection, + qh: &QueueHandle, + source: &ZwlrDataControlSourceV1, + mime: Option, + ); + + /// The client has requested the data for this source to be sent. + /// Send the data, then close the fd. + fn send_request( + &mut self, + conn: &Connection, + qh: &QueueHandle, + source: &ZwlrDataControlSourceV1, + mime: String, + fd: WritePipe, + ); + + /// The data source is no longer valid + /// Cleanup & destroy this resource + fn cancelled( + &mut self, + conn: &Connection, + qh: &QueueHandle, + source: &ZwlrDataControlSourceV1, + ); +} + +impl Dispatch for DataControlDeviceManagerState +where + D: Dispatch + DataControlSourceHandler, + U: DataControlSourceDataExt, { - match event { - Event::Send { mime_type, fd } => { - let pipe = unsafe { FromRawFd::from_raw_fd(fd) }; - implem(mime_type, pipe, ddata); - } - Event::Cancelled => source.destroy(), - _ => unreachable!(), - } -} - -pub struct DataControlSource { - pub(crate) source: ZwlrDataControlSourceV1, -} - -impl DataControlSource { - pub fn new( - manager: &Attached, - mime_types: Vec, - mut callback: F, - ) -> Self - where - F: FnMut(String, WritePipe, DispatchData) + 'static, - { - let source = manager.create_data_source(); - - source.quick_assign(move |source, evt, ddata| { - data_control_source_impl(&source, evt, &mut callback, ddata); - }); - - for mime_type in mime_types { - source.offer(mime_type); - } - - Self { - source: source.detach(), + fn event( + state: &mut D, + source: &ZwlrDataControlSourceV1, + event: ::Event, + _data: &U, + conn: &Connection, + qh: &QueueHandle, + ) { + match event { + Event::Send { mime_type, fd } => { + state.send_request(conn, qh, source, mime_type, fd.into()); + } + Event::Cancelled => { + state.cancelled(conn, qh, source); + } + _ => {} } } } + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct CopyPasteSource { + pub(crate) inner: ZwlrDataControlSourceV1, +} + +impl CopyPasteSource { + /// Set the selection of the provided data device as a response to the event with with provided serial. + pub fn set_selection(&self, device: &DataControlDevice) { + device.device.set_selection(Some(&self.inner)); + } + + pub const fn inner(&self) -> &ZwlrDataControlSourceV1 { + &self.inner + } +} + +impl Drop for CopyPasteSource { + fn drop(&mut self) { + self.inner.destroy(); + } +} diff --git a/src/clients/wayland/wlr_foreign_toplevel/handle.rs b/src/clients/wayland/wlr_foreign_toplevel/handle.rs index aab2828..2279472 100644 --- a/src/clients/wayland/wlr_foreign_toplevel/handle.rs +++ b/src/clients/wayland/wlr_foreign_toplevel/handle.rs @@ -1,13 +1,15 @@ +use super::manager::ToplevelManagerState; +use crate::lock; use std::collections::HashSet; -use std::sync::{Arc, RwLock}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; use tracing::trace; -use wayland_client::{DispatchData, Main}; -use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{Event, ZwlrForeignToplevelHandleV1}; -use crate::write_lock; - -const STATE_ACTIVE: u32 = 2; -const STATE_FULLSCREEN: u32 = 3; +use wayland_client::protocol::wl_output::WlOutput; +use wayland_client::protocol::wl_seat::WlSeat; +use wayland_client::{Connection, Dispatch, Proxy, QueueHandle}; +use wayland_protocols_wlr::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::{ + Event, ZwlrForeignToplevelHandleV1, +}; static COUNTER: AtomicUsize = AtomicUsize::new(1); @@ -15,138 +17,168 @@ fn get_id() -> usize { COUNTER.fetch_add(1, Ordering::Relaxed) } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] +pub struct ToplevelHandle { + pub handle: ZwlrForeignToplevelHandleV1, +} + +impl PartialEq for ToplevelHandle { + fn eq(&self, other: &Self) -> bool { + self.handle == other.handle + } +} + +impl ToplevelHandle { + pub fn info(&self) -> Option { + trace!("Retrieving handle info"); + + let data = self.handle.data::()?; + data.info() + } + + pub fn focus(&self, seat: &WlSeat) { + trace!("Activating handle"); + self.handle.activate(seat); + } +} + +#[derive(Debug, Default)] +pub struct ToplevelHandleData { + pub inner: Arc>, +} + +impl ToplevelHandleData { + fn info(&self) -> Option { + lock!(self.inner).current_info.clone() + } +} + +#[derive(Debug, Default)] +pub struct ToplevelHandleDataInner { + initial_done: bool, + output: Option, + + current_info: Option, + pending_info: ToplevelInfo, +} + +#[derive(Debug, Clone)] pub struct ToplevelInfo { pub id: usize, pub app_id: String, pub title: String, - pub active: bool, pub fullscreen: bool, - - ready: bool, + pub focused: bool, } -impl ToplevelInfo { - fn new() -> Self { - let id = get_id(); +impl Default for ToplevelInfo { + fn default() -> Self { Self { - id, - ..Default::default() + id: get_id(), + app_id: String::new(), + title: String::new(), + fullscreen: false, + focused: false, } } } -pub struct Toplevel; - -#[derive(Debug, Clone)] -pub struct ToplevelEvent { - pub toplevel: ToplevelInfo, - pub change: ToplevelChange, +pub trait ToplevelHandleDataExt { + fn toplevel_handle_data(&self) -> &ToplevelHandleData; } -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ToplevelChange { - New, - Close, - Title(String), - Focus(bool), - Fullscreen(bool), +impl ToplevelHandleDataExt for ToplevelHandleData { + fn toplevel_handle_data(&self) -> &ToplevelHandleData { + self + } } -fn toplevel_implem(event: Event, info: &mut ToplevelInfo, implem: &mut F, ddata: DispatchData) +pub trait ToplevelHandleHandler: Sized { + fn new_handle(&mut self, conn: &Connection, qh: &QueueHandle, handle: ToplevelHandle); + + fn update_handle(&mut self, conn: &Connection, qh: &QueueHandle, handle: ToplevelHandle); + + fn remove_handle(&mut self, conn: &Connection, qh: &QueueHandle, handle: ToplevelHandle); +} + +impl Dispatch for ToplevelManagerState where - F: FnMut(ToplevelEvent, DispatchData), + D: Dispatch + ToplevelHandleHandler, + U: ToplevelHandleDataExt, { - trace!("event: {event:?} (info: {info:?})"); + fn event( + state: &mut D, + handle: &ZwlrForeignToplevelHandleV1, + event: Event, + data: &U, + conn: &Connection, + qh: &QueueHandle, + ) { + const STATE_ACTIVE: u32 = 2; + const STATE_FULLSCREEN: u32 = 3; - let change = match event { - Event::AppId { app_id } => { - info.app_id = app_id; - None - } - Event::Title { title } => { - info.title = title.clone(); + let data = data.toplevel_handle_data(); - if info.ready { - Some(ToplevelChange::Title(title)) - } else { - None + trace!("Processing handle event: {event:?}"); + + match event { + Event::Title { title } => { + lock!(data.inner).pending_info.title = title; } - } - Event::State { state } => { - // state is received as a `Vec` where every 4 bytes make up a `u32` - // the u32 then represents a value in the `State` enum. - assert_eq!(state.len() % 4, 0); + Event::AppId { app_id } => lock!(data.inner).pending_info.app_id = app_id, + Event::State { state } => { + // state is received as a `Vec` where every 4 bytes make up a `u32` + // the u32 then represents a value in the `State` enum. + assert_eq!(state.len() % 4, 0); + let state = (0..state.len() / 4) + .map(|i| { + let slice: [u8; 4] = state[i * 4..i * 4 + 4] + .try_into() + .expect("Received invalid state length"); + u32::from_le_bytes(slice) + }) + .collect::>(); - let state = (0..state.len() / 4) - .map(|i| { - let slice: [u8; 4] = state[i * 4..i * 4 + 4] - .try_into() - .expect("Received invalid state length"); - u32::from_le_bytes(slice) - }) - .collect::>(); - - let new_active = state.contains(&STATE_ACTIVE); - let new_fullscreen = state.contains(&STATE_FULLSCREEN); - - let change = if info.ready && new_active != info.active { - Some(ToplevelChange::Focus(new_active)) - } else if info.ready && new_fullscreen != info.fullscreen { - Some(ToplevelChange::Fullscreen(new_fullscreen)) - } else { - None - }; - - info.active = new_active; - info.fullscreen = new_fullscreen; - - change - } - Event::Closed => { - if info.ready { - Some(ToplevelChange::Close) - } else { - None + lock!(data.inner).pending_info.focused = state.contains(&STATE_ACTIVE); + lock!(data.inner).pending_info.fullscreen = state.contains(&STATE_FULLSCREEN); } - } - Event::OutputEnter { output: _ } - | Event::OutputLeave { output: _ } - | Event::Parent { parent: _ } => None, - Event::Done => { - if info.ready || info.app_id.is_empty() { - None - } else { - info.ready = true; - Some(ToplevelChange::New) + Event::OutputEnter { output } => lock!(data.inner).output = Some(output), + Event::OutputLeave { output: _ } => lock!(data.inner).output = None, + Event::Closed => state.remove_handle( + conn, + qh, + ToplevelHandle { + handle: handle.clone(), + }, + ), + Event::Done => { + { + let pending_info = lock!(data.inner).pending_info.clone(); + lock!(data.inner).current_info = Some(pending_info); + } + + if !lock!(data.inner).initial_done { + lock!(data.inner).initial_done = true; + state.new_handle( + conn, + qh, + ToplevelHandle { + handle: handle.clone(), + }, + ); + } else { + state.update_handle( + conn, + qh, + ToplevelHandle { + handle: handle.clone(), + }, + ); + } } + _ => {} } - _ => unreachable!(), - }; - - if let Some(change) = change { - let event = ToplevelEvent { - change, - toplevel: info.clone(), - }; - - implem(event, ddata); - } -} - -impl Toplevel { - pub fn init(handle: &Main, mut callback: F) -> Self - where - F: FnMut(ToplevelEvent, DispatchData) + 'static, - { - let inner = Arc::new(RwLock::new(ToplevelInfo::new())); - - handle.quick_assign(move |_handle, event, ddata| { - let mut inner = write_lock!(inner); - toplevel_implem(event, &mut inner, &mut callback, ddata); - }); - - Self + trace!("Event processed"); } } diff --git a/src/clients/wayland/wlr_foreign_toplevel/manager.rs b/src/clients/wayland/wlr_foreign_toplevel/manager.rs index af136fa..ed3d56a 100644 --- a/src/clients/wayland/wlr_foreign_toplevel/manager.rs +++ b/src/clients/wayland/wlr_foreign_toplevel/manager.rs @@ -1,163 +1,86 @@ -use super::handle::{Toplevel, ToplevelEvent}; -use crate::wayland::LazyGlobal; -use smithay_client_toolkit::environment::{Environment, GlobalHandler}; -use std::cell::RefCell; -use std::rc::{self, Rc}; -use tracing::warn; -use wayland_client::protocol::wl_registry::WlRegistry; -use wayland_client::{Attached, DispatchData}; -use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::{ +use super::handle::{ToplevelHandleData, ToplevelHandleDataExt, ToplevelHandleHandler}; +use smithay_client_toolkit::error::GlobalError; +use smithay_client_toolkit::globals::{GlobalData, ProvidesBoundGlobal}; +use std::marker::PhantomData; +use tracing::{debug, warn}; +use wayland_client::globals::{BindError, GlobalList}; +use wayland_client::{event_created_child, Connection, Dispatch, QueueHandle}; +use wayland_protocols_wlr::foreign_toplevel::v1::client::{ zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1, - zwlr_foreign_toplevel_manager_v1::{self, ZwlrForeignToplevelManagerV1}, + zwlr_foreign_toplevel_manager_v1::{Event, ZwlrForeignToplevelManagerV1}, }; -struct ToplevelHandlerInner { - manager: LazyGlobal, - registry: Option>, - toplevels: Vec, +pub struct ToplevelManagerState { + manager: ZwlrForeignToplevelManagerV1, + _phantom: PhantomData, } -impl ToplevelHandlerInner { - const fn new() -> Self { - let toplevels = vec![]; - - Self { - registry: None, - manager: LazyGlobal::Unknown, - toplevels, - } - } -} - -pub struct ToplevelHandler { - inner: Rc>, - status_listeners: Rc>>>>, -} - -impl ToplevelHandler { - pub fn init() -> Self { - let inner = Rc::new(RefCell::new(ToplevelHandlerInner::new())); - - Self { - inner, - status_listeners: Rc::new(RefCell::new(Vec::new())), - } - } -} - -impl GlobalHandler for ToplevelHandler { - fn created( - &mut self, - registry: Attached, - id: u32, - version: u32, - _ddata: DispatchData, - ) { - let mut inner = RefCell::borrow_mut(&self.inner); - if inner.registry.is_none() { - inner.registry = Some(registry); - } - if matches!(inner.manager, LazyGlobal::Unknown) { - inner.manager = LazyGlobal::Seen { id, version } - } else { - warn!( - "Compositor advertised zwlr_foreign_toplevel_manager_v1 multiple times, ignoring." - ); - } - } - - fn get(&self) -> Option> { - let mut inner = RefCell::borrow_mut(&self.inner); - - match inner.manager { - LazyGlobal::Bound(ref mgr) => Some(mgr.clone()), - LazyGlobal::Unknown => None, - LazyGlobal::Seen { id, version } => { - let registry = inner.registry.as_ref().expect("Failed to get registry"); - // current max protocol version = 3 - let version = std::cmp::min(version, 3); - let manager = registry.bind::(version, id); - - { - let inner = self.inner.clone(); - let status_listeners = self.status_listeners.clone(); - - manager.quick_assign(move |_, event, _ddata| { - let mut inner = RefCell::borrow_mut(&inner); - let status_listeners = status_listeners.clone(); - - match event { - zwlr_foreign_toplevel_manager_v1::Event::Toplevel { - toplevel: handle, - } => { - let toplevel = - Toplevel::init(&handle.clone(), move |event, ddata| { - notify_status_listeners( - &handle, - &event, - ddata, - &status_listeners, - ); - }); - - inner.toplevels.push(toplevel); - } - zwlr_foreign_toplevel_manager_v1::Event::Finished => {} - _ => unreachable!(), - } - }); - } - - inner.manager = LazyGlobal::Bound((*manager).clone()); - Some((*manager).clone()) - } - } - } -} - -type ToplevelStatusCallback = - dyn FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static; - -/// Notifies the callbacks of an event on the toplevel -fn notify_status_listeners( - toplevel: &ZwlrForeignToplevelHandleV1, - event: &ToplevelEvent, - mut ddata: DispatchData, - listeners: &RefCell>>>, -) { - listeners.borrow_mut().retain(|lst| { - rc::Weak::upgrade(lst).map_or(false, |cb| { - (cb.borrow_mut())(toplevel.clone(), event.clone(), ddata.reborrow()); - true - }) - }); -} - -pub struct ToplevelStatusListener { - _cb: Rc>, -} - -pub trait ToplevelHandling { - fn listen(&mut self, f: F) -> ToplevelStatusListener +impl ToplevelManagerState { + pub fn bind(globals: &GlobalList, qh: &QueueHandle) -> Result where - F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static; -} - -impl ToplevelHandling for ToplevelHandler { - fn listen(&mut self, f: F) -> ToplevelStatusListener - where - F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static, + State: Dispatch + 'static, { - let rc = Rc::new(RefCell::new(f)) as Rc<_>; - self.status_listeners.borrow_mut().push(Rc::downgrade(&rc)); - ToplevelStatusListener { _cb: rc } + let manager = globals.bind(qh, 1..=3, GlobalData)?; + debug!("Bound to ZwlForeignToplevelManagerV1 global"); + Ok(Self { + manager, + _phantom: PhantomData, + }) } } -pub fn listen_for_toplevels(env: &Environment, f: F) -> ToplevelStatusListener -where - E: ToplevelHandling, - F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static, -{ - env.with_inner(move |inner| ToplevelHandling::listen(inner, f)) +pub trait ToplevelManagerHandler: Sized { + /// Advertises a new toplevel. + fn toplevel( + &mut self, + conn: &Connection, + qh: &QueueHandle, + manager: ToplevelManagerState, + ); +} + +impl ProvidesBoundGlobal for ToplevelManagerState { + fn bound_global(&self) -> Result { + Ok(self.manager.clone()) + } +} + +impl Dispatch for ToplevelManagerState +where + D: Dispatch + + Dispatch + + ToplevelManagerHandler + + ToplevelHandleHandler + + 'static, + V: ToplevelHandleDataExt + Default + 'static + Send + Sync, +{ + event_created_child!(D, ZwlrForeignToplevelManagerV1, [ + 0 => (ZwlrForeignToplevelHandleV1, V::default()) + ]); + + fn event( + state: &mut D, + toplevel_manager: &ZwlrForeignToplevelManagerV1, + event: Event, + _data: &GlobalData, + conn: &Connection, + qhandle: &QueueHandle, + ) { + match event { + Event::Toplevel { toplevel: _ } => { + state.toplevel( + conn, + qhandle, + ToplevelManagerState { + manager: toplevel_manager.clone(), + _phantom: PhantomData, + }, + ); + } + Event::Finished => { + warn!("Foreign toplevel manager is no longer valid, but has not been dropped by client. This could cause window tracking issues."); + } + _ => {} + } + } } diff --git a/src/clients/wayland/wlr_foreign_toplevel/mod.rs b/src/clients/wayland/wlr_foreign_toplevel/mod.rs index edb9691..14cbac9 100644 --- a/src/clients/wayland/wlr_foreign_toplevel/mod.rs +++ b/src/clients/wayland/wlr_foreign_toplevel/mod.rs @@ -1,39 +1,84 @@ -use std::sync::RwLock; -use indexmap::IndexMap; -use tokio::sync::broadcast::Sender; -use tracing::trace; -use super::Env; -use handle::{ToplevelEvent, ToplevelChange, ToplevelInfo}; -use manager::{ToplevelHandling, ToplevelStatusListener}; -use wayland_client::DispatchData; -use wayland_protocols::wlr::unstable::foreign_toplevel::v1::client::zwlr_foreign_toplevel_handle_v1::ZwlrForeignToplevelHandleV1; -use crate::{send, write_lock}; - pub mod handle; pub mod manager; -impl ToplevelHandling for Env { - fn listen(&mut self, f: F) -> ToplevelStatusListener - where - F: FnMut(ZwlrForeignToplevelHandleV1, ToplevelEvent, DispatchData) + 'static, - { - self.toplevel.listen(f) +use self::handle::ToplevelHandleHandler; +use self::manager::{ToplevelManagerHandler, ToplevelManagerState}; +use crate::clients::wayland::Environment; +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), +} + +impl ToplevelManagerHandler for Environment { + fn toplevel( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + _manager: ToplevelManagerState, + ) { + debug!("Manager received new handle"); } } -pub fn update_toplevels( - toplevels: &RwLock>, - handle: ZwlrForeignToplevelHandleV1, - event: ToplevelEvent, - tx: &Sender, -) { - trace!("Received toplevel event: {:?}", event); +impl ToplevelHandleHandler for Environment { + fn new_handle(&mut self, _conn: &Connection, _qh: &QueueHandle, handle: ToplevelHandle) { + debug!("Handler received new handle"); - if event.change == ToplevelChange::Close { - write_lock!(toplevels).remove(&event.toplevel.id); - } else { - write_lock!(toplevels).insert(event.toplevel.id, (event.toplevel.clone(), 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)); + } + None => { + error!("Handle is missing information!"); + } + } } - send!(tx, event); + fn update_handle( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + handle: ToplevelHandle, + ) { + debug!("Handler received handle update"); + + match handle.info() { + Some(info) => { + trace!("Updating handle: {info:?}"); + self.handles.insert(info.id, handle.clone()); + send!(self.toplevel_tx, ToplevelEvent::Update(handle)); + } + None => { + error!("Handle is missing information!"); + } + } + } + + fn remove_handle( + &mut self, + _conn: &Connection, + _qh: &QueueHandle, + 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!"); + } + } + } } diff --git a/src/config/common.rs b/src/config/common.rs index e0f97b5..0188cd3 100644 --- a/src/config/common.rs +++ b/src/config/common.rs @@ -37,17 +37,16 @@ pub enum TransitionType { } impl TransitionType { - pub fn to_revealer_transition_type(&self, orientation: Orientation) -> RevealerTransitionType { + pub const fn to_revealer_transition_type( + &self, + orientation: Orientation, + ) -> RevealerTransitionType { match (self, orientation) { - (TransitionType::SlideStart, Orientation::Horizontal) => { - RevealerTransitionType::SlideLeft - } - (TransitionType::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp, - (TransitionType::SlideEnd, Orientation::Horizontal) => { - RevealerTransitionType::SlideRight - } - (TransitionType::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown, - (TransitionType::Crossfade, _) => RevealerTransitionType::Crossfade, + (Self::SlideStart, Orientation::Horizontal) => RevealerTransitionType::SlideLeft, + (Self::SlideStart, Orientation::Vertical) => RevealerTransitionType::SlideUp, + (Self::SlideEnd, Orientation::Horizontal) => RevealerTransitionType::SlideRight, + (Self::SlideEnd, Orientation::Vertical) => RevealerTransitionType::SlideDown, + (Self::Crossfade, _) => RevealerTransitionType::Crossfade, _ => RevealerTransitionType::None, } } @@ -152,7 +151,7 @@ impl CommonConfig { revealer.connect_child_revealed_notify(move |revealer| { if !revealer.reveals_child() { - container.hide() + container.hide(); } }); }, diff --git a/src/desktop_file.rs b/src/desktop_file.rs index a2c9758..bcab737 100644 --- a/src/desktop_file.rs +++ b/src/desktop_file.rs @@ -29,13 +29,12 @@ pub fn find_desktop_file(app_id: &str) -> Option { for dir in dirs { let mut walker = WalkDir::new(dir).max_depth(5).into_iter(); - let entry = walker.find(|entry| match entry { - Ok(entry) => { + let entry = walker.find(|entry| { + entry.as_ref().map_or(false, |entry| { let file_name = entry.file_name().to_string_lossy().to_lowercase(); let test_name = format!("{}.desktop", app_id.to_lowercase()); file_name == test_name - } - _ => false, + }) }); if let Some(Ok(entry)) = entry { diff --git a/src/dynamic_string.rs b/src/dynamic_string.rs index edfacc7..b27e2d3 100644 --- a/src/dynamic_string.rs +++ b/src/dynamic_string.rs @@ -55,7 +55,7 @@ impl DynamicString { if let OutputStream::Stdout(out) = out { let mut label_parts = lock!(label_parts); - let _ = std::mem::replace(&mut label_parts[i], out); + let _: String = std::mem::replace(&mut label_parts[i], out); let string = label_parts.join(""); send!(tx, string); diff --git a/src/error.rs b/src/error.rs index d5a19b2..bfb22b2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,9 +5,11 @@ pub enum ExitCode { Config = 3, } -pub const ERR_OUTPUTS: &str = "GTK and Sway are reporting a different set of outputs - this is a severe bug and should never happen"; +pub const ERR_OUTPUTS: &str = "GTK and Wayland are reporting a different set of outputs - this is a severe bug and should never happen"; pub const ERR_MUTEX_LOCK: &str = "Failed to get lock on Mutex"; pub const ERR_READ_LOCK: &str = "Failed to get read lock"; pub const ERR_WRITE_LOCK: &str = "Failed to get write lock"; pub const ERR_CHANNEL_SEND: &str = "Failed to send message to channel"; pub const ERR_CHANNEL_RECV: &str = "Failed to receive message from channel"; + +pub const ERR_WAYLAND_DATA: &str = "Failed to get data for Wayland object"; diff --git a/src/logging.rs b/src/logging.rs index 0357955..18d5995 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -58,7 +58,7 @@ fn install_tracing() -> Result { const DEFAULT_LOG: &str = "info"; const DEFAULT_FILE_LOG: &str = "warn"; - let fmt_layer = fmt::layer().with_target(true); + let fmt_layer = fmt::layer().with_target(true).with_line_number(true); let filter_layer = EnvFilter::try_from_env("IRONBAR_LOG").or_else(|_| EnvFilter::try_new(DEFAULT_LOG))?; diff --git a/src/macros.rs b/src/macros.rs index 2197024..49e91a2 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -53,9 +53,10 @@ macro_rules! try_send { /// ``` #[macro_export] macro_rules! lock { - ($mutex:expr) => { + ($mutex:expr) => {{ + tracing::trace!("Locking {}", std::stringify!($mutex)); $mutex.lock().expect($crate::error::ERR_MUTEX_LOCK) - }; + }}; } /// Gets a read lock on a `RwLock`. diff --git a/src/main.rs b/src/main.rs index 84fb2ad..984126b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -127,7 +127,7 @@ fn create_bars( wl: &WaylandClient, config: &Config, ) -> Result<()> { - let outputs = wl.outputs.as_slice(); + let outputs = wl.get_outputs(); debug!("Received {} outputs from Wayland", outputs.len()); debug!("Outputs: {:?}", outputs); @@ -141,7 +141,8 @@ fn create_bars( let output = outputs .get(i as usize) .ok_or_else(|| Report::msg(error::ERR_OUTPUTS))?; - let monitor_name = &output.name; + + let Some(monitor_name) = &output.name else { continue }; config.monitors.as_ref().map_or_else( || { diff --git a/src/modules/clipboard.rs b/src/modules/clipboard.rs index 5045a1a..3e5e061 100644 --- a/src/modules/clipboard.rs +++ b/src/modules/clipboard.rs @@ -80,7 +80,7 @@ impl Module