Skip to main content

wingy_wayland/window/
mod.rs

1//! The Wayland window.
2
3use std::ffi::c_void;
4use std::ptr::NonNull;
5use std::sync::atomic::AtomicBool;
6use std::sync::{Arc, Mutex};
7
8use dpi::{LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, Position, Size};
9use rwh_06::RawWindowHandle;
10use sctk::reexports::client::Proxy;
11use sctk::reexports::client::protocol::wl_surface::WlSurface;
12use sctk::shell::WaylandSurface;
13use sctk::shell::xdg::window::{Window as SctkWindow, WindowDecorations};
14use tracing::warn;
15use wingy_core::cursor::Cursor;
16use wingy_core::error::{CreateWindowError, NotSupportedError, RequestError};
17use wingy_core::event_loop::AsyncRequestSerial;
18use wingy_core::monitor::{Fullscreen, MonitorHandle as CoreMonitorHandle};
19use wingy_core::window::{
20    CursorGrabMode, ImeCapabilities, ImeRequest, ImeRequestError, ResizeDirection, Theme,
21    UserAttentionType, Window as CoreWindow, WindowAttributes, WindowButtons, WindowId,
22    WindowLevel,
23};
24
25use super::ActiveEventLoop;
26use super::types::xdg_activation::XdgActivationTokenData;
27use crate::window::common::WindowCommon;
28use crate::{WindowAttributesWayland, output};
29pub(crate) mod state;
30pub use state::WindowState;
31pub(crate) mod handles;
32pub use handles::Handles;
33use handles::WindowRequests;
34pub(crate) mod common;
35
36/// The Wayland window.
37#[derive(Debug)]
38pub struct Window {
39    /// Reference to the underlying SCTK window.
40    window: SctkWindow,
41
42    /// Keeps the window's state alive for as long as this handle exists, independently of
43    /// whatever the event loop's `windows` map does with its own reference.
44    ///
45    /// Unlike `Popup`, which only ever holds a `Weak` (via `common.state`) so that a
46    /// compositor-initiated destroy can drop the state out from under a still-live handle, a
47    /// top-level `Window` has no equivalent unilateral server-side destruction to react to --
48    /// it can only be closed by the application dropping this handle. This field is never read;
49    /// it exists solely to uphold that guarantee.
50    #[expect(dead_code, reason = "kept alive for its Drop, not read")]
51    window_state: Arc<Mutex<WindowState>>,
52
53    common: WindowCommon,
54}
55
56impl Window {
57    pub(crate) fn new(
58        event_loop_window_target: &ActiveEventLoop,
59        mut attributes: WindowAttributes,
60    ) -> Result<Self, CreateWindowError> {
61        let queue_handle = event_loop_window_target.queue_handle.clone();
62        let mut state = event_loop_window_target.state.borrow_mut();
63
64        let monitors = state.monitors.clone();
65
66        let surface = state.compositor_state.create_surface(&queue_handle);
67        let compositor = state.compositor_state.clone();
68        let xdg_activation =
69            state.xdg_activation.as_ref().map(|activation_state| activation_state.global().clone());
70        let display = event_loop_window_target.handle.connection.display();
71
72        let size: Size = attributes.surface_size.unwrap_or(LogicalSize::new(800., 600.).into());
73
74        // We prefer server side decorations, however to not have decorations we ask for client
75        // side decorations instead.
76        let default_decorations = if attributes.decorations {
77            WindowDecorations::RequestServer
78        } else {
79            WindowDecorations::RequestClient
80        };
81
82        let window =
83            state.xdg_shell.create_window(surface.clone(), default_decorations, &queue_handle);
84
85        let WindowAttributesWayland { name: app_name, activation_token, prefer_csd, .. } =
86            *attributes
87                .platform
88                .take()
89                .and_then(|p| p.cast::<WindowAttributesWayland>().ok())
90                .unwrap_or_default();
91
92        let mut scale_factor = None;
93        if let Some(RawWindowHandle::Wayland(handle)) = attributes.parent_window() {
94            if let Some(s) =
95                state.windows.borrow().get(&WindowId::from_raw(handle.surface.as_ptr() as usize))
96            {
97                scale_factor = Some(s.lock().unwrap().scale_factor());
98            }
99        }
100        let scale_factor = scale_factor.unwrap_or(1.0);
101
102        let mut window_state = WindowState::new(
103            event_loop_window_target,
104            &state,
105            size,
106            state::WindowType::Window { window: window.clone(), last_configure: None },
107            attributes.preferred_theme,
108            prefer_csd,
109            scale_factor,
110            None,
111        );
112
113        window_state.set_window_icon(attributes.window_icon);
114
115        // Set transparency hint.
116        window_state.set_transparent(attributes.transparent);
117
118        // Set blur.
119        let _ = window_state.set_blur(attributes.blur);
120
121        // Set the decorations hint.
122        window_state.set_decorate(attributes.decorations);
123
124        // Set the app_id.
125        if let Some(name) = app_name.map(|name| name.general) {
126            window.set_app_id(name);
127        }
128
129        // Set the window title.
130        window_state.set_title(attributes.title);
131
132        // Set the min and max sizes. We must set the hints upon creating a window, so
133        // we use the default `1.` scaling...
134        let min_size = attributes.min_surface_size.map(|size| size.to_logical(1.));
135        let max_size = attributes.max_surface_size.map(|size| size.to_logical(1.));
136        window_state.set_min_surface_size(min_size);
137        window_state.set_max_surface_size(max_size);
138
139        // Non-resizable implies that the min and max sizes are set to the same value.
140        window_state.set_resizable(attributes.resizable);
141
142        // Set startup mode.
143        match attributes.fullscreen {
144            Some(Fullscreen::Exclusive(..)) => {
145                warn!("`Fullscreen::Exclusive` is ignored on Wayland");
146            },
147            Some(Fullscreen::Borderless(monitor)) => {
148                let output = monitor.as_ref().and_then(|monitor| {
149                    monitor.cast_ref::<output::MonitorHandle>().map(|handle| &handle.proxy)
150                });
151
152                window.set_fullscreen(output)
153            },
154            _ if attributes.maximized => window.set_maximized(),
155            _ => (),
156        };
157
158        window_state.set_cursor(attributes.cursor);
159
160        // Apply resize increments.
161        if let Some(increments) = attributes.surface_resize_increments {
162            let increments = increments.to_logical(window_state.scale_factor());
163            window_state.set_resize_increments(Some(increments));
164        }
165
166        // Activate the window when the token is passed.
167        if let (Some(xdg_activation), Some(token)) = (xdg_activation.as_ref(), activation_token) {
168            xdg_activation.activate(token.into_raw(), &surface);
169        }
170
171        // XXX Do initial commit.
172        window.commit();
173
174        // Add the window and window requests into the state.
175        let window_state = Arc::new(Mutex::new(window_state));
176        let window_id = super::make_wid(&surface);
177        state.windows.get_mut().insert(window_id, window_state.clone());
178
179        let window_requests = WindowRequests {
180            redraw_requested: AtomicBool::new(true),
181            closed: AtomicBool::new(false),
182        };
183        let window_requests = Arc::new(window_requests);
184        state.window_requests.get_mut().insert(window_id, window_requests.clone());
185
186        // Setup the event sync to insert `WindowEvents` right from the window.
187        let window_events_sink = state.window_events_sink.clone();
188
189        let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
190        let event_queue = wayland_source.queue();
191
192        // Do a roundtrip.
193        event_queue.roundtrip(&mut state).map_err(|err| os_error!(err))?;
194
195        // XXX Wait for the initial configure to arrive.
196        while !window_state.lock().unwrap().is_configured() {
197            event_queue.blocking_dispatch(&mut state).map_err(|err| os_error!(err))?;
198        }
199
200        // Wake-up event loop, so it'll send initial redraw requested.
201        let event_loop_awakener = event_loop_window_target.event_loop_awakener.clone();
202        event_loop_awakener.ping();
203
204        let state_weak = Arc::downgrade(&window_state);
205
206        Ok(Self {
207            window,
208            window_state,
209            common: WindowCommon {
210                state: state_weak,
211                window_id,
212                display,
213                handles: Handles {
214                    queue_handle,
215                    window_requests,
216                    monitors,
217                    event_loop_awakener,
218                    window_events_sink,
219
220                    compositor,
221
222                    xdg_activation,
223                    attention_requested: Arc::new(AtomicBool::new(false)),
224                },
225            },
226        })
227    }
228
229    pub(crate) fn xdg_toplevel(&self) -> Option<NonNull<c_void>> {
230        NonNull::new(self.window.xdg_toplevel().id().as_ptr().cast())
231    }
232}
233
234impl Window {
235    pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, RequestError> {
236        let xdg_activation = match self.common.handles.xdg_activation.as_ref() {
237            Some(xdg_activation) => xdg_activation,
238            None => return Err(NotSupportedError::new("xdg_activation_v1 is not available").into()),
239        };
240
241        let serial = AsyncRequestSerial::get();
242
243        let data = XdgActivationTokenData::Obtain((self.common.id(), serial));
244        let xdg_activation_token =
245            xdg_activation.get_activation_token(&self.common.handles.queue_handle, data);
246        xdg_activation_token.set_surface(self.surface());
247        xdg_activation_token.commit();
248
249        Ok(serial)
250    }
251
252    #[inline]
253    pub fn surface(&self) -> &WlSurface {
254        self.window.wl_surface()
255    }
256}
257
258impl CoreWindow for Window {
259    fn window_type(&self) -> wingy_core::window::WindowType {
260        wingy_core::window::WindowType::Window
261    }
262
263    fn id(&self) -> WindowId {
264        self.common.id()
265    }
266
267    fn request_redraw(&self) {
268        self.common.request_redraw();
269    }
270
271    #[inline]
272    fn title(&self) -> String {
273        self.common.title()
274    }
275
276    fn pre_present_notify(&self) {
277        self.common.pre_present_notify();
278    }
279
280    fn reset_dead_keys(&self) {
281        self.common.reset_dead_keys();
282    }
283
284    fn surface_position(&self) -> PhysicalPosition<i32> {
285        (0, 0).into()
286    }
287
288    fn outer_position(&self) -> Result<PhysicalPosition<i32>, RequestError> {
289        Err(NotSupportedError::new("window position information is not available on Wayland")
290            .into())
291    }
292
293    fn set_outer_position(&self, _position: Position) {
294        // Not possible.
295    }
296
297    fn surface_size(&self) -> PhysicalSize<u32> {
298        self.common.surface_size()
299    }
300
301    fn request_surface_size(&self, size: Size) -> Option<PhysicalSize<u32>> {
302        self.common.request_surface_size(size)
303    }
304
305    fn outer_size(&self) -> PhysicalSize<u32> {
306        self.common.outer_size()
307    }
308
309    fn safe_area(&self) -> PhysicalInsets<u32> {
310        self.common.safe_area()
311    }
312
313    fn set_min_surface_size(&self, min_size: Option<Size>) {
314        self.common.set_min_surface_size(min_size);
315    }
316
317    /// Set the maximum surface size for the window.
318    #[inline]
319    fn set_max_surface_size(&self, max_size: Option<Size>) {
320        self.common.set_max_surface_size(max_size);
321    }
322
323    fn surface_resize_increments(&self) -> Option<PhysicalSize<u32>> {
324        self.common.surface_resize_increments()
325    }
326
327    fn set_surface_resize_increments(&self, increments: Option<Size>) {
328        self.common.set_surface_resize_increments(increments);
329    }
330
331    fn set_title(&self, title: &str) {
332        self.common.set_title(title);
333    }
334
335    #[inline]
336    fn set_transparent(&self, transparent: bool) {
337        self.common.set_transparent(transparent);
338    }
339
340    fn set_visible(&self, visible: bool) {
341        self.common.set_visible(visible);
342    }
343
344    fn is_visible(&self) -> Option<bool> {
345        self.common.is_visible()
346    }
347
348    fn set_resizable(&self, resizable: bool) {
349        self.common.set_resizable(resizable);
350    }
351
352    fn is_resizable(&self) -> bool {
353        self.common.is_resizable()
354    }
355
356    fn set_enabled_buttons(&self, _buttons: WindowButtons) {
357        // TODO(kchibisov) v5 of the xdg_shell allows that.
358    }
359
360    fn enabled_buttons(&self) -> WindowButtons {
361        // TODO(kchibisov) v5 of the xdg_shell allows that.
362        WindowButtons::all()
363    }
364
365    fn set_minimized(&self, minimized: bool) {
366        // You can't unminimize the window on Wayland.
367        if !minimized {
368            warn!("Unminimizing is ignored on Wayland.");
369            return;
370        }
371
372        self.window.set_minimized();
373    }
374
375    fn is_minimized(&self) -> Option<bool> {
376        // XXX clients don't know whether they are minimized or not.
377        None
378    }
379
380    fn set_maximized(&self, maximized: bool) {
381        self.common.set_maximized(maximized)
382    }
383
384    fn is_maximized(&self) -> bool {
385        self.common.is_maximized()
386    }
387
388    fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
389        self.common.set_fullscreen(fullscreen)
390    }
391
392    fn fullscreen(&self) -> Option<Fullscreen> {
393        self.common.fullscreen()
394    }
395
396    #[inline]
397    fn scale_factor(&self) -> f64 {
398        self.common.scale_factor()
399    }
400
401    #[inline]
402    fn set_blur(&self, blur: bool) {
403        self.common.set_blur(blur);
404    }
405
406    #[inline]
407    fn set_decorations(&self, decorate: bool) {
408        self.common.set_decorations(decorate);
409    }
410
411    #[inline]
412    fn is_decorated(&self) -> bool {
413        self.common.is_decorated().unwrap()
414    }
415
416    fn set_window_level(&self, level: WindowLevel) {
417        self.common.set_window_level(level);
418    }
419
420    fn set_window_icon(&self, window_icon: Option<wingy_core::icon::Icon>) {
421        self.common.set_window_icon(window_icon);
422    }
423
424    #[inline]
425    fn request_ime_update(&self, request: ImeRequest) -> Result<(), ImeRequestError> {
426        self.common.request_ime_update(request)
427    }
428
429    #[inline]
430    fn ime_capabilities(&self) -> Option<ImeCapabilities> {
431        self.common.ime_capabilities()
432    }
433
434    fn focus_window(&self) {
435        self.common.focus_window();
436    }
437
438    fn has_focus(&self) -> bool {
439        self.common.has_focus()
440    }
441
442    fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
443        self.common.request_user_attention(request_type);
444    }
445
446    fn set_theme(&self, theme: Option<Theme>) {
447        self.common.set_theme(theme);
448    }
449
450    fn theme(&self) -> Option<Theme> {
451        self.common.theme()
452    }
453
454    fn set_content_protected(&self, protected: bool) {
455        self.common.set_content_protected(protected);
456    }
457
458    fn set_cursor(&self, cursor: Cursor) {
459        self.common.set_cursor(cursor);
460    }
461
462    fn set_cursor_position(&self, position: Position) -> Result<(), RequestError> {
463        self.common.set_cursor_position(position)
464    }
465
466    fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), RequestError> {
467        self.common.set_cursor_grab(mode)
468    }
469
470    fn set_cursor_visible(&self, visible: bool) {
471        self.common.set_cursor_visible(visible);
472    }
473
474    fn drag_window(&self) -> Result<(), RequestError> {
475        self.common.drag_window()
476    }
477
478    fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), RequestError> {
479        self.common.drag_resize_window(direction)
480    }
481
482    fn show_window_menu(&self, position: Position) {
483        self.common.show_window_menu(position);
484    }
485
486    fn set_cursor_hittest(&self, hittest: bool) -> Result<(), RequestError> {
487        self.common.set_cursor_hittest(hittest)
488    }
489
490    fn current_monitor(&self) -> Option<CoreMonitorHandle> {
491        self.common.current_monitor()
492    }
493
494    fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
495        self.common.available_monitors()
496    }
497
498    fn primary_monitor(&self) -> Option<CoreMonitorHandle> {
499        self.common.primary_monitor()
500    }
501
502    /// Get the raw-window-handle v0.6 display handle.
503    fn rwh_06_display_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
504        &self.common
505    }
506
507    /// Get the raw-window-handle v0.6 window handle.
508    fn rwh_06_window_handle(&self) -> &dyn rwh_06::HasWindowHandle {
509        &self.common
510    }
511}