1use crate::{
2 Result, emit,
3 ipc::{self, Rect},
4};
5use serde::Deserialize;
6use smithay_client_toolkit::{
7 compositor::{CompositorHandler, CompositorState},
8 delegate_compositor, delegate_layer, delegate_output, delegate_registry, delegate_shm,
9 output::{OutputHandler, OutputState},
10 registry::{ProvidesRegistryState, RegistryState},
11 shell::{
12 WaylandSurface,
13 wlr_layer::{
14 Anchor, KeyboardInteractivity, Layer, LayerShell, LayerShellHandler, LayerSurface,
15 LayerSurfaceConfigure,
16 },
17 },
18 shm::{
19 Shm, ShmHandler,
20 slot::{Buffer, SlotPool},
21 },
22};
23use std::{
24 fs::File,
25 io::{BufReader, Read},
26 os::unix::net::UnixStream,
27 path::Path,
28 time::{Duration, Instant},
29};
30use wayland_client::{
31 Connection, Dispatch, QueueHandle, delegate_noop,
32 globals::registry_queue_init,
33 protocol::{
34 wl_output::{self, WlOutput},
35 wl_shm,
36 wl_subcompositor::WlSubcompositor,
37 wl_subsurface::WlSubsurface,
38 wl_surface::WlSurface,
39 },
40};
41use wayland_protocols::wp::presentation_time::client::{
42 wp_presentation::WpPresentation,
43 wp_presentation_feedback::{self, WpPresentationFeedback},
44};
45use wayland_protocols::wp::viewporter::client::{
46 wp_viewport::WpViewport, wp_viewporter::WpViewporter,
47};
48
49enum Receipt {
50 Initial(usize),
51 Landed,
52}
53
54#[derive(Clone, Copy, PartialEq)]
55enum Texture {
56 None,
57 Flash,
58 Image,
59}
60
61#[derive(Deserialize)]
62pub struct Options {
63 bounds: Rect,
64 pid: u32,
65 flash: bool,
66 animate: bool,
67}
68#[derive(Deserialize)]
69#[serde(tag = "command", rename_all = "lowercase")]
70enum Command {
71 Close,
72 Animate { title: String, frame: Rect },
73}
74struct Overlay {
75 layer: LayerSurface,
76 image: WlSurface,
77 subsurface: WlSubsurface,
78 viewport: WpViewport,
79 _background: WpViewport,
80 bounds: Rect,
81 configured: bool,
82 presented: bool,
83 frame_pending: bool,
84 receipt_pending: bool,
85 texture: Texture,
86 background_attached: bool,
87}
88struct Feedback {
89 registry: RegistryState,
90 output: OutputState,
91 shm: Shm,
92 presentation: WpPresentation,
93 _pool: SlotPool,
94 overlays: Vec<Overlay>,
95 background: Buffer,
96 image: Buffer,
97 flash: Buffer,
98 image_size: (u32, u32),
99 options: Options,
100 start: Instant,
101 flight: Option<(Rect, Instant)>,
102 destination: Option<ipc::Window>,
103 ready: bool,
104 landed: bool,
105 landing_receipts: usize,
106 done: bool,
107}
108
109fn clip(rect: Rect, output: Rect, image: (u32, u32)) -> Option<(Rect, Rect)> {
110 if !rect.valid() || !output.valid() || !rect.intersects(output) {
111 return None;
112 }
113 let x = rect.x.max(output.x);
114 let y = rect.y.max(output.y);
115 let right = (rect.x + rect.width).min(output.x + output.width);
116 let bottom = (rect.y + rect.height).min(output.y + output.height);
117 if right - x < 1. || bottom - y < 1. {
118 return None;
119 }
120 // Wayland source rectangles use 24.8 fixed point. Quantize edges together so rounding
121 // can never put the right/bottom edge outside the image at fractional output scales.
122 let left_px = ((x - rect.x) / rect.width * f64::from(image.0) * 256.).floor() / 256.;
123 let top_px = ((y - rect.y) / rect.height * f64::from(image.1) * 256.).floor() / 256.;
124 let right_px = ((right - rect.x) / rect.width * f64::from(image.0) * 256.).floor() / 256.;
125 let bottom_px = ((bottom - rect.y) / rect.height * f64::from(image.1) * 256.).floor() / 256.;
126 if right_px <= left_px || bottom_px <= top_px {
127 return None;
128 }
129 Some((
130 Rect {
131 x: x - output.x,
132 y: y - output.y,
133 width: right - x,
134 height: bottom - y,
135 },
136 Rect {
137 x: left_px,
138 y: top_px,
139 width: right_px - left_px,
140 height: bottom_px - top_px,
141 },
142 ))
143}
144fn interpolate(from: Rect, to: Rect, progress: f64) -> Rect {
145 let t = 1.0 - (1.0 - progress.clamp(0., 1.)).powi(3);
146 Rect {
147 x: from.x + (to.x - from.x) * t,
148 y: from.y + (to.y - from.y) * t,
149 width: from.width + (to.width - from.width) * t,
150 height: from.height + (to.height - from.height) * t,
151 }
152}
153fn target(bounds: Rect, frame: Rect) -> Result<Rect> {
154 if !bounds.valid()
155 || !frame.valid()
156 || frame.x < 0.
157 || frame.y < 0.
158 || frame.x + frame.width > 1.01
159 || frame.y + frame.height > 1.01
160 {
161 return Err("Invalid capture animation destination.".into());
162 }
163 Ok(Rect {
164 x: bounds.x + frame.x * bounds.width,
165 y: bounds.y + frame.y * bounds.height,
166 width: frame.width * bounds.width,
167 height: frame.height * bounds.height,
168 })
169}
170
171pub fn run(directory: &Path, mut options: Options) -> Result<()> {
172 if !options.bounds.valid() {
173 return Err("Invalid capture bounds.".into());
174 }
175 ipc::ensure_unlocked()?;
176 options.animate &= ipc::animations_enabled();
177 let mut events = UnixStream::connect(ipc::session_directory()?.join(".socket2.sock"))?;
178 events.set_nonblocking(true)?;
179 let mut decoder = png::Decoder::new(BufReader::new(File::open(directory.join("capture.png"))?));
180 decoder.set_limits(png::Limits {
181 bytes: 128 * 1024 * 1024,
182 });
183 let mut reader = decoder.read_info()?;
184 let mut bytes = vec![
185 0;
186 reader
187 .output_buffer_size()
188 .ok_or("Invalid capture image size.")?
189 ];
190 let info = reader.next_frame(&mut bytes)?;
191 if info.color_type != png::ColorType::Rgba || info.bit_depth != png::BitDepth::Eight {
192 return Err("Expected RGBA capture image.".into());
193 }
194 let (connection, mut queue, mut state, compositor, layer_shell, subcompositor, viewporter) = {
195 let connection = Connection::connect_to_env()?;
196 let (globals, queue) = registry_queue_init::<Feedback>(&connection)?;
197 let qh = queue.handle();
198 let compositor = CompositorState::bind(&globals, &qh)?;
199 let layer_shell = LayerShell::bind(&globals, &qh)?;
200 let subcompositor: WlSubcompositor = globals.bind(&qh, 1..=1, ())?;
201 let viewporter: WpViewporter = globals.bind(&qh, 1..=1, ())?;
202 let presentation: WpPresentation = globals.bind(&qh, 1..=1, ())?;
203 let shm = Shm::bind(&globals, &qh)?;
204 let mut pool = SlotPool::new(bytes.len() * 2 + 4, &shm)?;
205 let (background, canvas) = pool.create_buffer(1, 1, 4, wl_shm::Format::Argb8888)?;
206 canvas.fill(0);
207 let (image, canvas) = pool.create_buffer(
208 info.width as i32,
209 info.height as i32,
210 info.width as i32 * 4,
211 wl_shm::Format::Argb8888,
212 )?;
213 // Wayland's ARGB8888 is native-endian and premultiplied; PNG is straight RGBA.
214 for (src, dst) in bytes.chunks_exact(4).zip(canvas.chunks_exact_mut(4)) {
215 let a = u32::from(src[3]);
216 dst.copy_from_slice(&[
217 (u32::from(src[2]) * a / 255) as u8,
218 (u32::from(src[1]) * a / 255) as u8,
219 (u32::from(src[0]) * a / 255) as u8,
220 src[3],
221 ]);
222 }
223 let (flash, canvas) = pool.create_buffer(
224 info.width as i32,
225 info.height as i32,
226 info.width as i32 * 4,
227 wl_shm::Format::Argb8888,
228 )?;
229 for (src, dst) in bytes.chunks_exact(4).zip(canvas.chunks_exact_mut(4)) {
230 let a = u32::from(src[3]);
231 for (i, channel) in [2, 1, 0].into_iter().enumerate() {
232 dst[i] = ((u32::from(src[channel]) * 3 + 255) / 4 * a / 255) as u8;
233 }
234 dst[3] = src[3];
235 }
236 let state = Feedback {
237 registry: RegistryState::new(&globals),
238 output: OutputState::new(&globals, &qh),
239 shm,
240 presentation,
241 _pool: pool,
242 overlays: Vec::new(),
243 background,
244 image,
245 flash,
246 image_size: (info.width, info.height),
247 options,
248 start: Instant::now(),
249 flight: None,
250 destination: None,
251 ready: false,
252 landed: false,
253 landing_receipts: 0,
254 done: false,
255 };
256 (
257 connection,
258 queue,
259 state,
260 compositor,
261 layer_shell,
262 subcompositor,
263 viewporter,
264 )
265 };
266 let qh = queue.handle();
267 queue.roundtrip(&mut state)?;
268 queue.roundtrip(&mut state)?;
269 let region = compositor.wl_compositor().create_region(&qh, ());
270 for output in state.output.outputs() {
271 let data = state
272 .output
273 .info(&output)
274 .ok_or("Missing output information.")?;
275 let (x, y) = data.logical_position.ok_or("Missing output position.")?;
276 let (width, height) = data.logical_size.ok_or("Missing output size.")?;
277 if width <= 0 || height <= 0 {
278 continue;
279 }
280 let surface = compositor.create_surface(&qh);
281 surface.set_input_region(Some(®ion));
282 let background = viewporter.get_viewport(&surface, &qh, ());
283 background.set_destination(width, height);
284 let image = compositor.create_surface(&qh);
285 image.set_input_region(Some(®ion));
286 let viewport = viewporter.get_viewport(&image, &qh, ());
287 let subsurface = subcompositor.get_subsurface(&image, &surface, &qh, ());
288 let layer = layer_shell.create_layer_surface(
289 &qh,
290 surface,
291 Layer::Overlay,
292 Some("t3-snap-shot"),
293 Some(&output),
294 );
295 layer.set_anchor(Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT);
296 layer.set_keyboard_interactivity(KeyboardInteractivity::None);
297 layer.set_exclusive_zone(-1);
298 layer.set_size(width as u32, height as u32);
299 layer.commit();
300 state.overlays.push(Overlay {
301 layer,
302 image,
303 subsurface,
304 viewport,
305 _background: background,
306 bounds: Rect {
307 x: x.into(),
308 y: y.into(),
309 width: width.into(),
310 height: height.into(),
311 },
312 configured: false,
313 presented: !state.options.bounds.intersects(Rect {
314 x: x.into(),
315 y: y.into(),
316 width: width.into(),
317 height: height.into(),
318 }),
319 frame_pending: false,
320 receipt_pending: false,
321 texture: Texture::None,
322 background_attached: false,
323 });
324 }
325 region.destroy();
326 if state.overlays.is_empty() {
327 return Err("No output is available for capture effects.".into());
328 }
329 let stdin = std::io::stdin();
330 if state.overlays.iter().all(|o| o.presented) {
331 return Err("The captured window is outside the visible outputs.".into());
332 }
333 let deadline = Instant::now() + Duration::from_secs(8);
334 let mut commands = Vec::new();
335 while !state.done {
336 queue.dispatch_pending(&mut state)?;
337 if state.done {
338 break;
339 }
340 connection.flush()?;
341 let Some(read) = queue.prepare_read() else {
342 continue;
343 };
344 let timeout = rustix::event::Timespec::try_from(
345 deadline
346 .checked_duration_since(Instant::now())
347 .ok_or("Capture feedback expired.")?,
348 )?;
349 let mut fds = [
350 rustix::event::PollFd::new(&connection, rustix::event::PollFlags::IN),
351 rustix::event::PollFd::new(&stdin, rustix::event::PollFlags::IN),
352 rustix::event::PollFd::new(&events, rustix::event::PollFlags::IN),
353 ];
354 if rustix::event::poll(&mut fds, Some(&timeout))? == 0 {
355 break;
356 }
357 if fds[0].revents().intersects(
358 rustix::event::PollFlags::IN
359 | rustix::event::PollFlags::HUP
360 | rustix::event::PollFlags::ERR,
361 ) {
362 read.read()?;
363 } else {
364 drop(read);
365 }
366 let input_ready = fds[1]
367 .revents()
368 .intersects(rustix::event::PollFlags::IN | rustix::event::PollFlags::HUP);
369 let events_ready = !fds[2].revents().is_empty();
370 drop(fds);
371 if events_ready {
372 let mut bytes = [0; 8192];
373 match events.read(&mut bytes) {
374 Ok(0) => break,
375 Ok(_) => {
376 ipc::ensure_unlocked()?;
377 if let Some(destination) = &state.destination {
378 let current = ipc::destination(
379 ipc::windows()?,
380 state.options.pid,
381 &destination.title,
382 )?;
383 if current.as_ref() != Some(destination) {
384 break;
385 }
386 if ipc::active_window()?.address != destination.address {
387 break;
388 }
389 }
390 }
391 Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {}
392 Err(error) => return Err(error.into()),
393 }
394 }
395 if input_ready {
396 let mut buf = [0; 2048];
397 let count = stdin.lock().read(&mut buf)?;
398 if count == 0 {
399 break;
400 }
401 commands.extend_from_slice(&buf[..count]);
402 if commands.len() > 4096 {
403 return Err("Oversized feedback command.".into());
404 }
405 while let Some(end) = commands.iter().position(|byte| *byte == b'\n') {
406 let line = commands.drain(..=end).collect::<Vec<_>>();
407 match serde_json::from_slice::<Command>(&line)? {
408 Command::Close => state.done = true,
409 Command::Animate { title, frame }
410 if state.options.animate && state.flight.is_none() =>
411 {
412 ipc::ensure_unlocked()?;
413 let window = ipc::destination(ipc::windows()?, state.options.pid, &title)?
414 .ok_or("Capture destination is not visible.")?;
415 let dest = target(window.bounds(), frame)?;
416 state.flight = Some((dest, Instant::now()));
417 state.destination = Some(window);
418 state.draw(&qh)?;
419 }
420 _ => {}
421 }
422 }
423 }
424 }
425 // Dropping the connection destroys every overlay, including cancellation/error paths.
426 emit(serde_json::json!({"event":"done"}))
427}
428
429impl Feedback {
430 fn draw(&mut self, qh: &QueueHandle<Self>) -> Result<()> {
431 let progress = self
432 .flight
433 .map(|(_, start)| start.elapsed().as_secs_f64() / 0.48);
434 let rect = self
435 .flight
436 .map(|(to, _)| interpolate(self.options.bounds, to, progress.unwrap_or(0.)))
437 .unwrap_or(self.options.bounds);
438 let flashing = self.options.flash
439 && self.start.elapsed() < Duration::from_millis(130)
440 && self.flight.is_none();
441 let visible = self.options.animate || flashing;
442 let driver = self
443 .overlays
444 .iter()
445 .position(|o| o.bounds.intersects(rect))
446 .unwrap_or(0);
447 let landing = progress.is_some_and(|p| p >= 1.) && !self.landed;
448 for (index, overlay) in self.overlays.iter_mut().enumerate() {
449 if !overlay.configured {
450 continue;
451 }
452 let surface = overlay.layer.wl_surface();
453 if let Some((dest, src)) =
454 clip(rect, overlay.bounds, self.image_size).filter(|_| visible)
455 {
456 overlay
457 .viewport
458 .set_source(src.x, src.y, src.width, src.height);
459 overlay.viewport.set_destination(
460 dest.width.round().max(1.) as i32,
461 dest.height.round().max(1.) as i32,
462 );
463 overlay
464 .subsurface
465 .set_position(dest.x.round() as i32, dest.y.round() as i32);
466 // Hyprland 0.56.2 retains the old surface size on viewport-only commits.
467 // Reattach the immutable buffer so the destination size is applied too.
468 let texture = if flashing {
469 Texture::Flash
470 } else {
471 Texture::Image
472 };
473 overlay.image.attach(
474 Some((if flashing { &self.flash } else { &self.image }).wl_buffer()),
475 0,
476 0,
477 );
478 if overlay.texture != texture {
479 overlay.image.damage_buffer(0, 0, i32::MAX, i32::MAX);
480 }
481 overlay.texture = texture;
482 } else {
483 if overlay.texture != Texture::None {
484 overlay.image.attach(None, 0, 0);
485 overlay.texture = Texture::None;
486 }
487 }
488 overlay.image.commit();
489 if !overlay.background_attached {
490 surface.attach(Some(self.background.wl_buffer()), 0, 0);
491 overlay.background_attached = true;
492 }
493 if !overlay.presented && !overlay.receipt_pending {
494 self.presentation
495 .feedback(surface, qh, Receipt::Initial(index));
496 overlay.receipt_pending = true;
497 }
498 if landing && overlay.bounds.intersects(rect) {
499 self.presentation.feedback(surface, qh, Receipt::Landed);
500 self.landing_receipts += 1;
501 }
502 if !overlay.frame_pending
503 && self.ready
504 && index == driver
505 && (flashing || progress.is_some_and(|p| p < 1.))
506 {
507 surface.frame(qh, surface.clone());
508 overlay.frame_pending = true;
509 }
510 surface.commit();
511 }
512 if landing {
513 // Presentation feedback, not a frame callback, acknowledges the final painted image.
514 self.landed = true;
515 if self.landing_receipts == 0 {
516 self.done = true;
517 }
518 }
519 Ok(())
520 }
521}
522impl CompositorHandler for Feedback {
523 fn scale_factor_changed(
524 &mut self,
525 _: &Connection,
526 _: &QueueHandle<Self>,
527 _: &WlSurface,
528 _: i32,
529 ) {
530 }
531 fn transform_changed(
532 &mut self,
533 _: &Connection,
534 _: &QueueHandle<Self>,
535 _: &WlSurface,
536 _: wl_output::Transform,
537 ) {
538 }
539 fn surface_enter(
540 &mut self,
541 _: &Connection,
542 _: &QueueHandle<Self>,
543 _: &WlSurface,
544 _: &WlOutput,
545 ) {
546 }
547 fn surface_leave(
548 &mut self,
549 _: &Connection,
550 _: &QueueHandle<Self>,
551 _: &WlSurface,
552 _: &WlOutput,
553 ) {
554 }
555 fn frame(&mut self, _: &Connection, qh: &QueueHandle<Self>, surface: &WlSurface, _: u32) {
556 if self.landed {
557 return;
558 }
559 if let Some(overlay) = self
560 .overlays
561 .iter_mut()
562 .find(|o| o.layer.wl_surface() == surface)
563 {
564 overlay.frame_pending = false;
565 }
566 if self.ready && self.draw(qh).is_err() {
567 self.done = true;
568 }
569 }
570}
571impl LayerShellHandler for Feedback {
572 fn closed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: &LayerSurface) {
573 self.done = true;
574 }
575 fn configure(
576 &mut self,
577 _: &Connection,
578 qh: &QueueHandle<Self>,
579 layer: &LayerSurface,
580 config: LayerSurfaceConfigure,
581 _: u32,
582 ) {
583 if let Some(overlay) = self.overlays.iter_mut().find(|o| o.layer == *layer) {
584 if overlay.configured
585 || (config.new_size.0 != 0
586 && config.new_size
587 != (overlay.bounds.width as u32, overlay.bounds.height as u32))
588 {
589 self.done = true;
590 return;
591 }
592 overlay.configured = true;
593 }
594 if self.overlays.iter().all(|o| o.configured) && self.draw(qh).is_err() {
595 self.done = true;
596 }
597 }
598}
599impl OutputHandler for Feedback {
600 fn output_state(&mut self) -> &mut OutputState {
601 &mut self.output
602 }
603 fn new_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {
604 if !self.overlays.is_empty() {
605 self.done = true;
606 }
607 }
608 fn update_output(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {
609 if self.ready {
610 self.done = true;
611 }
612 }
613 fn output_destroyed(&mut self, _: &Connection, _: &QueueHandle<Self>, _: WlOutput) {
614 self.done = true;
615 }
616}
617impl ShmHandler for Feedback {
618 fn shm_state(&mut self) -> &mut Shm {
619 &mut self.shm
620 }
621}
622impl ProvidesRegistryState for Feedback {
623 fn registry(&mut self) -> &mut RegistryState {
624 &mut self.registry
625 }
626 smithay_client_toolkit::registry_handlers!(OutputState);
627}
628delegate_registry!(Feedback);
629delegate_output!(Feedback);
630delegate_shm!(Feedback);
631delegate_layer!(Feedback);
632delegate_compositor!(Feedback);
633delegate_noop!(Feedback: ignore WlSubcompositor);
634delegate_noop!(Feedback: ignore WlSubsurface);
635delegate_noop!(Feedback: ignore WpViewporter);
636delegate_noop!(Feedback: ignore WpViewport);
637delegate_noop!(Feedback: ignore WpPresentation);
638impl Dispatch<WpPresentationFeedback, Receipt> for Feedback {
639 fn event(
640 state: &mut Self,
641 _: &WpPresentationFeedback,
642 event: wp_presentation_feedback::Event,
643 receipt: &Receipt,
644 _: &Connection,
645 qh: &QueueHandle<Self>,
646 ) {
647 match event {
648 wp_presentation_feedback::Event::Presented { .. } => {
649 match receipt {
650 Receipt::Initial(index) => {
651 state.overlays[*index].presented = true;
652 if !state.ready && state.overlays.iter().all(|o| o.presented) {
653 state.ready = true;
654 if emit(serde_json::json!({"event":"ready","animate":state.options.animate})).is_err() || state.draw(qh).is_err() {state.done=true;}
655 }
656 }
657 Receipt::Landed => {
658 state.landing_receipts = state.landing_receipts.saturating_sub(1);
659 if state.landing_receipts == 0
660 && emit(serde_json::json!({"event":"landed"})).is_err()
661 {
662 state.done = true;
663 }
664 }
665 }
666 }
667 wp_presentation_feedback::Event::Discarded => state.done = true,
668 _ => {}
669 }
670 }
671}
672delegate_noop!(Feedback: ignore wayland_client::protocol::wl_region::WlRegion);
673
674#[cfg(test)]
675mod tests {
676 use super::*;
677 #[test]
678 fn clips_across_negative_origin_and_scaled_outputs() {
679 let rect = Rect {
680 x: -100.,
681 y: 0.,
682 width: 200.,
683 height: 100.,
684 };
685 let (dest, src) = clip(
686 rect,
687 Rect {
688 x: 0.,
689 y: 0.,
690 width: 1920.,
691 height: 1080.,
692 },
693 (400, 200),
694 )
695 .unwrap();
696 assert_eq!(
697 dest,
698 Rect {
699 x: 0.,
700 y: 0.,
701 width: 100.,
702 height: 100.
703 }
704 );
705 assert_eq!(
706 src,
707 Rect {
708 x: 200.,
709 y: 0.,
710 width: 200.,
711 height: 200.
712 }
713 );
714 assert!(
715 clip(
716 rect,
717 Rect {
718 x: 200.,
719 y: 0.,
720 width: 100.,
721 height: 100.
722 },
723 (400, 200)
724 )
725 .is_none()
726 );
727 }
728 #[test]
729 fn flight_ends_at_exact_composer_bounds() {
730 let a = Rect {
731 x: -300.,
732 y: 0.,
733 width: 600.,
734 height: 500.,
735 };
736 let b = Rect {
737 x: 100.,
738 y: 400.,
739 width: 100.,
740 height: 80.,
741 };
742 assert_eq!(interpolate(a, b, 0.), a);
743 assert_eq!(interpolate(a, b, 1.), b);
744 assert_eq!(
745 target(
746 a,
747 Rect {
748 x: 0.5,
749 y: 0.4,
750 width: 0.2,
751 height: 0.1
752 }
753 )
754 .unwrap(),
755 Rect {
756 x: 0.,
757 y: 200.,
758 width: 120.,
759 height: 50.
760 }
761 );
762 assert!(
763 target(
764 a,
765 Rect {
766 x: 590.,
767 y: 0.,
768 width: 30.,
769 height: 30.
770 }
771 )
772 .is_err()
773 );
774 }
775}