- 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.
1458 lines
58 KiB
Rust
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)
|
|
});
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|