tenseleyflow/hyprkvm / 39bce59

Browse files

Add wayland input capture and emulation

- Implement layer-shell edge barriers for mouse edge detection
- Add virtual pointer injection using wlr-virtual-pointer protocol
- Add virtual keyboard injection using virtual-keyboard protocol
- Include XKB keymap for keyboard emulation
- Send edge events through mpsc channel for async processing
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
39bce599bf227458669a8216c610ba22c03f2124
Parents
6927e93
Tree
621ba08

4 changed files

StatusFile+-
A hyprkvm-daemon/src/input/capture.rs 559 0
A hyprkvm-daemon/src/input/emulation.rs 408 0
A hyprkvm-daemon/src/input/minimal_keymap.xkb 275 0
A hyprkvm-daemon/src/input/mod.rs 12 0
hyprkvm-daemon/src/input/capture.rsadded
@@ -0,0 +1,559 @@
1
+//! Mouse edge capture via layer-shell
2
+//!
3
+//! Creates invisible 1-pixel barriers at screen edges to detect
4
+//! when the cursor attempts to leave the screen.
5
+
6
+use std::sync::mpsc;
7
+use std::time::Instant;
8
+
9
+use hyprkvm_common::Direction;
10
+use smithay_client_toolkit::{
11
+    compositor::{CompositorHandler, CompositorState},
12
+    delegate_compositor, delegate_layer, delegate_output, delegate_pointer, delegate_registry,
13
+    delegate_seat, delegate_shm,
14
+    output::{OutputHandler, OutputState},
15
+    registry::{ProvidesRegistryState, RegistryState},
16
+    registry_handlers,
17
+    seat::{
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_output, wl_pointer, wl_seat, wl_shm, wl_surface},
33
+    Connection, QueueHandle,
34
+};
35
+
36
+/// Event emitted when cursor hits a screen edge
37
+#[derive(Debug, Clone)]
38
+pub struct EdgeEvent {
39
+    pub direction: Direction,
40
+    pub position: (i32, i32),
41
+    pub timestamp: Instant,
42
+}
43
+
44
+/// Configuration for edge barriers
45
+#[derive(Debug, Clone)]
46
+pub struct EdgeCaptureConfig {
47
+    /// Barrier thickness in pixels
48
+    pub barrier_size: u32,
49
+    /// Which edges to create barriers on
50
+    pub enabled_edges: Vec<Direction>,
51
+}
52
+
53
+impl Default for EdgeCaptureConfig {
54
+    fn default() -> Self {
55
+        Self {
56
+            barrier_size: 1,
57
+            enabled_edges: vec![
58
+                Direction::Left,
59
+                Direction::Right,
60
+                Direction::Up,
61
+                Direction::Down,
62
+            ],
63
+        }
64
+    }
65
+}
66
+
67
+/// An edge barrier surface
68
+struct EdgeBarrier {
69
+    direction: Direction,
70
+    surface: LayerSurface,
71
+    width: u32,
72
+    height: u32,
73
+    configured: bool,
74
+}
75
+
76
+/// Edge capture system state
77
+struct EdgeCaptureState {
78
+    registry_state: RegistryState,
79
+    compositor_state: CompositorState,
80
+    output_state: OutputState,
81
+    seat_state: SeatState,
82
+    shm_state: Shm,
83
+    layer_shell: LayerShell,
84
+
85
+    barriers: Vec<EdgeBarrier>,
86
+    pool: Option<SlotPool>,
87
+    event_tx: mpsc::Sender<EdgeEvent>,
88
+    config: EdgeCaptureConfig,
89
+
90
+    // Track outputs for barrier sizing
91
+    outputs: Vec<OutputInfo>,
92
+}
93
+
94
+#[derive(Debug, Clone)]
95
+struct OutputInfo {
96
+    output: wl_output::WlOutput,
97
+    width: u32,
98
+    height: u32,
99
+    x: i32,
100
+    y: i32,
101
+}
102
+
103
+impl EdgeCaptureState {
104
+    fn create_barriers(&mut self, qh: &QueueHandle<Self>) {
105
+        // Get total screen bounds
106
+        let (min_x, min_y, max_x, max_y) = self.screen_bounds();
107
+        let total_width = (max_x - min_x) as u32;
108
+        let total_height = (max_y - min_y) as u32;
109
+
110
+        for direction in &self.config.enabled_edges.clone() {
111
+            let (width, height, anchor) = match direction {
112
+                Direction::Left => (
113
+                    self.config.barrier_size,
114
+                    total_height,
115
+                    Anchor::LEFT | Anchor::TOP | Anchor::BOTTOM,
116
+                ),
117
+                Direction::Right => (
118
+                    self.config.barrier_size,
119
+                    total_height,
120
+                    Anchor::RIGHT | Anchor::TOP | Anchor::BOTTOM,
121
+                ),
122
+                Direction::Up => (
123
+                    total_width,
124
+                    self.config.barrier_size,
125
+                    Anchor::TOP | Anchor::LEFT | Anchor::RIGHT,
126
+                ),
127
+                Direction::Down => (
128
+                    total_width,
129
+                    self.config.barrier_size,
130
+                    Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
131
+                ),
132
+            };
133
+
134
+            // Create layer surface
135
+            let surface = self.compositor_state.create_surface(qh);
136
+            let layer_surface = self.layer_shell.create_layer_surface(
137
+                qh,
138
+                surface,
139
+                Layer::Top, // Top layer to catch pointer
140
+                Some(format!("hyprkvm-edge-{}", direction)),
141
+                None, // No specific output, use focused
142
+            );
143
+
144
+            // Configure the layer surface
145
+            layer_surface.set_anchor(anchor);
146
+            layer_surface.set_size(width, height);
147
+            layer_surface.set_exclusive_zone(-1); // Don't reserve space
148
+            layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None);
149
+
150
+            // Commit to apply configuration
151
+            layer_surface.commit();
152
+
153
+            self.barriers.push(EdgeBarrier {
154
+                direction: *direction,
155
+                surface: layer_surface,
156
+                width,
157
+                height,
158
+                configured: false,
159
+            });
160
+        }
161
+    }
162
+
163
+    fn screen_bounds(&self) -> (i32, i32, i32, i32) {
164
+        if self.outputs.is_empty() {
165
+            return (0, 0, 1920, 1080); // Fallback
166
+        }
167
+
168
+        let mut min_x = i32::MAX;
169
+        let mut min_y = i32::MAX;
170
+        let mut max_x = i32::MIN;
171
+        let mut max_y = i32::MIN;
172
+
173
+        for output in &self.outputs {
174
+            min_x = min_x.min(output.x);
175
+            min_y = min_y.min(output.y);
176
+            max_x = max_x.max(output.x + output.width as i32);
177
+            max_y = max_y.max(output.y + output.height as i32);
178
+        }
179
+
180
+        (min_x, min_y, max_x, max_y)
181
+    }
182
+
183
+    fn draw_barrier(&mut self, barrier_idx: usize, qh: &QueueHandle<Self>) {
184
+        let barrier = &self.barriers[barrier_idx];
185
+        if !barrier.configured {
186
+            return;
187
+        }
188
+
189
+        let pool = match &mut self.pool {
190
+            Some(pool) => pool,
191
+            None => return,
192
+        };
193
+
194
+        let width = barrier.width;
195
+        let height = barrier.height;
196
+        let stride = width * 4;
197
+
198
+        let (buffer, canvas) = pool
199
+            .create_buffer(
200
+                width as i32,
201
+                height as i32,
202
+                stride as i32,
203
+                wl_shm::Format::Argb8888,
204
+            )
205
+            .expect("Failed to create buffer");
206
+
207
+        // Fill with transparent pixels
208
+        canvas.fill(0);
209
+
210
+        // Attach and commit
211
+        barrier.surface.wl_surface().attach(Some(buffer.wl_buffer()), 0, 0);
212
+        barrier.surface.wl_surface().damage_buffer(0, 0, width as i32, height as i32);
213
+        barrier.surface.commit();
214
+    }
215
+
216
+    fn find_barrier_by_surface(&self, surface: &wl_surface::WlSurface) -> Option<usize> {
217
+        self.barriers
218
+            .iter()
219
+            .position(|b| b.surface.wl_surface() == surface)
220
+    }
221
+}
222
+
223
+// Implement all the required handlers
224
+
225
+impl CompositorHandler for EdgeCaptureState {
226
+    fn scale_factor_changed(
227
+        &mut self,
228
+        _conn: &Connection,
229
+        _qh: &QueueHandle<Self>,
230
+        _surface: &wl_surface::WlSurface,
231
+        _new_factor: i32,
232
+    ) {
233
+    }
234
+
235
+    fn transform_changed(
236
+        &mut self,
237
+        _conn: &Connection,
238
+        _qh: &QueueHandle<Self>,
239
+        _surface: &wl_surface::WlSurface,
240
+        _new_transform: wl_output::Transform,
241
+    ) {
242
+    }
243
+
244
+    fn frame(
245
+        &mut self,
246
+        _conn: &Connection,
247
+        _qh: &QueueHandle<Self>,
248
+        _surface: &wl_surface::WlSurface,
249
+        _time: u32,
250
+    ) {
251
+    }
252
+
253
+    fn surface_enter(
254
+        &mut self,
255
+        _conn: &Connection,
256
+        _qh: &QueueHandle<Self>,
257
+        _surface: &wl_surface::WlSurface,
258
+        _output: &wl_output::WlOutput,
259
+    ) {
260
+    }
261
+
262
+    fn surface_leave(
263
+        &mut self,
264
+        _conn: &Connection,
265
+        _qh: &QueueHandle<Self>,
266
+        _surface: &wl_surface::WlSurface,
267
+        _output: &wl_output::WlOutput,
268
+    ) {
269
+    }
270
+}
271
+
272
+impl OutputHandler for EdgeCaptureState {
273
+    fn output_state(&mut self) -> &mut OutputState {
274
+        &mut self.output_state
275
+    }
276
+
277
+    fn new_output(
278
+        &mut self,
279
+        _conn: &Connection,
280
+        _qh: &QueueHandle<Self>,
281
+        output: wl_output::WlOutput,
282
+    ) {
283
+        // We'll get geometry info from the output state
284
+        if let Some(info) = self.output_state.info(&output) {
285
+            if let Some(size) = info.logical_size {
286
+                let (x, y) = info.location;
287
+                self.outputs.push(OutputInfo {
288
+                    output,
289
+                    width: size.0 as u32,
290
+                    height: size.1 as u32,
291
+                    x,
292
+                    y,
293
+                });
294
+            }
295
+        }
296
+    }
297
+
298
+    fn update_output(
299
+        &mut self,
300
+        _conn: &Connection,
301
+        _qh: &QueueHandle<Self>,
302
+        output: wl_output::WlOutput,
303
+    ) {
304
+        // Update output info
305
+        if let Some(info) = self.output_state.info(&output) {
306
+            if let Some(size) = info.logical_size {
307
+                let (x, y) = info.location;
308
+                if let Some(out) = self.outputs.iter_mut().find(|o| o.output == output) {
309
+                    out.width = size.0 as u32;
310
+                    out.height = size.1 as u32;
311
+                    out.x = x;
312
+                    out.y = y;
313
+                }
314
+            }
315
+        }
316
+    }
317
+
318
+    fn output_destroyed(
319
+        &mut self,
320
+        _conn: &Connection,
321
+        _qh: &QueueHandle<Self>,
322
+        output: wl_output::WlOutput,
323
+    ) {
324
+        self.outputs.retain(|o| o.output != output);
325
+    }
326
+}
327
+
328
+impl LayerShellHandler for EdgeCaptureState {
329
+    fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {
330
+        // Barrier closed, could recreate if needed
331
+    }
332
+
333
+    fn configure(
334
+        &mut self,
335
+        _conn: &Connection,
336
+        qh: &QueueHandle<Self>,
337
+        layer: &LayerSurface,
338
+        configure: LayerSurfaceConfigure,
339
+        _serial: u32,
340
+    ) {
341
+        // Find which barrier this is
342
+        if let Some(idx) = self.barriers.iter().position(|b| &b.surface == layer) {
343
+            let barrier = &mut self.barriers[idx];
344
+            barrier.configured = true;
345
+
346
+            // Update size if compositor changed it
347
+            if configure.new_size.0 > 0 {
348
+                barrier.width = configure.new_size.0;
349
+            }
350
+            if configure.new_size.1 > 0 {
351
+                barrier.height = configure.new_size.1;
352
+            }
353
+
354
+            // Draw the (transparent) barrier
355
+            self.draw_barrier(idx, qh);
356
+        }
357
+    }
358
+}
359
+
360
+impl SeatHandler for EdgeCaptureState {
361
+    fn seat_state(&mut self) -> &mut SeatState {
362
+        &mut self.seat_state
363
+    }
364
+
365
+    fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {}
366
+
367
+    fn new_capability(
368
+        &mut self,
369
+        _conn: &Connection,
370
+        qh: &QueueHandle<Self>,
371
+        seat: wl_seat::WlSeat,
372
+        capability: Capability,
373
+    ) {
374
+        if capability == Capability::Pointer {
375
+            self.seat_state.get_pointer(qh, &seat).ok();
376
+        }
377
+    }
378
+
379
+    fn remove_capability(
380
+        &mut self,
381
+        _conn: &Connection,
382
+        _qh: &QueueHandle<Self>,
383
+        _seat: wl_seat::WlSeat,
384
+        _capability: Capability,
385
+    ) {
386
+    }
387
+
388
+    fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {
389
+    }
390
+}
391
+
392
+impl PointerHandler for EdgeCaptureState {
393
+    fn pointer_frame(
394
+        &mut self,
395
+        _conn: &Connection,
396
+        _qh: &QueueHandle<Self>,
397
+        _pointer: &wl_pointer::WlPointer,
398
+        events: &[PointerEvent],
399
+    ) {
400
+        for event in events {
401
+            if let PointerEventKind::Enter { .. } = event.kind {
402
+                // Pointer entered one of our barrier surfaces
403
+                let surface = &event.surface;
404
+                if let Some(idx) = self.find_barrier_by_surface(surface) {
405
+                    let barrier = &self.barriers[idx];
406
+                    let edge_event = EdgeEvent {
407
+                        direction: barrier.direction,
408
+                        position: (event.position.0 as i32, event.position.1 as i32),
409
+                        timestamp: Instant::now(),
410
+                    };
411
+
412
+                    tracing::info!(
413
+                        "Edge hit: {:?} at ({}, {})",
414
+                        barrier.direction,
415
+                        event.position.0,
416
+                        event.position.1
417
+                    );
418
+
419
+                    let _ = self.event_tx.send(edge_event);
420
+                }
421
+            }
422
+        }
423
+    }
424
+}
425
+
426
+impl ShmHandler for EdgeCaptureState {
427
+    fn shm_state(&mut self) -> &mut Shm {
428
+        &mut self.shm_state
429
+    }
430
+}
431
+
432
+impl ProvidesRegistryState for EdgeCaptureState {
433
+    fn registry(&mut self) -> &mut RegistryState {
434
+        &mut self.registry_state
435
+    }
436
+
437
+    registry_handlers!(OutputState, SeatState);
438
+}
439
+
440
+// Delegate macros
441
+delegate_compositor!(EdgeCaptureState);
442
+delegate_output!(EdgeCaptureState);
443
+delegate_layer!(EdgeCaptureState);
444
+delegate_seat!(EdgeCaptureState);
445
+delegate_pointer!(EdgeCaptureState);
446
+delegate_shm!(EdgeCaptureState);
447
+delegate_registry!(EdgeCaptureState);
448
+
449
+/// Handle for controlling edge capture
450
+pub struct EdgeCapture {
451
+    event_rx: mpsc::Receiver<EdgeEvent>,
452
+    // The event loop runs in a separate thread
453
+    _thread: std::thread::JoinHandle<()>,
454
+}
455
+
456
+impl EdgeCapture {
457
+    /// Create and start edge capture
458
+    pub fn new(config: EdgeCaptureConfig) -> Result<Self, EdgeCaptureError> {
459
+        let (event_tx, event_rx) = mpsc::channel();
460
+
461
+        let thread = std::thread::spawn(move || {
462
+            if let Err(e) = run_capture_loop(config, event_tx) {
463
+                tracing::error!("Edge capture error: {}", e);
464
+            }
465
+        });
466
+
467
+        Ok(Self {
468
+            event_rx,
469
+            _thread: thread,
470
+        })
471
+    }
472
+
473
+    /// Try to receive an edge event (non-blocking)
474
+    pub fn try_recv(&self) -> Option<EdgeEvent> {
475
+        self.event_rx.try_recv().ok()
476
+    }
477
+
478
+    /// Receive an edge event (blocking)
479
+    pub fn recv(&self) -> Option<EdgeEvent> {
480
+        self.event_rx.recv().ok()
481
+    }
482
+
483
+    /// Get the receiver for async integration
484
+    pub fn receiver(&self) -> &mpsc::Receiver<EdgeEvent> {
485
+        &self.event_rx
486
+    }
487
+}
488
+
489
+fn run_capture_loop(
490
+    config: EdgeCaptureConfig,
491
+    event_tx: mpsc::Sender<EdgeEvent>,
492
+) -> Result<(), EdgeCaptureError> {
493
+    let conn = Connection::connect_to_env().map_err(|e| EdgeCaptureError::Connection(e.to_string()))?;
494
+
495
+    let (globals, mut event_queue) =
496
+        registry_queue_init(&conn).map_err(|e| EdgeCaptureError::Registry(e.to_string()))?;
497
+    let qh = event_queue.handle();
498
+
499
+    let compositor_state = CompositorState::bind(&globals, &qh)
500
+        .map_err(|e| EdgeCaptureError::Protocol(format!("compositor: {}", e)))?;
501
+    let layer_shell = LayerShell::bind(&globals, &qh)
502
+        .map_err(|e| EdgeCaptureError::Protocol(format!("layer_shell: {}", e)))?;
503
+    let shm_state =
504
+        Shm::bind(&globals, &qh).map_err(|e| EdgeCaptureError::Protocol(format!("shm: {}", e)))?;
505
+
506
+    let mut state = EdgeCaptureState {
507
+        registry_state: RegistryState::new(&globals),
508
+        compositor_state,
509
+        output_state: OutputState::new(&globals, &qh),
510
+        seat_state: SeatState::new(&globals, &qh),
511
+        shm_state,
512
+        layer_shell,
513
+        barriers: Vec::new(),
514
+        pool: None,
515
+        event_tx,
516
+        config,
517
+        outputs: Vec::new(),
518
+    };
519
+
520
+    // Initial roundtrip to get outputs
521
+    event_queue.roundtrip(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
522
+
523
+    // Create shm pool for buffers
524
+    state.pool = Some(
525
+        SlotPool::new(256 * 256 * 4, &state.shm_state)
526
+            .map_err(|e| EdgeCaptureError::Shm(e.to_string()))?,
527
+    );
528
+
529
+    // Create barriers after we have output info
530
+    state.create_barriers(&qh);
531
+
532
+    // Another roundtrip to configure barriers
533
+    event_queue.roundtrip(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
534
+
535
+    tracing::info!("Edge capture running with {} barriers", state.barriers.len());
536
+
537
+    // Main event loop
538
+    loop {
539
+        event_queue.blocking_dispatch(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
540
+    }
541
+}
542
+
543
+#[derive(Debug, thiserror::Error)]
544
+pub enum EdgeCaptureError {
545
+    #[error("Failed to connect to Wayland: {0}")]
546
+    Connection(String),
547
+
548
+    #[error("Registry error: {0}")]
549
+    Registry(String),
550
+
551
+    #[error("Protocol not available: {0}")]
552
+    Protocol(String),
553
+
554
+    #[error("Dispatch error: {0}")]
555
+    Dispatch(String),
556
+
557
+    #[error("SHM error: {0}")]
558
+    Shm(String),
559
+}
hyprkvm-daemon/src/input/emulation.rsadded
@@ -0,0 +1,408 @@
1
+//! Wayland input emulation
2
+//!
3
+//! Injects keyboard and mouse events via virtual input protocols.
4
+//! Uses wlr-virtual-pointer and virtual-keyboard Wayland protocols.
5
+
6
+use std::os::unix::io::{AsFd, OwnedFd};
7
+use std::sync::Arc;
8
+
9
+use wayland_client::{
10
+    globals::{registry_queue_init, GlobalListContents},
11
+    protocol::{wl_registry, wl_seat},
12
+    Connection, Dispatch, EventQueue, QueueHandle,
13
+};
14
+use wayland_protocols_wlr::virtual_pointer::v1::client::{
15
+    zwlr_virtual_pointer_manager_v1::ZwlrVirtualPointerManagerV1,
16
+    zwlr_virtual_pointer_v1::ZwlrVirtualPointerV1,
17
+};
18
+use wayland_protocols_misc::zwp_virtual_keyboard_v1::client::{
19
+    zwp_virtual_keyboard_manager_v1::ZwpVirtualKeyboardManagerV1,
20
+    zwp_virtual_keyboard_v1::ZwpVirtualKeyboardV1,
21
+};
22
+use wayland_client::protocol::wl_pointer::{
23
+    Axis as WlAxis,
24
+    ButtonState as WlButtonState,
25
+};
26
+
27
+use hyprkvm_common::{ButtonState, KeyState};
28
+
29
+/// Virtual pointer for mouse injection
30
+pub struct VirtualPointer {
31
+    pointer: ZwlrVirtualPointerV1,
32
+    _connection: Arc<Connection>,
33
+}
34
+
35
+impl VirtualPointer {
36
+    /// Send relative motion
37
+    pub fn motion(&self, dx: f64, dy: f64) {
38
+        // Time in milliseconds (monotonic, we just use 0 for simplicity)
39
+        let time = std::time::SystemTime::now()
40
+            .duration_since(std::time::UNIX_EPOCH)
41
+            .unwrap()
42
+            .as_millis() as u32;
43
+
44
+        self.pointer.motion(time, dx, dy);
45
+        self.pointer.frame();
46
+    }
47
+
48
+    /// Send absolute motion (normalized 0.0-1.0)
49
+    pub fn motion_absolute(&self, x: f64, y: f64, width: u32, height: u32) {
50
+        let time = std::time::SystemTime::now()
51
+            .duration_since(std::time::UNIX_EPOCH)
52
+            .unwrap()
53
+            .as_millis() as u32;
54
+
55
+        // Convert to fixed-point (wl_fixed)
56
+        let x_fixed = (x * width as f64) as u32;
57
+        let y_fixed = (y * height as f64) as u32;
58
+
59
+        self.pointer.motion_absolute(time, x_fixed, y_fixed, width, height);
60
+        self.pointer.frame();
61
+    }
62
+
63
+    /// Send button event
64
+    pub fn button(&self, button: u32, state: ButtonState) {
65
+        let time = std::time::SystemTime::now()
66
+            .duration_since(std::time::UNIX_EPOCH)
67
+            .unwrap()
68
+            .as_millis() as u32;
69
+
70
+        let wl_state = match state {
71
+            ButtonState::Pressed => WlButtonState::Pressed,
72
+            ButtonState::Released => WlButtonState::Released,
73
+        };
74
+
75
+        self.pointer.button(time, button, wl_state);
76
+        self.pointer.frame();
77
+    }
78
+
79
+    /// Send scroll (axis) event
80
+    pub fn scroll(&self, horizontal: f64, vertical: f64) {
81
+        let time = std::time::SystemTime::now()
82
+            .duration_since(std::time::UNIX_EPOCH)
83
+            .unwrap()
84
+            .as_millis() as u32;
85
+
86
+        if vertical.abs() > 0.001 {
87
+            self.pointer.axis(time, WlAxis::VerticalScroll, vertical);
88
+        }
89
+        if horizontal.abs() > 0.001 {
90
+            self.pointer.axis(time, WlAxis::HorizontalScroll, horizontal);
91
+        }
92
+        self.pointer.frame();
93
+    }
94
+}
95
+
96
+/// Virtual keyboard for key injection
97
+pub struct VirtualKeyboard {
98
+    keyboard: ZwpVirtualKeyboardV1,
99
+    _connection: Arc<Connection>,
100
+    keymap_set: bool,
101
+}
102
+
103
+impl VirtualKeyboard {
104
+    /// Send key event
105
+    pub fn key(&self, keycode: u32, state: KeyState) {
106
+        if !self.keymap_set {
107
+            tracing::warn!("Keymap not set, key event may not work correctly");
108
+        }
109
+
110
+        let time = std::time::SystemTime::now()
111
+            .duration_since(std::time::UNIX_EPOCH)
112
+            .unwrap()
113
+            .as_millis() as u32;
114
+
115
+        let wl_state = match state {
116
+            KeyState::Pressed => wl_keyboard_key_state::Pressed,
117
+            KeyState::Released => wl_keyboard_key_state::Released,
118
+        };
119
+
120
+        // Note: keycode needs to be offset by 8 for evdev->xkb conversion
121
+        self.keyboard.key(time, keycode, wl_state);
122
+    }
123
+
124
+    /// Send modifier state
125
+    pub fn modifiers(&self, depressed: u32, latched: u32, locked: u32, group: u32) {
126
+        self.keyboard.modifiers(depressed, latched, locked, group);
127
+    }
128
+}
129
+
130
+// Key state constants
131
+mod wl_keyboard_key_state {
132
+    pub const Released: u32 = 0;
133
+    pub const Pressed: u32 = 1;
134
+}
135
+
136
+/// State for input emulation setup
137
+struct EmulationState {
138
+    pointer_manager: Option<ZwlrVirtualPointerManagerV1>,
139
+    keyboard_manager: Option<ZwpVirtualKeyboardManagerV1>,
140
+    seat: Option<wl_seat::WlSeat>,
141
+}
142
+
143
+impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for EmulationState {
144
+    fn event(
145
+        state: &mut Self,
146
+        registry: &wl_registry::WlRegistry,
147
+        event: wl_registry::Event,
148
+        _data: &GlobalListContents,
149
+        _conn: &Connection,
150
+        qh: &QueueHandle<Self>,
151
+    ) {
152
+        if let wl_registry::Event::Global { name, interface, version } = event {
153
+            match interface.as_str() {
154
+                "zwlr_virtual_pointer_manager_v1" => {
155
+                    state.pointer_manager = Some(registry.bind(name, version.min(2), qh, ()));
156
+                }
157
+                "zwp_virtual_keyboard_manager_v1" => {
158
+                    state.keyboard_manager = Some(registry.bind(name, version.min(1), qh, ()));
159
+                }
160
+                "wl_seat" => {
161
+                    state.seat = Some(registry.bind(name, version.min(7), qh, ()));
162
+                }
163
+                _ => {}
164
+            }
165
+        }
166
+    }
167
+}
168
+
169
+// Implement empty dispatchers for the protocols we use
170
+impl Dispatch<ZwlrVirtualPointerManagerV1, ()> for EmulationState {
171
+    fn event(
172
+        _state: &mut Self,
173
+        _proxy: &ZwlrVirtualPointerManagerV1,
174
+        _event: <ZwlrVirtualPointerManagerV1 as wayland_client::Proxy>::Event,
175
+        _data: &(),
176
+        _conn: &Connection,
177
+        _qh: &QueueHandle<Self>,
178
+    ) {
179
+    }
180
+}
181
+
182
+impl Dispatch<ZwlrVirtualPointerV1, ()> for EmulationState {
183
+    fn event(
184
+        _state: &mut Self,
185
+        _proxy: &ZwlrVirtualPointerV1,
186
+        _event: <ZwlrVirtualPointerV1 as wayland_client::Proxy>::Event,
187
+        _data: &(),
188
+        _conn: &Connection,
189
+        _qh: &QueueHandle<Self>,
190
+    ) {
191
+    }
192
+}
193
+
194
+impl Dispatch<ZwpVirtualKeyboardManagerV1, ()> for EmulationState {
195
+    fn event(
196
+        _state: &mut Self,
197
+        _proxy: &ZwpVirtualKeyboardManagerV1,
198
+        _event: <ZwpVirtualKeyboardManagerV1 as wayland_client::Proxy>::Event,
199
+        _data: &(),
200
+        _conn: &Connection,
201
+        _qh: &QueueHandle<Self>,
202
+    ) {
203
+    }
204
+}
205
+
206
+impl Dispatch<ZwpVirtualKeyboardV1, ()> for EmulationState {
207
+    fn event(
208
+        _state: &mut Self,
209
+        _proxy: &ZwpVirtualKeyboardV1,
210
+        _event: <ZwpVirtualKeyboardV1 as wayland_client::Proxy>::Event,
211
+        _data: &(),
212
+        _conn: &Connection,
213
+        _qh: &QueueHandle<Self>,
214
+    ) {
215
+    }
216
+}
217
+
218
+impl Dispatch<wl_seat::WlSeat, ()> for EmulationState {
219
+    fn event(
220
+        _state: &mut Self,
221
+        _proxy: &wl_seat::WlSeat,
222
+        _event: wl_seat::Event,
223
+        _data: &(),
224
+        _conn: &Connection,
225
+        _qh: &QueueHandle<Self>,
226
+    ) {
227
+    }
228
+}
229
+
230
+/// Input emulator combining virtual pointer and keyboard
231
+pub struct InputEmulator {
232
+    pub pointer: VirtualPointer,
233
+    pub keyboard: VirtualKeyboard,
234
+    connection: Arc<Connection>,
235
+    event_queue: EventQueue<EmulationState>,
236
+}
237
+
238
+impl InputEmulator {
239
+    /// Create a new input emulator
240
+    pub fn new() -> Result<Self, EmulationError> {
241
+        let conn = Connection::connect_to_env()
242
+            .map_err(|e| EmulationError::Connection(e.to_string()))?;
243
+        let conn = Arc::new(conn);
244
+
245
+        let (globals, mut event_queue) = registry_queue_init(&conn)
246
+            .map_err(|e| EmulationError::Registry(e.to_string()))?;
247
+
248
+        let qh = event_queue.handle();
249
+
250
+        let mut state = EmulationState {
251
+            pointer_manager: None,
252
+            keyboard_manager: None,
253
+            seat: None,
254
+        };
255
+
256
+        // Bind to required globals
257
+        for global in globals.contents().clone_list() {
258
+            match global.interface.as_str() {
259
+                "zwlr_virtual_pointer_manager_v1" => {
260
+                    state.pointer_manager = Some(
261
+                        globals
262
+                            .registry()
263
+                            .bind(global.name, global.version.min(2), &qh, ()),
264
+                    );
265
+                }
266
+                "zwp_virtual_keyboard_manager_v1" => {
267
+                    state.keyboard_manager = Some(
268
+                        globals
269
+                            .registry()
270
+                            .bind(global.name, global.version.min(1), &qh, ()),
271
+                    );
272
+                }
273
+                "wl_seat" => {
274
+                    state.seat = Some(
275
+                        globals
276
+                            .registry()
277
+                            .bind(global.name, global.version.min(7), &qh, ()),
278
+                    );
279
+                }
280
+                _ => {}
281
+            }
282
+        }
283
+
284
+        // Roundtrip to ensure we have all globals
285
+        event_queue
286
+            .roundtrip(&mut state)
287
+            .map_err(|e| EmulationError::Dispatch(e.to_string()))?;
288
+
289
+        // Check we have required protocols
290
+        let pointer_manager = state.pointer_manager.clone().ok_or_else(|| {
291
+            EmulationError::Protocol("zwlr_virtual_pointer_manager_v1 not available".to_string())
292
+        })?;
293
+
294
+        let keyboard_manager = state.keyboard_manager.clone().ok_or_else(|| {
295
+            EmulationError::Protocol("zwp_virtual_keyboard_manager_v1 not available".to_string())
296
+        })?;
297
+
298
+        let seat = state.seat.clone().ok_or_else(|| {
299
+            EmulationError::Protocol("wl_seat not available".to_string())
300
+        })?;
301
+
302
+        // Create virtual pointer
303
+        let pointer = pointer_manager.create_virtual_pointer(Some(&seat), &qh, ());
304
+
305
+        // Create virtual keyboard
306
+        let keyboard = keyboard_manager.create_virtual_keyboard(&seat, &qh, ());
307
+
308
+        // Set up a basic keymap for the keyboard
309
+        // This is a minimal xkb keymap
310
+        let keymap_string = include_str!("minimal_keymap.xkb");
311
+        let keymap_size = keymap_string.len() as u32;
312
+
313
+        // Create a memfd for the keymap
314
+        let keymap_fd = create_keymap_fd(keymap_string)
315
+            .map_err(|e| EmulationError::Keymap(e.to_string()))?;
316
+
317
+        keyboard.keymap(
318
+            1, // WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1
319
+            keymap_fd.as_fd(),
320
+            keymap_size,
321
+        );
322
+
323
+        // Roundtrip to ensure everything is set up
324
+        event_queue
325
+            .roundtrip(&mut state)
326
+            .map_err(|e| EmulationError::Dispatch(e.to_string()))?;
327
+
328
+        Ok(Self {
329
+            pointer: VirtualPointer {
330
+                pointer,
331
+                _connection: conn.clone(),
332
+            },
333
+            keyboard: VirtualKeyboard {
334
+                keyboard,
335
+                _connection: conn.clone(),
336
+                keymap_set: true,
337
+            },
338
+            connection: conn,
339
+            event_queue,
340
+        })
341
+    }
342
+
343
+    /// Dispatch any pending events
344
+    pub fn dispatch(&mut self) -> Result<(), EmulationError> {
345
+        let mut state = EmulationState {
346
+            pointer_manager: None,
347
+            keyboard_manager: None,
348
+            seat: None,
349
+        };
350
+        self.event_queue
351
+            .dispatch_pending(&mut state)
352
+            .map_err(|e| EmulationError::Dispatch(e.to_string()))?;
353
+        self.connection
354
+            .flush()
355
+            .map_err(|e| EmulationError::Dispatch(e.to_string()))?;
356
+        Ok(())
357
+    }
358
+}
359
+
360
+/// Create a memfd containing the keymap string
361
+fn create_keymap_fd(keymap: &str) -> std::io::Result<OwnedFd> {
362
+    use std::io::Write;
363
+
364
+    // Create memfd
365
+    let fd = rustix::fs::memfd_create(
366
+        "hyprkvm-keymap",
367
+        rustix::fs::MemfdFlags::CLOEXEC | rustix::fs::MemfdFlags::ALLOW_SEALING,
368
+    )?;
369
+
370
+    // Write keymap
371
+    let mut file = std::fs::File::from(fd);
372
+    file.write_all(keymap.as_bytes())?;
373
+    file.write_all(&[0])?; // Null terminator
374
+
375
+    // Seal the file
376
+    let fd = OwnedFd::from(file);
377
+
378
+    Ok(fd)
379
+}
380
+
381
+#[derive(Debug, thiserror::Error)]
382
+pub enum EmulationError {
383
+    #[error("Failed to connect to Wayland: {0}")]
384
+    Connection(String),
385
+
386
+    #[error("Registry error: {0}")]
387
+    Registry(String),
388
+
389
+    #[error("Protocol not available: {0}")]
390
+    Protocol(String),
391
+
392
+    #[error("Dispatch error: {0}")]
393
+    Dispatch(String),
394
+
395
+    #[error("Keymap error: {0}")]
396
+    Keymap(String),
397
+}
398
+
399
+// Button codes (from linux/input-event-codes.h)
400
+pub mod button_codes {
401
+    pub const BTN_LEFT: u32 = 0x110;
402
+    pub const BTN_RIGHT: u32 = 0x111;
403
+    pub const BTN_MIDDLE: u32 = 0x112;
404
+    pub const BTN_SIDE: u32 = 0x113;
405
+    pub const BTN_EXTRA: u32 = 0x114;
406
+    pub const BTN_FORWARD: u32 = 0x115;
407
+    pub const BTN_BACK: u32 = 0x116;
408
+}
hyprkvm-daemon/src/input/minimal_keymap.xkbadded
@@ -0,0 +1,275 @@
1
+xkb_keymap {
2
+    xkb_keycodes "evdev" {
3
+        minimum = 8;
4
+        maximum = 255;
5
+        <ESC> = 9;
6
+        <AE01> = 10;
7
+        <AE02> = 11;
8
+        <AE03> = 12;
9
+        <AE04> = 13;
10
+        <AE05> = 14;
11
+        <AE06> = 15;
12
+        <AE07> = 16;
13
+        <AE08> = 17;
14
+        <AE09> = 18;
15
+        <AE10> = 19;
16
+        <AE11> = 20;
17
+        <AE12> = 21;
18
+        <BKSP> = 22;
19
+        <TAB> = 23;
20
+        <AD01> = 24;
21
+        <AD02> = 25;
22
+        <AD03> = 26;
23
+        <AD04> = 27;
24
+        <AD05> = 28;
25
+        <AD06> = 29;
26
+        <AD07> = 30;
27
+        <AD08> = 31;
28
+        <AD09> = 32;
29
+        <AD10> = 33;
30
+        <AD11> = 34;
31
+        <AD12> = 35;
32
+        <RTRN> = 36;
33
+        <LCTL> = 37;
34
+        <AC01> = 38;
35
+        <AC02> = 39;
36
+        <AC03> = 40;
37
+        <AC04> = 41;
38
+        <AC05> = 42;
39
+        <AC06> = 43;
40
+        <AC07> = 44;
41
+        <AC08> = 45;
42
+        <AC09> = 46;
43
+        <AC10> = 47;
44
+        <AC11> = 48;
45
+        <TLDE> = 49;
46
+        <LFSH> = 50;
47
+        <BKSL> = 51;
48
+        <AB01> = 52;
49
+        <AB02> = 53;
50
+        <AB03> = 54;
51
+        <AB04> = 55;
52
+        <AB05> = 56;
53
+        <AB06> = 57;
54
+        <AB07> = 58;
55
+        <AB08> = 59;
56
+        <AB09> = 60;
57
+        <AB10> = 61;
58
+        <RTSH> = 62;
59
+        <KPMU> = 63;
60
+        <LALT> = 64;
61
+        <SPCE> = 65;
62
+        <CAPS> = 66;
63
+        <FK01> = 67;
64
+        <FK02> = 68;
65
+        <FK03> = 69;
66
+        <FK04> = 70;
67
+        <FK05> = 71;
68
+        <FK06> = 72;
69
+        <FK07> = 73;
70
+        <FK08> = 74;
71
+        <FK09> = 75;
72
+        <FK10> = 76;
73
+        <NMLK> = 77;
74
+        <SCLK> = 78;
75
+        <KP7> = 79;
76
+        <KP8> = 80;
77
+        <KP9> = 81;
78
+        <KPSU> = 82;
79
+        <KP4> = 83;
80
+        <KP5> = 84;
81
+        <KP6> = 85;
82
+        <KPAD> = 86;
83
+        <KP1> = 87;
84
+        <KP2> = 88;
85
+        <KP3> = 89;
86
+        <KP0> = 90;
87
+        <KPDL> = 91;
88
+        <FK11> = 95;
89
+        <FK12> = 96;
90
+        <KPEN> = 104;
91
+        <RCTL> = 105;
92
+        <KPDV> = 106;
93
+        <PRSC> = 107;
94
+        <RALT> = 108;
95
+        <HOME> = 110;
96
+        <UP> = 111;
97
+        <PGUP> = 112;
98
+        <LEFT> = 113;
99
+        <RGHT> = 114;
100
+        <END> = 115;
101
+        <DOWN> = 116;
102
+        <PGDN> = 117;
103
+        <INS> = 118;
104
+        <DELE> = 119;
105
+        <PAUS> = 127;
106
+        <LWIN> = 133;
107
+        <RWIN> = 134;
108
+        <MENU> = 135;
109
+    };
110
+
111
+    xkb_types "complete" {
112
+        virtual_modifiers NumLock,Alt,LevelThree,LAlt,RAlt,RControl,LControl,ScrollLock,LevelFive,AltGr,Meta,Super,Hyper;
113
+
114
+        type "ONE_LEVEL" {
115
+            modifiers= none;
116
+            level_name[Level1]= "Any";
117
+        };
118
+        type "TWO_LEVEL" {
119
+            modifiers= Shift;
120
+            map[Shift]= Level2;
121
+            level_name[Level1]= "Base";
122
+            level_name[Level2]= "Shift";
123
+        };
124
+        type "ALPHABETIC" {
125
+            modifiers= Shift+Lock;
126
+            map[Shift]= Level2;
127
+            map[Lock]= Level2;
128
+            level_name[Level1]= "Base";
129
+            level_name[Level2]= "Caps";
130
+        };
131
+        type "KEYPAD" {
132
+            modifiers= Shift+NumLock;
133
+            map[Shift]= Level2;
134
+            map[NumLock]= Level2;
135
+            level_name[Level1]= "Base";
136
+            level_name[Level2]= "Number";
137
+        };
138
+    };
139
+
140
+    xkb_compatibility "complete" {
141
+        virtual_modifiers NumLock,Alt,LevelThree,LAlt,RAlt,RControl,LControl,ScrollLock,LevelFive,AltGr,Meta,Super,Hyper;
142
+
143
+        interpret.useModMapMods= AnyLevel;
144
+        interpret.repeat= False;
145
+
146
+        interpret Shift_L+AnyOf(all) {
147
+            action= SetMods(modifiers=Shift,clearLocks);
148
+        };
149
+        interpret Shift_R+AnyOf(all) {
150
+            action= SetMods(modifiers=Shift,clearLocks);
151
+        };
152
+        interpret Control_L+AnyOf(all) {
153
+            action= SetMods(modifiers=Control,clearLocks);
154
+        };
155
+        interpret Control_R+AnyOf(all) {
156
+            action= SetMods(modifiers=Control,clearLocks);
157
+        };
158
+        interpret Caps_Lock+AnyOf(all) {
159
+            action= LockMods(modifiers=Lock);
160
+        };
161
+        interpret Num_Lock+AnyOf(all) {
162
+            action= LockMods(modifiers=NumLock);
163
+        };
164
+        interpret Alt_L+AnyOf(all) {
165
+            action= SetMods(modifiers=Alt,clearLocks);
166
+        };
167
+        interpret Alt_R+AnyOf(all) {
168
+            action= SetMods(modifiers=Alt,clearLocks);
169
+        };
170
+        interpret Super_L+AnyOf(all) {
171
+            action= SetMods(modifiers=Super,clearLocks);
172
+        };
173
+        interpret Super_R+AnyOf(all) {
174
+            action= SetMods(modifiers=Super,clearLocks);
175
+        };
176
+    };
177
+
178
+    xkb_symbols "us" {
179
+        name[group1]="English (US)";
180
+
181
+        key <ESC>  { [ Escape ] };
182
+        key <AE01> { [ 1, exclam ] };
183
+        key <AE02> { [ 2, at ] };
184
+        key <AE03> { [ 3, numbersign ] };
185
+        key <AE04> { [ 4, dollar ] };
186
+        key <AE05> { [ 5, percent ] };
187
+        key <AE06> { [ 6, asciicircum ] };
188
+        key <AE07> { [ 7, ampersand ] };
189
+        key <AE08> { [ 8, asterisk ] };
190
+        key <AE09> { [ 9, parenleft ] };
191
+        key <AE10> { [ 0, parenright ] };
192
+        key <AE11> { [ minus, underscore ] };
193
+        key <AE12> { [ equal, plus ] };
194
+        key <BKSP> { [ BackSpace ] };
195
+        key <TAB>  { [ Tab, ISO_Left_Tab ] };
196
+        key <AD01> { [ q, Q ] };
197
+        key <AD02> { [ w, W ] };
198
+        key <AD03> { [ e, E ] };
199
+        key <AD04> { [ r, R ] };
200
+        key <AD05> { [ t, T ] };
201
+        key <AD06> { [ y, Y ] };
202
+        key <AD07> { [ u, U ] };
203
+        key <AD08> { [ i, I ] };
204
+        key <AD09> { [ o, O ] };
205
+        key <AD10> { [ p, P ] };
206
+        key <AD11> { [ bracketleft, braceleft ] };
207
+        key <AD12> { [ bracketright, braceright ] };
208
+        key <RTRN> { [ Return ] };
209
+        key <LCTL> { [ Control_L ] };
210
+        key <AC01> { [ a, A ] };
211
+        key <AC02> { [ s, S ] };
212
+        key <AC03> { [ d, D ] };
213
+        key <AC04> { [ f, F ] };
214
+        key <AC05> { [ g, G ] };
215
+        key <AC06> { [ h, H ] };
216
+        key <AC07> { [ j, J ] };
217
+        key <AC08> { [ k, K ] };
218
+        key <AC09> { [ l, L ] };
219
+        key <AC10> { [ semicolon, colon ] };
220
+        key <AC11> { [ apostrophe, quotedbl ] };
221
+        key <TLDE> { [ grave, asciitilde ] };
222
+        key <LFSH> { [ Shift_L ] };
223
+        key <BKSL> { [ backslash, bar ] };
224
+        key <AB01> { [ z, Z ] };
225
+        key <AB02> { [ x, X ] };
226
+        key <AB03> { [ c, C ] };
227
+        key <AB04> { [ v, V ] };
228
+        key <AB05> { [ b, B ] };
229
+        key <AB06> { [ n, N ] };
230
+        key <AB07> { [ m, M ] };
231
+        key <AB08> { [ comma, less ] };
232
+        key <AB09> { [ period, greater ] };
233
+        key <AB10> { [ slash, question ] };
234
+        key <RTSH> { [ Shift_R ] };
235
+        key <LALT> { [ Alt_L ] };
236
+        key <SPCE> { [ space ] };
237
+        key <CAPS> { [ Caps_Lock ] };
238
+        key <FK01> { [ F1 ] };
239
+        key <FK02> { [ F2 ] };
240
+        key <FK03> { [ F3 ] };
241
+        key <FK04> { [ F4 ] };
242
+        key <FK05> { [ F5 ] };
243
+        key <FK06> { [ F6 ] };
244
+        key <FK07> { [ F7 ] };
245
+        key <FK08> { [ F8 ] };
246
+        key <FK09> { [ F9 ] };
247
+        key <FK10> { [ F10 ] };
248
+        key <FK11> { [ F11 ] };
249
+        key <FK12> { [ F12 ] };
250
+        key <NMLK> { [ Num_Lock ] };
251
+        key <SCLK> { [ Scroll_Lock ] };
252
+        key <RCTL> { [ Control_R ] };
253
+        key <RALT> { [ Alt_R ] };
254
+        key <HOME> { [ Home ] };
255
+        key <UP>   { [ Up ] };
256
+        key <PGUP> { [ Prior ] };
257
+        key <LEFT> { [ Left ] };
258
+        key <RGHT> { [ Right ] };
259
+        key <END>  { [ End ] };
260
+        key <DOWN> { [ Down ] };
261
+        key <PGDN> { [ Next ] };
262
+        key <INS>  { [ Insert ] };
263
+        key <DELE> { [ Delete ] };
264
+        key <PAUS> { [ Pause ] };
265
+        key <LWIN> { [ Super_L ] };
266
+        key <RWIN> { [ Super_R ] };
267
+        key <MENU> { [ Menu ] };
268
+
269
+        modifier_map Shift { <LFSH>, <RTSH> };
270
+        modifier_map Lock { <CAPS> };
271
+        modifier_map Control { <LCTL>, <RCTL> };
272
+        modifier_map Mod1 { <LALT>, <RALT> };
273
+        modifier_map Mod4 { <LWIN>, <RWIN> };
274
+    };
275
+};
hyprkvm-daemon/src/input/mod.rsadded
@@ -0,0 +1,12 @@
1
+//! Input handling module
2
+//!
3
+//! Handles input capture (when sending to remote) and injection (when receiving).
4
+
5
+pub mod capture;
6
+pub mod emulation;
7
+
8
+// TODO: Sprint 3 - Implement input handling
9
+// pub mod router;
10
+
11
+pub use capture::{EdgeCapture, EdgeCaptureConfig, EdgeCaptureError, EdgeEvent};
12
+pub use emulation::{InputEmulator, VirtualKeyboard, VirtualPointer, EmulationError, button_codes};