winit/platform_impl/linux/
window.rs

1use std::{
2    cell::RefCell,
3    collections::VecDeque,
4    rc::Rc,
5    sync::atomic::{AtomicBool, AtomicI32, Ordering},
6};
7
8use gdk::{prelude::DisplayExtManual, WindowEdge, WindowState};
9use glib::{translate::ToGlibPtr, Cast, ObjectType};
10use gtk::{
11    prelude::GtkSettingsExt,
12    traits::{ApplicationWindowExt, ContainerExt, GtkWindowExt, WidgetExt},
13    Settings,
14};
15use raw_window_handle::{
16    RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
17    XlibDisplayHandle, XlibWindowHandle,
18};
19
20use crate::{
21    dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
22    error::{ExternalError, NotSupportedError, OsError as RootOsError},
23    platform_impl::WindowId,
24    window::{
25        CursorGrabMode, CursorIcon, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
26        WindowAttributes, WindowButtons, WindowLevel,
27    },
28};
29
30use super::{
31    util, EventLoopWindowTarget, Fullscreen, MonitorHandle, PlatformSpecificWindowBuilderAttributes,
32};
33
34// Currently GTK doesn't provide feature for detect theme, so we need to check theme manually.
35// ref: https://github.com/WebKit/WebKit/blob/e44ffaa0d999a9807f76f1805943eea204cfdfbc/Source/WebKit/UIProcess/API/gtk/PageClientImpl.cpp#L587
36const GTK_THEME_SUFFIX_LIST: [&str; 3] = ["-dark", "-Dark", "-Darker"];
37
38pub(crate) enum WindowRequest {
39    Title(String),
40    Position((i32, i32)),
41    Size((i32, i32)),
42    SizeConstraints(Option<Size>, Option<Size>),
43    Visible(bool),
44    Focus,
45    Resizable(bool),
46    // Closable(bool),
47    Minimized(bool),
48    Maximized(bool),
49    DragWindow,
50    Fullscreen(Option<Fullscreen>),
51    Decorations(bool),
52    AlwaysOnBottom(bool),
53    AlwaysOnTop(bool),
54    WindowIcon(Option<Icon>),
55    UserAttention(Option<UserAttentionType>),
56    SetSkipTaskbar(bool),
57    CursorIcon(Option<CursorIcon>),
58    CursorPosition((i32, i32)),
59    CursorIgnoreEvents(bool),
60    WireUpEvents { transparent: Rc<AtomicBool> },
61    // SetVisibleOnAllWorkspaces(bool),
62    // ProgressBarState(ProgressBarState),
63}
64
65pub struct Window {
66    /// Window id.
67    pub(crate) window_id: WindowId,
68    /// Gtk application window.
69    pub(crate) window: gtk::ApplicationWindow,
70    pub(crate) default_vbox: Option<gtk::Box>,
71    /// Window requests sender
72    pub(crate) window_requests_tx: glib::Sender<(WindowId, WindowRequest)>,
73    scale_factor: Rc<AtomicI32>,
74    position: Rc<(AtomicI32, AtomicI32)>,
75    size: Rc<(AtomicI32, AtomicI32)>,
76    maximized: Rc<AtomicBool>,
77    minimized: Rc<AtomicBool>,
78    fullscreen: RefCell<Option<Fullscreen>>,
79    min_size: RefCell<Option<Size>>,
80    max_size: RefCell<Option<Size>>,
81    transparent: Rc<AtomicBool>,
82    /// Draw event Sender
83    draw_tx: crossbeam_channel::Sender<WindowId>,
84}
85impl Window {
86    #[inline]
87    pub(crate) fn new<T>(
88        window_target: &EventLoopWindowTarget<T>,
89        attribs: WindowAttributes,
90        pl_attribs: PlatformSpecificWindowBuilderAttributes,
91    ) -> Result<Self, RootOsError> {
92        let app = &window_target.app;
93        let window_requests_tx = window_target.window_requests_tx.clone();
94        let draw_tx = window_target.draw_tx.clone();
95        let window = gtk::ApplicationWindow::builder()
96            .application(app)
97            .accept_focus(attribs.active)
98            .build();
99        let window_id = WindowId(window.id() as u64);
100        window_target.windows.borrow_mut().insert(window_id);
101
102        // Set Width/Height & Resizable
103        let win_scale_factor = window.scale_factor();
104        let (width, height) = attribs
105            .inner_size
106            .map(|size| size.to_logical::<f64>(win_scale_factor as f64).into())
107            .unwrap_or((800, 600));
108        window.set_default_size(1, 1);
109        window.resize(width, height);
110
111        if attribs.maximized {
112            window.maximize();
113        }
114        window.set_resizable(attribs.resizable);
115        // window.set_deletable(attribs.closable);
116
117        // Set Min/Max Size
118        util::set_size_constraints(&window, attribs.min_inner_size, attribs.max_inner_size);
119
120        // Set Position
121        if let Some(position) = attribs.position {
122            let (x, y): (i32, i32) = position.to_logical::<i32>(win_scale_factor as f64).into();
123            window.move_(x, y);
124        }
125
126        // Set GDK Visual
127        if pl_attribs.rgba_visual || attribs.transparent {
128            if let Some(screen) = GtkWindowExt::screen(&window) {
129                if let Some(visual) = screen.rgba_visual() {
130                    window.set_visual(Some(&visual));
131                }
132            }
133        }
134
135        if pl_attribs.app_paintable || attribs.transparent {
136            // Set a few attributes to make the window can be painted.
137            // See Gtk drawing model for more info:
138            // https://docs.gtk.org/gtk3/drawing-model.html
139            window.set_app_paintable(true);
140        }
141
142        if !pl_attribs.double_buffered {
143            let widget = window.upcast_ref::<gtk::Widget>();
144            if !window_target.is_wayland() {
145                unsafe {
146                    gtk::ffi::gtk_widget_set_double_buffered(widget.to_glib_none().0, 0);
147                }
148            }
149        }
150
151        // Add vbox for utilities like menu
152        let default_vbox = if pl_attribs.default_vbox {
153            let box_ = gtk::Box::new(gtk::Orientation::Vertical, 0);
154            window.add(&box_);
155            Some(box_)
156        } else {
157            None
158        };
159
160        // Rest attributes
161        window.set_title(&attribs.title);
162        let fullscreen = attribs.fullscreen.map(|f| f.into());
163        if fullscreen != None {
164            let m = match fullscreen {
165                Some(Fullscreen::Exclusive(ref m)) => Some(&m.monitor),
166                Some(Fullscreen::Borderless(Some(ref m))) => Some(&m.monitor),
167                _ => None,
168            };
169            if let Some(monitor) = m {
170                let display = window.display();
171                let monitors = display.n_monitors();
172                for i in 0..monitors {
173                    let m = display.monitor(i).unwrap();
174                    if &m == monitor {
175                        let screen = display.default_screen();
176                        window.fullscreen_on_monitor(&screen, i);
177                    }
178                }
179            } else {
180                window.fullscreen();
181            }
182        }
183        window.set_visible(attribs.visible);
184        window.set_decorated(attribs.decorations);
185
186        match attribs.window_level {
187            WindowLevel::AlwaysOnBottom => window.set_keep_below(true),
188            WindowLevel::Normal => (),
189            WindowLevel::AlwaysOnTop => window.set_keep_above(true),
190        }
191
192        // TODO imeplement this?
193        // if attribs.visible_on_all_workspaces {
194        //     window.stick();
195        // }
196
197        if let Some(icon) = attribs.window_icon {
198            window.set_icon(Some(&icon.inner.into()));
199        }
200
201        // Set theme
202        let settings = Settings::default();
203
204        if let Some(settings) = settings {
205            if let Some(preferred_theme) = attribs.preferred_theme {
206                match preferred_theme {
207                    Theme::Dark => settings.set_gtk_application_prefer_dark_theme(true),
208                    Theme::Light => {
209                        let theme_name = settings.gtk_theme_name().map(|t| t.as_str().to_owned());
210                        if let Some(theme) = theme_name {
211                            // Remove dark variant.
212                            if let Some(theme) = GTK_THEME_SUFFIX_LIST
213                                .iter()
214                                .find(|t| theme.ends_with(*t))
215                                .map(|v| theme.strip_suffix(v))
216                            {
217                                settings.set_gtk_theme_name(theme);
218                            }
219                        }
220                    }
221                }
222            }
223        }
224
225        if attribs.visible {
226            window.show_all();
227        } else {
228            window.hide();
229        }
230
231        // TODO it's impossible to set parent window from raw handle.
232        // We need a gtk variant of it.
233        if let Some(parent) = pl_attribs.parent {
234            window.set_transient_for(Some(&parent));
235        }
236
237        // TODO I don't understand why unfocussed window need focus
238        // restore accept-focus after the window has been drawn
239        // if the window was initially created without focus
240        // if !attributes.focused {
241        //     let signal_id = Arc::new(RefCell::new(None));
242        //     let signal_id_ = signal_id.clone();
243        //     let id = window.connect_draw(move |window, _| {
244        //         if let Some(id) = signal_id_.take() {
245        //             window.set_accept_focus(true);
246        //             window.disconnect(id);
247        //         }
248        //         glib::Propagation::Proceed
249        //     });
250        //     signal_id.borrow_mut().replace(id);
251        // }
252
253        // Set window position and size callback
254        let w_pos = window.position();
255        let position: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_pos.0.into(), w_pos.1.into()));
256        let position_clone = position.clone();
257
258        let w_size = window.size();
259        let size: Rc<(AtomicI32, AtomicI32)> = Rc::new((w_size.0.into(), w_size.1.into()));
260        let size_clone = size.clone();
261
262        window.connect_configure_event(move |_, event| {
263            let (x, y) = event.position();
264            position_clone.0.store(x, Ordering::Release);
265            position_clone.1.store(y, Ordering::Release);
266
267            let (w, h) = event.size();
268            size_clone.0.store(w as i32, Ordering::Release);
269            size_clone.1.store(h as i32, Ordering::Release);
270
271            false
272        });
273
274        // Set minimized/maximized callback
275        let w_max = window.is_maximized();
276        let maximized: Rc<AtomicBool> = Rc::new(w_max.into());
277        let max_clone = maximized.clone();
278        let minimized = Rc::new(AtomicBool::new(false));
279        let minimized_clone = minimized.clone();
280
281        window.connect_window_state_event(move |_, event| {
282            let state = event.new_window_state();
283            max_clone.store(state.contains(WindowState::MAXIMIZED), Ordering::Release);
284            minimized_clone.store(state.contains(WindowState::ICONIFIED), Ordering::Release);
285            glib::Propagation::Proceed
286        });
287
288        // Set scale factor callback
289        let scale_factor: Rc<AtomicI32> = Rc::new(win_scale_factor.into());
290        let scale_factor_clone = scale_factor.clone();
291        window.connect_scale_factor_notify(move |window| {
292            scale_factor_clone.store(window.scale_factor(), Ordering::Release);
293        });
294
295        // Check if we should paint the transparent background ourselves.
296        let mut transparent = false;
297        if attribs.transparent && pl_attribs.auto_transparent {
298            transparent = true;
299        }
300        let transparent = Rc::new(AtomicBool::new(transparent));
301
302        // Send WireUp event to let eventloop handle the rest of window setup to prevent gtk panic
303        // in other thread.
304        if let Err(e) = window_requests_tx.send((
305            window_id,
306            WindowRequest::WireUpEvents {
307                transparent: transparent.clone(),
308            },
309        )) {
310            log::warn!("Fail to send wire up events request: {}", e);
311        }
312
313        if let Err(e) = draw_tx.send(window_id) {
314            log::warn!("Failed to send redraw event to event channel: {}", e);
315        }
316
317        let win = Self {
318            window_id,
319            window,
320            default_vbox,
321            window_requests_tx,
322            draw_tx,
323            scale_factor,
324            position,
325            size,
326            maximized,
327            minimized,
328            fullscreen: RefCell::new(fullscreen),
329            min_size: RefCell::new(attribs.min_inner_size),
330            max_size: RefCell::new(attribs.min_inner_size),
331            transparent,
332        };
333
334        win.set_skip_taskbar(pl_attribs.skip_taskbar);
335
336        Ok(win)
337    }
338
339    #[inline]
340    pub fn id(&self) -> WindowId {
341        self.window_id
342    }
343
344    #[inline]
345    pub fn set_title(&self, title: &str) {
346        if let Err(e) = self
347            .window_requests_tx
348            .send((self.window_id, WindowRequest::Title(title.to_string())))
349        {
350            log::warn!("Fail to send title request: {}", e);
351        }
352    }
353
354    #[inline]
355    pub fn set_transparent(&self, transparent: bool) {
356        self.transparent.store(transparent, Ordering::Relaxed);
357    }
358
359    #[inline]
360    pub fn set_visible(&self, visible: bool) {
361        if let Err(e) = self
362            .window_requests_tx
363            .send((self.window_id, WindowRequest::Visible(visible)))
364        {
365            log::warn!("Fail to send visible request: {}", e);
366        }
367    }
368
369    #[inline]
370    pub fn is_visible(&self) -> Option<bool> {
371        Some(self.window.is_visible())
372    }
373
374    #[inline]
375    pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
376        let (x, y) = &*self.position;
377        Ok(
378            LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire))
379                .to_physical(self.scale_factor.load(Ordering::Acquire) as f64),
380        )
381    }
382    #[inline]
383    pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
384        let (x, y) = &*self.position;
385        Ok(
386            LogicalPosition::new(x.load(Ordering::Acquire), y.load(Ordering::Acquire))
387                .to_physical(self.scale_factor.load(Ordering::Acquire) as f64),
388        )
389    }
390    #[inline]
391    pub fn set_outer_position(&self, position: Position) {
392        let (x, y): (i32, i32) = position.to_logical::<i32>(self.scale_factor()).into();
393
394        if let Err(e) = self
395            .window_requests_tx
396            .send((self.window_id, WindowRequest::Position((x, y))))
397        {
398            log::warn!("Fail to send position request: {}", e);
399        }
400    }
401    #[inline]
402    pub fn inner_size(&self) -> PhysicalSize<u32> {
403        let (width, height) = &*self.size;
404
405        LogicalSize::new(
406            width.load(Ordering::Acquire) as u32,
407            height.load(Ordering::Acquire) as u32,
408        )
409        .to_physical(self.scale_factor.load(Ordering::Acquire) as f64)
410    }
411
412    #[inline]
413    pub fn outer_size(&self) -> PhysicalSize<u32> {
414        let (width, height) = &*self.size;
415
416        LogicalSize::new(
417            width.load(Ordering::Acquire) as u32,
418            height.load(Ordering::Acquire) as u32,
419        )
420        .to_physical(self.scale_factor.load(Ordering::Acquire) as f64)
421    }
422
423    #[inline]
424    pub fn set_inner_size(&self, size: Size) {
425        let (width, height) = size.to_logical::<i32>(self.scale_factor()).into();
426
427        if let Err(e) = self
428            .window_requests_tx
429            .send((self.window_id, WindowRequest::Size((width, height))))
430        {
431            log::warn!("Fail to send size request: {}", e);
432        }
433    }
434
435    fn set_size_constraints(&self) {
436        if let Err(e) = self.window_requests_tx.send((
437            self.window_id,
438            WindowRequest::SizeConstraints(*self.min_size.borrow(), *self.max_size.borrow()),
439        )) {
440            log::warn!("Fail to send size constraint request: {}", e);
441        }
442    }
443
444    #[inline]
445    pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
446        let mut min_size = self.min_size.borrow_mut();
447        *min_size = dimensions;
448        self.set_size_constraints()
449    }
450
451    #[inline]
452    pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
453        let mut max_size = self.min_size.borrow_mut();
454        *max_size = dimensions;
455        self.set_size_constraints()
456    }
457
458    #[inline]
459    pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> {
460        // TODO implement this
461        None
462    }
463
464    #[inline]
465    pub fn set_resize_increments(&self, _increments: Option<Size>) {
466        // TODO implement this
467    }
468
469    #[inline]
470    pub fn set_resizable(&self, resizable: bool) {
471        if let Err(e) = self
472            .window_requests_tx
473            .send((self.window_id, WindowRequest::Resizable(resizable)))
474        {
475            log::warn!("Fail to send resizable request: {}", e);
476        }
477    }
478
479    #[inline]
480    pub fn is_resizable(&self) -> bool {
481        self.window.is_resizable()
482    }
483
484    #[inline]
485    pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {
486        // TODO implement this
487    }
488
489    #[inline]
490    pub fn enabled_buttons(&self) -> WindowButtons {
491        // TODO implement this
492        WindowButtons::empty()
493    }
494
495    #[inline]
496    pub fn set_cursor_icon(&self, cursor: CursorIcon) {
497        if let Err(e) = self
498            .window_requests_tx
499            .send((self.window_id, WindowRequest::CursorIcon(Some(cursor))))
500        {
501            log::warn!("Fail to send cursor icon request: {}", e);
502        }
503    }
504
505    #[inline]
506    pub fn set_cursor_grab(&self, _mode: CursorGrabMode) -> Result<(), ExternalError> {
507        // TODO implement this
508        Ok(())
509    }
510
511    #[inline]
512    pub fn set_cursor_visible(&self, visible: bool) {
513        let cursor = if visible {
514            Some(CursorIcon::Default)
515        } else {
516            None
517        };
518        if let Err(e) = self
519            .window_requests_tx
520            .send((self.window_id, WindowRequest::CursorIcon(cursor)))
521        {
522            log::warn!("Fail to send cursor visibility request: {}", e);
523        }
524    }
525
526    #[inline]
527    pub fn drag_window(&self) -> Result<(), ExternalError> {
528        if let Err(e) = self
529            .window_requests_tx
530            .send((self.window_id, WindowRequest::DragWindow))
531        {
532            log::warn!("Fail to send drag window request: {}", e);
533        }
534        Ok(())
535    }
536
537    #[inline]
538    pub fn drag_resize_window(&self, _direction: ResizeDirection) -> Result<(), ExternalError> {
539        // TODO implement this
540        Ok(())
541    }
542
543    #[inline]
544    pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
545        if let Err(e) = self
546            .window_requests_tx
547            .send((self.window_id, WindowRequest::CursorIgnoreEvents(!hittest)))
548        {
549            log::warn!("Fail to send cursor position request: {}", e);
550        }
551
552        Ok(())
553    }
554
555    #[inline]
556    pub fn scale_factor(&self) -> f64 {
557        self.scale_factor.load(Ordering::Acquire) as f64
558    }
559
560    #[inline]
561    pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
562        let inner_pos = self.inner_position().unwrap_or_default();
563        let (x, y): (i32, i32) = position.to_logical::<i32>(self.scale_factor()).into();
564
565        if let Err(e) = self.window_requests_tx.send((
566            self.window_id,
567            WindowRequest::CursorPosition((x + inner_pos.x, y + inner_pos.y)),
568        )) {
569            log::warn!("Fail to send cursor position request: {}", e);
570        }
571
572        Ok(())
573    }
574
575    #[inline]
576    pub fn set_maximized(&self, maximized: bool) {
577        if let Err(e) = self
578            .window_requests_tx
579            .send((self.window_id, WindowRequest::Maximized(maximized)))
580        {
581            log::warn!("Fail to send maximized request: {}", e);
582        }
583    }
584
585    #[inline]
586    pub fn is_maximized(&self) -> bool {
587        self.maximized.load(Ordering::Acquire)
588    }
589
590    #[inline]
591    pub fn set_minimized(&self, minimized: bool) {
592        if let Err(e) = self
593            .window_requests_tx
594            .send((self.window_id, WindowRequest::Minimized(minimized)))
595        {
596            log::warn!("Fail to send minimized request: {}", e);
597        }
598    }
599
600    #[inline]
601    pub fn is_minimized(&self) -> Option<bool> {
602        Some(self.minimized.load(Ordering::Acquire))
603    }
604
605    #[inline]
606    pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
607        self.fullscreen.borrow().clone()
608    }
609
610    #[inline]
611    pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
612        self.fullscreen.replace(monitor.clone());
613        if let Err(e) = self
614            .window_requests_tx
615            .send((self.window_id, WindowRequest::Fullscreen(monitor)))
616        {
617            log::warn!("Fail to send fullscreen request: {}", e);
618        }
619    }
620
621    #[inline]
622    pub fn set_decorations(&self, decorations: bool) {
623        if let Err(e) = self
624            .window_requests_tx
625            .send((self.window_id, WindowRequest::Decorations(decorations)))
626        {
627            log::warn!("Fail to send decorations request: {}", e);
628        }
629    }
630
631    #[inline]
632    pub fn is_decorated(&self) -> bool {
633        self.window.is_decorated()
634    }
635
636    #[inline]
637    pub fn set_window_level(&self, level: WindowLevel) {
638        match level {
639            WindowLevel::AlwaysOnBottom => {
640                if let Err(e) = self
641                    .window_requests_tx
642                    .send((self.window_id, WindowRequest::AlwaysOnTop(true)))
643                {
644                    log::warn!("Fail to send always on top request: {}", e);
645                }
646            }
647            WindowLevel::Normal => (),
648            WindowLevel::AlwaysOnTop => {
649                if let Err(e) = self
650                    .window_requests_tx
651                    .send((self.window_id, WindowRequest::AlwaysOnBottom(true)))
652                {
653                    log::warn!("Fail to send always on bottom request: {}", e);
654                }
655            }
656        }
657    }
658
659    #[inline]
660    pub fn set_window_icon(&self, window_icon: Option<Icon>) {
661        if let Err(e) = self
662            .window_requests_tx
663            .send((self.window_id, WindowRequest::WindowIcon(window_icon)))
664        {
665            log::warn!("Fail to send window icon request: {}", e);
666        }
667    }
668
669    #[inline]
670    pub fn set_ime_position(&self, _position: Position) {
671        // TODO implement this
672    }
673
674    #[inline]
675    pub fn set_ime_allowed(&self, _allowed: bool) {
676        // TODO implement this
677    }
678
679    #[inline]
680    pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
681        // TODO implement this
682    }
683
684    #[inline]
685    pub fn focus_window(&self) {
686        if !self.minimized.load(Ordering::Acquire) && self.window.get_visible() {
687            if let Err(e) = self
688                .window_requests_tx
689                .send((self.window_id, WindowRequest::Focus))
690            {
691                log::warn!("Fail to send visible request: {}", e);
692            }
693        }
694    }
695
696    pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
697        if let Err(e) = self
698            .window_requests_tx
699            .send((self.window_id, WindowRequest::UserAttention(request_type)))
700        {
701            log::warn!("Fail to send user attention request: {}", e);
702        }
703    }
704
705    #[inline]
706    pub fn request_redraw(&self) {
707        if let Err(e) = self.draw_tx.send(self.window_id) {
708            log::warn!("Failed to send redraw event to event channel: {}", e);
709        }
710    }
711
712    #[inline]
713    pub fn current_monitor(&self) -> Option<MonitorHandle> {
714        let display = self.window.display();
715        // `.window()` returns `None` if the window is invisible;
716        // we fallback to the primary monitor
717        self.window
718            .window()
719            .map(|window| display.monitor_at_window(&window))
720            .unwrap_or_else(|| display.primary_monitor())
721            .map(|monitor| MonitorHandle { monitor })
722    }
723
724    #[inline]
725    pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
726        let mut handles = VecDeque::new();
727        let display = self.window.display();
728        let numbers = display.n_monitors();
729
730        for i in 0..numbers {
731            let monitor = MonitorHandle::new(&display, i);
732            handles.push_back(monitor);
733        }
734
735        handles
736    }
737
738    #[inline]
739    pub fn primary_monitor(&self) -> Option<MonitorHandle> {
740        let display = self.window.display();
741        display
742            .primary_monitor()
743            .map(|monitor| MonitorHandle { monitor })
744    }
745
746    fn is_wayland(&self) -> bool {
747        self.window.display().backend().is_wayland()
748    }
749
750    #[inline]
751    pub fn raw_window_handle(&self) -> RawWindowHandle {
752        if self.is_wayland() {
753            let mut window_handle = WaylandWindowHandle::empty();
754            if let Some(window) = self.window.window() {
755                window_handle.surface = unsafe {
756                    gdk_wayland_sys::gdk_wayland_window_get_wl_surface(window.as_ptr() as *mut _)
757                };
758            }
759
760            RawWindowHandle::Wayland(window_handle)
761        } else {
762            let mut window_handle = XlibWindowHandle::empty();
763            unsafe {
764                if let Some(window) = self.window.window() {
765                    window_handle.window =
766                        gdk_x11_sys::gdk_x11_window_get_xid(window.as_ptr() as *mut _);
767                }
768            }
769            RawWindowHandle::Xlib(window_handle)
770        }
771    }
772
773    #[inline]
774    pub fn raw_display_handle(&self) -> RawDisplayHandle {
775        if self.is_wayland() {
776            let mut display_handle = WaylandDisplayHandle::empty();
777            display_handle.display = unsafe {
778                gdk_wayland_sys::gdk_wayland_display_get_wl_display(
779                    self.window.display().as_ptr() as *mut _
780                )
781            };
782            RawDisplayHandle::Wayland(display_handle)
783        } else {
784            let mut display_handle = XlibDisplayHandle::empty();
785            unsafe {
786                if let Ok(xlib) = x11_dl::xlib::Xlib::open() {
787                    let display = (xlib.XOpenDisplay)(std::ptr::null());
788                    display_handle.display = display as _;
789                    display_handle.screen = (xlib.XDefaultScreen)(display) as _;
790                }
791            }
792            RawDisplayHandle::Xlib(display_handle)
793        }
794    }
795
796    #[inline]
797    pub fn set_theme(&self, theme: Option<Theme>) {
798        if let Some(settings) = Settings::default() {
799            if let Some(preferred_theme) = theme {
800                match preferred_theme {
801                    Theme::Dark => settings.set_gtk_application_prefer_dark_theme(true),
802                    Theme::Light => {
803                        let theme_name = settings.gtk_theme_name().map(|t| t.as_str().to_owned());
804                        if let Some(theme) = theme_name {
805                            // Remove dark variant.
806                            if let Some(theme) = GTK_THEME_SUFFIX_LIST
807                                .iter()
808                                .find(|t| theme.ends_with(*t))
809                                .map(|v| theme.strip_suffix(v))
810                            {
811                                settings.set_gtk_theme_name(theme);
812                            }
813                        }
814                    }
815                }
816            }
817        }
818    }
819
820    #[inline]
821    pub fn theme(&self) -> Option<Theme> {
822        if let Some(settings) = Settings::default() {
823            let theme_name = settings.gtk_theme_name().map(|s| s.as_str().to_owned());
824            if let Some(theme) = theme_name {
825                if GTK_THEME_SUFFIX_LIST.iter().any(|t| theme.ends_with(t)) {
826                    return Some(Theme::Dark);
827                }
828            }
829        }
830        Some(Theme::Light)
831    }
832
833    #[inline]
834    pub fn has_focus(&self) -> bool {
835        self.window.is_active()
836    }
837
838    pub fn title(&self) -> String {
839        self.window
840            .title()
841            .map(|t| t.as_str().to_string())
842            .unwrap_or_default()
843    }
844
845    pub fn set_skip_taskbar(&self, skip: bool) {
846        if let Err(e) = self
847            .window_requests_tx
848            .send((self.window_id, WindowRequest::SetSkipTaskbar(skip)))
849        {
850            log::warn!("Fail to send skip taskbar request: {}", e);
851        }
852    }
853}
854
855// We need to keep GTK window which isn't thread safe.
856// We make sure all non thread safe window calls are sent to event loop to handle.
857unsafe impl Send for Window {}
858unsafe impl Sync for Window {}
859
860/// A constant used to determine how much inside the window, the resize handler should appear (only used in Linux(gtk) and Windows).
861/// You probably need to scale it by the scale_factor of the window.
862pub const BORDERLESS_RESIZE_INSET: i32 = 5;
863
864pub fn hit_test(window: &gdk::Window, cx: f64, cy: f64) -> WindowEdge {
865    let (left, top) = window.position();
866    let (w, h) = (window.width(), window.height());
867    let (right, bottom) = (left + w, top + h);
868    let (cx, cy) = (cx as i32, cy as i32);
869
870    const LEFT: i32 = 0b0001;
871    const RIGHT: i32 = 0b0010;
872    const TOP: i32 = 0b0100;
873    const BOTTOM: i32 = 0b1000;
874    const TOPLEFT: i32 = TOP | LEFT;
875    const TOPRIGHT: i32 = TOP | RIGHT;
876    const BOTTOMLEFT: i32 = BOTTOM | LEFT;
877    const BOTTOMRIGHT: i32 = BOTTOM | RIGHT;
878
879    let inset = BORDERLESS_RESIZE_INSET * window.scale_factor();
880    #[rustfmt::skip]
881  let result =
882      (LEFT * (if cx < (left + inset) { 1 } else { 0 }))
883    | (RIGHT * (if cx >= (right - inset) { 1 } else { 0 }))
884    | (TOP * (if cy < (top + inset) { 1 } else { 0 }))
885    | (BOTTOM * (if cy >= (bottom - inset) { 1 } else { 0 }));
886
887    match result {
888        LEFT => WindowEdge::West,
889        TOP => WindowEdge::North,
890        RIGHT => WindowEdge::East,
891        BOTTOM => WindowEdge::South,
892        TOPLEFT => WindowEdge::NorthWest,
893        TOPRIGHT => WindowEdge::NorthEast,
894        BOTTOMLEFT => WindowEdge::SouthWest,
895        BOTTOMRIGHT => WindowEdge::SouthEast,
896        // we return `WindowEdge::__Unknown` to be ignored later.
897        // we must return 8 or bigger, otherwise it will be the same as one of the other 7 variants of `WindowEdge` enum.
898        _ => WindowEdge::__Unknown(8),
899    }
900}