| 1 | //! Input handling shared by every backend. Backends translate their events |
| 2 | //! into these calls, with pointer positions in global logical coordinates. |
| 3 | |
| 4 | use crate::state::State; |
| 5 | use monolith_layout::{Rect, WindowId}; |
| 6 | use smithay::backend::input::{ButtonState, InputTime, KeyState}; |
| 7 | use smithay::backend::session::Session; |
| 8 | use smithay::input::keyboard::{FilterResult, Keycode, xkb}; |
| 9 | use smithay::input::pointer::{AxisFrame, ButtonEvent, MotionEvent}; |
| 10 | use smithay::utils::{Logical, Point, SERIAL_COUNTER}; |
| 11 | use smithay::wayland::shell::wlr_layer::Layer; |
| 12 | |
| 13 | impl State { |
| 14 | pub fn pointer_to(&mut self, pos: Point<f64, Logical>, time: InputTime) { |
| 15 | self.input.pointer_pos = pos; |
| 16 | self.activity(); |
| 17 | if drag_to(self, pos) { |
| 18 | return; |
| 19 | } |
| 20 | let Some(pointer) = self.input.seat.get_pointer() else { |
| 21 | return; |
| 22 | }; |
| 23 | focus_on_hover(self, pos, self.input.warping); |
| 24 | let under = self.surface_under(pos); |
| 25 | pointer.motion( |
| 26 | self, |
| 27 | under, |
| 28 | &MotionEvent { |
| 29 | location: pos, |
| 30 | serial: SERIAL_COUNTER.next_serial(), |
| 31 | time, |
| 32 | }, |
| 33 | ); |
| 34 | } |
| 35 | |
| 36 | pub fn pointer_button(&mut self, button: u32, pressed: ButtonState, time: InputTime) { |
| 37 | self.activity(); |
| 38 | if pressed == ButtonState::Released && self.input.drag.take().is_some() { |
| 39 | return; |
| 40 | } |
| 41 | if pressed == ButtonState::Pressed && !self.services.lock.active() { |
| 42 | focus_under_pointer(self); |
| 43 | if start_drag(self, button) { |
| 44 | return; |
| 45 | } |
| 46 | } |
| 47 | let Some(pointer) = self.input.seat.get_pointer() else { |
| 48 | return; |
| 49 | }; |
| 50 | pointer.button( |
| 51 | self, |
| 52 | &ButtonEvent { |
| 53 | button, |
| 54 | state: pressed, |
| 55 | serial: SERIAL_COUNTER.next_serial(), |
| 56 | time, |
| 57 | }, |
| 58 | ); |
| 59 | } |
| 60 | |
| 61 | pub fn pointer_axis(&mut self, frame: AxisFrame) { |
| 62 | self.activity(); |
| 63 | if let Some(pointer) = self.input.seat.get_pointer() { |
| 64 | pointer.axis(self, frame); |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | pub fn pointer_frame(&mut self) { |
| 69 | if let Some(pointer) = self.input.seat.get_pointer() { |
| 70 | pointer.frame(self); |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | pub fn key(&mut self, code: Keycode, pressed: KeyState, time: InputTime) { |
| 75 | self.activity(); |
| 76 | let Some(keyboard) = self.input.seat.get_keyboard() else { |
| 77 | return; |
| 78 | }; |
| 79 | let action = keyboard.input( |
| 80 | self, |
| 81 | code, |
| 82 | pressed, |
| 83 | SERIAL_COUNTER.next_serial(), |
| 84 | time, |
| 85 | |state, mods, handle| { |
| 86 | if pressed == KeyState::Released { |
| 87 | let before = state.input.suppressed.len(); |
| 88 | state.input.suppressed.retain(|k| *k != code); |
| 89 | return if state.input.suppressed.len() < before { |
| 90 | FilterResult::Intercept(None) |
| 91 | } else { |
| 92 | FilterResult::Forward |
| 93 | }; |
| 94 | } |
| 95 | let sym = handle.modified_sym().raw(); |
| 96 | if (xkb::keysyms::KEY_XF86Switch_VT_1..=xkb::keysyms::KEY_XF86Switch_VT_12) |
| 97 | .contains(&sym) |
| 98 | { |
| 99 | state.input.suppressed.push(code); |
| 100 | let vt = (sym - xkb::keysyms::KEY_XF86Switch_VT_1 + 1) as i32; |
| 101 | return FilterResult::Intercept(Some(Action::Vt(vt))); |
| 102 | } |
| 103 | if state.services.lock.active() { |
| 104 | return FilterResult::Forward; |
| 105 | } |
| 106 | match state.settings.binds.find(mods, &handle.raw_syms()) { |
| 107 | Some(cmd) => { |
| 108 | tracing::debug!(?cmd, "bind"); |
| 109 | state.input.suppressed.push(code); |
| 110 | FilterResult::Intercept(Some(Action::Run(cmd))) |
| 111 | } |
| 112 | None => FilterResult::Forward, |
| 113 | } |
| 114 | }, |
| 115 | ); |
| 116 | match action { |
| 117 | Some(Some(Action::Run(cmd))) => { |
| 118 | self.wm.layer_focus = None; |
| 119 | if let Err(e) = self.run(cmd) { |
| 120 | tracing::warn!("bind failed: {e}"); |
| 121 | } |
| 122 | } |
| 123 | Some(Some(Action::Vt(vt))) => { |
| 124 | if let Some(drm) = self.backend.drm_mut() |
| 125 | && let Err(e) = drm.session.change_vt(vt) |
| 126 | { |
| 127 | tracing::warn!("switching to VT {vt} failed: {e}"); |
| 128 | } |
| 129 | } |
| 130 | _ => {} |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | enum Action { |
| 136 | Run(monolith_ipc::Command), |
| 137 | Vt(i32), |
| 138 | } |
| 139 | |
| 140 | fn focus_under_pointer(state: &mut State) { |
| 141 | let pos = state.input.pointer_pos; |
| 142 | let layers = [Layer::Overlay, Layer::Top, Layer::Bottom, Layer::Background]; |
| 143 | if let Some((layer, surface, _)) = state.layer_under(pos, &layers) |
| 144 | && layer.can_receive_keyboard_focus() |
| 145 | { |
| 146 | state.wm.layer_focus = Some(surface); |
| 147 | state.sync_focus(); |
| 148 | return; |
| 149 | } |
| 150 | if state.wm.layer_focus.take().is_some() { |
| 151 | state.sync_focus(); |
| 152 | } |
| 153 | let id = window_at(state, pos); |
| 154 | if let Some(id) = id |
| 155 | && state.wm.layout.focused() != Some(id) |
| 156 | { |
| 157 | state.input.followed = Some(id); |
| 158 | state.wm.layout.focus(id); |
| 159 | state.apply_layout(); |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | fn window_at(state: &State, pos: Point<f64, Logical>) -> Option<WindowId> { |
| 164 | let (w, _) = state.wm.space.element_under(pos)?; |
| 165 | state.window_id(&crate::wm::root(w)?) |
| 166 | } |
| 167 | |
| 168 | /// Layers above windows, like a bar, keep the focus where it was. |
| 169 | /// Focus changes only when the pointer enters another window. Moving |
| 170 | /// inside one, or being warped, leaves focus alone, so a new window keeps |
| 171 | /// it until the pointer actually goes somewhere else. |
| 172 | fn focus_on_hover(state: &mut State, pos: Point<f64, Logical>, warped: bool) { |
| 173 | if !state.settings.config.general.focus_follows_mouse |
| 174 | || state.services.lock.active() |
| 175 | || state |
| 176 | .layer_under(pos, &[Layer::Overlay, Layer::Top]) |
| 177 | .is_some() |
| 178 | { |
| 179 | return; |
| 180 | } |
| 181 | let under = window_at(state, pos); |
| 182 | if std::mem::replace(&mut state.input.hovered, under) == under || warped { |
| 183 | return; |
| 184 | } |
| 185 | let Some(id) = under else { |
| 186 | return; |
| 187 | }; |
| 188 | if state.wm.layout.focused() == Some(id) { |
| 189 | return; |
| 190 | } |
| 191 | state.wm.layer_focus = None; |
| 192 | state.input.followed = Some(id); |
| 193 | state.wm.layout.focus(id); |
| 194 | state.apply_layout(); |
| 195 | } |
| 196 | |
| 197 | impl State { |
| 198 | /// Puts the pointer in the middle of a window that gained focus from |
| 199 | /// anything but the pointer. |
| 200 | pub fn follow_focus(&mut self) { |
| 201 | let focused = self.wm.layout.focused(); |
| 202 | if focused == std::mem::replace(&mut self.input.followed, focused) |
| 203 | || !self.settings.config.general.mouse_follows_focus |
| 204 | || self.backend.drm().is_none() |
| 205 | || self.services.lock.active() |
| 206 | { |
| 207 | return; |
| 208 | } |
| 209 | let Some((_, r, _)) = focused.and_then(|id| self.wm.get(id)?.placed) else { |
| 210 | return; |
| 211 | }; |
| 212 | self.input.warping = true; |
| 213 | self.pointer_to( |
| 214 | Point::from((r.x + r.w / 2.0, r.y + r.h / 2.0)), |
| 215 | InputTime::now(), |
| 216 | ); |
| 217 | self.input.warping = false; |
| 218 | self.pointer_frame(); |
| 219 | self.dirty.cursor = true; |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | const BTN_LEFT: u32 = 0x110; |
| 224 | const BTN_RIGHT: u32 = 0x111; |
| 225 | |
| 226 | /// A floating window being moved or resized with Mod and a mouse button. |
| 227 | pub struct Drag { |
| 228 | window: WindowId, |
| 229 | from: Point<f64, Logical>, |
| 230 | rect: Rect, |
| 231 | resize: bool, |
| 232 | } |
| 233 | |
| 234 | fn start_drag(state: &mut State, button: u32) -> bool { |
| 235 | if button != BTN_LEFT && button != BTN_RIGHT { |
| 236 | return false; |
| 237 | } |
| 238 | let held = state |
| 239 | .input |
| 240 | .seat |
| 241 | .get_keyboard() |
| 242 | .map(|k| k.modifier_state()) |
| 243 | .is_some_and(|m| crate::commands::held(&m).has(state.settings.config.general.mod_key)); |
| 244 | let Some(id) = state.wm.layout.focused() else { |
| 245 | return false; |
| 246 | }; |
| 247 | let Some(rect) = state |
| 248 | .wm |
| 249 | .layout |
| 250 | .window(id) |
| 251 | .filter(|w| w.floating && !w.fullscreen) |
| 252 | .and_then(|w| w.float_rect) |
| 253 | else { |
| 254 | return false; |
| 255 | }; |
| 256 | if !held { |
| 257 | return false; |
| 258 | } |
| 259 | state.input.drag = Some(Drag { |
| 260 | window: id, |
| 261 | from: state.input.pointer_pos, |
| 262 | rect, |
| 263 | resize: button == BTN_RIGHT, |
| 264 | }); |
| 265 | true |
| 266 | } |
| 267 | |
| 268 | fn drag_to(state: &mut State, pos: Point<f64, Logical>) -> bool { |
| 269 | let Some(d) = &state.input.drag else { |
| 270 | return false; |
| 271 | }; |
| 272 | let (dx, dy) = (pos.x - d.from.x, pos.y - d.from.y); |
| 273 | let r = d.rect; |
| 274 | let rect = if d.resize { |
| 275 | Rect::new(r.x, r.y, (r.w + dx).max(32.0), (r.h + dy).max(32.0)) |
| 276 | } else { |
| 277 | Rect::new(r.x + dx, r.y + dy, r.w, r.h) |
| 278 | }; |
| 279 | let id = d.window; |
| 280 | state.wm.layout.set_float_rect(id, rect); |
| 281 | state.apply_layout(); |
| 282 | true |
| 283 | } |