Files
niri/src/input.rs
T
Ivan Molodetskikh 6fcdb41922 Refactor layout for configurability, add preset-column-widths option
layout.rs finally gets a struct actually named Layout.
2023-10-05 09:25:07 +04:00

749 lines
31 KiB
Rust

use smithay::backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _, GestureSwipeUpdateEvent as _,
InputBackend, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent,
PointerMotionEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState,
};
use smithay::backend::libinput::LibinputInputBackend;
use smithay::input::keyboard::{keysyms, FilterResult, KeysymHandle, ModifiersState};
use smithay::input::pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
GestureSwipeUpdateEvent, MotionEvent, RelativeMotionEvent,
};
use smithay::utils::SERIAL_COUNTER;
use smithay::wayland::tablet_manager::{TabletDescriptor, TabletSeatTrait};
use crate::config::{Action, Config, Modifiers};
use crate::niri::State;
use crate::utils::{center, get_monotonic_time, spawn};
pub enum CompositorMod {
Super,
Alt,
}
impl From<Action> for FilterResult<Action> {
fn from(value: Action) -> Self {
match value {
Action::None => FilterResult::Forward,
action => FilterResult::Intercept(action),
}
}
}
fn action(
config: &Config,
comp_mod: CompositorMod,
keysym: KeysymHandle,
mods: ModifiersState,
) -> Action {
use keysyms::*;
// Handle hardcoded binds.
#[allow(non_upper_case_globals)] // wat
match keysym.modified_sym().raw() {
modified @ KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12 => {
let vt = (modified - KEY_XF86Switch_VT_1 + 1) as i32;
return Action::ChangeVt(vt);
}
KEY_XF86PowerOff => return Action::Suspend,
_ => (),
}
// Handle configured binds.
let mut modifiers = Modifiers::empty();
if mods.ctrl {
modifiers |= Modifiers::CTRL;
}
if mods.shift {
modifiers |= Modifiers::SHIFT;
}
if mods.alt {
modifiers |= Modifiers::ALT;
}
if mods.logo {
modifiers |= Modifiers::SUPER;
}
let (mod_down, mut comp_mod) = match comp_mod {
CompositorMod::Super => (mods.logo, Modifiers::SUPER),
CompositorMod::Alt => (mods.alt, Modifiers::ALT),
};
if mod_down {
modifiers |= Modifiers::COMPOSITOR;
} else {
comp_mod = Modifiers::empty();
}
let Some(&raw) = keysym.raw_syms().first() else {
return Action::None;
};
for bind in &config.binds.0 {
if bind.key.keysym != raw {
continue;
}
if bind.key.modifiers | comp_mod == modifiers {
return bind.actions.first().cloned().unwrap_or(Action::None);
}
}
Action::None
}
impl State {
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
let _span = tracy_client::span!("process_input_event");
// A bit of a hack, but animation end runs some logic (i.e. workspace clean-up) and it
// doesn't always trigger due to damage, etc. So run it here right before it might prove
// important. Besides, animations affect the input, so it's best to have up-to-date values
// here.
self.niri.layout.advance_animations(get_monotonic_time());
let comp_mod = self.backend.mod_key();
match event {
InputEvent::Keyboard { event, .. } => {
let serial = SERIAL_COUNTER.next_serial();
let time = Event::time_msec(&event);
let action = self.niri.seat.get_keyboard().unwrap().input(
self,
event.key_code(),
event.state(),
serial,
time,
|self_, mods, keysym| {
if event.state() == KeyState::Pressed {
let config = self_.niri.config.borrow();
action(&config, comp_mod, keysym, *mods).into()
} else {
FilterResult::Forward
}
},
);
if let Some(action) = action {
match action {
Action::None => unreachable!(),
Action::Quit => {
info!("quitting because quit bind was pressed");
self.niri.stop_signal.stop()
}
Action::ChangeVt(vt) => {
self.backend.change_vt(vt);
}
Action::Suspend => {
self.backend.suspend();
}
Action::ToggleDebugTint => {
self.backend.toggle_debug_tint();
}
Action::Spawn(command) => {
if let Some((command, args)) = command.split_first() {
spawn(command, args);
}
}
Action::Screenshot => {
let active = self.niri.layout.active_output().cloned();
if let Some(active) = active {
if let Some(renderer) = self.backend.renderer() {
if let Err(err) = self.niri.screenshot(renderer, &active) {
warn!("error taking screenshot: {err:?}");
}
}
}
}
Action::CloseWindow => {
if let Some(window) = self.niri.layout.focus() {
window.toplevel().send_close();
}
}
Action::FullscreenWindow => {
let focus = self.niri.layout.focus().cloned();
if let Some(window) = focus {
self.niri.layout.toggle_fullscreen(&window);
}
}
Action::MoveColumnLeft => {
self.niri.layout.move_left();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveColumnRight => {
self.niri.layout.move_right();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowDown => {
self.niri.layout.move_down();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowUp => {
self.niri.layout.move_up();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusColumnLeft => {
self.niri.layout.focus_left();
}
Action::FocusColumnRight => {
self.niri.layout.focus_right();
}
Action::FocusWindowDown => {
self.niri.layout.focus_down();
}
Action::FocusWindowUp => {
self.niri.layout.focus_up();
}
Action::MoveWindowToWorkspaceDown => {
self.niri.layout.move_to_workspace_down();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowToWorkspaceUp => {
self.niri.layout.move_to_workspace_up();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::MoveWindowToWorkspace(idx) => {
self.niri.layout.move_to_workspace(idx);
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWorkspaceDown => {
self.niri.layout.switch_workspace_down();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWorkspaceUp => {
self.niri.layout.switch_workspace_up();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::FocusWorkspace(idx) => {
self.niri.layout.switch_workspace(idx);
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ConsumeWindowIntoColumn => {
self.niri.layout.consume_into_column();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::ExpelWindowFromColumn => {
self.niri.layout.expel_from_column();
// FIXME: granular
self.niri.queue_redraw_all();
}
Action::SwitchPresetColumnWidth => {
self.niri.layout.toggle_width();
}
Action::MaximizeColumn => {
self.niri.layout.toggle_full_width();
}
Action::FocusMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.layout.focus_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::FocusMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.layout.focus_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::FocusMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.layout.focus_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::FocusMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.layout.focus_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveWindowToMonitorLeft => {
if let Some(output) = self.niri.output_left() {
self.niri.layout.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveWindowToMonitorRight => {
if let Some(output) = self.niri.output_right() {
self.niri.layout.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveWindowToMonitorDown => {
if let Some(output) = self.niri.output_down() {
self.niri.layout.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::MoveWindowToMonitorUp => {
if let Some(output) = self.niri.output_up() {
self.niri.layout.move_to_output(&output);
self.move_cursor_to_output(&output);
}
}
Action::SetColumnWidth(change) => {
self.niri.layout.set_column_width(change);
}
}
}
}
InputEvent::PointerMotion { event, .. } => {
// We need an output to be able to move the pointer.
if self.niri.global_space.outputs().next().is_none() {
return;
}
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
let pos = pointer.current_location();
// We have an output, so we can compute the new location and focus.
let mut new_pos = pos + event.delta();
if self
.niri
.global_space
.output_under(new_pos)
.next()
.is_none()
{
// We ended up outside the outputs and need to clip the movement.
if let Some(output) = self.niri.global_space.output_under(pos).next() {
// The pointer was previously on some output. Clip the movement against its
// boundaries.
let geom = self.niri.global_space.output_geometry(output).unwrap();
new_pos.x = new_pos
.x
.clamp(geom.loc.x as f64, (geom.loc.x + geom.size.w - 1) as f64);
new_pos.y = new_pos
.y
.clamp(geom.loc.y as f64, (geom.loc.y + geom.size.h - 1) as f64);
} else {
// The pointer was not on any output in the first place. Find one for it.
// Let's do the simple thing and just put it on the first output.
let output = self.niri.global_space.outputs().next().unwrap();
let geom = self.niri.global_space.output_geometry(output).unwrap();
new_pos = center(geom).to_f64();
}
}
let under = self.niri.surface_under_and_global_space(new_pos);
pointer.motion(
self,
under.clone(),
&MotionEvent {
location: new_pos,
serial,
time: event.time_msec(),
},
);
pointer.relative_motion(
self,
under,
&RelativeMotionEvent {
delta: event.delta(),
delta_unaccel: event.delta_unaccel(),
utime: event.time(),
},
);
pointer.frame(self);
// Redraw to update the cursor position.
// FIXME: redraw only outputs overlapping the cursor.
self.niri.queue_redraw_all();
}
InputEvent::PointerMotionAbsolute { event, .. } => {
let Some(output) = self.niri.global_space.outputs().next() else {
return;
};
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
let under = self.niri.surface_under_and_global_space(pos);
pointer.motion(
self,
under,
&MotionEvent {
location: pos,
serial,
time: event.time_msec(),
},
);
pointer.frame(self);
// Redraw to update the cursor position.
// FIXME: redraw only outputs overlapping the cursor.
self.niri.queue_redraw_all();
}
InputEvent::PointerButton { event, .. } => {
let pointer = self.niri.seat.get_pointer().unwrap();
let serial = SERIAL_COUNTER.next_serial();
let button = event.button_code();
let button_state = event.state();
if ButtonState::Pressed == button_state && !pointer.is_grabbed() {
if let Some(window) = self.niri.window_under_cursor() {
let window = window.clone();
self.niri.layout.activate_window(&window);
} else if let Some(output) = self.niri.output_under_cursor() {
self.niri.layout.activate_output(&output);
}
};
pointer.button(
self,
&ButtonEvent {
button,
state: button_state,
serial,
time: event.time_msec(),
},
);
pointer.frame(self);
}
InputEvent::PointerAxis { event, .. } => {
let source = event.source();
let horizontal_amount = event.amount(Axis::Horizontal).unwrap_or_else(|| {
event.amount_discrete(Axis::Horizontal).unwrap_or(0.0) * 3.0
});
let vertical_amount = event
.amount(Axis::Vertical)
.unwrap_or_else(|| event.amount_discrete(Axis::Vertical).unwrap_or(0.0) * 3.0);
let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal);
let vertical_amount_discrete = event.amount_discrete(Axis::Vertical);
let mut frame = AxisFrame::new(event.time_msec()).source(source);
if horizontal_amount != 0.0 {
frame = frame.value(Axis::Horizontal, horizontal_amount);
if let Some(discrete) = horizontal_amount_discrete {
frame = frame.discrete(Axis::Horizontal, discrete as i32);
}
}
if vertical_amount != 0.0 {
frame = frame.value(Axis::Vertical, vertical_amount);
if let Some(discrete) = vertical_amount_discrete {
frame = frame.discrete(Axis::Vertical, discrete 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.niri.seat.get_pointer().unwrap();
pointer.axis(self, frame);
pointer.frame(self);
}
InputEvent::TabletToolAxis { event, .. } => {
let Some(output) = self.niri.output_for_tablet() else {
return;
};
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
let under = self.niri.surface_under_and_global_space(pos);
pointer.motion(
self,
under.clone(),
&MotionEvent {
location: pos,
serial,
time: event.time_msec(),
},
);
pointer.frame(self);
let tablet_seat = self.niri.seat.tablet_seat();
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device()));
let tool = tablet_seat.get_tool(&event.tool());
if let (Some(tablet), Some(tool)) = (tablet, tool) {
if event.pressure_has_changed() {
tool.pressure(event.pressure());
}
if event.distance_has_changed() {
tool.distance(event.distance());
}
if event.tilt_has_changed() {
tool.tilt(event.tilt());
}
if event.slider_has_changed() {
tool.slider_position(event.slider_position());
}
if event.rotation_has_changed() {
tool.rotation(event.rotation());
}
if event.wheel_has_changed() {
tool.wheel(event.wheel_delta(), event.wheel_delta_discrete());
}
tool.motion(
pos,
under,
&tablet,
SERIAL_COUNTER.next_serial(),
event.time_msec(),
);
}
// Redraw to update the cursor position.
// FIXME: redraw only outputs overlapping the cursor.
self.niri.queue_redraw_all();
}
InputEvent::TabletToolTip { event, .. } => {
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
if let Some(tool) = tool {
match event.tip_state() {
TabletToolTipState::Down => {
let serial = SERIAL_COUNTER.next_serial();
tool.tip_down(serial, event.time_msec());
let pointer = self.niri.seat.get_pointer().unwrap();
if !pointer.is_grabbed() {
if let Some(window) = self.niri.window_under_cursor() {
let window = window.clone();
self.niri.layout.activate_window(&window);
} else if let Some(output) = self.niri.output_under_cursor() {
self.niri.layout.activate_output(&output);
}
};
}
TabletToolTipState::Up => {
tool.tip_up(event.time_msec());
}
}
}
}
InputEvent::TabletToolProximity { event, .. } => {
let Some(output) = self.niri.output_for_tablet() else {
return;
};
let output_geo = self.niri.global_space.output_geometry(output).unwrap();
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
let under = self.niri.surface_under_and_global_space(pos);
pointer.motion(
self,
under.clone(),
&MotionEvent {
location: pos,
serial,
time: event.time_msec(),
},
);
pointer.frame(self);
let tablet_seat = self.niri.seat.tablet_seat();
let tool = tablet_seat.add_tool::<Self>(&self.niri.display_handle, &event.tool());
let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&event.device()));
if let (Some(under), Some(tablet)) = (under, tablet) {
match event.state() {
ProximityState::In => tool.proximity_in(
pos,
under,
&tablet,
SERIAL_COUNTER.next_serial(),
event.time_msec(),
),
ProximityState::Out => tool.proximity_out(event.time_msec()),
}
}
}
InputEvent::TabletToolButton { event, .. } => {
let tool = self.niri.seat.tablet_seat().get_tool(&event.tool());
if let Some(tool) = tool {
tool.button(
event.button(),
event.button_state(),
SERIAL_COUNTER.next_serial(),
event.time_msec(),
);
}
}
InputEvent::DeviceAdded { device } => {
if device.has_capability(DeviceCapability::TabletTool) {
self.niri.seat.tablet_seat().add_tablet::<Self>(
&self.niri.display_handle,
&TabletDescriptor::from(&device),
);
}
}
InputEvent::DeviceRemoved { device } => {
if device.has_capability(DeviceCapability::TabletTool) {
let tablet_seat = self.niri.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();
}
}
}
InputEvent::GestureSwipeBegin { event } => {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_swipe_begin(
self,
&GestureSwipeBeginEvent {
serial,
time: event.time_msec(),
fingers: event.fingers(),
},
);
}
InputEvent::GestureSwipeUpdate { event } => {
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_swipe_update(
self,
&GestureSwipeUpdateEvent {
time: event.time_msec(),
delta: event.delta(),
},
);
}
InputEvent::GestureSwipeEnd { event } => {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_swipe_end(
self,
&GestureSwipeEndEvent {
serial,
time: event.time_msec(),
cancelled: event.cancelled(),
},
);
}
InputEvent::GesturePinchBegin { event } => {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_pinch_begin(
self,
&GesturePinchBeginEvent {
serial,
time: event.time_msec(),
fingers: event.fingers(),
},
);
}
InputEvent::GesturePinchUpdate { event } => {
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_pinch_update(
self,
&GesturePinchUpdateEvent {
time: event.time_msec(),
delta: event.delta(),
scale: event.scale(),
rotation: event.rotation(),
},
);
}
InputEvent::GesturePinchEnd { event } => {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_pinch_end(
self,
&GesturePinchEndEvent {
serial,
time: event.time_msec(),
cancelled: event.cancelled(),
},
);
}
InputEvent::GestureHoldBegin { event } => {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_hold_begin(
self,
&GestureHoldBeginEvent {
serial,
time: event.time_msec(),
fingers: event.fingers(),
},
);
}
InputEvent::GestureHoldEnd { event } => {
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.niri.seat.get_pointer().unwrap();
pointer.gesture_hold_end(
self,
&GestureHoldEndEvent {
serial,
time: event.time_msec(),
cancelled: event.cancelled(),
},
);
}
InputEvent::TouchDown { .. } => (),
InputEvent::TouchMotion { .. } => (),
InputEvent::TouchUp { .. } => (),
InputEvent::TouchCancel { .. } => (),
InputEvent::TouchFrame { .. } => (),
InputEvent::Special(_) => (),
}
}
pub fn process_libinput_event(&mut self, event: &mut InputEvent<LibinputInputBackend>) {
if let InputEvent::DeviceAdded { device } = event {
// According to Mutter code, this setting is specific to touchpads.
let is_touchpad = device.config_tap_finger_count() > 0;
if is_touchpad {
let c = &self.niri.config.borrow().input.touchpad;
let _ = device.config_tap_set_enabled(c.tap);
let _ = device.config_scroll_set_natural_scroll_enabled(c.natural_scroll);
let _ = device.config_accel_set_speed(c.accel_speed);
}
}
}
}