mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-23 02:05:33 +07:00
implement keyboard-shortcuts-inhibit and wlr-virtual-pointer (#630)
* stub keyboard-shortcuts-inhibit and virtual-pointer impls * implement keyboard-shortcuts-inhibit * implement virtual-pointer * deal with supressed key release edge-case; add allow-inhibiting property * add toggle-keyboard-shortcuts-inhibit bind * add InputBackend extensions; use Device::output() for absolute pos events * add a `State` parameter to the backend exts and better document future intent * Add some tests for is_inhibiting_shortcuts --------- Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
+48
-1
@@ -1191,6 +1191,7 @@ pub struct Bind {
|
|||||||
pub repeat: bool,
|
pub repeat: bool,
|
||||||
pub cooldown: Option<Duration>,
|
pub cooldown: Option<Duration>,
|
||||||
pub allow_when_locked: bool,
|
pub allow_when_locked: bool,
|
||||||
|
pub allow_inhibiting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
|
||||||
@@ -1278,6 +1279,7 @@ pub enum Action {
|
|||||||
id: u64,
|
id: u64,
|
||||||
write_to_disk: bool,
|
write_to_disk: bool,
|
||||||
},
|
},
|
||||||
|
ToggleKeyboardShortcutsInhibit,
|
||||||
CloseWindow,
|
CloseWindow,
|
||||||
#[knuffel(skip)]
|
#[knuffel(skip)]
|
||||||
CloseWindowById(u64),
|
CloseWindowById(u64),
|
||||||
@@ -3015,6 +3017,7 @@ where
|
|||||||
let mut cooldown = None;
|
let mut cooldown = None;
|
||||||
let mut allow_when_locked = false;
|
let mut allow_when_locked = false;
|
||||||
let mut allow_when_locked_node = None;
|
let mut allow_when_locked_node = None;
|
||||||
|
let mut allow_inhibiting = true;
|
||||||
for (name, val) in &node.properties {
|
for (name, val) in &node.properties {
|
||||||
match &***name {
|
match &***name {
|
||||||
"repeat" => {
|
"repeat" => {
|
||||||
@@ -3029,6 +3032,9 @@ where
|
|||||||
allow_when_locked = knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
allow_when_locked = knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||||
allow_when_locked_node = Some(name);
|
allow_when_locked_node = Some(name);
|
||||||
}
|
}
|
||||||
|
"allow-inhibiting" => {
|
||||||
|
allow_inhibiting = knuffel::traits::DecodeScalar::decode(val, ctx)?;
|
||||||
|
}
|
||||||
name_str => {
|
name_str => {
|
||||||
ctx.emit_error(DecodeError::unexpected(
|
ctx.emit_error(DecodeError::unexpected(
|
||||||
name,
|
name,
|
||||||
@@ -3050,6 +3056,7 @@ where
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(child) = children.next() {
|
if let Some(child) = children.next() {
|
||||||
@@ -3072,12 +3079,19 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The toggle-inhibit action must always be uninhibitable.
|
||||||
|
// Otherwise, it would be impossible to trigger it.
|
||||||
|
if matches!(action, Action::ToggleKeyboardShortcutsInhibit) {
|
||||||
|
allow_inhibiting = false;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
key,
|
key,
|
||||||
action,
|
action,
|
||||||
repeat,
|
repeat,
|
||||||
cooldown,
|
cooldown,
|
||||||
allow_when_locked,
|
allow_when_locked,
|
||||||
|
allow_inhibiting,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -3463,6 +3477,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
binds {
|
binds {
|
||||||
|
Mod+Escape { toggle-keyboard-shortcuts-inhibit; }
|
||||||
|
Mod+Shift+Escape allow-inhibiting=true { toggle-keyboard-shortcuts-inhibit; }
|
||||||
Mod+T allow-when-locked=true { spawn "alacritty"; }
|
Mod+T allow-when-locked=true { spawn "alacritty"; }
|
||||||
Mod+Q { close-window; }
|
Mod+Q { close-window; }
|
||||||
Mod+Shift+H { focus-monitor-left; }
|
Mod+Shift+H { focus-monitor-left; }
|
||||||
@@ -3470,7 +3486,7 @@ mod tests {
|
|||||||
Mod+Comma { consume-window-into-column; }
|
Mod+Comma { consume-window-into-column; }
|
||||||
Mod+1 { focus-workspace 1; }
|
Mod+1 { focus-workspace 1; }
|
||||||
Mod+Shift+1 { focus-workspace "workspace-1"; }
|
Mod+Shift+1 { focus-workspace "workspace-1"; }
|
||||||
Mod+Shift+E { quit skip-confirmation=true; }
|
Mod+Shift+E allow-inhibiting=false { quit skip-confirmation=true; }
|
||||||
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
Mod+WheelScrollDown cooldown-ms=150 { focus-workspace-down; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3779,6 +3795,28 @@ mod tests {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
binds: Binds(vec![
|
binds: Binds(vec![
|
||||||
|
Bind {
|
||||||
|
key: Key {
|
||||||
|
trigger: Trigger::Keysym(Keysym::Escape),
|
||||||
|
modifiers: Modifiers::COMPOSITOR,
|
||||||
|
},
|
||||||
|
action: Action::ToggleKeyboardShortcutsInhibit,
|
||||||
|
repeat: true,
|
||||||
|
cooldown: None,
|
||||||
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: false,
|
||||||
|
},
|
||||||
|
Bind {
|
||||||
|
key: Key {
|
||||||
|
trigger: Trigger::Keysym(Keysym::Escape),
|
||||||
|
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
|
||||||
|
},
|
||||||
|
action: Action::ToggleKeyboardShortcutsInhibit,
|
||||||
|
repeat: true,
|
||||||
|
cooldown: None,
|
||||||
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: false,
|
||||||
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
trigger: Trigger::Keysym(Keysym::t),
|
trigger: Trigger::Keysym(Keysym::t),
|
||||||
@@ -3788,6 +3826,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: true,
|
allow_when_locked: true,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3798,6 +3837,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3808,6 +3848,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3818,6 +3859,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3828,6 +3870,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3838,6 +3881,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3850,6 +3894,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3860,6 +3905,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: false,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3870,6 +3916,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: Some(Duration::from_millis(150)),
|
cooldown: Some(Duration::from_millis(150)),
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
switch_events: SwitchBinds {
|
switch_events: SwitchBinds {
|
||||||
|
|||||||
@@ -536,6 +536,16 @@ binds {
|
|||||||
Ctrl+Print { screenshot-screen; }
|
Ctrl+Print { screenshot-screen; }
|
||||||
Alt+Print { screenshot-window; }
|
Alt+Print { screenshot-window; }
|
||||||
|
|
||||||
|
// Applications such as remote-desktop clients and software KVM switches may
|
||||||
|
// request that niri stops processing the keyboard shortcuts defined here
|
||||||
|
// so they may, for example, forward the key presses as-is to a remote machine.
|
||||||
|
// It's a good idea to bind an escape hatch to toggle the inhibitor,
|
||||||
|
// so a buggy application can't hold your session hostage.
|
||||||
|
//
|
||||||
|
// The allow-inhibiting=false property can be applied to other binds as well,
|
||||||
|
// which ensures niri always processes them, even when an inhibitor is active.
|
||||||
|
Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; }
|
||||||
|
|
||||||
// The quit action will show a confirmation dialog to avoid accidental exits.
|
// The quit action will show a confirmation dialog to avoid accidental exits.
|
||||||
Mod+Shift+E { quit; }
|
Mod+Shift+E { quit; }
|
||||||
Ctrl+Alt+Delete { quit; }
|
Ctrl+Alt+Delete { quit; }
|
||||||
|
|||||||
+62
-7
@@ -11,7 +11,7 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use smithay::backend::allocator::dmabuf::Dmabuf;
|
use smithay::backend::allocator::dmabuf::Dmabuf;
|
||||||
use smithay::backend::drm::DrmNode;
|
use smithay::backend::drm::DrmNode;
|
||||||
use smithay::backend::input::TabletToolDescriptor;
|
use smithay::backend::input::{InputEvent, TabletToolDescriptor};
|
||||||
use smithay::desktop::{PopupKind, PopupManager};
|
use smithay::desktop::{PopupKind, PopupManager};
|
||||||
use smithay::input::pointer::{
|
use smithay::input::pointer::{
|
||||||
CursorIcon, CursorImageStatus, CursorImageSurfaceData, PointerHandle,
|
CursorIcon, CursorImageStatus, CursorImageSurfaceData, PointerHandle,
|
||||||
@@ -35,6 +35,9 @@ use smithay::wayland::fractional_scale::FractionalScaleHandler;
|
|||||||
use smithay::wayland::idle_inhibit::IdleInhibitHandler;
|
use smithay::wayland::idle_inhibit::IdleInhibitHandler;
|
||||||
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
|
use smithay::wayland::idle_notify::{IdleNotifierHandler, IdleNotifierState};
|
||||||
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
use smithay::wayland::input_method::{InputMethodHandler, PopupSurface};
|
||||||
|
use smithay::wayland::keyboard_shortcuts_inhibit::{
|
||||||
|
KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
|
||||||
|
};
|
||||||
use smithay::wayland::output::OutputHandler;
|
use smithay::wayland::output::OutputHandler;
|
||||||
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler};
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsHandler};
|
||||||
use smithay::wayland::security_context::{
|
use smithay::wayland::security_context::{
|
||||||
@@ -59,11 +62,12 @@ use smithay::wayland::xdg_activation::{
|
|||||||
use smithay::{
|
use smithay::{
|
||||||
delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
delegate_cursor_shape, delegate_data_control, delegate_data_device, delegate_dmabuf,
|
||||||
delegate_drm_lease, delegate_fractional_scale, delegate_idle_inhibit, delegate_idle_notify,
|
delegate_drm_lease, delegate_fractional_scale, delegate_idle_inhibit, delegate_idle_notify,
|
||||||
delegate_input_method_manager, delegate_output, delegate_pointer_constraints,
|
delegate_input_method_manager, delegate_keyboard_shortcuts_inhibit, delegate_output,
|
||||||
delegate_pointer_gestures, delegate_presentation, delegate_primary_selection,
|
delegate_pointer_constraints, delegate_pointer_gestures, delegate_presentation,
|
||||||
delegate_relative_pointer, delegate_seat, delegate_security_context, delegate_session_lock,
|
delegate_primary_selection, delegate_relative_pointer, delegate_seat,
|
||||||
delegate_single_pixel_buffer, delegate_tablet_manager, delegate_text_input_manager,
|
delegate_security_context, delegate_session_lock, delegate_single_pixel_buffer,
|
||||||
delegate_viewporter, delegate_virtual_keyboard_manager, delegate_xdg_activation,
|
delegate_tablet_manager, delegate_text_input_manager, delegate_viewporter,
|
||||||
|
delegate_virtual_keyboard_manager, delegate_xdg_activation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
pub use crate::handlers::xdg_shell::KdeDecorationsModeState;
|
||||||
@@ -75,10 +79,15 @@ use crate::protocols::gamma_control::{GammaControlHandler, GammaControlManagerSt
|
|||||||
use crate::protocols::mutter_x11_interop::MutterX11InteropHandler;
|
use crate::protocols::mutter_x11_interop::MutterX11InteropHandler;
|
||||||
use crate::protocols::output_management::{OutputManagementHandler, OutputManagementManagerState};
|
use crate::protocols::output_management::{OutputManagementHandler, OutputManagementManagerState};
|
||||||
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler, ScreencopyManagerState};
|
use crate::protocols::screencopy::{Screencopy, ScreencopyHandler, ScreencopyManagerState};
|
||||||
|
use crate::protocols::virtual_pointer::{
|
||||||
|
VirtualPointerAxisEvent, VirtualPointerButtonEvent, VirtualPointerHandler,
|
||||||
|
VirtualPointerInputBackend, VirtualPointerManagerState, VirtualPointerMotionAbsoluteEvent,
|
||||||
|
VirtualPointerMotionEvent,
|
||||||
|
};
|
||||||
use crate::utils::{output_size, send_scale_transform, with_toplevel_role};
|
use crate::utils::{output_size, send_scale_transform, with_toplevel_role};
|
||||||
use crate::{
|
use crate::{
|
||||||
delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop,
|
delegate_foreign_toplevel, delegate_gamma_control, delegate_mutter_x11_interop,
|
||||||
delegate_output_management, delegate_screencopy,
|
delegate_output_management, delegate_screencopy, delegate_virtual_pointer,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const XDG_ACTIVATION_TOKEN_TIMEOUT: Duration = Duration::from_secs(10);
|
pub const XDG_ACTIVATION_TOKEN_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
@@ -243,7 +252,28 @@ impl InputMethodHandler for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl KeyboardShortcutsInhibitHandler for State {
|
||||||
|
fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState {
|
||||||
|
&mut self.niri.keyboard_shortcuts_inhibit_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
|
||||||
|
// FIXME: show a confirmation dialog with a "remember for this application" kind of toggle.
|
||||||
|
inhibitor.activate();
|
||||||
|
self.niri
|
||||||
|
.keyboard_shortcuts_inhibiting_surfaces
|
||||||
|
.insert(inhibitor.wl_surface().clone(), inhibitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inhibitor_destroyed(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
|
||||||
|
self.niri
|
||||||
|
.keyboard_shortcuts_inhibiting_surfaces
|
||||||
|
.remove(&inhibitor.wl_surface().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate_input_method_manager!(State);
|
delegate_input_method_manager!(State);
|
||||||
|
delegate_keyboard_shortcuts_inhibit!(State);
|
||||||
delegate_virtual_keyboard_manager!(State);
|
delegate_virtual_keyboard_manager!(State);
|
||||||
|
|
||||||
impl SelectionHandler for State {
|
impl SelectionHandler for State {
|
||||||
@@ -562,6 +592,31 @@ impl ScreencopyHandler for State {
|
|||||||
}
|
}
|
||||||
delegate_screencopy!(State);
|
delegate_screencopy!(State);
|
||||||
|
|
||||||
|
impl VirtualPointerHandler for State {
|
||||||
|
fn virtual_pointer_manager_state(&mut self) -> &mut VirtualPointerManagerState {
|
||||||
|
&mut self.niri.virtual_pointer_state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_virtual_pointer_motion(&mut self, event: VirtualPointerMotionEvent) {
|
||||||
|
self.process_input_event(InputEvent::<VirtualPointerInputBackend>::PointerMotion { event });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_virtual_pointer_motion_absolute(&mut self, event: VirtualPointerMotionAbsoluteEvent) {
|
||||||
|
self.process_input_event(
|
||||||
|
InputEvent::<VirtualPointerInputBackend>::PointerMotionAbsolute { event },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_virtual_pointer_button(&mut self, event: VirtualPointerButtonEvent) {
|
||||||
|
self.process_input_event(InputEvent::<VirtualPointerInputBackend>::PointerButton { event });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_virtual_pointer_axis(&mut self, event: VirtualPointerAxisEvent) {
|
||||||
|
self.process_input_event(InputEvent::<VirtualPointerInputBackend>::PointerAxis { event });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate_virtual_pointer!(State);
|
||||||
|
|
||||||
impl DrmLeaseHandler for State {
|
impl DrmLeaseHandler for State {
|
||||||
fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState {
|
fn drm_lease_state(&mut self, node: DrmNode) -> &mut DrmLeaseState {
|
||||||
self.backend
|
self.backend
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
use ::input as libinput;
|
||||||
|
use smithay::backend::input;
|
||||||
|
use smithay::backend::winit::WinitVirtualDevice;
|
||||||
|
use smithay::output::Output;
|
||||||
|
|
||||||
|
use crate::niri::State;
|
||||||
|
use crate::protocols::virtual_pointer::VirtualPointer;
|
||||||
|
|
||||||
|
pub trait NiriInputBackend: input::InputBackend<Device = Self::NiriDevice> {
|
||||||
|
type NiriDevice: NiriInputDevice;
|
||||||
|
}
|
||||||
|
impl<T: input::InputBackend> NiriInputBackend for T
|
||||||
|
where
|
||||||
|
Self::Device: NiriInputDevice,
|
||||||
|
{
|
||||||
|
type NiriDevice = Self::Device;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait NiriInputDevice: input::Device {
|
||||||
|
// FIXME: this should maybe be per-event, not per-device,
|
||||||
|
// but it's not clear that this matters in practice?
|
||||||
|
// it might be more obvious once we implement it for libinput
|
||||||
|
fn output(&self, state: &State) -> Option<Output>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NiriInputDevice for libinput::Device {
|
||||||
|
fn output(&self, _state: &State) -> Option<Output> {
|
||||||
|
// FIXME: Allow specifying the output per-device?
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NiriInputDevice for WinitVirtualDevice {
|
||||||
|
fn output(&self, _state: &State) -> Option<Output> {
|
||||||
|
// FIXME: we should be returning the single output that the winit backend creates,
|
||||||
|
// but for now, that will cause issues because the output is normally upside down,
|
||||||
|
// so we apply Transform::Flipped180 to it and that would also cause
|
||||||
|
// the cursor position to be flipped, which is not what we want.
|
||||||
|
//
|
||||||
|
// instead, we just return None and rely on the fact that it has only one output.
|
||||||
|
// doing so causes the cursor to be placed in *global* output coordinates,
|
||||||
|
// which are not flipped, and happen to be what we want.
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NiriInputDevice for VirtualPointer {
|
||||||
|
fn output(&self, _: &State) -> Option<Output> {
|
||||||
|
self.output().cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
+138
-13
@@ -11,7 +11,7 @@ use niri_ipc::LayoutSwitchTarget;
|
|||||||
use smithay::backend::input::{
|
use smithay::backend::input::{
|
||||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
|
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
|
||||||
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
|
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
|
||||||
InputBackend, InputEvent, KeyState, KeyboardKeyEvent, Keycode, MouseButton, PointerAxisEvent,
|
InputEvent, KeyState, KeyboardKeyEvent, Keycode, MouseButton, PointerAxisEvent,
|
||||||
PointerButtonEvent, PointerMotionEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent,
|
PointerButtonEvent, PointerMotionEvent, ProximityState, Switch, SwitchState, SwitchToggleEvent,
|
||||||
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent,
|
TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent, TabletToolTipEvent,
|
||||||
TabletToolTipState, TouchEvent,
|
TabletToolTipState, TouchEvent,
|
||||||
@@ -28,7 +28,9 @@ use smithay::input::touch::{
|
|||||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent, UpEvent,
|
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent, UpEvent,
|
||||||
};
|
};
|
||||||
use smithay::input::SeatHandler;
|
use smithay::input::SeatHandler;
|
||||||
|
use smithay::output::Output;
|
||||||
use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER};
|
use smithay::utils::{Logical, Point, Rectangle, Transform, SERIAL_COUNTER};
|
||||||
|
use smithay::wayland::keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitor;
|
||||||
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraint};
|
||||||
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
|
||||||
use touch_move_grab::TouchMoveGrab;
|
use touch_move_grab::TouchMoveGrab;
|
||||||
@@ -42,6 +44,7 @@ use crate::ui::screenshot_ui::ScreenshotUi;
|
|||||||
use crate::utils::spawning::spawn;
|
use crate::utils::spawning::spawn;
|
||||||
use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
use crate::utils::{center, get_monotonic_time, ResizeEdge};
|
||||||
|
|
||||||
|
pub mod backend_ext;
|
||||||
pub mod move_grab;
|
pub mod move_grab;
|
||||||
pub mod resize_grab;
|
pub mod resize_grab;
|
||||||
pub mod scroll_tracker;
|
pub mod scroll_tracker;
|
||||||
@@ -50,6 +53,8 @@ pub mod swipe_tracker;
|
|||||||
pub mod touch_move_grab;
|
pub mod touch_move_grab;
|
||||||
pub mod touch_resize_grab;
|
pub mod touch_resize_grab;
|
||||||
|
|
||||||
|
use backend_ext::{NiriInputBackend as InputBackend, NiriInputDevice as _};
|
||||||
|
|
||||||
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
|
pub const DOUBLE_CLICK_TIME: Duration = Duration::from_millis(400);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@@ -264,8 +269,10 @@ impl State {
|
|||||||
where
|
where
|
||||||
I::Device: 'static,
|
I::Device: 'static,
|
||||||
{
|
{
|
||||||
|
let device_output = event.device().output(self);
|
||||||
|
let device_output = device_output.as_ref();
|
||||||
let (target_geo, keep_ratio, px, transform) =
|
let (target_geo, keep_ratio, px, transform) =
|
||||||
if let Some(output) = self.niri.output_for_tablet() {
|
if let Some(output) = device_output.or_else(|| self.niri.output_for_tablet()) {
|
||||||
(
|
(
|
||||||
self.niri.global_space.output_geometry(output).unwrap(),
|
self.niri.global_space.output_geometry(output).unwrap(),
|
||||||
true,
|
true,
|
||||||
@@ -318,6 +325,18 @@ impl State {
|
|||||||
Some(pos + target_geo.loc.to_f64())
|
Some(pos + target_geo.loc.to_f64())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_inhibiting_shortcuts(&self) -> bool {
|
||||||
|
self.niri
|
||||||
|
.keyboard_focus
|
||||||
|
.surface()
|
||||||
|
.and_then(|surface| {
|
||||||
|
self.niri
|
||||||
|
.keyboard_shortcuts_inhibiting_surfaces
|
||||||
|
.get(surface)
|
||||||
|
})
|
||||||
|
.is_some_and(KeyboardShortcutsInhibitor::is_active)
|
||||||
|
}
|
||||||
|
|
||||||
fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent) {
|
fn on_keyboard<I: InputBackend>(&mut self, event: I::KeyboardKeyEvent) {
|
||||||
let comp_mod = self.backend.mod_key();
|
let comp_mod = self.backend.mod_key();
|
||||||
|
|
||||||
@@ -342,6 +361,8 @@ impl State {
|
|||||||
self.hide_cursor_if_needed();
|
self.hide_cursor_if_needed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_inhibiting_shortcuts = self.is_inhibiting_shortcuts();
|
||||||
|
|
||||||
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
|
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
|
||||||
self,
|
self,
|
||||||
event.key_code(),
|
event.key_code(),
|
||||||
@@ -372,6 +393,7 @@ impl State {
|
|||||||
*mods,
|
*mods,
|
||||||
&this.niri.screenshot_ui,
|
&this.niri.screenshot_ui,
|
||||||
this.niri.config.borrow().input.disable_power_key_handling,
|
this.niri.config.borrow().input.disable_power_key_handling,
|
||||||
|
is_inhibiting_shortcuts,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) else {
|
) else {
|
||||||
@@ -610,6 +632,19 @@ impl State {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Action::ToggleKeyboardShortcutsInhibit => {
|
||||||
|
if let Some(inhibitor) = self.niri.keyboard_focus.surface().and_then(|surface| {
|
||||||
|
self.niri
|
||||||
|
.keyboard_shortcuts_inhibiting_surfaces
|
||||||
|
.get(surface)
|
||||||
|
}) {
|
||||||
|
if inhibitor.is_active() {
|
||||||
|
inhibitor.inactivate();
|
||||||
|
} else {
|
||||||
|
inhibitor.activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Action::CloseWindow => {
|
Action::CloseWindow => {
|
||||||
if let Some(mapped) = self.niri.layout.focus() {
|
if let Some(mapped) = self.niri.layout.focus() {
|
||||||
mapped.toplevel().send_close();
|
mapped.toplevel().send_close();
|
||||||
@@ -1731,12 +1766,14 @@ impl State {
|
|||||||
&mut self,
|
&mut self,
|
||||||
event: I::PointerMotionAbsoluteEvent,
|
event: I::PointerMotionAbsoluteEvent,
|
||||||
) {
|
) {
|
||||||
let Some(output_geo) = self.global_bounding_rectangle() else {
|
let Some(pos) = self.compute_absolute_location(&event, None).or_else(|| {
|
||||||
|
self.global_bounding_rectangle().map(|output_geo| {
|
||||||
|
event.position_transformed(output_geo.size) + output_geo.loc.to_f64()
|
||||||
|
})
|
||||||
|
}) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
|
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
|
||||||
let pointer = self.niri.seat.get_pointer().unwrap();
|
let pointer = self.niri.seat.get_pointer().unwrap();
|
||||||
@@ -2613,14 +2650,13 @@ impl State {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes the cursor position for the touch event.
|
fn compute_absolute_location<I: InputBackend>(
|
||||||
///
|
|
||||||
/// This function handles the touch output mapping, as well as coordinate transform
|
|
||||||
fn compute_touch_location<I: InputBackend, E: AbsolutePositionEvent<I>>(
|
|
||||||
&self,
|
&self,
|
||||||
evt: &E,
|
evt: &impl AbsolutePositionEvent<I>,
|
||||||
|
fallback_output: Option<&Output>,
|
||||||
) -> Option<Point<f64, Logical>> {
|
) -> Option<Point<f64, Logical>> {
|
||||||
let output = self.niri.output_for_touch()?;
|
let output = evt.device().output(self);
|
||||||
|
let output = output.as_ref().or(fallback_output)?;
|
||||||
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
|
||||||
let transform = output.current_transform();
|
let transform = output.current_transform();
|
||||||
let size = transform.invert().transform_size(output_geo.size);
|
let size = transform.invert().transform_size(output_geo.size);
|
||||||
@@ -2630,6 +2666,16 @@ impl State {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes the cursor position for the touch event.
|
||||||
|
///
|
||||||
|
/// This function handles the touch output mapping, as well as coordinate transform
|
||||||
|
fn compute_touch_location<I: InputBackend>(
|
||||||
|
&self,
|
||||||
|
evt: &impl AbsolutePositionEvent<I>,
|
||||||
|
) -> Option<Point<f64, Logical>> {
|
||||||
|
self.compute_absolute_location(evt, self.niri.output_for_touch())
|
||||||
|
}
|
||||||
|
|
||||||
fn on_touch_down<I: InputBackend>(&mut self, evt: I::TouchDownEvent) {
|
fn on_touch_down<I: InputBackend>(&mut self, evt: I::TouchDownEvent) {
|
||||||
let Some(handle) = self.niri.seat.get_touch() else {
|
let Some(handle) = self.niri.seat.get_touch() else {
|
||||||
return;
|
return;
|
||||||
@@ -2780,6 +2826,7 @@ fn should_intercept_key(
|
|||||||
mods: ModifiersState,
|
mods: ModifiersState,
|
||||||
screenshot_ui: &ScreenshotUi,
|
screenshot_ui: &ScreenshotUi,
|
||||||
disable_power_key_handling: bool,
|
disable_power_key_handling: bool,
|
||||||
|
is_inhibiting_shortcuts: bool,
|
||||||
) -> FilterResult<Option<Bind>> {
|
) -> FilterResult<Option<Bind>> {
|
||||||
// Actions are only triggered on presses, release of the key
|
// Actions are only triggered on presses, release of the key
|
||||||
// shouldn't try to intercept anything unless we have marked
|
// shouldn't try to intercept anything unless we have marked
|
||||||
@@ -2820,6 +2867,10 @@ fn should_intercept_key(
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
// The screenshot UI owns the focus anyway, so this doesn't really matter.
|
||||||
|
// But logically, nothing can inhibit its actions. Only opening it can be
|
||||||
|
// inhibited.
|
||||||
|
allow_inhibiting: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2827,10 +2878,19 @@ fn should_intercept_key(
|
|||||||
|
|
||||||
match (final_bind, pressed) {
|
match (final_bind, pressed) {
|
||||||
(Some(bind), true) => {
|
(Some(bind), true) => {
|
||||||
suppressed_keys.insert(key_code);
|
if is_inhibiting_shortcuts && bind.allow_inhibiting {
|
||||||
FilterResult::Intercept(Some(bind))
|
FilterResult::Forward
|
||||||
|
} else {
|
||||||
|
suppressed_keys.insert(key_code);
|
||||||
|
FilterResult::Intercept(Some(bind))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(_, false) => {
|
(_, false) => {
|
||||||
|
// By this point, we know that the key was supressed on press. Even if we're inhibiting
|
||||||
|
// shortcuts, we should still suppress the release.
|
||||||
|
// But we don't need to check for shortcuts inhibition here, because
|
||||||
|
// if it was inhibited on press (forwarded to the client), it wouldn't be suppressed,
|
||||||
|
// so the release would already have been forwarded at the start of this function.
|
||||||
suppressed_keys.remove(&key_code);
|
suppressed_keys.remove(&key_code);
|
||||||
FilterResult::Intercept(None)
|
FilterResult::Intercept(None)
|
||||||
}
|
}
|
||||||
@@ -2870,6 +2930,12 @@ fn find_bind(
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
// In a worst-case scenario, the user has no way to unlock the compositor and a
|
||||||
|
// misbehaving client has a keyboard shortcuts inhibitor, "jailing" the user.
|
||||||
|
// The user must always be able to change VTs to recover from such a situation.
|
||||||
|
// It also makes no sense to inhibit the default power key handling.
|
||||||
|
// Hardcoded binds must never be inhibited.
|
||||||
|
allow_inhibiting: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3035,6 +3101,7 @@ fn allowed_when_locked(action: &Action) -> bool {
|
|||||||
| Action::PowerOffMonitors
|
| Action::PowerOffMonitors
|
||||||
| Action::PowerOnMonitors
|
| Action::PowerOnMonitors
|
||||||
| Action::SwitchLayout(_)
|
| Action::SwitchLayout(_)
|
||||||
|
| Action::ToggleKeyboardShortcutsInhibit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3317,6 +3384,8 @@ pub fn mods_with_finger_scroll_binds(comp_mod: CompositorMod, binds: &Binds) ->
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::animation::Clock;
|
use crate::animation::Clock;
|
||||||
|
|
||||||
@@ -3332,6 +3401,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
let comp_mod = CompositorMod::Super;
|
let comp_mod = CompositorMod::Super;
|
||||||
@@ -3339,6 +3409,7 @@ mod tests {
|
|||||||
|
|
||||||
let screenshot_ui = ScreenshotUi::new(Clock::default(), Default::default());
|
let screenshot_ui = ScreenshotUi::new(Clock::default(), Default::default());
|
||||||
let disable_power_key_handling = false;
|
let disable_power_key_handling = false;
|
||||||
|
let is_inhibiting_shortcuts = Cell::new(false);
|
||||||
|
|
||||||
// The key_code we pick is arbitrary, the only thing
|
// The key_code we pick is arbitrary, the only thing
|
||||||
// that matters is that they are different between cases.
|
// that matters is that they are different between cases.
|
||||||
@@ -3356,6 +3427,7 @@ mod tests {
|
|||||||
mods,
|
mods,
|
||||||
&screenshot_ui,
|
&screenshot_ui,
|
||||||
disable_power_key_handling,
|
disable_power_key_handling,
|
||||||
|
is_inhibiting_shortcuts.get(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3372,6 +3444,7 @@ mod tests {
|
|||||||
mods,
|
mods,
|
||||||
&screenshot_ui,
|
&screenshot_ui,
|
||||||
disable_power_key_handling,
|
disable_power_key_handling,
|
||||||
|
is_inhibiting_shortcuts.get(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -3452,6 +3525,53 @@ mod tests {
|
|||||||
|
|
||||||
// Ensure that no keys are being suppressed.
|
// Ensure that no keys are being suppressed.
|
||||||
assert!(suppressed_keys.is_empty());
|
assert!(suppressed_keys.is_empty());
|
||||||
|
|
||||||
|
// Now test shortcut inhibiting.
|
||||||
|
|
||||||
|
// With inhibited shortcuts, we don't intercept our shortcut.
|
||||||
|
is_inhibiting_shortcuts.set(true);
|
||||||
|
|
||||||
|
mods = ModifiersState {
|
||||||
|
logo: true,
|
||||||
|
ctrl: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter = close_key_event(&mut suppressed_keys, mods, true);
|
||||||
|
assert!(matches!(filter, FilterResult::Forward));
|
||||||
|
assert!(suppressed_keys.is_empty());
|
||||||
|
|
||||||
|
let filter = close_key_event(&mut suppressed_keys, mods, false);
|
||||||
|
assert!(matches!(filter, FilterResult::Forward));
|
||||||
|
assert!(suppressed_keys.is_empty());
|
||||||
|
|
||||||
|
// Toggle it off after pressing the shortcut.
|
||||||
|
let filter = close_key_event(&mut suppressed_keys, mods, true);
|
||||||
|
assert!(matches!(filter, FilterResult::Forward));
|
||||||
|
assert!(suppressed_keys.is_empty());
|
||||||
|
|
||||||
|
is_inhibiting_shortcuts.set(false);
|
||||||
|
|
||||||
|
let filter = close_key_event(&mut suppressed_keys, mods, false);
|
||||||
|
assert!(matches!(filter, FilterResult::Forward));
|
||||||
|
assert!(suppressed_keys.is_empty());
|
||||||
|
|
||||||
|
// Toggle it on after pressing the shortcut.
|
||||||
|
let filter = close_key_event(&mut suppressed_keys, mods, true);
|
||||||
|
assert!(matches!(
|
||||||
|
filter,
|
||||||
|
FilterResult::Intercept(Some(Bind {
|
||||||
|
action: Action::CloseWindow,
|
||||||
|
..
|
||||||
|
}))
|
||||||
|
));
|
||||||
|
assert!(suppressed_keys.contains(&close_key_code));
|
||||||
|
|
||||||
|
is_inhibiting_shortcuts.set(true);
|
||||||
|
|
||||||
|
let filter = close_key_event(&mut suppressed_keys, mods, false);
|
||||||
|
assert!(matches!(filter, FilterResult::Intercept(None)));
|
||||||
|
assert!(suppressed_keys.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3466,6 +3586,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3476,6 +3597,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3486,6 +3608,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3496,6 +3619,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
Bind {
|
Bind {
|
||||||
key: Key {
|
key: Key {
|
||||||
@@ -3506,6 +3630,7 @@ mod tests {
|
|||||||
repeat: true,
|
repeat: true,
|
||||||
cooldown: None,
|
cooldown: None,
|
||||||
allow_when_locked: false,
|
allow_when_locked: false,
|
||||||
|
allow_inhibiting: true,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -77,6 +77,9 @@ use smithay::wayland::fractional_scale::FractionalScaleManagerState;
|
|||||||
use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
|
use smithay::wayland::idle_inhibit::IdleInhibitManagerState;
|
||||||
use smithay::wayland::idle_notify::IdleNotifierState;
|
use smithay::wayland::idle_notify::IdleNotifierState;
|
||||||
use smithay::wayland::input_method::{InputMethodManagerState, InputMethodSeat};
|
use smithay::wayland::input_method::{InputMethodManagerState, InputMethodSeat};
|
||||||
|
use smithay::wayland::keyboard_shortcuts_inhibit::{
|
||||||
|
KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
|
||||||
|
};
|
||||||
use smithay::wayland::output::OutputManagerState;
|
use smithay::wayland::output::OutputManagerState;
|
||||||
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsState};
|
use smithay::wayland::pointer_constraints::{with_pointer_constraint, PointerConstraintsState};
|
||||||
use smithay::wayland::pointer_gestures::PointerGesturesState;
|
use smithay::wayland::pointer_gestures::PointerGesturesState;
|
||||||
@@ -131,6 +134,7 @@ use crate::protocols::gamma_control::GammaControlManagerState;
|
|||||||
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
|
use crate::protocols::mutter_x11_interop::MutterX11InteropManagerState;
|
||||||
use crate::protocols::output_management::OutputManagementManagerState;
|
use crate::protocols::output_management::OutputManagementManagerState;
|
||||||
use crate::protocols::screencopy::{Screencopy, ScreencopyBuffer, ScreencopyManagerState};
|
use crate::protocols::screencopy::{Screencopy, ScreencopyBuffer, ScreencopyManagerState};
|
||||||
|
use crate::protocols::virtual_pointer::VirtualPointerManagerState;
|
||||||
use crate::pw_utils::{Cast, PipeWire};
|
use crate::pw_utils::{Cast, PipeWire};
|
||||||
#[cfg(feature = "xdp-gnome-screencast")]
|
#[cfg(feature = "xdp-gnome-screencast")]
|
||||||
use crate::pw_utils::{CastSizeChange, CastTarget, PwToNiri};
|
use crate::pw_utils::{CastSizeChange, CastTarget, PwToNiri};
|
||||||
@@ -252,7 +256,9 @@ pub struct Niri {
|
|||||||
pub tablet_state: TabletManagerState,
|
pub tablet_state: TabletManagerState,
|
||||||
pub text_input_state: TextInputManagerState,
|
pub text_input_state: TextInputManagerState,
|
||||||
pub input_method_state: InputMethodManagerState,
|
pub input_method_state: InputMethodManagerState,
|
||||||
|
pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
|
||||||
pub virtual_keyboard_state: VirtualKeyboardManagerState,
|
pub virtual_keyboard_state: VirtualKeyboardManagerState,
|
||||||
|
pub virtual_pointer_state: VirtualPointerManagerState,
|
||||||
pub pointer_gestures_state: PointerGesturesState,
|
pub pointer_gestures_state: PointerGesturesState,
|
||||||
pub relative_pointer_state: RelativePointerManagerState,
|
pub relative_pointer_state: RelativePointerManagerState,
|
||||||
pub pointer_constraints_state: PointerConstraintsState,
|
pub pointer_constraints_state: PointerConstraintsState,
|
||||||
@@ -290,6 +296,7 @@ pub struct Niri {
|
|||||||
pub previously_focused_window: Option<Window>,
|
pub previously_focused_window: Option<Window>,
|
||||||
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
|
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
|
||||||
pub is_fdo_idle_inhibited: Arc<AtomicBool>,
|
pub is_fdo_idle_inhibited: Arc<AtomicBool>,
|
||||||
|
pub keyboard_shortcuts_inhibiting_surfaces: HashMap<WlSurface, KeyboardShortcutsInhibitor>,
|
||||||
|
|
||||||
pub cursor_manager: CursorManager,
|
pub cursor_manager: CursorManager,
|
||||||
pub cursor_texture_cache: CursorTextureCache,
|
pub cursor_texture_cache: CursorTextureCache,
|
||||||
@@ -1818,11 +1825,16 @@ impl Niri {
|
|||||||
InputMethodManagerState::new::<State, _>(&display_handle, |client| {
|
InputMethodManagerState::new::<State, _>(&display_handle, |client| {
|
||||||
!client.get_data::<ClientState>().unwrap().restricted
|
!client.get_data::<ClientState>().unwrap().restricted
|
||||||
});
|
});
|
||||||
|
let keyboard_shortcuts_inhibit_state =
|
||||||
|
KeyboardShortcutsInhibitState::new::<State>(&display_handle);
|
||||||
let virtual_keyboard_state =
|
let virtual_keyboard_state =
|
||||||
VirtualKeyboardManagerState::new::<State, _>(&display_handle, |client| {
|
VirtualKeyboardManagerState::new::<State, _>(&display_handle, |client| {
|
||||||
!client.get_data::<ClientState>().unwrap().restricted
|
!client.get_data::<ClientState>().unwrap().restricted
|
||||||
});
|
});
|
||||||
|
let virtual_pointer_state =
|
||||||
|
VirtualPointerManagerState::new::<State, _>(&display_handle, |client| {
|
||||||
|
!client.get_data::<ClientState>().unwrap().restricted
|
||||||
|
});
|
||||||
let foreign_toplevel_state =
|
let foreign_toplevel_state =
|
||||||
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
|
ForeignToplevelManagerState::new::<State, _>(&display_handle, |client| {
|
||||||
!client.get_data::<ClientState>().unwrap().restricted
|
!client.get_data::<ClientState>().unwrap().restricted
|
||||||
@@ -2014,7 +2026,9 @@ impl Niri {
|
|||||||
xdg_foreign_state,
|
xdg_foreign_state,
|
||||||
text_input_state,
|
text_input_state,
|
||||||
input_method_state,
|
input_method_state,
|
||||||
|
keyboard_shortcuts_inhibit_state,
|
||||||
virtual_keyboard_state,
|
virtual_keyboard_state,
|
||||||
|
virtual_pointer_state,
|
||||||
shm_state,
|
shm_state,
|
||||||
output_manager_state,
|
output_manager_state,
|
||||||
dmabuf_state,
|
dmabuf_state,
|
||||||
@@ -2049,6 +2063,7 @@ impl Niri {
|
|||||||
previously_focused_window: None,
|
previously_focused_window: None,
|
||||||
idle_inhibiting_surfaces: HashSet::new(),
|
idle_inhibiting_surfaces: HashSet::new(),
|
||||||
is_fdo_idle_inhibited: Arc::new(AtomicBool::new(false)),
|
is_fdo_idle_inhibited: Arc::new(AtomicBool::new(false)),
|
||||||
|
keyboard_shortcuts_inhibiting_surfaces: HashMap::new(),
|
||||||
cursor_manager,
|
cursor_manager,
|
||||||
cursor_texture_cache: Default::default(),
|
cursor_texture_cache: Default::default(),
|
||||||
cursor_shape_manager_state,
|
cursor_shape_manager_state,
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ pub mod gamma_control;
|
|||||||
pub mod mutter_x11_interop;
|
pub mod mutter_x11_interop;
|
||||||
pub mod output_management;
|
pub mod output_management;
|
||||||
pub mod screencopy;
|
pub mod screencopy;
|
||||||
|
pub mod virtual_pointer;
|
||||||
|
|
||||||
pub mod raw;
|
pub mod raw;
|
||||||
|
|||||||
@@ -0,0 +1,563 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use smithay::backend::input::{
|
||||||
|
AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, ButtonState, Device,
|
||||||
|
DeviceCapability, Event, InputBackend, PointerAxisEvent, PointerButtonEvent,
|
||||||
|
PointerMotionAbsoluteEvent, PointerMotionEvent, UnusedEvent,
|
||||||
|
};
|
||||||
|
use smithay::input::pointer::AxisFrame;
|
||||||
|
use smithay::output::Output;
|
||||||
|
use smithay::reexports::wayland_protocols_wlr;
|
||||||
|
use smithay::reexports::wayland_server::protocol::wl_pointer;
|
||||||
|
use smithay::reexports::wayland_server::protocol::wl_seat::WlSeat;
|
||||||
|
use smithay::reexports::wayland_server::{
|
||||||
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
|
||||||
|
};
|
||||||
|
use wayland_backend::protocol::WEnum;
|
||||||
|
use wayland_protocols_wlr::virtual_pointer::v1::server::{
|
||||||
|
zwlr_virtual_pointer_manager_v1, zwlr_virtual_pointer_v1,
|
||||||
|
};
|
||||||
|
use zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1;
|
||||||
|
use zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1;
|
||||||
|
|
||||||
|
const VERSION: u32 = 2;
|
||||||
|
|
||||||
|
pub struct VirtualPointerManagerState {
|
||||||
|
virtual_pointers: HashSet<ZwlrVirtualPointerV1>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualPointerManagerGlobalData {
|
||||||
|
filter: Box<dyn for<'c> Fn(&'c Client) -> bool + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualPointerInputBackend;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||||
|
pub struct VirtualPointer {
|
||||||
|
pointer: ZwlrVirtualPointerV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct VirtualPointerUserData {
|
||||||
|
seat: Option<WlSeat>,
|
||||||
|
output: Option<Output>,
|
||||||
|
|
||||||
|
axis_frame: Mutex<Option<AxisFrame>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualPointer {
|
||||||
|
fn data(&self) -> &VirtualPointerUserData {
|
||||||
|
self.pointer.data().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seat(&self) -> Option<&WlSeat> {
|
||||||
|
self.data().seat.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output(&self) -> Option<&Output> {
|
||||||
|
self.data().output.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish_axis_frame(&self) -> Option<AxisFrame> {
|
||||||
|
self.data().axis_frame.lock().unwrap().take()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mutate_axis_frame(&self, time: Option<u32>, f: impl FnOnce(AxisFrame) -> AxisFrame) {
|
||||||
|
let mut frame = self.data().axis_frame.lock().unwrap();
|
||||||
|
|
||||||
|
*frame = frame.or(time.map(AxisFrame::new)).map(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Device for VirtualPointer {
|
||||||
|
fn id(&self) -> String {
|
||||||
|
format!("wlr virtual pointer {}", self.pointer.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
String::from("virtual pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_capability(&self, capability: DeviceCapability) -> bool {
|
||||||
|
matches!(capability, DeviceCapability::Pointer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usb_id(&self) -> Option<(u32, u32)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn syspath(&self) -> Option<std::path::PathBuf> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualPointerMotionEvent {
|
||||||
|
pointer: VirtualPointer,
|
||||||
|
time: u32,
|
||||||
|
dx: f64,
|
||||||
|
dy: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event<VirtualPointerInputBackend> for VirtualPointerMotionEvent {
|
||||||
|
fn time(&self) -> u64 {
|
||||||
|
self.time as u64 * 1000 // millis to micros
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device(&self) -> VirtualPointer {
|
||||||
|
self.pointer.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerMotionEvent<VirtualPointerInputBackend> for VirtualPointerMotionEvent {
|
||||||
|
fn delta_x(&self) -> f64 {
|
||||||
|
self.dx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delta_y(&self) -> f64 {
|
||||||
|
self.dy
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delta_x_unaccel(&self) -> f64 {
|
||||||
|
self.dx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delta_y_unaccel(&self) -> f64 {
|
||||||
|
self.dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualPointerMotionAbsoluteEvent {
|
||||||
|
pointer: VirtualPointer,
|
||||||
|
time: u32,
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
x_extent: u32,
|
||||||
|
y_extent: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event<VirtualPointerInputBackend> for VirtualPointerMotionAbsoluteEvent {
|
||||||
|
fn time(&self) -> u64 {
|
||||||
|
self.time as u64 * 1000 // millis to micros
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device(&self) -> VirtualPointer {
|
||||||
|
self.pointer.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AbsolutePositionEvent<VirtualPointerInputBackend> for VirtualPointerMotionAbsoluteEvent {
|
||||||
|
fn x(&self) -> f64 {
|
||||||
|
self.x as f64 / self.x_extent as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y(&self) -> f64 {
|
||||||
|
self.y as f64 / self.y_extent as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn x_transformed(&self, width: i32) -> f64 {
|
||||||
|
(self.x as i64 * width as i64) as f64 / self.x_extent as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn y_transformed(&self, height: i32) -> f64 {
|
||||||
|
(self.y as i64 * height as i64) as f64 / self.y_extent as f64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualPointerButtonEvent {
|
||||||
|
pointer: VirtualPointer,
|
||||||
|
time: u32,
|
||||||
|
button: u32,
|
||||||
|
state: ButtonState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event<VirtualPointerInputBackend> for VirtualPointerButtonEvent {
|
||||||
|
fn time(&self) -> u64 {
|
||||||
|
self.time as u64 * 1000 // millis to micros
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device(&self) -> VirtualPointer {
|
||||||
|
self.pointer.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerButtonEvent<VirtualPointerInputBackend> for VirtualPointerButtonEvent {
|
||||||
|
fn button_code(&self) -> u32 {
|
||||||
|
self.button
|
||||||
|
}
|
||||||
|
|
||||||
|
fn state(&self) -> ButtonState {
|
||||||
|
self.state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VirtualPointerAxisEvent {
|
||||||
|
pointer: VirtualPointer,
|
||||||
|
frame: AxisFrame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Event<VirtualPointerInputBackend> for VirtualPointerAxisEvent {
|
||||||
|
fn time(&self) -> u64 {
|
||||||
|
self.frame.time as u64 * 1000 // millis to micros
|
||||||
|
}
|
||||||
|
|
||||||
|
fn device(&self) -> VirtualPointer {
|
||||||
|
self.pointer.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tuple_axis<T>(tuple: (T, T), axis: Axis) -> T {
|
||||||
|
match axis {
|
||||||
|
Axis::Horizontal => tuple.0,
|
||||||
|
Axis::Vertical => tuple.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerAxisEvent<VirtualPointerInputBackend> for VirtualPointerAxisEvent {
|
||||||
|
fn amount(&self, axis: Axis) -> Option<f64> {
|
||||||
|
Some(tuple_axis(self.frame.axis, axis))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn amount_v120(&self, axis: Axis) -> Option<f64> {
|
||||||
|
self.frame.v120.map(|v120| tuple_axis(v120, axis) as f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self) -> AxisSource {
|
||||||
|
self.frame.source.unwrap_or_else(|| {
|
||||||
|
warn!("AxisSource: no source set, giving bogus value");
|
||||||
|
AxisSource::Continuous
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn relative_direction(&self, axis: Axis) -> AxisRelativeDirection {
|
||||||
|
tuple_axis(self.frame.relative_direction, axis)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerMotionAbsoluteEvent<VirtualPointerInputBackend> for VirtualPointerMotionAbsoluteEvent {}
|
||||||
|
|
||||||
|
impl InputBackend for VirtualPointerInputBackend {
|
||||||
|
type Device = VirtualPointer;
|
||||||
|
|
||||||
|
type KeyboardKeyEvent = UnusedEvent;
|
||||||
|
type PointerAxisEvent = VirtualPointerAxisEvent;
|
||||||
|
type PointerButtonEvent = VirtualPointerButtonEvent;
|
||||||
|
type PointerMotionEvent = VirtualPointerMotionEvent;
|
||||||
|
type PointerMotionAbsoluteEvent = VirtualPointerMotionAbsoluteEvent;
|
||||||
|
|
||||||
|
type GestureSwipeBeginEvent = UnusedEvent;
|
||||||
|
type GestureSwipeUpdateEvent = UnusedEvent;
|
||||||
|
type GestureSwipeEndEvent = UnusedEvent;
|
||||||
|
type GesturePinchBeginEvent = UnusedEvent;
|
||||||
|
type GesturePinchUpdateEvent = UnusedEvent;
|
||||||
|
type GesturePinchEndEvent = UnusedEvent;
|
||||||
|
type GestureHoldBeginEvent = UnusedEvent;
|
||||||
|
type GestureHoldEndEvent = UnusedEvent;
|
||||||
|
|
||||||
|
type TouchDownEvent = UnusedEvent;
|
||||||
|
type TouchUpEvent = UnusedEvent;
|
||||||
|
type TouchMotionEvent = UnusedEvent;
|
||||||
|
type TouchCancelEvent = UnusedEvent;
|
||||||
|
type TouchFrameEvent = UnusedEvent;
|
||||||
|
type TabletToolAxisEvent = UnusedEvent;
|
||||||
|
type TabletToolProximityEvent = UnusedEvent;
|
||||||
|
type TabletToolTipEvent = UnusedEvent;
|
||||||
|
type TabletToolButtonEvent = UnusedEvent;
|
||||||
|
|
||||||
|
type SwitchToggleEvent = UnusedEvent;
|
||||||
|
|
||||||
|
type SpecialEvent = UnusedEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait VirtualPointerHandler {
|
||||||
|
fn virtual_pointer_manager_state(&mut self) -> &mut VirtualPointerManagerState;
|
||||||
|
|
||||||
|
fn create_virtual_pointer(&mut self, pointer: VirtualPointer) {
|
||||||
|
let _ = pointer;
|
||||||
|
}
|
||||||
|
fn destroy_virtual_pointer(&mut self, pointer: VirtualPointer) {
|
||||||
|
let _ = pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_virtual_pointer_motion(&mut self, event: VirtualPointerMotionEvent);
|
||||||
|
fn on_virtual_pointer_motion_absolute(&mut self, event: VirtualPointerMotionAbsoluteEvent);
|
||||||
|
fn on_virtual_pointer_button(&mut self, event: VirtualPointerButtonEvent);
|
||||||
|
fn on_virtual_pointer_axis(&mut self, event: VirtualPointerAxisEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VirtualPointerManagerState {
|
||||||
|
pub fn new<D, F>(display: &DisplayHandle, filter: F) -> Self
|
||||||
|
where
|
||||||
|
D: GlobalDispatch<ZwlrVirtualPointerManagerV1, VirtualPointerManagerGlobalData>,
|
||||||
|
D: Dispatch<ZwlrVirtualPointerManagerV1, ()>,
|
||||||
|
D: Dispatch<ZwlrVirtualPointerV1, VirtualPointerUserData>,
|
||||||
|
D: VirtualPointerHandler,
|
||||||
|
D: 'static,
|
||||||
|
F: for<'c> Fn(&'c Client) -> bool + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let global_data = VirtualPointerManagerGlobalData {
|
||||||
|
filter: Box::new(filter),
|
||||||
|
};
|
||||||
|
display.create_global::<D, ZwlrVirtualPointerManagerV1, _>(VERSION, global_data);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
virtual_pointers: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> GlobalDispatch<ZwlrVirtualPointerManagerV1, VirtualPointerManagerGlobalData, D>
|
||||||
|
for VirtualPointerManagerState
|
||||||
|
where
|
||||||
|
D: GlobalDispatch<ZwlrVirtualPointerManagerV1, VirtualPointerManagerGlobalData>,
|
||||||
|
D: Dispatch<ZwlrVirtualPointerManagerV1, ()>,
|
||||||
|
D: Dispatch<ZwlrVirtualPointerV1, VirtualPointerUserData>,
|
||||||
|
D: VirtualPointerHandler,
|
||||||
|
D: 'static,
|
||||||
|
{
|
||||||
|
fn bind(
|
||||||
|
_state: &mut D,
|
||||||
|
_handle: &DisplayHandle,
|
||||||
|
_client: &Client,
|
||||||
|
manager: New<ZwlrVirtualPointerManagerV1>,
|
||||||
|
_manager_state: &VirtualPointerManagerGlobalData,
|
||||||
|
data_init: &mut DataInit<'_, D>,
|
||||||
|
) {
|
||||||
|
data_init.init(manager, ());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_view(client: Client, global_data: &VirtualPointerManagerGlobalData) -> bool {
|
||||||
|
(global_data.filter)(&client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Dispatch<ZwlrVirtualPointerManagerV1, (), D> for VirtualPointerManagerState
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrVirtualPointerManagerV1, ()>,
|
||||||
|
D: Dispatch<ZwlrVirtualPointerV1, VirtualPointerUserData>,
|
||||||
|
D: VirtualPointerHandler,
|
||||||
|
D: 'static,
|
||||||
|
{
|
||||||
|
fn request(
|
||||||
|
state: &mut D,
|
||||||
|
_client: &Client,
|
||||||
|
_resource: &ZwlrVirtualPointerManagerV1,
|
||||||
|
request: <ZwlrVirtualPointerManagerV1 as Resource>::Request,
|
||||||
|
_data: &(),
|
||||||
|
_dhandle: &DisplayHandle,
|
||||||
|
data_init: &mut DataInit<'_, D>,
|
||||||
|
) {
|
||||||
|
let (id, seat, output) = match request {
|
||||||
|
zwlr_virtual_pointer_manager_v1::Request::CreateVirtualPointer { seat, id } => {
|
||||||
|
(id, seat, None)
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_manager_v1::Request::CreateVirtualPointerWithOutput {
|
||||||
|
seat,
|
||||||
|
output,
|
||||||
|
id,
|
||||||
|
} => (id, seat, output.as_ref().and_then(Output::from_resource)),
|
||||||
|
zwlr_virtual_pointer_manager_v1::Request::Destroy => return,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pointer = data_init.init(
|
||||||
|
id,
|
||||||
|
VirtualPointerUserData {
|
||||||
|
seat,
|
||||||
|
output,
|
||||||
|
axis_frame: Mutex::new(None),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
state
|
||||||
|
.virtual_pointer_manager_state()
|
||||||
|
.virtual_pointers
|
||||||
|
.insert(pointer.clone());
|
||||||
|
|
||||||
|
state.create_virtual_pointer(VirtualPointer { pointer });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Dispatch<ZwlrVirtualPointerV1, VirtualPointerUserData, D> for VirtualPointerManagerState
|
||||||
|
where
|
||||||
|
D: Dispatch<ZwlrVirtualPointerV1, VirtualPointerUserData>,
|
||||||
|
D: VirtualPointerHandler,
|
||||||
|
D: 'static,
|
||||||
|
{
|
||||||
|
fn request(
|
||||||
|
handler: &mut D,
|
||||||
|
_client: &Client,
|
||||||
|
resource: &ZwlrVirtualPointerV1,
|
||||||
|
request: <ZwlrVirtualPointerV1 as Resource>::Request,
|
||||||
|
_data: &VirtualPointerUserData,
|
||||||
|
_dhandle: &DisplayHandle,
|
||||||
|
_data_init: &mut DataInit<'_, D>,
|
||||||
|
) {
|
||||||
|
let pointer = VirtualPointer {
|
||||||
|
pointer: resource.clone(),
|
||||||
|
};
|
||||||
|
match request {
|
||||||
|
zwlr_virtual_pointer_v1::Request::Motion { time, dx, dy } => {
|
||||||
|
let event = VirtualPointerMotionEvent {
|
||||||
|
pointer,
|
||||||
|
time,
|
||||||
|
dx,
|
||||||
|
dy,
|
||||||
|
};
|
||||||
|
handler.on_virtual_pointer_motion(event);
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::MotionAbsolute {
|
||||||
|
time,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
x_extent,
|
||||||
|
y_extent,
|
||||||
|
} => {
|
||||||
|
let event = VirtualPointerMotionAbsoluteEvent {
|
||||||
|
pointer,
|
||||||
|
time,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
x_extent,
|
||||||
|
y_extent,
|
||||||
|
};
|
||||||
|
handler.on_virtual_pointer_motion_absolute(event);
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::Button {
|
||||||
|
time,
|
||||||
|
button,
|
||||||
|
state,
|
||||||
|
} => {
|
||||||
|
// state is an enum but wlroots treats it as a C boolean (zero or nonzero)
|
||||||
|
// so we emulate that behaviour too. ButtonState::Pressed and any invalid value
|
||||||
|
// counts as pressed.
|
||||||
|
// https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/3187479c07c34a4de82c06a316a763a36a0499da/types/wlr_virtual_pointer_v1.c#L74
|
||||||
|
let state = match state {
|
||||||
|
WEnum::Value(wl_pointer::ButtonState::Released) => ButtonState::Released,
|
||||||
|
_ => ButtonState::Pressed,
|
||||||
|
};
|
||||||
|
let event = VirtualPointerButtonEvent {
|
||||||
|
pointer,
|
||||||
|
time,
|
||||||
|
button,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
handler.on_virtual_pointer_button(event);
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::Axis { time, axis, value } => {
|
||||||
|
let axis = match axis {
|
||||||
|
WEnum::Value(wl_pointer::Axis::VerticalScroll) => Axis::Vertical,
|
||||||
|
WEnum::Value(wl_pointer::Axis::HorizontalScroll) => Axis::Horizontal,
|
||||||
|
_ => {
|
||||||
|
warn!("Axis: invalid axis");
|
||||||
|
resource.post_error(
|
||||||
|
zwlr_virtual_pointer_v1::Error::InvalidAxis,
|
||||||
|
"invalid axis",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pointer.mutate_axis_frame(Some(time), |frame| frame.value(axis, value));
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::Frame => {
|
||||||
|
if let Some(frame) = pointer.finish_axis_frame() {
|
||||||
|
let event = VirtualPointerAxisEvent { pointer, frame };
|
||||||
|
handler.on_virtual_pointer_axis(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::AxisSource { axis_source } => {
|
||||||
|
let axis_source = match axis_source {
|
||||||
|
WEnum::Value(wl_pointer::AxisSource::Wheel) => AxisSource::Wheel,
|
||||||
|
WEnum::Value(wl_pointer::AxisSource::Finger) => AxisSource::Finger,
|
||||||
|
WEnum::Value(wl_pointer::AxisSource::Continuous) => AxisSource::Continuous,
|
||||||
|
WEnum::Value(wl_pointer::AxisSource::WheelTilt) => AxisSource::WheelTilt,
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
warn!("AxisSource: invalid axis source");
|
||||||
|
resource.post_error(
|
||||||
|
zwlr_virtual_pointer_v1::Error::InvalidAxisSource,
|
||||||
|
"invalid axis source",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pointer.mutate_axis_frame(None, |frame| frame.source(axis_source));
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::AxisStop { time, axis } => {
|
||||||
|
let axis = match axis {
|
||||||
|
WEnum::Value(wl_pointer::Axis::VerticalScroll) => Axis::Vertical,
|
||||||
|
WEnum::Value(wl_pointer::Axis::HorizontalScroll) => Axis::Horizontal,
|
||||||
|
_ => {
|
||||||
|
warn!("AxisStop: invalid axis");
|
||||||
|
resource.post_error(
|
||||||
|
zwlr_virtual_pointer_v1::Error::InvalidAxis,
|
||||||
|
"invalid axis",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pointer.mutate_axis_frame(Some(time), |frame| frame.stop(axis));
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::AxisDiscrete {
|
||||||
|
time,
|
||||||
|
axis,
|
||||||
|
value,
|
||||||
|
discrete,
|
||||||
|
} => {
|
||||||
|
let axis = match axis {
|
||||||
|
WEnum::Value(wl_pointer::Axis::VerticalScroll) => Axis::Vertical,
|
||||||
|
WEnum::Value(wl_pointer::Axis::HorizontalScroll) => Axis::Horizontal,
|
||||||
|
_ => {
|
||||||
|
warn!("AxisDiscrete: invalid axis");
|
||||||
|
resource.post_error(
|
||||||
|
zwlr_virtual_pointer_v1::Error::InvalidAxis,
|
||||||
|
"invalid axis",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pointer.mutate_axis_frame(Some(time), |frame| {
|
||||||
|
frame.value(axis, value).v120(axis, discrete)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
zwlr_virtual_pointer_v1::Request::Destroy => {}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroyed(
|
||||||
|
handler: &mut D,
|
||||||
|
_client: wayland_backend::server::ClientId,
|
||||||
|
resource: &ZwlrVirtualPointerV1,
|
||||||
|
_data: &VirtualPointerUserData,
|
||||||
|
) {
|
||||||
|
let pointer = VirtualPointer {
|
||||||
|
pointer: resource.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
handler.destroy_virtual_pointer(pointer);
|
||||||
|
handler
|
||||||
|
.virtual_pointer_manager_state()
|
||||||
|
.virtual_pointers
|
||||||
|
.remove(resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! delegate_virtual_pointer {
|
||||||
|
($(@<$( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+>)? $ty: ty) => {
|
||||||
|
smithay::reexports::wayland_server::delegate_global_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||||
|
smithay::reexports::wayland_protocols_wlr::virtual_pointer::v1::server::zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1: $crate::protocols::virtual_pointer::VirtualPointerManagerGlobalData
|
||||||
|
] => $crate::protocols::virtual_pointer::VirtualPointerManagerState);
|
||||||
|
|
||||||
|
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||||
|
smithay::reexports::wayland_protocols_wlr::virtual_pointer::v1::server::zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1: ()
|
||||||
|
] => $crate::protocols::virtual_pointer::VirtualPointerManagerState);
|
||||||
|
|
||||||
|
smithay::reexports::wayland_server::delegate_dispatch!($(@< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $ty: [
|
||||||
|
smithay::reexports::wayland_protocols_wlr::virtual_pointer::v1::server::zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1: $crate::protocols::virtual_pointer::VirtualPointerUserData
|
||||||
|
] => $crate::protocols::virtual_pointer::VirtualPointerManagerState);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user