Files
feresaria/vendor/smithay/anvil/src/input_handler.rs
T
aria 993bc0f88f Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by
  other input, focus changes, grabs, locks, and session teardown.
- Ship the native ferese-rime user service, Rime defaults, installer
  inventory, package dependencies, session environment, and docs.
- Vendor Smithay 8a27023 with the SHM texture-cache format fix and add
  GPU regressions for format changes and resize snapshots.
- Release reused CRTC owners before staging output modes, then restore
  changed outputs before former owners when a transaction fails.
- Wait for resize-capture GPU completion, expand filtered surface
  damage, and preserve IME popup origin under fractional scaling.
- Mark weekends like holidays in both calendar layouts.
2026-10-30 03:13:43 +09:00

1458 lines
58 KiB
Rust

use std::{convert::TryInto, process::Command, sync::atomic::Ordering};
use crate::{AnvilState, focus::PointerFocusTarget, shell::FullscreenSurface};
#[cfg(feature = "udev")]
use crate::udev::UdevData;
#[cfg(feature = "udev")]
use smithay::{backend::renderer::DebugFlags, input::tablet};
use smithay::{
backend::input::{
self, Axis, AxisSource, Device, DeviceCapability, Event, GestureBeginEvent, GestureEndEvent,
GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _, InputBackend, InputEvent, InputTime,
KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, TouchEvent,
},
desktop::{WindowSurfaceType, layer_map_for_output},
input::{
keyboard::{FilterResult, Keysym, ModifiersState, keysyms as xkb},
pointer::{AxisFrame, ButtonEvent, MotionEvent},
pointer::{
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
},
tablet::{TabletDescriptor, TabletSeatTrait},
touch::{DownEvent, UpEvent},
},
output::Scale,
reexports::{
wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1,
wayland_server::protocol::wl_pointer,
},
utils::{Logical, Point, SERIAL_COUNTER as SCOUNTER, Serial, Transform},
wayland::{
input_method::InputMethodSeat,
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat,
shell::wlr_layer::{KeyboardInteractivity, Layer as WlrLayer},
virtual_keyboard::{VirtualKeyboardBackend, VirtualKeyboardHandler, VirtualKeyboardSpecialEvent},
},
};
use smithay::backend::input::AbsolutePositionEvent;
#[cfg(any(feature = "winit", feature = "x11"))]
use smithay::output::Output;
use tracing::{debug, error, info, warn};
use crate::state::Backend;
#[cfg(feature = "udev")]
use smithay::{
backend::{
input::{
PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState,
},
session::Session,
},
input::pointer::RelativeMotionEvent,
reexports::wayland_server::DisplayHandle,
wayland::{
pointer_constraints::{PointerConstraint, with_pointer_constraint},
seat::WaylandFocus,
},
};
impl<BackendData: Backend> AnvilState<BackendData> {
// Allow in this method because of existing usage
#[allow(clippy::uninlined_format_args)]
fn process_common_key_action(&mut self, action: KeyAction) {
match action {
KeyAction::None => (),
KeyAction::Quit => {
info!("Quitting.");
self.running.store(false, Ordering::SeqCst);
}
KeyAction::Run(cmd) => {
info!(cmd, "Starting program");
if let Err(e) = Command::new(&cmd)
.envs(
self.socket_name
.clone()
.map(|v| ("WAYLAND_DISPLAY", v))
.into_iter()
.chain(
#[cfg(feature = "xwayland")]
self.xdisplay.map(|v| ("DISPLAY", format!(":{v}"))),
#[cfg(not(feature = "xwayland"))]
None,
),
)
.spawn()
{
error!(cmd, err = %e, "Failed to start program");
}
}
KeyAction::TogglePreview => {
self.show_window_preview = !self.show_window_preview;
}
KeyAction::ToggleDecorations => {
for element in self.space.elements() {
#[allow(irrefutable_let_patterns)]
if let Some(toplevel) = element.0.toplevel() {
let mode_changed = toplevel.with_pending_state(|state| {
if let Some(current_mode) = state.decoration_mode {
let new_mode =
if current_mode == zxdg_toplevel_decoration_v1::Mode::ClientSide {
zxdg_toplevel_decoration_v1::Mode::ServerSide
} else {
zxdg_toplevel_decoration_v1::Mode::ClientSide
};
state.decoration_mode = Some(new_mode);
true
} else {
false
}
});
if mode_changed && toplevel.is_initial_configure_sent() {
toplevel.send_pending_configure();
}
}
}
}
_ => unreachable!(
"Common key action handler encountered backend specific action {:?}",
action
),
}
}
fn keyboard_key_to_action<B: InputBackend>(&mut self, evt: B::KeyboardKeyEvent) -> KeyAction {
let keycode = evt.key_code();
let state = evt.state();
debug!(?keycode, ?state, "key");
let serial = SCOUNTER.next_serial();
let time = Event::time(&evt);
let mut suppressed_keys = self.suppressed_keys.clone();
let keyboard = self.seat.get_keyboard().unwrap();
for layer in self.layer_shell_state.layer_surfaces().rev() {
let exclusive = layer.with_cached_state(|data| {
data.keyboard_interactivity == KeyboardInteractivity::Exclusive
&& (data.layer == WlrLayer::Top || data.layer == WlrLayer::Overlay)
});
if exclusive {
let surface = self.space.outputs().find_map(|o| {
let map = layer_map_for_output(o);
map.layers().find(|l| l.layer_surface() == &layer).cloned()
});
if let Some(surface) = surface {
keyboard.set_focus(self, Some(surface.into()), serial);
keyboard.input::<(), _>(self, keycode, state, serial, time, |_, _, _| {
FilterResult::Forward
});
return KeyAction::None;
};
}
}
let inhibited = self
.space
.element_under(self.pointer.current_location())
.and_then(|(window, _)| {
let surface = window.wl_surface()?;
self.seat.keyboard_shortcuts_inhibitor_for_surface(&surface)
})
.map(|inhibitor| inhibitor.is_active())
.unwrap_or(false);
let action = keyboard
.input(self, keycode, state, serial, time, |_, modifiers, handle| {
let keysym = handle.modified_sym();
debug!(
?state,
mods = ?modifiers,
keysym = ::xkbcommon::xkb::keysym_get_name(keysym),
"keysym"
);
// If the key is pressed and triggered a action
// we will not forward the key to the client.
// Additionally add the key to the suppressed keys
// so that we can decide on a release if the key
// should be forwarded to the client or not.
if let KeyState::Pressed = state {
if !inhibited {
let action = process_keyboard_shortcut(*modifiers, keysym);
if action.is_some() {
suppressed_keys.push(keysym);
}
action
.map(FilterResult::Intercept)
.unwrap_or(FilterResult::Forward)
} else {
FilterResult::Forward
}
} else {
let suppressed = suppressed_keys.contains(&keysym);
if suppressed {
suppressed_keys.retain(|k| *k != keysym);
FilterResult::Intercept(KeyAction::None)
} else {
FilterResult::Forward
}
}
})
.unwrap_or(KeyAction::None);
self.suppressed_keys = suppressed_keys;
action
}
fn on_pointer_button<B: InputBackend>(&mut self, evt: B::PointerButtonEvent) {
let serial = SCOUNTER.next_serial();
let button = evt.button_code();
let state = wl_pointer::ButtonState::from(evt.state());
if wl_pointer::ButtonState::Pressed == state {
self.update_keyboard_focus(self.pointer.current_location(), serial);
};
let pointer = self.pointer.clone();
pointer.button(
self,
&ButtonEvent {
button,
state: state.try_into().unwrap(),
serial,
time: evt.time(),
},
);
pointer.frame(self);
}
fn on_gesture_swipe_begin<B: InputBackend>(&mut self, evt: B::GestureSwipeBeginEvent) {
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
pointer.gesture_swipe_begin(
self,
&GestureSwipeBeginEvent {
serial,
time: evt.time(),
fingers: evt.fingers(),
},
);
}
fn on_gesture_swipe_update<B: InputBackend>(&mut self, evt: B::GestureSwipeUpdateEvent) {
let pointer = self.pointer.clone();
pointer.gesture_swipe_update(
self,
&GestureSwipeUpdateEvent {
time: evt.time(),
delta: evt.delta(),
},
);
}
fn on_gesture_swipe_end<B: InputBackend>(&mut self, evt: B::GestureSwipeEndEvent) {
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
pointer.gesture_swipe_end(
self,
&GestureSwipeEndEvent {
serial,
time: evt.time(),
cancelled: evt.cancelled(),
},
);
}
fn on_gesture_pinch_begin<B: InputBackend>(&mut self, evt: B::GesturePinchBeginEvent) {
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
pointer.gesture_pinch_begin(
self,
&GesturePinchBeginEvent {
serial,
time: evt.time(),
fingers: evt.fingers(),
},
);
}
fn on_gesture_pinch_update<B: InputBackend>(&mut self, evt: B::GesturePinchUpdateEvent) {
let pointer = self.pointer.clone();
pointer.gesture_pinch_update(
self,
&GesturePinchUpdateEvent {
time: evt.time(),
delta: evt.delta(),
scale: evt.scale(),
rotation: evt.rotation(),
},
);
}
fn on_gesture_pinch_end<B: InputBackend>(&mut self, evt: B::GesturePinchEndEvent) {
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
pointer.gesture_pinch_end(
self,
&GesturePinchEndEvent {
serial,
time: evt.time(),
cancelled: evt.cancelled(),
},
);
}
fn on_gesture_hold_begin<B: InputBackend>(&mut self, evt: B::GestureHoldBeginEvent) {
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
pointer.gesture_hold_begin(
self,
&GestureHoldBeginEvent {
serial,
time: evt.time(),
fingers: evt.fingers(),
},
);
}
fn on_gesture_hold_end<B: InputBackend>(&mut self, evt: B::GestureHoldEndEvent) {
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
pointer.gesture_hold_end(
self,
&GestureHoldEndEvent {
serial,
time: evt.time(),
cancelled: evt.cancelled(),
},
);
}
fn update_keyboard_focus(&mut self, location: Point<f64, Logical>, serial: Serial) {
let keyboard = self.seat.get_keyboard().unwrap();
let touch = self.seat.get_touch();
let input_method = self.seat.input_method();
// change the keyboard focus unless the pointer or keyboard is grabbed
// We test for any matching surface type here but always use the root
// (in case of a window the toplevel) surface for the focus.
// So for example if a user clicks on a subsurface or popup the toplevel
// will receive the keyboard focus. Directly assigning the focus to the
// matching surface leads to issues with clients dismissing popups and
// subsurface menus (for example firefox-wayland).
// see here for a discussion about that issue:
// https://gitlab.freedesktop.org/wayland/wayland/-/issues/294
if !self.pointer.is_grabbed()
&& (!keyboard.is_grabbed() || input_method.keyboard_grabbed())
&& !touch.map(|touch| touch.is_grabbed()).unwrap_or(false)
{
let output = self.space.output_under(location).next().cloned();
if let Some(output) = output.as_ref() {
let output_geo = self.space.output_geometry(output).unwrap();
if let Some(window) = output
.user_data()
.get::<FullscreenSurface>()
.and_then(|f| f.get())
{
if let Some((_, _)) =
window.surface_under(location - output_geo.loc.to_f64(), WindowSurfaceType::ALL)
{
#[cfg(feature = "xwayland")]
if let Some(surface) = window.0.x11_surface() {
self.xwm.as_mut().unwrap().raise_window(surface).unwrap();
}
keyboard.set_focus(self, Some(window.into()), serial);
return;
}
}
let layers = layer_map_for_output(output);
if let Some(layer) = layers
.layer_under(WlrLayer::Overlay, location - output_geo.loc.to_f64())
.or_else(|| layers.layer_under(WlrLayer::Top, location - output_geo.loc.to_f64()))
{
if layer.can_receive_keyboard_focus() {
if let Some((_, _)) = layer.surface_under(
location
- output_geo.loc.to_f64()
- layers.layer_geometry(layer).unwrap().loc.to_f64(),
WindowSurfaceType::ALL,
) {
keyboard.set_focus(self, Some(layer.clone().into()), serial);
return;
}
}
}
}
if let Some((window, _)) = self.space.element_under(location).map(|(w, p)| (w.clone(), p)) {
self.space.raise_element(&window, true);
#[cfg(feature = "xwayland")]
if let Some(surface) = window.0.x11_surface() {
self.xwm.as_mut().unwrap().raise_window(surface).unwrap();
}
keyboard.set_focus(self, Some(window.into()), serial);
return;
}
if let Some(output) = output.as_ref() {
let output_geo = self.space.output_geometry(output).unwrap();
let layers = layer_map_for_output(output);
if let Some(layer) = layers
.layer_under(WlrLayer::Bottom, location - output_geo.loc.to_f64())
.or_else(|| layers.layer_under(WlrLayer::Background, location - output_geo.loc.to_f64()))
{
if layer.can_receive_keyboard_focus() {
if let Some((_, _)) = layer.surface_under(
location
- output_geo.loc.to_f64()
- layers.layer_geometry(layer).unwrap().loc.to_f64(),
WindowSurfaceType::ALL,
) {
keyboard.set_focus(self, Some(layer.clone().into()), serial);
}
}
}
};
}
}
pub fn surface_under(
&self,
pos: Point<f64, Logical>,
) -> Option<(PointerFocusTarget, Point<f64, Logical>)> {
let output = self.space.outputs().find(|o| {
let geometry = self.space.output_geometry(o).unwrap();
geometry.contains(pos.to_i32_round())
})?;
let output_geo = self.space.output_geometry(output).unwrap();
let layers = layer_map_for_output(output);
let mut under = None;
if let Some((surface, loc)) = output
.user_data()
.get::<FullscreenSurface>()
.and_then(|f| f.get())
.and_then(|w| w.surface_under(pos - output_geo.loc.to_f64(), WindowSurfaceType::ALL))
{
under = Some((surface, loc + output_geo.loc));
} else if let Some(focus) = layers
.layer_under(WlrLayer::Overlay, pos - output_geo.loc.to_f64())
.or_else(|| layers.layer_under(WlrLayer::Top, pos - output_geo.loc.to_f64()))
.and_then(|layer| {
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(surface, loc)| {
(
PointerFocusTarget::from(surface),
loc + layer_loc + output_geo.loc,
)
})
})
{
under = Some(focus)
} else if let Some(focus) = self.space.element_under(pos).and_then(|(window, loc)| {
window
.surface_under(pos - loc.to_f64(), WindowSurfaceType::ALL)
.map(|(surface, surf_loc)| (surface, surf_loc + loc))
}) {
under = Some(focus);
} else if let Some(focus) = layers
.layer_under(WlrLayer::Bottom, pos - output_geo.loc.to_f64())
.or_else(|| layers.layer_under(WlrLayer::Background, pos - output_geo.loc.to_f64()))
.and_then(|layer| {
let layer_loc = layers.layer_geometry(layer).unwrap().loc;
layer
.surface_under(
pos - output_geo.loc.to_f64() - layer_loc.to_f64(),
WindowSurfaceType::ALL,
)
.map(|(surface, loc)| {
(
PointerFocusTarget::from(surface),
loc + layer_loc + output_geo.loc,
)
})
})
{
under = Some(focus)
};
under.map(|(s, l)| (s, l.to_f64()))
}
fn on_pointer_axis<B: InputBackend>(&mut self, evt: B::PointerAxisEvent) {
let horizontal_amount = evt
.amount(input::Axis::Horizontal)
.unwrap_or_else(|| evt.amount_v120(input::Axis::Horizontal).unwrap_or(0.0) * 15.0 / 120.);
let vertical_amount = evt
.amount(input::Axis::Vertical)
.unwrap_or_else(|| evt.amount_v120(input::Axis::Vertical).unwrap_or(0.0) * 15.0 / 120.);
let horizontal_amount_discrete = evt.amount_v120(input::Axis::Horizontal);
let vertical_amount_discrete = evt.amount_v120(input::Axis::Vertical);
{
let mut frame = AxisFrame::new(evt.time()).source(evt.source());
if horizontal_amount != 0.0 {
frame = frame.relative_direction(Axis::Horizontal, evt.relative_direction(Axis::Horizontal));
frame = frame.value(Axis::Horizontal, horizontal_amount);
if let Some(discrete) = horizontal_amount_discrete {
frame = frame.v120(Axis::Horizontal, discrete as i32);
}
}
if vertical_amount != 0.0 {
frame = frame.relative_direction(Axis::Vertical, evt.relative_direction(Axis::Vertical));
frame = frame.value(Axis::Vertical, vertical_amount);
if let Some(discrete) = vertical_amount_discrete {
frame = frame.v120(Axis::Vertical, discrete as i32);
}
}
if evt.source() == AxisSource::Finger {
if evt.amount(Axis::Horizontal) == Some(0.0) {
frame = frame.stop(Axis::Horizontal);
}
if evt.amount(Axis::Vertical) == Some(0.0) {
frame = frame.stop(Axis::Vertical);
}
}
let pointer = self.pointer.clone();
pointer.axis(self, frame);
pointer.frame(self);
}
}
fn touch_location_transformed<B: InputBackend, E: AbsolutePositionEvent<B>>(
&self,
evt: &E,
) -> Option<Point<f64, Logical>> {
let output = self
.space
.outputs()
.find(|output| output.name().starts_with("eDP"))
.or_else(|| self.space.outputs().next());
let output = output?;
let output_geometry = self.space.output_geometry(output)?;
let transform = output.current_transform();
let size = transform.invert().transform_size(output_geometry.size);
Some(
transform.transform_point_in(evt.position_transformed(size), &size.to_f64())
+ output_geometry.loc.to_f64(),
)
}
fn on_touch_down<B: InputBackend>(&mut self, evt: B::TouchDownEvent) {
let Some(handle) = self.seat.get_touch() else {
return;
};
let Some(touch_location) = self.touch_location_transformed(&evt) else {
return;
};
let serial = SCOUNTER.next_serial();
self.update_keyboard_focus(touch_location, serial);
let under = self.surface_under(touch_location);
handle.down(
self,
under,
&DownEvent {
slot: evt.slot(),
location: touch_location,
serial,
time: evt.time(),
},
);
}
fn on_touch_up<B: InputBackend>(&mut self, evt: B::TouchUpEvent) {
let Some(handle) = self.seat.get_touch() else {
return;
};
let serial = SCOUNTER.next_serial();
handle.up(
self,
&UpEvent {
slot: evt.slot(),
serial,
time: evt.time(),
},
)
}
fn on_touch_motion<B: InputBackend>(&mut self, evt: B::TouchMotionEvent) {
let Some(handle) = self.seat.get_touch() else {
return;
};
let Some(touch_location) = self.touch_location_transformed(&evt) else {
return;
};
let under = self.surface_under(touch_location);
handle.motion(
self,
under,
&smithay::input::touch::MotionEvent {
slot: evt.slot(),
location: touch_location,
time: evt.time(),
},
);
}
fn on_touch_frame<B: InputBackend>(&mut self, _evt: B::TouchFrameEvent) {
let Some(handle) = self.seat.get_touch() else {
return;
};
handle.frame(self);
}
fn on_touch_cancel<B: InputBackend>(&mut self, _evt: B::TouchCancelEvent) {
let Some(handle) = self.seat.get_touch() else {
return;
};
handle.cancel(self);
}
fn on_device_added<B: InputBackend>(&mut self, device: B::Device) {
let dh = &self.display_handle;
if device.has_capability(DeviceCapability::TabletTool) {
self.seat
.tablet_seat()
.add_wp_tablet(dh, &TabletDescriptor::from(&device));
}
if device.has_capability(DeviceCapability::Touch) && self.seat.get_touch().is_none() {
self.seat.add_touch();
}
}
fn on_device_removed<B: InputBackend>(&mut self, device: B::Device) {
if device.has_capability(DeviceCapability::TabletTool) {
let tablet_seat = self.seat.tablet_seat();
tablet_seat.remove_tablet(&TabletDescriptor::from(&device));
// If there are no tablets in seat we can remove all tools
if tablet_seat.count_tablets() == 0 {
tablet_seat.clear_tools();
}
}
}
}
#[cfg(any(feature = "winit", feature = "x11"))]
impl<BackendData: Backend> AnvilState<BackendData> {
pub fn process_input_event_windowed<B: InputBackend>(&mut self, event: InputEvent<B>, output_name: &str) {
match event {
InputEvent::Keyboard { event } => match self.keyboard_key_to_action::<B>(event) {
KeyAction::ScaleUp => {
let output = self
.space
.outputs()
.find(|o| o.name() == output_name)
.unwrap()
.clone();
let current_scale = output.current_scale().fractional_scale();
let new_scale = current_scale + 0.25;
output.change_current_state(None, None, Some(Scale::Fractional(new_scale)), None);
crate::shell::fixup_positions(&mut self.space, self.pointer.current_location());
self.backend_data.reset_buffers(&output);
}
KeyAction::ScaleDown => {
let output = self
.space
.outputs()
.find(|o| o.name() == output_name)
.unwrap()
.clone();
let current_scale = output.current_scale().fractional_scale();
let new_scale = f64::max(1.0, current_scale - 0.25);
output.change_current_state(None, None, Some(Scale::Fractional(new_scale)), None);
crate::shell::fixup_positions(&mut self.space, self.pointer.current_location());
self.backend_data.reset_buffers(&output);
}
KeyAction::RotateOutput => {
let output = self
.space
.outputs()
.find(|o| o.name() == output_name)
.unwrap()
.clone();
let current_transform = output.current_transform();
let new_transform = match current_transform {
Transform::Normal => Transform::_90,
Transform::_90 => Transform::_180,
Transform::_180 => Transform::_270,
Transform::_270 => Transform::Flipped,
Transform::Flipped => Transform::Flipped90,
Transform::Flipped90 => Transform::Flipped180,
Transform::Flipped180 => Transform::Flipped270,
Transform::Flipped270 => Transform::Normal,
};
tracing::info!(?current_transform, ?new_transform, output = ?output.name(), "changing output transform");
output.change_current_state(None, Some(new_transform), None, None);
crate::shell::fixup_positions(&mut self.space, self.pointer.current_location());
self.backend_data.reset_buffers(&output);
}
action => match action {
KeyAction::None
| KeyAction::Quit
| KeyAction::Run(_)
| KeyAction::TogglePreview
| KeyAction::ToggleDecorations => self.process_common_key_action(action),
_ => tracing::warn!(
?action,
output_name,
"Key action unsupported on on output backend.",
),
},
},
InputEvent::PointerMotionAbsolute { event } => {
let output = self
.space
.outputs()
.find(|o| o.name() == output_name)
.unwrap()
.clone();
self.on_pointer_move_absolute_windowed::<B>(event, &output)
}
InputEvent::PointerButton { event } => self.on_pointer_button::<B>(event),
InputEvent::PointerAxis { event } => self.on_pointer_axis::<B>(event),
InputEvent::GestureSwipeBegin { event, .. } => self.on_gesture_swipe_begin::<B>(event),
InputEvent::GestureSwipeUpdate { event, .. } => self.on_gesture_swipe_update::<B>(event),
InputEvent::GestureSwipeEnd { event, .. } => self.on_gesture_swipe_end::<B>(event),
InputEvent::GesturePinchBegin { event, .. } => self.on_gesture_pinch_begin::<B>(event),
InputEvent::GesturePinchUpdate { event, .. } => self.on_gesture_pinch_update::<B>(event),
InputEvent::GesturePinchEnd { event, .. } => self.on_gesture_pinch_end::<B>(event),
InputEvent::GestureHoldBegin { event, .. } => self.on_gesture_hold_begin::<B>(event),
InputEvent::GestureHoldEnd { event, .. } => self.on_gesture_hold_end::<B>(event),
InputEvent::TouchDown { event } => self.on_touch_down::<B>(event),
InputEvent::TouchUp { event } => self.on_touch_up::<B>(event),
InputEvent::TouchMotion { event } => self.on_touch_motion::<B>(event),
InputEvent::TouchFrame { event } => self.on_touch_frame::<B>(event),
InputEvent::TouchCancel { event } => self.on_touch_cancel::<B>(event),
InputEvent::DeviceAdded { device } => self.on_device_added::<B>(device),
InputEvent::DeviceRemoved { device } => self.on_device_removed::<B>(device),
_ => (), // other events are not handled in anvil (yet)
}
}
fn on_pointer_move_absolute_windowed<B: InputBackend>(
&mut self,
evt: B::PointerMotionAbsoluteEvent,
output: &Output,
) {
let output_geo = self.space.output_geometry(output).unwrap();
let pos = evt.position_transformed(output_geo.size) + output_geo.loc.to_f64();
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
let under = self.surface_under(pos);
pointer.motion(
self,
under,
&MotionEvent {
location: pos,
serial,
time: evt.time(),
},
);
pointer.frame(self);
}
pub fn release_all_keys(&mut self) {
let keyboard = self.seat.get_keyboard().unwrap();
for keycode in keyboard.pressed_keys() {
keyboard.input(
self,
keycode,
KeyState::Released,
SCOUNTER.next_serial(),
InputTime::now(),
|_, _, _| FilterResult::Forward::<bool>,
);
}
}
}
#[cfg(feature = "udev")]
impl AnvilState<UdevData> {
pub fn process_input_event<B: InputBackend>(&mut self, dh: &DisplayHandle, event: InputEvent<B>) {
match event {
InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::<B>(event) {
#[cfg(feature = "udev")]
KeyAction::VtSwitch(vt) => {
info!(to = vt, "Trying to switch vt");
if let Err(err) = self.backend_data.session.change_vt(vt) {
error!(vt, "Error switching vt: {}", err);
}
}
KeyAction::Screen(num) => {
let geometry = self
.space
.outputs()
.nth(num)
.map(|o| self.space.output_geometry(o).unwrap());
if let Some(geometry) = geometry {
let x = geometry.loc.x as f64 + geometry.size.w as f64 / 2.0;
let y = geometry.size.h as f64 / 2.0;
let location = (x, y).into();
let pointer = self.pointer.clone();
let under = self.surface_under(location);
pointer.motion(
self,
under,
&MotionEvent {
location,
serial: SCOUNTER.next_serial(),
time: InputTime::now(),
},
);
pointer.frame(self);
}
}
KeyAction::ScaleUp => {
let pos = self.pointer.current_location().to_i32_round();
let output = self
.space
.outputs()
.find(|o| self.space.output_geometry(o).unwrap().contains(pos))
.cloned();
if let Some(output) = output {
let (output_location, scale) = (
self.space.output_geometry(&output).unwrap().loc,
output.current_scale().fractional_scale(),
);
let new_scale = scale + 0.25;
output.change_current_state(None, None, Some(Scale::Fractional(new_scale)), None);
let rescale = scale / new_scale;
let output_location = output_location.to_f64();
let mut pointer_output_location = self.pointer.current_location() - output_location;
pointer_output_location.x *= rescale;
pointer_output_location.y *= rescale;
let pointer_location = output_location + pointer_output_location;
crate::shell::fixup_positions(&mut self.space, pointer_location);
let pointer = self.pointer.clone();
let under = self.surface_under(pointer_location);
pointer.motion(
self,
under,
&MotionEvent {
location: pointer_location,
serial: SCOUNTER.next_serial(),
time: InputTime::now(),
},
);
pointer.frame(self);
self.backend_data.reset_buffers(&output);
}
}
KeyAction::ScaleDown => {
let pos = self.pointer.current_location().to_i32_round();
let output = self
.space
.outputs()
.find(|o| self.space.output_geometry(o).unwrap().contains(pos))
.cloned();
if let Some(output) = output {
let (output_location, scale) = (
self.space.output_geometry(&output).unwrap().loc,
output.current_scale().fractional_scale(),
);
let new_scale = f64::max(1.0, scale - 0.25);
output.change_current_state(None, None, Some(Scale::Fractional(new_scale)), None);
let rescale = scale / new_scale;
let output_location = output_location.to_f64();
let mut pointer_output_location = self.pointer.current_location() - output_location;
pointer_output_location.x *= rescale;
pointer_output_location.y *= rescale;
let pointer_location = output_location + pointer_output_location;
crate::shell::fixup_positions(&mut self.space, pointer_location);
let pointer = self.pointer.clone();
let under = self.surface_under(pointer_location);
pointer.motion(
self,
under,
&MotionEvent {
location: pointer_location,
serial: SCOUNTER.next_serial(),
time: InputTime::now(),
},
);
pointer.frame(self);
self.backend_data.reset_buffers(&output);
}
}
KeyAction::RotateOutput => {
let pos = self.pointer.current_location().to_i32_round();
let output = self
.space
.outputs()
.find(|o| self.space.output_geometry(o).unwrap().contains(pos))
.cloned();
if let Some(output) = output {
let current_transform = output.current_transform();
let new_transform = match current_transform {
Transform::Normal => Transform::_90,
Transform::_90 => Transform::_180,
Transform::_180 => Transform::_270,
Transform::_270 => Transform::Flipped,
Transform::Flipped => Transform::Flipped90,
Transform::Flipped90 => Transform::Flipped180,
Transform::Flipped180 => Transform::Flipped270,
Transform::Flipped270 => Transform::Normal,
};
output.change_current_state(None, Some(new_transform), None, None);
crate::shell::fixup_positions(&mut self.space, self.pointer.current_location());
self.backend_data.reset_buffers(&output);
}
}
KeyAction::ToggleTint => {
let mut debug_flags = self.backend_data.debug_flags();
debug_flags.toggle(DebugFlags::TINT);
self.backend_data.set_debug_flags(debug_flags);
}
action => match action {
KeyAction::None
| KeyAction::Quit
| KeyAction::Run(_)
| KeyAction::TogglePreview
| KeyAction::ToggleDecorations => self.process_common_key_action(action),
_ => unreachable!(),
},
},
InputEvent::PointerMotion { event, .. } => self.on_pointer_move::<B>(dh, event),
InputEvent::PointerMotionAbsolute { event, .. } => self.on_pointer_move_absolute::<B>(dh, event),
InputEvent::PointerButton { event, .. } => self.on_pointer_button::<B>(event),
InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::<B>(event),
InputEvent::TabletToolAxis { event, .. } => self.on_tablet_tool_axis::<B>(event),
InputEvent::TabletToolProximity { event, .. } => self.on_tablet_tool_proximity::<B>(dh, event),
InputEvent::TabletToolTip { event, .. } => self.on_tablet_tool_tip::<B>(event),
InputEvent::TabletToolButton { event, .. } => self.on_tablet_button::<B>(event),
InputEvent::GestureSwipeBegin { event, .. } => self.on_gesture_swipe_begin::<B>(event),
InputEvent::GestureSwipeUpdate { event, .. } => self.on_gesture_swipe_update::<B>(event),
InputEvent::GestureSwipeEnd { event, .. } => self.on_gesture_swipe_end::<B>(event),
InputEvent::GesturePinchBegin { event, .. } => self.on_gesture_pinch_begin::<B>(event),
InputEvent::GesturePinchUpdate { event, .. } => self.on_gesture_pinch_update::<B>(event),
InputEvent::GesturePinchEnd { event, .. } => self.on_gesture_pinch_end::<B>(event),
InputEvent::GestureHoldBegin { event, .. } => self.on_gesture_hold_begin::<B>(event),
InputEvent::GestureHoldEnd { event, .. } => self.on_gesture_hold_end::<B>(event),
InputEvent::TouchDown { event } => self.on_touch_down::<B>(event),
InputEvent::TouchUp { event } => self.on_touch_up::<B>(event),
InputEvent::TouchMotion { event } => self.on_touch_motion::<B>(event),
InputEvent::TouchFrame { event } => self.on_touch_frame::<B>(event),
InputEvent::TouchCancel { event } => self.on_touch_cancel::<B>(event),
InputEvent::DeviceAdded { device } => self.on_device_added::<B>(device),
InputEvent::DeviceRemoved { device } => self.on_device_removed::<B>(device),
_ => {
// other events are not handled in anvil (yet)
}
}
}
fn on_pointer_move<B: InputBackend>(&mut self, _dh: &DisplayHandle, evt: B::PointerMotionEvent) {
let mut pointer_location = self.pointer.current_location();
let serial = SCOUNTER.next_serial();
let pointer = self.pointer.clone();
let under = self.surface_under(pointer_location);
let mut pointer_locked = false;
let mut pointer_confined = false;
let mut confine_region = None;
if let Some((surface, surface_loc)) = under
.as_ref()
.and_then(|(target, l)| Some((target.wl_surface()?, l)))
{
with_pointer_constraint(&surface, &pointer, |constraint| match constraint {
Some(constraint) if constraint.is_active() => {
// Constraint does not apply if not within region
if !constraint
.region()
.is_none_or(|x| x.contains((pointer_location - *surface_loc).to_i32_round()))
{
return;
}
match &*constraint {
PointerConstraint::Locked(_locked) => {
pointer_locked = true;
}
PointerConstraint::Confined(confine) => {
pointer_confined = true;
confine_region = confine.region().cloned();
}
}
}
_ => {}
});
}
pointer.relative_motion(
self,
under.clone(),
&RelativeMotionEvent {
delta: evt.delta(),
delta_unaccel: evt.delta_unaccel(),
time: evt.time(),
},
);
// If pointer is locked, only emit relative motion
if pointer_locked {
pointer.frame(self);
return;
}
// Clamp delta, such that the pointer never moves out of bounds
let mut delta = self.clamp_coords(pointer_location + evt.delta()) - pointer_location;
// Clamp the individual x and y components of delta to respect confinement regions
if pointer_confined {
if let Some((_, surface_loc)) = &under {
if let Some(region) = &confine_region {
// Clamp delta.x
if !region.contains(
(pointer_location + Point::new(delta.x, 0f64) - *surface_loc).to_i32_round(),
) {
delta.x = 0f64;
}
// Clamp delta.y
if !region.contains(
(pointer_location + Point::new(0f64, delta.y) - *surface_loc).to_i32_round(),
) {
delta.y = 0f64;
}
}
}
}
pointer_location += delta;
let new_under = self.surface_under(pointer_location);
// If confined, don't move pointer if it would go outside surface
if pointer_confined {
if let Some((surface, _)) = &under {
if new_under.as_ref().and_then(|(under, _)| under.wl_surface()) != surface.wl_surface() {
pointer.frame(self);
return;
}
}
}
pointer.motion(
self,
under,
&MotionEvent {
location: pointer_location,
serial,
time: evt.time(),
},
);
pointer.frame(self);
// If pointer is now in a constraint region, activate it
// TODO Anywhere else pointer is moved needs to do this
if let Some((under, surface_location)) =
new_under.and_then(|(target, loc)| Some((target.wl_surface()?.into_owned(), loc)))
{
with_pointer_constraint(&under, &pointer, |constraint| match constraint {
Some(constraint) if !constraint.is_active() => {
let point = (pointer_location - surface_location).to_i32_round();
if constraint.region().is_none_or(|region| region.contains(point)) {
constraint.activate();
}
}
_ => {}
});
}
}
fn on_pointer_move_absolute<B: InputBackend>(
&mut self,
_dh: &DisplayHandle,
evt: B::PointerMotionAbsoluteEvent,
) {
let serial = SCOUNTER.next_serial();
let max_x = self
.space
.outputs()
.fold(0, |acc, o| acc + self.space.output_geometry(o).unwrap().size.w);
let max_h_output = self
.space
.outputs()
.max_by_key(|o| self.space.output_geometry(o).unwrap().size.h)
.unwrap();
let max_y = self.space.output_geometry(max_h_output).unwrap().size.h;
let mut pointer_location = (evt.x_transformed(max_x), evt.y_transformed(max_y)).into();
// clamp to screen limits
pointer_location = self.clamp_coords(pointer_location);
let pointer = self.pointer.clone();
let under = self.surface_under(pointer_location);
pointer.motion(
self,
under,
&MotionEvent {
location: pointer_location,
serial,
time: evt.time(),
},
);
pointer.frame(self);
}
fn on_tablet_tool_axis<B: InputBackend>(&mut self, evt: B::TabletToolAxisEvent) {
let tablet_seat = self.seat.tablet_seat();
if let Some(pointer_location) = self.touch_location_transformed(&evt) {
let pointer = self.pointer.clone();
let under = self.surface_under(pointer_location);
let tool = tablet_seat.get_tool(&evt.tool());
let time = InputTime::now();
pointer.motion(
self,
under.clone(),
&MotionEvent {
location: pointer_location,
serial: SCOUNTER.next_serial(),
time,
},
);
if let Some(tool) = tool {
let frame = tablet::tool::AxisFrame {
pressure: evt.pressure_has_changed().then(|| evt.pressure()),
distance: evt.distance_has_changed().then(|| evt.distance()),
tilt: evt.tilt_has_changed().then(|| evt.tilt()),
rotation: evt.rotation_has_changed().then(|| evt.rotation()),
slider: evt.slider_has_changed().then(|| evt.slider_position()),
wheel: evt
.wheel_has_changed()
.then(|| (evt.wheel_delta(), evt.wheel_delta_discrete())),
};
tool.axis(self, frame);
tool.motion(
self,
under,
&tablet::tool::MotionEvent {
location: pointer_location,
serial: SCOUNTER.next_serial(),
time,
},
);
tool.frame(self, time);
}
pointer.frame(self);
}
}
fn on_tablet_tool_proximity<B: InputBackend>(
&mut self,
dh: &DisplayHandle,
evt: B::TabletToolProximityEvent,
) {
let tablet_seat = self.seat.tablet_seat();
if let Some(pointer_location) = self.touch_location_transformed(&evt) {
let tool = evt.tool();
let pointer = self.pointer.clone();
let under = self.surface_under(pointer_location);
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&evt.device()));
let tool = tablet_seat
.get_tool(&tool)
.unwrap_or_else(|| tablet_seat.add_wp_tool(self, dh, &tool));
pointer.motion(
self,
under.clone(),
&MotionEvent {
location: pointer_location,
serial: SCOUNTER.next_serial(),
time: evt.time(),
},
);
pointer.frame(self);
if let Some(tablet) = tablet {
let frame = tablet::tool::AxisFrame {
pressure: evt.pressure_has_changed().then(|| evt.pressure()),
distance: evt.distance_has_changed().then(|| evt.distance()),
tilt: evt.tilt_has_changed().then(|| evt.tilt()),
rotation: evt.rotation_has_changed().then(|| evt.rotation()),
slider: evt.slider_has_changed().then(|| evt.slider_position()),
wheel: evt
.wheel_has_changed()
.then(|| (evt.wheel_delta(), evt.wheel_delta_discrete())),
};
match evt.state() {
ProximityState::In => {
tool.proximity_in(
self,
under,
tablet,
&tablet::tool::ProximityInEvent {
location: pointer_location,
axis: Some(frame),
serial: SCOUNTER.next_serial(),
time: evt.time(),
},
);
}
ProximityState::Out => {
tool.proximity_out(
self,
&tablet::tool::ProximityOutEvent {
serial: SCOUNTER.next_serial(),
time: evt.time(),
},
);
}
}
// Doing this in an idle handler would allow other events (e.g. buttons) to be
// sent as part of the same frame, which is closer to what the protocol
// expect, and let well behaved clients accumulate events.
tool.frame(self, evt.time());
}
}
}
fn on_tablet_tool_tip<B: InputBackend>(&mut self, evt: B::TabletToolTipEvent) {
let tool = self.seat.tablet_seat().get_tool(&evt.tool());
if let Some(tool) = tool {
let serial = SCOUNTER.next_serial();
match evt.tip_state() {
TabletToolTipState::Down => {
tool.down(
self,
&tablet::tool::DownEvent {
serial,
time: evt.time(),
},
);
// change the keyboard focus
self.update_keyboard_focus(self.pointer.current_location(), serial);
}
TabletToolTipState::Up => {
tool.up(
self,
&tablet::tool::UpEvent {
serial,
time: evt.time(),
},
);
}
}
tool.frame(self, evt.time());
}
}
fn on_tablet_button<B: InputBackend>(&mut self, evt: B::TabletToolButtonEvent) {
let tool = self.seat.tablet_seat().get_tool(&evt.tool());
if let Some(tool) = tool {
tool.button(
self,
&tablet::tool::ButtonEvent {
serial: SCOUNTER.next_serial(),
button: evt.button(),
state: evt.button_state(),
time: evt.time(),
},
);
tool.frame(self, evt.time());
}
}
fn clamp_coords(&self, pos: Point<f64, Logical>) -> Point<f64, Logical> {
if self.space.outputs().next().is_none() {
return pos;
}
let (pos_x, pos_y) = pos.into();
let max_x = self
.space
.outputs()
.fold(0, |acc, o| acc + self.space.output_geometry(o).unwrap().size.w);
let clamped_x = pos_x.clamp(0.0, max_x as f64);
let max_y = self
.space
.outputs()
.find(|o| {
let geo = self.space.output_geometry(o).unwrap();
geo.contains((clamped_x as i32, 0))
})
.map(|o| self.space.output_geometry(o).unwrap().size.h);
if let Some(max_y) = max_y {
let clamped_y = pos_y.clamp(0.0, max_y as f64);
(clamped_x, clamped_y).into()
} else {
(clamped_x, pos_y).into()
}
}
}
/// Possible results of a keyboard action
#[allow(dead_code)] // some of these are only read if udev is enabled
#[derive(Debug)]
enum KeyAction {
/// Quit the compositor
Quit,
/// Trigger a vt-switch
VtSwitch(i32),
/// run a command
Run(String),
/// Switch the current screen
Screen(usize),
ScaleUp,
ScaleDown,
TogglePreview,
RotateOutput,
ToggleTint,
ToggleDecorations,
/// Do nothing more
None,
}
fn process_keyboard_shortcut(modifiers: ModifiersState, keysym: Keysym) -> Option<KeyAction> {
if modifiers.ctrl && modifiers.alt && keysym == Keysym::BackSpace || modifiers.logo && keysym == Keysym::q
{
// ctrl+alt+backspace = quit
// logo + q = quit
Some(KeyAction::Quit)
} else if (xkb::KEY_XF86Switch_VT_1..=xkb::KEY_XF86Switch_VT_12).contains(&keysym.raw()) {
// VTSwitch
Some(KeyAction::VtSwitch(
(keysym.raw() - xkb::KEY_XF86Switch_VT_1 + 1) as i32,
))
} else if modifiers.logo && keysym == Keysym::Return {
// run terminal
Some(KeyAction::Run("weston-terminal".into()))
} else if modifiers.logo && (xkb::KEY_1..=xkb::KEY_9).contains(&keysym.raw()) {
Some(KeyAction::Screen((keysym.raw() - xkb::KEY_1) as usize))
} else if modifiers.logo && modifiers.shift && keysym == Keysym::M {
Some(KeyAction::ScaleDown)
} else if modifiers.logo && modifiers.shift && keysym == Keysym::P {
Some(KeyAction::ScaleUp)
} else if modifiers.logo && modifiers.shift && keysym == Keysym::W {
Some(KeyAction::TogglePreview)
} else if modifiers.logo && modifiers.shift && keysym == Keysym::R {
Some(KeyAction::RotateOutput)
} else if modifiers.logo && modifiers.shift && keysym == Keysym::T {
Some(KeyAction::ToggleTint)
} else if modifiers.logo && modifiers.shift && keysym == Keysym::D {
Some(KeyAction::ToggleDecorations)
} else {
None
}
}
impl<BackendData: Backend> VirtualKeyboardHandler for AnvilState<BackendData> {
fn process_virtual_keyboard_event(&mut self, event: InputEvent<VirtualKeyboardBackend>) {
let keyboard = self.seat.get_keyboard().unwrap();
match event {
// The device's keycodes only mean anything under its own keymap, so
// activate it before processing events. Anvil never switches back;
// a real compositor should track which device the seat keymap came
// from and restore its configured one when a physical key arrives.
InputEvent::DeviceAdded { device }
| InputEvent::Special(VirtualKeyboardSpecialEvent::KeymapChanged { device }) => {
let Some(keymap) = device.keymap() else {
return;
};
if let Err(err) = keyboard.set_keymap_from_string(self, keymap.to_string()) {
warn!(?err, "Failed to apply virtual keyboard keymap");
}
}
InputEvent::Keyboard { event } => {
match self.keyboard_key_to_action::<VirtualKeyboardBackend>(event) {
action @ (KeyAction::None
| KeyAction::Quit
| KeyAction::Run(_)
| KeyAction::TogglePreview
| KeyAction::ToggleDecorations) => self.process_common_key_action(action),
// VT switching and output changes are out-of-scope for virtual keyboards.
action => debug!(?action, "ignoring key action from a virtual keyboard"),
}
}
InputEvent::Special(VirtualKeyboardSpecialEvent::Modifiers {
mods_depressed,
mods_latched,
mods_locked,
group,
..
}) => {
keyboard.with_xkb_state(self, |mut context| {
context.set_modifier_mask(mods_depressed, mods_latched, mods_locked, group)
});
}
_ => (),
}
}
}