585 lines
23 KiB
Rust
585 lines
23 KiB
Rust
use std::process::Command;
|
|
|
|
use smithay::{
|
|
backend::input::{
|
|
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
|
|
KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
|
|
TouchEvent,
|
|
},
|
|
input::{
|
|
keyboard::{FilterResult, keysyms},
|
|
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerHandle, RelativeMotionEvent},
|
|
touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent},
|
|
},
|
|
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
|
utils::{Logical, Point, SERIAL_COUNTER, Serial},
|
|
wayland::{
|
|
keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat,
|
|
pointer_constraints::{PointerConstraint, with_pointer_constraint},
|
|
},
|
|
};
|
|
|
|
use crate::{Ferese, config::BindingAction};
|
|
|
|
impl Ferese {
|
|
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
|
|
let seat = self.seat.clone();
|
|
self.idle_notifier_state.notify_activity(&seat);
|
|
|
|
match event {
|
|
InputEvent::Keyboard { event, .. } => {
|
|
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
|
|
let keycode = event.key_code();
|
|
let state = event.state();
|
|
|
|
keyboard.input::<(), _>(
|
|
self,
|
|
keycode,
|
|
state,
|
|
SERIAL_COUNTER.next_serial(),
|
|
Event::time(&event) as u32,
|
|
|data, modifiers, keysym| {
|
|
if state == KeyState::Released && data.intercepted_keys.remove(&keycode) {
|
|
return FilterResult::Intercept(());
|
|
}
|
|
|
|
let symbol = keysym.modified_sym().raw();
|
|
if overview_escape(symbol, data.overview.is_active()) {
|
|
if state == KeyState::Pressed {
|
|
data.intercepted_keys.insert(keycode);
|
|
data.set_overview_active(false);
|
|
}
|
|
|
|
return FilterResult::Intercept(());
|
|
}
|
|
if emergency_shortcut_escape(symbol, modifiers.ctrl, modifiers.alt) {
|
|
if state == KeyState::Pressed {
|
|
data.intercepted_keys.insert(keycode);
|
|
|
|
if let Some(inhibitor) = data.active_shortcuts_inhibitor.take() {
|
|
inhibitor.inactivate();
|
|
}
|
|
}
|
|
|
|
return FilterResult::Intercept(());
|
|
}
|
|
|
|
if data.seat.keyboard_shortcuts_inhibited() {
|
|
return FilterResult::Forward;
|
|
}
|
|
|
|
if let Some(vt) = virtual_terminal(
|
|
symbol,
|
|
modifiers.ctrl,
|
|
modifiers.alt,
|
|
data.direct_backend.is_some(),
|
|
) {
|
|
if state == KeyState::Pressed {
|
|
data.intercepted_keys.insert(keycode);
|
|
crate::backends::direct::switch_vt(data, vt);
|
|
}
|
|
|
|
return FilterResult::Intercept(());
|
|
}
|
|
|
|
let raw_symbols = keysym
|
|
.raw_syms()
|
|
.into_iter()
|
|
.map(|symbol| symbol.raw())
|
|
.collect::<Vec<_>>();
|
|
let action = data.bindings.iter().find_map(|binding| {
|
|
binding
|
|
.matches(
|
|
keycode,
|
|
&raw_symbols,
|
|
modifiers.logo,
|
|
modifiers.ctrl,
|
|
modifiers.alt,
|
|
modifiers.shift,
|
|
)
|
|
.then(|| binding.action.clone())
|
|
});
|
|
let Some(action) = action else {
|
|
return FilterResult::Forward;
|
|
};
|
|
|
|
if state == KeyState::Pressed {
|
|
data.intercepted_keys.insert(keycode);
|
|
data.execute_binding(action);
|
|
}
|
|
|
|
FilterResult::Intercept(())
|
|
},
|
|
);
|
|
}
|
|
InputEvent::PointerMotionAbsolute { event, .. } => {
|
|
let Some(position) = self.absolute_event_position(&event) else {
|
|
return;
|
|
};
|
|
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
|
let position = self.constrain_pointer_position(&pointer, position);
|
|
|
|
pointer.motion(
|
|
self,
|
|
self.surface_under(position),
|
|
&MotionEvent {
|
|
location: position,
|
|
serial: SERIAL_COUNTER.next_serial(),
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
pointer.frame(self);
|
|
self.focus_window_under_pointer(&pointer, position);
|
|
self.activate_focused_pointer_constraint(&pointer);
|
|
crate::backends::direct::render_all(self);
|
|
}
|
|
InputEvent::PointerMotion { event, .. } => {
|
|
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
|
let position = pointer.current_location();
|
|
let focus = self.surface_under(position);
|
|
let (scale_x, scale_y) = self
|
|
.window_under_visual(position)
|
|
.and_then(|window| self.visual_scale_for_window(&window))
|
|
.unwrap_or((1.0, 1.0));
|
|
let delta = event.delta();
|
|
let delta_unaccel = event.delta_unaccel();
|
|
|
|
pointer.relative_motion(
|
|
self,
|
|
focus,
|
|
&RelativeMotionEvent {
|
|
delta: (delta.x / scale_x, delta.y / scale_y).into(),
|
|
delta_unaccel: (delta_unaccel.x / scale_x, delta_unaccel.y / scale_y)
|
|
.into(),
|
|
utime: (event.time_msec() as u64).saturating_mul(1_000),
|
|
},
|
|
);
|
|
let requested = self.clamp_pointer_position(position + delta);
|
|
let location = self.constrain_pointer_position(&pointer, requested);
|
|
|
|
pointer.motion(
|
|
self,
|
|
self.surface_under(location),
|
|
&MotionEvent {
|
|
location,
|
|
serial: SERIAL_COUNTER.next_serial(),
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
pointer.frame(self);
|
|
self.focus_window_under_pointer(&pointer, location);
|
|
self.activate_focused_pointer_constraint(&pointer);
|
|
crate::backends::direct::render_all(self);
|
|
}
|
|
InputEvent::PointerButton { event, .. } => {
|
|
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
|
let serial = SERIAL_COUNTER.next_serial();
|
|
|
|
if self.overview.is_active() {
|
|
let position = pointer.current_location();
|
|
if self.layer_under(position).is_some() {
|
|
pointer.button(
|
|
self,
|
|
&ButtonEvent {
|
|
button: event.button_code(),
|
|
state: event.state(),
|
|
serial,
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
pointer.frame(self);
|
|
return;
|
|
}
|
|
|
|
if event.state() == ButtonState::Pressed {
|
|
if let Some(window) = self.window_under_visual(position)
|
|
&& let Some(id) = self.window_ids.get(&window).copied()
|
|
{
|
|
self.select_overview_window(id);
|
|
} else {
|
|
self.set_overview_active(false);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if event.state() == ButtonState::Pressed && !pointer.is_grabbed() {
|
|
self.focus_window_at(pointer.current_location(), serial);
|
|
}
|
|
|
|
pointer.button(
|
|
self,
|
|
&ButtonEvent {
|
|
button: event.button_code(),
|
|
state: event.state(),
|
|
serial,
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
pointer.frame(self);
|
|
}
|
|
InputEvent::PointerAxis { event, .. } => {
|
|
let source = event.source();
|
|
let horizontal = event.amount(Axis::Horizontal).unwrap_or_else(|| {
|
|
event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 15.0 / 120.0
|
|
});
|
|
let vertical = event.amount(Axis::Vertical).unwrap_or_else(|| {
|
|
event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 15.0 / 120.0
|
|
});
|
|
let mut frame = AxisFrame::new(event.time() as u32).source(source);
|
|
if horizontal != 0.0 {
|
|
frame = frame.value(Axis::Horizontal, horizontal);
|
|
if let Some(value) = event.amount_v120(Axis::Horizontal) {
|
|
frame = frame.v120(Axis::Horizontal, value as i32);
|
|
}
|
|
}
|
|
|
|
if vertical != 0.0 {
|
|
frame = frame.value(Axis::Vertical, vertical);
|
|
if let Some(value) = event.amount_v120(Axis::Vertical) {
|
|
frame = frame.v120(Axis::Vertical, value as i32);
|
|
}
|
|
}
|
|
|
|
if source == AxisSource::Finger {
|
|
if event.amount(Axis::Horizontal) == Some(0.0) {
|
|
frame = frame.stop(Axis::Horizontal);
|
|
}
|
|
if event.amount(Axis::Vertical) == Some(0.0) {
|
|
frame = frame.stop(Axis::Vertical);
|
|
}
|
|
}
|
|
|
|
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
|
|
|
pointer.axis(self, frame);
|
|
pointer.frame(self);
|
|
}
|
|
InputEvent::TouchDown { event, .. } => {
|
|
let Some(location) = self.absolute_event_position(&event) else {
|
|
return;
|
|
};
|
|
self.focus_window_at(location, SERIAL_COUNTER.next_serial());
|
|
let touch = self.seat.get_touch().expect("seat has touch capability");
|
|
let serial = SERIAL_COUNTER.next_serial();
|
|
|
|
touch.down(
|
|
self,
|
|
self.surface_under(location),
|
|
&DownEvent {
|
|
slot: event.slot(),
|
|
location,
|
|
serial,
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
}
|
|
InputEvent::TouchMotion { event, .. } => {
|
|
let Some(location) = self.absolute_event_position(&event) else {
|
|
return;
|
|
};
|
|
let touch = self.seat.get_touch().expect("seat has touch capability");
|
|
|
|
touch.motion(
|
|
self,
|
|
self.surface_under(location),
|
|
&TouchMotionEvent {
|
|
slot: event.slot(),
|
|
location,
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
}
|
|
InputEvent::TouchUp { event, .. } => {
|
|
let touch = self.seat.get_touch().expect("seat has touch capability");
|
|
touch.up(
|
|
self,
|
|
&UpEvent {
|
|
slot: event.slot(),
|
|
serial: SERIAL_COUNTER.next_serial(),
|
|
time: event.time() as u32,
|
|
},
|
|
);
|
|
}
|
|
InputEvent::TouchCancel { .. } => {
|
|
let touch = self.seat.get_touch().expect("seat has touch capability");
|
|
touch.cancel(self);
|
|
}
|
|
InputEvent::TouchFrame { .. } => {
|
|
let touch = self.seat.get_touch().expect("seat has touch capability");
|
|
touch.frame(self);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn absolute_event_position<I, E>(&self, event: &E) -> Option<Point<f64, Logical>>
|
|
where
|
|
I: InputBackend,
|
|
E: AbsolutePositionEvent<I>,
|
|
{
|
|
let output = self
|
|
.focused_output()
|
|
.or_else(|| self.space.outputs().next())?;
|
|
let geometry = self.space.output_geometry(output)?;
|
|
|
|
Some(event.position_transformed(geometry.size) + geometry.loc.to_f64())
|
|
}
|
|
|
|
fn focus_window_at(&mut self, position: Point<f64, Logical>, serial: Serial) {
|
|
let keyboard = self.seat.get_keyboard().expect("seat has a keyboard");
|
|
self.focus_output_at(position);
|
|
|
|
if let Some((layer, _, _)) = self.layer_under(position) {
|
|
if layer.can_receive_keyboard_focus() {
|
|
keyboard.set_focus(self, Some(layer.wl_surface().clone()), serial);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if let Some(window) = self.window_under_visual(position) {
|
|
self.focused_window = self.window_ids.get(&window).copied();
|
|
if let Some(focused) = self.focused_window
|
|
&& let Err(error) = self.workspaces.focus_window(focused)
|
|
{
|
|
tracing::error!(%error, ?focused, "failed to update workspace focus");
|
|
}
|
|
self.raise_window(&window, true);
|
|
let surface = window
|
|
.toplevel()
|
|
.expect("mapped window has a toplevel")
|
|
.wl_surface()
|
|
.clone();
|
|
keyboard.set_focus(self, Some(surface), serial);
|
|
} else {
|
|
self.focused_window = None;
|
|
keyboard.set_focus(self, Option::<WlSurface>::None, serial);
|
|
}
|
|
|
|
self.space.elements().for_each(|window| {
|
|
if let Some(toplevel) = window.toplevel() {
|
|
toplevel.send_pending_configure();
|
|
}
|
|
});
|
|
|
|
self.relayout();
|
|
}
|
|
|
|
fn focus_window_under_pointer(
|
|
&mut self,
|
|
pointer: &PointerHandle<Self>,
|
|
position: Point<f64, Logical>,
|
|
) {
|
|
if self.overview.is_active()
|
|
|| !self.input_settings.focus_follows_mouse
|
|
|| pointer.is_grabbed()
|
|
{
|
|
return;
|
|
}
|
|
|
|
let Some(window) = self.window_under_visual(position) else {
|
|
return;
|
|
};
|
|
if self.window_ids.get(&window).copied() == self.focused_window {
|
|
return;
|
|
}
|
|
|
|
self.focus_window_at(position, SERIAL_COUNTER.next_serial());
|
|
}
|
|
|
|
fn clamp_pointer_position(&self, requested: Point<f64, Logical>) -> Point<f64, Logical> {
|
|
self.space
|
|
.outputs()
|
|
.filter_map(|output| self.space.output_geometry(output))
|
|
.map(|geometry| {
|
|
let position = Point::from((
|
|
requested.x.clamp(
|
|
f64::from(geometry.loc.x),
|
|
f64::from(geometry.loc.x + geometry.size.w - 1),
|
|
),
|
|
requested.y.clamp(
|
|
f64::from(geometry.loc.y),
|
|
f64::from(geometry.loc.y + geometry.size.h - 1),
|
|
),
|
|
));
|
|
let x = position.x - requested.x;
|
|
let y = position.y - requested.y;
|
|
(x * x + y * y, position)
|
|
})
|
|
.min_by(|(left, _), (right, _)| left.total_cmp(right))
|
|
.map(|(_, position)| position)
|
|
.unwrap_or(requested)
|
|
}
|
|
|
|
fn constrain_pointer_position(
|
|
&self,
|
|
pointer: &PointerHandle<Self>,
|
|
requested: Point<f64, Logical>,
|
|
) -> Point<f64, Logical> {
|
|
let current = pointer.current_location();
|
|
let Some(focused_surface) = pointer.current_focus() else {
|
|
return requested;
|
|
};
|
|
let Some((surface, origin)) = self.surface_under(current) else {
|
|
return requested;
|
|
};
|
|
if surface != focused_surface {
|
|
return requested;
|
|
}
|
|
|
|
with_pointer_constraint(&surface, pointer, |constraint| {
|
|
let Some(constraint) = constraint.filter(|constraint| constraint.is_active()) else {
|
|
return requested;
|
|
};
|
|
|
|
match &*constraint {
|
|
PointerConstraint::Locked(_) => current,
|
|
PointerConstraint::Confined(_) => {
|
|
let remains_on_surface = self
|
|
.surface_under(requested)
|
|
.is_some_and(|(candidate, _)| candidate == surface);
|
|
let inside_region = constraint
|
|
.region()
|
|
.is_none_or(|region| region.contains((requested - origin).to_i32_round()));
|
|
|
|
if remains_on_surface && inside_region {
|
|
requested
|
|
} else {
|
|
current
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
pub fn activate_focused_pointer_constraint(&self, pointer: &PointerHandle<Self>) {
|
|
let position = pointer.current_location();
|
|
let Some((surface, origin)) = self.surface_under(position) else {
|
|
return;
|
|
};
|
|
if pointer.current_focus().as_ref() != Some(&surface) {
|
|
return;
|
|
}
|
|
|
|
with_pointer_constraint(&surface, pointer, |constraint| {
|
|
let Some(constraint) = constraint else {
|
|
return;
|
|
};
|
|
let inside_region = constraint
|
|
.region()
|
|
.is_none_or(|region| region.contains((position - origin).to_i32_round()));
|
|
|
|
if !constraint.is_active() && inside_region {
|
|
constraint.activate();
|
|
}
|
|
});
|
|
}
|
|
|
|
fn execute_binding(&mut self, action: BindingAction) {
|
|
match action {
|
|
BindingAction::Spawn(mut argv) => {
|
|
let program = argv.remove(0);
|
|
|
|
match Command::new(&program).args(argv).spawn() {
|
|
Ok(child) => {
|
|
tracing::info!(%program, pid = child.id(), "spawned binding command")
|
|
}
|
|
Err(error) => {
|
|
tracing::warn!(%program, %error, "failed to spawn binding command")
|
|
}
|
|
}
|
|
}
|
|
BindingAction::Close => self.close_focused_window(),
|
|
BindingAction::Exit => self.loop_signal.stop(),
|
|
BindingAction::Focus(direction) => self.focus_direction(direction),
|
|
BindingAction::Move(direction) => self.move_direction(direction),
|
|
BindingAction::Resize(direction) => self.resize_direction(direction),
|
|
BindingAction::SwitchWorkspace(workspace) => {
|
|
self.switch_workspace(u32::from(workspace));
|
|
}
|
|
BindingAction::MoveToWorkspace(workspace) => {
|
|
self.move_focused_to_workspace(u32::from(workspace));
|
|
}
|
|
BindingAction::ToggleFullscreen => self.toggle_focused_fullscreen(),
|
|
BindingAction::ToggleMaximized => self.toggle_focused_maximized(),
|
|
BindingAction::ToggleLayout => self.toggle_layout_mode(),
|
|
BindingAction::CycleColumnWidth => self.cycle_focused_column_width(),
|
|
BindingAction::CenterColumn => self.center_focused_column(),
|
|
BindingAction::Consume => self.consume_focused_window(),
|
|
BindingAction::Expel => self.expel_focused_window(),
|
|
BindingAction::ToggleFloating => self.toggle_focused_floating(),
|
|
BindingAction::ToggleOverview => self.toggle_overview(),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn virtual_terminal(symbol: u32, ctrl: bool, alt: bool, direct: bool) -> Option<i32> {
|
|
if !direct {
|
|
return None;
|
|
}
|
|
|
|
if (keysyms::KEY_XF86Switch_VT_1..=keysyms::KEY_XF86Switch_VT_12).contains(&symbol) {
|
|
return Some((symbol - keysyms::KEY_XF86Switch_VT_1 + 1) as i32);
|
|
}
|
|
|
|
(ctrl && alt && (keysyms::KEY_F1..=keysyms::KEY_F12).contains(&symbol))
|
|
.then_some((symbol - keysyms::KEY_F1 + 1) as i32)
|
|
}
|
|
|
|
fn emergency_shortcut_escape(symbol: u32, ctrl: bool, alt: bool) -> bool {
|
|
ctrl && alt && symbol == keysyms::KEY_Escape
|
|
}
|
|
|
|
fn overview_escape(symbol: u32, overview_active: bool) -> bool {
|
|
overview_active && symbol == keysyms::KEY_Escape
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{emergency_shortcut_escape, overview_escape, virtual_terminal};
|
|
|
|
#[test]
|
|
fn escape_closes_only_an_active_overview() {
|
|
assert!(overview_escape(keysyms::KEY_Escape, true));
|
|
assert!(!overview_escape(keysyms::KEY_Escape, false));
|
|
assert!(!overview_escape(keysyms::KEY_q, true));
|
|
}
|
|
|
|
#[test]
|
|
fn emergency_escape_requires_control_alt_escape() {
|
|
assert!(emergency_shortcut_escape(keysyms::KEY_Escape, true, true));
|
|
assert!(!emergency_shortcut_escape(keysyms::KEY_Escape, true, false));
|
|
assert!(!emergency_shortcut_escape(keysyms::KEY_q, true, true));
|
|
}
|
|
use smithay::input::keyboard::keysyms;
|
|
|
|
#[test]
|
|
fn maps_xkb_virtual_terminal_symbols_for_direct_sessions() {
|
|
assert_eq!(
|
|
virtual_terminal(keysyms::KEY_XF86Switch_VT_1, false, false, true),
|
|
Some(1)
|
|
);
|
|
assert_eq!(
|
|
virtual_terminal(keysyms::KEY_XF86Switch_VT_12, false, false, true),
|
|
Some(12)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn accepts_plain_function_symbols_only_with_control_alt() {
|
|
assert_eq!(virtual_terminal(keysyms::KEY_F7, true, true, true), Some(7));
|
|
assert_eq!(virtual_terminal(keysyms::KEY_F7, true, false, true), None);
|
|
assert_eq!(virtual_terminal(keysyms::KEY_F7, false, true, true), None);
|
|
}
|
|
|
|
#[test]
|
|
fn leaves_virtual_terminal_keys_to_nested_clients() {
|
|
assert_eq!(
|
|
virtual_terminal(keysyms::KEY_XF86Switch_VT_3, false, false, false),
|
|
None
|
|
);
|
|
assert_eq!(virtual_terminal(keysyms::KEY_F3, true, true, false), None);
|
|
}
|
|
}
|