@@ -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 | +} |