windows: More precise handling of `WM_SETTINGCHANGE` and appearance updates (#33829)

张小白 created

This PR adds more fine-grained handling of the `WM_SETTINGCHANGE`
message.
Plus, we now only trigger the `appearance_changed` callback when the
actual window appearance has changed, rather than calling it every time.


Release Notes:

- N/A

Change summary

crates/gpui/src/platform/windows/events.rs          | 73 +++++++-------
crates/gpui/src/platform/windows/system_settings.rs | 22 ++++
crates/gpui/src/platform/windows/util.rs            |  4 
crates/gpui/src/platform/windows/window.rs          | 36 +++++-
4 files changed, 89 insertions(+), 46 deletions(-)

Detailed changes

crates/gpui/src/platform/windows/events.rs 🔗

@@ -93,7 +93,7 @@ pub(crate) fn handle_msg(
         WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
         WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
         WM_SETCURSOR => handle_set_cursor(handle, lparam, state_ptr),
-        WM_SETTINGCHANGE => handle_system_settings_changed(handle, lparam, state_ptr),
+        WM_SETTINGCHANGE => handle_system_settings_changed(handle, wparam, lparam, state_ptr),
         WM_INPUTLANGCHANGE => handle_input_language_changed(lparam, state_ptr),
         WM_GPUI_CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
         _ => None,
@@ -1152,37 +1152,23 @@ fn handle_set_cursor(
 
 fn handle_system_settings_changed(
     handle: HWND,
+    wparam: WPARAM,
     lparam: LPARAM,
     state_ptr: Rc<WindowsWindowStatePtr>,
 ) -> Option<isize> {
-    let mut lock = state_ptr.state.borrow_mut();
-    let display = lock.display;
-    // system settings
-    lock.system_settings.update(display);
-    // mouse double click
-    lock.click_state.system_update();
-    // window border offset
-    lock.border_offset.update(handle).log_err();
-    drop(lock);
-
-    // lParam is a pointer to a string that indicates the area containing the system parameter
-    // that was changed.
-    let parameter = PCWSTR::from_raw(lparam.0 as _);
-    if unsafe { !parameter.is_null() && !parameter.is_empty() } {
-        if let Some(parameter_string) = unsafe { parameter.to_string() }.log_err() {
-            log::info!("System settings changed: {}", parameter_string);
-            match parameter_string.as_str() {
-                "ImmersiveColorSet" => {
-                    handle_system_theme_changed(handle, state_ptr);
-                }
-                _ => {}
-            }
-        }
-    }
-
+    if wparam.0 != 0 {
+        let mut lock = state_ptr.state.borrow_mut();
+        let display = lock.display;
+        lock.system_settings.update(display, wparam.0);
+        lock.click_state.system_update(wparam.0);
+        lock.border_offset.update(handle).log_err();
+    } else {
+        handle_system_theme_changed(handle, lparam, state_ptr)?;
+    };
     // Force to trigger WM_NCCALCSIZE event to ensure that we handle auto hide
     // taskbar correctly.
     notify_frame_changed(handle);
+
     Some(0)
 }
 
@@ -1199,17 +1185,34 @@ fn handle_system_command(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -
 
 fn handle_system_theme_changed(
     handle: HWND,
+    lparam: LPARAM,
     state_ptr: Rc<WindowsWindowStatePtr>,
 ) -> Option<isize> {
-    let mut callback = state_ptr
-        .state
-        .borrow_mut()
-        .callbacks
-        .appearance_changed
-        .take()?;
-    callback();
-    state_ptr.state.borrow_mut().callbacks.appearance_changed = Some(callback);
-    configure_dwm_dark_mode(handle);
+    // lParam is a pointer to a string that indicates the area containing the system parameter
+    // that was changed.
+    let parameter = PCWSTR::from_raw(lparam.0 as _);
+    if unsafe { !parameter.is_null() && !parameter.is_empty() } {
+        if let Some(parameter_string) = unsafe { parameter.to_string() }.log_err() {
+            log::info!("System settings changed: {}", parameter_string);
+            match parameter_string.as_str() {
+                "ImmersiveColorSet" => {
+                    let new_appearance = system_appearance()
+                        .context("unable to get system appearance when handling ImmersiveColorSet")
+                        .log_err()?;
+                    let mut lock = state_ptr.state.borrow_mut();
+                    if new_appearance != lock.appearance {
+                        lock.appearance = new_appearance;
+                        let mut callback = lock.callbacks.appearance_changed.take()?;
+                        drop(lock);
+                        callback();
+                        state_ptr.state.borrow_mut().callbacks.appearance_changed = Some(callback);
+                        configure_dwm_dark_mode(handle, new_appearance);
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
     Some(0)
 }
 

crates/gpui/src/platform/windows/system_settings.rs 🔗

@@ -32,14 +32,32 @@ pub(crate) struct MouseWheelSettings {
 impl WindowsSystemSettings {
     pub(crate) fn new(display: WindowsDisplay) -> Self {
         let mut settings = Self::default();
-        settings.update(display);
+        settings.init(display);
         settings
     }
 
-    pub(crate) fn update(&mut self, display: WindowsDisplay) {
+    fn init(&mut self, display: WindowsDisplay) {
         self.mouse_wheel_settings.update();
         self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
     }
+
+    pub(crate) fn update(&mut self, display: WindowsDisplay, wparam: usize) {
+        match wparam {
+            // SPI_SETWORKAREA
+            47 => self.update_taskbar_position(display),
+            // SPI_GETWHEELSCROLLLINES, SPI_GETWHEELSCROLLCHARS
+            104 | 108 => self.update_mouse_wheel_settings(),
+            _ => {}
+        }
+    }
+
+    fn update_mouse_wheel_settings(&mut self) {
+        self.mouse_wheel_settings.update();
+    }
+
+    fn update_taskbar_position(&mut self, display: WindowsDisplay) {
+        self.auto_hide_taskbar_position = AutoHideTaskbarPosition::new(display).log_err().flatten();
+    }
 }
 
 impl MouseWheelSettings {

crates/gpui/src/platform/windows/util.rs 🔗

@@ -144,8 +144,8 @@ pub(crate) fn load_cursor(style: CursorStyle) -> Option<HCURSOR> {
 }
 
 /// This function is used to configure the dark mode for the window built-in title bar.
-pub(crate) fn configure_dwm_dark_mode(hwnd: HWND) {
-    let dark_mode_enabled: BOOL = match system_appearance().log_err().unwrap_or_default() {
+pub(crate) fn configure_dwm_dark_mode(hwnd: HWND, appearance: WindowAppearance) {
+    let dark_mode_enabled: BOOL = match appearance {
         WindowAppearance::Dark | WindowAppearance::VibrantDark => true.into(),
         WindowAppearance::Light | WindowAppearance::VibrantLight => false.into(),
     };

crates/gpui/src/platform/windows/window.rs 🔗

@@ -37,6 +37,7 @@ pub struct WindowsWindowState {
     pub min_size: Option<Size<Pixels>>,
     pub fullscreen_restore_bounds: Bounds<Pixels>,
     pub border_offset: WindowBorderOffset,
+    pub appearance: WindowAppearance,
     pub scale_factor: f32,
     pub restore_from_minimized: Option<Box<dyn FnMut(RequestFrameOptions)>>,
 
@@ -84,6 +85,7 @@ impl WindowsWindowState {
         display: WindowsDisplay,
         gpu_context: &BladeContext,
         min_size: Option<Size<Pixels>>,
+        appearance: WindowAppearance,
     ) -> Result<Self> {
         let scale_factor = {
             let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
@@ -118,6 +120,7 @@ impl WindowsWindowState {
             logical_size,
             fullscreen_restore_bounds,
             border_offset,
+            appearance,
             scale_factor,
             restore_from_minimized,
             min_size,
@@ -206,6 +209,7 @@ impl WindowsWindowStatePtr {
             context.display,
             context.gpu_context,
             context.min_size,
+            context.appearance,
         )?);
 
         Ok(Rc::new_cyclic(|this| Self {
@@ -338,6 +342,7 @@ struct WindowCreateContext<'a> {
     main_receiver: flume::Receiver<Runnable>,
     gpu_context: &'a BladeContext,
     main_thread_id_win32: u32,
+    appearance: WindowAppearance,
 }
 
 impl WindowsWindow {
@@ -387,6 +392,7 @@ impl WindowsWindow {
         } else {
             WindowsDisplay::primary_monitor().unwrap()
         };
+        let appearance = system_appearance().unwrap_or_default();
         let mut context = WindowCreateContext {
             inner: None,
             handle,
@@ -403,6 +409,7 @@ impl WindowsWindow {
             main_receiver,
             gpu_context,
             main_thread_id_win32,
+            appearance,
         };
         let lpparam = Some(&context as *const _ as *const _);
         let creation_result = unsafe {
@@ -426,7 +433,7 @@ impl WindowsWindow {
         let state_ptr = context.inner.take().unwrap()?;
         let hwnd = creation_result?;
         register_drag_drop(state_ptr.clone())?;
-        configure_dwm_dark_mode(hwnd);
+        configure_dwm_dark_mode(hwnd, appearance);
         state_ptr.state.borrow_mut().border_offset.update(hwnd)?;
         let placement = retrieve_window_placement(
             hwnd,
@@ -543,7 +550,7 @@ impl PlatformWindow for WindowsWindow {
     }
 
     fn appearance(&self) -> WindowAppearance {
-        system_appearance().log_err().unwrap_or_default()
+        self.0.state.borrow().appearance
     }
 
     fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
@@ -951,7 +958,7 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy)]
 pub(crate) struct ClickState {
     button: MouseButton,
     last_click: Instant,
@@ -993,10 +1000,25 @@ impl ClickState {
         self.current_count
     }
 
-    pub fn system_update(&mut self) {
-        self.double_click_spatial_tolerance_width = unsafe { GetSystemMetrics(SM_CXDOUBLECLK) };
-        self.double_click_spatial_tolerance_height = unsafe { GetSystemMetrics(SM_CYDOUBLECLK) };
-        self.double_click_interval = Duration::from_millis(unsafe { GetDoubleClickTime() } as u64);
+    pub fn system_update(&mut self, wparam: usize) {
+        match wparam {
+            // SPI_SETDOUBLECLKWIDTH
+            29 => {
+                self.double_click_spatial_tolerance_width =
+                    unsafe { GetSystemMetrics(SM_CXDOUBLECLK) }
+            }
+            // SPI_SETDOUBLECLKHEIGHT
+            30 => {
+                self.double_click_spatial_tolerance_height =
+                    unsafe { GetSystemMetrics(SM_CYDOUBLECLK) }
+            }
+            // SPI_SETDOUBLECLICKTIME
+            32 => {
+                self.double_click_interval =
+                    Duration::from_millis(unsafe { GetDoubleClickTime() } as u64)
+            }
+            _ => {}
+        }
     }
 
     #[inline]