1use std::borrow::Cow;
22use std::fs::File;
23use std::time::{Duration, Instant};
24
25use gbm::{BufferObject, Device as RawGbmDevice};
26use pyo3::prelude::*;
27use pyo3::types::PyBytes;
28use std::sync::Mutex;
29use std::collections::hash_map::DefaultHasher;
30use std::hash::{Hash, Hasher};
31use smithay::backend::renderer::utils::RendererSurfaceState;
32use smithay::backend::input::InputTime;
33use smithay::backend::allocator::dmabuf::Dmabuf;
34use smithay::backend::allocator::{ Buffer, Fourcc};
35use smithay::backend::renderer::damage::OutputDamageTracker;
36use smithay::backend::renderer::{
37 Bind, ExportMem, gles::GlesRenderer, pixman::PixmanRenderer, ImportDma,
38};
39use smithay::backend::allocator::Modifier;
40use smithay::backend::drm::DrmNode;
41use smithay::output::WeakOutput;
42use smithay::wayland::image_capture_source::{
43 ImageCaptureSource, ImageCaptureSourceHandler, ImageCaptureSourceState,
44 OutputCaptureSourceHandler, OutputCaptureSourceState,
45};
46use smithay::wayland::image_copy_capture::{
47 BufferConstraints, CaptureFailureReason, DmabufConstraints, Frame as CopyFrame,
48 FrameRef as CopyFrameRef, ImageCopyCaptureHandler, ImageCopyCaptureState,
49 Session as CopySession, SessionRef as CopySessionRef,
50};
51use smithay::input::dnd::{DndFocus, Source};
52use std::sync::Arc;
53use crate::wayland::cursor::{Cursor, CursorJob};
54use crate::wayland::keymap::KeymapPolicy;
55use smithay::reexports::wayland_protocols_misc::zwp_virtual_keyboard_v1::server::{
56 zwp_virtual_keyboard_manager_v1::{self, ZwpVirtualKeyboardManagerV1},
57 zwp_virtual_keyboard_v1::{self, ZwpVirtualKeyboardV1},
58};
59use smithay::reexports::wayland_server::{DataInit, Dispatch, GlobalDispatch, New};
60use smithay::wayland::viewporter::ViewporterState;
61use smithay::wayland::pointer_warp::{PointerWarpHandler, PointerWarpManager};
62use smithay::reexports::wayland_server::protocol::wl_pointer::WlPointer;
63use smithay::reexports::wayland_server::protocol::wl_shm;
64use smithay::wayland::relative_pointer::RelativePointerManagerState;
65use smithay::wayland::pointer_constraints::{
66 with_pointer_constraint, PointerConstraint, PointerConstraintsHandler, PointerConstraintsState,
67};
68use smithay::input::pointer::PointerHandle;
69use smithay::wayland::single_pixel_buffer::SinglePixelBufferState;
70use smithay::desktop::utils::under_from_surface_tree;
71use smithay::desktop::{PopupKind, PopupManager, WindowSurfaceType};
72use smithay::wayland::presentation::PresentationState;
73use smithay::wayland::foreign_toplevel_list::{
74 ForeignToplevelHandle, ForeignToplevelListHandler, ForeignToplevelListState,
75};
76use smithay::wayland::shell::xdg::decoration::{
77 XdgDecorationHandler, XdgDecorationState,
78};
79use smithay::desktop::{layer_map_for_output, LayerSurface as DesktopLayerSurface};
80use smithay::wayland::shell::wlr_layer::{
81 WlrLayerShellHandler, WlrLayerShellState, Layer as WlrLayer, LayerSurface as WlrLayerSurface,
82};
83use smithay::reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
84use smithay::wayland::selection::wlr_data_control::{DataControlHandler, DataControlState};
85use smithay::wayland::selection::ext_data_control::{
86 DataControlHandler as ExtDataControlHandler, DataControlState as ExtDataControlState,
87};
88use smithay::wayland::cursor_shape::CursorShapeManagerState;
89use smithay::wayland::xdg_activation::{
90 XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData,
91};
92use smithay::wayland::selection::primary_selection::{
93 set_primary_focus, PrimarySelectionHandler, PrimarySelectionState,
94};
95
96use smithay::{
97 delegate_dispatch2,
98 desktop::{Space, Window},
99 input::{
100 Seat, SeatHandler, SeatState,
101 keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
102 pointer::{
103 AxisFrame, ButtonEvent, CursorIcon, CursorImageAttributes, CursorImageStatus,
104 GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
105 GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
106 GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData, MotionEvent,
107 PointerTarget, RelativeMotionEvent,
108 },
109 touch::{DownEvent, FrameMarker, OrientationEvent, ShapeEvent, TouchTarget, UpEvent},
110 },
111 output::Output,
112 reexports::{
113 wayland_protocols::xdg::shell::server::xdg_toplevel::State as XdgState,
114 wayland_server::{
115 Client, DisplayHandle, Resource,
116 backend::{ClientData, ClientId, DisconnectReason, GlobalId, ObjectId},
117 protocol::{wl_buffer::WlBuffer, wl_surface::WlSurface},
118 },
119 },
120 utils::{Clock, IsAlive, Logical, Monotonic, Point, Rectangle, Serial},
121 wayland::{
122 buffer::BufferHandler,
123 compositor::{
124 BufferAssignment, CompositorClientState, CompositorHandler, CompositorState,
125 SurfaceAttributes, with_states,
126 },
127 dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier, get_dmabuf},
128 fractional_scale::{FractionalScaleHandler, FractionalScaleManagerState},
129 output::{OutputHandler, OutputManagerState},
130 seat::WaylandFocus,
131 selection::{
132 SelectionHandler, SelectionSource, SelectionTarget,
133 data_device::{
134 DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler,
135 request_data_device_client_selection, set_data_device_focus,
136 },
137 },
138 shell::xdg::{
139 PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
140 XdgToplevelSurfaceData,
141 },
142 shm::{BufferAccessError, ShmHandler, ShmState, with_buffer_contents},
143 },
144};
145
146use crate::encoders::FrameEncoder;
147use crate::encoders::overlay::OverlayState;
148use crate::{RustCaptureSettings, StripeState};
149
150use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering, fence};
151
152static SERIAL_COUNTER: AtomicU32 = AtomicU32::new(1);
153
154pub fn next_serial() -> Serial {
164 Serial::from(SERIAL_COUNTER.fetch_add(1, Ordering::SeqCst))
165}
166
167pub fn wayland_time() -> InputTime {
172 let mut ts = libc::timespec {
173 tv_sec: 0,
174 tv_nsec: 0,
175 };
176 unsafe {
177 libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
178 }
179 InputTime::from_micros(
180 (ts.tv_sec as u64)
181 .wrapping_mul(1_000_000)
182 .wrapping_add((ts.tv_nsec as u64) / 1_000),
183 )
184}
185
186use crate::pace::FramePace;
187
188pub struct WlCapture {
193 pub settings: RustCaptureSettings,
194 pub callback: Option<Arc<Py<PyAny>>>,
198 pub video_encoder: Option<FrameEncoder>,
201 pub vaapi_state: StripeState,
202 pub recording_sink: Option<Arc<crate::recording_sink::RecordingSink>>,
203 pub deliver_tx:
204 Option<std::sync::mpsc::SyncSender<Vec<crate::encoders::software::EncodedStripe>>>,
205 pub deliver_join: Option<std::thread::JoinHandle<()>>,
206 pub deliver_discard: Option<Arc<AtomicBool>>,
209 pub pending_hw_delivery: Option<Vec<crate::encoders::software::EncodedStripe>>,
210 pub pending_hw_damage: bool,
211 pub encode_pool: Option<Arc<crate::WlFramePool>>,
212 pub encode_join: Option<std::thread::JoinHandle<Option<FrameEncoder>>>,
213 pub encode_controls: Arc<crate::WlEncodeControls>,
214 pub encode_stats: Arc<crate::WlEncodeStats>,
215 pub report: Arc<crate::report::StreamReport>,
217 pub pool_last_render: Vec<u64>,
218 pub render_seq: u64,
219 pub pool_content_gen: Vec<u64>,
220 pub content_gen: u64,
221 pub frame_counter: u16,
222 pub pending_force_idr: bool,
223 pub needs_full_render: bool,
224 pub pace: FramePace,
225 pub hw_error_streak: u32,
228 pub hw_rebuilt: bool,
232}
233
234impl WlCapture {
235 pub fn request_idr(&mut self) {
241 if self.encode_pool.is_some() {
242 self.encode_controls
243 .force_idr
244 .store(true, Ordering::Relaxed);
245 } else {
246 self.pending_force_idr = true;
247 }
248 self.needs_full_render = true;
249 }
250
251 pub fn invalidate_reference(&mut self, frame_id: u16) {
255 self.take_report(crate::encoders::reference::ReferenceReport::Lost(frame_id));
256 }
257
258 pub fn acknowledge_reference(&mut self, frame_id: u16, held: bool) {
261 use crate::encoders::reference::ReferenceReport;
262 self.take_report(if held {
263 ReferenceReport::Held(frame_id)
264 } else {
265 ReferenceReport::Sent(frame_id)
266 });
267 }
268
269 fn take_report(&mut self, report: crate::encoders::reference::ReferenceReport) {
270 if self.encode_pool.is_some() {
271 self.encode_controls
272 .reference_reports
273 .lock()
274 .unwrap()
275 .push(report);
276 } else if let Some(encoder) = self.video_encoder.as_mut()
277 && !encoder.take_report(report)
278 {
279 self.request_idr();
280 }
281 }
282}
283
284pub struct OutputNode {
289 pub id: u32,
290 pub output: Output,
291 pub global: Option<GlobalId>,
297 pub owner: Option<u32>,
299 pub view_size: (i32, i32),
302 pub view_scale: f64,
304 pub pos: (i32, i32),
309 pub damage_tracker: OutputDamageTracker,
310 pub content_tracker: Option<OutputDamageTracker>,
317 pub frame_buffer: Vec<u8>,
320 pub offscreen_buffer: Option<(BufferObject<()>, Dmabuf)>,
322 pub undrawn_ticks: u32,
324 pub overlay_state: OverlayState,
327 pub capture: Option<WlCapture>,
328 pub frame_seq: u64,
330 pub target_seeded: bool,
334 pub content_hold_until: Option<Instant>,
340}
341
342pub struct PendingHostLayout {
347 pub epoch: u64,
348 pub want: (i32, i32),
349 pub geometry_waiters: Vec<std::sync::mpsc::Sender<(i32, i32, f64)>>,
350}
351
352pub struct OutputCopySession {
357 pub session: CopySession,
358 pub output: WeakOutput,
359 pub pending: Option<CopyFrame>,
360 pub delivered_once: bool,
363}
364
365pub fn windows_on_output(space: &Space<Window>, display_id: u32) -> impl Iterator<Item = &Window> {
369 space.elements().filter(move |w| {
370 window_output_id(w) == display_id
371 && !window_meta(w)
372 .map(|m| m.parked.load(Ordering::Relaxed))
373 .unwrap_or(false)
374 })
375}
376
377pub fn output_content_covers(
381 space: &Space<Window>,
382 display_id: u32,
383 logical_w: f64,
384 logical_h: f64,
385) -> bool {
386 windows_on_output(space, display_id).any(|w| {
387 let size = w.geometry().size;
388 size.w as f64 >= logical_w && size.h as f64 >= logical_h
389 })
390}
391
392impl OutputNode {
393 pub fn logical_geometry(&self) -> Option<Rectangle<i32, Logical>> {
397 let scale = self.output.current_scale().fractional_scale();
398 let (w, h) = if self.owner.is_some() {
399 self.capture
400 .as_ref()
401 .map(|c| (c.settings.width, c.settings.height))
402 .unwrap_or(self.view_size)
403 } else {
404 let mode = self.output.current_mode()?;
405 (mode.size.w, mode.size.h)
406 };
407 Some(Rectangle::new(
408 Point::from(self.pos),
409 (
410 (w as f64 / scale).round() as i32,
411 (h as f64 / scale).round() as i32,
412 )
413 .into(),
414 ))
415 }
416}
417
418static NEXT_WINDOW_ID: AtomicU32 = AtomicU32::new(1);
419
420pub struct WindowMeta {
424 pub id: u32,
425 pub output: AtomicU32,
426 pub placed: AtomicBool,
431 pub parked: AtomicBool,
434}
435
436pub const PARKED_POS: (i32, i32) = (-(1 << 20), -(1 << 20));
440
441pub const PARKED_LOGICAL_SIZE: (i32, i32) = (320, 240);
448
449pub fn window_meta(window: &Window) -> Option<&WindowMeta> {
452 window.user_data().get::<WindowMeta>()
453}
454
455pub fn window_output_id(window: &Window) -> u32 {
457 window_meta(window)
458 .map(|m| m.output.load(Ordering::Relaxed))
459 .unwrap_or(0)
460}
461
462pub type ScreenshotRequest = (u32, std::sync::mpsc::Sender<Result<Vec<u8>, String>>);
465
466#[derive(Clone, Copy, Debug, PartialEq)]
468pub struct PointerAt {
469 pub x: f64,
471 pub y: f64,
472 pub scale: f64,
475 pub motion: u64,
477}
478
479#[derive(Default)]
484pub struct PointerShare {
485 version: AtomicU64,
487 position: AtomicU64,
489 scale: AtomicU64,
490 motion: AtomicU64,
491}
492
493impl PointerShare {
494 pub fn publish(&self, at: PointerAt) {
496 let position = (at.x as f32).to_bits() as u64 | ((at.y as f32).to_bits() as u64) << 32;
497 let scale = at.scale.to_bits();
498 let version = self.version.load(Ordering::Relaxed);
499 if version != 0
500 && self.position.load(Ordering::Relaxed) == position
501 && self.scale.load(Ordering::Relaxed) == scale
502 && self.motion.load(Ordering::Relaxed) == at.motion
503 {
504 return;
505 }
506 self.version.store(version + 1, Ordering::Relaxed);
507 fence(Ordering::Release);
508 self.position.store(position, Ordering::Relaxed);
509 self.scale.store(scale, Ordering::Relaxed);
510 self.motion.store(at.motion, Ordering::Relaxed);
511 self.version.store(version + 2, Ordering::Release);
512 }
513
514 pub fn read(&self) -> Option<PointerAt> {
515 loop {
516 let version = self.version.load(Ordering::Acquire);
517 if version == 0 {
518 return None;
519 }
520 if version & 1 == 0 {
521 let position = self.position.load(Ordering::Relaxed);
522 let scale = self.scale.load(Ordering::Relaxed);
523 let motion = self.motion.load(Ordering::Relaxed);
524 fence(Ordering::Acquire);
525 if self.version.load(Ordering::Relaxed) == version {
526 return Some(PointerAt {
527 x: f32::from_bits(position as u32) as f64,
528 y: f32::from_bits((position >> 32) as u32) as f64,
529 scale: f64::from_bits(scale),
530 motion,
531 });
532 }
533 }
534 std::hint::spin_loop();
535 }
536 }
537}
538
539pub struct AppState {
585 pub compositor_state: CompositorState,
586 pub fractional_scale_state: FractionalScaleManagerState,
587 pub viewporter_state: ViewporterState,
588 pub presentation_state: PresentationState,
589 pub shm_state: ShmState,
590 pub single_pixel_buffer: SinglePixelBufferState,
591 pub dmabuf_state: DmabufState,
592 pub dmabuf_global: Option<DmabufGlobal>,
593 pub ext_data_control_state: ExtDataControlState,
594 pub cursor_shape_state: CursorShapeManagerState,
595 pub image_capture_source_state: ImageCaptureSourceState,
596 pub output_capture_source_state: OutputCaptureSourceState,
597 pub image_copy_capture_state: ImageCopyCaptureState,
598 pub copy_sessions: Vec<OutputCopySession>,
599 pub output_state: OutputManagerState,
600 pub seat_state: SeatState<AppState>,
601 pub shell_state: XdgShellState,
602 pub layer_shell_state: WlrLayerShellState,
603 pub space: Space<Window>,
604 pub data_device_state: DataDeviceState,
605 pub data_control_state: DataControlState,
606 pub dh: DisplayHandle,
607 pub seat: Seat<AppState>,
608 pub output_nodes: Vec<OutputNode>,
611 pub pending_windows: Vec<Window>,
612
613 pub foreign_toplevel_list: ForeignToplevelListState,
614 pub xdg_decoration_state: XdgDecorationState,
615 pub xdg_activation_state: XdgActivationState,
616 pub primary_selection_state: PrimarySelectionState,
617 pub popups: PopupManager,
618
619 pub gles_renderer: Option<GlesRenderer>,
620 pub pixman_renderer: Option<PixmanRenderer>,
621
622 pub gbm_device: Option<RawGbmDevice<File>>,
623
624 pub settings: RustCaptureSettings,
627 pub cursor_callback_set: bool,
630 pub cursor_tx: std::sync::mpsc::Sender<CursorJob>,
632 pub clipboard_callback: Option<Py<PyAny>>,
633 pub pending_clipboard_read: Option<Vec<String>>,
636 pub clipboard_generation: Arc<AtomicU64>,
639 pub current_selection_mimes: Vec<String>,
643
644 pub last_log_time: Instant,
645 pub start_time: Instant,
646 pub clock: Clock<Monotonic>,
647
648 pub use_gpu: bool,
649
650 pub cursor_helper: Cursor,
651
652 pub keymap_policy: KeymapPolicy,
655 pub host: Option<crate::wayland::host::HostSession>,
658 pub host_layout_pending: std::collections::HashMap<u32, PendingHostLayout>,
663 pub host_frame_tx: smithay::reexports::calloop::channel::Sender<usize>,
667 pub host_mode_refusals: std::collections::HashMap<u32, u32>,
670
671 pub current_cursor_icon: Option<CursorImageStatus>,
672 pub cursor_surface_pending: bool,
677 pub cursor_buffer: Option<WlBuffer>,
678 pub render_cursor_on_framebuffer: bool,
679 pub pointer_warp_state: PointerWarpManager,
680 pub relative_pointer_state: RelativePointerManagerState,
681 pub pointer_constraints_state: PointerConstraintsState,
682 pub render_node_path: String,
683 pub auto_gpu_selected: bool,
684 pub pending_screenshot: Option<ScreenshotRequest>,
687 pub command_rx: Option<smithay::reexports::calloop::channel::Channel<crate::ThreadCommand>>,
691 pub last_input_at: Option<Instant>,
695 pub pointer_motion_at: Option<Instant>,
700 pub input_interval: Option<Duration>,
701 pub pointer_share: Arc<PointerShare>,
703 pub motion_applied: u64,
705 pub frame_idle_long: bool,
710 pub last_idle_service_at: Option<Instant>,
713 pub deliver_reaper: Vec<std::thread::JoinHandle<()>>,
717 pub encode_reaper: Vec<std::thread::JoinHandle<Option<crate::FrameEncoder>>>,
721}
722
723impl PointerConstraintsHandler for AppState {
728 fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
729 let Some(focus) = pointer.current_focus() else {
730 return;
731 };
732 if focus.wl_surface().as_deref() != Some(surface) {
733 return;
734 }
735 with_pointer_constraint(surface, pointer, |constraint| {
736 if let Some(constraint) = constraint {
737 constraint.activate();
738 }
739 });
740 }
741}
742
743impl ForeignToplevelListHandler for AppState {
746 fn foreign_toplevel_list_state(&mut self) -> &mut ForeignToplevelListState {
747 &mut self.foreign_toplevel_list
748 }
749}
750
751impl XdgActivationHandler for AppState {
758 fn activation_state(&mut self) -> &mut XdgActivationState {
759 &mut self.xdg_activation_state
760 }
761
762 fn token_created(&mut self, _token: XdgActivationToken, _data: XdgActivationTokenData) -> bool {
763 true
764 }
765
766 fn request_activation(
767 &mut self,
768 _token: XdgActivationToken,
769 token_data: XdgActivationTokenData,
770 surface: WlSurface,
771 ) {
772 if token_data.timestamp.elapsed().as_secs() < 10 {
773 let window = self
774 .space
775 .elements()
776 .find(|w| w.wl_surface().as_deref() == Some(&surface))
777 .cloned();
778 if let Some(window) = window {
779 self.space.raise_element(&window, true);
780 }
781 }
782 }
783}
784
785impl PrimarySelectionHandler for AppState {
790 fn primary_selection_state(&mut self) -> &mut PrimarySelectionState {
791 &mut self.primary_selection_state
792 }
793}
794
795impl XdgDecorationHandler for AppState {
805 fn new_decoration(&mut self, toplevel: ToplevelSurface) {
806 toplevel.with_pending_state(|state| {
807 state.decoration_mode = Some(Mode::ServerSide);
808 });
809 toplevel.send_configure();
810 }
811
812 fn request_mode(&mut self, toplevel: ToplevelSurface, mode: Mode) {
813 toplevel.with_pending_state(|state| {
814 state.decoration_mode = Some(mode);
815 });
816 toplevel.send_configure();
817 }
818
819 fn unset_mode(&mut self, toplevel: ToplevelSurface) {
820 toplevel.with_pending_state(|state| {
821 state.decoration_mode = Some(Mode::ServerSide);
822 });
823 toplevel.send_configure();
824 }
825}
826
827impl WlrLayerShellHandler for AppState {
833 fn shell_state(&mut self) -> &mut WlrLayerShellState {
834 &mut self.layer_shell_state
835 }
836
837 fn new_layer_surface(
838 &mut self,
839 surface: WlrLayerSurface,
840 output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
841 _layer: WlrLayer,
842 namespace: String,
843 ) {
844 let smithay_output = if let Some(wlo) = output.as_ref() {
845 self.output_nodes
846 .iter()
847 .map(|n| &n.output)
848 .find(|o| o.owns(wlo))
849 } else {
850 self.primary_output()
851 };
852
853 if let Some(output) = smithay_output {
854 let mode = output.current_mode().unwrap();
855
856 surface.with_pending_state(|state| {
857 state.size =
858 Some(((mode.size.w as f64) as i32, (mode.size.h as f64) as i32).into());
859 });
860 surface.send_configure();
861
862 let layer = DesktopLayerSurface::new(surface, namespace);
863 let _ = layer_map_for_output(output).map_layer(&layer);
864 }
865 }
866
867 fn layer_destroyed(&mut self, _surface: WlrLayerSurface) {}
868}
869
870impl CompositorHandler for AppState {
873 fn compositor_state(&mut self) -> &mut CompositorState {
874 &mut self.compositor_state
875 }
876 fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
877 &client.get_data::<ClientState>().unwrap().compositor_state
878 }
879
880 fn commit(&mut self, surface: &WlSurface) {
912 smithay::backend::renderer::utils::on_commit_buffer_handler::<Self>(surface);
913 self.popups.commit(surface);
916
917 for node in &self.output_nodes {
918 let mut layer_map = layer_map_for_output(&node.output);
919 let found = layer_map
920 .layers()
921 .any(|layer| layer.wl_surface() == surface);
922 if found {
923 layer_map.arrange();
924 break;
925 }
926 }
927
928 if let Some(CursorImageStatus::Surface(ref cursor_surface)) = self.current_cursor_icon
929 && cursor_surface == surface
930 {
931 let status = CursorImageStatus::Surface(surface.clone());
932 self.cursor_surface_pending = false;
933 self.send_cursor_image(&status);
934 }
935
936 if let Some(handle) = with_states(surface, |states| {
937 states.data_map.get::<ForeignToplevelHandle>().cloned()
938 }) && let Some(window) = self
939 .space
940 .elements()
941 .find(|w| w.wl_surface().as_deref() == Some(surface))
942 && let Some(_toplevel) = window.toplevel()
943 {
944 let (title, app_id) = with_states(surface, |states| {
945 let attributes = states
946 .data_map
947 .get::<XdgToplevelSurfaceData>()
948 .unwrap()
949 .lock()
950 .unwrap();
951 (attributes.title.clone(), attributes.app_id.clone())
952 });
953
954 handle.send_title(&title.unwrap_or_default());
955 handle.send_app_id(&app_id.unwrap_or_default());
956 handle.send_done();
957 }
958
959 let mapped = self
960 .space
961 .elements()
962 .find(|w| {
963 w.toplevel()
964 .map(|tl| tl.wl_surface() == surface)
965 .unwrap_or(false)
966 })
967 .cloned();
968 if let Some(window) = mapped {
969 window.on_commit();
970 let has_buffer =
974 smithay::backend::renderer::utils::with_renderer_surface_state(surface, |s| {
975 s.buffer().is_some()
976 })
977 .unwrap_or(false);
978 if !has_buffer {
979 self.space.unmap_elem(&window);
980 self.pending_windows.push(window);
981 self.refocus_pointer();
982 return;
983 }
984 }
985
986 if let Some(idx) = self.pending_windows.iter().position(|w| {
987 w.toplevel()
988 .map(|tl| tl.wl_surface() == surface)
989 .unwrap_or(false)
990 }) {
991 let window = self.pending_windows.remove(idx);
992 let toplevel = window.toplevel().unwrap();
993
994 let needs_placement = window_meta(&window)
999 .map(|meta| !meta.placed.swap(true, Ordering::Relaxed))
1000 .unwrap_or(false);
1001
1002 if needs_placement {
1003 let mut target_id = self.pointer_display();
1007 let mut park = false;
1013 if self.would_cover_screen(&window, target_id) {
1014 let empty = self
1019 .output_nodes
1020 .iter()
1021 .filter(|n| n.owner.is_none())
1022 .map(|n| n.id)
1023 .find(|oid| {
1024 !self
1025 .space
1026 .elements()
1027 .chain(self.pending_windows.iter())
1028 .any(|w| window_output_id(w) == *oid)
1029 });
1030 match empty {
1031 Some(oid) => target_id = oid,
1032 None => park = true,
1033 }
1034 }
1035 if let Some(meta) = window_meta(&window) {
1036 meta.output.store(target_id, Ordering::Relaxed);
1037 meta.parked.store(park, Ordering::Relaxed);
1038 }
1039 let (logical_width, logical_height) = if park {
1040 PARKED_LOGICAL_SIZE
1041 } else if let Some(size) = self.logical_size_of(target_id) {
1042 size
1043 } else {
1044 let scale = self.settings.scale.max(0.1);
1045 (
1046 (self.settings.width as f64 / scale).round() as i32,
1047 (self.settings.height as f64 / scale).round() as i32,
1048 )
1049 };
1050
1051 toplevel.with_pending_state(|state| {
1052 state.states.set(XdgState::Activated);
1053 state.states.set(XdgState::Fullscreen);
1054 state.size = Some((logical_width, logical_height).into());
1055 });
1056 toplevel.send_configure();
1057
1058 self.pending_windows.push(window);
1059 } else if smithay::backend::renderer::utils::with_renderer_surface_state(surface, |s| {
1060 s.buffer().is_none()
1061 })
1062 .unwrap_or(true)
1063 {
1064 let tl = toplevel.clone();
1070 self.pending_windows.push(window);
1071 self.send_forced_fullscreen_configure(&tl);
1072 } else {
1073 let target_id = window_output_id(&window);
1074 let parked = window_meta(&window)
1075 .map(|meta| meta.parked.load(Ordering::Relaxed))
1076 .unwrap_or(false);
1077 let node_idx = self.node_idx_for_id(target_id).unwrap_or(0);
1078 let (target_output, pos) = {
1079 let node = &self.output_nodes[node_idx];
1080 (node.output.clone(), node.pos)
1081 };
1082 self.space.map_element(
1085 window.clone(),
1086 if parked { PARKED_POS } else { pos },
1087 !parked,
1088 );
1089 window.on_commit();
1090
1091 if !parked {
1092 target_output.enter(surface);
1093 let scale = target_output.current_scale().fractional_scale();
1094 with_states(surface, |states| {
1095 smithay::wayland::compositor::send_surface_state(
1096 surface,
1097 states,
1098 scale.ceil() as i32,
1099 smithay::utils::Transform::Normal,
1100 );
1101 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1102 fs.set_preferred_scale(scale);
1103 });
1104 });
1105
1106 if let Some(geo_out) = self.output_nodes[node_idx].logical_geometry() {
1107 let (expected_w, expected_h) = (geo_out.size.w, geo_out.size.h);
1108 let geo = window.geometry();
1109 if (geo.size.w - expected_w).abs() > 1
1110 || (geo.size.h - expected_h).abs() > 1
1111 {
1112 toplevel.with_pending_state(|state| {
1113 state.states.set(XdgState::Activated);
1114 state.states.set(XdgState::Fullscreen);
1115 state.size = Some((expected_w, expected_h).into());
1116 });
1117 toplevel.send_configure();
1118 }
1119 }
1120
1121 let serial = next_serial();
1122 let target = FocusTarget::Window(window.clone());
1123 if let Some(keyboard) = self.seat.get_keyboard() {
1124 keyboard.set_focus(self, Some(target.clone()), serial);
1125 }
1126 self.refocus_pointer();
1127 } else {
1128 let geo = window.geometry();
1133 if (geo.size.w - PARKED_LOGICAL_SIZE.0).abs() > 1
1134 || (geo.size.h - PARKED_LOGICAL_SIZE.1).abs() > 1
1135 {
1136 let tl = toplevel.clone();
1137 self.send_forced_fullscreen_configure(&tl);
1138 }
1139 }
1140 }
1141 }
1142 }
1143}
1144
1145impl AppState {
1146 pub(crate) fn primary_output(&self) -> Option<&Output> {
1148 self.output_nodes.first().map(|n| &n.output)
1149 }
1150
1151 pub(crate) fn node_idx_for_id(&self, id: u32) -> Option<usize> {
1152 self.output_nodes.iter().position(|n| n.id == id)
1153 }
1154
1155 pub(crate) fn view_ids_of(&self, id: u32) -> Vec<u32> {
1157 self.output_nodes
1158 .iter()
1159 .filter(|n| n.owner == Some(id))
1160 .map(|n| n.id)
1161 .collect()
1162 }
1163
1164 pub(crate) fn screen_id_of(&self, id: u32) -> u32 {
1166 self.node_idx_for_id(id)
1167 .and_then(|i| self.output_nodes[i].owner)
1168 .unwrap_or(id)
1169 }
1170
1171 pub(crate) fn node_idx_under(&self, p: Point<f64, Logical>) -> Option<usize> {
1173 self.output_nodes.iter().position(|n| {
1174 n.logical_geometry()
1175 .map(|g| g.to_f64().contains(p))
1176 .unwrap_or(false)
1177 })
1178 }
1179
1180 pub(crate) fn layout_physical_to_logical(&self, x: f64, y: f64) -> Point<f64, Logical> {
1185 let mut best: Option<(f64, Point<f64, Logical>)> = None;
1186 for node in self.output_nodes.iter().filter(|n| n.owner.is_none()) {
1187 let Some(mode) = node.output.current_mode() else {
1188 continue;
1189 };
1190 let scale = node.output.current_scale().fractional_scale();
1191 let (px, py) = (node.pos.0 as f64, node.pos.1 as f64);
1192 let cx = x.max(px).min(px + mode.size.w as f64 - 1.0);
1193 let cy = y.max(py).min(py + mode.size.h as f64 - 1.0);
1194 let d2 = (x - cx).powi(2) + (y - cy).powi(2);
1195 let logical = Point::from((
1196 node.pos.0 as f64 + (cx - px) / scale,
1197 node.pos.1 as f64 + (cy - py) / scale,
1198 ));
1199 if best.as_ref().map(|(bd, _)| d2 < *bd).unwrap_or(true) {
1200 best = Some((d2, logical));
1201 }
1202 }
1203 best.map(|(_, p)| p).unwrap_or_else(|| (0.0, 0.0).into())
1204 }
1205
1206 pub(crate) fn pointer_locked(
1210 &self,
1211 pointer: &PointerHandle<Self>,
1212 under: &Option<(FocusTarget, Point<f64, Logical>)>,
1213 location: Point<f64, Logical>,
1214 ) -> bool {
1215 let Some((target, origin)) = under.as_ref() else {
1216 return false;
1217 };
1218 if pointer.current_focus().as_ref() != Some(target) {
1219 return false;
1220 }
1221 let Some(surface) = target.wl_surface() else {
1222 return false;
1223 };
1224 with_pointer_constraint(&surface, pointer, |constraint| match constraint {
1225 Some(constraint) if constraint.is_active() => {
1226 let local = (location - *origin).to_i32_round();
1227 constraint
1228 .region()
1229 .is_none_or(|region| region.contains(local))
1230 && matches!(&*constraint, PointerConstraint::Locked(_))
1231 }
1232 _ => false,
1233 })
1234 }
1235
1236 pub(crate) fn pointer_target_under(
1242 &self,
1243 p: Point<f64, Logical>,
1244 ) -> Option<(FocusTarget, Point<f64, Logical>)> {
1245 use smithay::wayland::shell::wlr_layer::Layer;
1246 let layer_hit = |layers: &[Layer]| {
1247 let node = &self.output_nodes[self.node_idx_under(p)?];
1248 let origin = Point::<i32, Logical>::from(node.pos);
1249 let local = (p - origin.to_f64()).to_i32_round();
1250 let layer_map = layer_map_for_output(&node.output);
1251 layer_map.layers().rev().find_map(|layer| {
1252 if !layers.contains(&layer.layer()) {
1253 return None;
1254 }
1255 let bbox = layer_map.layer_geometry(layer)?;
1256 popup_under(layer.wl_surface(), bbox.loc + origin, p).or_else(|| {
1257 bbox.contains(local).then(|| {
1258 (
1259 FocusTarget::LayerSurface(layer.clone()),
1260 (bbox.loc + origin).to_f64(),
1261 )
1262 })
1263 })
1264 })
1265 };
1266 layer_hit(&[Layer::Overlay, Layer::Top])
1267 .or_else(|| {
1268 self.space.element_under(p).map(|(window, loc)| {
1269 window
1270 .wl_surface()
1271 .and_then(|root| popup_under(&root, loc + window.geometry().loc, p))
1272 .unwrap_or_else(|| (FocusTarget::Window(window.clone()), loc.to_f64()))
1273 })
1274 })
1275 .or_else(|| layer_hit(&[Layer::Bottom, Layer::Background]))
1276 }
1277
1278 pub(crate) fn refocus_pointer(&mut self) {
1285 if self.host.is_some() {
1286 return;
1287 }
1288 let Some(pointer) = self.seat.get_pointer() else {
1289 return;
1290 };
1291 if pointer.is_grabbed() {
1292 return;
1293 }
1294 let location = pointer.current_location();
1295 let under = self.pointer_target_under(location).filter(|(target, _)| {
1296 target
1297 .wl_surface()
1298 .and_then(|surface| surface.client())
1299 .is_none_or(|client| pointer.client_pointers(&client).next().is_some())
1300 });
1301 if pointer.current_focus() == under.as_ref().map(|(t, _)| t.clone()) {
1302 return;
1303 }
1304 let time = wayland_time();
1305 pointer.motion(
1306 self,
1307 under.clone(),
1308 &MotionEvent {
1309 location,
1310 serial: next_serial(),
1311 time,
1312 },
1313 );
1314 if under.is_some() {
1315 pointer.motion(
1316 self,
1317 under.clone(),
1318 &MotionEvent {
1319 location,
1320 serial: next_serial(),
1321 time,
1322 },
1323 );
1324 }
1325 pointer.frame(self);
1326 self.activate_constraint_under(&pointer, &under, location);
1327 }
1328
1329 pub(crate) fn activate_constraint_under(
1331 &self,
1332 pointer: &PointerHandle<Self>,
1333 under: &Option<(FocusTarget, Point<f64, Logical>)>,
1334 location: Point<f64, Logical>,
1335 ) {
1336 let Some((target, origin)) = under.as_ref() else {
1337 return;
1338 };
1339 let Some(surface) = target.wl_surface() else {
1340 return;
1341 };
1342 with_pointer_constraint(&surface, pointer, |constraint| {
1343 if let Some(constraint) = constraint
1344 && !constraint.is_active()
1345 && constraint
1346 .region()
1347 .is_none_or(|region| region.contains((location - *origin).to_i32_round()))
1348 {
1349 constraint.activate();
1350 }
1351 });
1352 }
1353
1354 pub(crate) fn clamp_logical(&self, p: Point<f64, Logical>) -> Point<f64, Logical> {
1356 let mut best: Option<(f64, Point<f64, Logical>)> = None;
1357 for node in self.output_nodes.iter().filter(|n| n.owner.is_none()) {
1358 let Some(geo) = node.logical_geometry() else {
1359 continue;
1360 };
1361 let scale = node.output.current_scale().fractional_scale();
1362 let g = geo.to_f64();
1363 let margin = 1.0 / scale.max(0.1);
1364 let cx = p.x.max(g.loc.x).min(g.loc.x + g.size.w - margin);
1365 let cy = p.y.max(g.loc.y).min(g.loc.y + g.size.h - margin);
1366 let d2 = (p.x - cx).powi(2) + (p.y - cy).powi(2);
1367 if best.as_ref().map(|(bd, _)| d2 < *bd).unwrap_or(true) {
1368 best = Some((d2, (cx, cy).into()));
1369 }
1370 }
1371 best.map(|(_, p)| p).unwrap_or(p)
1372 }
1373
1374 pub(crate) fn layout_logical_to_physical(&self, p: Point<f64, Logical>) -> (f64, f64) {
1377 let (x, y, _) = self.layout_logical_to_physical_scaled(p);
1378 (x, y)
1379 }
1380
1381 pub(crate) fn layout_logical_to_physical_scaled(
1383 &self,
1384 p: Point<f64, Logical>,
1385 ) -> (f64, f64, f64) {
1386 let idx = self.node_idx_under(p).unwrap_or(0);
1387 let Some(node) = self.output_nodes.get(idx) else {
1388 return (p.x, p.y, 1.0);
1389 };
1390 let scale = node.output.current_scale().fractional_scale();
1391 (
1392 node.pos.0 as f64 + (p.x - node.pos.0 as f64) * scale,
1393 node.pos.1 as f64 + (p.y - node.pos.1 as f64) * scale,
1394 scale,
1395 )
1396 }
1397
1398 pub(crate) fn logical_size_of(&self, id: u32) -> Option<(i32, i32)> {
1400 let idx = self.node_idx_for_id(id)?;
1401 let geo = self.output_nodes[idx].logical_geometry()?;
1402 Some((geo.size.w, geo.size.h))
1403 }
1404
1405 pub(crate) fn release_grab_across_screens(
1412 &mut self,
1413 pointer: &PointerHandle<Self>,
1414 under: &Option<(FocusTarget, Point<f64, Logical>)>,
1415 serial: Serial,
1416 time: InputTime,
1417 ) {
1418 let Some((FocusTarget::Window(next), _)) = under else {
1419 return;
1420 };
1421 let Some(GrabStartData {
1422 focus: Some((FocusTarget::Window(held), _)),
1423 ..
1424 }) = pointer.grab_start_data()
1425 else {
1426 return;
1427 };
1428 if held == *next || pointer.current_focus() != Some(FocusTarget::Window(held.clone())) {
1429 return;
1430 }
1431 let client_of = |w: &Window| w.wl_surface().and_then(|s| s.client()).map(|c| c.id());
1432 if client_of(&held).is_some() && client_of(&held) == client_of(next) {
1433 pointer.unset_grab(self, serial, time);
1434 }
1435 }
1436
1437 pub(crate) fn pointer_display(&self) -> u32 {
1439 self.seat
1440 .get_pointer()
1441 .map(|p| p.current_location())
1442 .and_then(|pos| self.node_idx_under(pos))
1443 .map(|idx| self.output_nodes[idx].id)
1444 .unwrap_or(0)
1445 }
1446
1447 pub(crate) fn would_cover_screen(&self, window: &Window, id: u32) -> bool {
1451 let Some(client) = window.wl_surface().and_then(|s| s.client()) else {
1452 return false;
1453 };
1454 self.space
1455 .elements()
1456 .chain(self.pending_windows.iter())
1457 .filter(|w| !std::ptr::eq(*w, window) && w.wl_surface() != window.wl_surface())
1458 .any(|w| {
1459 window_output_id(w) == id
1460 && !window_meta(w)
1461 .map(|m| m.parked.load(Ordering::Relaxed))
1462 .unwrap_or(false)
1463 && w.wl_surface()
1464 .and_then(|s| s.client())
1465 .is_some_and(|c| c.id() == client.id())
1466 })
1467 }
1468
1469 pub(crate) fn park_window(&mut self, window: &Window, id: u32) {
1473 let old_id = window_output_id(window);
1474 if let Some(meta) = window_meta(window) {
1475 meta.output.store(id, Ordering::Relaxed);
1476 meta.parked.store(true, Ordering::Relaxed);
1477 }
1478 if let (Some(surface), Some(idx)) = (window.wl_surface(), self.node_idx_for_id(old_id)) {
1479 self.output_nodes[idx].output.leave(&surface);
1480 }
1481 self.space.map_element(window.clone(), PARKED_POS, false);
1482 self.refocus_pointer();
1483 if let Some(toplevel) = window.toplevel() {
1484 let toplevel = toplevel.clone();
1485 self.send_forced_fullscreen_configure(&toplevel);
1486 }
1487 }
1488
1489 pub(crate) fn place_window_on_output(&mut self, window: &Window, id: u32) -> bool {
1493 let Some(idx) = self.node_idx_for_id(id) else {
1494 return false;
1495 };
1496 let old_id = window_output_id(window);
1497 let (new_output, pos) = {
1498 let node = &self.output_nodes[idx];
1499 (node.output.clone(), node.pos)
1500 };
1501 let old_output = self
1502 .node_idx_for_id(old_id)
1503 .map(|i| self.output_nodes[i].output.clone());
1504 if let Some(meta) = window_meta(window) {
1505 meta.output.store(id, Ordering::Relaxed);
1506 meta.parked.store(false, Ordering::Relaxed);
1507 }
1508 self.space.map_element(window.clone(), pos, true);
1509 self.refocus_pointer();
1510 if let Some(surface) = window.wl_surface() {
1511 if let Some(old) = old_output
1512 && old_id != id
1513 {
1514 old.leave(&surface);
1515 }
1516 new_output.enter(&surface);
1517 let scale = new_output.current_scale().fractional_scale();
1518 with_states(&surface, |states| {
1519 smithay::wayland::compositor::send_surface_state(
1520 &surface,
1521 states,
1522 scale.ceil() as i32,
1523 smithay::utils::Transform::Normal,
1524 );
1525 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
1526 fs.set_preferred_scale(scale);
1527 });
1528 });
1529 }
1530 if let Some(toplevel) = window.toplevel() {
1531 let toplevel = toplevel.clone();
1532 self.send_forced_fullscreen_configure(&toplevel);
1533 }
1534 true
1535 }
1536
1537 pub(crate) fn process_pending_clipboard_read(&mut self) {
1553 let Some(mimes) = self.pending_clipboard_read.take() else {
1554 return;
1555 };
1556 if crate::PY_SHUTDOWN.load(Ordering::Relaxed) {
1557 return;
1558 }
1559 let Some(cb) = self
1560 .clipboard_callback
1561 .as_ref()
1562 .map(|c| Python::attach(|py| c.clone_ref(py)))
1563 else {
1564 return;
1565 };
1566 let cleared = mimes.is_empty();
1567 let mut pipes = Vec::new();
1568 for mime in mimes {
1569 let Ok((reader, writer)) = std::io::pipe() else {
1570 continue;
1571 };
1572 if request_data_device_client_selection::<AppState>(
1573 &self.seat,
1574 mime.clone(),
1575 writer.into(),
1576 )
1577 .is_ok()
1578 {
1579 pipes.push((mime, reader));
1580 }
1581 }
1582 if pipes.is_empty() && !cleared {
1583 return;
1584 }
1585 let generations = self.clipboard_generation.clone();
1586 let generation = generations.fetch_add(1, Ordering::Relaxed) + 1;
1587 std::thread::spawn(move || {
1588 let entries: Vec<(String, Vec<u8>)> = pipes
1589 .into_iter()
1590 .filter_map(|(mime, reader)| read_selection(reader).map(|bytes| (mime, bytes)))
1591 .collect();
1592 if (entries.is_empty() && !cleared) || crate::PY_SHUTDOWN.load(Ordering::Relaxed) {
1593 return;
1594 }
1595 Python::attach(|py| {
1596 if generations.load(Ordering::Relaxed) != generation {
1597 return;
1598 }
1599 let entries: Vec<(String, Bound<'_, PyBytes>)> = entries
1600 .iter()
1601 .map(|(mime, bytes)| (mime.clone(), PyBytes::new(py, bytes)))
1602 .collect();
1603 let _ = cb.call1(py, (entries,));
1604 });
1605 });
1606 }
1607
1608 pub(crate) fn send_cursor_image(&mut self, image: &CursorImageStatus) {
1627 if !self.cursor_callback_set {
1628 return;
1629 }
1630 let job = match image {
1631 CursorImageStatus::Named(icon) => {
1632 self.cursor_buffer = None;
1633 CursorJob::Named {
1634 name: cursor_icon_to_str(icon),
1635 }
1636 }
1637 CursorImageStatus::Hidden => {
1638 self.cursor_buffer = None;
1639 CursorJob::Hide
1640 }
1641 CursorImageStatus::Surface(surface) => {
1642 let mut hot_x = 0;
1643 let mut hot_y = 0;
1644 let mut is_cursor_role = false;
1645
1646 with_states(surface, |states| {
1649 if states.role == Some("cursor_image") {
1650 is_cursor_role = true;
1651 }
1652 let buffer_scale = states
1653 .cached_state
1654 .get::<SurfaceAttributes>()
1655 .current()
1656 .buffer_scale
1657 .max(1);
1658 if let Some(attributes) = states.data_map.get::<Mutex<CursorImageAttributes>>()
1659 && let Ok(guard) = attributes.lock()
1660 {
1661 hot_x = guard.hotspot.x * buffer_scale;
1662 hot_y = guard.hotspot.y * buffer_scale;
1663 }
1664 });
1665
1666 if !is_cursor_role {
1667 return;
1668 }
1669
1670 let buffer_found = with_states(surface, |states| {
1671 let mut attrs = states.cached_state.get::<SurfaceAttributes>();
1672
1673 if let Some(BufferAssignment::NewBuffer(b)) = &attrs.current().buffer {
1674 return Some(b.clone());
1675 }
1676
1677 if let Some(mutex) = states.data_map.get::<Mutex<RendererSurfaceState>>()
1678 && let Ok(renderer_state) = mutex.try_lock()
1679 && let Some(b) = renderer_state.buffer()
1680 {
1681 let wl_buffer: &wayland_server::protocol::wl_buffer::WlBuffer = b;
1682 return Some(wl_buffer.clone());
1683 }
1684 None
1685 });
1686
1687 let Some(buffer) = buffer_found else { return };
1688
1689 let shm_result = with_buffer_contents(&buffer, |ptr, len, spec| {
1690 let slice = unsafe { std::slice::from_raw_parts(ptr, len) };
1691 let mut hasher = DefaultHasher::new();
1692 spec.width.hash(&mut hasher);
1693 spec.height.hash(&mut hasher);
1694 spec.stride.hash(&mut hasher);
1695 spec.offset.hash(&mut hasher);
1696 spec.format.hash(&mut hasher);
1697 let start = (spec.offset.max(0) as usize).min(len);
1698 let span =
1699 (spec.stride.max(0) as usize).saturating_mul(spec.height.max(0) as usize);
1700 let end = start.saturating_add(span).min(len);
1701 slice[start..end].hash(&mut hasher);
1702 let hash = hasher.finish();
1703 (
1704 hash,
1705 spec.width,
1706 spec.height,
1707 spec.stride,
1708 spec.format,
1709 spec.offset,
1710 slice.to_vec(),
1711 )
1712 });
1713
1714 let job = match shm_result {
1715 Ok((hash, width, height, stride, format, buf_offset, raw_bytes)) => {
1716 Some(CursorJob::Shm {
1717 hash,
1718 width,
1719 height,
1720 stride,
1721 offset: buf_offset,
1722 opaque: format == wl_shm::Format::Xrgb8888,
1723 bytes: raw_bytes,
1724 hot_x,
1725 hot_y,
1726 })
1727 }
1728 Err(BufferAccessError::NotManaged) => {
1729 let mut gles_job = None;
1730 let dmabuf_opt = get_dmabuf(&buffer).ok().cloned();
1731 if let Some(mut dmabuf) = dmabuf_opt
1732 && let Some(renderer) = self.gles_renderer.as_mut()
1733 {
1734 let width = dmabuf.width() as i32;
1735 let height = dmabuf.height() as i32;
1736
1737 match renderer.bind(&mut dmabuf) {
1738 Ok(frame) => {
1739 let rect =
1740 Rectangle::new((0, 0).into(), (width, height).into());
1741 match renderer.copy_framebuffer(&frame, rect, Fourcc::Abgr8888)
1742 {
1743 Ok(mapping) => match renderer.map_texture(&mapping) {
1744 Ok(data) => {
1745 let mut hasher = DefaultHasher::new();
1746 data.hash(&mut hasher);
1747 gles_job = Some(CursorJob::Gles {
1748 hash: hasher.finish(),
1749 width,
1750 height,
1751 bytes: data.to_vec(),
1752 hot_x,
1753 hot_y,
1754 });
1755 }
1756 Err(e) => eprintln!("Failed to map texture: {:?}", e),
1757 },
1758 Err(e) => eprintln!("Failed to copy framebuffer: {:?}", e),
1759 }
1760 }
1761 Err(e) => eprintln!("Failed to bind dmabuf to renderer: {:?}", e),
1762 }
1763 }
1764 gles_job
1765 }
1766 Err(_) => None,
1767 };
1768
1769 self.cursor_buffer = Some(buffer);
1770 let Some(job) = job else { return };
1771 job
1772 }
1773 };
1774 let _ = self.cursor_tx.send(job);
1775 }
1776
1777 pub(crate) fn flush_pending_cursor(&mut self) {
1780 if !self.cursor_surface_pending {
1781 return;
1782 }
1783 self.cursor_surface_pending = false;
1784 if let Some(icon) = self.current_cursor_icon.clone() {
1785 self.send_cursor_image(&icon);
1786 }
1787 }
1788
1789 pub(crate) fn apply_keymap_policy(&mut self) {
1792 let text = self.keymap_policy.keymap_text();
1793 self.apply_keymap_text(text);
1794 }
1795
1796 pub(crate) fn apply_keymap_text(&mut self, text: String) {
1799 if text.is_empty() {
1800 return;
1801 }
1802 if let Some(host) = &self.host {
1806 host.set_keymap(&text);
1807 }
1808 if let Some(keyboard) = self.seat.get_keyboard()
1809 && let Err(e) = keyboard.set_keymap_from_string(self, text)
1810 {
1811 eprintln!("[Wayland] keymap swap failed: {e:?}");
1812 }
1813 }
1814
1815 pub(crate) fn bind_keysyms(&mut self, keysyms: &[u32]) -> Vec<(u32, u32)> {
1820 let pressed: std::collections::HashSet<u32> = self
1821 .seat
1822 .get_keyboard()
1823 .map(|k| k.pressed_keys().iter().map(|c| c.raw()).collect())
1824 .unwrap_or_default();
1825 let (out, changed) = self.keymap_policy.bind_many(keysyms, &pressed);
1826 if changed {
1827 self.apply_keymap_policy();
1828 }
1829 out
1830 }
1831
1832 pub(crate) fn bind_keysyms_plain(&mut self, keysyms: &[u32]) -> Vec<u32> {
1836 let pressed: std::collections::HashSet<u32> = self
1837 .seat
1838 .get_keyboard()
1839 .map(|k| k.pressed_keys().iter().map(|c| c.raw()).collect())
1840 .unwrap_or_default();
1841 let (out, changed) = self.keymap_policy.bind_many_plain(keysyms, &pressed);
1842 if changed {
1843 self.apply_keymap_policy();
1844 }
1845 out
1846 }
1847
1848 pub(crate) fn send_forced_fullscreen_configure(&mut self, toplevel: &ToplevelSurface) {
1854 let output_id = self
1855 .space
1856 .elements()
1857 .chain(self.pending_windows.iter())
1858 .find(|w| w.toplevel().map(|t| t == toplevel).unwrap_or(false))
1859 .map(window_output_id)
1860 .unwrap_or(0);
1861 let parked = self
1862 .space
1863 .elements()
1864 .chain(self.pending_windows.iter())
1865 .find(|w| w.toplevel().map(|t| t == toplevel).unwrap_or(false))
1866 .and_then(window_meta)
1867 .map(|meta| meta.parked.load(Ordering::Relaxed))
1868 .unwrap_or(false);
1869 let (logical_width, logical_height) = if parked {
1870 PARKED_LOGICAL_SIZE
1871 } else if let Some(size) = self.logical_size_of(output_id) {
1872 size
1873 } else {
1874 let scale = self.settings.scale.max(0.1);
1875 (
1876 (self.settings.width as f64 / scale).round() as i32,
1877 (self.settings.height as f64 / scale).round() as i32,
1878 )
1879 };
1880 toplevel.with_pending_state(|state| {
1881 state.states.set(XdgState::Fullscreen);
1882 state.states.set(XdgState::Activated);
1883 state.size = Some((logical_width, logical_height).into());
1884 });
1885 toplevel.send_configure();
1886 }
1887}
1888
1889const CLIPBOARD_IMAGE_MIMES: &[&str] = &[
1893 "image/png",
1894 "image/jpeg",
1895 "image/webp",
1896 "image/bmp",
1897 "image/svg+xml",
1898 "image/svg",
1899];
1900const CLIPBOARD_TEXT_MIMES: &[&str] = &[
1901 "text/plain;charset=utf-8",
1902 "UTF8_STRING",
1903 "text/plain",
1904 "STRING",
1905 "TEXT",
1906];
1907const CLIPBOARD_SECRET_HINTS: &[&str] = &[
1912 "x-kde-passwordManagerHint",
1913 "text/x-kde-passwordManagerHint",
1914 "application/x-kde-passwordManagerHint",
1915];
1916
1917fn clipboard_flavors(offered: &[String]) -> Vec<String> {
1921 let has = |want: &str| offered.iter().any(|m| m == want);
1922 match CLIPBOARD_IMAGE_MIMES.iter().find(|m| has(m)) {
1923 Some(image) => vec![*image],
1924 None => ["text/html"]
1925 .iter()
1926 .filter(|m| has(m))
1927 .chain(CLIPBOARD_TEXT_MIMES.iter().find(|m| has(m)))
1928 .copied()
1929 .collect(),
1930 }
1931 .into_iter()
1932 .chain(CLIPBOARD_SECRET_HINTS.iter().copied().filter(|m| has(m)))
1933 .map(str::to_string)
1934 .collect()
1935}
1936
1937fn read_selection(reader: std::io::PipeReader) -> Option<Vec<u8>> {
1940 use std::io::Read;
1941 use std::os::fd::AsRawFd;
1942 const CAP: usize = 64 * 1024 * 1024;
1943 const IDLE_DEADLINE: std::time::Duration = std::time::Duration::from_secs(10);
1944 let mut last_data = Instant::now();
1945 let mut buf = Vec::new();
1946 let mut chunk = [0u8; 65536];
1947 loop {
1948 let remaining = IDLE_DEADLINE.checked_sub(last_data.elapsed())?;
1949 let mut pfd = libc::pollfd {
1950 fd: reader.as_raw_fd(),
1951 events: libc::POLLIN,
1952 revents: 0,
1953 };
1954 let timeout_ms = remaining.as_millis().min(i32::MAX as u128).max(1) as i32;
1955 let ready = unsafe { libc::poll(&mut pfd, 1, timeout_ms) };
1956 if ready < 0 {
1957 if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
1958 continue;
1959 }
1960 return None;
1961 }
1962 if ready == 0 {
1963 continue;
1964 }
1965 match (&reader).read(&mut chunk) {
1966 Ok(0) => break,
1967 Ok(n) => {
1968 last_data = Instant::now();
1969 let take_n = n.min(CAP - buf.len());
1970 buf.extend_from_slice(&chunk[..take_n]);
1971 if buf.len() == CAP {
1972 break;
1973 }
1974 }
1975 Err(e)
1976 if e.kind() == std::io::ErrorKind::Interrupted
1977 || e.kind() == std::io::ErrorKind::WouldBlock =>
1978 {
1979 continue;
1980 }
1981 Err(_) => return None,
1982 }
1983 }
1984 (!buf.is_empty()).then_some(buf)
1985}
1986
1987impl SelectionHandler for AppState {
1991 type SelectionUserData = std::sync::Arc<Vec<(String, Vec<u8>)>>;
1992
1993 fn new_selection(
2002 &mut self,
2003 ty: SelectionTarget,
2004 source: Option<SelectionSource>,
2005 seat: Seat<Self>,
2006 ) {
2007 if ty != SelectionTarget::Clipboard {
2008 return;
2009 }
2010 let chosen = source.map_or_else(Vec::new, |source| clipboard_flavors(&source.mime_types()));
2011 self.current_selection_mimes = chosen.clone();
2014 if crate::PY_SHUTDOWN.load(Ordering::Relaxed) {
2015 return;
2016 }
2017 if self.clipboard_callback.is_none() {
2018 return;
2019 }
2020 self.pending_clipboard_read = Some(chosen);
2021 let _ = seat;
2022 }
2023
2024 fn send_selection(
2028 &mut self,
2029 ty: SelectionTarget,
2030 mime_type: String,
2031 fd: std::os::fd::OwnedFd,
2032 _seat: Seat<Self>,
2033 user_data: &Self::SelectionUserData,
2034 ) {
2035 if ty != SelectionTarget::Clipboard && ty != SelectionTarget::Primary {
2036 return;
2037 }
2038 let entries = user_data.clone();
2041 let Some(index) = entries
2042 .iter()
2043 .position(|(mime, _)| *mime == mime_type)
2044 .or_else(|| {
2045 entries
2046 .iter()
2047 .position(|(mime, _)| mime.starts_with("text/plain"))
2048 .filter(|_| !mime_type.contains('/') || mime_type.starts_with("text/plain"))
2049 })
2050 else {
2051 return;
2052 };
2053 std::thread::spawn(move || {
2054 let _ = crate::wayland::wlclient::write_fd_all(
2055 &fd,
2056 &entries[index].1,
2057 crate::wayland::wlclient::IO_TIMEOUT,
2058 );
2059 });
2060 }
2061}
2062
2063impl DataDeviceHandler for AppState {
2066 fn data_device_state(&mut self) -> &mut DataDeviceState {
2067 &mut self.data_device_state
2068 }
2069}
2070impl DataControlHandler for AppState {
2073 fn data_control_state(&mut self) -> &mut DataControlState {
2074 &mut self.data_control_state
2075 }
2076}
2077impl smithay::input::tablet::TabletSeatHandler for AppState {
2080 type ToolFocus = WlSurface;
2081}
2082
2083impl ExtDataControlHandler for AppState {
2087 fn data_control_state(&mut self) -> &mut ExtDataControlState {
2088 &mut self.ext_data_control_state
2089 }
2090}
2091
2092impl WaylandDndGrabHandler for AppState {}
2094impl BufferHandler for AppState {
2096 fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
2097}
2098impl ShmHandler for AppState {
2100 fn shm_state(&self) -> &ShmState {
2101 &self.shm_state
2102 }
2103}
2104impl OutputHandler for AppState {}
2106
2107impl DmabufHandler for AppState {
2113 fn dmabuf_state(&mut self) -> &mut DmabufState {
2114 &mut self.dmabuf_state
2115 }
2116
2117 fn dmabuf_imported(
2118 &mut self,
2119 _global: &DmabufGlobal,
2120 dmabuf: Dmabuf,
2121 notifier: ImportNotifier,
2122 ) {
2123 if let Some(renderer) = self.gles_renderer.as_mut() {
2124 if renderer.import_dmabuf(&dmabuf, None).is_ok() {
2125 let _ = notifier.successful::<AppState>();
2126 } else {
2127 notifier.failed();
2128 }
2129 } else {
2130 notifier.failed();
2131 }
2132 }
2133}
2134
2135impl ImageCaptureSourceHandler for AppState {
2136 fn source_destroyed(&mut self, _source: ImageCaptureSource) {}
2137}
2138
2139impl OutputCaptureSourceHandler for AppState {
2140 fn output_capture_source_state(&mut self) -> &mut OutputCaptureSourceState {
2141 &mut self.output_capture_source_state
2142 }
2143
2144 fn output_source_created(&mut self, source: ImageCaptureSource, output: &Output) {
2145 source.user_data().insert_if_missing(|| output.downgrade());
2146 }
2147}
2148
2149impl ImageCopyCaptureHandler for AppState {
2155 fn image_copy_capture_state(&mut self) -> &mut ImageCopyCaptureState {
2156 &mut self.image_copy_capture_state
2157 }
2158
2159 fn capture_constraints(&mut self, source: &ImageCaptureSource) -> Option<BufferConstraints> {
2160 let output = source.user_data().get::<WeakOutput>()?.upgrade()?;
2161 output_capture_constraints(&output, self.gles_renderer.as_ref(), &self.render_node_path)
2162 }
2163
2164 fn new_session(&mut self, session: CopySession) {
2165 let Some(output) = session.source().user_data().get::<WeakOutput>().cloned() else {
2166 session.stop();
2167 return;
2168 };
2169 match self.capture_constraints(&session.source()) {
2170 Some(constraints) => {
2171 session.update_constraints(constraints);
2172 self.copy_sessions.push(OutputCopySession {
2173 session,
2174 output,
2175 pending: None,
2176 delivered_once: false,
2177 });
2178 }
2179 None => session.stop(),
2180 }
2181 }
2182
2183 fn frame(&mut self, session: &CopySessionRef, frame: CopyFrame) {
2184 match self
2185 .copy_sessions
2186 .iter_mut()
2187 .find(|cs| cs.session == *session)
2188 {
2189 Some(cs) => cs.pending = Some(frame),
2190 None => frame.fail(CaptureFailureReason::Unknown),
2191 }
2192 }
2193
2194 fn frame_aborted(&mut self, frame: CopyFrameRef) {
2195 for cs in self.copy_sessions.iter_mut() {
2196 if cs.pending.as_ref().is_some_and(|f| *f == frame) {
2197 cs.pending = None;
2198 }
2199 }
2200 }
2201
2202 fn session_destroyed(&mut self, session: CopySessionRef) {
2203 self.copy_sessions.retain(|cs| cs.session != session);
2204 }
2205}
2206
2207impl AppState {
2208 pub fn copy_frame_pending_for(&self, output: &Output) -> bool {
2209 self.copy_sessions
2210 .iter()
2211 .any(|cs| cs.pending.is_some() && cs.output.upgrade().as_ref() == Some(output))
2212 }
2213}
2214
2215pub fn output_capture_constraints(
2219 output: &Output,
2220 renderer: Option<&GlesRenderer>,
2221 render_node_path: &str,
2222) -> Option<BufferConstraints> {
2223 let mode = output.current_mode()?;
2224 let dma = renderer.and_then(|renderer| {
2225 let node = DrmNode::from_path(render_node_path).ok()?;
2226 let mut formats: Vec<(Fourcc, Vec<Modifier>)> = Vec::new();
2227 for f in Bind::<Dmabuf>::supported_formats(renderer)? {
2228 match formats.iter_mut().find(|(code, _)| *code == f.code) {
2229 Some((_, mods)) => mods.push(f.modifier),
2230 None => formats.push((f.code, vec![f.modifier])),
2231 }
2232 }
2233 (!formats.is_empty()).then_some(DmabufConstraints { node, formats })
2234 });
2235 Some(BufferConstraints {
2236 size: (mode.size.w, mode.size.h).into(),
2237 shm: vec![wl_shm::Format::Argb8888, wl_shm::Format::Xrgb8888],
2238 dma,
2239 })
2240}
2241
2242impl FractionalScaleHandler for AppState {
2245 fn new_fractional_scale(
2246 &mut self,
2247 surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface,
2248 ) {
2249 if let Some(output) = self.primary_output() {
2250 let scale = output.current_scale().fractional_scale();
2251 with_states(&surface, |states| {
2252 smithay::wayland::compositor::send_surface_state(
2253 &surface,
2254 states,
2255 scale.ceil() as i32,
2256 smithay::utils::Transform::Normal,
2257 );
2258 smithay::wayland::fractional_scale::with_fractional_scale(states, |fs| {
2259 fs.set_preferred_scale(scale);
2260 });
2261 });
2262 }
2263 }
2264}
2265
2266#[derive(Debug, Clone, PartialEq)]
2270#[allow(clippy::large_enum_variant)]
2271pub enum FocusTarget {
2272 Window(Window),
2273 Popup(PopupKind),
2274 LayerSurface(DesktopLayerSurface),
2275}
2276
2277fn popup_under(
2280 root: &WlSurface,
2281 origin: Point<i32, Logical>,
2282 p: Point<f64, Logical>,
2283) -> Option<(FocusTarget, Point<f64, Logical>)> {
2284 PopupManager::popups_for_surface(root).find_map(|(popup, location)| {
2285 let at = origin + location - popup.geometry().loc;
2286 under_from_surface_tree(popup.wl_surface(), p, at, WindowSurfaceType::ALL)
2287 .map(|_| (FocusTarget::Popup(popup), at.to_f64()))
2288 })
2289}
2290
2291impl From<Window> for FocusTarget {
2293 fn from(w: Window) -> Self {
2294 FocusTarget::Window(w)
2295 }
2296}
2297
2298impl From<PopupKind> for FocusTarget {
2300 fn from(p: PopupKind) -> Self {
2301 FocusTarget::Popup(p)
2302 }
2303}
2304
2305impl From<DesktopLayerSurface> for FocusTarget {
2307 fn from(l: DesktopLayerSurface) -> Self {
2308 FocusTarget::LayerSurface(l)
2309 }
2310}
2311
2312impl IsAlive for FocusTarget {
2315 fn alive(&self) -> bool {
2316 match self {
2317 FocusTarget::Window(w) => w.alive(),
2318 FocusTarget::Popup(p) => p.alive(),
2319 FocusTarget::LayerSurface(l) => l.alive(),
2320 }
2321 }
2322}
2323
2324impl WaylandFocus for FocusTarget {
2327 fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
2328 match self {
2329 FocusTarget::Window(w) => w.wl_surface(),
2330 FocusTarget::Popup(p) => Some(Cow::Borrowed(p.wl_surface())),
2331 FocusTarget::LayerSurface(l) => Some(Cow::Borrowed(l.wl_surface())),
2332 }
2333 }
2334 fn same_client_as(&self, object_id: &ObjectId) -> bool {
2335 match self {
2336 FocusTarget::Window(w) => w.same_client_as(object_id),
2337 FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id),
2338 FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id),
2339 }
2340 }
2341}
2342
2343impl KeyboardTarget<AppState> for FocusTarget {
2346 fn enter(
2347 &self,
2348 seat: &Seat<AppState>,
2349 data: &mut AppState,
2350 keys: Vec<KeysymHandle<'_>>,
2351 serial: Serial,
2352 ) {
2353 if let Some(surface) = self.wl_surface() {
2354 smithay::input::keyboard::KeyboardTarget::enter(
2355 surface.as_ref(),
2356 seat,
2357 data,
2358 keys,
2359 serial,
2360 );
2361 }
2362 }
2363 fn leave(&self, seat: &Seat<AppState>, data: &mut AppState, serial: Serial) {
2364 if let Some(surface) = self.wl_surface() {
2365 smithay::input::keyboard::KeyboardTarget::leave(surface.as_ref(), seat, data, serial);
2366 }
2367 }
2368 fn key(
2369 &self,
2370 seat: &Seat<AppState>,
2371 data: &mut AppState,
2372 key: KeysymHandle<'_>,
2373 state: smithay::backend::input::KeyState,
2374 serial: Serial,
2375 time: InputTime,
2376 ) {
2377 if let Some(surface) = self.wl_surface() {
2378 smithay::input::keyboard::KeyboardTarget::key(
2379 surface.as_ref(),
2380 seat,
2381 data,
2382 key,
2383 state,
2384 serial,
2385 time,
2386 );
2387 }
2388 }
2389 fn modifiers(
2390 &self,
2391 seat: &Seat<AppState>,
2392 data: &mut AppState,
2393 modifiers: ModifiersState,
2394 serial: Serial,
2395 ) {
2396 if let Some(surface) = self.wl_surface() {
2397 smithay::input::keyboard::KeyboardTarget::modifiers(
2398 surface.as_ref(),
2399 seat,
2400 data,
2401 modifiers,
2402 serial,
2403 );
2404 }
2405 }
2406}
2407
2408impl DndFocus<AppState> for FocusTarget {
2411 type OfferData<S: Source> = <WlSurface as DndFocus<AppState>>::OfferData<S>;
2412
2413 fn enter<S: Source>(
2414 &self,
2415 data: &mut AppState,
2416 dh: &DisplayHandle,
2417 source: Arc<S>,
2418 seat: &Seat<AppState>,
2419 location: Point<f64, Logical>,
2420 serial: &Serial,
2421 ) -> Option<Self::OfferData<S>> {
2422 if let Some(surface) = self.wl_surface() {
2423 <WlSurface as DndFocus<AppState>>::enter(
2424 surface.as_ref(),
2425 data,
2426 dh,
2427 source,
2428 seat,
2429 location,
2430 serial,
2431 )
2432 } else {
2433 None
2434 }
2435 }
2436
2437 fn motion<S: Source>(
2438 &self,
2439 data: &mut AppState,
2440 offer: Option<&mut Self::OfferData<S>>,
2441 seat: &Seat<AppState>,
2442 location: Point<f64, Logical>,
2443 time: InputTime,
2444 ) {
2445 if let Some(surface) = self.wl_surface() {
2446 <WlSurface as DndFocus<AppState>>::motion(
2447 surface.as_ref(),
2448 data,
2449 offer,
2450 seat,
2451 location,
2452 time,
2453 )
2454 }
2455 }
2456
2457 fn leave<S: Source>(
2458 &self,
2459 data: &mut AppState,
2460 offer: Option<&mut Self::OfferData<S>>,
2461 seat: &Seat<AppState>,
2462 ) {
2463 if let Some(surface) = self.wl_surface() {
2464 <WlSurface as DndFocus<AppState>>::leave(surface.as_ref(), data, offer, seat)
2465 }
2466 }
2467
2468 fn drop<S: Source>(
2469 &self,
2470 data: &mut AppState,
2471 offer: Option<&mut Self::OfferData<S>>,
2472 seat: &Seat<AppState>,
2473 ) {
2474 if let Some(surface) = self.wl_surface() {
2475 <WlSurface as DndFocus<AppState>>::drop(surface.as_ref(), data, offer, seat)
2476 }
2477 }
2478}
2479
2480impl PointerTarget<AppState> for FocusTarget {
2483 fn enter(&self, seat: &Seat<AppState>, data: &mut AppState, event: &MotionEvent) {
2484 if let Some(surface) = self.wl_surface() {
2485 smithay::input::pointer::PointerTarget::enter(surface.as_ref(), seat, data, event);
2486 }
2487 }
2488 fn motion(&self, seat: &Seat<AppState>, data: &mut AppState, event: &MotionEvent) {
2489 if let Some(surface) = self.wl_surface() {
2490 smithay::input::pointer::PointerTarget::motion(surface.as_ref(), seat, data, event);
2491 }
2492 }
2493 fn relative_motion(
2494 &self,
2495 seat: &Seat<AppState>,
2496 data: &mut AppState,
2497 event: &RelativeMotionEvent,
2498 ) {
2499 if let Some(surface) = self.wl_surface() {
2500 smithay::input::pointer::PointerTarget::relative_motion(
2501 surface.as_ref(),
2502 seat,
2503 data,
2504 event,
2505 );
2506 }
2507 }
2508 fn button(&self, seat: &Seat<AppState>, data: &mut AppState, event: &ButtonEvent) {
2509 if let Some(surface) = self.wl_surface() {
2510 smithay::input::pointer::PointerTarget::button(surface.as_ref(), seat, data, event);
2511 }
2512 }
2513 fn axis(&self, seat: &Seat<AppState>, data: &mut AppState, frame: AxisFrame) {
2514 if let Some(surface) = self.wl_surface() {
2515 smithay::input::pointer::PointerTarget::axis(surface.as_ref(), seat, data, frame);
2516 }
2517 }
2518 fn frame(&self, seat: &Seat<AppState>, data: &mut AppState) {
2519 if let Some(surface) = self.wl_surface() {
2520 smithay::input::pointer::PointerTarget::frame(surface.as_ref(), seat, data);
2521 }
2522 }
2523 fn leave(&self, seat: &Seat<AppState>, data: &mut AppState, serial: Serial, time: InputTime) {
2524 if let Some(surface) = self.wl_surface() {
2525 smithay::input::pointer::PointerTarget::leave(
2526 surface.as_ref(),
2527 seat,
2528 data,
2529 serial,
2530 time,
2531 );
2532 }
2533 }
2534 fn gesture_swipe_begin(
2535 &self,
2536 seat: &Seat<AppState>,
2537 data: &mut AppState,
2538 event: &GestureSwipeBeginEvent,
2539 ) {
2540 if let Some(surface) = self.wl_surface() {
2541 smithay::input::pointer::PointerTarget::gesture_swipe_begin(
2542 surface.as_ref(),
2543 seat,
2544 data,
2545 event,
2546 );
2547 }
2548 }
2549 fn gesture_swipe_update(
2550 &self,
2551 seat: &Seat<AppState>,
2552 data: &mut AppState,
2553 event: &GestureSwipeUpdateEvent,
2554 ) {
2555 if let Some(surface) = self.wl_surface() {
2556 smithay::input::pointer::PointerTarget::gesture_swipe_update(
2557 surface.as_ref(),
2558 seat,
2559 data,
2560 event,
2561 );
2562 }
2563 }
2564 fn gesture_swipe_end(
2565 &self,
2566 seat: &Seat<AppState>,
2567 data: &mut AppState,
2568 event: &GestureSwipeEndEvent,
2569 ) {
2570 if let Some(surface) = self.wl_surface() {
2571 smithay::input::pointer::PointerTarget::gesture_swipe_end(
2572 surface.as_ref(),
2573 seat,
2574 data,
2575 event,
2576 );
2577 }
2578 }
2579 fn gesture_pinch_begin(
2580 &self,
2581 seat: &Seat<AppState>,
2582 data: &mut AppState,
2583 event: &GesturePinchBeginEvent,
2584 ) {
2585 if let Some(surface) = self.wl_surface() {
2586 smithay::input::pointer::PointerTarget::gesture_pinch_begin(
2587 surface.as_ref(),
2588 seat,
2589 data,
2590 event,
2591 );
2592 }
2593 }
2594 fn gesture_pinch_update(
2595 &self,
2596 seat: &Seat<AppState>,
2597 data: &mut AppState,
2598 event: &GesturePinchUpdateEvent,
2599 ) {
2600 if let Some(surface) = self.wl_surface() {
2601 smithay::input::pointer::PointerTarget::gesture_pinch_update(
2602 surface.as_ref(),
2603 seat,
2604 data,
2605 event,
2606 );
2607 }
2608 }
2609 fn gesture_pinch_end(
2610 &self,
2611 seat: &Seat<AppState>,
2612 data: &mut AppState,
2613 event: &GesturePinchEndEvent,
2614 ) {
2615 if let Some(surface) = self.wl_surface() {
2616 smithay::input::pointer::PointerTarget::gesture_pinch_end(
2617 surface.as_ref(),
2618 seat,
2619 data,
2620 event,
2621 );
2622 }
2623 }
2624 fn gesture_hold_begin(
2625 &self,
2626 seat: &Seat<AppState>,
2627 data: &mut AppState,
2628 event: &GestureHoldBeginEvent,
2629 ) {
2630 if let Some(surface) = self.wl_surface() {
2631 smithay::input::pointer::PointerTarget::gesture_hold_begin(
2632 surface.as_ref(),
2633 seat,
2634 data,
2635 event,
2636 );
2637 }
2638 }
2639 fn gesture_hold_end(
2640 &self,
2641 seat: &Seat<AppState>,
2642 data: &mut AppState,
2643 event: &GestureHoldEndEvent,
2644 ) {
2645 if let Some(surface) = self.wl_surface() {
2646 smithay::input::pointer::PointerTarget::gesture_hold_end(
2647 surface.as_ref(),
2648 seat,
2649 data,
2650 event,
2651 );
2652 }
2653 }
2654}
2655
2656impl TouchTarget<AppState> for FocusTarget {
2659 fn down(&self, seat: &Seat<AppState>, data: &mut AppState, event: &DownEvent) {
2660 if let Some(surface) = self.wl_surface() {
2661 smithay::input::touch::TouchTarget::down(surface.as_ref(), seat, data, event);
2662 }
2663 }
2664 fn up(&self, seat: &Seat<AppState>, data: &mut AppState, event: &UpEvent) {
2665 if let Some(surface) = self.wl_surface() {
2666 smithay::input::touch::TouchTarget::up(surface.as_ref(), seat, data, event);
2667 }
2668 }
2669 fn motion(
2670 &self,
2671 seat: &Seat<AppState>,
2672 data: &mut AppState,
2673 event: &smithay::input::touch::MotionEvent,
2674 ) {
2675 if let Some(surface) = self.wl_surface() {
2676 smithay::input::touch::TouchTarget::motion(surface.as_ref(), seat, data, event);
2677 }
2678 }
2679 fn frame(&self, seat: &Seat<AppState>, data: &mut AppState, marker: FrameMarker) {
2680 if let Some(surface) = self.wl_surface() {
2681 smithay::input::touch::TouchTarget::frame(surface.as_ref(), seat, data, marker);
2682 }
2683 }
2684 fn cancel(&self, seat: &Seat<AppState>, data: &mut AppState, marker: FrameMarker) {
2685 if let Some(surface) = self.wl_surface() {
2686 smithay::input::touch::TouchTarget::cancel(surface.as_ref(), seat, data, marker);
2687 }
2688 }
2689 fn shape(&self, seat: &Seat<AppState>, data: &mut AppState, event: &ShapeEvent) {
2690 if let Some(surface) = self.wl_surface() {
2691 smithay::input::touch::TouchTarget::shape(surface.as_ref(), seat, data, event);
2692 }
2693 }
2694 fn orientation(&self, seat: &Seat<AppState>, data: &mut AppState, event: &OrientationEvent) {
2695 if let Some(surface) = self.wl_surface() {
2696 smithay::input::touch::TouchTarget::orientation(surface.as_ref(), seat, data, event);
2697 }
2698 }
2699 fn last_frame(&self, seat: &Seat<AppState>, data: &mut AppState) -> Option<FrameMarker> {
2700 self.wl_surface().and_then(|surface| {
2701 smithay::input::touch::TouchTarget::last_frame(surface.as_ref(), seat, data)
2702 })
2703 }
2704}
2705
2706pub fn cursor_icon_to_str(icon: &CursorIcon) -> &'static str {
2710 match icon {
2711 CursorIcon::Default => "default",
2712 CursorIcon::ContextMenu => "context-menu",
2713 CursorIcon::Help => "help",
2714 CursorIcon::Pointer => "pointer",
2715 CursorIcon::Progress => "progress",
2716 CursorIcon::Wait => "wait",
2717 CursorIcon::Cell => "cell",
2718 CursorIcon::Crosshair => "crosshair",
2719 CursorIcon::Text => "text",
2720 CursorIcon::VerticalText => "vertical-text",
2721 CursorIcon::Alias => "alias",
2722 CursorIcon::Copy => "copy",
2723 CursorIcon::Move => "move",
2724 CursorIcon::NoDrop => "no-drop",
2725 CursorIcon::NotAllowed => "not-allowed",
2726 CursorIcon::Grab => "grab",
2727 CursorIcon::Grabbing => "grabbing",
2728 CursorIcon::AllScroll => "all-scroll",
2729 CursorIcon::ColResize => "col-resize",
2730 CursorIcon::RowResize => "row-resize",
2731 CursorIcon::NResize => "n-resize",
2732 CursorIcon::EResize => "e-resize",
2733 CursorIcon::SResize => "s-resize",
2734 CursorIcon::WResize => "w-resize",
2735 CursorIcon::NeResize => "ne-resize",
2736 CursorIcon::NwResize => "nw-resize",
2737 CursorIcon::SeResize => "se-resize",
2738 CursorIcon::SwResize => "sw-resize",
2739 CursorIcon::EwResize => "ew-resize",
2740 CursorIcon::NsResize => "ns-resize",
2741 CursorIcon::NeswResize => "nesw-resize",
2742 CursorIcon::NwseResize => "nwse-resize",
2743 CursorIcon::ZoomIn => "zoom-in",
2744 CursorIcon::ZoomOut => "zoom-out",
2745 _ => "default",
2746 }
2747}
2748
2749impl SeatHandler for AppState {
2752 type KeyboardFocus = FocusTarget;
2753 type PointerFocus = FocusTarget;
2754 type TouchFocus = FocusTarget;
2755 fn seat_state(&mut self) -> &mut SeatState<AppState> {
2756 &mut self.seat_state
2757 }
2758
2759 fn cursor_image(&mut self, _seat: &Seat<AppState>, image: CursorImageStatus) {
2763 self.current_cursor_icon = Some(image.clone());
2764 self.cursor_surface_pending = matches!(image, CursorImageStatus::Surface(_));
2765 if !self.cursor_surface_pending {
2766 self.send_cursor_image(&image);
2767 }
2768 }
2769
2770 fn focus_changed(&mut self, seat: &Seat<AppState>, focus: Option<&Self::KeyboardFocus>) {
2775 let dh = &self.dh;
2776 let client = focus
2777 .and_then(|t| t.wl_surface())
2778 .and_then(|s| dh.get_client(s.id()).ok());
2779 set_data_device_focus(dh, seat, client.clone());
2780 set_primary_focus(dh, seat, client);
2781 }
2782}
2783
2784impl PointerWarpHandler for AppState {
2791 fn warp_pointer(
2792 &mut self,
2793 surface: WlSurface,
2794 _pointer: WlPointer,
2795 pos: Point<f64, Logical>,
2796 serial: Serial,
2797 ) {
2798 let surface_origin = self.space.elements().find_map(|window| {
2799 if window.wl_surface().as_deref() == Some(&surface) {
2800 self.space.element_location(window)
2801 } else {
2802 None
2803 }
2804 });
2805
2806 if let Some(origin) = surface_origin {
2807 let global_pos = origin.to_f64() + pos;
2808 let time = wayland_time();
2809
2810 if let Some(pointer) = self.seat.get_pointer() {
2811 let under = self
2812 .space
2813 .element_under(global_pos)
2814 .map(|(w, loc)| (FocusTarget::Window(w.clone()), loc.to_f64()));
2815
2816 pointer.motion(
2817 self,
2818 under,
2819 &MotionEvent {
2820 location: global_pos,
2821 serial,
2822 time,
2823 },
2824 );
2825 }
2826 }
2827 }
2828}
2829
2830impl XdgShellHandler for AppState {
2832 fn xdg_shell_state(&mut self) -> &mut XdgShellState {
2833 &mut self.shell_state
2834 }
2835 fn new_toplevel(&mut self, surface: ToplevelSurface) {
2841 let target_id = self.pointer_display();
2842 let window = Window::new_wayland_window(surface.clone());
2843 window
2844 .user_data()
2845 .insert_if_missing_threadsafe(|| WindowMeta {
2846 id: NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed),
2847 output: AtomicU32::new(target_id),
2848 placed: AtomicBool::new(false),
2849 parked: AtomicBool::new(false),
2850 });
2851 self.pending_windows.push(window);
2852 let (title, app_id) = with_states(surface.wl_surface(), |states| {
2853 let attributes = states
2854 .data_map
2855 .get::<XdgToplevelSurfaceData>()
2856 .unwrap()
2857 .lock()
2858 .unwrap();
2859 (attributes.title.clone(), attributes.app_id.clone())
2860 });
2861
2862 let handle = self
2863 .foreign_toplevel_list
2864 .new_toplevel::<AppState>(title.unwrap_or_default(), app_id.unwrap_or_default());
2865
2866 with_states(surface.wl_surface(), |states| {
2867 states.data_map.insert_if_missing(|| handle)
2868 });
2869 }
2870 fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
2874 if let Err(err) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
2875 eprintln!("Failed to track popup: {:?}", err);
2876 }
2877 let _ = surface.send_configure();
2878 }
2879 fn grab(
2882 &mut self,
2883 surface: PopupSurface,
2884 _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat,
2885 serial: Serial,
2886 ) {
2887 let kind = PopupKind::Xdg(surface);
2888 let Ok(root_surface) = smithay::desktop::find_popup_root_surface(&kind) else {
2889 return;
2890 };
2891 let root = self
2892 .space
2893 .elements()
2894 .find(|w| w.wl_surface().as_deref() == Some(&root_surface))
2895 .cloned()
2896 .map(FocusTarget::Window)
2897 .or_else(|| {
2898 self.output_nodes.iter().find_map(|node| {
2899 layer_map_for_output(&node.output)
2900 .layer_for_surface(&root_surface, WindowSurfaceType::TOPLEVEL)
2901 .cloned()
2902 .map(FocusTarget::LayerSurface)
2903 })
2904 });
2905 if let Some(root) = root {
2906 let _ = self.popups.grab_popup(root, kind, &self.seat, serial);
2907 }
2908 }
2909 fn reposition_request(
2913 &mut self,
2914 surface: PopupSurface,
2915 _positioner: PositionerState,
2916 token: u32,
2917 ) {
2918 if let Err(err) = self.popups.track_popup(PopupKind::Xdg(surface.clone())) {
2919 eprintln!("Failed to track popup: {:?}", err);
2920 }
2921 let _ = surface.send_repositioned(token);
2922 }
2923 fn fullscreen_request(
2928 &mut self,
2929 surface: ToplevelSurface,
2930 output: Option<smithay::reexports::wayland_server::protocol::wl_output::WlOutput>,
2931 ) {
2932 if let Some(id) = output.as_ref().and_then(|wlo| {
2933 self.output_nodes
2934 .iter()
2935 .find(|n| n.output.owns(wlo))
2936 .map(|n| n.id)
2937 }) {
2938 let mapped = self
2939 .space
2940 .elements()
2941 .find(|w| w.toplevel().map(|tl| *tl == surface).unwrap_or(false))
2942 .cloned();
2943 if let Some(window) = mapped {
2944 if self.place_window_on_output(&window, id) {
2945 return;
2946 }
2947 } else if let Some(meta) = self
2948 .pending_windows
2949 .iter()
2950 .find(|w| w.toplevel().map(|tl| *tl == surface).unwrap_or(false))
2951 .and_then(window_meta)
2952 {
2953 meta.output.store(id, Ordering::Relaxed);
2957 meta.placed.store(true, Ordering::Relaxed);
2958 }
2959 }
2960 self.send_forced_fullscreen_configure(&surface);
2961 }
2962 fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
2966 self.send_forced_fullscreen_configure(&surface);
2967 }
2968 fn maximize_request(&mut self, surface: ToplevelSurface) {
2970 self.send_forced_fullscreen_configure(&surface);
2971 }
2972 fn unmaximize_request(&mut self, surface: ToplevelSurface) {
2974 self.send_forced_fullscreen_configure(&surface);
2975 }
2976 fn toplevel_destroyed(&mut self, surface: ToplevelSurface) {
2982 if let Some(idx) = self
2983 .pending_windows
2984 .iter()
2985 .position(|w| w.toplevel().map(|t| *t == surface).unwrap_or(false))
2986 {
2987 self.pending_windows.remove(idx);
2988 }
2989 let mapped = self
2990 .space
2991 .elements()
2992 .find(|w| w.toplevel().map(|t| *t == surface).unwrap_or(false))
2993 .cloned();
2994 if let Some(window) = mapped {
2995 self.space.unmap_elem(&window);
2996 self.refocus_pointer();
2997 }
2998 if let Some(handle) = with_states(surface.wl_surface(), |states| {
2999 states.data_map.get::<ForeignToplevelHandle>().cloned()
3000 }) {
3001 self.foreign_toplevel_list.remove_toplevel(&handle);
3002 }
3003 }
3004}
3005
3006pub struct PfVirtualKeyboard {
3017 inner: Mutex<PfVkState>,
3018}
3019
3020#[derive(Default)]
3021struct PfVkState {
3022 syms: Option<std::collections::HashMap<u32, u32>>,
3024 pressed: std::collections::HashMap<u32, u32>,
3027}
3028
3029impl GlobalDispatch<ZwpVirtualKeyboardManagerV1, ()> for AppState {
3030 fn bind(
3031 _state: &mut Self,
3032 _dh: &DisplayHandle,
3033 _client: &Client,
3034 resource: New<ZwpVirtualKeyboardManagerV1>,
3035 _global_data: &(),
3036 data_init: &mut DataInit<'_, Self>,
3037 ) {
3038 data_init.init(resource, ());
3039 }
3040}
3041
3042impl Dispatch<ZwpVirtualKeyboardManagerV1, ()> for AppState {
3043 fn request(
3044 _state: &mut Self,
3045 _client: &Client,
3046 _resource: &ZwpVirtualKeyboardManagerV1,
3047 request: zwp_virtual_keyboard_manager_v1::Request,
3048 _data: &(),
3049 _dh: &DisplayHandle,
3050 data_init: &mut DataInit<'_, Self>,
3051 ) {
3052 if let zwp_virtual_keyboard_manager_v1::Request::CreateVirtualKeyboard { seat: _, id } =
3053 request
3054 {
3055 data_init.init(
3056 id,
3057 PfVirtualKeyboard {
3058 inner: Mutex::new(PfVkState::default()),
3059 },
3060 );
3061 }
3062 }
3063}
3064
3065impl Dispatch<ZwpVirtualKeyboardV1, PfVirtualKeyboard> for AppState {
3066 fn request(
3067 state: &mut Self,
3068 _client: &Client,
3069 resource: &ZwpVirtualKeyboardV1,
3070 request: zwp_virtual_keyboard_v1::Request,
3071 data: &PfVirtualKeyboard,
3072 _dh: &DisplayHandle,
3073 _data_init: &mut DataInit<'_, Self>,
3074 ) {
3075 use smithay::input::keyboard::xkb;
3076 match request {
3077 zwp_virtual_keyboard_v1::Request::Keymap { format, fd, size } => {
3078 if format != 1 {
3079 return;
3080 }
3081 let ctx = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
3082 let keymap = unsafe {
3083 xkb::Keymap::new_from_fd(
3084 &ctx,
3085 fd,
3086 size as usize,
3087 xkb::KEYMAP_FORMAT_TEXT_V1,
3088 xkb::KEYMAP_COMPILE_NO_FLAGS,
3089 )
3090 };
3091 let Ok(Some(keymap)) = keymap else {
3092 eprintln!("[Wayland] virtual-keyboard keymap failed to compile; ignoring.");
3093 return;
3094 };
3095 let syms = crate::wayland::keymap::level0_syms(&keymap);
3096 let missing: Vec<u32> = syms
3099 .values()
3100 .copied()
3101 .filter(|&s| !state.keymap_policy.resolves_plain(s))
3102 .collect();
3103 if !missing.is_empty() {
3104 let _ = state.bind_keysyms_plain(&missing);
3105 }
3106 data.inner.lock().unwrap().syms = Some(syms);
3107 }
3108 zwp_virtual_keyboard_v1::Request::Key {
3109 time: _,
3110 key,
3111 state: key_state,
3112 } => {
3113 let mut vk = data.inner.lock().unwrap();
3114 if vk.syms.is_none() {
3115 drop(vk);
3116 resource.post_error(
3117 zwp_virtual_keyboard_v1::Error::NoKeymap,
3118 "`key` sent before keymap.",
3119 );
3120 return;
3121 }
3122 let vk_kc = key.wrapping_add(8);
3123 let seat_kc = if key_state == 1 {
3124 let sym = vk.syms.as_ref().and_then(|m| m.get(&vk_kc)).copied();
3125 let kc = match sym {
3129 Some(sym) => {
3130 let bound = state.bind_keysyms_plain(&[sym])[0];
3131 if bound != 0 { bound } else { vk_kc }
3132 }
3133 None => vk_kc,
3134 };
3135 vk.pressed.insert(vk_kc, kc);
3136 kc
3137 } else {
3138 vk.pressed.remove(&vk_kc).unwrap_or(vk_kc)
3139 };
3140 drop(vk);
3141 let pressed = key_state == 1;
3142 if let Some(keyboard) = state.seat.get_keyboard() {
3143 let keyboard = keyboard.clone();
3144 let serial = next_serial();
3145 let time = wayland_time();
3146 keyboard.input(
3147 state,
3148 smithay::backend::input::Keycode::new(seat_kc),
3149 if pressed {
3150 smithay::backend::input::KeyState::Pressed
3151 } else {
3152 smithay::backend::input::KeyState::Released
3153 },
3154 serial,
3155 time,
3156 |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
3157 );
3158 }
3159 }
3160 zwp_virtual_keyboard_v1::Request::Modifiers { .. } => {}
3161 zwp_virtual_keyboard_v1::Request::Destroy => {}
3162 _ => {}
3163 }
3164 }
3165
3166 fn destroyed(
3169 state: &mut Self,
3170 _client: ClientId,
3171 _resource: &ZwpVirtualKeyboardV1,
3172 data: &PfVirtualKeyboard,
3173 ) {
3174 let held: Vec<u32> = data
3175 .inner
3176 .lock()
3177 .unwrap()
3178 .pressed
3179 .drain()
3180 .map(|(_, kc)| kc)
3181 .collect();
3182 if held.is_empty() {
3183 return;
3184 }
3185 if let Some(keyboard) = state.seat.get_keyboard() {
3186 let keyboard = keyboard.clone();
3187 for kc in held {
3188 let serial = next_serial();
3189 let time = wayland_time();
3190 keyboard.input(
3191 state,
3192 smithay::backend::input::Keycode::new(kc),
3193 smithay::backend::input::KeyState::Released,
3194 serial,
3195 time,
3196 |_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
3197 );
3198 }
3199 }
3200 }
3201}
3202
3203#[derive(Default)]
3206pub struct ClientState {
3207 pub compositor_state: CompositorClientState,
3208}
3209impl ClientData for ClientState {
3211 fn initialized(&self, _client_id: ClientId) {}
3212 fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
3213}
3214
3215delegate_dispatch2!(AppState);
3216
3217pub(crate) fn rgba_readback_stride(buf_len: usize, height: usize, width: usize) -> usize {
3224 let row = width.saturating_mul(4);
3225 if height == 0 {
3226 return row;
3227 }
3228 (buf_len / height).max(row)
3229}
3230
3231#[cfg(test)]
3232mod stride_tests {
3233 use super::rgba_readback_stride;
3234
3235 #[test]
3237 fn packed_readback_is_width_times_four() {
3238 assert_eq!(rgba_readback_stride(64 * 4 * 48, 48, 64), 64 * 4);
3239 assert_eq!(rgba_readback_stride(3 * 4 * 2, 2, 3), 12);
3240 }
3241
3242 #[test]
3245 fn padded_readback_recovers_real_stride() {
3246 let (w, h, pad) = (3usize, 2usize, 4usize);
3247 let stride = w * 4 + pad;
3248 assert_eq!(rgba_readback_stride(stride * h, h, w), stride);
3249 }
3250
3251 #[test]
3254 fn padded_extraction_has_no_skew() {
3255 let (w, h) = (3usize, 2usize);
3256 let stride = 16usize;
3257 let mut buf = vec![0u8; stride * h];
3258 for y in 0..h {
3259 for x in 0..w {
3260 let o = y * stride + x * 4;
3261 buf[o] = x as u8 + 1;
3262 buf[o + 1] = y as u8 + 1;
3263 }
3264 }
3265 let s = rgba_readback_stride(buf.len(), h, w);
3266 assert_eq!(s, stride);
3267 for y in 0..h {
3268 for x in 0..w {
3269 let o = y * s + x * 4;
3270 assert_eq!(buf[o], x as u8 + 1);
3271 assert_eq!(buf[o + 1], y as u8 + 1);
3272 }
3273 }
3274 }
3275
3276 #[test]
3278 fn zero_height_no_divide_by_zero() {
3279 assert_eq!(rgba_readback_stride(0, 0, 10), 40);
3280 }
3281
3282 #[test]
3284 fn truncated_buffer_keeps_full_row() {
3285 assert_eq!(rgba_readback_stride(10, 5, 64), 64 * 4);
3286 }
3287}
3288
3289#[cfg(test)]
3290mod clipboard_flavor_tests {
3291 use super::{CLIPBOARD_SECRET_HINTS, clipboard_flavors};
3292
3293 fn offer(mimes: &[&str]) -> Vec<String> {
3294 clipboard_flavors(&mimes.iter().map(|m| m.to_string()).collect::<Vec<_>>())
3295 }
3296
3297 #[test]
3299 fn a_secret_copy_is_read_with_its_hint() {
3300 for hint in CLIPBOARD_SECRET_HINTS {
3301 assert_eq!(
3302 offer(&["TEXT", "text/plain", "text/plain;charset=utf-8", hint]),
3303 vec!["text/plain;charset=utf-8".to_string(), hint.to_string()]
3304 );
3305 }
3306 }
3307
3308 #[test]
3310 fn an_ordinary_copy_reads_as_before() {
3311 assert_eq!(offer(&["text/plain", "image/png"]), vec!["image/png"]);
3312 assert_eq!(
3313 offer(&["UTF8_STRING", "text/html"]),
3314 vec!["text/html", "UTF8_STRING"]
3315 );
3316 }
3317
3318 #[test]
3320 fn an_unreadable_copy_stages_nothing() {
3321 assert!(offer(&["application/x-private"]).is_empty());
3322 }
3323}
3324
3325#[cfg(test)]
3326mod pointer_share_tests {
3327 use super::{PointerAt, PointerShare};
3328 use std::sync::Arc;
3329
3330 fn at(x: f64, y: f64, motion: u64) -> PointerAt {
3331 PointerAt {
3332 x,
3333 y,
3334 scale: 1.5,
3335 motion,
3336 }
3337 }
3338
3339 #[test]
3342 fn a_published_position_reads_back_whole() {
3343 let share = PointerShare::default();
3344 assert_eq!(share.read(), None);
3345 share.publish(at(1920.5, 33.25, 7));
3346 assert_eq!(share.read(), Some(at(1920.5, 33.25, 7)));
3347 share.publish(at(-12.0, 7679.75, 9));
3348 assert_eq!(share.read(), Some(at(-12.0, 7679.75, 9)));
3349 }
3350
3351 #[test]
3354 fn a_read_racing_the_writer_is_one_pass() {
3355 let share = Arc::new(PointerShare::default());
3356 let writer = {
3357 let share = share.clone();
3358 std::thread::spawn(move || {
3359 for n in 1..200_000u64 {
3360 share.publish(at(n as f64, (n % 4096) as f64, n));
3361 }
3362 })
3363 };
3364 let mut seen = 0;
3365 while !writer.is_finished() || seen == 0 {
3366 if let Some(p) = share.read() {
3367 assert_eq!(p.x, p.motion as f32 as f64);
3368 assert_eq!(p.y, (p.motion % 4096) as f64);
3369 seen += 1;
3370 }
3371 }
3372 writer.join().unwrap();
3373 }
3374}