1use std::borrow::Cow;
22use std::fs::File;
23use std::time::{Duration, Instant};
24
25use gbm::{BufferObject, Device as RawGbmDevice};
26use std::sync::Mutex;
27use std::collections::hash_map::DefaultHasher;
28use std::hash::{Hash, Hasher};
29use smithay::backend::renderer::utils::RendererSurfaceState;
30use smithay::backend::input::InputTime;
31use smithay::backend::allocator::dmabuf::Dmabuf;
32use smithay::backend::allocator::{ Buffer, Fourcc};
33use smithay::backend::renderer::damage::OutputDamageTracker;
34use smithay::backend::renderer::{
35 Bind, ExportMem, gles::GlesRenderer, pixman::PixmanRenderer, ImportDma,
36};
37use smithay::backend::allocator::Modifier;
38use smithay::backend::drm::DrmNode;
39use smithay::output::WeakOutput;
40use smithay::wayland::image_capture_source::{
41 ImageCaptureSource, ImageCaptureSourceHandler, ImageCaptureSourceState,
42 OutputCaptureSourceHandler, OutputCaptureSourceState,
43};
44use smithay::wayland::image_copy_capture::{
45 BufferConstraints, CaptureFailureReason, DmabufConstraints, Frame as CopyFrame,
46 FrameRef as CopyFrameRef, ImageCopyCaptureHandler, ImageCopyCaptureState,
47 Session as CopySession, SessionRef as CopySessionRef,
48};
49use smithay::input::dnd::{DnDGrab, DndFocus, GrabType, Source};
50use std::sync::Arc;
51use crate::wayland::cursor::{Cursor, CursorJob};
52use crate::wayland::keymap::KeymapPolicy;
53use smithay::reexports::wayland_protocols_misc::zwp_virtual_keyboard_v1::server::{
54 zwp_virtual_keyboard_manager_v1::{self, ZwpVirtualKeyboardManagerV1},
55 zwp_virtual_keyboard_v1::{self, ZwpVirtualKeyboardV1},
56};
57use smithay::reexports::wayland_server::{DataInit, Dispatch, GlobalDispatch, New};
58use smithay::wayland::viewporter::ViewporterState;
59use smithay::wayland::pointer_warp::{PointerWarpHandler, PointerWarpManager};
60use smithay::reexports::wayland_server::protocol::wl_pointer::WlPointer;
61use smithay::reexports::wayland_server::protocol::wl_shm;
62use smithay::wayland::relative_pointer::RelativePointerManagerState;
63use smithay::wayland::pointer_constraints::{
64 with_pointer_constraint, PointerConstraint, PointerConstraintsHandler, PointerConstraintsState,
65};
66use smithay::input::pointer::PointerHandle;
67use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
68use smithay::desktop::utils::under_from_surface_tree;
69use smithay::desktop::{PopupKind, PopupManager, WindowSurfaceType};
70use smithay::wayland::presentation::PresentationState;
71use smithay::wayland::foreign_toplevel_list::{
72 ForeignToplevelHandle, ForeignToplevelListHandler, ForeignToplevelListState,
73};
74use smithay::wayland::shell::xdg::decoration::{
75 XdgDecorationHandler, XdgDecorationState,
76};
77use smithay::desktop::{layer_map_for_output, LayerSurface as DesktopLayerSurface};
78use smithay::wayland::shell::wlr_layer::{
79 WlrLayerShellHandler, WlrLayerShellState, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
80};
81use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
82use smithay::wayland::selection::wlr_data_control::{DataControlHandler, DataControlState};
83use smithay::wayland::selection::ext_data_control::{
84 DataControlHandler as ExtDataControlHandler, DataControlState as ExtDataControlState,
85};
86use smithay::wayland::cursor_shape::CursorShapeManagerState;
87use smithay::wayland::xdg_activation::{
88 XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
89};
90use smithay::wayland::selection::primary_selection::{
91 set_primary_focus, PrimarySelectionHandler, PrimarySelectionState,
92};
93use smithay::wayland::keyboard_shortcuts_inhibit::{
94 KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, KeyboardShortcutsInhibitor,
95};
96
97use smithay::{
98 delegate_dispatch2,
99 desktop::{Space, Window},
100 input::{
101 Seat, SeatHandler, SeatState,
102 keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
103 pointer::{
104 AxisFrame, ButtonEvent, CursorIcon, CursorImageAttributes, CursorImageStatus, Focus,
105 GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
106 GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
107 GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, MotionEvent,
108 PointerTarget, RelativeMotionEvent,
109 },
110 touch::{DownEvent, FrameMarker, OrientationEvent, ShapeEvent, TouchTarget, UpEvent},
111 },
112 output::Output,
113 reexports::{
114 wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
115 wayland_server::{
116 Client, DisplayHandle, Resource,
117 backend::{ClientData, ClientId, DisconnectReason, GlobalId, ObjectId},
118 protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface},
119 },
120 },
121 utils::{Clock, IsAlive, Logical, Monotonic, Point, Rectangle, Serial},
122 wayland::{
123 buffer::BufferHandler,
124 compositor::{
125 BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
126 SurfaceAttributes, with_states,
127 },
128 dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf},
129 fractional_scale::{FractionalScaleHandler, FractionalScaleManagerState},
130 output::{OutputHandler, OutputManagerState},
131 seat::WaylandFocus,
132 selection::{
133 SelectionHandler, SelectionSource, SelectionTarget,
134 data_device::{
135 DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler,
136 request_data_device_client_selection, set_data_device_focus,
137 },
138 },
139 shell::xdg::{
140 PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
141 XdgToplevelSurfaceData,
142 },
143 shm::{BufferAccessError, ShmHandler, ShmState, with_buffer_contents},
144 },
145};
146
147use crate::CaptureSettings;
148use crate::encoders::FrameEncoder;
149use crate::encoders::overlay::OverlayState;
150
151use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering, fence};
152
153static SERIAL_COUNTER: AtomicU32 = AtomicU32::new(1);
154
155pub fn next_serial() -> Serial {
165 Serial::from(SERIAL_COUNTER.fetch_add(1, Ordering::SeqCst))
166}
167
168pub fn wayland_time() -> InputTime {
173 let mut ts = libc::timespec {
174 tv_sec: 0,
175 tv_nsec: 0,
176 };
177 unsafe {
178 libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
179 }
180 InputTime::from_micros(
181 (ts.tv_sec as u64)
182 .wrapping_mul(1_000_000)
183 .wrapping_add((ts.tv_nsec as u64) / 1_000),
184 )
185}
186
187use crate::pace::FramePace;
188
189pub struct WlCapture {
194 pub settings: CaptureSettings,
195 pub callback: Option<Arc<dyn crate::sink::FrameSink>>,
199 pub zero_copy: Option<Arc<Mutex<Option<FrameEncoder>>>>,
203 pub gpu_pool: Option<Arc<crate::WlFramePool<Dmabuf>>>,
206 pub gpu_ring: Vec<(BufferObject<()>, Dmabuf)>,
207 pub host_still: Option<(BufferObject<()>, Dmabuf)>,
209 pub host_inflight: Vec<(u64, crate::wayland::host::HostFrame)>,
213 pub scanout_inflight: Vec<(u64, smithay::backend::renderer::utils::Buffer)>,
217 pub direct: Option<Result<(), &'static str>>,
220 pub published: u64,
221 pub host_retained_use: u64,
223 pub recording_sink: Option<Arc<crate::recording_sink::RecordingSink>>,
224 pub deliver_tx:
225 Option<std::sync::mpsc::SyncSender<Vec<crate::encoders::software::EncodedStripe>>>,
226 pub deliver_join: Option<std::thread::JoinHandle<()>>,
227 pub deliver_discard: Option<Arc<AtomicBool>>,
230 pub encode_pool: Option<Arc<crate::WlFramePool>>,
231 pub encode_join: Option<std::thread::JoinHandle<Option<FrameEncoder>>>,
232 pub encode_controls: Arc<crate::WlEncodeControls>,
233 pub encode_stats: Arc<crate::WlEncodeStats>,
234 pub report: Arc<crate::report::StreamReport>,
236 pub pool_last_render: Vec<u64>,
237 pub render_seq: u64,
238 pub pool_content_gen: Vec<u64>,
239 pub content_gen: u64,
240 pub frame_counter: u16,
241 pub pending_force_idr: bool,
242 pub needs_full_render: bool,
243 pub pace: FramePace,
246 pub change_pending: bool,
249 pub callbacks_at: Option<Instant>,
251 pub callbacks_owed: bool,
253}
254
255impl WlCapture {
256 pub fn request_idr(&mut self) {
262 if self.pooled() {
263 self.encode_controls
264 .force_idr
265 .store(true, Ordering::Relaxed);
266 } else {
267 self.pending_force_idr = true;
268 }
269 self.needs_full_render = true;
270 }
271
272 pub fn invalidate_reference(&mut self, frame_id: u16, withheld: bool) {
276 use crate::encoders::reference::ReferenceReport;
277 self.take_report(if withheld {
278 ReferenceReport::Withheld(frame_id)
279 } else {
280 ReferenceReport::Lost(frame_id)
281 });
282 }
283
284 pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
287 use crate::encoders::reference::ReferenceReport;
288 self.take_report(if held {
289 ReferenceReport::Held(frame_id)
290 } else {
291 ReferenceReport::Sent(frame_id)
292 });
293 }
294
295 fn take_report(&mut self, report: crate::encoders::reference::ReferenceReport) {
296 if self.pooled() {
297 self.encode_controls
298 .reference_reports
299 .lock()
300 .unwrap()
301 .push(report);
302 }
303 }
304
305 pub fn pooled(&self) -> bool {
308 self.encode_pool.is_some() || self.gpu_pool.is_some()
309 }
310}
311
312pub struct OutputNode {
317 pub id: u32,
318 pub output: Output,
319 pub global: Option<GlobalId>,
325 pub owner: Option<u32>,
327 pub view_size: (i32, i32),
330 pub view_scale: f64,
332 pub pos: (i32, i32),
337 pub damage_tracker: OutputDamageTracker,
338 pub content_tracker: Option<OutputDamageTracker>,
345 pub frame_buffer: Vec<u8>,
348 pub offscreen_buffer: Option<(BufferObject<()>, Dmabuf)>,
350 pub undrawn_ticks: u32,
352 pub overlay_state: OverlayState,
355 pub capture: Option<WlCapture>,
356 pub frame_seq: u64,
358 pub target_seeded: bool,
362 pub content_hold_until: Option<Instant>,
368}
369
370pub struct PendingHostLayout {
375 pub epoch: u64,
376 pub want: (i32, i32),
377 pub geometry_waiters: Vec<std::sync::mpsc::Sender<(i32, i32, f64)>>,
378}
379
380pub struct OutputCopySession {
385 pub session: CopySession,
386 pub output: WeakOutput,
387 pub pending: Option<CopyFrame>,
388 pub delivered_once: bool,
391}
392
393pub fn windows_on_output(space: &Space<Window>, display_id: u32) -> impl Iterator<Item = &Window> {
397 space.elements().filter(move |w| {
398 window_output_id(w) == display_id
399 && !window_meta(w)
400 .map(|m| m.parked.load(Ordering::Relaxed))
401 .unwrap_or(false)
402 })
403}
404
405pub fn output_content_covers(
409 space: &Space<Window>,
410 display_id: u32,
411 logical_w: f64,
412 logical_h: f64,
413) -> bool {
414 windows_on_output(space, display_id).any(|w| {
415 let size = w.geometry().size;
416 f64::from(size.w) >= logical_w && f64::from(size.h) >= logical_h
417 })
418}
419
420impl OutputNode {
421 pub fn logical_geometry(&self) -> Option<Rectangle<i32, Logical>> {
425 let scale = self.output.current_scale().fractional_scale();
426 let (w, h) = if self.owner.is_some() {
427 self.capture
428 .as_ref()
429 .map(|c| (c.settings.width, c.settings.height))
430 .unwrap_or(self.view_size)
431 } else {
432 let mode = self.output.current_mode()?;
433 (mode.size.w, mode.size.h)
434 };
435 Some(Rectangle::new(
436 Point::from(self.pos),
437 (
438 (f64::from(w) / scale).round() as i32,
439 (f64::from(h) / scale).round() as i32,
440 )
441 .into(),
442 ))
443 }
444}
445
446pub(crate) static NEXT_WINDOW_ID: AtomicU32 = AtomicU32::new(1);
447
448pub struct WindowMeta {
452 pub id: u32,
453 pub output: AtomicU32,
454 pub placed: AtomicBool,
459 pub parked: AtomicBool,
462}
463
464pub const PARKED_POS: (i32, i32) = (-(1 << 20), -(1 << 20));
468
469pub const PARKED_LOGICAL_SIZE: (i32, i32) = (320, 240);
476
477pub fn is_override_redirect(window: &Window) -> bool {
479 window
480 .x11_surface()
481 .is_some_and(|s| s.is_override_redirect())
482}
483
484pub fn window_meta(window: &Window) -> Option<&WindowMeta> {
487 window.user_data().get::<WindowMeta>()
488}
489
490pub fn window_output_id(window: &Window) -> u32 {
492 window_meta(window)
493 .map(|m| m.output.load(Ordering::Relaxed))
494 .unwrap_or(0)
495}
496
497pub(crate) struct ScreenshotRequest {
499 pub display_id: u32,
500 pub resp: std::sync::mpsc::Sender<Result<crate::computer_use::ScreenshotFrame, String>>,
501 pub canceled: Arc<AtomicBool>,
502 pub deadline: Instant,
503}
504
505impl ScreenshotRequest {
506 fn inactive_reason(&self, now: Instant) -> Option<&'static str> {
507 if self.canceled.load(Ordering::Acquire) {
508 Some("Screenshot canceled")
509 } else if now >= self.deadline {
510 Some("Screenshot timed out")
511 } else {
512 None
513 }
514 }
515}
516
517#[derive(Default)]
519pub(crate) struct ScreenshotQueue {
520 requests: Vec<ScreenshotRequest>,
521}
522
523impl ScreenshotQueue {
524 pub fn enqueue(&mut self, request: ScreenshotRequest, now: Instant) {
525 self.prune(now);
526 let error = request.inactive_reason(now).or_else(|| {
527 if self.requests.len() >= crate::computer_use::MAX_SCREENSHOT_REQUESTS
528 || self.contains(request.display_id)
529 {
530 Some("Screenshot busy: a readback is already pending or the queue is full")
531 } else {
532 None
533 }
534 });
535 if let Some(error) = error {
536 let _ = request.resp.send(Err(error.to_string()));
537 } else {
538 self.requests.push(request);
539 }
540 }
541
542 pub fn prune(&mut self, now: Instant) {
543 self.requests.retain(|request| {
544 if let Some(error) = request.inactive_reason(now) {
545 let _ = request.resp.send(Err(error.to_string()));
546 false
547 } else {
548 true
549 }
550 });
551 }
552
553 pub fn contains(&self, display_id: u32) -> bool {
554 self.requests.iter().any(|r| r.display_id == display_id)
555 }
556
557 pub fn is_empty(&self) -> bool {
558 self.requests.is_empty()
559 }
560
561 pub fn take(&mut self, display_id: u32, now: Instant) -> Option<ScreenshotRequest> {
562 self.prune(now);
563 let index = self
564 .requests
565 .iter()
566 .position(|r| r.display_id == display_id)?;
567 Some(self.requests.remove(index))
568 }
569
570 pub fn fail_display(&mut self, display_id: u32, reason: &str) {
571 if let Some(index) = self
572 .requests
573 .iter()
574 .position(|r| r.display_id == display_id)
575 {
576 let request = self.requests.remove(index);
577 let _ = request.resp.send(Err(reason.to_string()));
578 }
579 }
580
581 pub fn fail_all(&mut self, reason: &str) {
582 for request in self.requests.drain(..) {
583 let _ = request.resp.send(Err(reason.to_string()));
584 }
585 }
586}
587
588#[cfg(test)]
589mod screenshot_queue_tests {
590 use super::{ScreenshotQueue, ScreenshotRequest};
591 use std::sync::{
592 Arc,
593 atomic::{AtomicBool, Ordering},
594 mpsc,
595 };
596 use std::time::{Duration, Instant};
597
598 fn request(
599 display_id: u32,
600 deadline: Instant,
601 ) -> (
602 ScreenshotRequest,
603 mpsc::Receiver<Result<crate::computer_use::ScreenshotFrame, String>>,
604 ) {
605 let (resp, rx) = mpsc::channel();
606 (
607 ScreenshotRequest {
608 display_id,
609 resp,
610 canceled: Arc::new(AtomicBool::new(false)),
611 deadline,
612 },
613 rx,
614 )
615 }
616
617 #[test]
618 fn screenshot_duplicate_does_not_replace_the_waiter() {
619 let now = Instant::now();
620 let mut queue = ScreenshotQueue::default();
621 let (first, first_rx) = request(7, now + Duration::from_secs(1));
622 let (second, second_rx) = request(7, now + Duration::from_secs(1));
623 queue.enqueue(first, now);
624 queue.enqueue(second, now);
625 assert!(
626 second_rx
627 .recv_timeout(Duration::from_secs(1))
628 .unwrap()
629 .err()
630 .unwrap()
631 .contains("busy")
632 );
633 assert!(matches!(
634 first_rx.try_recv(),
635 Err(mpsc::TryRecvError::Empty)
636 ));
637 assert!(
638 queue
639 .take(7, now)
640 .unwrap()
641 .resp
642 .send(Err("first reply".to_string()))
643 .is_ok()
644 );
645 assert_eq!(
646 first_rx
647 .recv_timeout(Duration::from_secs(1))
648 .unwrap()
649 .err()
650 .unwrap(),
651 "first reply"
652 );
653 }
654
655 #[test]
656 fn screenshot_outputs_have_independent_bounded_slots() {
657 let now = Instant::now();
658 let mut queue = ScreenshotQueue::default();
659 let mut receivers = Vec::new();
660 for display in 0..crate::computer_use::MAX_SCREENSHOT_REQUESTS as u32 {
661 let (request, rx) = request(display, now + Duration::from_secs(1));
662 queue.enqueue(request, now);
663 receivers.push(rx);
664 }
665 let (overflow, rx) = request(99, now + Duration::from_secs(1));
666 queue.enqueue(overflow, now);
667 assert!(
668 rx.recv_timeout(Duration::from_secs(1))
669 .unwrap()
670 .err()
671 .unwrap()
672 .contains("busy")
673 );
674 assert_eq!(queue.take(2, now).unwrap().display_id, 2);
675 assert!(queue.contains(0));
676 assert!(queue.contains(1));
677 assert!(queue.contains(3));
678 let (replacement, _) = request(99, now + Duration::from_secs(1));
679 queue.enqueue(replacement, now);
680 assert!(queue.contains(99));
681 }
682
683 #[test]
684 fn screenshot_canceled_and_expired_requests_release_slots() {
685 let now = Instant::now();
686 let mut queue = ScreenshotQueue::default();
687 let (canceled, canceled_rx) = request(0, now + Duration::from_secs(2));
688 let flag = canceled.canceled.clone();
689 let (expired, expired_rx) = request(1, now + Duration::from_secs(1));
690 queue.enqueue(canceled, now);
691 queue.enqueue(expired, now);
692 flag.store(true, Ordering::Release);
693 queue.prune(now + Duration::from_secs(1));
694 assert!(queue.is_empty());
695 assert_eq!(
696 canceled_rx
697 .recv_timeout(Duration::from_secs(1))
698 .unwrap()
699 .err()
700 .unwrap(),
701 "Screenshot canceled"
702 );
703 assert_eq!(
704 expired_rx
705 .recv_timeout(Duration::from_secs(1))
706 .unwrap()
707 .err()
708 .unwrap(),
709 "Screenshot timed out"
710 );
711 let (fresh, _) = request(0, now + Duration::from_secs(3));
712 queue.enqueue(fresh, now + Duration::from_secs(1));
713 assert!(queue.contains(0));
714 }
715
716 #[test]
717 fn screenshot_inactive_requests_never_enter_the_queue() {
718 let now = Instant::now();
719 let mut queue = ScreenshotQueue::default();
720 let (expired, rx) = request(0, now);
721 queue.enqueue(expired, now);
722 assert_eq!(
723 rx.recv_timeout(Duration::from_secs(1))
724 .unwrap()
725 .err()
726 .unwrap(),
727 "Screenshot timed out"
728 );
729 let (canceled, rx) = request(0, now + Duration::from_secs(1));
730 canceled.canceled.store(true, Ordering::Release);
731 queue.enqueue(canceled, now);
732 assert_eq!(
733 rx.recv_timeout(Duration::from_secs(1))
734 .unwrap()
735 .err()
736 .unwrap(),
737 "Screenshot canceled"
738 );
739 assert!(queue.is_empty());
740 }
741
742 #[test]
743 fn screenshot_timeout_is_checked_again_before_handoff() {
744 let now = Instant::now();
745 let mut queue = ScreenshotQueue::default();
746 let (pending, rx) = request(4, now + Duration::from_secs(1));
747 queue.enqueue(pending, now);
748 assert!(queue.take(4, now + Duration::from_secs(1)).is_none());
749 assert_eq!(
750 rx.recv_timeout(Duration::from_secs(1))
751 .unwrap()
752 .err()
753 .unwrap(),
754 "Screenshot timed out"
755 );
756 assert!(queue.is_empty());
757 }
758
759 #[test]
760 fn screenshot_output_removal_does_not_cancel_other_outputs() {
761 let now = Instant::now();
762 let mut queue = ScreenshotQueue::default();
763 let (removed, removed_rx) = request(2, now + Duration::from_secs(1));
764 let (other, other_rx) = request(3, now + Duration::from_secs(1));
765 queue.enqueue(removed, now);
766 queue.enqueue(other, now);
767 queue.fail_display(2, "removed");
768 assert_eq!(
769 removed_rx
770 .recv_timeout(Duration::from_secs(1))
771 .unwrap()
772 .err()
773 .unwrap(),
774 "removed"
775 );
776 assert!(queue.contains(3));
777 assert!(matches!(
778 other_rx.try_recv(),
779 Err(mpsc::TryRecvError::Empty)
780 ));
781 let (reused, _) = request(2, now + Duration::from_secs(1));
782 queue.enqueue(reused, now);
783 assert!(queue.contains(2));
784 queue.fail_all("disconnected");
785 assert!(queue.is_empty());
786 assert_eq!(
787 other_rx
788 .recv_timeout(Duration::from_secs(1))
789 .unwrap()
790 .err()
791 .unwrap(),
792 "disconnected"
793 );
794 }
795}
796
797#[derive(Clone, Copy, Debug, PartialEq)]
799pub struct PointerAt {
800 pub x: f64,
802 pub y: f64,
803 pub scale: f64,
806 pub motion: u64,
808}
809
810#[derive(Default)]
815pub struct PointerShare {
816 version: AtomicU64,
818 position: AtomicU64,
820 scale: AtomicU64,
821 motion: AtomicU64,
822}
823
824impl PointerShare {
825 pub fn publish(&self, at: PointerAt) {
827 let position =
828 u64::from((at.x as f32).to_bits()) | u64::from((at.y as f32).to_bits()) << 32;
829 let scale = at.scale.to_bits();
830 let version = self.version.load(Ordering::Relaxed);
831 if version != 0
832 && self.position.load(Ordering::Relaxed) == position
833 && self.scale.load(Ordering::Relaxed) == scale
834 && self.motion.load(Ordering::Relaxed) == at.motion
835 {
836 return;
837 }
838 self.version.store(version + 1, Ordering::Relaxed);
839 fence(Ordering::Release);
840 self.position.store(position, Ordering::Relaxed);
841 self.scale.store(scale, Ordering::Relaxed);
842 self.motion.store(at.motion, Ordering::Relaxed);
843 self.version.store(version + 2, Ordering::Release);
844 }
845
846 pub fn read(&self) -> Option<PointerAt> {
847 loop {
848 let version = self.version.load(Ordering::Acquire);
849 if version == 0 {
850 return None;
851 }
852 if version & 1 == 0 {
853 let position = self.position.load(Ordering::Relaxed);
854 let scale = self.scale.load(Ordering::Relaxed);
855 let motion = self.motion.load(Ordering::Relaxed);
856 fence(Ordering::Acquire);
857 if self.version.load(Ordering::Relaxed) == version {
858 return Some(PointerAt {
859 x: f64::from(f32::from_bits(position as u32)),
860 y: f64::from(f32::from_bits((position >> 32) as u32)),
861 scale: f64::from_bits(scale),
862 motion,
863 });
864 }
865 }
866 std::hint::spin_loop();
867 }
868 }
869}
870
871pub struct AppState {
917 pub compositor_state: CompositorState,
918 pub fractional_scale_state: FractionalScaleManagerState,
919 pub viewporter_state: ViewporterState,
920 pub presentation_state: PresentationState,
921 pub shm_state: ShmState,
922 pub single_pixel_buffer: SinglePixelBufferState,
923 pub dmabuf_state: DmabufState,
924 pub dmabuf_global: Option<DmabufGlobal>,
925 pub ext_data_control_state: ExtDataControlState,
926 pub cursor_shape_state: CursorShapeManagerState,
927 pub image_capture_source_state: ImageCaptureSourceState,
928 pub output_capture_source_state: OutputCaptureSourceState,
929 pub image_copy_capture_state: ImageCopyCaptureState,
930 pub copy_sessions: Vec<OutputCopySession>,
931 pub output_state: OutputManagerState,
932 pub seat_state: SeatState<AppState>,
933 pub shell_state: XdgShellState,
934 pub layer_shell_state: WlrLayerShellState,
935 pub space: Space<Window>,
936 pub data_device_state: DataDeviceState,
937 pub data_control_state: DataControlState,
938 pub dh: DisplayHandle,
939 pub seat: Seat<AppState>,
940 pub output_nodes: Vec<OutputNode>,
943 pub pending_windows: Vec<Window>,
944
945 pub foreign_toplevel_list: ForeignToplevelListState,
946 pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState,
950 pub xdg_decoration_state: XdgDecorationState,
951 pub xdg_activation_state: XdgActivationState,
952 pub primary_selection_state: PrimarySelectionState,
953 pub popups: PopupManager,
954
955 pub(crate) screenshot_readbacks: crate::ScreenshotReadbacks,
958 pub gles_renderer: Option<GlesRenderer>,
959 pub pixman_renderer: Option<PixmanRenderer>,
960
961 pub gbm_device: Option<RawGbmDevice<File>>,
962
963 pub settings: CaptureSettings,
966 pub cursor_callback_set: bool,
969 pub cursor_tx: std::sync::mpsc::Sender<CursorJob>,
971 pub clipboard_callback: Option<Arc<dyn crate::sink::ClipboardSink>>,
972 pub pending_clipboard_read: Option<Vec<String>>,
975 pub clipboard_generation: Arc<AtomicU64>,
978 pub current_selection_mimes: Vec<String>,
982
983 pub last_log_time: Instant,
984 pub start_time: Instant,
985 pub clock: Clock<Monotonic>,
986
987 pub use_gpu: bool,
988
989 pub cursor_helper: Cursor,
990
991 pub keymap_policy: KeymapPolicy,
994 pub host: Option<crate::wayland::host::HostSession>,
997 pub host_layout_pending: std::collections::HashMap<u32, PendingHostLayout>,
1002 pub host_frame_tx: smithay::reexports::calloop::channel::Sender<usize>,
1006 pub host_mode_refusals: std::collections::HashMap<u32, u32>,
1009
1010 pub current_cursor_icon: Option<CursorImageStatus>,
1011 pub cursor_surface_pending: bool,
1016 pub cursor_buffer: Option<WlBuffer>,
1017 pub dnd_icon: Option<(WlSurface, Point<i32, Logical>)>,
1021 pub render_cursor_on_framebuffer: bool,
1022 pub pointer_warp_state: PointerWarpManager,
1023 pub relative_pointer_state: RelativePointerManagerState,
1024 pub pointer_constraints_state: PointerConstraintsState,
1025 pub render_node_path: String,
1026 pub auto_gpu_selected: bool,
1027 pub(crate) pending_screenshots: ScreenshotQueue,
1029 pub command_rx: Option<smithay::reexports::calloop::channel::Channel<crate::ThreadCommand>>,
1033 pub last_input_at: Option<Instant>,
1037 pub pointer_motion_at: Option<Instant>,
1042 pub input_interval: Option<Duration>,
1043 pub pointer_share: Arc<PointerShare>,
1045 pub motion_applied: u64,
1047 pub frame_idle_long: bool,
1052 pub last_idle_service_at: Option<Instant>,
1055 pub deliver_reaper: Vec<std::thread::JoinHandle<()>>,
1059 pub encode_reaper: Vec<std::thread::JoinHandle<Option<crate::FrameEncoder>>>,
1063 pub wm: Option<crate::wayland::wm::WmState>,
1066 pub foreign_toplevel: crate::wayland::foreign_toplevel::ForeignToplevelState,
1068 pub workspace: crate::wayland::workspace::WorkspaceState,
1070 pub xwm: crate::wayland::xwm::XwmState,
1072 pub loop_handle: smithay::reexports::calloop::LoopHandle<'static, AppState>,
1074 pub keyboard_focus: Option<u32>,
1078}
1079
1080impl PointerConstraintsHandler for AppState {
1085 fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
1086 let Some(focus) = pointer.current_focus() else {
1087 return;
1088 };
1089 if focus.wl_surface().as_deref() != Some(surface) {
1090 return;
1091 }
1092 with_pointer_constraint(surface, pointer, |constraint| {
1093 if let Some(constraint) = constraint {
1094 constraint.activate();
1095 }
1096 });
1097 }
1098}
1099
1100impl ForeignToplevelListHandler for AppState {
1103 fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {
1104 &mut self.foreign_toplevel_list
1105 }
1106}
1107
1108impl KeyboardShortcutsInhibitHandler for AppState {
1115 fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState {
1116 &mut self.keyboard_shortcuts_inhibit_state
1117 }
1118
1119 fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) {
1122 let focused = crate::wayland::wm::focused_surface(self);
1123 if focused.as_ref() == Some(inhibitor.wl_surface()) {
1124 inhibitor.activate();
1125 }
1126 }
1127}
1128
1129impl XdgActivationHandler for AppState {
1130 fn activation_state(&mut self) -> &mut XdgActivationState {
1131 &mut self.xdg_activation_state
1132 }
1133
1134 fn token_created(&mut self, _token: XdgActivationToken, _data: XdgActivationTokenData) -> bool {
1135 true
1136 }
1137
1138 fn request_activation(
1139 &mut self,
1140 _token: XdgActivationToken,
1141 token_data: XdgActivationTokenData,
1142 surface: WlSurface,
1143 ) {
1144 if token_data.timestamp.elapsed().as_secs() < 10 {
1145 let window = self
1146 .space
1147 .elements()
1148 .find(|w| w.wl_surface().as_deref() == Some(&surface))
1149 .cloned();
1150 if let Some(window) = window {
1151 let id = window_meta(&window).map(|m| m.id).unwrap_or(0);
1152 if !crate::wayland::wm::request(self, id, crate::wm::Request::Activate) {
1153 self.space.raise_element(&window, true);
1154 }
1155 }
1156 }
1157 }
1158}
1159
1160impl PrimarySelectionHandler for AppState {
1165 fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {
1166 &mut self.primary_selection_state
1167 }
1168}
1169
1170impl XdgDecorationHandler for AppState {
1180 fn new_decoration(&mut self, toplevel: ToplevelSurface) {
1181 toplevel.with_pending_state(|state| {
1182 state.decoration_mode = Some(Mode::ServerSide);
1183 });
1184 toplevel.send_configure();
1185 if let Some(window) = self.window_of_toplevel(&toplevel) {
1188 crate::wayland::wm::set_decoration(self, &window, crate::wm::Decoration::Unspecified);
1189 }
1190 }
1191
1192 fn request_mode(&mut self, toplevel: ToplevelSurface, mode: Mode) {
1193 toplevel.with_pending_state(|state| {
1194 state.decoration_mode = Some(mode);
1195 });
1196 toplevel.send_configure();
1197 if let Some(window) = self.window_of_toplevel(&toplevel) {
1198 let preference = match mode {
1199 Mode::ClientSide => crate::wm::Decoration::Client,
1200 _ => crate::wm::Decoration::Server,
1201 };
1202 crate::wayland::wm::set_decoration(self, &window, preference);
1203 }
1204 }
1205
1206 fn unset_mode(&mut self, toplevel: ToplevelSurface) {
1207 toplevel.with_pending_state(|state| {
1208 state.decoration_mode = Some(Mode::ServerSide);
1209 });
1210 toplevel.send_configure();
1211 if let Some(window) = self.window_of_toplevel(&toplevel) {
1212 crate::wayland::wm::set_decoration(self, &window, crate::wm::Decoration::Unspecified);
1213 }
1214 }
1215}
1216
1217impl WlrLayerShellHandler for AppState {
1224 fn shell_state(&mut self) -> &mut WlrLayerShellState {
1225 &mut self.layer_shell_state
1226 }
1227
1228 fn new_layer_surface(
1229 &mut self,
1230 surface: WlrLayerSurface,
1231 output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
1232 _layer: WlrLayer,
1233 namespace: String,
1234 ) {
1235 let smithay_output = if let Some(wlo) = output.as_ref() {
1236 self.output_nodes
1237 .iter()
1238 .map(|n| &n.output)
1239 .find(|o| o.owns(wlo))
1240 } else {
1241 self.primary_output()
1242 };
1243
1244 if let Some(output) = smithay_output {
1245 let mode = output.current_mode().unwrap();
1246
1247 surface.with_pending_state(|state| {
1248 state.size =
1249 Some((f64::from(mode.size.w) as i32, f64::from(mode.size.h) as i32).into());
1250 });
1251 surface.send_configure();
1252
1253 let layer = DesktopLayerSurface::new(surface, namespace);
1254 let _ = layer_map_for_output(output).map_layer(&layer);
1255 }
1256 }
1257
1258 fn layer_destroyed(&mut self, _surface: WlrLayerSurface) {
1259 for node in &self.output_nodes {
1260 let mut map = layer_map_for_output(&node.output);
1261 map.cleanup();
1262 map.arrange();
1263 }
1264 crate::wayland::wm::outputs_changed(self);
1265 crate::wayland::wm::sync_layer_keyboard(self);
1266 }
1267}
1268
1269impl CompositorHandler for AppState {
1272 fn compositor_state(&mut self) -> &mut CompositorState {
1273 &mut self.compositor_state
1274 }
1275 fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
1276 if let Some(data) = client.get_data::<smithay::xwayland::XWaylandClientData>() {
1277 return &data.compositor_state;
1278 }
1279 &client.get_data::<ClientState>().unwrap().compositor_state
1280 }
1281
1282 fn commit(&mut self, surface: &WlSurface) {
1314 let new_content = with_states(surface, |states| {
1317 states.role != Some("cursor_image") && {
1318 let mut attrs = states.cached_state.get::<SurfaceAttributes>();
1319 let current = attrs.current();
1320 current.buffer.is_some() || !current.damage.is_empty()
1321 }
1322 });
1323 if new_content && self.host.is_none() {
1325 for node in &mut self.output_nodes {
1326 if let Some(cap) = node.capture.as_mut() {
1327 cap.change_pending = true;
1328 }
1329 }
1330 }
1331 smithay::backend::renderer::utils::on_commit_buffer_handler::<Self>(surface);
1332 if let Some((icon, offset)) = self.dnd_icon.as_mut()
1333 && icon == surface
1334 {
1335 let delta = with_states(surface, |states| {
1336 states
1337 .cached_state
1338 .get::<SurfaceAttributes>()
1339 .current()
1340 .buffer_delta
1341 .take()
1342 });
1343 *offset += delta.unwrap_or_default();
1344 }
1345 self.popups.commit(surface);
1348
1349 for node in &self.output_nodes {
1350 let mut layer_map = layer_map_for_output(&node.output);
1351 let found = layer_map
1352 .layers()
1353 .any(|layer| layer.wl_surface() == surface);
1354 if found {
1355 layer_map.arrange();
1356 drop(layer_map);
1357 crate::wayland::wm::outputs_changed(self);
1358 crate::wayland::wm::sync_layer_keyboard(self);
1359 break;
1360 }
1361 }
1362
1363 if let Some(CursorImageStatus::Surface(ref cursor_surface)) = self.current_cursor_icon
1364 && cursor_surface == surface
1365 {
1366 let status = CursorImageStatus::Surface(surface.clone());
1367 self.cursor_surface_pending = false;
1368 self.send_cursor_image(&status);
1369 }
1370
1371 let mut foreign_update = None;
1372 if let Some(handle) = with_states(surface, |states| {
1373 states.data_map.get::<ForeignToplevelHandle>().cloned()
1374 }) && let Some(window) = self
1375 .space
1376 .elements()
1377 .find(|w| w.wl_surface().as_deref() == Some(surface))
1378 && let Some(_toplevel) = window.toplevel()
1379 {
1380 let (title, app_id) = with_states(surface, |states| {
1381 let attributes = states
1382 .data_map
1383 .get::<XdgToplevelSurfaceData>()
1384 .unwrap()
1385 .lock()
1386 .unwrap();
1387 (attributes.title.clone(), attributes.app_id.clone())
1388 });
1389
1390 handle.send_title(&title.unwrap_or_default());
1391 handle.send_app_id(&app_id.unwrap_or_default());
1392 handle.send_done();
1393 foreign_update = window_meta(window).map(|m| m.id);
1394 }
1395 if let Some(id) = foreign_update {
1396 crate::wayland::foreign_toplevel::update(self, id);
1397 }
1398
1399 let mapped = self
1400 .space
1401 .elements()
1402 .find(|w| {
1403 w.toplevel()
1404 .map(|tl| tl.wl_surface() == surface)
1405 .unwrap_or(false)
1406 })
1407 .cloned();
1408 if let Some(window) = mapped {
1409 window.on_commit();
1410 let has_buffer =
1414 smithay::backend::renderer::utils::with_renderer_surface_state(surface, |s| {
1415 s.buffer().is_some()
1416 })
1417 .unwrap_or(false);
1418 if !has_buffer {
1419 self.space.unmap_elem(&window);
1420 if let Some(id) = window_meta(&window).map(|m| m.id) {
1421 crate::wayland::foreign_toplevel::remove(self, id);
1422 }
1423 crate::wayland::wm::window_unmapped(self, &window);
1424 self.pending_windows.push(window);
1425 self.refocus_pointer();
1426 return;
1427 }
1428 let size = window.geometry().size;
1429 crate::wayland::wm::window_resized(
1430 self,
1431 &window,
1432 crate::wm::Size {
1433 w: size.w,
1434 h: size.h,
1435 },
1436 );
1437 }
1438
1439 if let Some(idx) = self.pending_windows.iter().position(|w| {
1440 w.toplevel()
1441 .map(|tl| tl.wl_surface() == surface)
1442 .unwrap_or(false)
1443 }) {
1444 let window = self.pending_windows.remove(idx);
1445 let toplevel = window.toplevel().unwrap();
1446
1447 let needs_placement = window_meta(&window)
1452 .map(|meta| !meta.placed.swap(true, Ordering::Relaxed))
1453 .unwrap_or(false);
1454
1455 if needs_placement && crate::wayland::wm::active(self) {
1456 let target_id = self.pointer_display();
1459 if let Some(meta) = window_meta(&window) {
1460 meta.output.store(target_id, Ordering::Relaxed);
1461 }
1462 self.pending_windows.push(window.clone());
1463 crate::wayland::wm::window_added(self, &window);
1464 if !toplevel.is_initial_configure_sent() {
1465 toplevel.send_configure();
1466 }
1467 } else if needs_placement {
1468 let mut target_id = self.pointer_display();
1472 let mut park = false;
1478 if self.would_cover_screen(&window, target_id) {
1479 let empty = self
1484 .output_nodes
1485 .iter()
1486 .filter(|n| n.owner.is_none())
1487 .map(|n| n.id)
1488 .find(|oid| {
1489 !self
1490 .space
1491 .elements()
1492 .chain(self.pending_windows.iter())
1493 .any(|w| window_output_id(w) == *oid)
1494 });
1495 match empty {
1496 Some(oid) => target_id = oid,
1497 None => park = true,
1498 }
1499 }
1500 if let Some(meta) = window_meta(&window) {
1501 meta.output.store(target_id, Ordering::Relaxed);
1502 meta.parked.store(park, Ordering::Relaxed);
1503 }
1504 let (logical_width, logical_height) = if park {
1505 PARKED_LOGICAL_SIZE
1506 } else if let Some(size) = self.logical_size_of(target_id) {
1507 size
1508 } else {
1509 let scale = self.settings.scale.max(0.1);
1510 (
1511 (f64::from(self.settings.width) / scale).round() as i32,
1512 (f64::from(self.settings.height) / scale).round() as i32,
1513 )
1514 };
1515
1516 toplevel.with_pending_state(|state| {
1517 state.states.set(XdgState::Activated);
1518 state.states.set(XdgState::Fullscreen);
1519 state.size = Some((logical_width, logical_height).into());
1520 });
1521 toplevel.send_configure();
1522
1523 self.pending_windows.push(window);
1524 } else if smithay::backend::renderer::utils::with_renderer_surface_state(surface, |s| {
1525 s.buffer().is_none()
1526 })
1527 .unwrap_or(true)
1528 {
1529 let tl = toplevel.clone();
1536 self.pending_windows.push(window);
1537 if !crate::wayland::wm::active(self) {
1538 self.send_forced_fullscreen_configure(&tl);
1539 }
1540 } else if crate::wayland::wm::active(self) {
1541 let id = window_meta(&window).map(|m| m.id).unwrap_or(0);
1542 let rect = crate::wayland::wm::rect_of(self, id);
1543 let node_idx = self.node_idx_for_id(window_output_id(&window)).unwrap_or(0);
1544 let (target_output, pos) = {
1545 let node = &self.output_nodes[node_idx];
1546 (node.output.clone(), node.pos)
1547 };
1548 let at = rect.map(|r| (r.x, r.y)).unwrap_or(pos);
1549 self.space.map_element(window.clone(), at, false);
1550 window.on_commit();
1551 target_output.enter(surface);
1552 let scale = target_output.current_scale().fractional_scale();
1553 with_states(surface, |states| {
1554 smithay::wayland::compositor::send_surface_state(
1555 surface,
1556 states,
1557 scale.ceil() as i32,
1558 smithay::utils::Transform::Normal,
1559 );
1560 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1561 fs.set_preferred_scale(scale);
1562 });
1563 });
1564 let size = window.geometry().size;
1565 crate::wayland::wm::window_mapped(
1566 self,
1567 &window,
1568 crate::wm::Size {
1569 w: size.w,
1570 h: size.h,
1571 },
1572 );
1573 crate::wayland::foreign_toplevel::add(self, &window);
1574 self.refocus_pointer();
1575 } else {
1576 let target_id = window_output_id(&window);
1577 let parked = window_meta(&window)
1578 .map(|meta| meta.parked.load(Ordering::Relaxed))
1579 .unwrap_or(false);
1580 let node_idx = self.node_idx_for_id(target_id).unwrap_or(0);
1581 let (target_output, pos) = {
1582 let node = &self.output_nodes[node_idx];
1583 (node.output.clone(), node.pos)
1584 };
1585 self.space.map_element(
1588 window.clone(),
1589 if parked { PARKED_POS } else { pos },
1590 !parked,
1591 );
1592 window.on_commit();
1593
1594 if !parked {
1595 target_output.enter(surface);
1596 let scale = target_output.current_scale().fractional_scale();
1597 with_states(surface, |states| {
1598 smithay::wayland::compositor::send_surface_state(
1599 surface,
1600 states,
1601 scale.ceil() as i32,
1602 smithay::utils::Transform::Normal,
1603 );
1604 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1605 fs.set_preferred_scale(scale);
1606 });
1607 });
1608
1609 if let Some(geo_out) = self.output_nodes[node_idx].logical_geometry() {
1610 let (expected_w, expected_h) = (geo_out.size.w, geo_out.size.h);
1611 let geo = window.geometry();
1612 if (geo.size.w - expected_w).abs() > 1
1613 || (geo.size.h - expected_h).abs() > 1
1614 {
1615 toplevel.with_pending_state(|state| {
1616 state.states.set(XdgState::Activated);
1617 state.states.set(XdgState::Fullscreen);
1618 state.size = Some((expected_w, expected_h).into());
1619 });
1620 toplevel.send_configure();
1621 }
1622 }
1623
1624 let serial = next_serial();
1625 let target = FocusTarget::Window(window.clone());
1626 if let Some(keyboard) = self.seat.get_keyboard() {
1627 keyboard.set_focus(self, Some(target), serial);
1628 }
1629 crate::wayland::foreign_toplevel::add(self, &window);
1630 self.refocus_pointer();
1631 } else {
1632 let geo = window.geometry();
1637 if (geo.size.w - PARKED_LOGICAL_SIZE.0).abs() > 1
1638 || (geo.size.h - PARKED_LOGICAL_SIZE.1).abs() > 1
1639 {
1640 let tl = toplevel.clone();
1641 self.send_forced_fullscreen_configure(&tl);
1642 }
1643 }
1644 }
1645 }
1646 }
1647}
1648
1649impl AppState {
1650 pub(crate) fn window_of_toplevel(&self, toplevel: &ToplevelSurface) -> Option<Window> {
1652 self.space
1653 .elements()
1654 .chain(self.pending_windows.iter())
1655 .chain(self.wm.iter().flat_map(|wm| wm.hidden.values()))
1656 .find(|w| w.toplevel().is_some_and(|t| t == toplevel))
1657 .cloned()
1658 }
1659
1660 pub(crate) fn id_of_toplevel(&self, toplevel: &ToplevelSurface) -> Option<u32> {
1662 self.window_of_toplevel(toplevel)
1663 .and_then(|w| window_meta(&w).map(|m| m.id))
1664 }
1665
1666 pub(crate) fn primary_output(&self) -> Option<&Output> {
1668 self.output_nodes.first().map(|n| &n.output)
1669 }
1670
1671 pub(crate) fn node_idx_for_id(&self, id: u32) -> Option<usize> {
1672 self.output_nodes.iter().position(|n| n.id == id)
1673 }
1674
1675 pub(crate) fn view_ids_of(&self, id: u32) -> Vec<u32> {
1677 self.output_nodes
1678 .iter()
1679 .filter(|n| n.owner == Some(id))
1680 .map(|n| n.id)
1681 .collect()
1682 }
1683
1684 pub(crate) fn screen_id_of(&self, id: u32) -> u32 {
1686 self.node_idx_for_id(id)
1687 .and_then(|i| self.output_nodes[i].owner)
1688 .unwrap_or(id)
1689 }
1690
1691 pub(crate) fn node_idx_under(&self, p: Point<f64, Logical>) -> Option<usize> {
1693 self.output_nodes.iter().position(|n| {
1694 n.logical_geometry()
1695 .map(|g| g.to_f64().contains(p))
1696 .unwrap_or(false)
1697 })
1698 }
1699
1700 pub(crate) fn layout_physical_to_logical(&self, x: f64, y: f64) -> Point<f64, Logical> {
1705 let mut best: Option<(f64, Point<f64, Logical>)> = None;
1706 for node in self.output_nodes.iter().filter(|n| n.owner.is_none()) {
1707 let Some(mode) = node.output.current_mode() else {
1708 continue;
1709 };
1710 let scale = node.output.current_scale().fractional_scale();
1711 let (px, py) = (f64::from(node.pos.0), f64::from(node.pos.1));
1712 let cx = x.max(px).min(px + f64::from(mode.size.w) - 1.0);
1713 let cy = y.max(py).min(py + f64::from(mode.size.h) - 1.0);
1714 let d2 = (x - cx).powi(2) + (y - cy).powi(2);
1715 let logical = Point::from((
1716 f64::from(node.pos.0) + (cx - px) / scale,
1717 f64::from(node.pos.1) + (cy - py) / scale,
1718 ));
1719 if best.as_ref().map(|(bd, _)| d2 < *bd).unwrap_or(true) {
1720 best = Some((d2, logical));
1721 }
1722 }
1723 best.map(|(_, p)| p).unwrap_or_else(|| (0.0, 0.0).into())
1724 }
1725
1726 pub(crate) fn pointer_locked(
1730 &self,
1731 pointer: &PointerHandle<Self>,
1732 under: &Option<(FocusTarget, Point<f64, Logical>)>,
1733 location: Point<f64, Logical>,
1734 ) -> bool {
1735 let Some((target, origin)) = under.as_ref() else {
1736 return false;
1737 };
1738 if pointer.current_focus().as_ref() != Some(target) {
1739 return false;
1740 }
1741 let Some(surface) = target.wl_surface() else {
1742 return false;
1743 };
1744 with_pointer_constraint(&surface, pointer, |constraint| match constraint {
1745 Some(constraint) if constraint.is_active() => {
1746 let local = (location - *origin).to_i32_round();
1747 constraint
1748 .region()
1749 .is_none_or(|region| region.contains(local))
1750 && matches!(&*constraint, PointerConstraint::Locked(_))
1751 }
1752 _ => false,
1753 })
1754 }
1755
1756 pub(crate) fn pointer_target_under(
1773 &self,
1774 p: Point<f64, Logical>,
1775 ) -> Option<(FocusTarget, Point<f64, Logical>)> {
1776 use smithay::wayland::shell::wlr_layer::Layer;
1777 let layer_hit = |layers: &[Layer]| {
1778 let node = &self.output_nodes[self.node_idx_under(p)?];
1779 let origin = Point::<i32, Logical>::from(node.pos);
1780 let local = (p - origin.to_f64()).to_i32_round();
1781 let layer_map = layer_map_for_output(&node.output);
1782 layer_map.layers().rev().find_map(|layer| {
1783 if !layers.contains(&layer.layer()) {
1784 return None;
1785 }
1786 let bbox = layer_map.layer_geometry(layer)?;
1787 popup_under(layer.wl_surface(), bbox.loc + origin, p).or_else(|| {
1788 layer
1791 .surface_under((local - bbox.loc).to_f64(), WindowSurfaceType::ALL)
1792 .map(|_| {
1793 (
1794 FocusTarget::LayerSurface(layer.clone()),
1795 (bbox.loc + origin).to_f64(),
1796 )
1797 })
1798 })
1799 })
1800 };
1801 if let Some(menu) = self.x11_menu_open() {
1802 return Some(menu);
1803 }
1804 let found = layer_hit(&[Layer::Overlay])
1805 .or_else(|| {
1806 self.space
1807 .element_under(p)
1808 .filter(|(window, _)| is_override_redirect(window))
1809 .map(|(window, loc)| (FocusTarget::Window(window.clone()), loc.to_f64()))
1810 })
1811 .or_else(|| layer_hit(&[Layer::Top]))
1812 .or_else(|| {
1813 self.space.element_under(p).map(|(window, loc)| {
1814 window
1815 .wl_surface()
1816 .and_then(|root| popup_under(&root, loc + window.geometry().loc, p))
1817 .unwrap_or_else(|| (FocusTarget::Window(window.clone()), loc.to_f64()))
1818 })
1819 })
1820 .or_else(|| layer_hit(&[Layer::Bottom, Layer::Background]));
1821 found.map(|(target, origin)| target.under_point(origin, p))
1822 }
1823
1824 fn x11_menu_open(&self) -> Option<(FocusTarget, Point<f64, Logical>)> {
1826 use smithay::xwayland::xwm::WmWindowType;
1827 let menu = self.space.elements().rev().find(|window| {
1828 window.x11_surface().is_some_and(|surface| {
1829 surface.is_override_redirect()
1830 && matches!(
1831 surface.window_type(),
1832 Some(
1833 WmWindowType::PopupMenu
1834 | WmWindowType::DropdownMenu
1835 | WmWindowType::Menu
1836 )
1837 )
1838 })
1839 })?;
1840 let loc = self.space.element_location(menu)?;
1841 Some((FocusTarget::Window(menu.clone()), loc.to_f64()))
1842 }
1843
1844 pub(crate) fn refocus_pointer(&mut self) {
1851 if self.host.is_some() || self.wm.as_ref().is_some_and(|wm| wm.pointer_taken) {
1852 return;
1853 }
1854 let Some(pointer) = self.seat.get_pointer() else {
1855 return;
1856 };
1857 if pointer.is_grabbed() {
1858 return;
1859 }
1860 let location = pointer.current_location();
1861 let under = self.pointer_target_under(location).filter(|(target, _)| {
1862 target
1863 .wl_surface()
1864 .and_then(|surface| surface.client())
1865 .is_none_or(|client| pointer.client_pointers(&client).next().is_some())
1866 });
1867 if pointer.current_focus() == under.as_ref().map(|(t, _)| t.clone()) {
1868 return;
1869 }
1870 let time = wayland_time();
1871 pointer.motion(
1872 self,
1873 under.clone(),
1874 &MotionEvent {
1875 location,
1876 serial: next_serial(),
1877 time,
1878 },
1879 );
1880 if under.is_some() {
1881 pointer.motion(
1882 self,
1883 under.clone(),
1884 &MotionEvent {
1885 location,
1886 serial: next_serial(),
1887 time,
1888 },
1889 );
1890 }
1891 pointer.frame(self);
1892 self.activate_constraint_under(&pointer, &under, location);
1893 }
1894
1895 pub(crate) fn activate_constraint_under(
1897 &self,
1898 pointer: &PointerHandle<Self>,
1899 under: &Option<(FocusTarget, Point<f64, Logical>)>,
1900 location: Point<f64, Logical>,
1901 ) {
1902 let Some((target, origin)) = under.as_ref() else {
1903 return;
1904 };
1905 let Some(surface) = target.wl_surface() else {
1906 return;
1907 };
1908 with_pointer_constraint(&surface, pointer, |constraint| {
1909 if let Some(constraint) = constraint
1910 && !constraint.is_active()
1911 && constraint
1912 .region()
1913 .is_none_or(|region| region.contains((location - *origin).to_i32_round()))
1914 {
1915 constraint.activate();
1916 }
1917 });
1918 }
1919
1920 pub(crate) fn clamp_logical(&self, p: Point<f64, Logical>) -> Point<f64, Logical> {
1922 let mut best: Option<(f64, Point<f64, Logical>)> = None;
1923 for node in self.output_nodes.iter().filter(|n| n.owner.is_none()) {
1924 let Some(geo) = node.logical_geometry() else {
1925 continue;
1926 };
1927 let scale = node.output.current_scale().fractional_scale();
1928 let g = geo.to_f64();
1929 let margin = 1.0 / scale.max(0.1);
1930 let cx = p.x.max(g.loc.x).min(g.loc.x + g.size.w - margin);
1931 let cy = p.y.max(g.loc.y).min(g.loc.y + g.size.h - margin);
1932 let d2 = (p.x - cx).powi(2) + (p.y - cy).powi(2);
1933 if best.as_ref().map(|(bd, _)| d2 < *bd).unwrap_or(true) {
1934 best = Some((d2, (cx, cy).into()));
1935 }
1936 }
1937 best.map(|(_, p)| p).unwrap_or(p)
1938 }
1939
1940 pub(crate) fn layout_logical_to_physical(&self, p: Point<f64, Logical>) -> (f64, f64) {
1943 let (x, y, _) = self.layout_logical_to_physical_scaled(p);
1944 (x, y)
1945 }
1946
1947 pub(crate) fn layout_logical_to_physical_scaled(
1949 &self,
1950 p: Point<f64, Logical>,
1951 ) -> (f64, f64, f64) {
1952 let idx = self.node_idx_under(p).unwrap_or(0);
1953 let Some(node) = self.output_nodes.get(idx) else {
1954 return (p.x, p.y, 1.0);
1955 };
1956 let scale = node.output.current_scale().fractional_scale();
1957 (
1958 f64::from(node.pos.0) + (p.x - f64::from(node.pos.0)) * scale,
1959 f64::from(node.pos.1) + (p.y - f64::from(node.pos.1)) * scale,
1960 scale,
1961 )
1962 }
1963
1964 pub(crate) fn logical_size_of(&self, id: u32) -> Option<(i32, i32)> {
1966 let idx = self.node_idx_for_id(id)?;
1967 let geo = self.output_nodes[idx].logical_geometry()?;
1968 Some((geo.size.w, geo.size.h))
1969 }
1970
1971 pub(crate) fn release_grab_across_screens(
1978 &mut self,
1979 pointer: &PointerHandle<Self>,
1980 under: &Option<(FocusTarget, Point<f64, Logical>)>,
1981 serial: Serial,
1982 time: InputTime,
1983 ) {
1984 let Some(FocusTarget::Window(next)) = under.as_ref().map(|(t, _)| t.owner()) else {
1985 return;
1986 };
1987 let Some(GrabStartData {
1988 focus: Some((start, _)),
1989 ..
1990 }) = pointer.grab_start_data()
1991 else {
1992 return;
1993 };
1994 let FocusTarget::Window(held) = start.owner().clone() else {
1995 return;
1996 };
1997 let focused = pointer.current_focus();
1998 if held == *next
1999 || focused.as_ref().map(FocusTarget::owner) != Some(&FocusTarget::Window(held.clone()))
2000 {
2001 return;
2002 }
2003 let client_of = |w: &Window| w.wl_surface().and_then(|s| s.client()).map(|c| c.id());
2004 if client_of(&held).is_some() && client_of(&held) == client_of(next) {
2005 pointer.unset_grab(self, serial, time);
2006 }
2007 }
2008
2009 pub(crate) fn pointer_shown_at(&self) -> Option<Point<f64, Logical>> {
2015 self.seat
2016 .get_pointer()
2017 .map(|p| crate::wayland::wm::judged_location(self, p.current_location()))
2018 }
2019
2020 pub(crate) fn pointer_display(&self) -> u32 {
2022 self.pointer_shown_at()
2023 .and_then(|pos| self.node_idx_under(pos))
2024 .map(|idx| self.output_nodes[idx].id)
2025 .unwrap_or(0)
2026 }
2027
2028 pub(crate) fn would_cover_screen(&self, window: &Window, id: u32) -> bool {
2032 let Some(client) = window.wl_surface().and_then(|s| s.client()) else {
2033 return false;
2034 };
2035 self.space
2036 .elements()
2037 .chain(self.pending_windows.iter())
2038 .filter(|w| !std::ptr::eq(*w, window) && w.wl_surface() != window.wl_surface())
2039 .any(|w| {
2040 window_output_id(w) == id
2041 && !window_meta(w)
2042 .map(|m| m.parked.load(Ordering::Relaxed))
2043 .unwrap_or(false)
2044 && w.wl_surface()
2045 .and_then(|s| s.client())
2046 .is_some_and(|c| c.id() == client.id())
2047 })
2048 }
2049
2050 pub(crate) fn park_window(&mut self, window: &Window, id: u32) {
2054 let old_id = window_output_id(window);
2055 if let Some(meta) = window_meta(window) {
2056 meta.output.store(id, Ordering::Relaxed);
2057 meta.parked.store(true, Ordering::Relaxed);
2058 }
2059 if let (Some(surface), Some(idx)) = (window.wl_surface(), self.node_idx_for_id(old_id)) {
2060 self.output_nodes[idx].output.leave(&surface);
2061 }
2062 self.space.map_element(window.clone(), PARKED_POS, false);
2063 self.refocus_pointer();
2064 if let Some(toplevel) = window.toplevel() {
2065 let toplevel = toplevel.clone();
2066 self.send_forced_fullscreen_configure(&toplevel);
2067 }
2068 }
2069
2070 pub(crate) fn place_window_on_output(&mut self, window: &Window, id: u32) -> bool {
2074 let Some(idx) = self.node_idx_for_id(id) else {
2075 return false;
2076 };
2077 let old_id = window_output_id(window);
2078 let (new_output, pos) = {
2079 let node = &self.output_nodes[idx];
2080 (node.output.clone(), node.pos)
2081 };
2082 let old_output = self
2083 .node_idx_for_id(old_id)
2084 .map(|i| self.output_nodes[i].output.clone());
2085 if let Some(meta) = window_meta(window) {
2086 meta.output.store(id, Ordering::Relaxed);
2087 meta.parked.store(false, Ordering::Relaxed);
2088 }
2089 self.space.map_element(window.clone(), pos, true);
2090 self.refocus_pointer();
2091 if let Some(surface) = window.wl_surface() {
2092 if let Some(old) = old_output
2093 && old_id != id
2094 {
2095 old.leave(&surface);
2096 }
2097 new_output.enter(&surface);
2098 let scale = new_output.current_scale().fractional_scale();
2099 with_states(&surface, |states| {
2100 smithay::wayland::compositor::send_surface_state(
2101 &surface,
2102 states,
2103 scale.ceil() as i32,
2104 smithay::utils::Transform::Normal,
2105 );
2106 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
2107 fs.set_preferred_scale(scale);
2108 });
2109 });
2110 }
2111 if let Some(toplevel) = window.toplevel() {
2112 let toplevel = toplevel.clone();
2113 self.send_forced_fullscreen_configure(&toplevel);
2114 }
2115 true
2116 }
2117
2118 pub(crate) fn process_pending_clipboard_read(&mut self) {
2134 let Some(mimes) = self.pending_clipboard_read.take() else {
2135 return;
2136 };
2137 if crate::SHUTDOWN.load(Ordering::Relaxed) {
2138 return;
2139 }
2140 let Some(cb) = self.clipboard_callback.clone() else {
2141 return;
2142 };
2143 let cleared = mimes.is_empty();
2144 let mut pipes = Vec::new();
2145 for mime in mimes {
2146 let Ok((reader, writer)) = std::io::pipe() else {
2147 continue;
2148 };
2149 let writer: std::os::fd::OwnedFd = writer.into();
2150 let served = match request_data_device_client_selection::<AppState>(
2151 &self.seat,
2152 mime.clone(),
2153 writer,
2154 ) {
2155 Ok(()) => true,
2156 Err(_) => match (std::io::pipe(), self.xwm.wm.as_mut()) {
2157 (Ok((again, writer)), Some(xwm)) => {
2158 drop(reader);
2159 if xwm
2160 .send_selection(SelectionTarget::Clipboard, mime.clone(), writer.into())
2161 .is_ok()
2162 {
2163 pipes.push((mime, again));
2164 }
2165 continue;
2166 }
2167 _ => false,
2168 },
2169 };
2170 if served {
2171 pipes.push((mime, reader));
2172 }
2173 }
2174 if pipes.is_empty() && !cleared {
2175 return;
2176 }
2177 let generations = self.clipboard_generation.clone();
2178 let generation = generations.fetch_add(1, Ordering::Relaxed) + 1;
2179 std::thread::spawn(move || {
2180 let entries: Vec<(String, Vec<u8>)> = pipes
2181 .into_iter()
2182 .filter_map(|(mime, reader)| read_selection(reader).map(|bytes| (mime, bytes)))
2183 .collect();
2184 if (entries.is_empty() && !cleared) || crate::SHUTDOWN.load(Ordering::Relaxed) {
2185 return;
2186 }
2187 if generations.load(Ordering::Relaxed) != generation {
2188 return;
2189 }
2190 cb.selection(entries);
2191 });
2192 }
2193
2194 pub(crate) fn send_cursor_image(&mut self, image: &CursorImageStatus) {
2213 if !self.cursor_callback_set {
2214 return;
2215 }
2216 let job = match image {
2217 CursorImageStatus::Named(icon) => {
2218 self.cursor_buffer = None;
2219 CursorJob::Named {
2220 name: cursor_icon_to_str(icon),
2221 }
2222 }
2223 CursorImageStatus::Hidden => {
2224 self.cursor_buffer = None;
2225 CursorJob::Hide
2226 }
2227 CursorImageStatus::Surface(surface) => {
2228 let mut hot_x = 0;
2229 let mut hot_y = 0;
2230 let mut is_cursor_role = false;
2231
2232 with_states(surface, |states| {
2235 if states.role == Some("cursor_image") {
2236 is_cursor_role = true;
2237 }
2238 let buffer_scale = states
2239 .cached_state
2240 .get::<SurfaceAttributes>()
2241 .current()
2242 .buffer_scale
2243 .max(1);
2244 if let Some(attributes) = states.data_map.get::<Mutex<CursorImageAttributes>>()
2245 && let Ok(guard) = attributes.lock()
2246 {
2247 hot_x = guard.hotspot.x * buffer_scale;
2248 hot_y = guard.hotspot.y * buffer_scale;
2249 }
2250 });
2251
2252 if !is_cursor_role {
2253 return;
2254 }
2255
2256 let buffer_found = with_states(surface, |states| {
2257 let mut attrs = states.cached_state.get::<SurfaceAttributes>();
2258
2259 if let Some(BufferAssignment::NewBuffer(b)) = &attrs.current().buffer {
2260 return Some(b.clone());
2261 }
2262
2263 if let Some(mutex) = states.data_map.get::<Mutex<RendererSurfaceState>>()
2264 && let Ok(renderer_state) = mutex.try_lock()
2265 && let Some(b) = renderer_state.buffer()
2266 {
2267 let wl_buffer: &wayland_server::protocol::wl_buffer::WlBuffer = b;
2268 return Some(wl_buffer.clone());
2269 }
2270 None
2271 });
2272
2273 let Some(buffer) = buffer_found else { return };
2274
2275 let shm_result = with_buffer_contents(&buffer, |ptr, len, spec| {
2276 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
2277 let mut hasher = DefaultHasher::new();
2278 spec.width.hash(&mut hasher);
2279 spec.height.hash(&mut hasher);
2280 spec.stride.hash(&mut hasher);
2281 spec.offset.hash(&mut hasher);
2282 spec.format.hash(&mut hasher);
2283 let start = (spec.offset.max(0) as usize).min(len);
2284 let span =
2285 (spec.stride.max(0) as usize).saturating_mul(spec.height.max(0) as usize);
2286 let end = start.saturating_add(span).min(len);
2287 slice[start..end].hash(&mut hasher);
2288 let hash = hasher.finish();
2289 (
2290 hash,
2291 spec.width,
2292 spec.height,
2293 spec.stride,
2294 spec.format,
2295 spec.offset,
2296 slice.to_vec(),
2297 )
2298 });
2299
2300 let job = match shm_result {
2301 Ok((hash, width, height, stride, format, buf_offset, raw_bytes)) => {
2302 Some(CursorJob::Shm {
2303 hash,
2304 width,
2305 height,
2306 stride,
2307 offset: buf_offset,
2308 opaque: format == wl_shm::Format::Xrgb8888,
2309 bytes: raw_bytes,
2310 hot_x,
2311 hot_y,
2312 })
2313 }
2314 Err(BufferAccessError::NotManaged) => {
2315 let mut gles_job = None;
2316 let dmabuf_opt = get_dmabuf(&buffer).ok().cloned();
2317 if let Some(mut dmabuf) = dmabuf_opt
2318 && let Some(renderer) = self.gles_renderer.as_mut()
2319 {
2320 let width = dmabuf.width() as i32;
2321 let height = dmabuf.height() as i32;
2322
2323 match renderer.bind(&mut dmabuf) {
2324 Ok(frame) => {
2325 let rect =
2326 Rectangle::new((0, 0).into(), (width, height).into());
2327 match renderer.copy_framebuffer(&frame, rect, Fourcc::Abgr8888)
2328 {
2329 Ok(mapping) => match renderer.map_texture(&mapping) {
2330 Ok(data) => {
2331 let mut hasher = DefaultHasher::new();
2332 data.hash(&mut hasher);
2333 gles_job = Some(CursorJob::Gles {
2334 hash: hasher.finish(),
2335 width,
2336 height,
2337 bytes: data.to_vec(),
2338 hot_x,
2339 hot_y,
2340 });
2341 }
2342 Err(e) => eprintln!("Failed to map texture: {:?}", e),
2343 },
2344 Err(e) => eprintln!("Failed to copy framebuffer: {:?}", e),
2345 }
2346 }
2347 Err(e) => eprintln!("Failed to bind dmabuf to renderer: {:?}", e),
2348 }
2349 }
2350 gles_job
2351 }
2352 Err(_) => None,
2353 };
2354
2355 self.cursor_buffer = Some(buffer);
2356 let Some(job) = job else { return };
2357 job
2358 }
2359 };
2360 let _ = self.cursor_tx.send(job);
2361 }
2362
2363 pub(crate) fn flush_pending_cursor(&mut self) {
2366 if !self.cursor_surface_pending {
2367 return;
2368 }
2369 self.cursor_surface_pending = false;
2370 if let Some(icon) = self.current_cursor_icon.clone() {
2371 self.send_cursor_image(&icon);
2372 }
2373 }
2374
2375 pub(crate) fn apply_keymap_policy(&mut self) {
2378 let text = self.keymap_policy.keymap_text();
2379 self.apply_keymap_text(text);
2380 }
2381
2382 pub(crate) fn apply_keymap_text(&mut self, text: String) {
2385 if text.is_empty() {
2386 return;
2387 }
2388 if let Some(host) = &self.host {
2392 host.set_keymap(&text);
2393 }
2394 if let Some(keyboard) = self.seat.get_keyboard()
2395 && let Err(e) = keyboard.set_keymap_from_string(self, text)
2396 {
2397 eprintln!("[Wayland] keymap swap failed: {e:?}");
2398 }
2399 }
2400
2401 pub(crate) fn bind_keysyms(&mut self, keysyms: &[u32]) -> Vec<(u32, u32)> {
2406 let pressed: std::collections::HashSet<u32> = self
2407 .seat
2408 .get_keyboard()
2409 .map(|k| k.pressed_keys().iter().map(|c| c.raw()).collect())
2410 .unwrap_or_default();
2411 let (out, changed) = self.keymap_policy.bind_many(keysyms, &pressed);
2412 if changed {
2413 self.apply_keymap_policy();
2414 }
2415 out
2416 }
2417
2418 pub(crate) fn bind_keysyms_plain(&mut self, keysyms: &[u32]) -> Vec<u32> {
2422 let pressed: std::collections::HashSet<u32> = self
2423 .seat
2424 .get_keyboard()
2425 .map(|k| k.pressed_keys().iter().map(|c| c.raw()).collect())
2426 .unwrap_or_default();
2427 let (out, changed) = self.keymap_policy.bind_many_plain(keysyms, &pressed);
2428 if changed {
2429 self.apply_keymap_policy();
2430 }
2431 out
2432 }
2433
2434 pub(crate) fn send_forced_fullscreen_configure(&mut self, toplevel: &ToplevelSurface) {
2440 let output_id = self
2441 .space
2442 .elements()
2443 .chain(self.pending_windows.iter())
2444 .find(|w| w.toplevel().map(|t| t == toplevel).unwrap_or(false))
2445 .map(window_output_id)
2446 .unwrap_or(0);
2447 let parked = self
2448 .space
2449 .elements()
2450 .chain(self.pending_windows.iter())
2451 .find(|w| w.toplevel().map(|t| t == toplevel).unwrap_or(false))
2452 .and_then(window_meta)
2453 .map(|meta| meta.parked.load(Ordering::Relaxed))
2454 .unwrap_or(false);
2455 let (logical_width, logical_height) = if parked {
2456 PARKED_LOGICAL_SIZE
2457 } else if let Some(size) = self.logical_size_of(output_id) {
2458 size
2459 } else {
2460 let scale = self.settings.scale.max(0.1);
2461 (
2462 (f64::from(self.settings.width) / scale).round() as i32,
2463 (f64::from(self.settings.height) / scale).round() as i32,
2464 )
2465 };
2466 toplevel.with_pending_state(|state| {
2467 state.states.set(XdgState::Fullscreen);
2468 state.states.set(XdgState::Activated);
2469 state.size = Some((logical_width, logical_height).into());
2470 });
2471 toplevel.send_configure();
2472 }
2473}
2474
2475const CLIPBOARD_IMAGE_MIMES: &[&str] = &[
2479 "image/png",
2480 "image/jpeg",
2481 "image/webp",
2482 "image/bmp",
2483 "image/svg+xml",
2484 "image/svg",
2485];
2486const CLIPBOARD_TEXT_MIMES: &[&str] = &[
2487 "text/plain;charset=utf-8",
2488 "UTF8_STRING",
2489 "text/plain",
2490 "STRING",
2491 "TEXT",
2492];
2493const CLIPBOARD_SECRET_HINTS: &[&str] = &[
2498 "x-kde-passwordManagerHint",
2499 "text/x-kde-passwordManagerHint",
2500 "application/x-kde-passwordManagerHint",
2501];
2502
2503pub(crate) fn clipboard_flavors(offered: &[String]) -> Vec<String> {
2507 let has = |want: &str| offered.iter().any(|m| m == want);
2508 match CLIPBOARD_IMAGE_MIMES.iter().find(|m| has(m)) {
2509 Some(image) => vec![*image],
2510 None => ["text/html"]
2511 .iter()
2512 .filter(|m| has(m))
2513 .chain(CLIPBOARD_TEXT_MIMES.iter().find(|m| has(m)))
2514 .copied()
2515 .collect(),
2516 }
2517 .into_iter()
2518 .chain(CLIPBOARD_SECRET_HINTS.iter().copied().filter(|m| has(m)))
2519 .map(str::to_string)
2520 .collect()
2521}
2522
2523fn read_selection(reader: std::io::PipeReader) -> Option<Vec<u8>> {
2526 use std::io::Read;
2527 use std::os::fd::AsRawFd;
2528 const CAP: usize = 64 * 1024 * 1024;
2529 const IDLE_DEADLINE: std::time::Duration = std::time::Duration::from_secs(10);
2530 let mut last_data = Instant::now();
2531 let mut buf = Vec::new();
2532 let mut chunk = [0u8; 65536];
2533 loop {
2534 let remaining = IDLE_DEADLINE.checked_sub(last_data.elapsed())?;
2535 let mut pfd = libc::pollfd {
2536 fd: reader.as_raw_fd(),
2537 events: libc::POLLIN,
2538 revents: 0,
2539 };
2540 let timeout_ms = remaining.as_millis().min(i32::MAX as u128).max(1) as i32;
2541 let ready = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
2542 if ready < 0 {
2543 if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
2544 continue;
2545 }
2546 return None;
2547 }
2548 if ready == 0 {
2549 continue;
2550 }
2551 match (&reader).read(&mut chunk) {
2552 Ok(0) => break,
2553 Ok(n) => {
2554 last_data = Instant::now();
2555 let take_n = n.min(CAP - buf.len());
2556 buf.extend_from_slice(&chunk[..take_n]);
2557 if buf.len() == CAP {
2558 break;
2559 }
2560 }
2561 Err(e)
2562 if e.kind() == std::io::ErrorKind::Interrupted
2563 || e.kind() == std::io::ErrorKind::WouldBlock =>
2564 {
2565 continue;
2566 }
2567 Err(_) => return None,
2568 }
2569 }
2570 (!buf.is_empty()).then_some(buf)
2571}
2572
2573impl SelectionHandler for AppState {
2577 type SelectionUserData = std::sync::Arc<Vec<(String, Vec<u8>)>>;
2578
2579 fn new_selection(
2588 &mut self,
2589 ty: SelectionTarget,
2590 source: Option<SelectionSource>,
2591 seat: Seat<Self>,
2592 ) {
2593 if ty != SelectionTarget::Clipboard {
2594 return;
2595 }
2596 let chosen = source.map_or_else(Vec::new, |source| clipboard_flavors(&source.mime_types()));
2597 self.current_selection_mimes.clone_from(&chosen);
2600 if crate::SHUTDOWN.load(Ordering::Relaxed) {
2601 return;
2602 }
2603 if self.clipboard_callback.is_none() {
2604 return;
2605 }
2606 self.pending_clipboard_read = Some(chosen);
2607 let _ = seat;
2608 }
2609
2610 fn send_selection(
2614 &mut self,
2615 ty: SelectionTarget,
2616 mime_type: String,
2617 fd: std::os::fd::OwnedFd,
2618 _seat: Seat<Self>,
2619 user_data: &Self::SelectionUserData,
2620 ) {
2621 if ty != SelectionTarget::Clipboard && ty != SelectionTarget::Primary {
2622 return;
2623 }
2624 let entries = user_data.clone();
2627 if entries.is_empty()
2628 && let Some(xwm) = self.xwm.wm.as_mut()
2629 {
2630 if let Err(e) = xwm.send_selection(ty, mime_type, fd) {
2631 eprintln!("[Wayland] X11 selection not read: {e:?}");
2632 }
2633 return;
2634 }
2635 let Some(index) = entries
2636 .iter()
2637 .position(|(mime, _)| *mime == mime_type)
2638 .or_else(|| {
2639 entries
2640 .iter()
2641 .position(|(mime, _)| mime.starts_with("text/plain"))
2642 .filter(|_| !mime_type.contains('/') || mime_type.starts_with("text/plain"))
2643 })
2644 else {
2645 return;
2646 };
2647 std::thread::spawn(move || {
2648 let _ = crate::wayland::wlclient::write_fd_all(
2649 &fd,
2650 &entries[index].1,
2651 crate::wayland::wlclient::IO_TIMEOUT,
2652 );
2653 });
2654 }
2655}
2656
2657impl DataDeviceHandler for AppState {
2660 fn data_device_state(&mut self) -> &mut DataDeviceState {
2661 &mut self.data_device_state
2662 }
2663}
2664impl DataControlHandler for AppState {
2667 fn data_control_state(&mut self) -> &mut DataControlState {
2668 &mut self.data_control_state
2669 }
2670}
2671impl smithay::input::tablet::TabletSeatHandler for AppState {
2674 type ToolFocus = WlSurface;
2675}
2676
2677impl ExtDataControlHandler for AppState {
2681 fn data_control_state(&mut self) -> &mut ExtDataControlState {
2682 &mut self.ext_data_control_state
2683 }
2684}
2685
2686impl WaylandDndGrabHandler for AppState {
2692 fn dnd_requested<S: Source>(
2693 &mut self,
2694 source: S,
2695 icon: Option<WlSurface>,
2696 seat: Seat<Self>,
2697 serial: Serial,
2698 type_: GrabType,
2699 ) {
2700 self.dnd_icon = icon.map(|surface| (surface, (0, 0).into()));
2701 let display = self.dh.clone();
2702 match type_ {
2703 GrabType::Pointer => {
2704 let Some(pointer) = seat.get_pointer() else {
2705 source.cancel();
2706 return;
2707 };
2708 let Some(start) = pointer.grab_start_data() else {
2709 source.cancel();
2710 return;
2711 };
2712 let grab = DnDGrab::new_pointer(&display, start, source, seat);
2713 pointer.set_grab(self, grab, serial, Focus::Keep);
2714 }
2715 GrabType::Touch => {
2716 let Some(touch) = seat.get_touch() else {
2717 source.cancel();
2718 return;
2719 };
2720 let Some(start) = touch.grab_start_data() else {
2721 source.cancel();
2722 return;
2723 };
2724 let grab = DnDGrab::new_touch(&display, start, source, seat);
2725 touch.set_grab(self, grab, serial);
2726 }
2727 }
2728 }
2729}
2730impl BufferHandler for AppState {
2732 fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
2733}
2734impl ShmHandler for AppState {
2736 fn shm_state(&self) -> &ShmState {
2737 &self.shm_state
2738 }
2739}
2740impl OutputHandler for AppState {
2744 fn output_bound(
2745 &mut self,
2746 output: Output,
2747 wl_output: smithay::reexports::wayland_server::protocol::wl_output::WlOutput,
2748 ) {
2749 crate::wayland::workspace::output_bound(self, &output, &wl_output);
2750 crate::wayland::foreign_toplevel::output_bound(self, &output, &wl_output);
2751 }
2752}
2753
2754impl DmabufHandler for AppState {
2760 fn dmabuf_state(&mut self) -> &mut DmabufState {
2761 &mut self.dmabuf_state
2762 }
2763
2764 fn dmabuf_imported(
2765 &mut self,
2766 _global: &DmabufGlobal,
2767 dmabuf: Dmabuf,
2768 notifier: ImportNotifier,
2769 ) {
2770 if let Some(renderer) = self.gles_renderer.as_mut() {
2771 if renderer.import_dmabuf(&dmabuf, None).is_ok() {
2772 let _ = notifier.successful::<AppState>();
2773 } else {
2774 notifier.failed();
2775 }
2776 } else {
2777 notifier.failed();
2778 }
2779 }
2780}
2781
2782impl ImageCaptureSourceHandler for AppState {
2783 fn source_destroyed(&mut self, _source: ImageCaptureSource) {}
2784}
2785
2786impl OutputCaptureSourceHandler for AppState {
2787 fn output_capture_source_state(&mut self) -> &mut OutputCaptureSourceState {
2788 &mut self.output_capture_source_state
2789 }
2790
2791 fn output_source_created(&mut self, source: ImageCaptureSource, output: &Output) {
2792 source.user_data().insert_if_missing(|| output.downgrade());
2793 }
2794}
2795
2796impl ImageCopyCaptureHandler for AppState {
2802 fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
2803 &mut self.image_copy_capture_state
2804 }
2805
2806 fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option<BufferConstraints> {
2807 let output = source.user_data().get::<WeakOutput>()?.upgrade()?;
2808 output_capture_constraints(&output, self.gles_renderer.as_ref(), &self.render_node_path)
2809 }
2810
2811 fn new_session(&mut self, session: CopySession) {
2812 let Some(output) = session.source().user_data().get::<WeakOutput>().cloned() else {
2813 session.stop();
2814 return;
2815 };
2816 match self.capture_constraints(&session.source()) {
2817 Some(constraints) => {
2818 session.update_constraints(constraints);
2819 self.copy_sessions.push(OutputCopySession {
2820 session,
2821 output,
2822 pending: None,
2823 delivered_once: false,
2824 });
2825 }
2826 None => session.stop(),
2827 }
2828 }
2829
2830 fn frame(&mut self, session: &CopySessionRef, frame: CopyFrame) {
2831 match self
2832 .copy_sessions
2833 .iter_mut()
2834 .find(|cs| cs.session == *session)
2835 {
2836 Some(cs) => cs.pending = Some(frame),
2837 None => frame.fail(CaptureFailureReason::Unknown),
2838 }
2839 }
2840
2841 fn frame_aborted(&mut self, frame: CopyFrameRef) {
2842 for cs in self.copy_sessions.iter_mut() {
2843 if cs.pending.as_ref().is_some_and(|f| *f == frame) {
2844 cs.pending = None;
2845 }
2846 }
2847 }
2848
2849 fn session_destroyed(&mut self, session: CopySessionRef) {
2850 self.copy_sessions.retain(|cs| cs.session != session);
2851 }
2852}
2853
2854impl AppState {
2855 pub fn copy_frame_pending_for(&self, output: &Output) -> bool {
2856 self.copy_sessions
2857 .iter()
2858 .any(|cs| cs.pending.is_some() && cs.output.upgrade().as_ref() == Some(output))
2859 }
2860}
2861
2862pub fn output_capture_constraints(
2866 output: &Output,
2867 renderer: Option<&GlesRenderer>,
2868 render_node_path: &str,
2869) -> Option<BufferConstraints> {
2870 let mode = output.current_mode()?;
2871 let dma = renderer.and_then(|renderer| {
2872 let node = DrmNode::from_path(render_node_path).ok()?;
2873 let mut formats: Vec<(Fourcc, Vec<Modifier>)> = Vec::new();
2874 for f in Bind::<Dmabuf>::supported_formats(renderer)? {
2875 match formats.iter_mut().find(|(code, _)| *code == f.code) {
2876 Some((_, mods)) => mods.push(f.modifier),
2877 None => formats.push((f.code, vec![f.modifier])),
2878 }
2879 }
2880 (!formats.is_empty()).then_some(DmabufConstraints { node, formats })
2881 });
2882 Some(BufferConstraints {
2883 size: (mode.size.w, mode.size.h).into(),
2884 shm: vec![wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888],
2885 dma,
2886 })
2887}
2888
2889impl FractionalScaleHandler for AppState {
2892 fn new_fractional_scale(
2893 &mut self,
2894 surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
2895 ) {
2896 if let Some(output) = self.primary_output() {
2897 let scale = output.current_scale().fractional_scale();
2898 with_states(&surface, |states| {
2899 smithay::wayland::compositor::send_surface_state(
2900 &surface,
2901 states,
2902 scale.ceil() as i32,
2903 smithay::utils::Transform::Normal,
2904 );
2905 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
2906 fs.set_preferred_scale(scale);
2907 });
2908 });
2909 }
2910 }
2911}
2912
2913#[derive(Debug, Clone, PartialEq)]
2917#[allow(clippy::large_enum_variant)]
2918pub enum FocusTarget {
2919 Window(Window),
2920 Popup(PopupKind),
2921 LayerSurface(DesktopLayerSurface),
2922 Subsurface(WlSurface, Box<FocusTarget>),
2927}
2928
2929impl FocusTarget {
2930 pub(crate) fn owner(&self) -> &FocusTarget {
2932 match self {
2933 FocusTarget::Subsurface(_, owner) => owner.owner(),
2934 target => target,
2935 }
2936 }
2937
2938 fn under_point(
2943 self,
2944 origin: Point<f64, Logical>,
2945 p: Point<f64, Logical>,
2946 ) -> (FocusTarget, Point<f64, Logical>) {
2947 let Some(root) = self.wl_surface().map(Cow::into_owned) else {
2948 return (self, origin);
2949 };
2950 match under_from_surface_tree(&root, p, origin.to_i32_round(), WindowSurfaceType::ALL) {
2951 Some((surface, at)) if surface != root => (
2952 FocusTarget::Subsurface(surface, Box::new(self)),
2953 at.to_f64(),
2954 ),
2955 _ => (self, origin),
2956 }
2957 }
2958}
2959
2960fn popup_under(
2963 root: &WlSurface,
2964 origin: Point<i32, Logical>,
2965 p: Point<f64, Logical>,
2966) -> Option<(FocusTarget, Point<f64, Logical>)> {
2967 PopupManager::popups_for_surface(root).find_map(|(popup, location)| {
2968 let at = origin + location - popup.geometry().loc;
2969 under_from_surface_tree(popup.wl_surface(), p, at, WindowSurfaceType::ALL)
2970 .map(|_| (FocusTarget::Popup(popup), at.to_f64()))
2971 })
2972}
2973
2974impl From<Window> for FocusTarget {
2976 fn from(w: Window) -> Self {
2977 FocusTarget::Window(w)
2978 }
2979}
2980
2981impl From<PopupKind> for FocusTarget {
2983 fn from(p: PopupKind) -> Self {
2984 FocusTarget::Popup(p)
2985 }
2986}
2987
2988impl From<DesktopLayerSurface> for FocusTarget {
2990 fn from(l: DesktopLayerSurface) -> Self {
2991 FocusTarget::LayerSurface(l)
2992 }
2993}
2994
2995impl IsAlive for FocusTarget {
2998 fn alive(&self) -> bool {
2999 match self {
3000 FocusTarget::Window(w) => w.alive(),
3001 FocusTarget::Popup(p) => p.alive(),
3002 FocusTarget::LayerSurface(l) => l.alive(),
3003 FocusTarget::Subsurface(surface, owner) => surface.alive() && owner.alive(),
3004 }
3005 }
3006}
3007
3008impl WaylandFocus for FocusTarget {
3011 fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
3012 match self {
3013 FocusTarget::Window(w) => w.wl_surface(),
3014 FocusTarget::Popup(p) => Some(Cow::Borrowed(p.wl_surface())),
3015 FocusTarget::LayerSurface(l) => Some(Cow::Borrowed(l.wl_surface())),
3016 FocusTarget::Subsurface(surface, _) => Some(Cow::Borrowed(surface)),
3017 }
3018 }
3019 fn same_client_as(&self, object_id: &ObjectId) -> bool {
3020 match self {
3021 FocusTarget::Window(w) => w.same_client_as(object_id),
3022 FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
3023 FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
3024 FocusTarget::Subsurface(surface, _) => surface.id().same_client_as(object_id),
3025 }
3026 }
3027}
3028
3029impl KeyboardTarget<AppState> for FocusTarget {
3034 fn enter(
3035 &self,
3036 seat: &Seat<AppState>,
3037 data: &mut AppState,
3038 keys: Vec<KeysymHandle<'_>>,
3039 serial: Serial,
3040 ) {
3041 if let Some(surface) = self.owner().wl_surface() {
3042 smithay::input::keyboard::KeyboardTarget::enter(
3043 surface.as_ref(),
3044 seat,
3045 data,
3046 keys,
3047 serial,
3048 );
3049 }
3050 }
3051 fn leave(&self, seat: &Seat<AppState>, data: &mut AppState, serial: Serial) {
3052 if let Some(surface) = self.owner().wl_surface() {
3053 smithay::input::keyboard::KeyboardTarget::leave(surface.as_ref(), seat, data, serial);
3054 }
3055 }
3056 fn key(
3057 &self,
3058 seat: &Seat<AppState>,
3059 data: &mut AppState,
3060 key: KeysymHandle<'_>,
3061 state: smithay::backend::input::KeyState,
3062 serial: Serial,
3063 time: InputTime,
3064 ) {
3065 if let Some(surface) = self.owner().wl_surface() {
3066 smithay::input::keyboard::KeyboardTarget::key(
3067 surface.as_ref(),
3068 seat,
3069 data,
3070 key,
3071 state,
3072 serial,
3073 time,
3074 );
3075 }
3076 }
3077 fn modifiers(
3078 &self,
3079 seat: &Seat<AppState>,
3080 data: &mut AppState,
3081 modifiers: ModifiersState,
3082 serial: Serial,
3083 ) {
3084 if let Some(surface) = self.owner().wl_surface() {
3085 smithay::input::keyboard::KeyboardTarget::modifiers(
3086 surface.as_ref(),
3087 seat,
3088 data,
3089 modifiers,
3090 serial,
3091 );
3092 }
3093 }
3094}
3095
3096impl DndFocus<AppState> for FocusTarget {
3099 type OfferData<S: Source> = <WlSurface as DndFocus<AppState>>::OfferData<S>;
3100
3101 fn enter<S: Source>(
3102 &self,
3103 data: &mut AppState,
3104 dh: &DisplayHandle,
3105 source: Arc<S>,
3106 seat: &Seat<AppState>,
3107 location: Point<f64, Logical>,
3108 serial: &Serial,
3109 ) -> Option<Self::OfferData<S>> {
3110 if let Some(surface) = self.wl_surface() {
3111 <WlSurface as DndFocus<AppState>>::enter(
3112 surface.as_ref(),
3113 data,
3114 dh,
3115 source,
3116 seat,
3117 location,
3118 serial,
3119 )
3120 } else {
3121 None
3122 }
3123 }
3124
3125 fn motion<S: Source>(
3126 &self,
3127 data: &mut AppState,
3128 offer: Option<&mut Self::OfferData<S>>,
3129 seat: &Seat<AppState>,
3130 location: Point<f64, Logical>,
3131 time: InputTime,
3132 ) {
3133 if let Some(surface) = self.wl_surface() {
3134 <WlSurface as DndFocus<AppState>>::motion(
3135 surface.as_ref(),
3136 data,
3137 offer,
3138 seat,
3139 location,
3140 time,
3141 )
3142 }
3143 }
3144
3145 fn leave<S: Source>(
3146 &self,
3147 data: &mut AppState,
3148 offer: Option<&mut Self::OfferData<S>>,
3149 seat: &Seat<AppState>,
3150 ) {
3151 if let Some(surface) = self.wl_surface() {
3152 <WlSurface as DndFocus<AppState>>::leave(surface.as_ref(), data, offer, seat)
3153 }
3154 }
3155
3156 fn drop<S: Source>(
3157 &self,
3158 data: &mut AppState,
3159 offer: Option<&mut Self::OfferData<S>>,
3160 seat: &Seat<AppState>,
3161 ) {
3162 if let Some(surface) = self.wl_surface() {
3163 <WlSurface as DndFocus<AppState>>::drop(surface.as_ref(), data, offer, seat)
3164 }
3165 }
3166}
3167
3168impl PointerTarget<AppState> for FocusTarget {
3171 fn enter(&self, seat: &Seat<AppState>, data: &mut AppState, event: &MotionEvent) {
3172 if let Some(surface) = self.wl_surface() {
3173 smithay::input::pointer::PointerTarget::enter(surface.as_ref(), seat, data, event);
3174 }
3175 }
3176 fn motion(&self, seat: &Seat<AppState>, data: &mut AppState, event: &MotionEvent) {
3177 if let Some(surface) = self.wl_surface() {
3178 smithay::input::pointer::PointerTarget::motion(surface.as_ref(), seat, data, event);
3179 }
3180 }
3181 fn relative_motion(
3182 &self,
3183 seat: &Seat<AppState>,
3184 data: &mut AppState,
3185 event: &RelativeMotionEvent,
3186 ) {
3187 if let Some(surface) = self.wl_surface() {
3188 smithay::input::pointer::PointerTarget::relative_motion(
3189 surface.as_ref(),
3190 seat,
3191 data,
3192 event,
3193 );
3194 }
3195 }
3196 fn button(&self, seat: &Seat<AppState>, data: &mut AppState, event: &ButtonEvent) {
3197 if let Some(surface) = self.wl_surface() {
3198 smithay::input::pointer::PointerTarget::button(surface.as_ref(), seat, data, event);
3199 }
3200 }
3201 fn axis(&self, seat: &Seat<AppState>, data: &mut AppState, frame: AxisFrame) {
3202 if let Some(surface) = self.wl_surface() {
3203 smithay::input::pointer::PointerTarget::axis(surface.as_ref(), seat, data, frame);
3204 }
3205 }
3206 fn frame(&self, seat: &Seat<AppState>, data: &mut AppState) {
3207 if let Some(surface) = self.wl_surface() {
3208 smithay::input::pointer::PointerTarget::frame(surface.as_ref(), seat, data);
3209 }
3210 }
3211 fn leave(&self, seat: &Seat<AppState>, data: &mut AppState, serial: Serial, time: InputTime) {
3212 if let Some(surface) = self.wl_surface() {
3213 smithay::input::pointer::PointerTarget::leave(
3214 surface.as_ref(),
3215 seat,
3216 data,
3217 serial,
3218 time,
3219 );
3220 }
3221 }
3222 fn gesture_swipe_begin(
3223 &self,
3224 seat: &Seat<AppState>,
3225 data: &mut AppState,
3226 event: &GestureSwipeBeginEvent,
3227 ) {
3228 if let Some(surface) = self.wl_surface() {
3229 smithay::input::pointer::PointerTarget::gesture_swipe_begin(
3230 surface.as_ref(),
3231 seat,
3232 data,
3233 event,
3234 );
3235 }
3236 }
3237 fn gesture_swipe_update(
3238 &self,
3239 seat: &Seat<AppState>,
3240 data: &mut AppState,
3241 event: &GestureSwipeUpdateEvent,
3242 ) {
3243 if let Some(surface) = self.wl_surface() {
3244 smithay::input::pointer::PointerTarget::gesture_swipe_update(
3245 surface.as_ref(),
3246 seat,
3247 data,
3248 event,
3249 );
3250 }
3251 }
3252 fn gesture_swipe_end(
3253 &self,
3254 seat: &Seat<AppState>,
3255 data: &mut AppState,
3256 event: &GestureSwipeEndEvent,
3257 ) {
3258 if let Some(surface) = self.wl_surface() {
3259 smithay::input::pointer::PointerTarget::gesture_swipe_end(
3260 surface.as_ref(),
3261 seat,
3262 data,
3263 event,
3264 );
3265 }
3266 }
3267 fn gesture_pinch_begin(
3268 &self,
3269 seat: &Seat<AppState>,
3270 data: &mut AppState,
3271 event: &GesturePinchBeginEvent,
3272 ) {
3273 if let Some(surface) = self.wl_surface() {
3274 smithay::input::pointer::PointerTarget::gesture_pinch_begin(
3275 surface.as_ref(),
3276 seat,
3277 data,
3278 event,
3279 );
3280 }
3281 }
3282 fn gesture_pinch_update(
3283 &self,
3284 seat: &Seat<AppState>,
3285 data: &mut AppState,
3286 event: &GesturePinchUpdateEvent,
3287 ) {
3288 if let Some(surface) = self.wl_surface() {
3289 smithay::input::pointer::PointerTarget::gesture_pinch_update(
3290 surface.as_ref(),
3291 seat,
3292 data,
3293 event,
3294 );
3295 }
3296 }
3297 fn gesture_pinch_end(
3298 &self,
3299 seat: &Seat<AppState>,
3300 data: &mut AppState,
3301 event: &GesturePinchEndEvent,
3302 ) {
3303 if let Some(surface) = self.wl_surface() {
3304 smithay::input::pointer::PointerTarget::gesture_pinch_end(
3305 surface.as_ref(),
3306 seat,
3307 data,
3308 event,
3309 );
3310 }
3311 }
3312 fn gesture_hold_begin(
3313 &self,
3314 seat: &Seat<AppState>,
3315 data: &mut AppState,
3316 event: &GestureHoldBeginEvent,
3317 ) {
3318 if let Some(surface) = self.wl_surface() {
3319 smithay::input::pointer::PointerTarget::gesture_hold_begin(
3320 surface.as_ref(),
3321 seat,
3322 data,
3323 event,
3324 );
3325 }
3326 }
3327 fn gesture_hold_end(
3328 &self,
3329 seat: &Seat<AppState>,
3330 data: &mut AppState,
3331 event: &GestureHoldEndEvent,
3332 ) {
3333 if let Some(surface) = self.wl_surface() {
3334 smithay::input::pointer::PointerTarget::gesture_hold_end(
3335 surface.as_ref(),
3336 seat,
3337 data,
3338 event,
3339 );
3340 }
3341 }
3342}
3343
3344impl TouchTarget<AppState> for FocusTarget {
3347 fn down(&self, seat: &Seat<AppState>, data: &mut AppState, event: &DownEvent) {
3348 if let Some(surface) = self.wl_surface() {
3349 smithay::input::touch::TouchTarget::down(surface.as_ref(), seat, data, event);
3350 }
3351 }
3352 fn up(&self, seat: &Seat<AppState>, data: &mut AppState, event: &UpEvent) {
3353 if let Some(surface) = self.wl_surface() {
3354 smithay::input::touch::TouchTarget::up(surface.as_ref(), seat, data, event);
3355 }
3356 }
3357 fn motion(
3358 &self,
3359 seat: &Seat<AppState>,
3360 data: &mut AppState,
3361 event: &smithay::input::touch::MotionEvent,
3362 ) {
3363 if let Some(surface) = self.wl_surface() {
3364 smithay::input::touch::TouchTarget::motion(surface.as_ref(), seat, data, event);
3365 }
3366 }
3367 fn frame(&self, seat: &Seat<AppState>, data: &mut AppState, marker: FrameMarker) {
3368 if let Some(surface) = self.wl_surface() {
3369 smithay::input::touch::TouchTarget::frame(surface.as_ref(), seat, data, marker);
3370 }
3371 }
3372 fn cancel(&self, seat: &Seat<AppState>, data: &mut AppState, marker: FrameMarker) {
3373 if let Some(surface) = self.wl_surface() {
3374 smithay::input::touch::TouchTarget::cancel(surface.as_ref(), seat, data, marker);
3375 }
3376 }
3377 fn shape(&self, seat: &Seat<AppState>, data: &mut AppState, event: &ShapeEvent) {
3378 if let Some(surface) = self.wl_surface() {
3379 smithay::input::touch::TouchTarget::shape(surface.as_ref(), seat, data, event);
3380 }
3381 }
3382 fn orientation(&self, seat: &Seat<AppState>, data: &mut AppState, event: &OrientationEvent) {
3383 if let Some(surface) = self.wl_surface() {
3384 smithay::input::touch::TouchTarget::orientation(surface.as_ref(), seat, data, event);
3385 }
3386 }
3387 fn last_frame(&self, seat: &Seat<AppState>, data: &mut AppState) -> Option<FrameMarker> {
3388 self.wl_surface().and_then(|surface| {
3389 smithay::input::touch::TouchTarget::last_frame(surface.as_ref(), seat, data)
3390 })
3391 }
3392}
3393
3394pub fn cursor_icon_to_str(icon: &CursorIcon) -> &'static str {
3398 match icon {
3399 CursorIcon::Default => "default",
3400 CursorIcon::ContextMenu => "context-menu",
3401 CursorIcon::Help => "help",
3402 CursorIcon::Pointer => "pointer",
3403 CursorIcon::Progress => "progress",
3404 CursorIcon::Wait => "wait",
3405 CursorIcon::Cell => "cell",
3406 CursorIcon::Crosshair => "crosshair",
3407 CursorIcon::Text => "text",
3408 CursorIcon::VerticalText => "vertical-text",
3409 CursorIcon::Alias => "alias",
3410 CursorIcon::Copy => "copy",
3411 CursorIcon::Move => "move",
3412 CursorIcon::NoDrop => "no-drop",
3413 CursorIcon::NotAllowed => "not-allowed",
3414 CursorIcon::Grab => "grab",
3415 CursorIcon::Grabbing => "grabbing",
3416 CursorIcon::AllScroll => "all-scroll",
3417 CursorIcon::ColResize => "col-resize",
3418 CursorIcon::RowResize => "row-resize",
3419 CursorIcon::NResize => "n-resize",
3420 CursorIcon::EResize => "e-resize",
3421 CursorIcon::SResize => "s-resize",
3422 CursorIcon::WResize => "w-resize",
3423 CursorIcon::NeResize => "ne-resize",
3424 CursorIcon::NwResize => "nw-resize",
3425 CursorIcon::SeResize => "se-resize",
3426 CursorIcon::SwResize => "sw-resize",
3427 CursorIcon::EwResize => "ew-resize",
3428 CursorIcon::NsResize => "ns-resize",
3429 CursorIcon::NeswResize => "nesw-resize",
3430 CursorIcon::NwseResize => "nwse-resize",
3431 CursorIcon::ZoomIn => "zoom-in",
3432 CursorIcon::ZoomOut => "zoom-out",
3433 _ => "default",
3434 }
3435}
3436
3437impl SeatHandler for AppState {
3440 type KeyboardFocus = FocusTarget;
3441 type PointerFocus = FocusTarget;
3442 type TouchFocus = FocusTarget;
3443 fn seat_state(&mut self) -> &mut SeatState<AppState> {
3444 &mut self.seat_state
3445 }
3446
3447 fn cursor_image(&mut self, _seat: &Seat<AppState>, image: CursorImageStatus) {
3451 self.current_cursor_icon = Some(image.clone());
3452 self.cursor_surface_pending = matches!(image, CursorImageStatus::Surface(_));
3453 if !self.cursor_surface_pending {
3454 self.send_cursor_image(&image);
3455 }
3456 }
3457
3458 fn focus_changed(&mut self, seat: &Seat<AppState>, focus: Option<&Self::KeyboardFocus>) {
3463 let dh = &self.dh;
3464 let client = focus
3465 .and_then(|t| t.wl_surface())
3466 .and_then(|s| dh.get_client(s.id()).ok());
3467 set_data_device_focus(dh, seat, client.clone());
3468 set_primary_focus(dh, seat, client);
3469 self.keyboard_focus = match focus {
3470 Some(FocusTarget::Window(window)) => window_meta(window).map(|m| m.id),
3471 _ => None,
3472 };
3473 crate::wayland::foreign_toplevel::update_all(self);
3474 crate::wayland::xwm::publish_stacking(self);
3475 }
3476}
3477
3478impl PointerWarpHandler for AppState {
3485 fn warp_pointer(
3486 &mut self,
3487 surface: WlSurface,
3488 _pointer: WlPointer,
3489 pos: Point<f64, Logical>,
3490 serial: Serial,
3491 ) {
3492 let surface_origin = self.space.elements().find_map(|window| {
3493 if window.wl_surface().as_deref() == Some(&surface) {
3494 self.space.element_location(window)
3495 } else {
3496 None
3497 }
3498 });
3499
3500 if let Some(origin) = surface_origin {
3501 let global_pos = origin.to_f64() + pos;
3502 let time = wayland_time();
3503
3504 if let Some(pointer) = self.seat.get_pointer() {
3505 let under = self
3506 .space
3507 .element_under(global_pos)
3508 .map(|(w, loc)| (FocusTarget::Window(w.clone()), loc.to_f64()));
3509
3510 pointer.motion(
3511 self,
3512 under,
3513 &MotionEvent {
3514 location: global_pos,
3515 serial,
3516 time,
3517 },
3518 );
3519 }
3520 }
3521 }
3522}
3523
3524impl XdgShellHandler for AppState {
3526 fn xdg_shell_state(&mut self) -> &mut XdgShellState {
3527 &mut self.shell_state
3528 }
3529 fn new_toplevel(&mut self, surface: ToplevelSurface) {
3535 let target_id = self.pointer_display();
3536 let window = Window::new_wayland_window(surface.clone());
3537 window
3538 .user_data()
3539 .insert_if_missing_threadsafe(|| WindowMeta {
3540 id: NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed),
3541 output: AtomicU32::new(target_id),
3542 placed: AtomicBool::new(false),
3543 parked: AtomicBool::new(false),
3544 });
3545 self.pending_windows.push(window);
3546 smithay::wayland::compositor::add_pre_commit_hook::<AppState, _>(
3549 surface.wl_surface(),
3550 |state, _, surface| {
3551 let removing = with_states(surface, |states| {
3552 matches!(
3553 states
3554 .cached_state
3555 .get::<smithay::wayland::compositor::SurfaceAttributes>()
3556 .pending()
3557 .buffer,
3558 Some(smithay::wayland::compositor::BufferAssignment::Removed)
3559 )
3560 });
3561 if !removing {
3562 return;
3563 }
3564 let window = state
3565 .space
3566 .elements()
3567 .find(|w| w.toplevel().is_some_and(|t| t.wl_surface() == surface))
3568 .cloned();
3569 if let Some(window) = window {
3570 crate::wayland::wm::snapshot(state, &window);
3571 }
3572 },
3573 );
3574 let (title, app_id) = with_states(surface.wl_surface(), |states| {
3575 let attributes = states
3576 .data_map
3577 .get::<XdgToplevelSurfaceData>()
3578 .unwrap()
3579 .lock()
3580 .unwrap();
3581 (attributes.title.clone(), attributes.app_id.clone())
3582 });
3583
3584 let handle = self
3585 .foreign_toplevel_list
3586 .new_toplevel::<AppState>(title.unwrap_or_default(), app_id.unwrap_or_default());
3587
3588 with_states(surface.wl_surface(), |states| {
3589 states.data_map.insert_if_missing(|| handle)
3590 });
3591 }
3592 fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
3596 if let Err(err) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
3597 eprintln!("Failed to track popup: {:?}", err);
3598 }
3599 let _ = surface.send_configure();
3600 }
3601 fn grab(
3604 &mut self,
3605 surface: PopupSurface,
3606 _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3607 serial: Serial,
3608 ) {
3609 let kind = PopupKind::Xdg(surface);
3610 let Ok(root_surface) = smithay::desktop::find_popup_root_surface(&kind) else {
3611 return;
3612 };
3613 let root = self
3614 .space
3615 .elements()
3616 .find(|w| w.wl_surface().as_deref() == Some(&root_surface))
3617 .cloned()
3618 .map(FocusTarget::Window)
3619 .or_else(|| {
3620 self.output_nodes.iter().find_map(|node| {
3621 layer_map_for_output(&node.output)
3622 .layer_for_surface(&root_surface, WindowSurfaceType::TOPLEVEL)
3623 .cloned()
3624 .map(FocusTarget::LayerSurface)
3625 })
3626 });
3627 if let Some(root) = root {
3628 let _ = self.popups.grab_popup(root, kind, &self.seat, serial);
3629 }
3630 }
3631 fn reposition_request(
3635 &mut self,
3636 surface: PopupSurface,
3637 _positioner: PositionerState,
3638 token: u32,
3639 ) {
3640 if let Err(err) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
3641 eprintln!("Failed to track popup: {:?}", err);
3642 }
3643 let _ = surface.send_repositioned(token);
3644 }
3645 fn fullscreen_request(
3650 &mut self,
3651 surface: ToplevelSurface,
3652 output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
3653 ) {
3654 if crate::wayland::wm::active(self) {
3655 let output_id = output.as_ref().and_then(|wlo| {
3656 self.output_nodes
3657 .iter()
3658 .find(|n| n.output.owns(wlo))
3659 .map(|n| n.id)
3660 });
3661 if let Some(id) = self.id_of_toplevel(&surface) {
3662 crate::wayland::wm::request(self, id, crate::wm::Request::Fullscreen(output_id));
3663 }
3664 return;
3665 }
3666 if let Some(id) = output.as_ref().and_then(|wlo| {
3667 self.output_nodes
3668 .iter()
3669 .find(|n| n.output.owns(wlo))
3670 .map(|n| n.id)
3671 }) {
3672 let mapped = self
3673 .space
3674 .elements()
3675 .find(|w| w.toplevel().map(|tl| *tl == surface).unwrap_or(false))
3676 .cloned();
3677 if let Some(window) = mapped {
3678 if self.place_window_on_output(&window, id) {
3679 return;
3680 }
3681 } else if let Some(meta) = self
3682 .pending_windows
3683 .iter()
3684 .find(|w| w.toplevel().map(|tl| *tl == surface).unwrap_or(false))
3685 .and_then(window_meta)
3686 {
3687 meta.output.store(id, Ordering::Relaxed);
3691 meta.placed.store(true, Ordering::Relaxed);
3692 }
3693 }
3694 self.send_forced_fullscreen_configure(&surface);
3695 }
3696 fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
3700 if let Some(id) = self.id_of_toplevel(&surface)
3701 && crate::wayland::wm::request(self, id, crate::wm::Request::Unfullscreen)
3702 {
3703 return;
3704 }
3705 self.send_forced_fullscreen_configure(&surface);
3706 }
3707 fn maximize_request(&mut self, surface: ToplevelSurface) {
3710 if let Some(id) = self.id_of_toplevel(&surface)
3711 && crate::wayland::wm::request(self, id, crate::wm::Request::Maximize)
3712 {
3713 return;
3714 }
3715 self.send_forced_fullscreen_configure(&surface);
3716 }
3717 fn unmaximize_request(&mut self, surface: ToplevelSurface) {
3719 if let Some(id) = self.id_of_toplevel(&surface)
3720 && crate::wayland::wm::request(self, id, crate::wm::Request::Unmaximize)
3721 {
3722 return;
3723 }
3724 self.send_forced_fullscreen_configure(&surface);
3725 }
3726 fn minimize_request(&mut self, surface: ToplevelSurface) {
3728 if let Some(id) = self.id_of_toplevel(&surface) {
3729 crate::wayland::wm::request(self, id, crate::wm::Request::Minimize);
3730 }
3731 }
3732 fn move_request(
3734 &mut self,
3735 surface: ToplevelSurface,
3736 _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3737 _serial: Serial,
3738 ) {
3739 if let Some(id) = self.id_of_toplevel(&surface) {
3740 crate::wayland::wm::request(self, id, crate::wm::Request::Move);
3741 }
3742 }
3743 fn resize_request(
3745 &mut self,
3746 surface: ToplevelSurface,
3747 _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3748 _serial: Serial,
3749 edges: smithay::reexports::wayland_protocols::xdg::shell::server::xdg_toplevel::ResizeEdge,
3750 ) {
3751 if let Some(id) = self.id_of_toplevel(&surface) {
3752 let edge = crate::wm::ResizeEdge::from_xdg(edges as u32);
3753 crate::wayland::wm::request(self, id, crate::wm::Request::Resize(edge));
3754 }
3755 }
3756 fn show_window_menu(
3758 &mut self,
3759 surface: ToplevelSurface,
3760 _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
3761 _serial: Serial,
3762 location: Point<i32, Logical>,
3763 ) {
3764 if let Some(id) = self.id_of_toplevel(&surface) {
3765 crate::wayland::wm::request(
3766 self,
3767 id,
3768 crate::wm::Request::ShowMenu {
3769 x: location.x,
3770 y: location.y,
3771 },
3772 );
3773 }
3774 }
3775 fn title_changed(&mut self, surface: ToplevelSurface) {
3777 if let Some(window) = self.window_of_toplevel(&surface) {
3778 crate::wayland::wm::window_changed(self, &window);
3779 if let Some(id) = window_meta(&window).map(|m| m.id) {
3780 crate::wayland::foreign_toplevel::update(self, id);
3781 }
3782 }
3783 }
3784 fn app_id_changed(&mut self, surface: ToplevelSurface) {
3786 if let Some(window) = self.window_of_toplevel(&surface) {
3787 crate::wayland::wm::window_changed(self, &window);
3788 if let Some(id) = window_meta(&window).map(|m| m.id) {
3789 crate::wayland::foreign_toplevel::update(self, id);
3790 }
3791 }
3792 }
3793 fn parent_changed(&mut self, surface: ToplevelSurface) {
3795 if let Some(window) = self.window_of_toplevel(&surface) {
3796 crate::wayland::wm::window_changed(self, &window);
3797 }
3798 }
3799 fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
3805 let id = self
3808 .wm
3809 .as_ref()
3810 .and_then(|wm| {
3811 wm.hidden
3812 .iter()
3813 .find(|(_, w)| w.toplevel().is_some_and(|t| *t == surface))
3814 .map(|(id, _)| *id)
3815 })
3816 .or_else(|| {
3817 self.space
3818 .elements()
3819 .chain(self.pending_windows.iter())
3820 .find(|w| w.toplevel().is_some_and(|t| *t == surface))
3821 .and_then(|w| window_meta(w).map(|m| m.id))
3822 });
3823 if let Some(idx) = self
3824 .pending_windows
3825 .iter()
3826 .position(|w| w.toplevel().map(|t| *t == surface).unwrap_or(false))
3827 {
3828 self.pending_windows.remove(idx);
3829 }
3830 let mapped = self
3831 .space
3832 .elements()
3833 .find(|w| w.toplevel().map(|t| *t == surface).unwrap_or(false))
3834 .cloned();
3835 if let Some(window) = mapped {
3836 crate::wayland::wm::snapshot(self, &window);
3837 self.space.unmap_elem(&window);
3838 self.refocus_pointer();
3839 }
3840 if let Some(handle) = with_states(surface.wl_surface(), |states| {
3841 states.data_map.get::<ForeignToplevelHandle>().cloned()
3842 }) {
3843 self.foreign_toplevel_list.remove_toplevel(&handle);
3844 }
3845 if let Some(id) = id {
3846 crate::wayland::foreign_toplevel::remove(self, id);
3847 crate::wayland::wm::window_removed(self, id);
3848 }
3849 }
3850}
3851
3852pub struct PfVirtualKeyboard {
3863 inner: Mutex<PfVkState>,
3864}
3865
3866#[derive(Default)]
3867struct PfVkState {
3868 syms: Option<std::collections::HashMap<u32, u32>>,
3870 pressed: std::collections::HashMap<u32, u32>,
3873}
3874
3875impl GlobalDispatch<ZwpVirtualKeyboardManagerV1, ()> for AppState {
3876 fn bind(
3877 _state: &mut Self,
3878 _dh: &DisplayHandle,
3879 _client: &Client,
3880 resource: New<ZwpVirtualKeyboardManagerV1>,
3881 _global_data: &(),
3882 data_init: &mut DataInit<'_, Self>,
3883 ) {
3884 data_init.init(resource, ());
3885 }
3886}
3887
3888impl Dispatch<ZwpVirtualKeyboardManagerV1, ()> for AppState {
3889 fn request(
3890 _state: &mut Self,
3891 _client: &Client,
3892 _resource: &ZwpVirtualKeyboardManagerV1,
3893 request: zwp_virtual_keyboard_manager_v1::Request,
3894 _data: &(),
3895 _dh: &DisplayHandle,
3896 data_init: &mut DataInit<'_, Self>,
3897 ) {
3898 if let zwp_virtual_keyboard_manager_v1::Request::CreateVirtualKeyboard { seat: _, id } =
3899 request
3900 {
3901 data_init.init(
3902 id,
3903 PfVirtualKeyboard {
3904 inner: Mutex::new(PfVkState::default()),
3905 },
3906 );
3907 }
3908 }
3909}
3910
3911impl Dispatch<ZwpVirtualKeyboardV1, PfVirtualKeyboard> for AppState {
3912 fn request(
3913 state: &mut Self,
3914 _client: &Client,
3915 resource: &ZwpVirtualKeyboardV1,
3916 request: zwp_virtual_keyboard_v1::Request,
3917 data: &PfVirtualKeyboard,
3918 _dh: &DisplayHandle,
3919 _data_init: &mut DataInit<'_, Self>,
3920 ) {
3921 use smithay::input::keyboard::xkb;
3922 match request {
3923 zwp_virtual_keyboard_v1::Request::Keymap { format, fd, size } => {
3924 if format != 1 {
3925 return;
3926 }
3927 const MAX_KEYMAP_BYTES: u64 = 4 << 20;
3930 let file = std::fs::File::from(fd);
3931 let length = file.metadata().map(|m| m.len()).unwrap_or(0);
3932 if size == 0 || u64::from(size) > length || u64::from(size) > MAX_KEYMAP_BYTES {
3933 eprintln!(
3934 "[Wayland] virtual-keyboard keymap of {size} bytes in a {length}-byte file; ignoring."
3935 );
3936 return;
3937 }
3938 let fd = std::os::fd::OwnedFd::from(file);
3939 let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
3940 let keymap = unsafe {
3941 xkb::Keymap::new_from_fd(
3942 &ctx,
3943 fd,
3944 size as usize,
3945 xkb::KEYMAP_FORMAT_TEXT_V1,
3946 xkb::KEYMAP_COMPILE_NO_FLAGS,
3947 )
3948 };
3949 let Ok(Some(keymap)) = keymap else {
3950 eprintln!("[Wayland] virtual-keyboard keymap failed to compile; ignoring.");
3951 return;
3952 };
3953 let syms = crate::wayland::keymap::level0_syms(&keymap);
3954 let missing: Vec<u32> = syms
3957 .values()
3958 .copied()
3959 .filter(|&s| !state.keymap_policy.resolves_plain(s))
3960 .collect();
3961 if !missing.is_empty() {
3962 let _ = state.bind_keysyms_plain(&missing);
3963 }
3964 data.inner.lock().unwrap().syms = Some(syms);
3965 }
3966 zwp_virtual_keyboard_v1::Request::Key {
3967 time: _,
3968 key,
3969 state: key_state,
3970 } => {
3971 let mut vk = data.inner.lock().unwrap();
3972 if vk.syms.is_none() {
3973 drop(vk);
3974 resource.post_error(
3975 zwp_virtual_keyboard_v1::Error::NoKeymap,
3976 "`key` sent before keymap.",
3977 );
3978 return;
3979 }
3980 let vk_kc = key.wrapping_add(8);
3981 let seat_kc = if key_state == 1 {
3982 let sym = vk.syms.as_ref().and_then(|m| m.get(&vk_kc)).copied();
3983 let kc = match sym {
3987 Some(sym) => {
3988 let bound = state.bind_keysyms_plain(&[sym])[0];
3989 if bound != 0 { bound } else { vk_kc }
3990 }
3991 None => vk_kc,
3992 };
3993 vk.pressed.insert(vk_kc, kc);
3994 kc
3995 } else {
3996 vk.pressed.remove(&vk_kc).unwrap_or(vk_kc)
3997 };
3998 drop(vk);
3999 let pressed = key_state == 1;
4000 if let Some(keyboard) = state.seat.get_keyboard() {
4001 let serial = next_serial();
4002 let time = wayland_time();
4003 keyboard.input(
4004 state,
4005 smithay::backend::input::Keycode::new(seat_kc),
4006 if pressed {
4007 smithay::backend::input::KeyState::Pressed
4008 } else {
4009 smithay::backend::input::KeyState::Released
4010 },
4011 serial,
4012 time,
4013 |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
4014 );
4015 }
4016 }
4017 zwp_virtual_keyboard_v1::Request::Modifiers { .. } => {}
4018 zwp_virtual_keyboard_v1::Request::Destroy => {}
4019 _ => {}
4020 }
4021 }
4022
4023 fn destroyed(
4026 state: &mut Self,
4027 _client: ClientId,
4028 _resource: &ZwpVirtualKeyboardV1,
4029 data: &PfVirtualKeyboard,
4030 ) {
4031 let held: Vec<u32> = data
4032 .inner
4033 .lock()
4034 .unwrap()
4035 .pressed
4036 .drain()
4037 .map(|(_, kc)| kc)
4038 .collect();
4039 if held.is_empty() {
4040 return;
4041 }
4042 if let Some(keyboard) = state.seat.get_keyboard() {
4043 for kc in held {
4044 let serial = next_serial();
4045 let time = wayland_time();
4046 keyboard.input(
4047 state,
4048 smithay::backend::input::Keycode::new(kc),
4049 smithay::backend::input::KeyState::Released,
4050 serial,
4051 time,
4052 |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
4053 );
4054 }
4055 }
4056 }
4057}
4058
4059#[derive(Default)]
4062pub struct ClientState {
4063 pub compositor_state: CompositorClientState,
4064}
4065impl ClientData for ClientState {
4067 fn initialized(&self, _client_id: ClientId) {}
4068 fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
4069}
4070
4071delegate_dispatch2!(AppState);
4072
4073pub(crate) fn rgba_readback_stride(buf_len: usize, height: usize, width: usize) -> usize {
4080 let row = width.saturating_mul(4);
4081 if height == 0 {
4082 return row;
4083 }
4084 (buf_len / height).max(row)
4085}
4086
4087#[cfg(test)]
4088mod stride_tests {
4089 use super::rgba_readback_stride;
4090
4091 #[test]
4093 fn packed_readback_is_width_times_four() {
4094 assert_eq!(rgba_readback_stride(64 * 4 * 48, 48, 64), 64 * 4);
4095 assert_eq!(rgba_readback_stride(3 * 4 * 2, 2, 3), 12);
4096 }
4097
4098 #[test]
4101 fn padded_readback_recovers_real_stride() {
4102 let (w, h, pad) = (3usize, 2usize, 4usize);
4103 let stride = w * 4 + pad;
4104 assert_eq!(rgba_readback_stride(stride * h, h, w), stride);
4105 }
4106
4107 #[test]
4110 fn padded_extraction_has_no_skew() {
4111 let (w, h) = (3usize, 2usize);
4112 let stride = 16usize;
4113 let mut buf = vec![0u8; stride * h];
4114 for y in 0..h {
4115 for x in 0..w {
4116 let o = y * stride + x * 4;
4117 buf[o] = x as u8 + 1;
4118 buf[o + 1] = y as u8 + 1;
4119 }
4120 }
4121 let s = rgba_readback_stride(buf.len(), h, w);
4122 assert_eq!(s, stride);
4123 for y in 0..h {
4124 for x in 0..w {
4125 let o = y * s + x * 4;
4126 assert_eq!(buf[o], x as u8 + 1);
4127 assert_eq!(buf[o + 1], y as u8 + 1);
4128 }
4129 }
4130 }
4131
4132 #[test]
4134 fn zero_height_no_divide_by_zero() {
4135 assert_eq!(rgba_readback_stride(0, 0, 10), 40);
4136 }
4137
4138 #[test]
4140 fn truncated_buffer_keeps_full_row() {
4141 assert_eq!(rgba_readback_stride(10, 5, 64), 64 * 4);
4142 }
4143}
4144
4145#[cfg(test)]
4146mod clipboard_flavor_tests {
4147 use super::{CLIPBOARD_SECRET_HINTS, clipboard_flavors};
4148
4149 fn offer(mimes: &[&str]) -> Vec<String> {
4150 clipboard_flavors(&mimes.iter().map(|m| m.to_string()).collect::<Vec<_>>())
4151 }
4152
4153 #[test]
4155 fn a_secret_copy_is_read_with_its_hint() {
4156 for hint in CLIPBOARD_SECRET_HINTS {
4157 assert_eq!(
4158 offer(&["TEXT", "text/plain", "text/plain;charset=utf-8", hint]),
4159 vec!["text/plain;charset=utf-8".to_string(), hint.to_string()]
4160 );
4161 }
4162 }
4163
4164 #[test]
4166 fn an_ordinary_copy_reads_as_before() {
4167 assert_eq!(offer(&["text/plain", "image/png"]), vec!["image/png"]);
4168 assert_eq!(
4169 offer(&["UTF8_STRING", "text/html"]),
4170 vec!["text/html", "UTF8_STRING"]
4171 );
4172 }
4173
4174 #[test]
4176 fn an_unreadable_copy_stages_nothing() {
4177 assert!(offer(&["application/x-private"]).is_empty());
4178 }
4179}
4180
4181#[cfg(test)]
4182mod pointer_share_tests {
4183 use super::{PointerAt, PointerShare};
4184 use std::sync::Arc;
4185
4186 fn at(x: f64, y: f64, motion: u64) -> PointerAt {
4187 PointerAt {
4188 x,
4189 y,
4190 scale: 1.5,
4191 motion,
4192 }
4193 }
4194
4195 #[test]
4198 fn a_published_position_reads_back_whole() {
4199 let share = PointerShare::default();
4200 assert_eq!(share.read(), None);
4201 share.publish(at(1920.5, 33.25, 7));
4202 assert_eq!(share.read(), Some(at(1920.5, 33.25, 7)));
4203 share.publish(at(-12.0, 7679.75, 9));
4204 assert_eq!(share.read(), Some(at(-12.0, 7679.75, 9)));
4205 }
4206
4207 #[test]
4210 fn a_read_racing_the_writer_is_one_pass() {
4211 let share = Arc::new(PointerShare::default());
4212 let writer = {
4213 let share = share.clone();
4214 std::thread::spawn(move || {
4215 for n in 1..200_000u64 {
4216 share.publish(at(n as f64, (n % 4096) as f64, n));
4217 }
4218 })
4219 };
4220 let mut seen = 0;
4221 while !writer.is_finished() || seen == 0 {
4222 if let Some(p) = share.read() {
4223 assert_eq!(p.x, f64::from(p.motion as f32));
4224 assert_eq!(p.y, (p.motion % 4096) as f64);
4225 seen += 1;
4226 }
4227 }
4228 writer.join().unwrap();
4229 }
4230}