// SPDX-License-Identifier: GPL-3.0-or-later pub mod libinput; use std::{collections::HashMap, mem::Discriminant, time::Duration}; use crate::{focus::pointer::PointerFocusTarget, state::WithState}; use pinnacle_api_defs::pinnacle::input::v0alpha1::{ set_libinput_setting_request::Setting, set_mousebind_request, SetKeybindResponse, SetMousebindResponse, }; use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent, }, desktop::{layer_map_for_output, space::SpaceElement, WindowSurfaceType}, input::{ keyboard::{keysyms, FilterResult, ModifiersState}, pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent}, }, reexports::input::{self, Led}, utils::{Logical, Point, SERIAL_COUNTER}, wayland::shell::wlr_layer, }; use tokio::sync::mpsc::UnboundedSender; use xkbcommon::xkb::Keysym; use crate::state::State; bitflags::bitflags! { #[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)] pub struct ModifierMask: u8 { const SHIFT = 1; const CTRL = 1 << 1; const ALT = 1 << 2; const SUPER = 1 << 3; } } impl From for ModifierMask { fn from(modifiers: ModifiersState) -> Self { let mut mask = ModifierMask::empty(); if modifiers.alt { mask |= ModifierMask::ALT; } if modifiers.shift { mask |= ModifierMask::SHIFT; } if modifiers.ctrl { mask |= ModifierMask::CTRL; } if modifiers.logo { mask |= ModifierMask::SUPER; } mask } } impl From<&ModifiersState> for ModifierMask { fn from(modifiers: &ModifiersState) -> Self { let mut mask = ModifierMask::empty(); if modifiers.alt { mask |= ModifierMask::ALT; } if modifiers.shift { mask |= ModifierMask::SHIFT; } if modifiers.ctrl { mask |= ModifierMask::CTRL; } if modifiers.logo { mask |= ModifierMask::SUPER; } mask } } #[derive(Default)] pub struct InputState { pub reload_keybind: Option<(ModifierMask, Keysym)>, pub kill_keybind: Option<(ModifierMask, Keysym)>, /// All libinput devices that have been connected pub libinput_devices: Vec, pub keybinds: HashMap<(ModifierMask, Keysym), UnboundedSender>>, pub mousebinds: HashMap< (ModifierMask, u32, set_mousebind_request::MouseEdge), UnboundedSender>, >, #[allow(clippy::type_complexity)] pub libinput_settings: HashMap, Box>, } impl InputState { pub fn clear(&mut self) { self.reload_keybind = None; self.kill_keybind = None; self.libinput_devices.clear(); self.keybinds.clear(); self.mousebinds.clear(); self.libinput_settings.clear(); } } impl std::fmt::Debug for InputState { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("InputState") .field("reload_keybind", &self.reload_keybind) .field("kill_keybind", &self.kill_keybind) .field("libinput_devices", &self.libinput_devices) .field("keybinds", &self.keybinds) .field("mousebinds", &self.mousebinds) .field("libinput_settings", &"...") .finish() } } impl InputState { pub fn new() -> Self { Default::default() } } #[derive(Debug)] enum KeyAction { CallCallback(UnboundedSender>), Quit, SwitchVt(i32), ReloadConfig, } impl State { pub fn process_input_event(&mut self, event: InputEvent) { match event { // TODO: rest of input events // InputEvent::DeviceAdded { device } => todo!(), // InputEvent::DeviceRemoved { device } => todo!(), InputEvent::Keyboard { event } => self.keyboard::(event), InputEvent::PointerMotion { event } => self.pointer_motion::(event), InputEvent::PointerMotionAbsolute { event } => self.pointer_motion_absolute::(event), InputEvent::PointerButton { event } => self.pointer_button::(event), InputEvent::PointerAxis { event } => self.pointer_axis::(event), _ => (), } } /// Get the [`PointerFocusTarget`] under `point` along with its origin in the global space. pub fn pointer_focus_target_under

