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