diff --git a/Cargo.toml b/Cargo.toml index e3c6a34..76847c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -218,23 +218,23 @@ version = "0.9.0" optional = true [dependencies.wayland-backend] -version = "0.3.5" +version = "0.3.6" optional = true [dependencies.wayland-client] -version = "0.31.3" +version = "0.31.8" optional = true [dependencies.wayland-cursor] -version = "0.31.3" +version = "0.31.8" optional = true [dependencies.wayland-egl] -version = "0.32.0" +version = "0.32.5" optional = true [dependencies.wayland-protocols] -version = "0.32.5" +version = "0.32.6" features = [ "unstable", "staging", @@ -243,21 +243,21 @@ features = [ optional = true [dependencies.wayland-protocols-misc] -version = "0.3.1" +version = "0.3.6" features = ["server"] optional = true [dependencies.wayland-protocols-wlr] -version = "0.3.1" +version = "0.3.6" features = ["server"] optional = true [dependencies.wayland-server] -version = "0.31.0" +version = "0.31.7" optional = true [dependencies.wayland-sys] -version = "0.31" +version = "0.31.6" optional = true [dependencies.winit] diff --git a/src/utils/clock.rs b/src/utils/clock.rs index bc44a39..78bf7f9 100644 --- a/src/utils/clock.rs +++ b/src/utils/clock.rs @@ -71,10 +71,12 @@ impl Time { /// This should match timestamps from libinput: /// pub fn as_millis(&self) -> u32 { - // Assume monotonic clock (but not realitime) fits as milliseconds in 32-bit debug_assert!(self.tp.tv_sec >= 0); debug_assert!(self.tp.tv_nsec >= 0); - self.tp.tv_sec as u32 * 1000 + self.tp.tv_nsec as u32 / 1000000 + + // The monotonic clock does not fit as milliseconds in 32-bit after ~50 days of uptime. We + // do a modulo conversion which should match what happens in libinput. + (self.as_micros() / 1000) as u32 } /// Returns the time in microseconds diff --git a/src/utils/geometry.rs b/src/utils/geometry.rs index d899f3b..581eb95 100644 --- a/src/utils/geometry.rs +++ b/src/utils/geometry.rs @@ -1115,13 +1115,13 @@ impl Rectangle { /// Convert to i32 by returning the largest integer-space rectangle fitting into the float-based rectangle #[inline] pub fn to_i32_down(self) -> Rectangle { - Rectangle::from_extemities(self.loc.to_i32_ceil(), (self.loc + self.size).to_i32_floor()) + Rectangle::from_extremities(self.loc.to_i32_ceil(), (self.loc + self.size).to_i32_floor()) } /// Convert to i32 by returning the smallest integet-space rectangle encapsulating the float-based rectangle #[inline] pub fn to_i32_up(self) -> Rectangle { - Rectangle::from_extemities(self.loc.to_i32_floor(), (self.loc + self.size).to_i32_ceil()) + Rectangle::from_extremities(self.loc.to_i32_floor(), (self.loc + self.size).to_i32_ceil()) } } @@ -1162,9 +1162,20 @@ impl Rectangle { /// Create a new [`Rectangle`] from the coordinates of its top-left corner and its bottom-right corner #[inline] + #[deprecated = "use Rectangle::from_extremities instead"] + #[doc(hidden)] pub fn from_extemities( topleft: impl Into>, bottomright: impl Into>, + ) -> Self { + Rectangle::from_extremities(topleft, bottomright) + } + + /// Create a new [`Rectangle`] from the coordinates of its top-left corner and its bottom-right corner + #[inline] + pub fn from_extremities( + topleft: impl Into>, + bottomright: impl Into>, ) -> Self { let topleft = topleft.into(); let bottomright = bottomright.into(); @@ -1236,7 +1247,7 @@ impl Rectangle { if !self.overlaps(other) { return None; } - Some(Rectangle::from_extemities( + Some(Rectangle::from_extremities( (self.loc.x.max(other.loc.x), self.loc.y.max(other.loc.y)), ( (self.loc.x.saturating_add(self.size.w)).min(other.loc.x.saturating_add(other.size.w)), @@ -1257,7 +1268,7 @@ impl Rectangle { match ret { None => Rectangle::default(), - Some((min_point, max_point)) => Rectangle::from_extemities(min_point, max_point), + Some((min_point, max_point)) => Rectangle::from_extremities(min_point, max_point), } } diff --git a/src/utils/hook.rs b/src/utils/hook.rs index b4b6bca..72ac9bc 100644 --- a/src/utils/hook.rs +++ b/src/utils/hook.rs @@ -3,8 +3,8 @@ use std::sync::Arc; crate::utils::ids::id_gen!(hooks_id); /// Unique hook identifier used to unregister commit/descruction hooks -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct HookId(usize); +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct HookId(Arc); pub(crate) struct Hook { pub id: HookId, @@ -22,7 +22,7 @@ impl std::fmt::Debug for Hook { impl Clone for Hook { fn clone(&self) -> Self { Self { - id: self.id, + id: self.id.clone(), cb: self.cb.clone(), } } @@ -31,14 +31,23 @@ impl Clone for Hook { impl Hook { pub fn new(cb: Arc) -> Self { Self { - id: HookId(hooks_id::next()), + id: HookId(Arc::new(InnerId::new())), cb, } } } -impl Drop for Hook { +#[derive(Debug, Eq, PartialEq)] +struct InnerId(usize); + +impl InnerId { + fn new() -> Self { + Self(hooks_id::next()) + } +} + +impl Drop for InnerId { fn drop(&mut self) { - hooks_id::remove(self.id.0); + hooks_id::remove(self.0); } } diff --git a/src/wayland/compositor/tree.rs b/src/wayland/compositor/tree.rs index 4edae9d..2be3dc5 100644 --- a/src/wayland/compositor/tree.rs +++ b/src/wayland/compositor/tree.rs @@ -211,7 +211,7 @@ impl PrivateSurfaceData { hook: impl Fn(&mut dyn Any, &DisplayHandle, &WlSurface) + Send + Sync + 'static, ) -> HookId { let hook: Hook = Hook::new(Arc::new(hook)); - let id = hook.id; + let id = hook.id.clone(); Self::lock_user_data(surface).pre_commit_hooks.push(hook); id } @@ -221,7 +221,7 @@ impl PrivateSurfaceData { hook: impl Fn(&mut dyn Any, &DisplayHandle, &WlSurface) + Send + Sync + 'static, ) -> HookId { let hook: Hook = Hook::new(Arc::new(hook)); - let id = hook.id; + let id = hook.id.clone(); Self::lock_user_data(surface).post_commit_hooks.push(hook); id } @@ -231,7 +231,7 @@ impl PrivateSurfaceData { hook: impl Fn(&mut dyn Any, &WlSurface) + Send + Sync + 'static, ) -> HookId { let hook: Hook = Hook::new(Arc::new(hook)); - let id = hook.id; + let id = hook.id.clone(); Self::lock_user_data(surface).destruction_hooks.push(hook); id } diff --git a/src/wayland/drm_syncobj/mod.rs b/src/wayland/drm_syncobj/mod.rs index ff5e973..c27cffc 100644 --- a/src/wayland/drm_syncobj/mod.rs +++ b/src/wayland/drm_syncobj/mod.rs @@ -333,8 +333,8 @@ where match request { wp_linux_drm_syncobj_surface_v1::Request::Destroy => { if let Ok(surface) = data.surface.upgrade() { - compositor::remove_pre_commit_hook(&surface, data.commit_hook_id); - compositor::remove_destruction_hook(&surface, data.destruction_hook_id); + compositor::remove_pre_commit_hook(&surface, data.commit_hook_id.clone()); + compositor::remove_destruction_hook(&surface, data.destruction_hook_id.clone()); with_states(&surface, |states| { *states .data_map diff --git a/src/wayland/idle_notify/mod.rs b/src/wayland/idle_notify/mod.rs index f64e5fd..8f6febe 100644 --- a/src/wayland/idle_notify/mod.rs +++ b/src/wayland/idle_notify/mod.rs @@ -74,6 +74,9 @@ pub struct IdleNotificationUserData { is_idle: AtomicBool, timeout: Duration, timer_token: Mutex>, + + /// If listener was created with `get_input_idle_notification` + ignore_inhibitor: bool, } impl IdleNotificationUserData { @@ -113,7 +116,7 @@ impl IdleNotifierState { D: IdleNotifierHandler, D: 'static, { - let global = display.create_global::(1, ()); + let global = display.create_global::(2, ()); Self { global, notifications: HashMap::new(), @@ -131,9 +134,13 @@ impl IdleNotifierState { self.is_inhibited = is_inhibited; for notification in self.notifications() { - if is_inhibited { - let data = notification.data::().unwrap(); + let data = notification.data::().unwrap(); + + if data.ignore_inhibitor { + continue; + } + if is_inhibited { if data.is_idle() { notification.resumed(); data.set_idle(false); @@ -189,7 +196,7 @@ impl IdleNotifierState { self.loop_handle.remove(token); } - if self.is_inhibited { + if !data.ignore_inhibitor && self.is_inhibited { return; } @@ -200,7 +207,10 @@ impl IdleNotifierState { move |_, _, state| { let data = idle_notification.data::().unwrap(); - if !state.idle_notifier_state().is_inhibited && !data.is_idle() { + let is_inhibited = !data.ignore_inhibitor && state.idle_notifier_state().is_inhibited; + let is_idle_already = data.is_idle(); + + if !is_inhibited && !is_idle_already { idle_notification.idled(); data.set_idle(true); } @@ -275,6 +285,32 @@ where is_idle: AtomicBool::new(false), timeout, timer_token: Mutex::new(None), + ignore_inhibitor: false, + }, + ); + + idle_notifier_state.reinsert_timer(&idle_notification); + + state + .idle_notifier_state() + .notifications + .entry(seat) + .or_default() + .push(idle_notification); + } + ext_idle_notifier_v1::Request::GetInputIdleNotification { id, timeout, seat } => { + let timeout = Duration::from_millis(timeout as u64); + + let idle_notifier_state = state.idle_notifier_state(); + + let idle_notification = data_init.init( + id, + IdleNotificationUserData { + seat: seat.clone(), + is_idle: AtomicBool::new(false), + timeout, + timer_token: Mutex::new(None), + ignore_inhibitor: true, }, ); diff --git a/src/wayland/input_method/input_method_handle.rs b/src/wayland/input_method/input_method_handle.rs index b33949c..9644e85 100644 --- a/src/wayland/input_method/input_method_handle.rs +++ b/src/wayland/input_method/input_method_handle.rs @@ -74,9 +74,9 @@ impl InputMethodHandle { } /// Callback function to access the input method object - pub(crate) fn with_instance(&self, mut f: F) + pub(crate) fn with_instance(&self, f: F) where - F: FnMut(&mut Instance), + F: FnOnce(&mut Instance), { let mut inner = self.inner.lock().unwrap(); if let Some(instance) = inner.instance.as_mut() { @@ -103,20 +103,17 @@ impl InputMethodHandle { pub(crate) fn set_text_input_rectangle( &self, state: &mut D, - x: i32, - y: i32, - width: i32, - height: i32, + rect: Rectangle, ) { let mut inner = self.inner.lock().unwrap(); - inner.popup_handle.rectangle = Rectangle::new((x, y).into(), (width, height).into()); + inner.popup_handle.rectangle = rect; let mut popup_surface = match inner.popup_handle.surface.clone() { Some(popup_surface) => popup_surface, None => return, }; - popup_surface.set_text_input_rectangle(x, y, width, height); + popup_surface.set_text_input_rectangle(rect.loc.x, rect.loc.y, rect.size.w, rect.size.h); if let Some(instance) = &inner.instance { let data = instance.object.data::>().unwrap(); @@ -149,14 +146,12 @@ impl InputMethodHandle { /// Deactivate the active input method. /// - /// The `done` is required in cases where the change in state is initiated not by text-input. - pub(crate) fn deactivate_input_method(&self, state: &mut D, done: bool) { + /// The `done` is always send when deactivating IME. + pub(crate) fn deactivate_input_method(&self, state: &mut D) { self.with_input_method(|im| { if let Some(instance) = im.instance.as_mut() { instance.object.deactivate(); - if done { - instance.done(); - } + instance.done(); if let Some(popup) = im.popup_handle.surface.as_mut() { let data = instance.object.data::>().unwrap(); if popup.get_parent().is_some() { @@ -211,7 +206,7 @@ where ) { match request { zwp_input_method_v2::Request::CommitString { text } => { - data.text_input_handle.with_focused_text_input(|ti, _surface| { + data.text_input_handle.with_active_text_input(|ti, _surface| { ti.commit_string(Some(text.clone())); }); } @@ -220,7 +215,7 @@ where cursor_begin, cursor_end, } => { - data.text_input_handle.with_focused_text_input(|ti, _surface| { + data.text_input_handle.with_active_text_input(|ti, _surface| { ti.preedit_string(Some(text.clone()), cursor_begin, cursor_end); }); } @@ -228,7 +223,7 @@ where before_length, after_length, } => { - data.text_input_handle.with_focused_text_input(|ti, _surface| { + data.text_input_handle.with_active_text_input(|ti, _surface| { ti.delete_surrounding_text(before_length, after_length); }); } @@ -332,8 +327,6 @@ where data: &InputMethodUserData, ) { data.handle.inner.lock().unwrap().instance = None; - data.text_input_handle.with_focused_text_input(|ti, surface| { - ti.leave(surface); - }); + data.text_input_handle.leave(); } } diff --git a/src/wayland/input_method/input_method_keyboard_grab.rs b/src/wayland/input_method/input_method_keyboard_grab.rs index 2bdedcf..24ac2a7 100644 --- a/src/wayland/input_method/input_method_keyboard_grab.rs +++ b/src/wayland/input_method/input_method_keyboard_grab.rs @@ -54,7 +54,7 @@ where let keyboard = inner.grab.as_ref().unwrap(); inner .text_input_handle - .focused_text_input_serial_or_default(serial.0, |serial| { + .active_text_input_serial_or_default(serial.0, |serial| { keyboard.key(serial, time, keycode.raw() - 8, key_state.into()); if let Some(serialized) = modifiers.map(|m| m.serialized) { keyboard.modifiers( diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index fd2d512..951c760 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -171,7 +171,7 @@ impl KeyboardTarget for WlSurface { let input_method = seat.input_method(); if input_method.has_instance() { - input_method.deactivate_input_method(state, true); + input_method.deactivate_input_method(state); } // NOTE: Always set focus regardless whether the client actually has the @@ -191,7 +191,7 @@ impl KeyboardTarget for WlSurface { let input_method = seat.input_method(); if input_method.has_instance() { - input_method.deactivate_input_method(state, true); + input_method.deactivate_input_method(state); text_input.leave(); } diff --git a/src/wayland/selection/device.rs b/src/wayland/selection/device.rs index 4027d9e..27c019d 100644 --- a/src/wayland/selection/device.rs +++ b/src/wayland/selection/device.rs @@ -1,6 +1,4 @@ -use std::any::Any; -use std::any::TypeId; - +use wayland_protocols::ext::data_control::v1::server::ext_data_control_device_v1::ExtDataControlDeviceV1; use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1 as PrimaryDevice; use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_device_v1::ZwlrDataControlDeviceV1; use wayland_server::backend::ObjectId; @@ -9,16 +7,26 @@ use wayland_server::protocol::wl_seat::WlSeat; use wayland_server::Resource; use super::data_device::DataDeviceUserData; +use super::ext_data_control::ExtDataControlDeviceUserData; use super::offer::SelectionOffer; use super::primary_selection::PrimaryDeviceUserData; use super::private::selection_dispatch; use super::wlr_data_control::DataControlDeviceUserData; +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub(crate) enum DataDeviceKind { + Core, + Primary, + WlrDataControl, + ExtDataControl, +} + #[derive(Debug, Clone, PartialEq, Eq)] -pub enum SelectionDevice { +pub(crate) enum SelectionDevice { DataDevice(WlDataDevice), Primary(PrimaryDevice), - DataControl(ZwlrDataControlDeviceV1), + WlrDataControl(ZwlrDataControlDeviceV1), + ExtDataControl(ExtDataControlDeviceV1), } impl SelectionDevice { @@ -34,9 +42,13 @@ impl SelectionDevice { selection_dispatch!(self; Self(device) => device.id()) } - /// Get the [`TypeId`] of the underlying data device provider. - pub fn inner_type_id(&self) -> TypeId { - selection_dispatch!(self; Self(device) => device.type_id()) + pub fn device_kind(&self) -> DataDeviceKind { + match self { + Self::DataDevice(_) => DataDeviceKind::Core, + Self::Primary(_) => DataDeviceKind::Primary, + Self::WlrDataControl(_) => DataDeviceKind::WlrDataControl, + Self::ExtDataControl(_) => DataDeviceKind::ExtDataControl, + } } /// [`WlSeat`] associated with this device. @@ -50,10 +62,14 @@ impl SelectionDevice { let data: &PrimaryDeviceUserData = device.data().unwrap(); data.wl_seat.clone() } - SelectionDevice::DataControl(device) => { + SelectionDevice::WlrDataControl(device) => { let data: &DataControlDeviceUserData = device.data().unwrap(); data.wl_seat.clone() } + SelectionDevice::ExtDataControl(device) => { + let data: &ExtDataControlDeviceUserData = device.data().unwrap(); + data.wl_seat.clone() + } } } @@ -63,9 +79,8 @@ impl SelectionDevice { (Self::DataDevice(device), SelectionOffer::DataDevice(offer)) => { device.selection(Some(offer)); } - (Self::DataControl(device), SelectionOffer::DataControl(offer)) => { - device.selection(Some(offer)); - } + (Self::WlrDataControl(obj), SelectionOffer::WlrDataControl(offer)) => obj.selection(Some(offer)), + (Self::ExtDataControl(obj), SelectionOffer::ExtDataControl(offer)) => obj.selection(Some(offer)), _ => unreachable!("non-supported configuration for setting clipboard selection."), } } @@ -73,7 +88,8 @@ impl SelectionDevice { pub fn unset_selection(&self) { match self { Self::DataDevice(device) => device.selection(None), - Self::DataControl(device) => device.selection(None), + Self::WlrDataControl(device) => device.selection(None), + Self::ExtDataControl(device) => device.selection(None), Self::Primary(_) => unreachable!("primary clipboard has no clipboard selection"), } } @@ -83,8 +99,11 @@ impl SelectionDevice { (Self::Primary(device), SelectionOffer::Primary(offer)) => { device.selection(Some(offer)); } - (Self::DataControl(device), SelectionOffer::DataControl(offer)) => { - device.primary_selection(Some(offer)); + (Self::WlrDataControl(obj), SelectionOffer::WlrDataControl(offer)) => { + obj.primary_selection(Some(offer)) + } + (Self::ExtDataControl(obj), SelectionOffer::ExtDataControl(offer)) => { + obj.primary_selection(Some(offer)) } _ => unreachable!("non-supported configuration for setting clipboard selection."), } @@ -93,7 +112,8 @@ impl SelectionDevice { pub fn unset_primary_selection(&self) { match self { Self::Primary(device) => device.selection(None), - Self::DataControl(device) => device.primary_selection(None), + Self::WlrDataControl(device) => device.primary_selection(None), + Self::ExtDataControl(device) => device.primary_selection(None), Self::DataDevice(_) => unreachable!("data control has primary selection"), } } diff --git a/src/wayland/selection/ext_data_control/device.rs b/src/wayland/selection/ext_data_control/device.rs new file mode 100644 index 0000000..64f82c6 --- /dev/null +++ b/src/wayland/selection/ext_data_control/device.rs @@ -0,0 +1,100 @@ +use std::cell::RefCell; + +use wayland_protocols::ext::data_control::v1::server::ext_data_control_device_v1::{ + self, ExtDataControlDeviceV1, +}; +use wayland_server::protocol::wl_seat::WlSeat; +use wayland_server::{Client, Dispatch, DisplayHandle}; + +use crate::input::Seat; +use crate::wayland::selection::device::SelectionDevice; +use crate::wayland::selection::offer::OfferReplySource; +use crate::wayland::selection::seat_data::SeatData; +use crate::wayland::selection::source::SelectionSourceProvider; +use crate::wayland::selection::{SelectionSource, SelectionTarget}; + +use super::{DataControlHandler, DataControlState}; + +#[doc(hidden)] +#[derive(Debug)] +pub struct ExtDataControlDeviceUserData { + pub(crate) primary: bool, + pub(crate) wl_seat: WlSeat, +} + +impl Dispatch for DataControlState +where + D: Dispatch, + D: DataControlHandler, + D: 'static, +{ + fn request( + handler: &mut D, + _client: &Client, + resource: &ExtDataControlDeviceV1, + request: ::Request, + data: &ExtDataControlDeviceUserData, + dh: &DisplayHandle, + _: &mut wayland_server::DataInit<'_, D>, + ) { + let seat = match Seat::::from_resource(&data.wl_seat) { + Some(seat) => seat, + None => return, + }; + + match request { + ext_data_control_device_v1::Request::SetSelection { source, .. } => { + seat.user_data() + .insert_if_missing(|| RefCell::new(SeatData::::new())); + + let source = source.map(SelectionSourceProvider::ExtDataControl); + + handler.new_selection( + SelectionTarget::Clipboard, + source.clone().map(|provider| SelectionSource { provider }), + seat.clone(), + ); + + seat.user_data() + .get::>>() + .unwrap() + .borrow_mut() + .set_clipboard_selection::(dh, source.map(OfferReplySource::Client)); + } + ext_data_control_device_v1::Request::SetPrimarySelection { source, .. } => { + // When the primary selection is disabled, we should simply ignore the requests. + if !data.primary { + return; + } + + seat.user_data() + .insert_if_missing(|| RefCell::new(SeatData::::new())); + + let source = source.map(SelectionSourceProvider::ExtDataControl); + + handler.new_selection( + SelectionTarget::Primary, + source.clone().map(|provider| SelectionSource { provider }), + seat.clone(), + ); + + seat.user_data() + .get::>>() + .unwrap() + .borrow_mut() + .set_primary_selection::(dh, source.map(OfferReplySource::Client)); + } + ext_data_control_device_v1::Request::Destroy => seat + .user_data() + .get::>>() + .unwrap() + .borrow_mut() + .retain_devices(|ndd| match ndd { + SelectionDevice::ExtDataControl(ndd) => ndd != resource, + _ => true, + }), + + _ => unreachable!(), + } + } +} diff --git a/src/wayland/selection/ext_data_control/mod.rs b/src/wayland/selection/ext_data_control/mod.rs new file mode 100644 index 0000000..f049f5d --- /dev/null +++ b/src/wayland/selection/ext_data_control/mod.rs @@ -0,0 +1,255 @@ +//! Automatic handling of the `ext_data_control` protocol +//! +//! ## Initialization +//! +//! To initialize this implementation, create [`DataControlState`], store it in your `State` +//! struct, and implement the required trait, as shown in the example: +//! +//! ``` +//! # extern crate wayland_server; +//! # #[macro_use] extern crate smithay; +//! use smithay::wayland::selection::SelectionHandler; +//! use smithay::wayland::selection::ext_data_control::{DataControlState, DataControlHandler}; +//! # use smithay::input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus}; +//! # use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +//! +//! # struct State { data_control_state: DataControlState } +//! # let mut display = wayland_server::Display::::new().unwrap(); +//! // Create the data_control state +//! let data_control_state = DataControlState::new::( +//! &display.handle(), None, |_| true +//! ); +//! +//! // insert the DataControlState into your state +//! // .. +//! +//! // implement the necessary traits +//! # impl SeatHandler for State { +//! # type KeyboardFocus = WlSurface; +//! # type PointerFocus = WlSurface; +//! # type TouchFocus = WlSurface; +//! # fn seat_state(&mut self) -> &mut SeatState { unimplemented!() } +//! # fn focus_changed(&mut self, seat: &Seat, focused: Option<&WlSurface>) { unimplemented!() } +//! # fn cursor_image(&mut self, seat: &Seat, image: CursorImageStatus) { unimplemented!() } +//! # } +//! impl SelectionHandler for State { +//! type SelectionUserData = (); +//! } +//! impl DataControlHandler for State { +//! fn data_control_state(&self) -> &DataControlState { &self.data_control_state } +//! // ... override default implementations here to customize handling ... +//! } +//! delegate_ext_data_control!(State); +//! +//! // You're now ready to go! +//! ``` +//! +//! Be aware that data control clients rely on other selection providers to be implemneted, like +//! wl_data_device or zwp_primary_selection. + +use wayland_protocols::ext::data_control::v1::server::ext_data_control_manager_v1::ExtDataControlManagerV1; +use wayland_server::backend::GlobalId; +use wayland_server::{Client, DisplayHandle, GlobalDispatch}; + +mod device; +mod source; + +pub use device::ExtDataControlDeviceUserData; +pub use source::ExtDataControlSourceUserData; + +use super::primary_selection::PrimarySelectionState; +use super::SelectionHandler; + +/// Access the data control state. +pub trait DataControlHandler: Sized + SelectionHandler { + /// [`DataControlState`] getter. + fn data_control_state(&self) -> &DataControlState; +} + +/// State of the data control. +#[derive(Debug)] +pub struct DataControlState { + manager_global: GlobalId, +} + +impl DataControlState { + /// Register new [ExtDataControlManagerV1] global. + /// + /// Passing `primary_selection` will enable support for primary selection as well. + pub fn new( + display: &DisplayHandle, + primary_selection: Option<&PrimarySelectionState>, + filter: F, + ) -> Self + where + D: GlobalDispatch + 'static, + F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static, + { + let data = ExtDataControlManagerGlobalData { + primary: primary_selection.is_some(), + filter: Box::new(filter), + }; + let manager_global = display.create_global::(1, data); + Self { manager_global } + } + + /// [ExtDataControlManagerV1] GlobalId getter. + pub fn global(&self) -> GlobalId { + self.manager_global.clone() + } +} + +#[allow(missing_debug_implementations)] +#[doc(hidden)] +pub struct ExtDataControlManagerGlobalData { + /// Whether to allow primary selection. + primary: bool, + + /// Filter whether the clients can view global. + filter: Box Fn(&'c Client) -> bool + Send + Sync>, +} + +#[doc(hidden)] +#[derive(Debug, Clone, Copy)] +pub struct ExtDataControlManagerUserData { + /// Whether to allow primary selection. + primary: bool, +} + +mod handlers { + use std::cell::RefCell; + + use tracing::error; + use wayland_protocols::ext::data_control::v1::server::{ + ext_data_control_device_v1::ExtDataControlDeviceV1, + ext_data_control_manager_v1::{self, ExtDataControlManagerV1}, + ext_data_control_source_v1::ExtDataControlSourceV1, + }; + use wayland_server::{Client, Dispatch, DisplayHandle, GlobalDispatch}; + + use crate::input::Seat; + use crate::wayland::selection::device::SelectionDevice; + use crate::wayland::selection::seat_data::SeatData; + use crate::wayland::selection::SelectionTarget; + + use super::DataControlHandler; + use super::DataControlState; + use super::ExtDataControlDeviceUserData; + use super::ExtDataControlManagerGlobalData; + use super::ExtDataControlManagerUserData; + use super::ExtDataControlSourceUserData; + + impl GlobalDispatch for DataControlState + where + D: GlobalDispatch, + D: Dispatch, + D: Dispatch, + D: Dispatch, + D: DataControlHandler, + D: 'static, + { + fn bind( + _state: &mut D, + _handle: &DisplayHandle, + _client: &wayland_server::Client, + resource: wayland_server::New, + global_data: &ExtDataControlManagerGlobalData, + data_init: &mut wayland_server::DataInit<'_, D>, + ) { + data_init.init( + resource, + ExtDataControlManagerUserData { + primary: global_data.primary, + }, + ); + } + + fn can_view(client: Client, global_data: &ExtDataControlManagerGlobalData) -> bool { + (global_data.filter)(&client) + } + } + + impl Dispatch for DataControlState + where + D: Dispatch, + D: Dispatch, + D: Dispatch, + D: DataControlHandler, + D: 'static, + { + fn request( + _handler: &mut D, + client: &wayland_server::Client, + _resource: &ExtDataControlManagerV1, + request: ::Request, + data: &ExtDataControlManagerUserData, + dh: &DisplayHandle, + data_init: &mut wayland_server::DataInit<'_, D>, + ) { + match request { + ext_data_control_manager_v1::Request::CreateDataSource { id } => { + data_init.init(id, ExtDataControlSourceUserData::new()); + } + ext_data_control_manager_v1::Request::GetDataDevice { id, seat: wl_seat } => { + match Seat::::from_resource(&wl_seat) { + Some(seat) => { + seat.user_data() + .insert_if_missing(|| RefCell::new(SeatData::::new())); + + let device = SelectionDevice::ExtDataControl(data_init.init( + id, + ExtDataControlDeviceUserData { + wl_seat, + primary: data.primary, + }, + )); + + let mut seat_data = seat + .user_data() + .get::>>() + .unwrap() + .borrow_mut(); + + seat_data.add_device(device.clone()); + + // NOTE: broadcast selection only to the newly created device. + let device = Some(&device); + seat_data.send_selection::(dh, SelectionTarget::Clipboard, device, true); + if data.primary { + seat_data.send_selection::(dh, SelectionTarget::Primary, device, true); + } + } + None => { + error!( + data_control_device = ?id, + client = ?client, + "Unmanaged seat given to a primary selection device." + ); + } + } + } + ext_data_control_manager_v1::Request::Destroy => (), + _ => unreachable!(), + } + } + } +} + +/// Macro to delegate implementation of the ext_data_control protocol +#[macro_export] +macro_rules! delegate_ext_data_control { + ($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => { + $crate::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::ext::data_control::v1::server::ext_data_control_manager_v1::ExtDataControlManagerV1: $crate::wayland::selection::ext_data_control::ExtDataControlManagerGlobalData + ] => $crate::wayland::selection::ext_data_control::DataControlState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::ext::data_control::v1::server::ext_data_control_manager_v1::ExtDataControlManagerV1: $crate::wayland::selection::ext_data_control::ExtDataControlManagerUserData + ] => $crate::wayland::selection::ext_data_control::DataControlState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::ext::data_control::v1::server::ext_data_control_device_v1::ExtDataControlDeviceV1: $crate::wayland::selection::ext_data_control::ExtDataControlDeviceUserData + ] => $crate::wayland::selection::ext_data_control::DataControlState); + $crate::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [ + $crate::reexports::wayland_protocols::ext::data_control::v1::server::ext_data_control_source_v1::ExtDataControlSourceV1: $crate::wayland::selection::ext_data_control::ExtDataControlSourceUserData + ] => $crate::wayland::selection::ext_data_control::DataControlState); + }; +} diff --git a/src/wayland/selection/ext_data_control/source.rs b/src/wayland/selection/ext_data_control/source.rs new file mode 100644 index 0000000..e254d99 --- /dev/null +++ b/src/wayland/selection/ext_data_control/source.rs @@ -0,0 +1,76 @@ +use std::sync::Mutex; + +use wayland_server::backend::ClientId; +use wayland_server::{Dispatch, DisplayHandle, Resource}; + +use crate::utils::alive_tracker::AliveTracker; +use crate::utils::IsAlive; + +use wayland_protocols::ext::data_control::v1::server::ext_data_control_source_v1::{ + self, ExtDataControlSourceV1, +}; + +use super::{DataControlHandler, DataControlState}; + +#[doc(hidden)] +#[derive(Default, Debug)] +pub struct ExtDataControlSourceUserData { + pub(crate) inner: Mutex, + alive_tracker: AliveTracker, +} + +impl ExtDataControlSourceUserData { + pub(crate) fn new() -> Self { + Self::default() + } +} + +/// The metadata describing a data source +#[derive(Debug, Default, Clone)] +pub struct SourceMetadata { + /// The MIME types supported by this source + pub mime_types: Vec, +} + +impl Dispatch for DataControlState +where + D: Dispatch, + D: DataControlHandler, + D: 'static, +{ + fn request( + _state: &mut D, + _client: &wayland_server::Client, + _resource: &ExtDataControlSourceV1, + request: ::Request, + data: &ExtDataControlSourceUserData, + _dhandle: &DisplayHandle, + _data_init: &mut wayland_server::DataInit<'_, D>, + ) { + match request { + ext_data_control_source_v1::Request::Offer { mime_type } => { + let mut data = data.inner.lock().unwrap(); + data.mime_types.push(mime_type); + } + ext_data_control_source_v1::Request::Destroy => (), + _ => unreachable!(), + } + } + + fn destroyed( + _state: &mut D, + _client: ClientId, + _resource: &ExtDataControlSourceV1, + data: &ExtDataControlSourceUserData, + ) { + data.alive_tracker.destroy_notify(); + } +} + +impl IsAlive for ExtDataControlSourceV1 { + #[inline] + fn alive(&self) -> bool { + let data: &ExtDataControlSourceUserData = self.data().unwrap(); + data.alive_tracker.alive() + } +} diff --git a/src/wayland/selection/mod.rs b/src/wayland/selection/mod.rs index 75cb897..7391a89 100644 --- a/src/wayland/selection/mod.rs +++ b/src/wayland/selection/mod.rs @@ -16,6 +16,7 @@ use std::os::unix::io::OwnedFd; use crate::input::{Seat, SeatHandler}; pub mod data_device; +pub mod ext_data_control; pub mod primary_selection; pub mod wlr_data_control; @@ -67,7 +68,8 @@ pub(crate) mod private { /// match self { /// Enum::DataDevice(foo) => foo.something(), /// Enum::Primary(foo) => foo.something(), - /// Enum::DataControl(foo) => foo.something(), + /// Enum::WlrDataControl(foo) => foo.something(), + /// Enum::ExtDataControl(foo) => foo.something(), /// } /// ``` /// @@ -77,7 +79,8 @@ pub(crate) mod private { /// match (self, other) { /// (Enum::DataDevice(foo), EnumNext::DataDevice(zoo)) => foo.something(zoo), /// (Enum::Primary(foo), EnumNext::Primary(zoo)) => foo.something(zoo), - /// (Enum::DataControl(foo), EnumNext::DataControl(zoo)) => foo.something(zoo), + /// (Enum::WlrDataControl(foo), EnumNext::WlrDataControl(zoo)) => foo.something(zoo), + /// (Enum::ExtDataControl(foo), EnumNext::ExtDataControl(zoo)) => foo.something(zoo), /// _ => unreachable!(), /// } /// ``` @@ -88,14 +91,16 @@ pub(crate) mod private { match $what { $enum::DataDevice($($c1)*) => $x, $enum::Primary($($c1)*) => $x, - $enum::DataControl($($c1)*) => $x, + $enum::WlrDataControl($($c1)*) => $x, + $enum::ExtDataControl($($c1)*) => $x, } }; ($what:ident$(, $what_next:ident)+; $enum:ident ( $($c1:tt)*) $(, $enum_next:ident ( $($c2:tt)* ) )+ => $x:expr) => { match ($what$(, $what_next)*) { ($enum::DataDevice($($c1)*)$(, $enum_next::DataDevice($($c2)*))*) => $x, ($enum::Primary($($c1)*)$(, $enum_next::Primary($($c2)*))*) => $x, - ($enum::DataControl($($c1)*)$(, $enum_next::DataControl($($c2)*))*) => $x, + ($enum::WlrDataControl($($c1)*)$(, $enum_next::WlrDataControl($($c2)*))*) => $x, + ($enum::ExtDataControl($($c1)*)$(, $enum_next::ExtDataControl($($c2)*))*) => $x, _ => unreachable!(), } }; diff --git a/src/wayland/selection/offer.rs b/src/wayland/selection/offer.rs index 16fc187..f072a56 100644 --- a/src/wayland/selection/offer.rs +++ b/src/wayland/selection/offer.rs @@ -1,19 +1,19 @@ -use std::any::TypeId; use std::os::unix::io::OwnedFd; use std::sync::Arc; use tracing::debug; -use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1 as PrimaryDevice; +use wayland_protocols::ext::data_control::v1::server::ext_data_control_offer_v1::{ + self, ExtDataControlOfferV1, +}; use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_offer_v1::{ self, ZwpPrimarySelectionOfferV1 as PrimaryOffer, }; use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_offer_v1::{ - self, ZwlrDataControlOfferV1 as DataControlOffer, + self, ZwlrDataControlOfferV1, }; use wayland_server::backend::protocol::Message; use wayland_server::backend::ObjectId; use wayland_server::backend::{ClientId, Handle, ObjectData}; -use wayland_server::protocol::wl_data_device::WlDataDevice; use wayland_server::protocol::wl_data_offer; use wayland_server::protocol::wl_seat::WlSeat; use wayland_server::DisplayHandle; @@ -24,7 +24,7 @@ use zwp_primary_selection_offer_v1::Request as PrimaryRequest; use crate::input::Seat; -use super::device::SelectionDevice; +use super::device::{DataDeviceKind, SelectionDevice}; use super::private::selection_dispatch; use super::source::{CompositorSelectionProvider, SelectionSourceProvider}; use super::SelectionHandler; @@ -57,10 +57,11 @@ impl OfferReplySource { /// Offer representing various selection offers. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum SelectionOffer { +pub(crate) enum SelectionOffer { DataDevice(WlDataOffer), Primary(PrimaryOffer), - DataControl(DataControlOffer), + WlrDataControl(ZwlrDataControlOfferV1), + ExtDataControl(ExtDataControlOfferV1), } impl SelectionOffer { @@ -76,32 +77,34 @@ impl SelectionOffer { // NOTE: the types are tied to the `SelectionDevice`, so every // RTTI like checking is safe and reliable. - let type_id = device.inner_type_id(); + let device_kind = device.device_kind(); let data = Arc::new(OfferReplyData { - type_id, + device_kind, seat: device.seat(), source: data, }); let backend = dh.backend_handle(); - let interface = if type_id == TypeId::of::() { - WlDataOffer::interface() - } else if type_id == TypeId::of::() { - PrimaryOffer::interface() - } else { - DataControlOffer::interface() + let interface = match device_kind { + DataDeviceKind::Core => WlDataOffer::interface(), + DataDeviceKind::Primary => PrimaryOffer::interface(), + DataDeviceKind::WlrDataControl => ZwlrDataControlOfferV1::interface(), + DataDeviceKind::ExtDataControl => ExtDataControlOfferV1::interface(), }; let offer = backend .create_object::(client_id, interface, device.version(), data) .unwrap(); - if type_id == TypeId::of::() { - Self::DataDevice(WlDataOffer::from_id(dh, offer).unwrap()) - } else if type_id == TypeId::of::() { - Self::Primary(PrimaryOffer::from_id(dh, offer).unwrap()) - } else { - Self::DataControl(DataControlOffer::from_id(dh, offer).unwrap()) + match device_kind { + DataDeviceKind::Core => Self::DataDevice(WlDataOffer::from_id(dh, offer).unwrap()), + DataDeviceKind::Primary => Self::Primary(PrimaryOffer::from_id(dh, offer).unwrap()), + DataDeviceKind::WlrDataControl => { + Self::WlrDataControl(ZwlrDataControlOfferV1::from_id(dh, offer).unwrap()) + } + DataDeviceKind::ExtDataControl => { + Self::ExtDataControl(ExtDataControlOfferV1::from_id(dh, offer).unwrap()) + } } } @@ -111,7 +114,7 @@ impl SelectionOffer { } struct OfferReplyData { - type_id: TypeId, + device_kind: DataDeviceKind, source: OfferReplySource, seat: WlSeat, } @@ -128,33 +131,47 @@ where msg: Message, ) -> Option>> { let dh = DisplayHandle::from(dh.clone()); - let type_id = self.type_id; // NOTE: we can't parse message more than once, since it expects the `OwnedFd` which // we can't clone. To achieve that, we use RTTI passed along the selection data, to // make the parsing work only once. - let (mime_type, fd, object_name) = if type_id == TypeId::of::() { - if let Ok((_resource, DataOfferRequest::Receive { mime_type, fd })) = - WlDataOffer::parse_request(&dh, msg) - { - (mime_type, fd, "wl_data_offer") - } else { - return None; + let (mime_type, fd, object_name) = match self.device_kind { + DataDeviceKind::Core => { + if let Ok((_resource, DataOfferRequest::Receive { mime_type, fd })) = + WlDataOffer::parse_request(&dh, msg) + { + (mime_type, fd, "wl_data_offer") + } else { + return None; + } } - } else if type_id == TypeId::of::() { - if let Ok((_resource, PrimaryRequest::Receive { mime_type, fd })) = - PrimaryOffer::parse_request(&dh, msg) - { - (mime_type, fd, "primary_selection_offer") - } else { - return None; + DataDeviceKind::Primary => { + if let Ok((_resource, PrimaryRequest::Receive { mime_type, fd })) = + PrimaryOffer::parse_request(&dh, msg) + { + (mime_type, fd, "primary_selection_offer") + } else { + return None; + } + } + DataDeviceKind::WlrDataControl => { + if let Ok((_resource, DataControlRequest::Receive { mime_type, fd })) = + ZwlrDataControlOfferV1::parse_request(&dh, msg) + { + (mime_type, fd, "wlr_data_control_offer") + } else { + return None; + } + } + DataDeviceKind::ExtDataControl => { + if let Ok((_resource, ext_data_control_offer_v1::Request::Receive { mime_type, fd })) = + ExtDataControlOfferV1::parse_request(&dh, msg) + { + (mime_type, fd, "ext_data_control_offer") + } else { + return None; + } } - } else if let Ok((_resource, DataControlRequest::Receive { mime_type, fd })) = - DataControlOffer::parse_request(&dh, msg) - { - (mime_type, fd, "data_control_offer") - } else { - return None; }; if !self.source.contains_mime_type(&mime_type) { diff --git a/src/wayland/selection/seat_data.rs b/src/wayland/selection/seat_data.rs index 79205c7..1b13815 100644 --- a/src/wayland/selection/seat_data.rs +++ b/src/wayland/selection/seat_data.rs @@ -10,8 +10,8 @@ use super::{SelectionHandler, SelectionTarget}; /// Seat data used to handle regular selection operations. /// -/// The data is shared accross primary, data device, and data control selections. -pub struct SeatData { +/// The data is shared across primary, data device, and data control selections. +pub(crate) struct SeatData { known_devices: Vec, clipboard_selection: Option>, clipboard_selection_focus: Option, @@ -20,7 +20,7 @@ pub struct SeatData { } impl SeatData { - /// Create a new [`SeatData`] with emply selections and without focusing any client. + /// Create a new [`SeatData`] with empty selections and without focusing any client. pub fn new() -> Self { Default::default() } @@ -166,21 +166,24 @@ impl SeatData { // later on. SelectionDevice::DataDevice(_) => ty == SelectionTarget::Clipboard, SelectionDevice::Primary(_) => ty == SelectionTarget::Primary, - SelectionDevice::DataControl(data_control) => { + SelectionDevice::WlrDataControl(data_control) => { // Primary selection is available for data control only since v2. update_data_control && (data_control.version() >= EVT_PRIMARY_SELECTION_SINCE || ty != SelectionTarget::Primary) } + SelectionDevice::ExtDataControl(_) => update_data_control, }) { // Data control doesn't require focus and should always get selection updates, unless // it was requested not to update them. - if !matches!(device, SelectionDevice::DataControl(_)) - && dh - .get_client(device.id()) - .map(|c| Some(&c) != client) - .unwrap_or(true) + if !matches!( + device, + SelectionDevice::WlrDataControl(_) | SelectionDevice::ExtDataControl(_) + ) && dh + .get_client(device.id()) + .map(|c| Some(&c) != client) + .unwrap_or(true) { continue; } @@ -198,7 +201,10 @@ impl SeatData { // DataControl devices is the client itself, however other devices use // the currently focused one as a client. let client_id = match device { - SelectionDevice::DataControl(device) => { + SelectionDevice::WlrDataControl(device) => { + dh.get_client(device.id()).ok().map(|c| c.id()) + } + SelectionDevice::ExtDataControl(device) => { dh.get_client(device.id()).ok().map(|c| c.id()) } _ => client.map(|c| c.id()), diff --git a/src/wayland/selection/source.rs b/src/wayland/selection/source.rs index e02c427..345e1ff 100644 --- a/src/wayland/selection/source.rs +++ b/src/wayland/selection/source.rs @@ -1,13 +1,15 @@ use std::os::unix::io::{AsFd, OwnedFd}; +use wayland_protocols::ext::data_control::v1::server::ext_data_control_source_v1::ExtDataControlSourceV1; use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1 as PrimarySource; -use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_source_v1::ZwlrDataControlSourceV1 as DataControlSource; +use wayland_protocols_wlr::data_control::v1::server::zwlr_data_control_source_v1::ZwlrDataControlSourceV1; use wayland_server::{protocol::wl_data_source::WlDataSource, Resource}; use crate::utils::IsAlive; use crate::wayland::selection::primary_selection::PrimarySourceUserData; use super::data_device::DataSourceUserData; +use super::ext_data_control::ExtDataControlSourceUserData; use super::private::selection_dispatch; use super::wlr_data_control::DataControlSourceUserData; use super::SelectionTarget; @@ -34,7 +36,9 @@ pub enum SelectionSourceProvider { /// The primary selection was used as a source. Primary(PrimarySource), /// The data control selection was used as source. - DataControl(DataControlSource), + WlrDataControl(ZwlrDataControlSourceV1), + /// The data control selection was used as source. + ExtDataControl(ExtDataControlSourceV1), } impl SelectionSourceProvider { @@ -59,10 +63,14 @@ impl SelectionSourceProvider { let data: &PrimarySourceUserData = source.data().unwrap(); data.inner.lock().unwrap().mime_types.contains(mime_type) } - Self::DataControl(source) => { + Self::WlrDataControl(source) => { let data: &DataControlSourceUserData = source.data().unwrap(); data.inner.lock().unwrap().mime_types.contains(mime_type) } + Self::ExtDataControl(source) => { + let data: &ExtDataControlSourceUserData = source.data().unwrap(); + data.inner.lock().unwrap().mime_types.contains(mime_type) + } } } @@ -77,10 +85,14 @@ impl SelectionSourceProvider { let data: &PrimarySourceUserData = source.data().unwrap(); data.inner.lock().unwrap().mime_types.clone() } - Self::DataControl(source) => { + Self::WlrDataControl(source) => { let data: &DataControlSourceUserData = source.data().unwrap(); data.inner.lock().unwrap().mime_types.clone() } + Self::ExtDataControl(source) => { + let data: &ExtDataControlSourceUserData = source.data().unwrap(); + data.inner.lock().unwrap().mime_types.clone() + } } } } diff --git a/src/wayland/selection/wlr_data_control/device.rs b/src/wayland/selection/wlr_data_control/device.rs index 0867817..2cb3914 100644 --- a/src/wayland/selection/wlr_data_control/device.rs +++ b/src/wayland/selection/wlr_data_control/device.rs @@ -48,7 +48,7 @@ where seat.user_data() .insert_if_missing(|| RefCell::new(SeatData::::new())); - let source = source.map(SelectionSourceProvider::DataControl); + let source = source.map(SelectionSourceProvider::WlrDataControl); handler.new_selection( SelectionTarget::Clipboard, @@ -71,7 +71,7 @@ where seat.user_data() .insert_if_missing(|| RefCell::new(SeatData::::new())); - let source = source.map(SelectionSourceProvider::DataControl); + let source = source.map(SelectionSourceProvider::WlrDataControl); handler.new_selection( SelectionTarget::Primary, @@ -91,7 +91,7 @@ where .unwrap() .borrow_mut() .retain_devices(|ndd| match ndd { - SelectionDevice::DataControl(ndd) => ndd != resource, + SelectionDevice::WlrDataControl(ndd) => ndd != resource, _ => true, }), diff --git a/src/wayland/selection/wlr_data_control/mod.rs b/src/wayland/selection/wlr_data_control/mod.rs index fa9f3d2..0c3b816 100644 --- a/src/wayland/selection/wlr_data_control/mod.rs +++ b/src/wayland/selection/wlr_data_control/mod.rs @@ -201,7 +201,7 @@ mod handlers { seat.user_data() .insert_if_missing(|| RefCell::new(SeatData::::new())); - let device = SelectionDevice::DataControl(data_init.init( + let device = SelectionDevice::WlrDataControl(data_init.init( id, DataControlDeviceUserData { wl_seat, diff --git a/src/wayland/shm/mod.rs b/src/wayland/shm/mod.rs index c40de81..184fb1f 100644 --- a/src/wayland/shm/mod.rs +++ b/src/wayland/shm/mod.rs @@ -486,7 +486,7 @@ impl ShmBufferUserData { hook: impl Fn(&mut dyn Any, &wl_buffer::WlBuffer) + Send + Sync + 'static, ) -> HookId { let hook: Hook = Hook::new(Arc::new(hook)); - let id = hook.id; + let id = hook.id.clone(); self.destruction_hooks.lock().unwrap().push(hook); id } diff --git a/src/wayland/text_input/text_input_handle.rs b/src/wayland/text_input/text_input_handle.rs index 9d81ec0..4b4ccd7 100644 --- a/src/wayland/text_input/text_input_handle.rs +++ b/src/wayland/text_input/text_input_handle.rs @@ -1,46 +1,64 @@ +use std::mem; use std::sync::{Arc, Mutex}; use tracing::debug; -use wayland_protocols::wp::text_input::zv3::server::zwp_text_input_v3::{self, ZwpTextInputV3}; +use wayland_protocols::wp::text_input::zv3::server::zwp_text_input_v3::{ + self, ChangeCause, ContentHint, ContentPurpose, ZwpTextInputV3, +}; use wayland_server::backend::{ClientId, ObjectId}; use wayland_server::{protocol::wl_surface::WlSurface, Dispatch, Resource}; use crate::input::SeatHandler; -use crate::utils::IsAlive; +use crate::utils::{Logical, Rectangle}; use crate::wayland::input_method::InputMethodHandle; use super::TextInputManagerState; -#[derive(Debug)] -struct Instance { - instance: ZwpTextInputV3, - serial: u32, -} - #[derive(Default, Debug)] pub(crate) struct TextInput { instances: Vec, focus: Option, - enabled_resource_id: Option, + active_text_input_id: Option, } impl TextInput { - fn with_focused_text_input(&mut self, mut f: F) + fn with_focused_client_all_text_inputs(&mut self, mut f: F) where F: FnMut(&ZwpTextInputV3, &WlSurface, u32), { - if let (Some(surface), Some(enabled_resource_id)) = (&self.focus, &self.enabled_resource_id) { - if !surface.alive() { - return; - } - - for ti in self.instances.iter_mut() { - let instance_id = ti.instance.id(); - if instance_id.same_client_as(&surface.id()) && instance_id.eq(enabled_resource_id) { - f(&ti.instance, surface, ti.serial); + if let Some(surface) = self.focus.as_ref().filter(|surface| surface.is_alive()) { + for text_input in self.instances.iter() { + let instance_id = text_input.instance.id(); + if instance_id.same_client_as(&surface.id()) { + f(&text_input.instance, surface, text_input.serial); break; } } + }; + } + + fn with_active_text_input(&mut self, mut f: F) + where + F: FnMut(&ZwpTextInputV3, &WlSurface, u32), + { + let active_id = match &self.active_text_input_id { + Some(active_text_input_id) => active_text_input_id, + None => return, + }; + + let surface = match self.focus.as_ref().filter(|surface| surface.is_alive()) { + Some(surface) => surface, + None => return, + }; + + let surface_id = surface.id(); + if let Some(text_input) = self + .instances + .iter() + .filter(|instance| instance.instance.id().same_client_as(&surface_id)) + .find(|instance| &instance.instance.id() == active_id) + { + f(&text_input.instance, surface, text_input.serial); } } } @@ -57,15 +75,20 @@ impl TextInputHandle { inner.instances.push(Instance { instance: instance.clone(), serial: 0, + pending_state: Default::default(), }); } fn increment_serial(&self, text_input: &ZwpTextInputV3) { - let mut inner = self.inner.lock().unwrap(); - for ti in inner.instances.iter_mut() { - if &ti.instance == text_input { - ti.serial += 1; - } + if let Some(instance) = self + .inner + .lock() + .unwrap() + .instances + .iter_mut() + .find(|instance| instance.instance == *text_input) + { + instance.serial += 1 } } @@ -81,18 +104,14 @@ impl TextInputHandle { self.inner.lock().unwrap().focus = surface; } - fn set_enabled_resource_id(&self, resource_id: Option) { - let mut inner = self.inner.lock().unwrap(); - if inner.enabled_resource_id.is_some() != resource_id.is_some() { - inner.enabled_resource_id = resource_id; - } - } - /// Send `leave` on the text-input instance for the currently focused /// surface. pub fn leave(&self) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|text_input, focus, _| { + // Leaving clears the active text input. + inner.active_text_input_id = None; + // NOTE: we implement it in a symmetrical way with `enter`. + inner.with_focused_client_all_text_inputs(|text_input, focus, _| { text_input.leave(focus); }); } @@ -101,7 +120,9 @@ impl TextInputHandle { /// surface. pub fn enter(&self) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|text_input, focus, _| { + // NOTE: protocol states that if we have multiple text inputs enabled, `enter` must + // be send for each of them. + inner.with_focused_client_all_text_inputs(|text_input, focus, _| { text_input.enter(focus); }); } @@ -110,7 +131,7 @@ impl TextInputHandle { /// the state should be discarded and wrong serial sent. pub fn done(&self, discard_state: bool) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|text_input, _, serial| { + inner.with_active_text_input(|text_input, _, serial| { if discard_state { debug!("discarding text-input state due to serial"); // Discarding is done by sending non-matching serial. @@ -127,20 +148,31 @@ impl TextInputHandle { F: FnMut(&ZwpTextInputV3, &WlSurface), { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|ti, surface, _| { + inner.with_focused_client_all_text_inputs(|ti, surface, _| { f(ti, surface); }); } - /// Call the callback with the serial of the focused text_input or with the passed + /// Access the active text-input instance for the currently focused surface. + pub fn with_active_text_input(&self, mut f: F) + where + F: FnMut(&ZwpTextInputV3, &WlSurface), + { + let mut inner = self.inner.lock().unwrap(); + inner.with_active_text_input(|ti, surface, _| { + f(ti, surface); + }); + } + + /// Call the callback with the serial of the active text_input or with the passed /// `default` one when empty. - pub(crate) fn focused_text_input_serial_or_default(&self, default: u32, mut callback: F) + pub(crate) fn active_text_input_serial_or_default(&self, default: u32, mut callback: F) where F: FnMut(u32), { let mut inner = self.inner.lock().unwrap(); let mut should_default = true; - inner.with_focused_text_input(|_, _, serial| { + inner.with_active_text_input(|_, _, serial| { should_default = false; callback(serial); }); @@ -191,41 +223,99 @@ where } }; + let mut guard = data.handle.inner.lock().unwrap(); + let pending_state = match guard.instances.iter_mut().find_map(|instance| { + if instance.instance == *resource { + Some(&mut instance.pending_state) + } else { + None + } + }) { + Some(pending_state) => pending_state, + None => { + debug!("got request for untracked text-input"); + return; + } + }; + match request { zwp_text_input_v3::Request::Enable => { - data.handle.set_enabled_resource_id(Some(resource.id())); - data.input_method_handle.activate_input_method(state, &focus); + pending_state.enable = Some(true); } zwp_text_input_v3::Request::Disable => { - data.handle.set_enabled_resource_id(None); - data.input_method_handle.deactivate_input_method(state, false); + pending_state.enable = Some(false); } zwp_text_input_v3::Request::SetSurroundingText { text, cursor, anchor } => { - data.input_method_handle.with_instance(|input_method| { - input_method - .object - .surrounding_text(text.clone(), cursor as u32, anchor as u32) - }); + pending_state.surrounding_text = Some((text, cursor as u32, anchor as u32)); } zwp_text_input_v3::Request::SetTextChangeCause { cause } => { - data.input_method_handle.with_instance(|input_method| { - input_method - .object - .text_change_cause(cause.into_result().unwrap()) - }); + pending_state.text_change_cause = Some(cause.into_result().unwrap()); } zwp_text_input_v3::Request::SetContentType { hint, purpose } => { - data.input_method_handle.with_instance(|input_method| { - input_method - .object - .content_type(hint.into_result().unwrap(), purpose.into_result().unwrap()); - }); + pending_state.content_type = + Some((hint.into_result().unwrap(), purpose.into_result().unwrap())); } zwp_text_input_v3::Request::SetCursorRectangle { x, y, width, height } => { - data.input_method_handle - .set_text_input_rectangle::(state, x, y, width, height); + pending_state.cursor_rectangle = Some(Rectangle::new((x, y).into(), (width, height).into())); } zwp_text_input_v3::Request::Commit => { + let mut new_state = mem::take(pending_state); + let _ = pending_state; + let active_text_input_id = &mut guard.active_text_input_id; + + if active_text_input_id.is_some() && *active_text_input_id != Some(resource.id()) { + debug!("discarding text_input request since we already have an active one"); + return; + } + + match new_state.enable { + Some(true) => { + *active_text_input_id = Some(resource.id()); + // Drop the guard before calling to other subsystem. + drop(guard); + data.input_method_handle.activate_input_method(state, &focus); + } + Some(false) => { + *active_text_input_id = None; + // Drop the guard before calling to other subsystem. + drop(guard); + data.input_method_handle.deactivate_input_method(state); + return; + } + None => { + if *active_text_input_id != Some(resource.id()) { + debug!("discarding text_input requests before enabling it"); + return; + } + + // Drop the guard before calling to other subsystems later on. + drop(guard); + } + } + + if let Some((text, cursor, anchor)) = new_state.surrounding_text.take() { + data.input_method_handle.with_instance(move |input_method| { + input_method.object.surrounding_text(text, cursor, anchor) + }); + } + + if let Some(cause) = new_state.text_change_cause.take() { + data.input_method_handle.with_instance(move |input_method| { + input_method.object.text_change_cause(cause); + }); + } + + if let Some((hint, purpose)) = new_state.content_type.take() { + data.input_method_handle.with_instance(move |input_method| { + input_method.object.content_type(hint, purpose); + }); + } + + if let Some(rect) = new_state.cursor_rectangle.take() { + data.input_method_handle + .set_text_input_rectangle::(state, rect); + } + data.input_method_handle.with_instance(|input_method| { input_method.done(); }); @@ -258,7 +348,23 @@ where }; if deactivate_im { - data.input_method_handle.deactivate_input_method(state, true); + data.input_method_handle.deactivate_input_method(state); } } } + +#[derive(Debug)] +struct Instance { + instance: ZwpTextInputV3, + serial: u32, + pending_state: TextInputState, +} + +#[derive(Debug, Default)] +struct TextInputState { + enable: Option, + surrounding_text: Option<(String, u32, u32)>, + content_type: Option<(ContentHint, ContentPurpose)>, + cursor_rectangle: Option>, + text_change_cause: Option, +} diff --git a/src/wayland/xdg_activation/mod.rs b/src/wayland/xdg_activation/mod.rs index cac78c7..6b124a3 100644 --- a/src/wayland/xdg_activation/mod.rs +++ b/src/wayland/xdg_activation/mod.rs @@ -153,6 +153,19 @@ impl XdgActivationTokenData { } } +impl Default for XdgActivationTokenData { + fn default() -> Self { + Self { + client_id: None, + serial: None, + app_id: None, + surface: None, + timestamp: Instant::now(), + user_data: Arc::new(UserDataMap::new()), + } + } +} + /// Tracks the list of pending and current activation requests #[derive(Debug)] pub struct XdgActivationState { @@ -185,9 +198,10 @@ impl XdgActivationState { /// instead use the return arguments to handle any initialization of the data you might need and to copy the token. pub fn create_external_token( &mut self, - app_id: impl Into>, + data: impl Into>, ) -> (&XdgActivationToken, &XdgActivationTokenData) { - let (token, data) = XdgActivationTokenData::new(None, None, app_id.into(), None); + let token = XdgActivationToken::new(); + let data = data.into().unwrap_or_default(); self.known_tokens.insert(token.clone(), data); self.known_tokens.get_key_value(&token).unwrap() } diff --git a/src/xwayland/xwm/mod.rs b/src/xwayland/xwm/mod.rs index 52fd2c6..ba08190 100644 --- a/src/xwayland/xwm/mod.rs +++ b/src/xwayland/xwm/mod.rs @@ -1422,6 +1422,14 @@ where } surface.state.lock().unwrap().mapped_onto = Some(frame_win); + + // A MapRequest can only happen when an X11 window does not have the + // override-redirect flag set. It's possible for a window to be created as + // override-redirect and later set the flag to false before mapping. + // In that case, we set the X11Surface's override-redirect state to false here + // to prevent `set_mapped` and `configure` from failing. + surface.state.lock().unwrap().override_redirect = false; + drop(_guard); state.map_window_request(xwm_id, surface); } diff --git a/src/xwayland/xwm/surface.rs b/src/xwayland/xwm/surface.rs index 8e9fbe3..09748b9 100644 --- a/src/xwayland/xwm/surface.rs +++ b/src/xwayland/xwm/surface.rs @@ -43,7 +43,6 @@ pub struct X11Surface { xwm: Option, client_scale: Option>, window: X11Window, - override_redirect: bool, conn: Weak, atoms: super::Atoms, pub(crate) state: Arc>, @@ -61,6 +60,7 @@ pub(crate) struct SharedSurfaceState { pub(super) wl_surface_serial: Option, pub(super) mapped_onto: Option, pub(super) geometry: Rectangle, + pub(super) override_redirect: bool, // The associated wl_surface. pub(crate) wl_surface: Option, @@ -170,7 +170,6 @@ impl X11Surface { xwm: xwm.map(|wm| wm.id), client_scale: xwm.map(|wm| wm.client_scale.clone()), window, - override_redirect, conn, atoms, state: Arc::new(Mutex::new(SharedSurfaceState { @@ -180,6 +179,7 @@ impl X11Surface { wl_surface: None, mapped_onto: None, geometry, + override_redirect, title: String::from(""), class: String::from(""), instance: String::from(""), @@ -216,7 +216,7 @@ impl X11Surface { /// /// It is an error to call this function on override redirect windows pub fn set_mapped(&self, mapped: bool) -> Result<(), X11SurfaceError> { - if self.override_redirect { + if self.is_override_redirect() { return Err(X11SurfaceError::UnsupportedForOverrideRedirect); } @@ -251,7 +251,7 @@ impl X11Surface { /// Returns if this window has the override redirect flag set or not pub fn is_override_redirect(&self) -> bool { - self.override_redirect + self.state.lock().unwrap().override_redirect } /// Returns if the window is currently mapped or not @@ -272,7 +272,7 @@ impl X11Surface { /// If `rect` is `None` a synthetic configure event with the existing state will be send. pub fn configure(&self, rect: impl Into>>) -> Result<(), X11SurfaceError> { let rect = rect.into(); - if self.override_redirect && rect.is_some() { + if self.is_override_redirect() && rect.is_some() { return Err(X11SurfaceError::UnsupportedForOverrideRedirect); }