1use crate::capture::OutputCapture;
10use crate::render::Gpu;
11use crate::wl::Region;
12use anyhow::Result;
13use smithay_client_toolkit::{
14 compositor::{CompositorHandler, CompositorState},
15 delegate_compositor, delegate_keyboard, delegate_layer, delegate_output, delegate_pointer,
16 delegate_registry, delegate_seat,
17 output::{OutputHandler, OutputState},
18 registry::{ProvidesRegistryState, RegistryState},
19 registry_handlers,
20 seat::{
21 Capability, SeatHandler, SeatState,
22 keyboard::{KeyEvent, KeyboardHandler, Keysym, Modifiers, RawModifiers},
23 pointer::{PointerEvent, PointerEventKind, PointerHandler},
24 },
25 shell::{
26 WaylandSurface,
27 wlr_layer::{
28 Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
29 LayerSurfaceConfigure,
30 },
31 },
32};
33use wayland_client::{
34 Connection, QueueHandle,
35 globals::registry_queue_init,
36 protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_surface},
37};
38
39const ACCENT: egui::Color32 = egui::Color32::from_rgb(0x4d, 0x9a, 0xff);
40
41#[derive(Clone, Copy, PartialEq, Eq)]
43pub enum Mode {
44 Region,
46 Point,
49 Magnify,
52}
53
54enum Outcome {
57 Region(Region),
58 Point { x: i32, y: i32 },
59}
60
61struct Surface {
63 layer: LayerSurface,
64 logical_x: i32,
66 logical_y: i32,
67 egui_ctx: egui::Context,
68 gpu: Option<Gpu>,
69 tex: Option<egui::TextureHandle>,
70 rgba: Vec<u8>,
73 img_w: usize,
74 img_h: usize,
75 width: u32,
77 height: u32,
78 scale: u32,
79}
80
81impl Surface {
82 fn ensure_gpu(&mut self, conn: &Connection) {
83 if self.gpu.is_some() || self.width == 0 {
84 return;
85 }
86 let (pw, ph) = (
87 (self.width * self.scale) as i32,
88 (self.height * self.scale) as i32,
89 );
90 self.gpu = Some(Gpu::new(conn, self.layer.wl_surface(), pw, ph));
91 }
92
93 fn render(
96 &mut self,
97 conn: &Connection,
98 mode: Mode,
99 selection: Option<Region>,
100 pointer: Option<(f64, f64)>,
101 zoom: f32,
102 ) {
103 self.ensure_gpu(conn);
104 if self.width == 0 {
105 return;
106 }
107 if self.tex.is_none() {
108 let img =
109 egui::ColorImage::from_rgba_unmultiplied([self.img_w, self.img_h], &self.rgba);
110 self.tex = Some(self.egui_ctx.load_texture(
111 "frozen",
112 img,
113 egui::TextureOptions::LINEAR,
114 ));
115 }
116 let (pw, ph) = (self.width * self.scale, self.height * self.scale);
117 let raw_input = egui::RawInput {
118 screen_rect: Some(egui::Rect::from_min_size(
119 egui::Pos2::ZERO,
120 egui::vec2(self.width as f32, self.height as f32),
121 )),
122 ..Default::default()
123 };
124 let tex = self.tex.clone();
125 let (w, h) = (self.width as f32, self.height as f32);
126 let (lx, ly) = (self.logical_x, self.logical_y);
127 let (img_w, img_h) = (self.img_w, self.img_h);
128 let frozen: &[u8] = &self.rgba;
129 let Some(gpu) = self.gpu.as_mut() else {
130 return;
131 };
132 gpu.render(
133 &self.egui_ctx,
134 raw_input,
135 self.scale as f32,
136 (pw, ph),
137 [0.0, 0.0, 0.0, 1.0],
138 |ui, _importer| match mode {
139 Mode::Region => {
140 draw_region_overlay(ui, tex.as_ref(), w, h, lx, ly, selection, pointer)
141 }
142 Mode::Point => draw_point_overlay(
143 ui,
144 tex.as_ref(),
145 w,
146 h,
147 lx,
148 ly,
149 pointer,
150 frozen,
151 img_w,
152 img_h,
153 ),
154 Mode::Magnify => {
155 draw_magnify_overlay(ui, tex.as_ref(), w, h, lx, ly, pointer, zoom)
156 }
157 },
158 );
159 self.layer.commit();
160 }
161}
162
163#[allow(clippy::too_many_arguments)]
166fn draw_region_overlay(
167 ui: &mut egui::Ui,
168 tex: Option<&egui::TextureHandle>,
169 w: f32,
170 h: f32,
171 lx: i32,
172 ly: i32,
173 selection: Option<Region>,
174 pointer: Option<(f64, f64)>,
175) {
176 let full_uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
177 egui::CentralPanel::default()
178 .frame(egui::Frame::NONE)
179 .show_inside(ui, |ui| {
180 let p = ui.painter();
181 let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(w, h));
182 if let Some(t) = tex {
183 p.image(t.id(), screen, full_uv, egui::Color32::WHITE);
184 }
185
186 match selection {
187 None => {
188 p.rect_filled(screen, 0.0, egui::Color32::from_black_alpha(48));
191 }
192 Some(sel) => {
193 let local = egui::Rect::from_min_size(
195 egui::pos2(sel.x as f32 - lx as f32, sel.y as f32 - ly as f32),
196 egui::vec2(sel.w as f32, sel.h as f32),
197 );
198 p.rect_filled(screen, 0.0, egui::Color32::from_black_alpha(120));
200 let vis = local.intersect(screen);
201 if vis.width() > 0.5
202 && vis.height() > 0.5
203 && let Some(t) = tex
204 {
205 let uv = egui::Rect::from_min_max(
206 egui::pos2(vis.min.x / w, vis.min.y / h),
207 egui::pos2(vis.max.x / w, vis.max.y / h),
208 );
209 p.image(t.id(), vis, uv, egui::Color32::WHITE);
210 }
211 p.rect_stroke(
212 local,
213 0.0,
214 egui::Stroke::new(2.0, ACCENT),
215 egui::StrokeKind::Inside,
216 );
217 if screen.contains(local.min) {
219 p.text(
220 local.min + egui::vec2(6.0, 6.0),
221 egui::Align2::LEFT_TOP,
222 format!("{} × {}", sel.w, sel.h),
223 egui::FontId::monospace(13.0),
224 egui::Color32::WHITE,
225 );
226 }
227 }
228 }
229
230 if let Some((gx, gy)) = pointer {
233 let (cx, cy) = (gx as f32 - lx as f32, gy as f32 - ly as f32);
234 if (0.0..=w).contains(&cx) && (0.0..=h).contains(&cy) {
235 let col = egui::Color32::from_white_alpha(150);
236 p.line_segment(
237 [egui::pos2(0.0, cy), egui::pos2(w, cy)],
238 egui::Stroke::new(1.0, col),
239 );
240 p.line_segment(
241 [egui::pos2(cx, 0.0), egui::pos2(cx, h)],
242 egui::Stroke::new(1.0, col),
243 );
244 if selection.is_none() {
245 let galley = p.layout_no_wrap(
246 crate::tr!("overlay-region-hint"),
247 egui::FontId::proportional(14.0),
248 egui::Color32::WHITE,
249 );
250 let at = egui::pos2(cx + 16.0, cy + 16.0);
251 let bg = egui::Rect::from_min_size(
252 at - egui::vec2(8.0, 5.0),
253 galley.size() + egui::vec2(16.0, 10.0),
254 );
255 p.rect_filled(bg, 6.0, egui::Color32::from_black_alpha(200));
256 p.galley(at, galley, egui::Color32::WHITE);
257 }
258 }
259 }
260 });
261}
262
263#[allow(clippy::too_many_arguments)]
268fn draw_point_overlay(
269 ui: &mut egui::Ui,
270 tex: Option<&egui::TextureHandle>,
271 w: f32,
272 h: f32,
273 lx: i32,
274 ly: i32,
275 pointer: Option<(f64, f64)>,
276 frozen: &[u8],
277 img_w: usize,
278 img_h: usize,
279) {
280 let full_uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
281 egui::CentralPanel::default()
282 .frame(egui::Frame::NONE)
283 .show_inside(ui, |ui| {
284 let p = ui.painter();
285 let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(w, h));
286 if let Some(t) = tex {
287 p.image(t.id(), screen, full_uv, egui::Color32::WHITE);
288 }
289 let Some((gx, gy)) = pointer else { return };
290 let (cx, cy) = (gx as f32 - lx as f32, gy as f32 - ly as f32);
291 if !((0.0..=w).contains(&cx) && (0.0..=h).contains(&cy)) {
292 return;
293 }
294
295 let px = ((cx / w) * img_w as f32).floor() as isize;
298 let py = ((cy / h) * img_h as f32).floor() as isize;
299 let sample = |ix: isize, iy: isize| -> Option<egui::Color32> {
300 if ix < 0 || iy < 0 || ix >= img_w as isize || iy >= img_h as isize {
301 return None;
302 }
303 let idx = ((iy as usize) * img_w + ix as usize) * 4;
304 frozen
305 .get(idx..idx + 3)
306 .map(|c| egui::Color32::from_rgb(c[0], c[1], c[2]))
307 };
308
309 let col = egui::Color32::from_white_alpha(120);
311 p.line_segment(
312 [egui::pos2(0.0, cy), egui::pos2(w, cy)],
313 egui::Stroke::new(1.0, col),
314 );
315 p.line_segment(
316 [egui::pos2(cx, 0.0), egui::pos2(cx, h)],
317 egui::Stroke::new(1.0, col),
318 );
319
320 const R: isize = 6;
323 const Z: f32 = 11.0;
324 let loupe = (2 * R + 1) as f32 * Z;
325 let mut origin = egui::pos2(cx + 20.0, cy + 20.0);
326 if origin.x + loupe > w {
327 origin.x = cx - 20.0 - loupe;
328 }
329 if origin.y + loupe + 26.0 > h {
330 origin.y = cy - 20.0 - loupe - 26.0;
331 }
332 origin.x = origin.x.clamp(2.0, (w - loupe).max(2.0));
333 origin.y = origin.y.clamp(2.0, (h - loupe - 26.0).max(2.0));
334
335 let frame = egui::Rect::from_min_size(origin, egui::vec2(loupe, loupe));
336 p.rect_filled(frame.expand(3.0), 4.0, egui::Color32::from_black_alpha(220));
337 for dy in -R..=R {
338 for dx in -R..=R {
339 let c = sample(px + dx, py + dy).unwrap_or(egui::Color32::from_gray(20));
340 let cell = egui::Rect::from_min_size(
341 origin + egui::vec2((dx + R) as f32 * Z, (dy + R) as f32 * Z),
342 egui::vec2(Z, Z),
343 );
344 p.rect_filled(cell, 0.0, c);
345 }
346 }
347 let centre = egui::Rect::from_min_size(
349 origin + egui::vec2(R as f32 * Z, R as f32 * Z),
350 egui::vec2(Z, Z),
351 );
352 p.rect_stroke(
353 centre,
354 0.0,
355 egui::Stroke::new(1.5, ACCENT),
356 egui::StrokeKind::Outside,
357 );
358
359 if let Some(c) = sample(px, py) {
361 let hex = format!("#{:02X}{:02X}{:02X}", c.r(), c.g(), c.b());
362 let at = egui::pos2(origin.x, origin.y + loupe + 4.0);
363 let galley =
364 p.layout_no_wrap(hex, egui::FontId::monospace(15.0), egui::Color32::WHITE);
365 let bg = egui::Rect::from_min_size(
366 at - egui::vec2(4.0, 2.0),
367 galley.size() + egui::vec2(30.0, 6.0),
368 );
369 p.rect_filled(bg, 4.0, egui::Color32::from_black_alpha(220));
370 let sw = egui::Rect::from_min_size(
371 egui::pos2(bg.max.x - 20.0, bg.min.y + 3.0),
372 egui::vec2(14.0, bg.height() - 6.0),
373 );
374 p.rect_filled(sw, 2.0, c);
375 p.galley(at, galley, egui::Color32::WHITE);
376
377 let hint = p.layout_no_wrap(
379 crate::tr!("overlay-pick-hint"),
380 egui::FontId::proportional(12.0),
381 egui::Color32::from_white_alpha(200),
382 );
383 let hat = egui::pos2(bg.min.x + 2.0, bg.max.y + 4.0);
384 let hbg = egui::Rect::from_min_size(
385 hat - egui::vec2(4.0, 2.0),
386 hint.size() + egui::vec2(8.0, 4.0),
387 );
388 p.rect_filled(hbg, 4.0, egui::Color32::from_black_alpha(200));
389 p.galley(hat, hint, egui::Color32::from_white_alpha(200));
390 }
391 });
392}
393
394#[allow(clippy::too_many_arguments)]
398fn draw_magnify_overlay(
399 ui: &mut egui::Ui,
400 tex: Option<&egui::TextureHandle>,
401 w: f32,
402 h: f32,
403 lx: i32,
404 ly: i32,
405 pointer: Option<(f64, f64)>,
406 zoom: f32,
407) {
408 let full_uv = egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0));
409 egui::CentralPanel::default()
410 .frame(egui::Frame::NONE)
411 .show_inside(ui, |ui| {
412 let p = ui.painter();
413 let screen = egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(w, h));
414 let Some(t) = tex else { return };
415
416 let here = pointer
418 .map(|(gx, gy)| (gx as f32 - lx as f32, gy as f32 - ly as f32))
419 .filter(|(cx, cy)| (0.0..=w).contains(cx) && (0.0..=h).contains(cy));
420 let Some((cx, cy)) = here else {
421 p.image(t.id(), screen, full_uv, egui::Color32::WHITE);
423 return;
424 };
425
426 let (cu, cv) = (cx / w, cy / h);
429 let uv = egui::Rect::from_min_max(
430 egui::pos2(cu - cx / (zoom * w), cv - cy / (zoom * h)),
431 egui::pos2(cu + (w - cx) / (zoom * w), cv + (h - cy) / (zoom * h)),
432 );
433 p.image(t.id(), screen, uv, egui::Color32::WHITE);
434
435 let col = egui::Color32::from_white_alpha(120);
437 p.line_segment(
438 [egui::pos2(0.0, cy), egui::pos2(w, cy)],
439 egui::Stroke::new(1.0, col),
440 );
441 p.line_segment(
442 [egui::pos2(cx, 0.0), egui::pos2(cx, h)],
443 egui::Stroke::new(1.0, col),
444 );
445
446 let label = format!("{zoom:.1}× · {}", crate::tr!("overlay-magnify-hint"));
448 let galley = p.layout_no_wrap(
449 label,
450 egui::FontId::proportional(13.0),
451 egui::Color32::WHITE,
452 );
453 let at = egui::pos2(14.0, 14.0);
454 let bg = egui::Rect::from_min_size(
455 at - egui::vec2(6.0, 4.0),
456 galley.size() + egui::vec2(12.0, 8.0),
457 );
458 p.rect_filled(bg, 6.0, egui::Color32::from_black_alpha(200));
459 p.galley(at, galley, egui::Color32::WHITE);
460 });
461}
462
463struct State {
464 registry_state: RegistryState,
465 seat_state: SeatState,
466 output_state: OutputState,
467 keyboard: Option<wl_keyboard::WlKeyboard>,
468 pointer: Option<wl_pointer::WlPointer>,
469 surfaces: Vec<Surface>,
470 mode: Mode,
472
473 pointer_pos: Option<(f64, f64)>,
475 start: Option<(f64, f64)>,
477 cur: Option<(f64, f64)>,
478 zoom: f32,
480 dirty: bool,
482 result: Option<Outcome>,
484 done: bool,
485}
486
487impl State {
488 fn selection(&self) -> Option<Region> {
490 let (sx, sy) = self.start?;
491 let (cx, cy) = self.cur?;
492 let (x0, y0) = (sx.min(cx), sy.min(cy));
493 let (x1, y1) = (sx.max(cx), sy.max(cy));
494 let r = Region {
495 x: x0.floor() as i32,
496 y: y0.floor() as i32,
497 w: (x1 - x0).round() as u32,
498 h: (y1 - y0).round() as u32,
499 };
500 (!r.is_empty()).then_some(r)
501 }
502
503 fn to_global(&self, surface: &wl_surface::WlSurface, pos: (f64, f64)) -> Option<(f64, f64)> {
505 self.surfaces
506 .iter()
507 .find(|s| s.layer.wl_surface() == surface)
508 .map(|s| (s.logical_x as f64 + pos.0, s.logical_y as f64 + pos.1))
509 }
510
511 fn redraw_all(&mut self, conn: &Connection) {
512 let sel = self.selection();
513 let ptr = self.pointer_pos;
514 let mode = self.mode;
515 for s in &mut self.surfaces {
516 s.render(conn, mode, sel, ptr, self.zoom);
517 }
518 }
519}
520
521pub fn select_region(captures: &[OutputCapture]) -> Result<Option<Region>> {
524 let conn = Connection::connect_to_env()?;
525 select_region_on(&conn, captures)
526}
527
528pub fn select_region_on(conn: &Connection, captures: &[OutputCapture]) -> Result<Option<Region>> {
534 Ok(run(conn, captures, Mode::Region)?.map(|o| match o {
535 Outcome::Region(r) => r,
536 Outcome::Point { .. } => unreachable!("region mode yields a region"),
537 }))
538}
539
540pub fn pick_point(captures: &[OutputCapture]) -> Result<Option<(i32, i32)>> {
543 let conn = Connection::connect_to_env()?;
544 pick_point_on(&conn, captures)
545}
546
547pub fn pick_point_on(conn: &Connection, captures: &[OutputCapture]) -> Result<Option<(i32, i32)>> {
553 Ok(run(conn, captures, Mode::Point)?.map(|o| match o {
554 Outcome::Point { x, y } => (x, y),
555 Outcome::Region(_) => unreachable!("point mode yields a point"),
556 }))
557}
558
559pub fn magnify(captures: &[OutputCapture]) -> Result<()> {
564 let conn = Connection::connect_to_env()?;
565 magnify_on(&conn, captures)
566}
567
568pub fn magnify_on(conn: &Connection, captures: &[OutputCapture]) -> Result<()> {
571 run(conn, captures, Mode::Magnify)?;
572 Ok(())
573}
574
575fn run(conn: &Connection, captures: &[OutputCapture], mode: Mode) -> Result<Option<Outcome>> {
578 let (globals, mut queue) = registry_queue_init(conn)?;
579 let qh = queue.handle();
580
581 let compositor =
582 CompositorState::bind(&globals, &qh).map_err(|e| anyhow::anyhow!("wl_compositor: {e}"))?;
583 let layer_shell =
584 LayerShell::bind(&globals, &qh).map_err(|e| anyhow::anyhow!("layer-shell missing: {e}"))?;
585
586 let mut state = State {
587 registry_state: RegistryState::new(&globals),
588 seat_state: SeatState::new(&globals, &qh),
589 output_state: OutputState::new(&globals, &qh),
590 keyboard: None,
591 pointer: None,
592 surfaces: Vec::new(),
593 mode,
594 pointer_pos: None,
595 start: None,
596 cur: None,
597 zoom: 3.0,
598 dirty: false,
599 result: None,
600 done: false,
601 };
602
603 queue.roundtrip(&mut state)?;
606
607 let outputs: Vec<_> = state.output_state.outputs().collect();
608 for wl_out in outputs {
609 let Some(info) = state.output_state.info(&wl_out) else {
610 continue;
611 };
612 let Some(name) = info.name.clone() else {
613 continue;
614 };
615 let Some(cap) = captures.iter().find(|c| c.output.name == name) else {
616 continue;
617 };
618 let (lx, ly) = info
619 .logical_position
620 .unwrap_or((cap.output.logical_x, cap.output.logical_y));
621
622 let surface = compositor.create_surface(&qh);
623 let layer = layer_shell.create_layer_surface(
624 &qh,
625 surface,
626 Layer::Overlay,
627 Some("wlr-overlay"),
628 Some(&wl_out),
629 );
630 layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
631 layer.set_keyboard_interactivity(KeyboardInteractivity::Exclusive);
632 layer.set_exclusive_zone(-1);
633 layer.commit();
634
635 let egui_ctx = egui::Context::default();
636 state.surfaces.push(Surface {
637 layer,
638 logical_x: lx,
639 logical_y: ly,
640 egui_ctx,
641 gpu: None,
642 tex: None,
643 rgba: cap.image.rgba.clone(),
644 img_w: cap.image.width as usize,
645 img_h: cap.image.height as usize,
646 width: 0,
647 height: 0,
648 scale: 1,
649 });
650 }
651
652 if state.surfaces.is_empty() {
653 anyhow::bail!("no output to select");
654 }
655
656 while !state.done {
657 queue.blocking_dispatch(&mut state)?;
658 if state.dirty {
659 state.dirty = false;
660 state.redraw_all(conn);
661 }
662 }
663
664 Ok(state.result)
665}
666
667impl CompositorHandler for State {
668 fn scale_factor_changed(
669 &mut self,
670 conn: &Connection,
671 _: &QueueHandle<Self>,
672 surface: &wl_surface::WlSurface,
673 new_factor: i32,
674 ) {
675 if let Some(s) = self
676 .surfaces
677 .iter_mut()
678 .find(|s| s.layer.wl_surface() == surface)
679 {
680 s.scale = new_factor.max(1) as u32;
681 s.layer.wl_surface().set_buffer_scale(new_factor.max(1));
682 if let (Some(gpu), true) = (s.gpu.as_ref(), s.width > 0) {
683 gpu.resize((s.width * s.scale) as i32, (s.height * s.scale) as i32);
684 }
685 }
686 let sel = self.selection();
687 let ptr = self.pointer_pos;
688 let mode = self.mode;
689 if let Some(s) = self
690 .surfaces
691 .iter_mut()
692 .find(|s| s.layer.wl_surface() == surface)
693 {
694 s.render(conn, mode, sel, ptr, self.zoom);
695 }
696 }
697
698 fn transform_changed(
699 &mut self,
700 _: &Connection,
701 _: &QueueHandle<Self>,
702 _: &wl_surface::WlSurface,
703 _: wl_output::Transform,
704 ) {
705 }
706
707 fn frame(
708 &mut self,
709 conn: &Connection,
710 _: &QueueHandle<Self>,
711 surface: &wl_surface::WlSurface,
712 _: u32,
713 ) {
714 let sel = self.selection();
715 let ptr = self.pointer_pos;
716 let mode = self.mode;
717 if let Some(s) = self
718 .surfaces
719 .iter_mut()
720 .find(|s| s.layer.wl_surface() == surface)
721 {
722 s.render(conn, mode, sel, ptr, self.zoom);
723 }
724 }
725
726 fn surface_enter(
727 &mut self,
728 _: &Connection,
729 _: &QueueHandle<Self>,
730 _: &wl_surface::WlSurface,
731 _: &wl_output::WlOutput,
732 ) {
733 }
734 fn surface_leave(
735 &mut self,
736 _: &Connection,
737 _: &QueueHandle<Self>,
738 _: &wl_surface::WlSurface,
739 _: &wl_output::WlOutput,
740 ) {
741 }
742}
743
744impl LayerShellHandler for State {
745 fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
746 self.done = true; }
748
749 fn configure(
750 &mut self,
751 conn: &Connection,
752 _: &QueueHandle<Self>,
753 layer: &LayerSurface,
754 configure: LayerSurfaceConfigure,
755 _: u32,
756 ) {
757 let (w, h) = configure.new_size;
758 let sel = self.selection();
759 let ptr = self.pointer_pos;
760 let mode = self.mode;
761 if let Some(s) = self.surfaces.iter_mut().find(|s| &s.layer == layer) {
762 if w > 0 && h > 0 {
763 s.width = w;
764 s.height = h;
765 }
766 if s.width == 0 {
767 return;
768 }
769 if let Some(gpu) = s.gpu.as_ref() {
770 gpu.resize((s.width * s.scale) as i32, (s.height * s.scale) as i32);
771 }
772 s.render(conn, mode, sel, ptr, self.zoom);
773 }
774 }
775}
776
777impl SeatHandler for State {
778 fn seat_state(&mut self) -> &mut SeatState {
779 &mut self.seat_state
780 }
781 fn new_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
782 fn new_capability(
783 &mut self,
784 _: &Connection,
785 qh: &QueueHandle<Self>,
786 seat: wl_seat::WlSeat,
787 cap: Capability,
788 ) {
789 if cap == Capability::Keyboard && self.keyboard.is_none() {
790 self.keyboard = self.seat_state.get_keyboard(qh, &seat, None).ok();
791 }
792 if cap == Capability::Pointer && self.pointer.is_none() {
793 self.pointer = self.seat_state.get_pointer(qh, &seat).ok();
794 }
795 }
796 fn remove_capability(
797 &mut self,
798 _: &Connection,
799 _: &QueueHandle<Self>,
800 _: wl_seat::WlSeat,
801 _: Capability,
802 ) {
803 }
804 fn remove_seat(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_seat::WlSeat) {}
805}
806
807impl KeyboardHandler for State {
808 fn enter(
809 &mut self,
810 _: &Connection,
811 _: &QueueHandle<Self>,
812 _: &wl_keyboard::WlKeyboard,
813 _: &wl_surface::WlSurface,
814 _: u32,
815 _: &[u32],
816 _: &[Keysym],
817 ) {
818 }
819 fn leave(
820 &mut self,
821 _: &Connection,
822 _: &QueueHandle<Self>,
823 _: &wl_keyboard::WlKeyboard,
824 _: &wl_surface::WlSurface,
825 _: u32,
826 ) {
827 }
828 fn press_key(
829 &mut self,
830 _: &Connection,
831 _: &QueueHandle<Self>,
832 _: &wl_keyboard::WlKeyboard,
833 _: u32,
834 event: KeyEvent,
835 ) {
836 match event.keysym {
837 Keysym::Escape => {
838 self.result = None;
839 self.done = true;
840 }
841 Keysym::Return | Keysym::KP_Enter => match self.mode {
842 Mode::Region => {
843 if let Some(r) = self.selection() {
844 self.result = Some(Outcome::Region(r));
845 self.done = true;
846 }
847 }
848 Mode::Point => {
849 if let Some((gx, gy)) = self.pointer_pos {
850 self.result = Some(Outcome::Point {
851 x: gx.round() as i32,
852 y: gy.round() as i32,
853 });
854 self.done = true;
855 }
856 }
857 Mode::Magnify => {}
859 },
860 _ => {}
861 }
862 }
863 fn release_key(
864 &mut self,
865 _: &Connection,
866 _: &QueueHandle<Self>,
867 _: &wl_keyboard::WlKeyboard,
868 _: u32,
869 _: KeyEvent,
870 ) {
871 }
872 fn repeat_key(
873 &mut self,
874 _: &Connection,
875 _: &QueueHandle<Self>,
876 _: &wl_keyboard::WlKeyboard,
877 _: u32,
878 _: KeyEvent,
879 ) {
880 }
881 fn update_modifiers(
882 &mut self,
883 _: &Connection,
884 _: &QueueHandle<Self>,
885 _: &wl_keyboard::WlKeyboard,
886 _: u32,
887 _: Modifiers,
888 _: RawModifiers,
889 _: u32,
890 ) {
891 }
892}
893
894impl PointerHandler for State {
895 fn pointer_frame(
896 &mut self,
897 _: &Connection,
898 _: &QueueHandle<Self>,
899 _: &wl_pointer::WlPointer,
900 events: &[PointerEvent],
901 ) {
902 let mode = self.mode;
903 for e in events {
904 match e.kind {
905 PointerEventKind::Enter { .. } => {
906 self.pointer_pos = self.to_global(&e.surface, e.position);
907 self.dirty = true;
908 }
909 PointerEventKind::Leave { .. } => {
910 self.pointer_pos = None;
911 self.dirty = true;
912 }
913 PointerEventKind::Press { button: 0x110, .. } => {
914 let g = self.to_global(&e.surface, e.position);
915 match mode {
916 Mode::Region => {
917 if let Some(g) = g {
918 self.start = Some(g);
919 self.cur = Some(g);
920 self.pointer_pos = Some(g);
921 self.dirty = true;
922 }
923 }
924 Mode::Point => {
926 if let Some((gx, gy)) = g {
927 self.result = Some(Outcome::Point {
928 x: gx.round() as i32,
929 y: gy.round() as i32,
930 });
931 self.done = true;
932 }
933 }
934 Mode::Magnify => {}
935 }
936 }
937 PointerEventKind::Axis { vertical, .. } if mode == Mode::Magnify => {
939 let notches = if vertical.discrete != 0 {
940 vertical.discrete as f32
941 } else {
942 (vertical.absolute / 15.0) as f32
943 };
944 if notches != 0.0 {
945 self.zoom = (self.zoom * 1.15_f32.powf(-notches)).clamp(1.5, 40.0);
947 self.dirty = true;
948 }
949 }
950 PointerEventKind::Motion { .. } => {
951 if let Some(g) = self.to_global(&e.surface, e.position) {
953 self.pointer_pos = Some(g);
954 if mode == Mode::Region && self.start.is_some() {
955 self.cur = Some(g);
956 }
957 self.dirty = true;
958 }
959 }
960 PointerEventKind::Release { button: 0x110, .. } if mode == Mode::Region => {
961 if let Some(g) = self.to_global(&e.surface, e.position) {
962 self.cur = Some(g);
963 }
964 if let Some(r) = self.selection() {
965 self.result = Some(Outcome::Region(r));
966 self.done = true;
967 } else {
968 self.start = None;
970 self.cur = None;
971 self.dirty = true;
972 }
973 }
974 _ => {}
975 }
976 }
977 }
978}
979
980impl OutputHandler for State {
981 fn output_state(&mut self) -> &mut OutputState {
982 &mut self.output_state
983 }
984 fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
985 fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
986 fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: wl_output::WlOutput) {}
987}
988
989impl ProvidesRegistryState for State {
990 fn registry(&mut self) -> &mut RegistryState {
991 &mut self.registry_state
992 }
993 registry_handlers![OutputState, SeatState];
994}
995
996delegate_compositor!(State);
997delegate_output!(State);
998delegate_seat!(State);
999delegate_keyboard!(State);
1000delegate_pointer!(State);
1001delegate_layer!(State);
1002delegate_registry!(State);