events.rs

   1use std::rc::Rc;
   2
   3use ::util::ResultExt;
   4use anyhow::Context as _;
   5use windows::{
   6    Win32::{
   7        Foundation::*,
   8        Graphics::Gdi::*,
   9        System::SystemServices::*,
  10        UI::{
  11            Controls::*,
  12            HiDpi::*,
  13            Input::{Ime::*, KeyboardAndMouse::*},
  14            WindowsAndMessaging::*,
  15        },
  16    },
  17    core::PCWSTR,
  18};
  19
  20use crate::*;
  21
  22pub(crate) const WM_GPUI_CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
  23pub(crate) const WM_GPUI_CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
  24pub(crate) const WM_GPUI_TASK_DISPATCHED_ON_MAIN_THREAD: u32 = WM_USER + 3;
  25pub(crate) const WM_GPUI_DOCK_MENU_ACTION: u32 = WM_USER + 4;
  26pub(crate) const WM_GPUI_FORCE_UPDATE_WINDOW: u32 = WM_USER + 5;
  27pub(crate) const WM_GPUI_KEYBOARD_LAYOUT_CHANGED: u32 = WM_USER + 6;
  28pub(crate) const WM_GPUI_GPU_DEVICE_LOST: u32 = WM_USER + 7;
  29pub(crate) const WM_GPUI_KEYDOWN: u32 = WM_USER + 8;
  30
  31const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
  32const AUTO_HIDE_TASKBAR_THICKNESS_PX: i32 = 1;
  33
  34impl WindowsWindowInner {
  35    pub(crate) fn handle_msg(
  36        self: &Rc<Self>,
  37        handle: HWND,
  38        msg: u32,
  39        wparam: WPARAM,
  40        lparam: LPARAM,
  41    ) -> LRESULT {
  42        let handled = match msg {
  43            // eagerly activate the window, so calls to `active_window` will work correctly
  44            WM_MOUSEACTIVATE => {
  45                unsafe { SetActiveWindow(handle).log_err() };
  46                None
  47            }
  48            WM_ACTIVATE => self.handle_activate_msg(wparam),
  49            WM_CREATE => self.handle_create_msg(handle),
  50            WM_MOVE => self.handle_move_msg(handle, lparam),
  51            WM_SIZE => self.handle_size_msg(wparam, lparam),
  52            WM_GETMINMAXINFO => self.handle_get_min_max_info_msg(lparam),
  53            WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(handle),
  54            WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(handle),
  55            WM_TIMER => self.handle_timer_msg(handle, wparam),
  56            WM_NCCALCSIZE => self.handle_calc_client_size(handle, wparam, lparam),
  57            WM_DPICHANGED => self.handle_dpi_changed_msg(handle, wparam, lparam),
  58            WM_DISPLAYCHANGE => self.handle_display_change_msg(handle),
  59            WM_NCHITTEST => self.handle_hit_test_msg(handle, lparam),
  60            WM_PAINT => self.handle_paint_msg(handle),
  61            WM_CLOSE => self.handle_close_msg(),
  62            WM_DESTROY => self.handle_destroy_msg(handle),
  63            WM_MOUSEMOVE => self.handle_mouse_move_msg(handle, lparam, wparam),
  64            WM_MOUSELEAVE | WM_NCMOUSELEAVE => self.handle_mouse_leave_msg(),
  65            WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(handle, lparam),
  66            // Treat double click as a second single click, since we track the double clicks ourselves.
  67            // If you don't interact with any elements, this will fall through to the windows default
  68            // behavior of toggling whether the window is maximized.
  69            WM_NCLBUTTONDBLCLK | WM_NCLBUTTONDOWN => {
  70                self.handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam)
  71            }
  72            WM_NCRBUTTONDOWN => {
  73                self.handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam)
  74            }
  75            WM_NCMBUTTONDOWN => {
  76                self.handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam)
  77            }
  78            WM_NCLBUTTONUP => {
  79                self.handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam)
  80            }
  81            WM_NCRBUTTONUP => {
  82                self.handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam)
  83            }
  84            WM_NCMBUTTONUP => {
  85                self.handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam)
  86            }
  87            WM_LBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Left, lparam),
  88            WM_RBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Right, lparam),
  89            WM_MBUTTONDOWN => self.handle_mouse_down_msg(handle, MouseButton::Middle, lparam),
  90            WM_XBUTTONDOWN => {
  91                self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_down_msg)
  92            }
  93            WM_LBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Left, lparam),
  94            WM_RBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Right, lparam),
  95            WM_MBUTTONUP => self.handle_mouse_up_msg(handle, MouseButton::Middle, lparam),
  96            WM_XBUTTONUP => {
  97                self.handle_xbutton_msg(handle, wparam, lparam, Self::handle_mouse_up_msg)
  98            }
  99            WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(handle, wparam, lparam),
 100            WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(handle, wparam, lparam),
 101            WM_SYSKEYUP => self.handle_syskeyup_msg(wparam, lparam),
 102            WM_KEYUP => self.handle_keyup_msg(wparam, lparam),
 103            WM_GPUI_KEYDOWN => self.handle_keydown_msg(wparam, lparam),
 104            WM_CHAR => self.handle_char_msg(wparam),
 105            WM_IME_STARTCOMPOSITION => self.handle_ime_position(handle),
 106            WM_IME_COMPOSITION => self.handle_ime_composition(handle, lparam),
 107            WM_SETCURSOR => self.handle_set_cursor(handle, lparam),
 108            WM_SETTINGCHANGE => self.handle_system_settings_changed(handle, wparam, lparam),
 109            WM_INPUTLANGCHANGE => self.handle_input_language_changed(),
 110            WM_SHOWWINDOW => self.handle_window_visibility_changed(handle, wparam),
 111            WM_GPUI_CURSOR_STYLE_CHANGED => self.handle_cursor_changed(lparam),
 112            WM_GPUI_FORCE_UPDATE_WINDOW => self.draw_window(handle, true),
 113            WM_GPUI_GPU_DEVICE_LOST => self.handle_device_lost(lparam),
 114            _ => None,
 115        };
 116        if let Some(n) = handled {
 117            LRESULT(n)
 118        } else {
 119            unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
 120        }
 121    }
 122
 123    fn handle_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
 124        let origin = logical_point(
 125            lparam.signed_loword() as f32,
 126            lparam.signed_hiword() as f32,
 127            self.state.scale_factor.get(),
 128        );
 129        self.state.origin.set(origin);
 130        let size = self.state.logical_size.get();
 131        let center_x = origin.x.0 + size.width.0 / 2.;
 132        let center_y = origin.y.0 + size.height.0 / 2.;
 133        let monitor_bounds = self.state.display.get().bounds();
 134        if center_x < monitor_bounds.left().0
 135            || center_x > monitor_bounds.right().0
 136            || center_y < monitor_bounds.top().0
 137            || center_y > monitor_bounds.bottom().0
 138        {
 139            // center of the window may have moved to another monitor
 140            let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
 141            // minimize the window can trigger this event too, in this case,
 142            // monitor is invalid, we do nothing.
 143            if !monitor.is_invalid() && self.state.display.get().handle != monitor {
 144                // we will get the same monitor if we only have one
 145                self.state
 146                    .display
 147                    .set(WindowsDisplay::new_with_handle(monitor).log_err()?);
 148            }
 149        }
 150        if let Some(mut callback) = self.state.callbacks.moved.take() {
 151            callback();
 152            self.state.callbacks.moved.set(Some(callback));
 153        }
 154        Some(0)
 155    }
 156
 157    fn handle_get_min_max_info_msg(&self, lparam: LPARAM) -> Option<isize> {
 158        let min_size = self.state.min_size?;
 159        let scale_factor = self.state.scale_factor.get();
 160        let boarder_offset = &self.state.border_offset;
 161
 162        unsafe {
 163            let minmax_info = &mut *(lparam.0 as *mut MINMAXINFO);
 164            minmax_info.ptMinTrackSize.x =
 165                min_size.width.scale(scale_factor).0 as i32 + boarder_offset.width_offset.get();
 166            minmax_info.ptMinTrackSize.y =
 167                min_size.height.scale(scale_factor).0 as i32 + boarder_offset.height_offset.get();
 168        }
 169        Some(0)
 170    }
 171
 172    fn handle_size_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 173        // Don't resize the renderer when the window is minimized, but record that it was minimized so
 174        // that on restore the swap chain can be recreated via `update_drawable_size_even_if_unchanged`.
 175        if wparam.0 == SIZE_MINIMIZED as usize {
 176            self.state
 177                .restore_from_minimized
 178                .set(self.state.callbacks.request_frame.take());
 179            return Some(0);
 180        }
 181
 182        let width = lparam.loword().max(1) as i32;
 183        let height = lparam.hiword().max(1) as i32;
 184        let new_size = size(DevicePixels(width), DevicePixels(height));
 185
 186        let scale_factor = self.state.scale_factor.get();
 187        let mut should_resize_renderer = false;
 188        if let Some(restore_from_minimized) = self.state.restore_from_minimized.take() {
 189            self.state
 190                .callbacks
 191                .request_frame
 192                .set(Some(restore_from_minimized));
 193        } else {
 194            should_resize_renderer = true;
 195        }
 196
 197        self.handle_size_change(new_size, scale_factor, should_resize_renderer);
 198        Some(0)
 199    }
 200
 201    fn handle_size_change(
 202        &self,
 203        device_size: Size<DevicePixels>,
 204        scale_factor: f32,
 205        should_resize_renderer: bool,
 206    ) {
 207        let new_logical_size = device_size.to_pixels(scale_factor);
 208
 209        self.state.logical_size.set(new_logical_size);
 210        if should_resize_renderer
 211            && let Err(e) = self.state.renderer.borrow_mut().resize(device_size)
 212        {
 213            log::error!("Failed to resize renderer, invalidating devices: {}", e);
 214            self.state
 215                .invalidate_devices
 216                .store(true, std::sync::atomic::Ordering::Release);
 217        }
 218        if let Some(mut callback) = self.state.callbacks.resize.take() {
 219            callback(new_logical_size, scale_factor);
 220            self.state.callbacks.resize.set(Some(callback));
 221        }
 222    }
 223
 224    fn handle_size_move_loop(&self, handle: HWND) -> Option<isize> {
 225        unsafe {
 226            let ret = SetTimer(
 227                Some(handle),
 228                SIZE_MOVE_LOOP_TIMER_ID,
 229                USER_TIMER_MINIMUM,
 230                None,
 231            );
 232            if ret == 0 {
 233                log::error!(
 234                    "unable to create timer: {}",
 235                    std::io::Error::last_os_error()
 236                );
 237            }
 238        }
 239        None
 240    }
 241
 242    fn handle_size_move_loop_exit(&self, handle: HWND) -> Option<isize> {
 243        unsafe {
 244            KillTimer(Some(handle), SIZE_MOVE_LOOP_TIMER_ID).log_err();
 245        }
 246        None
 247    }
 248
 249    fn handle_timer_msg(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {
 250        if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
 251            for runnable in self.main_receiver.drain() {
 252                WindowsDispatcher::execute_runnable(runnable);
 253            }
 254            self.handle_paint_msg(handle)
 255        } else {
 256            None
 257        }
 258    }
 259
 260    fn handle_paint_msg(&self, handle: HWND) -> Option<isize> {
 261        self.draw_window(handle, false)
 262    }
 263
 264    fn handle_close_msg(&self) -> Option<isize> {
 265        let mut callback = self.state.callbacks.should_close.take()?;
 266        let should_close = callback();
 267        self.state.callbacks.should_close.set(Some(callback));
 268        if should_close { None } else { Some(0) }
 269    }
 270
 271    fn handle_destroy_msg(&self, handle: HWND) -> Option<isize> {
 272        let callback = { self.state.callbacks.close.take() };
 273        if let Some(callback) = callback {
 274            callback();
 275        }
 276        unsafe {
 277            PostMessageW(
 278                Some(self.platform_window_handle),
 279                WM_GPUI_CLOSE_ONE_WINDOW,
 280                WPARAM(self.validation_number),
 281                LPARAM(handle.0 as isize),
 282            )
 283            .log_err();
 284        }
 285        Some(0)
 286    }
 287
 288    fn handle_mouse_move_msg(&self, handle: HWND, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
 289        self.start_tracking_mouse(handle, TME_LEAVE);
 290
 291        let Some(mut func) = self.state.callbacks.input.take() else {
 292            return Some(1);
 293        };
 294        let scale_factor = self.state.scale_factor.get();
 295
 296        let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
 297            flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
 298            flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
 299            flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
 300            flags if flags.contains(MK_XBUTTON1) => {
 301                Some(MouseButton::Navigate(NavigationDirection::Back))
 302            }
 303            flags if flags.contains(MK_XBUTTON2) => {
 304                Some(MouseButton::Navigate(NavigationDirection::Forward))
 305            }
 306            _ => None,
 307        };
 308        let x = lparam.signed_loword() as f32;
 309        let y = lparam.signed_hiword() as f32;
 310        let input = PlatformInput::MouseMove(MouseMoveEvent {
 311            position: logical_point(x, y, scale_factor),
 312            pressed_button,
 313            modifiers: current_modifiers(),
 314        });
 315        let handled = !func(input).propagate;
 316        self.state.callbacks.input.set(Some(func));
 317
 318        if handled { Some(0) } else { Some(1) }
 319    }
 320
 321    fn handle_mouse_leave_msg(&self) -> Option<isize> {
 322        self.state.hovered.set(false);
 323        if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
 324            callback(false);
 325            self.state
 326                .callbacks
 327                .hovered_status_change
 328                .set(Some(callback));
 329        }
 330
 331        Some(0)
 332    }
 333
 334    fn handle_syskeyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 335        let input = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {
 336            PlatformInput::KeyUp(KeyUpEvent { keystroke })
 337        })?;
 338        let mut func = self.state.callbacks.input.take()?;
 339
 340        func(input);
 341        self.state.callbacks.input.set(Some(func));
 342
 343        // Always return 0 to indicate that the message was handled, so we could properly handle `ModifiersChanged` event.
 344        Some(0)
 345    }
 346
 347    // It's a known bug that you can't trigger `ctrl-shift-0`. See:
 348    // https://superuser.com/questions/1455762/ctrl-shift-number-key-combination-has-stopped-working-for-a-few-numbers
 349    fn handle_keydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 350        let Some(input) = handle_key_event(
 351            wparam,
 352            lparam,
 353            &self.state,
 354            |keystroke, prefer_character_input| {
 355                PlatformInput::KeyDown(KeyDownEvent {
 356                    keystroke,
 357                    is_held: lparam.0 & (0x1 << 30) > 0,
 358                    prefer_character_input,
 359                })
 360            },
 361        ) else {
 362            return Some(1);
 363        };
 364
 365        let Some(mut func) = self.state.callbacks.input.take() else {
 366            return Some(1);
 367        };
 368
 369        let handled = !func(input).propagate;
 370
 371        self.state.callbacks.input.set(Some(func));
 372
 373        if handled { Some(0) } else { Some(1) }
 374    }
 375
 376    fn handle_keyup_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 377        let Some(input) = handle_key_event(wparam, lparam, &self.state, |keystroke, _| {
 378            PlatformInput::KeyUp(KeyUpEvent { keystroke })
 379        }) else {
 380            return Some(1);
 381        };
 382
 383        let Some(mut func) = self.state.callbacks.input.take() else {
 384            return Some(1);
 385        };
 386
 387        let handled = !func(input).propagate;
 388        self.state.callbacks.input.set(Some(func));
 389
 390        if handled { Some(0) } else { Some(1) }
 391    }
 392
 393    fn handle_char_msg(&self, wparam: WPARAM) -> Option<isize> {
 394        let input = self.parse_char_message(wparam)?;
 395        self.with_input_handler(|input_handler| {
 396            input_handler.replace_text_in_range(None, &input);
 397        });
 398
 399        Some(0)
 400    }
 401
 402    fn handle_mouse_down_msg(
 403        &self,
 404        handle: HWND,
 405        button: MouseButton,
 406        lparam: LPARAM,
 407    ) -> Option<isize> {
 408        unsafe { SetCapture(handle) };
 409
 410        let Some(mut func) = self.state.callbacks.input.take() else {
 411            return Some(1);
 412        };
 413        let x = lparam.signed_loword();
 414        let y = lparam.signed_hiword();
 415        let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
 416        let click_count = self.state.click_state.update(button, physical_point);
 417        let scale_factor = self.state.scale_factor.get();
 418
 419        let input = PlatformInput::MouseDown(MouseDownEvent {
 420            button,
 421            position: logical_point(x as f32, y as f32, scale_factor),
 422            modifiers: current_modifiers(),
 423            click_count,
 424            first_mouse: false,
 425        });
 426        let handled = !func(input).propagate;
 427        self.state.callbacks.input.set(Some(func));
 428
 429        if handled { Some(0) } else { Some(1) }
 430    }
 431
 432    fn handle_mouse_up_msg(
 433        &self,
 434        _handle: HWND,
 435        button: MouseButton,
 436        lparam: LPARAM,
 437    ) -> Option<isize> {
 438        unsafe { ReleaseCapture().log_err() };
 439
 440        let Some(mut func) = self.state.callbacks.input.take() else {
 441            return Some(1);
 442        };
 443        let x = lparam.signed_loword() as f32;
 444        let y = lparam.signed_hiword() as f32;
 445        let click_count = self.state.click_state.current_count.get();
 446        let scale_factor = self.state.scale_factor.get();
 447
 448        let input = PlatformInput::MouseUp(MouseUpEvent {
 449            button,
 450            position: logical_point(x, y, scale_factor),
 451            modifiers: current_modifiers(),
 452            click_count,
 453        });
 454        let handled = !func(input).propagate;
 455        self.state.callbacks.input.set(Some(func));
 456
 457        if handled { Some(0) } else { Some(1) }
 458    }
 459
 460    fn handle_xbutton_msg(
 461        &self,
 462        handle: HWND,
 463        wparam: WPARAM,
 464        lparam: LPARAM,
 465        handler: impl Fn(&Self, HWND, MouseButton, LPARAM) -> Option<isize>,
 466    ) -> Option<isize> {
 467        let nav_dir = match wparam.hiword() {
 468            XBUTTON1 => NavigationDirection::Back,
 469            XBUTTON2 => NavigationDirection::Forward,
 470            _ => return Some(1),
 471        };
 472        handler(self, handle, MouseButton::Navigate(nav_dir), lparam)
 473    }
 474
 475    fn handle_mouse_wheel_msg(
 476        &self,
 477        handle: HWND,
 478        wparam: WPARAM,
 479        lparam: LPARAM,
 480    ) -> Option<isize> {
 481        let modifiers = current_modifiers();
 482
 483        let Some(mut func) = self.state.callbacks.input.take() else {
 484            return Some(1);
 485        };
 486        let scale_factor = self.state.scale_factor.get();
 487        let wheel_scroll_amount = match modifiers.shift {
 488            true => self
 489                .system_settings()
 490                .mouse_wheel_settings
 491                .wheel_scroll_chars
 492                .get(),
 493            false => self
 494                .system_settings()
 495                .mouse_wheel_settings
 496                .wheel_scroll_lines
 497                .get(),
 498        };
 499
 500        let wheel_distance =
 501            (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_amount as f32;
 502        let mut cursor_point = POINT {
 503            x: lparam.signed_loword().into(),
 504            y: lparam.signed_hiword().into(),
 505        };
 506        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 507        let input = PlatformInput::ScrollWheel(ScrollWheelEvent {
 508            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 509            delta: ScrollDelta::Lines(match modifiers.shift {
 510                true => Point {
 511                    x: wheel_distance,
 512                    y: 0.0,
 513                },
 514                false => Point {
 515                    y: wheel_distance,
 516                    x: 0.0,
 517                },
 518            }),
 519            modifiers,
 520            touch_phase: TouchPhase::Moved,
 521        });
 522        let handled = !func(input).propagate;
 523        self.state.callbacks.input.set(Some(func));
 524
 525        if handled { Some(0) } else { Some(1) }
 526    }
 527
 528    fn handle_mouse_horizontal_wheel_msg(
 529        &self,
 530        handle: HWND,
 531        wparam: WPARAM,
 532        lparam: LPARAM,
 533    ) -> Option<isize> {
 534        let Some(mut func) = self.state.callbacks.input.take() else {
 535            return Some(1);
 536        };
 537        let scale_factor = self.state.scale_factor.get();
 538        let wheel_scroll_chars = self
 539            .system_settings()
 540            .mouse_wheel_settings
 541            .wheel_scroll_chars
 542            .get();
 543
 544        let wheel_distance =
 545            (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
 546        let mut cursor_point = POINT {
 547            x: lparam.signed_loword().into(),
 548            y: lparam.signed_hiword().into(),
 549        };
 550        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 551        let event = PlatformInput::ScrollWheel(ScrollWheelEvent {
 552            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 553            delta: ScrollDelta::Lines(Point {
 554                x: wheel_distance,
 555                y: 0.0,
 556            }),
 557            modifiers: current_modifiers(),
 558            touch_phase: TouchPhase::Moved,
 559        });
 560        let handled = !func(event).propagate;
 561        self.state.callbacks.input.set(Some(func));
 562
 563        if handled { Some(0) } else { Some(1) }
 564    }
 565
 566    fn retrieve_caret_position(&self) -> Option<POINT> {
 567        self.with_input_handler_and_scale_factor(|input_handler, scale_factor| {
 568            let caret_range = input_handler.selected_text_range(false)?;
 569            let caret_position = input_handler.bounds_for_range(caret_range.range)?;
 570            Some(POINT {
 571                // logical to physical
 572                x: (caret_position.origin.x.0 * scale_factor) as i32,
 573                y: (caret_position.origin.y.0 * scale_factor) as i32
 574                    + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
 575            })
 576        })
 577    }
 578
 579    fn handle_ime_position(&self, handle: HWND) -> Option<isize> {
 580        unsafe {
 581            let ctx = ImmGetContext(handle);
 582
 583            let Some(caret_position) = self.retrieve_caret_position() else {
 584                return Some(0);
 585            };
 586            {
 587                let config = COMPOSITIONFORM {
 588                    dwStyle: CFS_POINT,
 589                    ptCurrentPos: caret_position,
 590                    ..Default::default()
 591                };
 592                ImmSetCompositionWindow(ctx, &config as _).ok().log_err();
 593            }
 594            {
 595                let config = CANDIDATEFORM {
 596                    dwStyle: CFS_CANDIDATEPOS,
 597                    ptCurrentPos: caret_position,
 598                    ..Default::default()
 599                };
 600                ImmSetCandidateWindow(ctx, &config as _).ok().log_err();
 601            }
 602            ImmReleaseContext(handle, ctx).ok().log_err();
 603            Some(0)
 604        }
 605    }
 606
 607    fn handle_ime_composition(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
 608        let ctx = unsafe { ImmGetContext(handle) };
 609        let result = self.handle_ime_composition_inner(ctx, lparam);
 610        unsafe { ImmReleaseContext(handle, ctx).ok().log_err() };
 611        result
 612    }
 613
 614    fn handle_ime_composition_inner(&self, ctx: HIMC, lparam: LPARAM) -> Option<isize> {
 615        let lparam = lparam.0 as u32;
 616        if lparam == 0 {
 617            // Japanese IME may send this message with lparam = 0, which indicates that
 618            // there is no composition string.
 619            self.with_input_handler(|input_handler| {
 620                input_handler.replace_text_in_range(None, "");
 621            })?;
 622            Some(0)
 623        } else {
 624            if lparam & GCS_COMPSTR.0 > 0 {
 625                let comp_string = parse_ime_composition_string(ctx, GCS_COMPSTR)?;
 626                let caret_pos =
 627                    (!comp_string.is_empty() && lparam & GCS_CURSORPOS.0 > 0).then(|| {
 628                        let pos = retrieve_composition_cursor_position(ctx);
 629                        pos..pos
 630                    });
 631                self.with_input_handler(|input_handler| {
 632                    input_handler.replace_and_mark_text_in_range(None, &comp_string, caret_pos);
 633                })?;
 634            }
 635            if lparam & GCS_RESULTSTR.0 > 0 {
 636                let comp_result = parse_ime_composition_string(ctx, GCS_RESULTSTR)?;
 637                self.with_input_handler(|input_handler| {
 638                    input_handler.replace_text_in_range(None, &comp_result);
 639                })?;
 640                return Some(0);
 641            }
 642
 643            // currently, we don't care other stuff
 644            None
 645        }
 646    }
 647
 648    /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
 649    fn handle_calc_client_size(
 650        &self,
 651        handle: HWND,
 652        wparam: WPARAM,
 653        lparam: LPARAM,
 654    ) -> Option<isize> {
 655        if !self.hide_title_bar || self.state.is_fullscreen() || wparam.0 == 0 {
 656            return None;
 657        }
 658
 659        let is_maximized = self.state.is_maximized();
 660        let insets = get_client_area_insets(handle, is_maximized, self.windows_version);
 661        // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
 662        let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
 663        let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
 664
 665        requested_client_rect[0].left += insets.left;
 666        requested_client_rect[0].top += insets.top;
 667        requested_client_rect[0].right -= insets.right;
 668        requested_client_rect[0].bottom -= insets.bottom;
 669
 670        // Fix auto hide taskbar not showing. This solution is based on the approach
 671        // used by Chrome. However, it may result in one row of pixels being obscured
 672        // in our client area. But as Chrome says, "there seems to be no better solution."
 673        if is_maximized
 674            && let Some(taskbar_position) = self.system_settings().auto_hide_taskbar_position.get()
 675        {
 676            // For the auto-hide taskbar, adjust in by 1 pixel on taskbar edge,
 677            // so the window isn't treated as a "fullscreen app", which would cause
 678            // the taskbar to disappear.
 679            match taskbar_position {
 680                AutoHideTaskbarPosition::Left => {
 681                    requested_client_rect[0].left += AUTO_HIDE_TASKBAR_THICKNESS_PX
 682                }
 683                AutoHideTaskbarPosition::Top => {
 684                    requested_client_rect[0].top += AUTO_HIDE_TASKBAR_THICKNESS_PX
 685                }
 686                AutoHideTaskbarPosition::Right => {
 687                    requested_client_rect[0].right -= AUTO_HIDE_TASKBAR_THICKNESS_PX
 688                }
 689                AutoHideTaskbarPosition::Bottom => {
 690                    requested_client_rect[0].bottom -= AUTO_HIDE_TASKBAR_THICKNESS_PX
 691                }
 692            }
 693        }
 694
 695        Some(0)
 696    }
 697
 698    fn handle_activate_msg(self: &Rc<Self>, wparam: WPARAM) -> Option<isize> {
 699        let activated = wparam.loword() > 0;
 700        let this = self.clone();
 701        self.executor
 702            .spawn(async move {
 703                if let Some(mut func) = this.state.callbacks.active_status_change.take() {
 704                    func(activated);
 705                    this.state.callbacks.active_status_change.set(Some(func));
 706                }
 707            })
 708            .detach();
 709
 710        None
 711    }
 712
 713    fn handle_create_msg(&self, handle: HWND) -> Option<isize> {
 714        if self.hide_title_bar {
 715            notify_frame_changed(handle);
 716            Some(0)
 717        } else {
 718            None
 719        }
 720    }
 721
 722    fn handle_dpi_changed_msg(
 723        &self,
 724        handle: HWND,
 725        wparam: WPARAM,
 726        lparam: LPARAM,
 727    ) -> Option<isize> {
 728        let new_dpi = wparam.loword() as f32;
 729
 730        let is_maximized = self.state.is_maximized();
 731        let new_scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
 732        self.state.scale_factor.set(new_scale_factor);
 733        self.state.border_offset.update(handle).log_err();
 734
 735        if is_maximized {
 736            // Get the monitor and its work area at the new DPI
 737            let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONEAREST) };
 738            let mut monitor_info: MONITORINFO = unsafe { std::mem::zeroed() };
 739            monitor_info.cbSize = std::mem::size_of::<MONITORINFO>() as u32;
 740            if unsafe { GetMonitorInfoW(monitor, &mut monitor_info) }.as_bool() {
 741                let work_area = monitor_info.rcWork;
 742                let width = work_area.right - work_area.left;
 743                let height = work_area.bottom - work_area.top;
 744
 745                // Update the window size to match the new monitor work area
 746                // This will trigger WM_SIZE which will handle the size change
 747                unsafe {
 748                    SetWindowPos(
 749                        handle,
 750                        None,
 751                        work_area.left,
 752                        work_area.top,
 753                        width,
 754                        height,
 755                        SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED,
 756                    )
 757                    .context("unable to set maximized window position after dpi has changed")
 758                    .log_err();
 759                }
 760
 761                // SetWindowPos may not send WM_SIZE for maximized windows in some cases,
 762                // so we manually update the size to ensure proper rendering
 763                let device_size = size(DevicePixels(width), DevicePixels(height));
 764                self.handle_size_change(device_size, new_scale_factor, true);
 765            }
 766        } else {
 767            // For non-maximized windows, use the suggested RECT from the system
 768            let rect = unsafe { &*(lparam.0 as *const RECT) };
 769            let width = rect.right - rect.left;
 770            let height = rect.bottom - rect.top;
 771            // this will emit `WM_SIZE` and `WM_MOVE` right here
 772            // even before this function returns
 773            // the new size is handled in `WM_SIZE`
 774            unsafe {
 775                SetWindowPos(
 776                    handle,
 777                    None,
 778                    rect.left,
 779                    rect.top,
 780                    width,
 781                    height,
 782                    SWP_NOZORDER | SWP_NOACTIVATE,
 783                )
 784                .context("unable to set window position after dpi has changed")
 785                .log_err();
 786            }
 787        }
 788
 789        Some(0)
 790    }
 791
 792    /// The following conditions will trigger this event:
 793    /// 1. The monitor on which the window is located goes offline or changes resolution.
 794    /// 2. Another monitor goes offline, is plugged in, or changes resolution.
 795    ///
 796    /// In either case, the window will only receive information from the monitor on which
 797    /// it is located.
 798    ///
 799    /// For example, in the case of condition 2, where the monitor on which the window is
 800    /// located has actually changed nothing, it will still receive this event.
 801    fn handle_display_change_msg(&self, handle: HWND) -> Option<isize> {
 802        // NOTE:
 803        // Even the `lParam` holds the resolution of the screen, we just ignore it.
 804        // Because WM_DPICHANGED, WM_MOVE, WM_SIZE will come first, window reposition and resize
 805        // are handled there.
 806        // So we only care about if monitor is disconnected.
 807        let previous_monitor = self.state.display.get();
 808        if WindowsDisplay::is_connected(previous_monitor.handle) {
 809            // we are fine, other display changed
 810            return None;
 811        }
 812        // display disconnected
 813        // in this case, the OS will move our window to another monitor, and minimize it.
 814        // we deminimize the window and query the monitor after moving
 815        unsafe {
 816            let _ = ShowWindow(handle, SW_SHOWNORMAL);
 817        };
 818        let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
 819        // all monitors disconnected
 820        if new_monitor.is_invalid() {
 821            log::error!("No monitor detected!");
 822            return None;
 823        }
 824        let new_display = WindowsDisplay::new_with_handle(new_monitor).log_err()?;
 825        self.state.display.set(new_display);
 826        Some(0)
 827    }
 828
 829    fn handle_hit_test_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
 830        if !self.is_movable || self.state.is_fullscreen() {
 831            return None;
 832        }
 833
 834        let callback = self.state.callbacks.hit_test_window_control.take();
 835        let drag_area = if let Some(mut callback) = callback {
 836            let area = callback();
 837            self.state
 838                .callbacks
 839                .hit_test_window_control
 840                .set(Some(callback));
 841            if let Some(area) = area {
 842                match area {
 843                    WindowControlArea::Drag => Some(HTCAPTION as _),
 844                    WindowControlArea::Close => return Some(HTCLOSE as _),
 845                    WindowControlArea::Max => return Some(HTMAXBUTTON as _),
 846                    WindowControlArea::Min => return Some(HTMINBUTTON as _),
 847                }
 848            } else {
 849                None
 850            }
 851        } else {
 852            None
 853        };
 854
 855        if !self.hide_title_bar {
 856            // If the OS draws the title bar, we don't need to handle hit test messages.
 857            return drag_area;
 858        }
 859
 860        let dpi = unsafe { GetDpiForWindow(handle) };
 861        // We do not use the OS title bar, so the default `DefWindowProcW` will only register a 1px edge for resizes
 862        // We need to calculate the frame thickness ourselves and do the hit test manually.
 863        let frame_y = get_frame_thicknessx(dpi);
 864        let frame_x = get_frame_thicknessy(dpi);
 865        let mut cursor_point = POINT {
 866            x: lparam.signed_loword().into(),
 867            y: lparam.signed_hiword().into(),
 868        };
 869
 870        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 871        if !self.state.is_maximized() && 0 <= cursor_point.y && cursor_point.y <= frame_y {
 872            // x-axis actually goes from -frame_x to 0
 873            return Some(if cursor_point.x <= 0 {
 874                HTTOPLEFT
 875            } else {
 876                let mut rect = Default::default();
 877                unsafe { GetWindowRect(handle, &mut rect) }.log_err();
 878                // right and bottom bounds of RECT are exclusive, thus `-1`
 879                let right = rect.right - rect.left - 1;
 880                // the bounds include the padding frames, so accomodate for both of them
 881                if right - 2 * frame_x <= cursor_point.x {
 882                    HTTOPRIGHT
 883                } else {
 884                    HTTOP
 885                }
 886            } as _);
 887        }
 888
 889        drag_area
 890    }
 891
 892    fn handle_nc_mouse_move_msg(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
 893        self.start_tracking_mouse(handle, TME_LEAVE | TME_NONCLIENT);
 894
 895        let mut func = self.state.callbacks.input.take()?;
 896        let scale_factor = self.state.scale_factor.get();
 897
 898        let mut cursor_point = POINT {
 899            x: lparam.signed_loword().into(),
 900            y: lparam.signed_hiword().into(),
 901        };
 902        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 903        let input = PlatformInput::MouseMove(MouseMoveEvent {
 904            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 905            pressed_button: None,
 906            modifiers: current_modifiers(),
 907        });
 908        let handled = !func(input).propagate;
 909        self.state.callbacks.input.set(Some(func));
 910
 911        if handled { Some(0) } else { None }
 912    }
 913
 914    fn handle_nc_mouse_down_msg(
 915        &self,
 916        handle: HWND,
 917        button: MouseButton,
 918        wparam: WPARAM,
 919        lparam: LPARAM,
 920    ) -> Option<isize> {
 921        if let Some(mut func) = self.state.callbacks.input.take() {
 922            let scale_factor = self.state.scale_factor.get();
 923            let mut cursor_point = POINT {
 924                x: lparam.signed_loword().into(),
 925                y: lparam.signed_hiword().into(),
 926            };
 927            unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 928            let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
 929            let click_count = self.state.click_state.update(button, physical_point);
 930
 931            let input = PlatformInput::MouseDown(MouseDownEvent {
 932                button,
 933                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 934                modifiers: current_modifiers(),
 935                click_count,
 936                first_mouse: false,
 937            });
 938            let result = func(input);
 939            let handled = !result.propagate || result.default_prevented;
 940            self.state.callbacks.input.set(Some(func));
 941
 942            if handled {
 943                return Some(0);
 944            }
 945        } else {
 946        };
 947
 948        // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
 949        if button == MouseButton::Left {
 950            match wparam.0 as u32 {
 951                HTMINBUTTON => self.state.nc_button_pressed.set(Some(HTMINBUTTON)),
 952                HTMAXBUTTON => self.state.nc_button_pressed.set(Some(HTMAXBUTTON)),
 953                HTCLOSE => self.state.nc_button_pressed.set(Some(HTCLOSE)),
 954                _ => return None,
 955            };
 956            Some(0)
 957        } else {
 958            None
 959        }
 960    }
 961
 962    fn handle_nc_mouse_up_msg(
 963        &self,
 964        handle: HWND,
 965        button: MouseButton,
 966        wparam: WPARAM,
 967        lparam: LPARAM,
 968    ) -> Option<isize> {
 969        if let Some(mut func) = self.state.callbacks.input.take() {
 970            let scale_factor = self.state.scale_factor.get();
 971
 972            let mut cursor_point = POINT {
 973                x: lparam.signed_loword().into(),
 974                y: lparam.signed_hiword().into(),
 975            };
 976            unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 977            let input = PlatformInput::MouseUp(MouseUpEvent {
 978                button,
 979                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 980                modifiers: current_modifiers(),
 981                click_count: 1,
 982            });
 983            let handled = !func(input).propagate;
 984            self.state.callbacks.input.set(Some(func));
 985
 986            if handled {
 987                return Some(0);
 988            }
 989        } else {
 990        }
 991
 992        let last_pressed = self.state.nc_button_pressed.take();
 993        if button == MouseButton::Left
 994            && let Some(last_pressed) = last_pressed
 995        {
 996            let handled = match (wparam.0 as u32, last_pressed) {
 997                (HTMINBUTTON, HTMINBUTTON) => {
 998                    unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
 999                    true
1000                }
1001                (HTMAXBUTTON, HTMAXBUTTON) => {
1002                    if self.state.is_maximized() {
1003                        unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
1004                    } else {
1005                        unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
1006                    }
1007                    true
1008                }
1009                (HTCLOSE, HTCLOSE) => {
1010                    unsafe {
1011                        PostMessageW(Some(handle), WM_CLOSE, WPARAM::default(), LPARAM::default())
1012                            .log_err()
1013                    };
1014                    true
1015                }
1016                _ => false,
1017            };
1018            if handled {
1019                return Some(0);
1020            }
1021        }
1022
1023        None
1024    }
1025
1026    fn handle_cursor_changed(&self, lparam: LPARAM) -> Option<isize> {
1027        let had_cursor = self.state.current_cursor.get().is_some();
1028
1029        self.state.current_cursor.set(if lparam.0 == 0 {
1030            None
1031        } else {
1032            Some(HCURSOR(lparam.0 as _))
1033        });
1034
1035        if had_cursor != self.state.current_cursor.get().is_some() {
1036            unsafe { SetCursor(self.state.current_cursor.get()) };
1037        }
1038
1039        Some(0)
1040    }
1041
1042    fn handle_set_cursor(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
1043        if unsafe { !IsWindowEnabled(handle).as_bool() }
1044            || matches!(
1045                lparam.loword() as u32,
1046                HTLEFT
1047                    | HTRIGHT
1048                    | HTTOP
1049                    | HTTOPLEFT
1050                    | HTTOPRIGHT
1051                    | HTBOTTOM
1052                    | HTBOTTOMLEFT
1053                    | HTBOTTOMRIGHT
1054            )
1055        {
1056            return None;
1057        }
1058        unsafe {
1059            SetCursor(self.state.current_cursor.get());
1060        };
1061        Some(0)
1062    }
1063
1064    fn handle_system_settings_changed(
1065        &self,
1066        handle: HWND,
1067        wparam: WPARAM,
1068        lparam: LPARAM,
1069    ) -> Option<isize> {
1070        if wparam.0 != 0 {
1071            let display = self.state.display.get();
1072            self.state.click_state.system_update(wparam.0);
1073            self.state.border_offset.update(handle).log_err();
1074            // system settings may emit a window message which wants to take the refcell self.state, so drop it
1075
1076            self.system_settings().update(display, wparam.0);
1077        } else {
1078            self.handle_system_theme_changed(handle, lparam)?;
1079        };
1080        // Force to trigger WM_NCCALCSIZE event to ensure that we handle auto hide
1081        // taskbar correctly.
1082        notify_frame_changed(handle);
1083
1084        Some(0)
1085    }
1086
1087    fn handle_system_theme_changed(&self, handle: HWND, lparam: LPARAM) -> Option<isize> {
1088        // lParam is a pointer to a string that indicates the area containing the system parameter
1089        // that was changed.
1090        let parameter = PCWSTR::from_raw(lparam.0 as _);
1091        if unsafe { !parameter.is_null() && !parameter.is_empty() }
1092            && let Some(parameter_string) = unsafe { parameter.to_string() }.log_err()
1093        {
1094            log::info!("System settings changed: {}", parameter_string);
1095            if parameter_string.as_str() == "ImmersiveColorSet" {
1096                let new_appearance = system_appearance()
1097                    .context("unable to get system appearance when handling ImmersiveColorSet")
1098                    .log_err()?;
1099
1100                if new_appearance != self.state.appearance.get() {
1101                    self.state.appearance.set(new_appearance);
1102                    let mut callback = self.state.callbacks.appearance_changed.take()?;
1103
1104                    callback();
1105                    self.state.callbacks.appearance_changed.set(Some(callback));
1106                    configure_dwm_dark_mode(handle, new_appearance);
1107                }
1108            }
1109        }
1110        Some(0)
1111    }
1112
1113    fn handle_input_language_changed(&self) -> Option<isize> {
1114        unsafe {
1115            PostMessageW(
1116                Some(self.platform_window_handle),
1117                WM_GPUI_KEYBOARD_LAYOUT_CHANGED,
1118                WPARAM(self.validation_number),
1119                LPARAM(0),
1120            )
1121            .log_err();
1122        }
1123        Some(0)
1124    }
1125
1126    fn handle_window_visibility_changed(&self, handle: HWND, wparam: WPARAM) -> Option<isize> {
1127        if wparam.0 == 1 {
1128            self.draw_window(handle, false);
1129        }
1130        None
1131    }
1132
1133    fn handle_device_lost(&self, lparam: LPARAM) -> Option<isize> {
1134        let devices = lparam.0 as *const DirectXDevices;
1135        let devices = unsafe { &*devices };
1136        if let Err(err) = self
1137            .state
1138            .renderer
1139            .borrow_mut()
1140            .handle_device_lost(&devices)
1141        {
1142            panic!("Device lost: {err}");
1143        }
1144        Some(0)
1145    }
1146
1147    #[inline]
1148    fn draw_window(&self, handle: HWND, force_render: bool) -> Option<isize> {
1149        let mut request_frame = self.state.callbacks.request_frame.take()?;
1150
1151        // we are instructing gpui to force render a frame, this will
1152        // re-populate all the gpu textures for us so we can resume drawing in
1153        // case we disabled drawing earlier due to a device loss
1154        self.state.renderer.borrow_mut().mark_drawable();
1155        request_frame(RequestFrameOptions {
1156            require_presentation: false,
1157            force_render,
1158        });
1159
1160        self.state.callbacks.request_frame.set(Some(request_frame));
1161        unsafe { ValidateRect(Some(handle), None).ok().log_err() };
1162
1163        Some(0)
1164    }
1165
1166    #[inline]
1167    fn parse_char_message(&self, wparam: WPARAM) -> Option<String> {
1168        let code_point = wparam.loword();
1169
1170        // https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/#G2630
1171        match code_point {
1172            0xD800..=0xDBFF => {
1173                // High surrogate, wait for low surrogate
1174                self.state.pending_surrogate.set(Some(code_point));
1175                None
1176            }
1177            0xDC00..=0xDFFF => {
1178                if let Some(high_surrogate) = self.state.pending_surrogate.take() {
1179                    // Low surrogate, combine with pending high surrogate
1180                    String::from_utf16(&[high_surrogate, code_point]).ok()
1181                } else {
1182                    // Invalid low surrogate without a preceding high surrogate
1183                    log::warn!(
1184                        "Received low surrogate without a preceding high surrogate: {code_point:x}"
1185                    );
1186                    None
1187                }
1188            }
1189            _ => {
1190                self.state.pending_surrogate.set(None);
1191                char::from_u32(code_point as u32)
1192                    .filter(|c| !c.is_control())
1193                    .map(|c| c.to_string())
1194            }
1195        }
1196    }
1197
1198    fn start_tracking_mouse(&self, handle: HWND, flags: TRACKMOUSEEVENT_FLAGS) {
1199        if !self.state.hovered.get() {
1200            self.state.hovered.set(true);
1201            unsafe {
1202                TrackMouseEvent(&mut TRACKMOUSEEVENT {
1203                    cbSize: std::mem::size_of::<TRACKMOUSEEVENT>() as u32,
1204                    dwFlags: flags,
1205                    hwndTrack: handle,
1206                    dwHoverTime: HOVER_DEFAULT,
1207                })
1208                .log_err()
1209            };
1210            if let Some(mut callback) = self.state.callbacks.hovered_status_change.take() {
1211                callback(true);
1212                self.state
1213                    .callbacks
1214                    .hovered_status_change
1215                    .set(Some(callback));
1216            }
1217        }
1218    }
1219
1220    fn with_input_handler<F, R>(&self, f: F) -> Option<R>
1221    where
1222        F: FnOnce(&mut PlatformInputHandler) -> R,
1223    {
1224        let mut input_handler = self.state.input_handler.take()?;
1225        let result = f(&mut input_handler);
1226        self.state.input_handler.set(Some(input_handler));
1227        Some(result)
1228    }
1229
1230    fn with_input_handler_and_scale_factor<F, R>(&self, f: F) -> Option<R>
1231    where
1232        F: FnOnce(&mut PlatformInputHandler, f32) -> Option<R>,
1233    {
1234        let mut input_handler = self.state.input_handler.take()?;
1235        let scale_factor = self.state.scale_factor.get();
1236
1237        let result = f(&mut input_handler, scale_factor);
1238        self.state.input_handler.set(Some(input_handler));
1239        result
1240    }
1241}
1242
1243fn handle_key_event<F>(
1244    wparam: WPARAM,
1245    lparam: LPARAM,
1246    state: &WindowsWindowState,
1247    f: F,
1248) -> Option<PlatformInput>
1249where
1250    F: FnOnce(Keystroke, bool) -> PlatformInput,
1251{
1252    let virtual_key = VIRTUAL_KEY(wparam.loword());
1253    let modifiers = current_modifiers();
1254
1255    match virtual_key {
1256        VK_SHIFT | VK_CONTROL | VK_MENU | VK_LMENU | VK_RMENU | VK_LWIN | VK_RWIN => {
1257            if state
1258                .last_reported_modifiers
1259                .get()
1260                .is_some_and(|prev_modifiers| prev_modifiers == modifiers)
1261            {
1262                return None;
1263            }
1264            state.last_reported_modifiers.set(Some(modifiers));
1265            Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1266                modifiers,
1267                capslock: current_capslock(),
1268            }))
1269        }
1270        VK_PACKET => None,
1271        VK_CAPITAL => {
1272            let capslock = current_capslock();
1273            if state
1274                .last_reported_capslock
1275                .get()
1276                .is_some_and(|prev_capslock| prev_capslock == capslock)
1277            {
1278                return None;
1279            }
1280            state.last_reported_capslock.set(Some(capslock));
1281            Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent {
1282                modifiers,
1283                capslock,
1284            }))
1285        }
1286        vkey => {
1287            let keystroke = parse_normal_key(vkey, lparam, modifiers)?;
1288            Some(f(keystroke.0, keystroke.1))
1289        }
1290    }
1291}
1292
1293fn parse_immutable(vkey: VIRTUAL_KEY) -> Option<String> {
1294    Some(
1295        match vkey {
1296            VK_SPACE => "space",
1297            VK_BACK => "backspace",
1298            VK_RETURN => "enter",
1299            VK_TAB => "tab",
1300            VK_UP => "up",
1301            VK_DOWN => "down",
1302            VK_RIGHT => "right",
1303            VK_LEFT => "left",
1304            VK_HOME => "home",
1305            VK_END => "end",
1306            VK_PRIOR => "pageup",
1307            VK_NEXT => "pagedown",
1308            VK_BROWSER_BACK => "back",
1309            VK_BROWSER_FORWARD => "forward",
1310            VK_ESCAPE => "escape",
1311            VK_INSERT => "insert",
1312            VK_DELETE => "delete",
1313            VK_APPS => "menu",
1314            VK_F1 => "f1",
1315            VK_F2 => "f2",
1316            VK_F3 => "f3",
1317            VK_F4 => "f4",
1318            VK_F5 => "f5",
1319            VK_F6 => "f6",
1320            VK_F7 => "f7",
1321            VK_F8 => "f8",
1322            VK_F9 => "f9",
1323            VK_F10 => "f10",
1324            VK_F11 => "f11",
1325            VK_F12 => "f12",
1326            VK_F13 => "f13",
1327            VK_F14 => "f14",
1328            VK_F15 => "f15",
1329            VK_F16 => "f16",
1330            VK_F17 => "f17",
1331            VK_F18 => "f18",
1332            VK_F19 => "f19",
1333            VK_F20 => "f20",
1334            VK_F21 => "f21",
1335            VK_F22 => "f22",
1336            VK_F23 => "f23",
1337            VK_F24 => "f24",
1338            _ => return None,
1339        }
1340        .to_string(),
1341    )
1342}
1343
1344fn parse_normal_key(
1345    vkey: VIRTUAL_KEY,
1346    lparam: LPARAM,
1347    mut modifiers: Modifiers,
1348) -> Option<(Keystroke, bool)> {
1349    let (key_char, prefer_character_input) = process_key(vkey, lparam.hiword());
1350
1351    let key = parse_immutable(vkey).or_else(|| {
1352        let scan_code = lparam.hiword() & 0xFF;
1353        get_keystroke_key(vkey, scan_code as u32, &mut modifiers)
1354    })?;
1355
1356    Some((
1357        Keystroke {
1358            modifiers,
1359            key,
1360            key_char,
1361        },
1362        prefer_character_input,
1363    ))
1364}
1365
1366fn process_key(vkey: VIRTUAL_KEY, scan_code: u16) -> (Option<String>, bool) {
1367    let mut keyboard_state = [0u8; 256];
1368    unsafe {
1369        if GetKeyboardState(&mut keyboard_state).is_err() {
1370            return (None, false);
1371        }
1372    }
1373
1374    let mut buffer_c = [0u16; 8];
1375    let result_c = unsafe {
1376        ToUnicode(
1377            vkey.0 as u32,
1378            scan_code as u32,
1379            Some(&keyboard_state),
1380            &mut buffer_c,
1381            0x4,
1382        )
1383    };
1384
1385    if result_c == 0 {
1386        return (None, false);
1387    }
1388
1389    let c = &buffer_c[..result_c.unsigned_abs() as usize];
1390    let key_char = String::from_utf16(c)
1391        .ok()
1392        .filter(|s| !s.is_empty() && !s.chars().next().unwrap().is_control());
1393
1394    if result_c < 0 {
1395        return (key_char, true);
1396    }
1397
1398    if key_char.is_none() {
1399        return (None, false);
1400    }
1401
1402    // Workaround for some bug that makes the compiler think keyboard_state is still zeroed out
1403    let keyboard_state = std::hint::black_box(keyboard_state);
1404    let ctrl_down = (keyboard_state[VK_CONTROL.0 as usize] & 0x80) != 0;
1405    let alt_down = (keyboard_state[VK_MENU.0 as usize] & 0x80) != 0;
1406    let win_down = (keyboard_state[VK_LWIN.0 as usize] & 0x80) != 0
1407        || (keyboard_state[VK_RWIN.0 as usize] & 0x80) != 0;
1408
1409    let has_modifiers = ctrl_down || alt_down || win_down;
1410    if !has_modifiers {
1411        return (key_char, false);
1412    }
1413
1414    let mut state_no_modifiers = keyboard_state;
1415    state_no_modifiers[VK_CONTROL.0 as usize] = 0;
1416    state_no_modifiers[VK_LCONTROL.0 as usize] = 0;
1417    state_no_modifiers[VK_RCONTROL.0 as usize] = 0;
1418    state_no_modifiers[VK_MENU.0 as usize] = 0;
1419    state_no_modifiers[VK_LMENU.0 as usize] = 0;
1420    state_no_modifiers[VK_RMENU.0 as usize] = 0;
1421    state_no_modifiers[VK_LWIN.0 as usize] = 0;
1422    state_no_modifiers[VK_RWIN.0 as usize] = 0;
1423
1424    let mut buffer_c_no_modifiers = [0u16; 8];
1425    let result_c_no_modifiers = unsafe {
1426        ToUnicode(
1427            vkey.0 as u32,
1428            scan_code as u32,
1429            Some(&state_no_modifiers),
1430            &mut buffer_c_no_modifiers,
1431            0x4,
1432        )
1433    };
1434
1435    let c_no_modifiers = &buffer_c_no_modifiers[..result_c_no_modifiers.unsigned_abs() as usize];
1436    (
1437        key_char,
1438        result_c != result_c_no_modifiers || c != c_no_modifiers,
1439    )
1440}
1441
1442fn parse_ime_composition_string(ctx: HIMC, comp_type: IME_COMPOSITION_STRING) -> Option<String> {
1443    unsafe {
1444        let string_len = ImmGetCompositionStringW(ctx, comp_type, None, 0);
1445        if string_len >= 0 {
1446            let mut buffer = vec![0u8; string_len as usize + 2];
1447            ImmGetCompositionStringW(
1448                ctx,
1449                comp_type,
1450                Some(buffer.as_mut_ptr() as _),
1451                string_len as _,
1452            );
1453            let wstring = std::slice::from_raw_parts::<u16>(
1454                buffer.as_mut_ptr().cast::<u16>(),
1455                string_len as usize / 2,
1456            );
1457            Some(String::from_utf16_lossy(wstring))
1458        } else {
1459            None
1460        }
1461    }
1462}
1463
1464#[inline]
1465fn retrieve_composition_cursor_position(ctx: HIMC) -> usize {
1466    unsafe { ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0) as usize }
1467}
1468
1469#[inline]
1470fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1471    unsafe { GetKeyState(vkey.0 as i32) < 0 }
1472}
1473
1474#[inline]
1475pub(crate) fn current_modifiers() -> Modifiers {
1476    Modifiers {
1477        control: is_virtual_key_pressed(VK_CONTROL),
1478        alt: is_virtual_key_pressed(VK_MENU),
1479        shift: is_virtual_key_pressed(VK_SHIFT),
1480        platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1481        function: false,
1482    }
1483}
1484
1485#[inline]
1486pub(crate) fn current_capslock() -> Capslock {
1487    let on = unsafe { GetKeyState(VK_CAPITAL.0 as i32) & 1 } > 0;
1488    Capslock { on }
1489}
1490
1491fn get_client_area_insets(
1492    handle: HWND,
1493    is_maximized: bool,
1494    windows_version: WindowsVersion,
1495) -> RECT {
1496    // For maximized windows, Windows outdents the window rect from the screen's client rect
1497    // by `frame_thickness` on each edge, meaning `insets` must contain `frame_thickness`
1498    // on all sides (including the top) to avoid the client area extending onto adjacent
1499    // monitors.
1500    //
1501    // For non-maximized windows, things become complicated:
1502    //
1503    // - On Windows 10
1504    // The top inset must be zero, since if there is any nonclient area, Windows will draw
1505    // a full native titlebar outside the client area. (This doesn't occur in the maximized
1506    // case.)
1507    //
1508    // - On Windows 11
1509    // The top inset is calculated using an empirical formula that I derived through various
1510    // tests. Without this, the top 1-2 rows of pixels in our window would be obscured.
1511    let dpi = unsafe { GetDpiForWindow(handle) };
1512    let frame_thickness = get_frame_thicknessx(dpi);
1513    let top_insets = if is_maximized {
1514        frame_thickness
1515    } else {
1516        match windows_version {
1517            WindowsVersion::Win10 => 0,
1518            WindowsVersion::Win11 => (dpi as f32 / USER_DEFAULT_SCREEN_DPI as f32).round() as i32,
1519        }
1520    };
1521    RECT {
1522        left: frame_thickness,
1523        top: top_insets,
1524        right: frame_thickness,
1525        bottom: frame_thickness,
1526    }
1527}
1528
1529// there is some additional non-visible space when talking about window
1530// borders on Windows:
1531// - SM_CXSIZEFRAME: The resize handle.
1532// - SM_CXPADDEDBORDER: Additional border space that isn't part of the resize handle.
1533fn get_frame_thicknessx(dpi: u32) -> i32 {
1534    let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CXSIZEFRAME, dpi) };
1535    let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1536    resize_frame_thickness + padding_thickness
1537}
1538
1539fn get_frame_thicknessy(dpi: u32) -> i32 {
1540    let resize_frame_thickness = unsafe { GetSystemMetricsForDpi(SM_CYSIZEFRAME, dpi) };
1541    let padding_thickness = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1542    resize_frame_thickness + padding_thickness
1543}
1544
1545fn notify_frame_changed(handle: HWND) {
1546    unsafe {
1547        SetWindowPos(
1548            handle,
1549            None,
1550            0,
1551            0,
1552            0,
1553            0,
1554            SWP_FRAMECHANGED
1555                | SWP_NOACTIVATE
1556                | SWP_NOCOPYBITS
1557                | SWP_NOMOVE
1558                | SWP_NOOWNERZORDER
1559                | SWP_NOREPOSITION
1560                | SWP_NOSENDCHANGING
1561                | SWP_NOSIZE
1562                | SWP_NOZORDER,
1563        )
1564        .log_err();
1565    }
1566}