From 5f5c1984445273c063fcd397e0acad260cb339a6 Mon Sep 17 00:00:00 2001 From: FoOcean Date: Fri, 16 Oct 2026 09:50:13 -0400 Subject: [PATCH 1/2] style: satisfy clippy 1.99 lints CI now runs clippy 1.99, which flags manual_is_multiple_of in timer_view.rs and chunks_exact_to_as_chunks in recorder.rs, failing the PR Check job on master. Co-Authored-By: Claude Opus 5.5 --- src/ui/expanded/timer_view.rs | 2 +- src/utils/recorder.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) 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); From f5be61e6f1ee9e4739f6d54025a70f06cb62fac7 Mon Sep 17 00:00:00 2001 From: FoOcean Date: Fri, 16 Oct 2026 09:53:23 -0400 Subject: [PATCH 2/2] fix(window): stop disturbing exclusive fullscreen apps during mode switches DARK SOULS III minimized itself when switching to exclusive fullscreen while WinIsland ran, even with fullscreen auto-hide off. Every second the overlay re-asserted HWND_TOPMOST and reapplied skip-taskbar, and a display mode change moved the window immediately and again 750 ms later, plus snapped it back on every Moved event. Reordering or moving a topmost window during the switch made the game lose its display. Topmost enforcement now pauses while an app holds Direct3D exclusive fullscreen (SHQueryUserNotificationState) and for 2.5 s after the target display changes. The window moves once after the display settles and does not snap back during the transition. Skip-taskbar is only reapplied when it changes. Fullscreen detection compared the foreground monitor against the island's cached monitor rect, so it flipped between active and normal within the same second while the resolution changed. It now compares monitor identity. Co-Authored-By: Claude Opus 5.5 --- .../winisland-platform-windows/src/display.rs | 28 +++++++++++++------ .../src/window/mod.rs | 11 +++++++- crates/winisland-platform/src/display.rs | 2 ++ src/window/app.rs | 4 +++ src/window/app/events.rs | 1 + src/window/app/frame.rs | 11 ++++---- src/window/app/layout.rs | 23 ++++++++++++++- src/window/app/system.rs | 7 +++-- 8 files changed, 69 insertions(+), 18 deletions(-) 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/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); } } }