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
34const 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 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 }
64
65pub struct Window {
66 pub(crate) window_id: WindowId,
68 pub(crate) window: gtk::ApplicationWindow,
70 pub(crate) default_vbox: Option<gtk::Box>,
71 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_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 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 util::set_size_constraints(&window, attribs.min_inner_size, attribs.max_inner_size);
119
120 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 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 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 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 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 if let Some(icon) = attribs.window_icon {
198 window.set_icon(Some(&icon.inner.into()));
199 }
200
201 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 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 if let Some(parent) = pl_attribs.parent {
234 window.set_transient_for(Some(&parent));
235 }
236
237 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 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 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 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 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 None
462 }
463
464 #[inline]
465 pub fn set_resize_increments(&self, _increments: Option<Size>) {
466 }
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 }
488
489 #[inline]
490 pub fn enabled_buttons(&self) -> WindowButtons {
491 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 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 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 }
673
674 #[inline]
675 pub fn set_ime_allowed(&self, _allowed: bool) {
676 }
678
679 #[inline]
680 pub fn set_ime_purpose(&self, _purpose: ImePurpose) {
681 }
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 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 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
855unsafe impl Send for Window {}
858unsafe impl Sync for Window {}
859
860pub 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 _ => WindowEdge::__Unknown(8),
899 }
900}