Rust · 21828 bytes Raw Blame History
1 //! Input grabber - captures all keyboard/mouse input when active
2 //!
3 //! Uses layer-shell with exclusive keyboard grab to intercept all input.
4
5 use std::sync::atomic::{AtomicBool, Ordering};
6 use std::sync::Arc;
7 use std::thread;
8
9 use smithay_client_toolkit::{
10 compositor::{CompositorHandler, CompositorState},
11 delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer,
12 delegate_registry, delegate_seat, delegate_shm,
13 output::{OutputHandler, OutputState},
14 registry::{ProvidesRegistryState, RegistryState},
15 registry_handlers,
16 seat::{
17 keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers},
18 pointer::{PointerEvent, PointerEventKind, PointerHandler},
19 Capability, SeatHandler, SeatState,
20 },
21 shell::{
22 wlr_layer::{
23 Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
24 LayerSurfaceConfigure,
25 },
26 WaylandSurface,
27 },
28 shm::{slot::SlotPool, Shm, ShmHandler},
29 };
30 use wayland_client::{
31 globals::registry_queue_init,
32 protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
33 Connection, QueueHandle,
34 };
35
36 use hyprkvm_common::protocol::{InputEventPayload, InputEventType};
37
38 /// Input event from the grabber
39 #[derive(Debug, Clone)]
40 pub enum GrabEvent {
41 KeyDown { keycode: u32 },
42 KeyUp { keycode: u32 },
43 PointerMotion { dx: f64, dy: f64 },
44 PointerButton { button: u32, pressed: bool },
45 Scroll { horizontal: f64, vertical: f64 },
46 ModifiersChanged { mods: Modifiers },
47 /// Hotkey detected during recovery monitoring (bypasses libinput stale state)
48 RecoveryHotkey { direction: hyprkvm_common::Direction },
49 }
50
51 impl GrabEvent {
52 pub fn to_protocol(&self, seq: u64) -> InputEventPayload {
53 let timestamp_us = std::time::SystemTime::now()
54 .duration_since(std::time::UNIX_EPOCH)
55 .unwrap()
56 .as_micros() as u64;
57
58 let event = match self {
59 GrabEvent::KeyDown { keycode } => InputEventType::KeyDown { keycode: *keycode },
60 GrabEvent::KeyUp { keycode } => InputEventType::KeyUp { keycode: *keycode },
61 GrabEvent::PointerMotion { dx, dy } => InputEventType::PointerMotion { dx: *dx, dy: *dy },
62 GrabEvent::PointerButton { button, pressed } => {
63 InputEventType::PointerButton {
64 button: *button,
65 pressed: *pressed,
66 }
67 }
68 GrabEvent::Scroll { horizontal, vertical } => {
69 InputEventType::Scroll {
70 horizontal: *horizontal,
71 vertical: *vertical,
72 }
73 }
74 GrabEvent::ModifiersChanged { mods } => {
75 InputEventType::ModifierState {
76 shift: mods.shift,
77 ctrl: mods.ctrl,
78 alt: mods.alt,
79 super_key: mods.logo,
80 }
81 }
82 GrabEvent::RecoveryHotkey { .. } => {
83 // This is a local-only event, should never be sent over network
84 panic!("RecoveryHotkey cannot be converted to protocol");
85 }
86 };
87
88 InputEventPayload {
89 sequence: seq,
90 timestamp_us,
91 event,
92 }
93 }
94 }
95
96 /// Input grabber configuration
97 pub struct InputGrabberConfig {
98 /// Hide the cursor while grabbing
99 pub hide_cursor: bool,
100 }
101
102 impl Default for InputGrabberConfig {
103 fn default() -> Self {
104 Self { hide_cursor: true }
105 }
106 }
107
108 /// Input grabber - intercepts all keyboard and mouse input
109 pub struct InputGrabber {
110 active: Arc<AtomicBool>,
111 event_rx: std::sync::mpsc::Receiver<GrabEvent>,
112 _thread: thread::JoinHandle<()>,
113 }
114
115 impl InputGrabber {
116 /// Create a new input grabber
117 pub fn new(config: InputGrabberConfig) -> Result<Self, GrabberError> {
118 let active = Arc::new(AtomicBool::new(false));
119 let active_clone = active.clone();
120
121 let (event_tx, event_rx) = std::sync::mpsc::channel();
122
123 let thread = thread::Builder::new()
124 .name("input-grabber".to_string())
125 .spawn(move || {
126 if let Err(e) = run_grabber(active_clone, event_tx, config) {
127 tracing::error!("Grabber thread error: {}", e);
128 }
129 })
130 .map_err(|e| GrabberError::Thread(e.to_string()))?;
131
132 Ok(Self {
133 active,
134 event_rx,
135 _thread: thread,
136 })
137 }
138
139 /// Start grabbing input
140 pub fn start(&self) {
141 tracing::info!("Starting input grab");
142 self.active.store(true, Ordering::SeqCst);
143 }
144
145 /// Stop grabbing input
146 pub fn stop(&self) {
147 tracing::info!("Stopping input grab");
148 self.active.store(false, Ordering::SeqCst);
149 }
150
151 /// Check if currently grabbing
152 pub fn is_active(&self) -> bool {
153 self.active.load(Ordering::SeqCst)
154 }
155
156 /// Try to receive a grab event (non-blocking)
157 pub fn try_recv(&self) -> Option<GrabEvent> {
158 self.event_rx.try_recv().ok()
159 }
160 }
161
162 fn run_grabber(
163 active: Arc<AtomicBool>,
164 event_tx: std::sync::mpsc::Sender<GrabEvent>,
165 _config: InputGrabberConfig,
166 ) -> Result<(), GrabberError> {
167 let conn = Connection::connect_to_env()
168 .map_err(|e| GrabberError::Connection(e.to_string()))?;
169
170 let (globals, mut event_queue) = registry_queue_init(&conn)
171 .map_err(|e| GrabberError::Registry(e.to_string()))?;
172
173 let qh = event_queue.handle();
174
175 let shm_state = Shm::bind(&globals, &qh)
176 .map_err(|e| GrabberError::Protocol(format!("shm: {}", e)))?;
177
178 let mut state = GrabberState {
179 active,
180 event_tx,
181 registry_state: RegistryState::new(&globals),
182 seat_state: SeatState::new(&globals, &qh),
183 output_state: OutputState::new(&globals, &qh),
184 compositor_state: CompositorState::bind(&globals, &qh)
185 .map_err(|e| GrabberError::Protocol(e.to_string()))?,
186 layer_shell: LayerShell::bind(&globals, &qh)
187 .map_err(|e| GrabberError::Protocol(e.to_string()))?,
188 shm_state,
189 pool: None,
190
191 layer_surface: None,
192 keyboard: None,
193 pointer: None,
194 last_pointer_pos: (0.0, 0.0),
195 configured: false,
196 running: true,
197 surface_width: 1,
198 surface_height: 1,
199 is_mapped: false,
200 was_active: false,
201 };
202
203 // Wait for first output
204 event_queue
205 .roundtrip(&mut state)
206 .map_err(|e| GrabberError::Dispatch(e.to_string()))?;
207
208 // Create shm pool for buffers - needs to be large enough for fullscreen
209 // 4K display = 3840x2160x4 = ~33MB, allocate 64MB to be safe
210 state.pool = Some(
211 SlotPool::new(64 * 1024 * 1024, &state.shm_state)
212 .map_err(|e| GrabberError::Protocol(format!("pool: {}", e)))?,
213 );
214
215 // Create grabber surface (invisible fullscreen layer)
216 if let Some(output) = state.output_state.outputs().next() {
217 let surface = state.compositor_state.create_surface(&qh);
218
219 let layer = state.layer_shell.create_layer_surface(
220 &qh,
221 surface,
222 Layer::Overlay,
223 Some("hyprkvm-grabber"),
224 Some(&output),
225 );
226
227 // Configure for input grab - fullscreen transparent surface
228 // With all anchors and size 0,0, compositor will expand to fill output
229 layer.set_anchor(Anchor::all());
230 layer.set_exclusive_zone(-1); // Don't push other windows
231 layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
232 layer.set_size(0, 0); // Let compositor determine size (fullscreen)
233
234 layer.commit();
235 state.layer_surface = Some(layer);
236 }
237
238 // Another roundtrip to process configure
239 event_queue
240 .roundtrip(&mut state)
241 .map_err(|e| GrabberError::Dispatch(e.to_string()))?;
242
243 tracing::info!("Input grabber initialized, waiting for activation");
244
245 // Main loop - poll active flag and manage surface
246 loop {
247 if !state.running {
248 break;
249 }
250
251 // Check if active state changed
252 let is_active = state.active.load(Ordering::SeqCst);
253 if is_active != state.was_active {
254 state.was_active = is_active;
255 state.update_surface_mapping(is_active);
256 }
257
258 // Use dispatch with timeout to allow periodic checking of active flag
259 event_queue
260 .dispatch_pending(&mut state)
261 .map_err(|e| GrabberError::Dispatch(e.to_string()))?;
262
263 // Flush and prepare read
264 if let Some(guard) = event_queue
265 .prepare_read()
266 {
267 // Read events with timeout (50ms)
268 let _ = guard.read();
269 }
270
271 // Small sleep to avoid busy-looping
272 std::thread::sleep(std::time::Duration::from_millis(10));
273 }
274
275 Ok(())
276 }
277
278 struct GrabberState {
279 active: Arc<AtomicBool>,
280 event_tx: std::sync::mpsc::Sender<GrabEvent>,
281 registry_state: RegistryState,
282 seat_state: SeatState,
283 output_state: OutputState,
284 compositor_state: CompositorState,
285 layer_shell: LayerShell,
286 shm_state: Shm,
287 pool: Option<SlotPool>,
288
289 layer_surface: Option<LayerSurface>,
290 keyboard: Option<wl_keyboard::WlKeyboard>,
291 pointer: Option<wl_pointer::WlPointer>,
292 last_pointer_pos: (f64, f64),
293 configured: bool,
294 running: bool,
295 surface_width: u32,
296 surface_height: u32,
297 /// Track if surface is currently mapped (has buffer attached)
298 is_mapped: bool,
299 /// Last known active state - for detecting transitions
300 was_active: bool,
301 }
302
303 impl CompositorHandler for GrabberState {
304 fn scale_factor_changed(
305 &mut self,
306 _: &Connection,
307 _: &QueueHandle<Self>,
308 _: &wl_surface::WlSurface,
309 _: i32,
310 ) {
311 }
312
313 fn frame(
314 &mut self,
315 _: &Connection,
316 _: &QueueHandle<Self>,
317 _: &wl_surface::WlSurface,
318 _: u32,
319 ) {
320 }
321
322 fn transform_changed(
323 &mut self,
324 _: &Connection,
325 _: &QueueHandle<Self>,
326 _: &wl_surface::WlSurface,
327 _: wl_output::Transform,
328 ) {
329 }
330
331 fn surface_enter(
332 &mut self,
333 _: &Connection,
334 _: &QueueHandle<Self>,
335 _: &wl_surface::WlSurface,
336 _: &wl_output::WlOutput,
337 ) {
338 }
339
340 fn surface_leave(
341 &mut self,
342 _: &Connection,
343 _: &QueueHandle<Self>,
344 _: &wl_surface::WlSurface,
345 _: &wl_output::WlOutput,
346 ) {
347 }
348 }
349
350 impl OutputHandler for GrabberState {
351 fn output_state(&mut self) -> &mut OutputState {
352 &mut self.output_state
353 }
354
355 fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
356 fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
357 fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
358 }
359
360 impl LayerShellHandler for GrabberState {
361 fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
362 self.running = false;
363 }
364
365 fn configure(
366 &mut self,
367 _: &Connection,
368 _qh: &QueueHandle<Self>,
369 layer: &LayerSurface,
370 configure: LayerSurfaceConfigure,
371 _serial: u32,
372 ) {
373 // Update size from configure event
374 if configure.new_size.0 > 0 {
375 self.surface_width = configure.new_size.0;
376 }
377 if configure.new_size.1 > 0 {
378 self.surface_height = configure.new_size.1;
379 }
380
381 self.configured = true;
382
383 // Only map surface (attach buffer) if we're currently active
384 // Otherwise just acknowledge the configure by committing without a buffer
385 if self.active.load(Ordering::SeqCst) {
386 self.draw_surface(layer);
387 self.is_mapped = true;
388 } else {
389 // Commit without buffer - surface won't be mapped but config is ack'd
390 layer.commit();
391 }
392
393 tracing::debug!(
394 "Grabber surface configured: {}x{}, mapped: {}",
395 self.surface_width,
396 self.surface_height,
397 self.is_mapped
398 );
399 }
400 }
401
402 impl GrabberState {
403 fn update_surface_mapping(&mut self, should_map: bool) {
404 // Clone the wl_surface to avoid borrow issues
405 let wl_surface = match &self.layer_surface {
406 Some(l) => l.wl_surface().clone(),
407 None => return,
408 };
409
410 if should_map {
411 tracing::info!("Grabber activating - mapping surface");
412
413 let pool = match &mut self.pool {
414 Some(pool) => pool,
415 None => {
416 tracing::warn!("No SHM pool available for drawing");
417 return;
418 }
419 };
420
421 let width = self.surface_width;
422 let height = self.surface_height;
423 let stride = width * 4;
424
425 let (buffer, canvas) = match pool.create_buffer(
426 width as i32,
427 height as i32,
428 stride as i32,
429 wl_shm::Format::Argb8888,
430 ) {
431 Ok(result) => result,
432 Err(e) => {
433 tracing::error!("Failed to create buffer: {:?}", e);
434 return;
435 }
436 };
437
438 // Fill with fully transparent pixels (ARGB = 0x00000000)
439 canvas.fill(0);
440
441 // Attach buffer to surface
442 wl_surface.attach(Some(buffer.wl_buffer()), 0, 0);
443 wl_surface.damage_buffer(0, 0, width as i32, height as i32);
444 wl_surface.commit();
445
446 tracing::debug!("Attached {}x{} transparent buffer to grabber surface", width, height);
447 self.is_mapped = true;
448 } else {
449 tracing::info!("Grabber deactivating - unmapping surface");
450 // Unmap by attaching null buffer
451 wl_surface.attach(None, 0, 0);
452 wl_surface.commit();
453 self.is_mapped = false;
454 }
455 }
456
457 fn draw_surface(&mut self, layer: &LayerSurface) {
458 let wl_surface = layer.wl_surface().clone();
459
460 let pool = match &mut self.pool {
461 Some(pool) => pool,
462 None => {
463 tracing::warn!("No SHM pool available for drawing");
464 return;
465 }
466 };
467
468 let width = self.surface_width;
469 let height = self.surface_height;
470 let stride = width * 4;
471
472 let (buffer, canvas) = match pool.create_buffer(
473 width as i32,
474 height as i32,
475 stride as i32,
476 wl_shm::Format::Argb8888,
477 ) {
478 Ok(result) => result,
479 Err(e) => {
480 tracing::error!("Failed to create buffer: {:?}", e);
481 return;
482 }
483 };
484
485 // Fill with fully transparent pixels (ARGB = 0x00000000)
486 canvas.fill(0);
487
488 // Attach buffer to surface
489 wl_surface.attach(Some(buffer.wl_buffer()), 0, 0);
490 wl_surface.damage_buffer(0, 0, width as i32, height as i32);
491 layer.commit();
492
493 tracing::debug!("Attached {}x{} transparent buffer to grabber surface", width, height);
494 }
495 }
496
497 impl ShmHandler for GrabberState {
498 fn shm_state(&mut self) -> &mut Shm {
499 &mut self.shm_state
500 }
501 }
502
503 impl SeatHandler for GrabberState {
504 fn seat_state(&mut self) -> &mut SeatState {
505 &mut self.seat_state
506 }
507
508 fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
509
510 fn new_capability(
511 &mut self,
512 _: &Connection,
513 qh: &QueueHandle<Self>,
514 seat: wl_seat::WlSeat,
515 capability: Capability,
516 ) {
517 if capability == Capability::Keyboard && self.keyboard.is_none() {
518 let keyboard = self.seat_state.get_keyboard(qh, &seat, None)
519 .expect("Failed to get keyboard");
520 self.keyboard = Some(keyboard);
521 }
522
523 if capability == Capability::Pointer && self.pointer.is_none() {
524 let pointer = self.seat_state.get_pointer(qh, &seat)
525 .expect("Failed to get pointer");
526 self.pointer = Some(pointer);
527 }
528 }
529
530 fn remove_capability(
531 &mut self,
532 _: &Connection,
533 _: &QueueHandle<Self>,
534 _: wl_seat::WlSeat,
535 capability: Capability,
536 ) {
537 if capability == Capability::Keyboard {
538 if let Some(keyboard) = self.keyboard.take() {
539 keyboard.release();
540 }
541 }
542 if capability == Capability::Pointer {
543 if let Some(pointer) = self.pointer.take() {
544 pointer.release();
545 }
546 }
547 }
548
549 fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
550 }
551
552 impl KeyboardHandler for GrabberState {
553 fn enter(
554 &mut self,
555 _: &Connection,
556 _: &QueueHandle<Self>,
557 _: &wl_keyboard::WlKeyboard,
558 _: &wl_surface::WlSurface,
559 _: u32,
560 _: &[u32],
561 _: &[Keysym],
562 ) {
563 tracing::info!("Grabber keyboard ENTER - we have keyboard focus");
564 }
565
566 fn leave(
567 &mut self,
568 _: &Connection,
569 _: &QueueHandle<Self>,
570 _: &wl_keyboard::WlKeyboard,
571 _: &wl_surface::WlSurface,
572 _: u32,
573 ) {
574 tracing::info!("Grabber keyboard LEAVE - lost keyboard focus");
575 }
576
577 fn press_key(
578 &mut self,
579 _: &Connection,
580 _: &QueueHandle<Self>,
581 _: &wl_keyboard::WlKeyboard,
582 _: u32,
583 event: KeyEvent,
584 ) {
585 tracing::debug!("Grabber received key press: keycode={}, active={}", event.raw_code, self.active.load(Ordering::SeqCst));
586 if self.active.load(Ordering::SeqCst) {
587 let _ = self.event_tx.send(GrabEvent::KeyDown {
588 keycode: event.raw_code,
589 });
590 }
591 }
592
593 fn release_key(
594 &mut self,
595 _: &Connection,
596 _: &QueueHandle<Self>,
597 _: &wl_keyboard::WlKeyboard,
598 _: u32,
599 event: KeyEvent,
600 ) {
601 if self.active.load(Ordering::SeqCst) {
602 let _ = self.event_tx.send(GrabEvent::KeyUp {
603 keycode: event.raw_code,
604 });
605 }
606 }
607
608 fn update_modifiers(
609 &mut self,
610 _: &Connection,
611 _: &QueueHandle<Self>,
612 _: &wl_keyboard::WlKeyboard,
613 _: u32,
614 modifiers: Modifiers,
615 _: u32,
616 ) {
617 if self.active.load(Ordering::SeqCst) {
618 let _ = self.event_tx.send(GrabEvent::ModifiersChanged { mods: modifiers });
619 }
620 }
621 }
622
623 impl PointerHandler for GrabberState {
624 fn pointer_frame(
625 &mut self,
626 _: &Connection,
627 _: &QueueHandle<Self>,
628 _: &wl_pointer::WlPointer,
629 events: &[PointerEvent],
630 ) {
631 for event in events {
632 match &event.kind {
633 PointerEventKind::Enter { .. } => {
634 tracing::info!("Grabber pointer ENTER at ({}, {})", event.position.0, event.position.1);
635 }
636 PointerEventKind::Leave { .. } => {
637 tracing::info!("Grabber pointer LEAVE");
638 }
639 _ => {}
640 }
641 }
642
643 if !self.active.load(Ordering::SeqCst) {
644 return;
645 }
646
647 for event in events {
648 match event.kind {
649 PointerEventKind::Motion { .. } => {
650 let (x, y) = event.position;
651 let dx = x - self.last_pointer_pos.0;
652 let dy = y - self.last_pointer_pos.1;
653 self.last_pointer_pos = (x, y);
654
655 // Only send if there's actual motion
656 if dx.abs() > 0.001 || dy.abs() > 0.001 {
657 let _ = self.event_tx.send(GrabEvent::PointerMotion { dx, dy });
658 }
659 }
660 PointerEventKind::Press { button, .. } => {
661 let _ = self.event_tx.send(GrabEvent::PointerButton {
662 button,
663 pressed: true,
664 });
665 }
666 PointerEventKind::Release { button, .. } => {
667 let _ = self.event_tx.send(GrabEvent::PointerButton {
668 button,
669 pressed: false,
670 });
671 }
672 PointerEventKind::Axis {
673 horizontal,
674 vertical,
675 ..
676 } => {
677 let _ = self.event_tx.send(GrabEvent::Scroll {
678 horizontal: horizontal.absolute,
679 vertical: vertical.absolute,
680 });
681 }
682 _ => {}
683 }
684 }
685 }
686 }
687
688 impl ProvidesRegistryState for GrabberState {
689 fn registry(&mut self) -> &mut RegistryState {
690 &mut self.registry_state
691 }
692
693 registry_handlers![OutputState, SeatState];
694 }
695
696 delegate_compositor!(GrabberState);
697 delegate_output!(GrabberState);
698 delegate_seat!(GrabberState);
699 delegate_keyboard!(GrabberState);
700 delegate_pointer!(GrabberState);
701 delegate_layer!(GrabberState);
702 delegate_shm!(GrabberState);
703 delegate_registry!(GrabberState);
704
705 #[derive(Debug, thiserror::Error)]
706 pub enum GrabberError {
707 #[error("Failed to connect to Wayland: {0}")]
708 Connection(String),
709
710 #[error("Failed to initialize registry: {0}")]
711 Registry(String),
712
713 #[error("Protocol not available: {0}")]
714 Protocol(String),
715
716 #[error("Dispatch error: {0}")]
717 Dispatch(String),
718
719 #[error("Thread error: {0}")]
720 Thread(String),
721 }
722