( &self, point: P, ) -> Option<(PointerFocusTarget, Point)> where P: Into>, { let point: Point = point.into(); let output = self.space.outputs().find(|op| { self.space .output_geometry(op) .expect("called output_geometry on unmapped output (this shouldn't happen here)") .contains(point.to_i32_round()) })?; let output_geo = self .space .output_geometry(output) .expect("called output_geometry on unmapped output"); let layers = layer_map_for_output(output); let top_fullscreen_window = output .with_state(|state| state.focus_stack.stack.clone()) .into_iter() .rev() .find(|win| { win.with_state(|state| { state.fullscreen_or_maximized.is_fullscreen() && output.with_state(|op_state| { op_state .focused_tags() .any(|op_tag| state.tags.contains(op_tag)) }) }) }); if let Some(window) = top_fullscreen_window { let loc = self .space .element_location(&window) .expect("called elem loc on unmapped win") - window.geometry().loc; window .surface_under(point - loc.to_f64(), WindowSurfaceType::ALL) .map(|(surf, surf_loc)| (PointerFocusTarget::WlSurface(surf), surf_loc + loc)) } else if let (Some(layer), _) | (None, Some(layer)) = ( layers.layer_under(wlr_layer::Layer::Overlay, point), layers.layer_under(wlr_layer::Layer::Top, point), ) { let layer_loc = layers.layer_geometry(layer).expect("no layer geo").loc; layer .surface_under( point - layer_loc.to_f64() - output_geo.loc.to_f64(), WindowSurfaceType::ALL, ) .map(|(surf, surf_loc)| { ( PointerFocusTarget::WlSurface(surf), surf_loc + layer_loc + output_geo.loc, ) }) } else if let Some((surface, loc)) = self .space .elements() .rev() .filter(|win| win.is_on_active_tag()) .find_map(|win| { let loc = self .space .element_location(win) .expect("called elem loc on unmapped win") - win.geometry().loc; win.surface_under(point - loc.to_f64(), WindowSurfaceType::ALL) .map(|(surf, surf_loc)| (surf, surf_loc + loc)) }) { Some((PointerFocusTarget::WlSurface(surface), loc)) } else if let (Some(layer), _) | (None, Some(layer)) = ( layers.layer_under(wlr_layer::Layer::Overlay, point), layers.layer_under(wlr_layer::Layer::Top, point), ) { let layer_loc = layers.layer_geometry(layer).expect("no layer geo").loc; layer .surface_under( point - layer_loc.to_f64() - output_geo.loc.to_f64(), WindowSurfaceType::ALL, ) .map(|(surf, surf_loc)| { ( PointerFocusTarget::WlSurface(surf), surf_loc + layer_loc + output_geo.loc, ) }) } else { None } } /// Update the pointer focus if it's different from the previous one. pub fn update_pointer_focus(&mut self) { let Some(pointer) = self.seat.get_pointer() else { return; }; let location = pointer.current_location(); let surface_under = self.pointer_focus_target_under(location); if pointer .current_focus() .is_some_and(|foc| matches!(&surface_under, Some((f, _)) if f == &foc)) { return; } pointer.motion( self, surface_under, &MotionEvent { location, serial: SERIAL_COUNTER.next_serial(), time: Duration::from(self.clock.now()).as_millis() as u32, }, ); pointer.frame(self); } fn keyboard(&mut self, event: I::KeyboardKeyEvent) { let serial = SERIAL_COUNTER.next_serial(); let time = event.time_msec(); let press_state = event.state(); let reload_keybind = self.input_state.reload_keybind; let kill_keybind = self.input_state.kill_keybind; let keyboard = self.seat.get_keyboard().expect("Seat has no keyboard"); let modifiers = keyboard.modifier_state(); let mut leds = Led::empty(); if modifiers.num_lock { leds |= Led::NUMLOCK; } if modifiers.caps_lock { leds |= Led::CAPSLOCK; } // FIXME: Leds only update once another key is pressed. for device in self.input_state.libinput_devices.iter_mut() { device.led_update(leds); } let action = keyboard.input( self, event.key_code(), press_state, serial, time, |state, modifiers, keysym| { // tracing::debug!(keysym = ?keysym, raw_keysyms = ?keysym.raw_syms(), modified_syms = ?keysym.modified_syms()); if press_state == KeyState::Pressed { let mod_mask = ModifierMask::from(modifiers); let raw_sym = keysym.raw_syms().iter().next(); let mod_sym = keysym.modified_sym(); if let (Some(sender), _) | (None, Some(sender)) = ( state.input_state.keybinds.get(&(mod_mask, mod_sym)), raw_sym.and_then(|raw_sym| { state.input_state.keybinds.get(&(mod_mask, *raw_sym)) }), ) { return FilterResult::Intercept(KeyAction::CallCallback(sender.clone())); } if kill_keybind == Some((mod_mask, mod_sym)) { return FilterResult::Intercept(KeyAction::Quit); } else if reload_keybind == Some((mod_mask, mod_sym)) { return FilterResult::Intercept(KeyAction::ReloadConfig); } else if let mut vt @ keysyms::KEY_XF86Switch_VT_1 ..=keysyms::KEY_XF86Switch_VT_12 = keysym.modified_sym().raw() { vt = vt - keysyms::KEY_XF86Switch_VT_1 + 1; tracing::info!("Switching to vt {vt}"); return FilterResult::Intercept(KeyAction::SwitchVt(vt as i32)); } } FilterResult::Forward }, ); match action { Some(KeyAction::CallCallback(sender)) => { let _ = sender.send(Ok(SetKeybindResponse {})); } Some(KeyAction::SwitchVt(vt)) => { self.switch_vt(vt); } Some(KeyAction::Quit) => { self.shutdown(); } Some(KeyAction::ReloadConfig) => { self.start_config(self.config.dir(&self.xdg_base_dirs)) .expect("failed to restart config"); } None => (), } } fn pointer_button(&mut self, event: I::PointerButtonEvent) { let pointer = self.seat.get_pointer().expect("Seat has no pointer"); // FIXME: handle err let keyboard = self.seat.get_keyboard().expect("Seat has no keyboard"); // FIXME: handle err let serial = SERIAL_COUNTER.next_serial(); let button = event.button_code(); let button_state = event.state(); let pointer_loc = pointer.current_location(); let mod_mask = ModifierMask::from(keyboard.modifier_state()); let mouse_edge = match button_state { ButtonState::Released => set_mousebind_request::MouseEdge::Release, ButtonState::Pressed => set_mousebind_request::MouseEdge::Press, }; if let Some(stream) = self .input_state .mousebinds .get(&(mod_mask, button, mouse_edge)) { let _ = stream.send(Ok(SetMousebindResponse {})); return; } // If the button was clicked, focus on the window below if exists, else // unfocus on windows. if button_state == ButtonState::Pressed { if let Some((focus, _)) = self.pointer_focus_target_under(pointer_loc) { // NOTE: *Do not* set keyboard focus to an override redirect window. This leads // | to wonky things like right-click menus not correctly getting pointer // | clicks or showing up at all. // TODO: use update_keyboard_focus from anvil if let Some(window) = focus.window_for(self) { self.raise_window(window.clone(), true); if let Some(output) = window.output(self) { output.with_state_mut(|state| state.focus_stack.set_focus(window.clone())); } } if !matches!( focus.window_for(self), Some(window) if window.is_x11_override_redirect() ) { keyboard.set_focus(self, focus.to_keyboard_focus_target(self), serial); } for window in self.space.elements() { if let Some(toplevel) = window.toplevel() { toplevel.send_configure(); } } } else { if let Some(focused_op) = self.focused_output() { focused_op.with_state_mut(|state| { state.focus_stack.unset_focus(); for window in state.focus_stack.stack.iter() { window.set_activate(false); if let Some(toplevel) = window.toplevel() { toplevel.send_configure(); } } }); } keyboard.set_focus(self, None, serial); } }; pointer.button( self, &ButtonEvent { button, state: button_state, serial, time: event.time_msec(), }, ); pointer.frame(self); } fn pointer_axis(&mut self, event: I::PointerAxisEvent) { let source = event.source(); let horizontal_amount = event .amount(Axis::Horizontal) .unwrap_or_else(|| event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 3.0 / 120.); let vertical_amount = event .amount(Axis::Vertical) .unwrap_or_else(|| event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 3.0 / 120.); let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal); let vertical_amount_discrete = event.amount_v120(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.v120(Axis::Horizontal, discrete as i32); } } else if source == AxisSource::Finger { frame = frame.stop(Axis::Horizontal); } if vertical_amount != 0.0 { frame = frame.value(Axis::Vertical, vertical_amount); if let Some(discrete) = vertical_amount_discrete { frame = frame.v120(Axis::Vertical, discrete as i32); } } else if source == AxisSource::Finger { frame = frame.stop(Axis::Vertical); } let pointer = self.seat.get_pointer().expect("Seat has no pointer"); pointer.axis(self, frame); pointer.frame(self); } /// Clamp pointer coordinates inside outputs. /// /// This returns the nearest point inside an output. fn clamp_coords(&self, pos: Point) -> Point { if self.space.outputs().next().is_none() { return pos; } let (pos_x, pos_y) = pos.into(); let nearest_points = self.space.outputs().map(|op| { let size = self .space .output_geometry(op) .expect("called output_geometry on unmapped output") .size; let loc = op.current_location(); let pos_x = pos_x.clamp(loc.x as f64, (loc.x + size.w) as f64); let pos_y = pos_y.clamp(loc.y as f64, (loc.y + size.h) as f64); (pos_x, pos_y) }); let nearest_point = nearest_points.min_by(|(x1, y1), (x2, y2)| { f64::total_cmp( &((pos_x - x1).powi(2) + (pos_y - y1).powi(2)).sqrt(), &((pos_x - x2).powi(2) + (pos_y - y2).powi(2)).sqrt(), ) }); nearest_point.map(|point| point.into()).unwrap_or(pos) } /// Handle an absolute pointer motion event. /// /// This *should* only be generated on the winit backend. /// Unless there's a case where it's generated on udev that I'm unaware of. fn pointer_motion_absolute(&mut self, event: I::PointerMotionAbsoluteEvent) { let Some(pointer) = self.seat.get_pointer() else { tracing::error!("Pointer motion absolute received with no pointer on seat"); return; }; let Some(output) = self.space.outputs().next() else { return; }; let Some(output_geo) = self.space.output_geometry(output) else { unreachable!("output should have a geometry as it was mapped"); }; let pointer_loc = event.position_transformed(output_geo.size) + output_geo.loc.to_f64(); let serial = SERIAL_COUNTER.next_serial(); if let Some(output) = self.space.output_under(pointer_loc).next().cloned() { self.output_focus_stack.set_focus(output); } let pointer_focus = self.pointer_focus_target_under(pointer_loc); pointer.motion( self, pointer_focus, &MotionEvent { location: pointer_loc, serial, time: event.time_msec(), }, ); pointer.frame(self); } fn pointer_motion(&mut self, event: I::PointerMotionEvent) { let Some(pointer) = self.seat.get_pointer() else { tracing::error!("Pointer motion received with no pointer on seat"); return; }; let mut pointer_loc = pointer.current_location(); pointer_loc += event.delta(); // clamp to screen limits // this event is never generated by winit pointer_loc = self.clamp_coords(pointer_loc); if let Some(output) = self.space.output_under(pointer_loc).next().cloned() { self.output_focus_stack.set_focus(output); } let surface_under = self.pointer_focus_target_under(pointer_loc); pointer.motion( self, surface_under.clone(), &MotionEvent { location: pointer_loc, serial: SERIAL_COUNTER.next_serial(), time: event.time_msec(), }, ); pointer.relative_motion( self, surface_under, &RelativeMotionEvent { delta: event.delta(), delta_unaccel: event.delta_unaccel(), utime: event.time(), }, ); pointer.frame(self); if let Some(output) = self.focused_output().cloned() { self.schedule_render(&output); } } }