events.rs

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