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