Rust · 30122 bytes Raw Blame History
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 /// Monitor info from Hyprland
45 #[derive(Debug, Clone)]
46 pub struct MonitorInfo {
47 pub name: String,
48 pub x: i32,
49 pub y: i32,
50 pub width: u32,
51 pub height: u32,
52 pub scale: f32,
53 }
54
55 /// Configuration for edge barriers
56 #[derive(Debug, Clone)]
57 pub struct EdgeCaptureConfig {
58 /// Barrier thickness in pixels
59 pub barrier_size: u32,
60 /// Which edges to create barriers on
61 pub enabled_edges: Vec<Direction>,
62 /// Monitor positions from Hyprland (used instead of Wayland output positions)
63 pub monitors: Vec<MonitorInfo>,
64 }
65
66 impl Default for EdgeCaptureConfig {
67 fn default() -> Self {
68 Self {
69 barrier_size: 5, // 5 pixels to ensure we catch events
70 enabled_edges: vec![
71 Direction::Left,
72 Direction::Right,
73 Direction::Up,
74 Direction::Down,
75 ],
76 monitors: Vec::new(),
77 }
78 }
79 }
80
81 /// An edge barrier surface
82 struct EdgeBarrier {
83 direction: Direction,
84 surface: LayerSurface,
85 width: u32,
86 height: u32,
87 configured: bool,
88 }
89
90 /// Edge capture system state
91 struct EdgeCaptureState {
92 registry_state: RegistryState,
93 compositor_state: CompositorState,
94 output_state: OutputState,
95 seat_state: SeatState,
96 shm_state: Shm,
97 layer_shell: LayerShell,
98
99 barriers: Vec<EdgeBarrier>,
100 pool: Option<SlotPool>,
101 event_tx: mpsc::Sender<EdgeEvent>,
102 config: EdgeCaptureConfig,
103
104 // Track outputs for barrier sizing
105 outputs: Vec<OutputInfo>,
106
107 // Track which barrier surface the pointer is currently on
108 active_barrier_idx: Option<usize>,
109 // Last position on barrier (for detecting edge-pushing motion)
110 last_barrier_pos: (f64, f64),
111 // Debounce: last time we sent an event for each direction
112 last_trigger_time: std::collections::HashMap<Direction, Instant>,
113 }
114
115 #[derive(Debug, Clone)]
116 struct OutputInfo {
117 output: wl_output::WlOutput,
118 width: u32,
119 height: u32,
120 x: i32,
121 y: i32,
122 }
123
124 impl EdgeCaptureState {
125 /// Update output positions from Hyprland monitor info
126 fn update_output_positions(&mut self) {
127 // Hyprland reports physical resolution, Wayland reports logical (after scaling)
128 // We need to match by finding monitors whose logical size matches the wayland output
129 // Logical size = physical size / scale
130
131 let mut used_monitors: Vec<bool> = vec![false; self.config.monitors.len()];
132 let mut assigned_outputs: Vec<bool> = vec![false; self.outputs.len()];
133
134 // Helper to check if a monitor's logical size matches an output
135 let sizes_match = |mon: &MonitorInfo, out_w: u32, out_h: u32| -> bool {
136 // Use the monitor's actual scale from Hyprland
137 let scale = mon.scale as f64;
138 let logical_w = (mon.width as f64 / scale).round() as u32;
139 let logical_h = (mon.height as f64 / scale).round() as u32;
140
141 // Allow 1 pixel tolerance for rounding differences
142 let w_match = (logical_w as i32 - out_w as i32).abs() <= 1;
143 let h_match = (logical_h as i32 - out_h as i32).abs() <= 1;
144 w_match && h_match
145 };
146
147 // First pass: try to find unique matches
148 for (out_idx, out) in self.outputs.iter_mut().enumerate() {
149 let mut candidates: Vec<usize> = Vec::new();
150 for (i, mon) in self.config.monitors.iter().enumerate() {
151 if used_monitors[i] {
152 continue;
153 }
154 if sizes_match(mon, out.width, out.height) {
155 candidates.push(i);
156 }
157 }
158
159 // If exactly one candidate, use it
160 if candidates.len() == 1 {
161 let i = candidates[0];
162 let mon = &self.config.monitors[i];
163 tracing::info!("Matched output {}x{} to monitor {} at ({}, {}) scale={}",
164 out.width, out.height, mon.name, mon.x, mon.y, mon.scale);
165 out.x = mon.x;
166 out.y = mon.y;
167 used_monitors[i] = true;
168 assigned_outputs[out_idx] = true;
169 } else if candidates.len() > 1 {
170 tracing::debug!("Multiple candidates for output {}x{}: {:?}",
171 out.width, out.height,
172 candidates.iter().map(|&i| &self.config.monitors[i].name).collect::<Vec<_>>());
173 }
174 }
175
176 // Second pass: assign remaining outputs to remaining monitors by order
177 for (out_idx, out) in self.outputs.iter_mut().enumerate() {
178 if assigned_outputs[out_idx] {
179 continue; // Already assigned in first pass
180 }
181 // Find first unused monitor that matches
182 for (i, mon) in self.config.monitors.iter().enumerate() {
183 if used_monitors[i] {
184 continue;
185 }
186 if sizes_match(mon, out.width, out.height) {
187 tracing::info!("Assigned remaining output {}x{} to monitor {} at ({}, {}) scale={}",
188 out.width, out.height, mon.name, mon.x, mon.y, mon.scale);
189 out.x = mon.x;
190 out.y = mon.y;
191 used_monitors[i] = true;
192 assigned_outputs[out_idx] = true;
193 break;
194 }
195 }
196 }
197 }
198
199 fn create_barriers(&mut self, qh: &QueueHandle<Self>) {
200 // Update positions from Hyprland before creating barriers
201 self.update_output_positions();
202
203 // Log current outputs for debugging
204 tracing::info!("Creating barriers with {} outputs:", self.outputs.len());
205 for out in &self.outputs {
206 tracing::info!(" Output at ({}, {}) size {}x{}", out.x, out.y, out.width, out.height);
207 }
208
209 // Get total screen bounds
210 let (min_x, min_y, max_x, max_y) = self.screen_bounds();
211 tracing::info!("Screen bounds: ({}, {}) to ({}, {})", min_x, min_y, max_x, max_y);
212
213 for direction in &self.config.enabled_edges.clone() {
214 // Find the output(s) for this edge direction
215 // Up/Down may return multiple outputs (all monitors at top/bottom)
216 let edge_outputs = self.find_edge_outputs(*direction);
217 tracing::info!(" {:?} edge -> {} output(s)", direction, edge_outputs.len());
218
219 // Create a barrier on each edge output
220 for target_output in &edge_outputs {
221 let (width, height, anchor) = match direction {
222 Direction::Left => (
223 self.config.barrier_size,
224 target_output.height,
225 Anchor::LEFT | Anchor::TOP | Anchor::BOTTOM,
226 ),
227 Direction::Right => (
228 self.config.barrier_size,
229 target_output.height,
230 Anchor::RIGHT | Anchor::TOP | Anchor::BOTTOM,
231 ),
232 Direction::Up => (
233 target_output.width,
234 self.config.barrier_size,
235 Anchor::TOP | Anchor::LEFT | Anchor::RIGHT,
236 ),
237 Direction::Down => (
238 target_output.width,
239 self.config.barrier_size,
240 Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
241 ),
242 };
243
244 // Create layer surface on this specific output
245 let surface = self.compositor_state.create_surface(qh);
246 let layer_surface = self.layer_shell.create_layer_surface(
247 qh,
248 surface,
249 Layer::Top, // Top layer to catch pointer
250 Some(format!("hyprkvm-edge-{}", direction)),
251 Some(&target_output.output),
252 );
253
254 tracing::info!(
255 "Creating {:?} barrier on output at ({}, {}), size {}x{}",
256 direction,
257 target_output.x,
258 target_output.y,
259 width,
260 height
261 );
262
263 // Configure the layer surface
264 layer_surface.set_anchor(anchor);
265 layer_surface.set_size(width, height);
266 layer_surface.set_exclusive_zone(-1); // Don't reserve space
267 layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None);
268
269 // Commit to apply configuration
270 layer_surface.commit();
271
272 self.barriers.push(EdgeBarrier {
273 direction: *direction,
274 surface: layer_surface,
275 width,
276 height,
277 configured: false,
278 });
279 }
280
281 // Fallback if no outputs found for this direction
282 if edge_outputs.is_empty() {
283 tracing::warn!("No outputs found for {:?} edge, creating fallback barrier", direction);
284 let (width, height, anchor) = match direction {
285 Direction::Left => (
286 self.config.barrier_size,
287 (max_y - min_y) as u32,
288 Anchor::LEFT | Anchor::TOP | Anchor::BOTTOM,
289 ),
290 Direction::Right => (
291 self.config.barrier_size,
292 (max_y - min_y) as u32,
293 Anchor::RIGHT | Anchor::TOP | Anchor::BOTTOM,
294 ),
295 Direction::Up => (
296 (max_x - min_x) as u32,
297 self.config.barrier_size,
298 Anchor::TOP | Anchor::LEFT | Anchor::RIGHT,
299 ),
300 Direction::Down => (
301 (max_x - min_x) as u32,
302 self.config.barrier_size,
303 Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
304 ),
305 };
306
307 let surface = self.compositor_state.create_surface(qh);
308 let layer_surface = self.layer_shell.create_layer_surface(
309 qh,
310 surface,
311 Layer::Top,
312 Some(format!("hyprkvm-edge-{}", direction)),
313 None, // No specific output
314 );
315
316 layer_surface.set_anchor(anchor);
317 layer_surface.set_size(width, height);
318 layer_surface.set_exclusive_zone(-1);
319 layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None);
320 layer_surface.commit();
321
322 self.barriers.push(EdgeBarrier {
323 direction: *direction,
324 surface: layer_surface,
325 width,
326 height,
327 configured: false,
328 });
329 }
330 }
331 }
332
333 /// Find the output(s) at the edge of the screen for a given direction
334 /// For Left/Right: returns single monitor at the edge
335 /// For Up/Down: returns ALL monitors (in horizontal layouts, all monitors have exposed top/bottom edges)
336 fn find_edge_outputs(&self, direction: Direction) -> Vec<OutputInfo> {
337 if self.outputs.is_empty() {
338 return vec![];
339 }
340
341 match direction {
342 Direction::Left => {
343 // Find output with minimum x (leftmost) - single monitor
344 self.outputs.iter().min_by_key(|o| o.x).cloned().into_iter().collect()
345 }
346 Direction::Right => {
347 // Find output with maximum x + width (rightmost) - single monitor
348 self.outputs.iter().max_by_key(|o| o.x + o.width as i32).cloned().into_iter().collect()
349 }
350 Direction::Up => {
351 // For horizontal layouts: all monitors with nothing above them
352 // A monitor has nothing above if no other monitor overlaps its x range at a lower y
353 self.outputs.iter().filter(|o| {
354 !self.outputs.iter().any(|other| {
355 other.y + other.height as i32 <= o.y && // other is above
356 other.x < o.x + o.width as i32 && // overlaps in x
357 other.x + other.width as i32 > o.x
358 })
359 }).cloned().collect()
360 }
361 Direction::Down => {
362 // For horizontal layouts: all monitors with nothing below them
363 // A monitor has nothing below if no other monitor overlaps its x range at a higher y
364 self.outputs.iter().filter(|o| {
365 !self.outputs.iter().any(|other| {
366 other.y >= o.y + o.height as i32 && // other is below
367 other.x < o.x + o.width as i32 && // overlaps in x
368 other.x + other.width as i32 > o.x
369 })
370 }).cloned().collect()
371 }
372 }
373 }
374
375 fn screen_bounds(&self) -> (i32, i32, i32, i32) {
376 if self.outputs.is_empty() {
377 return (0, 0, 1920, 1080); // Fallback
378 }
379
380 let mut min_x = i32::MAX;
381 let mut min_y = i32::MAX;
382 let mut max_x = i32::MIN;
383 let mut max_y = i32::MIN;
384
385 for output in &self.outputs {
386 min_x = min_x.min(output.x);
387 min_y = min_y.min(output.y);
388 max_x = max_x.max(output.x + output.width as i32);
389 max_y = max_y.max(output.y + output.height as i32);
390 }
391
392 (min_x, min_y, max_x, max_y)
393 }
394
395 fn draw_barrier(&mut self, barrier_idx: usize, _qh: &QueueHandle<Self>) {
396 let barrier = &self.barriers[barrier_idx];
397 if !barrier.configured {
398 return;
399 }
400
401 let pool = match &mut self.pool {
402 Some(pool) => pool,
403 None => return,
404 };
405
406 let width = barrier.width;
407 let height = barrier.height;
408 let stride = width * 4;
409
410 let (buffer, canvas) = pool
411 .create_buffer(
412 width as i32,
413 height as i32,
414 stride as i32,
415 wl_shm::Format::Argb8888,
416 )
417 .expect("Failed to create buffer");
418
419 // Fill with transparent pixels
420 canvas.fill(0);
421
422 // Attach and commit
423 barrier.surface.wl_surface().attach(Some(buffer.wl_buffer()), 0, 0);
424 barrier.surface.wl_surface().damage_buffer(0, 0, width as i32, height as i32);
425 barrier.surface.commit();
426 }
427
428 fn find_barrier_by_surface(&self, surface: &wl_surface::WlSurface) -> Option<usize> {
429 self.barriers
430 .iter()
431 .position(|b| b.surface.wl_surface() == surface)
432 }
433 }
434
435 // Implement all the required handlers
436
437 impl CompositorHandler for EdgeCaptureState {
438 fn scale_factor_changed(
439 &mut self,
440 _conn: &Connection,
441 _qh: &QueueHandle<Self>,
442 _surface: &wl_surface::WlSurface,
443 _new_factor: i32,
444 ) {
445 }
446
447 fn transform_changed(
448 &mut self,
449 _conn: &Connection,
450 _qh: &QueueHandle<Self>,
451 _surface: &wl_surface::WlSurface,
452 _new_transform: wl_output::Transform,
453 ) {
454 }
455
456 fn frame(
457 &mut self,
458 _conn: &Connection,
459 _qh: &QueueHandle<Self>,
460 _surface: &wl_surface::WlSurface,
461 _time: u32,
462 ) {
463 }
464
465 fn surface_enter(
466 &mut self,
467 _conn: &Connection,
468 _qh: &QueueHandle<Self>,
469 _surface: &wl_surface::WlSurface,
470 _output: &wl_output::WlOutput,
471 ) {
472 }
473
474 fn surface_leave(
475 &mut self,
476 _conn: &Connection,
477 _qh: &QueueHandle<Self>,
478 _surface: &wl_surface::WlSurface,
479 _output: &wl_output::WlOutput,
480 ) {
481 }
482 }
483
484 impl OutputHandler for EdgeCaptureState {
485 fn output_state(&mut self) -> &mut OutputState {
486 &mut self.output_state
487 }
488
489 fn new_output(
490 &mut self,
491 _conn: &Connection,
492 _qh: &QueueHandle<Self>,
493 output: wl_output::WlOutput,
494 ) {
495 // We'll get geometry info from the output state
496 if let Some(info) = self.output_state.info(&output) {
497 if let Some(size) = info.logical_size {
498 let (x, y) = info.location;
499 self.outputs.push(OutputInfo {
500 output,
501 width: size.0 as u32,
502 height: size.1 as u32,
503 x,
504 y,
505 });
506 }
507 }
508 }
509
510 fn update_output(
511 &mut self,
512 _conn: &Connection,
513 _qh: &QueueHandle<Self>,
514 output: wl_output::WlOutput,
515 ) {
516 // Update output info
517 if let Some(info) = self.output_state.info(&output) {
518 if let Some(size) = info.logical_size {
519 let (x, y) = info.location;
520 if let Some(out) = self.outputs.iter_mut().find(|o| o.output == output) {
521 out.width = size.0 as u32;
522 out.height = size.1 as u32;
523 out.x = x;
524 out.y = y;
525 }
526 }
527 }
528 }
529
530 fn output_destroyed(
531 &mut self,
532 _conn: &Connection,
533 _qh: &QueueHandle<Self>,
534 output: wl_output::WlOutput,
535 ) {
536 self.outputs.retain(|o| o.output != output);
537 }
538 }
539
540 impl LayerShellHandler for EdgeCaptureState {
541 fn closed(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _layer: &LayerSurface) {
542 // Barrier closed, could recreate if needed
543 }
544
545 fn configure(
546 &mut self,
547 _conn: &Connection,
548 qh: &QueueHandle<Self>,
549 layer: &LayerSurface,
550 configure: LayerSurfaceConfigure,
551 _serial: u32,
552 ) {
553 // Find which barrier this is
554 if let Some(idx) = self.barriers.iter().position(|b| &b.surface == layer) {
555 let barrier = &mut self.barriers[idx];
556 barrier.configured = true;
557
558 // Update size if compositor changed it
559 if configure.new_size.0 > 0 {
560 barrier.width = configure.new_size.0;
561 }
562 if configure.new_size.1 > 0 {
563 barrier.height = configure.new_size.1;
564 }
565
566 // Draw the (transparent) barrier
567 self.draw_barrier(idx, qh);
568 }
569 }
570 }
571
572 impl SeatHandler for EdgeCaptureState {
573 fn seat_state(&mut self) -> &mut SeatState {
574 &mut self.seat_state
575 }
576
577 fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {}
578
579 fn new_capability(
580 &mut self,
581 _conn: &Connection,
582 qh: &QueueHandle<Self>,
583 seat: wl_seat::WlSeat,
584 capability: Capability,
585 ) {
586 if capability == Capability::Pointer {
587 self.seat_state.get_pointer(qh, &seat).ok();
588 }
589 }
590
591 fn remove_capability(
592 &mut self,
593 _conn: &Connection,
594 _qh: &QueueHandle<Self>,
595 _seat: wl_seat::WlSeat,
596 _capability: Capability,
597 ) {
598 }
599
600 fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _seat: wl_seat::WlSeat) {
601 }
602 }
603
604 impl PointerHandler for EdgeCaptureState {
605 fn pointer_frame(
606 &mut self,
607 _conn: &Connection,
608 _qh: &QueueHandle<Self>,
609 _pointer: &wl_pointer::WlPointer,
610 events: &[PointerEvent],
611 ) {
612 const DEBOUNCE_MS: u128 = 300; // Minimum time between triggers
613 const EDGE_THRESHOLD: f64 = 2.0; // Position threshold for "at edge"
614
615 for event in events {
616 match &event.kind {
617 PointerEventKind::Enter { .. } => {
618 // Pointer entered one of our barrier surfaces
619 if let Some(idx) = self.find_barrier_by_surface(&event.surface) {
620 self.active_barrier_idx = Some(idx);
621 self.last_barrier_pos = event.position;
622 tracing::debug!(
623 "Pointer entered {:?} barrier at ({}, {})",
624 self.barriers[idx].direction,
625 event.position.0,
626 event.position.1
627 );
628 }
629 }
630 PointerEventKind::Leave { .. } => {
631 if self.find_barrier_by_surface(&event.surface).is_some() {
632 self.active_barrier_idx = None;
633 tracing::debug!("Pointer left barrier");
634 }
635 }
636 PointerEventKind::Motion { .. } => {
637 // Check if we're on a barrier surface and detect edge-pushing motion
638 if let Some(idx) = self.active_barrier_idx {
639 let barrier = &self.barriers[idx];
640 let (x, y) = event.position;
641 let (last_x, last_y) = self.last_barrier_pos;
642
643 tracing::trace!(
644 "Motion on {:?} barrier: pos=({:.1}, {:.1}) last=({:.1}, {:.1}) size={}x{}",
645 barrier.direction,
646 x, y, last_x, last_y, barrier.width, barrier.height
647 );
648
649 // Calculate motion delta
650 let dx = x - last_x;
651 let dy = y - last_y;
652
653 // Check if motion is toward the edge while at the edge
654 let is_edge_push = match barrier.direction {
655 Direction::Left => x <= EDGE_THRESHOLD && dx < -0.1,
656 Direction::Right => x >= (barrier.width as f64 - EDGE_THRESHOLD) && dx > 0.1,
657 Direction::Up => y <= EDGE_THRESHOLD && dy < -0.1,
658 Direction::Down => y >= (barrier.height as f64 - EDGE_THRESHOLD) && dy > 0.1,
659 };
660
661 // Also trigger if cursor is stuck at edge position (x=0 or similar)
662 let is_at_edge = match barrier.direction {
663 Direction::Left => x < 1.0,
664 Direction::Right => x > (barrier.width as f64 - 1.0),
665 Direction::Up => y < 1.0,
666 Direction::Down => y > (barrier.height as f64 - 1.0),
667 };
668
669 if is_edge_push || is_at_edge {
670 // Check debounce
671 let now = Instant::now();
672 let should_trigger = self
673 .last_trigger_time
674 .get(&barrier.direction)
675 .map(|t| now.duration_since(*t).as_millis() >= DEBOUNCE_MS)
676 .unwrap_or(true);
677
678 if should_trigger {
679 self.last_trigger_time.insert(barrier.direction, now);
680
681 // Send edge event with direction only
682 // Main daemon will query Hyprland for actual cursor position
683 // and verify we're at screen boundary before triggering transfer
684 let edge_event = EdgeEvent {
685 direction: barrier.direction,
686 position: (0, 0), // Placeholder - main daemon uses Hyprland cursor pos
687 timestamp: now,
688 };
689
690 tracing::info!(
691 "Edge event: {:?} barrier triggered",
692 barrier.direction
693 );
694
695 let _ = self.event_tx.send(edge_event);
696 }
697 }
698
699 self.last_barrier_pos = event.position;
700 }
701 }
702 _ => {}
703 }
704 }
705 }
706 }
707
708 impl ShmHandler for EdgeCaptureState {
709 fn shm_state(&mut self) -> &mut Shm {
710 &mut self.shm_state
711 }
712 }
713
714 impl ProvidesRegistryState for EdgeCaptureState {
715 fn registry(&mut self) -> &mut RegistryState {
716 &mut self.registry_state
717 }
718
719 registry_handlers!(OutputState, SeatState);
720 }
721
722 // Delegate macros
723 delegate_compositor!(EdgeCaptureState);
724 delegate_output!(EdgeCaptureState);
725 delegate_layer!(EdgeCaptureState);
726 delegate_seat!(EdgeCaptureState);
727 delegate_pointer!(EdgeCaptureState);
728 delegate_shm!(EdgeCaptureState);
729 delegate_registry!(EdgeCaptureState);
730
731 /// Handle for controlling edge capture
732 pub struct EdgeCapture {
733 event_rx: mpsc::Receiver<EdgeEvent>,
734 // The event loop runs in a separate thread
735 _thread: std::thread::JoinHandle<()>,
736 }
737
738 impl EdgeCapture {
739 /// Create and start edge capture
740 pub fn new(config: EdgeCaptureConfig) -> Result<Self, EdgeCaptureError> {
741 let (event_tx, event_rx) = mpsc::channel();
742
743 let thread = std::thread::spawn(move || {
744 if let Err(e) = run_capture_loop(config, event_tx) {
745 tracing::error!("Edge capture error: {}", e);
746 }
747 });
748
749 Ok(Self {
750 event_rx,
751 _thread: thread,
752 })
753 }
754
755 /// Try to receive an edge event (non-blocking)
756 pub fn try_recv(&self) -> Option<EdgeEvent> {
757 self.event_rx.try_recv().ok()
758 }
759
760 /// Receive an edge event (blocking)
761 pub fn recv(&self) -> Option<EdgeEvent> {
762 self.event_rx.recv().ok()
763 }
764
765 /// Get the receiver for async integration
766 pub fn receiver(&self) -> &mpsc::Receiver<EdgeEvent> {
767 &self.event_rx
768 }
769 }
770
771 fn run_capture_loop(
772 config: EdgeCaptureConfig,
773 event_tx: mpsc::Sender<EdgeEvent>,
774 ) -> Result<(), EdgeCaptureError> {
775 let conn = Connection::connect_to_env().map_err(|e| EdgeCaptureError::Connection(e.to_string()))?;
776
777 let (globals, mut event_queue) =
778 registry_queue_init(&conn).map_err(|e| EdgeCaptureError::Registry(e.to_string()))?;
779 let qh = event_queue.handle();
780
781 let compositor_state = CompositorState::bind(&globals, &qh)
782 .map_err(|e| EdgeCaptureError::Protocol(format!("compositor: {}", e)))?;
783 let layer_shell = LayerShell::bind(&globals, &qh)
784 .map_err(|e| EdgeCaptureError::Protocol(format!("layer_shell: {}", e)))?;
785 let shm_state =
786 Shm::bind(&globals, &qh).map_err(|e| EdgeCaptureError::Protocol(format!("shm: {}", e)))?;
787
788 let mut state = EdgeCaptureState {
789 registry_state: RegistryState::new(&globals),
790 compositor_state,
791 output_state: OutputState::new(&globals, &qh),
792 seat_state: SeatState::new(&globals, &qh),
793 shm_state,
794 layer_shell,
795 barriers: Vec::new(),
796 pool: None,
797 event_tx,
798 config,
799 outputs: Vec::new(),
800 active_barrier_idx: None,
801 last_barrier_pos: (0.0, 0.0),
802 last_trigger_time: std::collections::HashMap::new(),
803 };
804
805 // Multiple roundtrips to ensure all output info is received
806 // Output geometry comes in separate events that may need multiple roundtrips
807 event_queue.roundtrip(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
808 event_queue.roundtrip(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
809 event_queue.roundtrip(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
810
811 tracing::info!("After roundtrips: {} outputs detected", state.outputs.len());
812
813 // Create shm pool for buffers
814 state.pool = Some(
815 SlotPool::new(256 * 256 * 4, &state.shm_state)
816 .map_err(|e| EdgeCaptureError::Shm(e.to_string()))?,
817 );
818
819 // Create barriers after we have output info
820 state.create_barriers(&qh);
821
822 // Another roundtrip to configure barriers
823 event_queue.roundtrip(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
824
825 tracing::info!("Edge capture running with {} barriers", state.barriers.len());
826
827 // Main event loop
828 loop {
829 event_queue.blocking_dispatch(&mut state).map_err(|e| EdgeCaptureError::Dispatch(e.to_string()))?;
830 }
831 }
832
833 #[derive(Debug, thiserror::Error)]
834 pub enum EdgeCaptureError {
835 #[error("Failed to connect to Wayland: {0}")]
836 Connection(String),
837
838 #[error("Registry error: {0}")]
839 Registry(String),
840
841 #[error("Protocol not available: {0}")]
842 Protocol(String),
843
844 #[error("Dispatch error: {0}")]
845 Dispatch(String),
846
847 #[error("SHM error: {0}")]
848 Shm(String),
849 }
850