mirror of
https://codeberg.org/hako/Rosenthal.git
synced 2025-04-14 10:04:32 +00:00
* rosenthal/packages/patches/niri.patch: New file. * rosenthal/packages/patches/rust-libspa-sys.patch: New file. * rosenthal/packages/patches/rust-libspa.patch: New file. * rosenthal/packages/patches/rust-pipewire.patch: New file. * rosenthal/packages/patches/rust-smithay.patch: New file. * rosenthal/packages/rust-crates.scm (niri-cargo-inputs): New variable. * rosenthal/packages/rust-apps.scm (niri): New variable. * README.org (Packages): Add it. * etc/manifest: Add it.
1902 lines
76 KiB
Diff
1902 lines
76 KiB
Diff
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<Monotonic> {
|
|
/// This should match timestamps from libinput:
|
|
/// <https://wayland.freedesktop.org/libinput/doc/latest/timestamps.html>
|
|
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<Kind> Rectangle<f64, Kind> {
|
|
/// Convert to i32 by returning the largest integer-space rectangle fitting into the float-based rectangle
|
|
#[inline]
|
|
pub fn to_i32_down<N: Coordinate>(self) -> Rectangle<N, Kind> {
|
|
- 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<N: Coordinate>(self) -> Rectangle<N, Kind> {
|
|
- 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<N: Coordinate, Kind> Rectangle<N, Kind> {
|
|
|
|
/// 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<Point<N, Kind>>,
|
|
bottomright: impl Into<Point<N, Kind>>,
|
|
+ ) -> 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<Point<N, Kind>>,
|
|
+ bottomright: impl Into<Point<N, Kind>>,
|
|
) -> Self {
|
|
let topleft = topleft.into();
|
|
let bottomright = bottomright.into();
|
|
@@ -1236,7 +1247,7 @@ impl<N: Coordinate, Kind> Rectangle<N, Kind> {
|
|
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<N: Coordinate, Kind> Rectangle<N, Kind> {
|
|
|
|
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<InnerId>);
|
|
|
|
pub(crate) struct Hook<T: ?Sized> {
|
|
pub id: HookId,
|
|
@@ -22,7 +22,7 @@ impl<T: ?Sized> std::fmt::Debug for Hook<T> {
|
|
impl<T: ?Sized> Clone for Hook<T> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
- id: self.id,
|
|
+ id: self.id.clone(),
|
|
cb: self.cb.clone(),
|
|
}
|
|
}
|
|
@@ -31,14 +31,23 @@ impl<T: ?Sized> Clone for Hook<T> {
|
|
impl<T: ?Sized> Hook<T> {
|
|
pub fn new(cb: Arc<T>) -> Self {
|
|
Self {
|
|
- id: HookId(hooks_id::next()),
|
|
+ id: HookId(Arc::new(InnerId::new())),
|
|
cb,
|
|
}
|
|
}
|
|
}
|
|
|
|
-impl<T: ?Sized> Drop for Hook<T> {
|
|
+#[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<CommitHook> = 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<CommitHook> = 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<DestructionHook> = 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<Option<RegistrationToken>>,
|
|
+
|
|
+ /// If listener was created with `get_input_idle_notification`
|
|
+ ignore_inhibitor: bool,
|
|
}
|
|
|
|
impl IdleNotificationUserData {
|
|
@@ -113,7 +116,7 @@ impl<D: IdleNotifierHandler> IdleNotifierState<D> {
|
|
D: IdleNotifierHandler,
|
|
D: 'static,
|
|
{
|
|
- let global = display.create_global::<D, ExtIdleNotifierV1, _>(1, ());
|
|
+ let global = display.create_global::<D, ExtIdleNotifierV1, _>(2, ());
|
|
Self {
|
|
global,
|
|
notifications: HashMap::new(),
|
|
@@ -131,9 +134,13 @@ impl<D: IdleNotifierHandler> IdleNotifierState<D> {
|
|
self.is_inhibited = is_inhibited;
|
|
|
|
for notification in self.notifications() {
|
|
- if is_inhibited {
|
|
- let data = notification.data::<IdleNotificationUserData>().unwrap();
|
|
+ let data = notification.data::<IdleNotificationUserData>().unwrap();
|
|
+
|
|
+ if data.ignore_inhibitor {
|
|
+ continue;
|
|
+ }
|
|
|
|
+ if is_inhibited {
|
|
if data.is_idle() {
|
|
notification.resumed();
|
|
data.set_idle(false);
|
|
@@ -189,7 +196,7 @@ impl<D: IdleNotifierHandler> IdleNotifierState<D> {
|
|
self.loop_handle.remove(token);
|
|
}
|
|
|
|
- if self.is_inhibited {
|
|
+ if !data.ignore_inhibitor && self.is_inhibited {
|
|
return;
|
|
}
|
|
|
|
@@ -200,7 +207,10 @@ impl<D: IdleNotifierHandler> IdleNotifierState<D> {
|
|
move |_, _, state| {
|
|
let data = idle_notification.data::<IdleNotificationUserData>().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<F>(&self, mut f: F)
|
|
+ pub(crate) fn with_instance<F>(&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<D: SeatHandler + 'static>(
|
|
&self,
|
|
state: &mut D,
|
|
- x: i32,
|
|
- y: i32,
|
|
- width: i32,
|
|
- height: i32,
|
|
+ rect: Rectangle<i32, Logical>,
|
|
) {
|
|
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::<InputMethodUserData<D>>().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<D: SeatHandler + 'static>(&self, state: &mut D, done: bool) {
|
|
+ /// The `done` is always send when deactivating IME.
|
|
+ pub(crate) fn deactivate_input_method<D: SeatHandler + 'static>(&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::<InputMethodUserData<D>>().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<D>,
|
|
) {
|
|
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<D: SeatHandler + 'static> KeyboardTarget<D> 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<D: SeatHandler + 'static> KeyboardTarget<D> 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<D> Dispatch<ExtDataControlDeviceV1, ExtDataControlDeviceUserData, D> for DataControlState
|
|
+where
|
|
+ D: Dispatch<ExtDataControlDeviceV1, ExtDataControlDeviceUserData>,
|
|
+ D: DataControlHandler,
|
|
+ D: 'static,
|
|
+{
|
|
+ fn request(
|
|
+ handler: &mut D,
|
|
+ _client: &Client,
|
|
+ resource: &ExtDataControlDeviceV1,
|
|
+ request: <ExtDataControlDeviceV1 as wayland_server::Resource>::Request,
|
|
+ data: &ExtDataControlDeviceUserData,
|
|
+ dh: &DisplayHandle,
|
|
+ _: &mut wayland_server::DataInit<'_, D>,
|
|
+ ) {
|
|
+ let seat = match Seat::<D>::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::<D::SelectionUserData>::new()));
|
|
+
|
|
+ let source = source.map(SelectionSourceProvider::ExtDataControl);
|
|
+
|
|
+ handler.new_selection(
|
|
+ SelectionTarget::Clipboard,
|
|
+ source.clone().map(|provider| SelectionSource { provider }),
|
|
+ seat.clone(),
|
|
+ );
|
|
+
|
|
+ seat.user_data()
|
|
+ .get::<RefCell<SeatData<D::SelectionUserData>>>()
|
|
+ .unwrap()
|
|
+ .borrow_mut()
|
|
+ .set_clipboard_selection::<D>(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::<D::SelectionUserData>::new()));
|
|
+
|
|
+ let source = source.map(SelectionSourceProvider::ExtDataControl);
|
|
+
|
|
+ handler.new_selection(
|
|
+ SelectionTarget::Primary,
|
|
+ source.clone().map(|provider| SelectionSource { provider }),
|
|
+ seat.clone(),
|
|
+ );
|
|
+
|
|
+ seat.user_data()
|
|
+ .get::<RefCell<SeatData<D::SelectionUserData>>>()
|
|
+ .unwrap()
|
|
+ .borrow_mut()
|
|
+ .set_primary_selection::<D>(dh, source.map(OfferReplySource::Client));
|
|
+ }
|
|
+ ext_data_control_device_v1::Request::Destroy => seat
|
|
+ .user_data()
|
|
+ .get::<RefCell<SeatData<D::SelectionUserData>>>()
|
|
+ .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::<State>::new().unwrap();
|
|
+//! // Create the data_control state
|
|
+//! let data_control_state = DataControlState::new::<State, _>(
|
|
+//! &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<Self> { unimplemented!() }
|
|
+//! # fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) { unimplemented!() }
|
|
+//! # fn cursor_image(&mut self, seat: &Seat<Self>, 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<D, F>(
|
|
+ display: &DisplayHandle,
|
|
+ primary_selection: Option<&PrimarySelectionState>,
|
|
+ filter: F,
|
|
+ ) -> Self
|
|
+ where
|
|
+ D: GlobalDispatch<ExtDataControlManagerV1, ExtDataControlManagerGlobalData> + '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::<D, ExtDataControlManagerV1, _>(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<dyn for<'c> 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<D> GlobalDispatch<ExtDataControlManagerV1, ExtDataControlManagerGlobalData, D> for DataControlState
|
|
+ where
|
|
+ D: GlobalDispatch<ExtDataControlManagerV1, ExtDataControlManagerGlobalData>,
|
|
+ D: Dispatch<ExtDataControlManagerV1, ExtDataControlManagerUserData>,
|
|
+ D: Dispatch<ExtDataControlDeviceV1, ExtDataControlDeviceUserData>,
|
|
+ D: Dispatch<ExtDataControlSourceV1, ExtDataControlSourceUserData>,
|
|
+ D: DataControlHandler,
|
|
+ D: 'static,
|
|
+ {
|
|
+ fn bind(
|
|
+ _state: &mut D,
|
|
+ _handle: &DisplayHandle,
|
|
+ _client: &wayland_server::Client,
|
|
+ resource: wayland_server::New<ExtDataControlManagerV1>,
|
|
+ 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<D> Dispatch<ExtDataControlManagerV1, ExtDataControlManagerUserData, D> for DataControlState
|
|
+ where
|
|
+ D: Dispatch<ExtDataControlManagerV1, ExtDataControlManagerUserData>,
|
|
+ D: Dispatch<ExtDataControlDeviceV1, ExtDataControlDeviceUserData>,
|
|
+ D: Dispatch<ExtDataControlSourceV1, ExtDataControlSourceUserData>,
|
|
+ D: DataControlHandler,
|
|
+ D: 'static,
|
|
+ {
|
|
+ fn request(
|
|
+ _handler: &mut D,
|
|
+ client: &wayland_server::Client,
|
|
+ _resource: &ExtDataControlManagerV1,
|
|
+ request: <ExtDataControlManagerV1 as wayland_server::Resource>::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::<D>::from_resource(&wl_seat) {
|
|
+ Some(seat) => {
|
|
+ seat.user_data()
|
|
+ .insert_if_missing(|| RefCell::new(SeatData::<D::SelectionUserData>::new()));
|
|
+
|
|
+ let device = SelectionDevice::ExtDataControl(data_init.init(
|
|
+ id,
|
|
+ ExtDataControlDeviceUserData {
|
|
+ wl_seat,
|
|
+ primary: data.primary,
|
|
+ },
|
|
+ ));
|
|
+
|
|
+ let mut seat_data = seat
|
|
+ .user_data()
|
|
+ .get::<RefCell<SeatData<D::SelectionUserData>>>()
|
|
+ .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::<D>(dh, SelectionTarget::Clipboard, device, true);
|
|
+ if data.primary {
|
|
+ seat_data.send_selection::<D>(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<SourceMetadata>,
|
|
+ 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<String>,
|
|
+}
|
|
+
|
|
+impl<D> Dispatch<ExtDataControlSourceV1, ExtDataControlSourceUserData, D> for DataControlState
|
|
+where
|
|
+ D: Dispatch<ExtDataControlSourceV1, ExtDataControlSourceUserData>,
|
|
+ D: DataControlHandler,
|
|
+ D: 'static,
|
|
+{
|
|
+ fn request(
|
|
+ _state: &mut D,
|
|
+ _client: &wayland_server::Client,
|
|
+ _resource: &ExtDataControlSourceV1,
|
|
+ request: <ExtDataControlSourceV1 as wayland_server::Resource>::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<U: Clone + Send + Sync + 'static> OfferReplySource<U> {
|
|
|
|
/// 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::<WlDataDevice>() {
|
|
- WlDataOffer::interface()
|
|
- } else if type_id == TypeId::of::<PrimaryDevice>() {
|
|
- 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::<D>(client_id, interface, device.version(), data)
|
|
.unwrap();
|
|
|
|
- if type_id == TypeId::of::<WlDataDevice>() {
|
|
- Self::DataDevice(WlDataOffer::from_id(dh, offer).unwrap())
|
|
- } else if type_id == TypeId::of::<PrimaryDevice>() {
|
|
- 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<U: Clone + Send + Sync + 'static> {
|
|
- type_id: TypeId,
|
|
+ device_kind: DataDeviceKind,
|
|
source: OfferReplySource<U>,
|
|
seat: WlSeat,
|
|
}
|
|
@@ -128,33 +131,47 @@ where
|
|
msg: Message<ObjectId, OwnedFd>,
|
|
) -> Option<Arc<dyn ObjectData<D>>> {
|
|
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::<WlDataDevice>() {
|
|
- 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::<PrimaryDevice>() {
|
|
- 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<U: Clone + Sync + Send + 'static> {
|
|
+/// The data is shared across primary, data device, and data control selections.
|
|
+pub(crate) struct SeatData<U: Clone + Sync + Send + 'static> {
|
|
known_devices: Vec<SelectionDevice>,
|
|
clipboard_selection: Option<OfferReplySource<U>>,
|
|
clipboard_selection_focus: Option<Client>,
|
|
@@ -20,7 +20,7 @@ pub struct SeatData<U: Clone + Sync + Send + 'static> {
|
|
}
|
|
|
|
impl<U: Clone + Send + Sync + 'static> SeatData<U> {
|
|
- /// 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<U: Clone + Send + Sync + 'static> SeatData<U> {
|
|
// 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<U: Clone + Send + Sync + 'static> SeatData<U> {
|
|
// 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::<D::SelectionUserData>::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::<D::SelectionUserData>::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::<D::SelectionUserData>::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<DestructionHook> = 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<Instance>,
|
|
focus: Option<WlSurface>,
|
|
- enabled_resource_id: Option<ObjectId>,
|
|
+ active_text_input_id: Option<ObjectId>,
|
|
}
|
|
|
|
impl TextInput {
|
|
- fn with_focused_text_input<F>(&mut self, mut f: F)
|
|
+ fn with_focused_client_all_text_inputs<F>(&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<F>(&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<ObjectId>) {
|
|
- 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<F>(&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<F>(&self, default: u32, mut callback: F)
|
|
+ pub(crate) fn active_text_input_serial_or_default<F>(&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::<D>(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::<D>(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<bool>,
|
|
+ surrounding_text: Option<(String, u32, u32)>,
|
|
+ content_type: Option<(ContentHint, ContentPurpose)>,
|
|
+ cursor_rectangle: Option<Rectangle<i32, Logical>>,
|
|
+ text_change_cause: Option<ChangeCause>,
|
|
+}
|
|
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<Option<String>>,
|
|
+ data: impl Into<Option<XdgActivationTokenData>>,
|
|
) -> (&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<XwmId>,
|
|
client_scale: Option<Arc<AtomicU32>>,
|
|
window: X11Window,
|
|
- override_redirect: bool,
|
|
conn: Weak<RustConnection>,
|
|
atoms: super::Atoms,
|
|
pub(crate) state: Arc<Mutex<SharedSurfaceState>>,
|
|
@@ -61,6 +60,7 @@ pub(crate) struct SharedSurfaceState {
|
|
pub(super) wl_surface_serial: Option<u64>,
|
|
pub(super) mapped_onto: Option<X11Window>,
|
|
pub(super) geometry: Rectangle<i32, Logical>,
|
|
+ pub(super) override_redirect: bool,
|
|
|
|
// The associated wl_surface.
|
|
pub(crate) wl_surface: Option<WlSurface>,
|
|
@@ -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<Option<Rectangle<i32, Logical>>>) -> 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);
|
|
}
|
|
|