diff --git a/crates/winisland-platform-windows/src/display.rs b/crates/winisland-platform-windows/src/display.rs index a702f936..b4b943ea 100644 --- a/crates/winisland-platform-windows/src/display.rs +++ b/crates/winisland-platform-windows/src/display.rs @@ -7,8 +7,9 @@ use windows::Win32::Graphics::Dxgi::{ }; use windows::Win32::Graphics::Gdi::{ EnumDisplayMonitors, GetMonitorInfoW, HDC, HMONITOR, MONITOR_DEFAULTTONEAREST, MONITORINFO, - MonitorFromWindow, + MonitorFromRect, MonitorFromWindow, }; +use windows::Win32::UI::Shell::{QUNS_RUNNING_D3D_FULL_SCREEN, SHQueryUserNotificationState}; use windows::Win32::UI::WindowsAndMessaging::{ CURSORINFO, FindWindowW, GetClassNameW, GetCursorInfo, GetCursorPos, GetForegroundWindow, GetWindowRect, GetWindowThreadProcessId, IsIconic, SW_RESTORE, SetForegroundWindow, ShowWindow, @@ -63,6 +64,12 @@ impl DisplayProvider for WindowsDisplay { foreground_fullscreen(target) } + fn exclusive_fullscreen_active(&self) -> bool { + // SAFETY: The query has no inputs and returns the state by value. + unsafe { SHQueryUserNotificationState() } + .is_ok_and(|state| state == QUNS_RUNNING_D3D_FULL_SCREEN) + } + fn cursor_hidden(&self) -> bool { let mut info = CURSORINFO { cbSize: size_of::() as u32, @@ -170,14 +177,17 @@ fn foreground_fullscreen(target: Rect) -> bool { && window_rect.top <= bounds.top && window_rect.right >= bounds.right && window_rect.bottom >= bounds.bottom; - let target_width = target.right.saturating_sub(target.left); - let target_height = target.bottom.saturating_sub(target.top); - let same_monitor = target_width == 0 - || target_height == 0 - || (bounds.left == target.left - && bounds.top == target.top - && bounds.right == target.right - && bounds.bottom == target.bottom); + let same_monitor = target.right <= target.left + || target.bottom <= target.top + || MonitorFromRect( + &RECT { + left: target.left, + top: target.top, + right: target.right, + bottom: target.bottom, + }, + MONITOR_DEFAULTTONEAREST, + ) == monitor; fullscreen && same_monitor } } diff --git a/crates/winisland-platform-windows/src/window/mod.rs b/crates/winisland-platform-windows/src/window/mod.rs index 3a82b033..b893412b 100644 --- a/crates/winisland-platform-windows/src/window/mod.rs +++ b/crates/winisland-platform-windows/src/window/mod.rs @@ -38,6 +38,7 @@ struct WindowRecord { window_backdrop: Option>, input: Option>, backdrop_capture: Option>, + skip_taskbar: Option, window: Arc, backdrop: Option>, } @@ -97,6 +98,7 @@ fn register_window( window_backdrop: None, input, backdrop_capture: None, + skip_taskbar: None, window, backdrop, }; @@ -527,6 +529,11 @@ impl WindowSystem for WindowsWindowSystem { } fn apply_overlay_styles(&self, id: WindowId, styles: OverlayStyles) { + let skip_taskbar_changed = WINDOWS.with(|windows| { + windows.borrow_mut().get_mut(&id).is_some_and(|record| { + record.skip_taskbar.replace(styles.skip_taskbar) != Some(styles.skip_taskbar) + }) + }); let keyboard = WINDOWS.with(|windows| { windows .borrow() @@ -549,7 +556,9 @@ impl WindowSystem for WindowsWindowSystem { styles::set_keyboard_input(HWND(hwnd as *mut _), true); } } - window.set_skip_taskbar(styles.skip_taskbar); + if skip_taskbar_changed { + window.set_skip_taskbar(styles.skip_taskbar); + } } } diff --git a/crates/winisland-platform/src/display.rs b/crates/winisland-platform/src/display.rs index dab3639b..7bd74b61 100644 --- a/crates/winisland-platform/src/display.rs +++ b/crates/winisland-platform/src/display.rs @@ -17,6 +17,8 @@ pub trait DisplayProvider { fn cursor_position(&self) -> Result, PlatformError>; /// Checks whether a foreign window covers the target; false on lookup failure. fn foreign_fullscreen_on(&self, target: Rect) -> bool; + /// Reports whether an app holds Direct3D exclusive fullscreen; false on lookup failure. + fn exclusive_fullscreen_active(&self) -> bool; /// Reports whether the system cursor is hidden; false on lookup failure. fn cursor_hidden(&self) -> bool; /// Starts WMI brightness tracking on a worker, or returns an unavailable error. diff --git a/src/ui/expanded/timer_view.rs b/src/ui/expanded/timer_view.rs index 7f02b676..32533213 100644 --- a/src/ui/expanded/timer_view.rs +++ b/src/ui/expanded/timer_view.rs @@ -299,7 +299,7 @@ pub fn clear_selection() { } pub fn duration_label(seconds: u64) -> String { - if seconds % 60 == 0 { + if seconds.is_multiple_of(60) { tr_args("timer_minutes", &[&(seconds / 60).to_string()]) } else { format_remaining(Duration::from_secs(seconds)) diff --git a/src/utils/recorder.rs b/src/utils/recorder.rs index 5fbe9ba5..b01589fd 100644 --- a/src/utils/recorder.rs +++ b/src/utils/recorder.rs @@ -129,7 +129,9 @@ fn write_job(directory: &Path, job: Job) -> io::Result<()> { let Job { first, last, frame } = job; let rgb: Vec = frame .pixels - .chunks_exact(4) + .as_chunks::<4>() + .0 + .iter() .flat_map(|pixel| [pixel[0], pixel[1], pixel[2]]) .collect(); let path = frame_path(directory, first); diff --git a/src/window/app.rs b/src/window/app.rs index a5fdedde..7adfe335 100644 --- a/src/window/app.rs +++ b/src/window/app.rs @@ -128,6 +128,8 @@ pub struct App { fullscreen_check: Every, config_check: Every, monitor_check: Every, + display_settle_until: Option, + display_transition: bool, working_set_trim: Every, compact_widget_refresh: Cooldown, last_config_modified: Option, @@ -277,6 +279,8 @@ impl Default for App { fullscreen_check: Every::millis(100), config_check: Every::millis(500), monitor_check: Every::secs(1), + display_settle_until: None, + display_transition: false, working_set_trim: Every::new(frame::WORKING_SET_TRIM_INTERVAL), compact_widget_refresh: Cooldown::ready(), last_config_modified, diff --git a/src/window/app/events.rs b/src/window/app/events.rs index 02bea1b9..66d3aba5 100644 --- a/src/window/app/events.rs +++ b/src/window/app/events.rs @@ -75,6 +75,7 @@ impl App { && !self.is_right_dragging && self.hide.origin.is_none() && self.geom.position_restore_after.is_none() + && !self.update_display_transition(&win, Instant::now()) && (position.x != self.geom.configured_x || position.y != self.geom.configured_y) { diff --git a/src/window/app/frame.rs b/src/window/app/frame.rs index 934961a6..cbea4769 100644 --- a/src/window/app/frame.rs +++ b/src/window/app/frame.rs @@ -58,10 +58,11 @@ impl App { if now < self.next_frame_deadline { return; } - if self - .geom - .position_restore_after - .is_some_and(|restore_after| now >= restore_after) + if !self.display_transition + && self + .geom + .position_restore_after + .is_some_and(|restore_after| now >= restore_after) { self.geom.position_restore_after = None; self.set_configured_window_position( @@ -70,7 +71,7 @@ impl App { self.geom.configured_y, ); } - if self.topmost_check.due(now) { + if self.topmost_check.due(now) && !self.update_display_transition(&window, now) { Self::enforce_overlay_window(&window); } self.handle_tray_events(&window); diff --git a/src/window/app/layout.rs b/src/window/app/layout.rs index a897a78f..4f8ede5f 100644 --- a/src/window/app/layout.rs +++ b/src/window/app/layout.rs @@ -1,4 +1,4 @@ -use std::time::Duration; +use std::time::{Duration, Instant}; use crate::platform::{MonitorRef, WindowRef, window}; use winisland_core::config::{ @@ -9,6 +9,8 @@ use winisland_render::text::FontManager; use super::{App, DEFAULT_ANIMATION_REFRESH_RATE_MILLIHERTZ, IslandLayout}; +pub(super) const DISPLAY_SETTLE: Duration = Duration::from_millis(2500); + impl App { pub(super) fn required_window_size(&self) -> WindowSize { let compact_scale = self.config.compact_scale; @@ -519,3 +521,22 @@ impl App { } } } + +impl App { + pub(super) fn update_display_transition(&mut self, window: &WindowRef, now: Instant) -> bool { + if let Some(monitor) = Self::get_target_monitor(window, self.config.monitor_index) { + let size = monitor.size(); + let position = monitor.position(); + if size.width > 0 + && size.height > 0 + && ((size.width, size.height) != self.geom.monitor_size + || (position.x, position.y) != self.geom.monitor_pos) + { + self.display_settle_until = Some(now + DISPLAY_SETTLE); + } + } + self.display_transition = self.display_settle_until.is_some_and(|until| now < until) + || crate::platform::display().exclusive_fullscreen_active(); + self.display_transition + } +} diff --git a/src/window/app/system.rs b/src/window/app/system.rs index 4ff4f4ed..ba67d070 100644 --- a/src/window/app/system.rs +++ b/src/window/app/system.rs @@ -742,8 +742,11 @@ impl App { self.geom.monitor_size = cur_mon_size; self.geom.monitor_pos = cur_mon_pos; let (position_x, position_y) = self.compute_window_position(mon_pos, mon_size); - self.set_configured_window_position(window_ref, position_x, position_y); - self.geom.position_restore_after = Some(now + Duration::from_millis(750)); + self.geom.configured_x = position_x; + self.geom.configured_y = position_y; + self.display_settle_until = Some(now + super::layout::DISPLAY_SETTLE); + self.display_transition = true; + self.geom.position_restore_after = Some(now + super::layout::DISPLAY_SETTLE); } } }