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        HiDpi::*,
  11        Input::{Ime::*, KeyboardAndMouse::*},
  12        WindowsAndMessaging::*,
  13    },
  14};
  15
  16use crate::*;
  17
  18pub(crate) const CURSOR_STYLE_CHANGED: u32 = WM_USER + 1;
  19pub(crate) const CLOSE_ONE_WINDOW: u32 = WM_USER + 2;
  20
  21const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
  22
  23pub(crate) fn handle_msg(
  24    handle: HWND,
  25    msg: u32,
  26    wparam: WPARAM,
  27    lparam: LPARAM,
  28    state_ptr: Rc<WindowsWindowStatePtr>,
  29) -> LRESULT {
  30    let handled = match msg {
  31        WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr),
  32        WM_CREATE => handle_create_msg(handle, state_ptr),
  33        WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
  34        WM_SIZE => handle_size_msg(lparam, state_ptr),
  35        WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => handle_size_move_loop(handle),
  36        WM_EXITSIZEMOVE | WM_EXITMENULOOP => handle_size_move_loop_exit(handle),
  37        WM_TIMER => handle_timer_msg(handle, wparam, state_ptr),
  38        WM_NCCALCSIZE => handle_calc_client_size(handle, wparam, lparam, state_ptr),
  39        WM_DPICHANGED => handle_dpi_changed_msg(handle, wparam, lparam, state_ptr),
  40        WM_DISPLAYCHANGE => handle_display_change_msg(handle, state_ptr),
  41        WM_NCHITTEST => handle_hit_test_msg(handle, msg, wparam, lparam, state_ptr),
  42        WM_PAINT => handle_paint_msg(handle, state_ptr),
  43        WM_CLOSE => handle_close_msg(state_ptr),
  44        WM_DESTROY => handle_destroy_msg(handle, state_ptr),
  45        WM_MOUSEMOVE => handle_mouse_move_msg(lparam, wparam, state_ptr),
  46        WM_NCMOUSEMOVE => handle_nc_mouse_move_msg(handle, lparam, state_ptr),
  47        WM_NCLBUTTONDOWN => {
  48            handle_nc_mouse_down_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
  49        }
  50        WM_NCRBUTTONDOWN => {
  51            handle_nc_mouse_down_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
  52        }
  53        WM_NCMBUTTONDOWN => {
  54            handle_nc_mouse_down_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
  55        }
  56        WM_NCLBUTTONUP => {
  57            handle_nc_mouse_up_msg(handle, MouseButton::Left, wparam, lparam, state_ptr)
  58        }
  59        WM_NCRBUTTONUP => {
  60            handle_nc_mouse_up_msg(handle, MouseButton::Right, wparam, lparam, state_ptr)
  61        }
  62        WM_NCMBUTTONUP => {
  63            handle_nc_mouse_up_msg(handle, MouseButton::Middle, wparam, lparam, state_ptr)
  64        }
  65        WM_LBUTTONDOWN => handle_mouse_down_msg(MouseButton::Left, lparam, state_ptr),
  66        WM_RBUTTONDOWN => handle_mouse_down_msg(MouseButton::Right, lparam, state_ptr),
  67        WM_MBUTTONDOWN => handle_mouse_down_msg(MouseButton::Middle, lparam, state_ptr),
  68        WM_XBUTTONDOWN => handle_xbutton_msg(wparam, lparam, handle_mouse_down_msg, state_ptr),
  69        WM_LBUTTONUP => handle_mouse_up_msg(MouseButton::Left, lparam, state_ptr),
  70        WM_RBUTTONUP => handle_mouse_up_msg(MouseButton::Right, lparam, state_ptr),
  71        WM_MBUTTONUP => handle_mouse_up_msg(MouseButton::Middle, lparam, state_ptr),
  72        WM_XBUTTONUP => handle_xbutton_msg(wparam, lparam, handle_mouse_up_msg, state_ptr),
  73        WM_MOUSEWHEEL => handle_mouse_wheel_msg(handle, wparam, lparam, state_ptr),
  74        WM_MOUSEHWHEEL => handle_mouse_horizontal_wheel_msg(handle, wparam, lparam, state_ptr),
  75        WM_SYSKEYDOWN => handle_syskeydown_msg(wparam, lparam, state_ptr),
  76        WM_SYSKEYUP => handle_syskeyup_msg(wparam, state_ptr),
  77        WM_KEYDOWN => handle_keydown_msg(wparam, lparam, state_ptr),
  78        WM_KEYUP => handle_keyup_msg(wparam, state_ptr),
  79        WM_CHAR => handle_char_msg(wparam, lparam, state_ptr),
  80        WM_IME_STARTCOMPOSITION => handle_ime_position(handle, state_ptr),
  81        WM_IME_COMPOSITION => handle_ime_composition(handle, lparam, state_ptr),
  82        WM_SETCURSOR => handle_set_cursor(lparam, state_ptr),
  83        WM_SETTINGCHANGE => handle_system_settings_changed(state_ptr),
  84        CURSOR_STYLE_CHANGED => handle_cursor_changed(lparam, state_ptr),
  85        _ => None,
  86    };
  87    if let Some(n) = handled {
  88        LRESULT(n)
  89    } else {
  90        unsafe { DefWindowProcW(handle, msg, wparam, lparam) }
  91    }
  92}
  93
  94fn handle_move_msg(
  95    handle: HWND,
  96    lparam: LPARAM,
  97    state_ptr: Rc<WindowsWindowStatePtr>,
  98) -> Option<isize> {
  99    let x = lparam.signed_loword() as i32;
 100    let y = lparam.signed_hiword() as i32;
 101    let mut lock = state_ptr.state.borrow_mut();
 102    lock.origin = point(x.into(), y.into());
 103    let size = lock.physical_size;
 104    let center_x = x + size.width.0 / 2;
 105    let center_y = y + size.height.0 / 2;
 106    let monitor_bounds = lock.display.bounds();
 107    if center_x < monitor_bounds.left().0
 108        || center_x > monitor_bounds.right().0
 109        || center_y < monitor_bounds.top().0
 110        || center_y > monitor_bounds.bottom().0
 111    {
 112        // center of the window may have moved to another monitor
 113        let monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
 114        // minimize the window can trigger this event too, in this case,
 115        // monitor is invalid, we do nothing.
 116        if !monitor.is_invalid() && lock.display.handle != monitor {
 117            // we will get the same monitor if we only have one
 118            lock.display = WindowsDisplay::new_with_handle(monitor);
 119        }
 120    }
 121    if let Some(mut callback) = lock.callbacks.moved.take() {
 122        drop(lock);
 123        callback();
 124        state_ptr.state.borrow_mut().callbacks.moved = Some(callback);
 125    }
 126    Some(0)
 127}
 128
 129fn handle_size_msg(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 130    let width = lparam.loword().max(1) as i32;
 131    let height = lparam.hiword().max(1) as i32;
 132    let new_physical_size = size(width.into(), height.into());
 133    let mut lock = state_ptr.state.borrow_mut();
 134    let scale_factor = lock.scale_factor;
 135    lock.physical_size = new_physical_size;
 136    lock.renderer.update_drawable_size(Size {
 137        width: width as f64,
 138        height: height as f64,
 139    });
 140    if let Some(mut callback) = lock.callbacks.resize.take() {
 141        drop(lock);
 142        let logical_size = logical_size(new_physical_size, scale_factor);
 143        callback(logical_size, scale_factor);
 144        state_ptr.state.borrow_mut().callbacks.resize = Some(callback);
 145    }
 146    Some(0)
 147}
 148
 149fn handle_size_move_loop(handle: HWND) -> Option<isize> {
 150    unsafe {
 151        let ret = SetTimer(handle, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
 152        if ret == 0 {
 153            log::error!(
 154                "unable to create timer: {}",
 155                std::io::Error::last_os_error()
 156            );
 157        }
 158    }
 159    None
 160}
 161
 162fn handle_size_move_loop_exit(handle: HWND) -> Option<isize> {
 163    unsafe {
 164        KillTimer(handle, SIZE_MOVE_LOOP_TIMER_ID).log_err();
 165    }
 166    None
 167}
 168
 169fn handle_timer_msg(
 170    handle: HWND,
 171    wparam: WPARAM,
 172    state_ptr: Rc<WindowsWindowStatePtr>,
 173) -> Option<isize> {
 174    if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
 175        handle_paint_msg(handle, state_ptr)
 176    } else {
 177        None
 178    }
 179}
 180
 181fn handle_paint_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 182    let mut lock = state_ptr.state.borrow_mut();
 183    if let Some(mut request_frame) = lock.callbacks.request_frame.take() {
 184        drop(lock);
 185        request_frame();
 186        state_ptr.state.borrow_mut().callbacks.request_frame = Some(request_frame);
 187    }
 188    unsafe { ValidateRect(handle, None).ok().log_err() };
 189    Some(0)
 190}
 191
 192fn handle_close_msg(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 193    let mut lock = state_ptr.state.borrow_mut();
 194    if let Some(mut callback) = lock.callbacks.should_close.take() {
 195        drop(lock);
 196        let should_close = callback();
 197        state_ptr.state.borrow_mut().callbacks.should_close = Some(callback);
 198        if should_close {
 199            None
 200        } else {
 201            Some(0)
 202        }
 203    } else {
 204        None
 205    }
 206}
 207
 208fn handle_destroy_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 209    let callback = {
 210        let mut lock = state_ptr.state.borrow_mut();
 211        lock.callbacks.close.take()
 212    };
 213    if let Some(callback) = callback {
 214        callback();
 215    }
 216    unsafe {
 217        PostMessageW(None, CLOSE_ONE_WINDOW, None, LPARAM(handle.0)).log_err();
 218    }
 219    Some(0)
 220}
 221
 222fn handle_mouse_move_msg(
 223    lparam: LPARAM,
 224    wparam: WPARAM,
 225    state_ptr: Rc<WindowsWindowStatePtr>,
 226) -> Option<isize> {
 227    let mut lock = state_ptr.state.borrow_mut();
 228    if let Some(mut callback) = lock.callbacks.input.take() {
 229        let scale_factor = lock.scale_factor;
 230        drop(lock);
 231        let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
 232            flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
 233            flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
 234            flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
 235            flags if flags.contains(MK_XBUTTON1) => {
 236                Some(MouseButton::Navigate(NavigationDirection::Back))
 237            }
 238            flags if flags.contains(MK_XBUTTON2) => {
 239                Some(MouseButton::Navigate(NavigationDirection::Forward))
 240            }
 241            _ => None,
 242        };
 243        let x = lparam.signed_loword() as f32;
 244        let y = lparam.signed_hiword() as f32;
 245        let event = MouseMoveEvent {
 246            position: logical_point(x, y, scale_factor),
 247            pressed_button,
 248            modifiers: current_modifiers(),
 249        };
 250        let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
 251            Some(0)
 252        } else {
 253            Some(1)
 254        };
 255        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 256        return result;
 257    }
 258    Some(1)
 259}
 260
 261fn handle_syskeydown_msg(
 262    wparam: WPARAM,
 263    lparam: LPARAM,
 264    state_ptr: Rc<WindowsWindowStatePtr>,
 265) -> Option<isize> {
 266    // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
 267    // shortcuts.
 268    let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
 269        return None;
 270    };
 271    let mut lock = state_ptr.state.borrow_mut();
 272    let Some(mut func) = lock.callbacks.input.take() else {
 273        return None;
 274    };
 275    drop(lock);
 276    let event = KeyDownEvent {
 277        keystroke,
 278        is_held: lparam.0 & (0x1 << 30) > 0,
 279    };
 280    let result = if func(PlatformInput::KeyDown(event)).default_prevented {
 281        Some(0)
 282    } else {
 283        None
 284    };
 285    state_ptr.state.borrow_mut().callbacks.input = Some(func);
 286
 287    result
 288}
 289
 290fn handle_syskeyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 291    // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
 292    // shortcuts.
 293    let Some(keystroke) = parse_syskeydown_msg_keystroke(wparam) else {
 294        return None;
 295    };
 296    let mut lock = state_ptr.state.borrow_mut();
 297    let Some(mut func) = lock.callbacks.input.take() else {
 298        return None;
 299    };
 300    drop(lock);
 301    let event = KeyUpEvent { keystroke };
 302    let result = if func(PlatformInput::KeyUp(event)).default_prevented {
 303        Some(0)
 304    } else {
 305        Some(1)
 306    };
 307    state_ptr.state.borrow_mut().callbacks.input = Some(func);
 308
 309    result
 310}
 311
 312fn handle_keydown_msg(
 313    wparam: WPARAM,
 314    lparam: LPARAM,
 315    state_ptr: Rc<WindowsWindowStatePtr>,
 316) -> Option<isize> {
 317    let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
 318        return Some(1);
 319    };
 320    let mut lock = state_ptr.state.borrow_mut();
 321    let Some(mut func) = lock.callbacks.input.take() else {
 322        return Some(1);
 323    };
 324    drop(lock);
 325    let event = KeyDownEvent {
 326        keystroke,
 327        is_held: lparam.0 & (0x1 << 30) > 0,
 328    };
 329    let result = if func(PlatformInput::KeyDown(event)).default_prevented {
 330        Some(0)
 331    } else {
 332        Some(1)
 333    };
 334    state_ptr.state.borrow_mut().callbacks.input = Some(func);
 335
 336    result
 337}
 338
 339fn handle_keyup_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 340    let Some(keystroke) = parse_keydown_msg_keystroke(wparam) else {
 341        return Some(1);
 342    };
 343    let mut lock = state_ptr.state.borrow_mut();
 344    let Some(mut func) = lock.callbacks.input.take() else {
 345        return Some(1);
 346    };
 347    drop(lock);
 348    let event = KeyUpEvent { keystroke };
 349    let result = if func(PlatformInput::KeyUp(event)).default_prevented {
 350        Some(0)
 351    } else {
 352        Some(1)
 353    };
 354    state_ptr.state.borrow_mut().callbacks.input = Some(func);
 355
 356    result
 357}
 358
 359fn handle_char_msg(
 360    wparam: WPARAM,
 361    lparam: LPARAM,
 362    state_ptr: Rc<WindowsWindowStatePtr>,
 363) -> Option<isize> {
 364    let Some(keystroke) = parse_char_msg_keystroke(wparam) else {
 365        return Some(1);
 366    };
 367    let mut lock = state_ptr.state.borrow_mut();
 368    let Some(mut func) = lock.callbacks.input.take() else {
 369        return Some(1);
 370    };
 371    drop(lock);
 372    let ime_key = keystroke.ime_key.clone();
 373    let event = KeyDownEvent {
 374        keystroke,
 375        is_held: lparam.0 & (0x1 << 30) > 0,
 376    };
 377
 378    let dispatch_event_result = func(PlatformInput::KeyDown(event));
 379    let mut lock = state_ptr.state.borrow_mut();
 380    lock.callbacks.input = Some(func);
 381    if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
 382        return Some(0);
 383    }
 384    let Some(ime_char) = ime_key else {
 385        return Some(1);
 386    };
 387    let Some(mut input_handler) = lock.input_handler.take() else {
 388        return Some(1);
 389    };
 390    drop(lock);
 391    input_handler.replace_text_in_range(None, &ime_char);
 392    state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 393
 394    Some(0)
 395}
 396
 397fn handle_mouse_down_msg(
 398    button: MouseButton,
 399    lparam: LPARAM,
 400    state_ptr: Rc<WindowsWindowStatePtr>,
 401) -> Option<isize> {
 402    let mut lock = state_ptr.state.borrow_mut();
 403    if let Some(mut callback) = lock.callbacks.input.take() {
 404        let x = lparam.signed_loword() as f32;
 405        let y = lparam.signed_hiword() as f32;
 406        let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
 407        let click_count = lock.click_state.update(button, physical_point);
 408        let scale_factor = lock.scale_factor;
 409        drop(lock);
 410
 411        let event = MouseDownEvent {
 412            button,
 413            position: logical_point(x, y, scale_factor),
 414            modifiers: current_modifiers(),
 415            click_count,
 416            first_mouse: false,
 417        };
 418        let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
 419            Some(0)
 420        } else {
 421            Some(1)
 422        };
 423        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 424
 425        result
 426    } else {
 427        Some(1)
 428    }
 429}
 430
 431fn handle_mouse_up_msg(
 432    button: MouseButton,
 433    lparam: LPARAM,
 434    state_ptr: Rc<WindowsWindowStatePtr>,
 435) -> Option<isize> {
 436    let mut lock = state_ptr.state.borrow_mut();
 437    if let Some(mut callback) = lock.callbacks.input.take() {
 438        let x = lparam.signed_loword() as f32;
 439        let y = lparam.signed_hiword() as f32;
 440        let click_count = lock.click_state.current_count;
 441        let scale_factor = lock.scale_factor;
 442        drop(lock);
 443
 444        let event = MouseUpEvent {
 445            button,
 446            position: logical_point(x, y, scale_factor),
 447            modifiers: current_modifiers(),
 448            click_count,
 449        };
 450        let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
 451            Some(0)
 452        } else {
 453            Some(1)
 454        };
 455        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 456
 457        result
 458    } else {
 459        Some(1)
 460    }
 461}
 462
 463fn handle_xbutton_msg(
 464    wparam: WPARAM,
 465    lparam: LPARAM,
 466    handler: impl Fn(MouseButton, LPARAM, Rc<WindowsWindowStatePtr>) -> Option<isize>,
 467    state_ptr: Rc<WindowsWindowStatePtr>,
 468) -> Option<isize> {
 469    let nav_dir = match wparam.hiword() {
 470        XBUTTON1 => NavigationDirection::Back,
 471        XBUTTON2 => NavigationDirection::Forward,
 472        _ => return Some(1),
 473    };
 474    handler(MouseButton::Navigate(nav_dir), lparam, state_ptr)
 475}
 476
 477fn handle_mouse_wheel_msg(
 478    handle: HWND,
 479    wparam: WPARAM,
 480    lparam: LPARAM,
 481    state_ptr: Rc<WindowsWindowStatePtr>,
 482) -> Option<isize> {
 483    let mut lock = state_ptr.state.borrow_mut();
 484    if let Some(mut callback) = lock.callbacks.input.take() {
 485        let scale_factor = lock.scale_factor;
 486        let wheel_scroll_lines = lock.system_settings.mouse_wheel_settings.wheel_scroll_lines;
 487        drop(lock);
 488        let wheel_distance =
 489            (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_lines as f32;
 490        let mut cursor_point = POINT {
 491            x: lparam.signed_loword().into(),
 492            y: lparam.signed_hiword().into(),
 493        };
 494        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 495        let event = ScrollWheelEvent {
 496            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 497            delta: ScrollDelta::Lines(Point {
 498                x: 0.0,
 499                y: wheel_distance,
 500            }),
 501            modifiers: current_modifiers(),
 502            touch_phase: TouchPhase::Moved,
 503        };
 504        let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
 505            Some(0)
 506        } else {
 507            Some(1)
 508        };
 509        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 510
 511        result
 512    } else {
 513        Some(1)
 514    }
 515}
 516
 517fn handle_mouse_horizontal_wheel_msg(
 518    handle: HWND,
 519    wparam: WPARAM,
 520    lparam: LPARAM,
 521    state_ptr: Rc<WindowsWindowStatePtr>,
 522) -> Option<isize> {
 523    let mut lock = state_ptr.state.borrow_mut();
 524    if let Some(mut callback) = lock.callbacks.input.take() {
 525        let scale_factor = lock.scale_factor;
 526        let wheel_scroll_chars = lock.system_settings.mouse_wheel_settings.wheel_scroll_chars;
 527        drop(lock);
 528        let wheel_distance =
 529            (-wparam.signed_hiword() as f32 / WHEEL_DELTA as f32) * wheel_scroll_chars as f32;
 530        let mut cursor_point = POINT {
 531            x: lparam.signed_loword().into(),
 532            y: lparam.signed_hiword().into(),
 533        };
 534        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 535        let event = ScrollWheelEvent {
 536            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 537            delta: ScrollDelta::Lines(Point {
 538                x: wheel_distance,
 539                y: 0.0,
 540            }),
 541            modifiers: current_modifiers(),
 542            touch_phase: TouchPhase::Moved,
 543        };
 544        let result = if callback(PlatformInput::ScrollWheel(event)).default_prevented {
 545            Some(0)
 546        } else {
 547            Some(1)
 548        };
 549        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 550
 551        result
 552    } else {
 553        Some(1)
 554    }
 555}
 556
 557fn handle_ime_position(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 558    unsafe {
 559        let mut lock = state_ptr.state.borrow_mut();
 560        let ctx = ImmGetContext(handle);
 561        let Some(mut input_handler) = lock.input_handler.take() else {
 562            return Some(1);
 563        };
 564        let scale_factor = lock.scale_factor;
 565        drop(lock);
 566
 567        let Some(caret_range) = input_handler.selected_text_range() else {
 568            state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 569            return Some(0);
 570        };
 571        let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
 572        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 573        let config = CANDIDATEFORM {
 574            dwStyle: CFS_CANDIDATEPOS,
 575            // logical to physical
 576            ptCurrentPos: POINT {
 577                x: (caret_position.origin.x.0 * scale_factor) as i32,
 578                y: (caret_position.origin.y.0 * scale_factor) as i32
 579                    + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
 580            },
 581            ..Default::default()
 582        };
 583        ImmSetCandidateWindow(ctx, &config as _).ok().log_err();
 584        ImmReleaseContext(handle, ctx).ok().log_err();
 585        Some(0)
 586    }
 587}
 588
 589fn handle_ime_composition(
 590    handle: HWND,
 591    lparam: LPARAM,
 592    state_ptr: Rc<WindowsWindowStatePtr>,
 593) -> Option<isize> {
 594    let mut ime_input = None;
 595    if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
 596        let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
 597            return None;
 598        };
 599        let mut lock = state_ptr.state.borrow_mut();
 600        let Some(mut input_handler) = lock.input_handler.take() else {
 601            return None;
 602        };
 603        drop(lock);
 604        input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
 605        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 606        ime_input = Some(string);
 607    }
 608    if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
 609        let Some(ref comp_string) = ime_input else {
 610            return None;
 611        };
 612        let caret_pos = retrieve_composition_cursor_position(handle);
 613        let mut lock = state_ptr.state.borrow_mut();
 614        let Some(mut input_handler) = lock.input_handler.take() else {
 615            return None;
 616        };
 617        drop(lock);
 618        input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
 619        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 620    }
 621    if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
 622        let Some(comp_result) = parse_ime_compostion_result(handle) else {
 623            return None;
 624        };
 625        let mut lock = state_ptr.state.borrow_mut();
 626        let Some(mut input_handler) = lock.input_handler.take() else {
 627            return Some(1);
 628        };
 629        drop(lock);
 630        input_handler.replace_text_in_range(None, &comp_result);
 631        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 632        return Some(0);
 633    }
 634    // currently, we don't care other stuff
 635    None
 636}
 637
 638/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
 639fn handle_calc_client_size(
 640    handle: HWND,
 641    wparam: WPARAM,
 642    lparam: LPARAM,
 643    state_ptr: Rc<WindowsWindowStatePtr>,
 644) -> Option<isize> {
 645    if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
 646        return None;
 647    }
 648
 649    if wparam.0 == 0 {
 650        return None;
 651    }
 652
 653    let dpi = unsafe { GetDpiForWindow(handle) };
 654
 655    let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
 656    let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 657    let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
 658
 659    // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
 660    let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
 661    let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
 662
 663    requested_client_rect[0].right -= frame_x + padding;
 664    requested_client_rect[0].left += frame_x + padding;
 665    requested_client_rect[0].bottom -= frame_y + padding;
 666
 667    Some(0)
 668}
 669
 670fn handle_activate_msg(
 671    handle: HWND,
 672    wparam: WPARAM,
 673    state_ptr: Rc<WindowsWindowStatePtr>,
 674) -> Option<isize> {
 675    let activated = wparam.loword() > 0;
 676    if state_ptr.hide_title_bar {
 677        if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
 678            unsafe {
 679                InvalidateRect(handle, Some(&titlebar_rect), FALSE)
 680                    .ok()
 681                    .log_err()
 682            };
 683        }
 684    }
 685    let this = state_ptr.clone();
 686    state_ptr
 687        .executor
 688        .spawn(async move {
 689            let mut lock = this.state.borrow_mut();
 690            if let Some(mut cb) = lock.callbacks.active_status_change.take() {
 691                drop(lock);
 692                cb(activated);
 693                this.state.borrow_mut().callbacks.active_status_change = Some(cb);
 694            }
 695        })
 696        .detach();
 697
 698    None
 699}
 700
 701fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 702    let mut size_rect = RECT::default();
 703    unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
 704
 705    let width = size_rect.right - size_rect.left;
 706    let height = size_rect.bottom - size_rect.top;
 707
 708    if state_ptr.hide_title_bar {
 709        unsafe {
 710            SetWindowPos(
 711                handle,
 712                None,
 713                size_rect.left,
 714                size_rect.top,
 715                width,
 716                height,
 717                SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
 718            )
 719            .log_err()
 720        };
 721    }
 722
 723    Some(0)
 724}
 725
 726fn handle_dpi_changed_msg(
 727    handle: HWND,
 728    wparam: WPARAM,
 729    lparam: LPARAM,
 730    state_ptr: Rc<WindowsWindowStatePtr>,
 731) -> Option<isize> {
 732    let new_dpi = wparam.loword() as f32;
 733    state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
 734
 735    let rect = unsafe { &*(lparam.0 as *const RECT) };
 736    let width = rect.right - rect.left;
 737    let height = rect.bottom - rect.top;
 738    // this will emit `WM_SIZE` and `WM_MOVE` right here
 739    // even before this function returns
 740    // the new size is handled in `WM_SIZE`
 741    unsafe {
 742        SetWindowPos(
 743            handle,
 744            None,
 745            rect.left,
 746            rect.top,
 747            width,
 748            height,
 749            SWP_NOZORDER | SWP_NOACTIVATE,
 750        )
 751        .context("unable to set window position after dpi has changed")
 752        .log_err();
 753    }
 754
 755    Some(0)
 756}
 757
 758/// The following conditions will trigger this event:
 759/// 1. The monitor on which the window is located goes offline or changes resolution.
 760/// 2. Another monitor goes offline, is plugged in, or changes resolution.
 761///
 762/// In either case, the window will only receive information from the monitor on which
 763/// it is located.
 764///
 765/// For example, in the case of condition 2, where the monitor on which the window is
 766/// located has actually changed nothing, it will still receive this event.
 767fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 768    // NOTE:
 769    // Even the `lParam` holds the resolution of the screen, we just ignore it.
 770    // Because WM_DPICHANGED, WM_MOVE, WM_SIEZ will come first, window reposition and resize
 771    // are handled there.
 772    // So we only care about if monitor is disconnected.
 773    let previous_monitor = state_ptr.as_ref().state.borrow().display;
 774    if WindowsDisplay::is_connected(previous_monitor.handle) {
 775        // we are fine, other display changed
 776        return None;
 777    }
 778    // display disconnected
 779    // in this case, the OS will move our window to another monitor, and minimize it.
 780    // we deminimize the window and query the monitor after moving
 781    unsafe {
 782        let _ = ShowWindow(handle, SW_SHOWNORMAL);
 783    };
 784    let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
 785    // all monitors disconnected
 786    if new_monitor.is_invalid() {
 787        log::error!("No monitor detected!");
 788        return None;
 789    }
 790    let new_display = WindowsDisplay::new_with_handle(new_monitor);
 791    state_ptr.as_ref().state.borrow_mut().display = new_display;
 792    Some(0)
 793}
 794
 795fn handle_hit_test_msg(
 796    handle: HWND,
 797    msg: u32,
 798    wparam: WPARAM,
 799    lparam: LPARAM,
 800    state_ptr: Rc<WindowsWindowStatePtr>,
 801) -> Option<isize> {
 802    if !state_ptr.hide_title_bar {
 803        return None;
 804    }
 805
 806    // default handler for resize areas
 807    let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
 808    if matches!(
 809        hit.0 as u32,
 810        HTNOWHERE
 811            | HTRIGHT
 812            | HTLEFT
 813            | HTTOPLEFT
 814            | HTTOP
 815            | HTTOPRIGHT
 816            | HTBOTTOMRIGHT
 817            | HTBOTTOM
 818            | HTBOTTOMLEFT
 819    ) {
 820        return Some(hit.0);
 821    }
 822
 823    if state_ptr.state.borrow().is_fullscreen() {
 824        return Some(HTCLIENT as _);
 825    }
 826
 827    let dpi = unsafe { GetDpiForWindow(handle) };
 828    let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 829    let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
 830
 831    let mut cursor_point = POINT {
 832        x: lparam.signed_loword().into(),
 833        y: lparam.signed_hiword().into(),
 834    };
 835    unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 836    if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
 837        return Some(HTTOP as _);
 838    }
 839
 840    let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
 841    if let Ok(titlebar_rect) = titlebar_rect {
 842        if cursor_point.y < titlebar_rect.bottom {
 843            let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
 844                * state_ptr.state.borrow().scale_factor) as i32;
 845            if cursor_point.x >= titlebar_rect.right - caption_btn_width {
 846                return Some(HTCLOSE as _);
 847            } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
 848                return Some(HTMAXBUTTON as _);
 849            } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
 850                return Some(HTMINBUTTON as _);
 851            }
 852
 853            return Some(HTCAPTION as _);
 854        }
 855    }
 856
 857    Some(HTCLIENT as _)
 858}
 859
 860fn handle_nc_mouse_move_msg(
 861    handle: HWND,
 862    lparam: LPARAM,
 863    state_ptr: Rc<WindowsWindowStatePtr>,
 864) -> Option<isize> {
 865    if !state_ptr.hide_title_bar {
 866        return None;
 867    }
 868
 869    let mut lock = state_ptr.state.borrow_mut();
 870    if let Some(mut callback) = lock.callbacks.input.take() {
 871        let scale_factor = lock.scale_factor;
 872        drop(lock);
 873        let mut cursor_point = POINT {
 874            x: lparam.signed_loword().into(),
 875            y: lparam.signed_hiword().into(),
 876        };
 877        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 878        let event = MouseMoveEvent {
 879            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 880            pressed_button: None,
 881            modifiers: current_modifiers(),
 882        };
 883        let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
 884            Some(0)
 885        } else {
 886            Some(1)
 887        };
 888        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 889
 890        result
 891    } else {
 892        None
 893    }
 894}
 895
 896fn handle_nc_mouse_down_msg(
 897    handle: HWND,
 898    button: MouseButton,
 899    wparam: WPARAM,
 900    lparam: LPARAM,
 901    state_ptr: Rc<WindowsWindowStatePtr>,
 902) -> Option<isize> {
 903    if !state_ptr.hide_title_bar {
 904        return None;
 905    }
 906
 907    let mut lock = state_ptr.state.borrow_mut();
 908    let result = if let Some(mut callback) = lock.callbacks.input.take() {
 909        let scale_factor = lock.scale_factor;
 910        let mut cursor_point = POINT {
 911            x: lparam.signed_loword().into(),
 912            y: lparam.signed_hiword().into(),
 913        };
 914        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 915        let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
 916        let click_count = lock.click_state.update(button, physical_point);
 917        drop(lock);
 918        let event = MouseDownEvent {
 919            button,
 920            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 921            modifiers: current_modifiers(),
 922            click_count,
 923            first_mouse: false,
 924        };
 925        let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
 926            Some(0)
 927        } else {
 928            None
 929        };
 930        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 931
 932        result
 933    } else {
 934        None
 935    };
 936
 937    // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
 938    result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0))
 939}
 940
 941fn handle_nc_mouse_up_msg(
 942    handle: HWND,
 943    button: MouseButton,
 944    wparam: WPARAM,
 945    lparam: LPARAM,
 946    state_ptr: Rc<WindowsWindowStatePtr>,
 947) -> Option<isize> {
 948    if !state_ptr.hide_title_bar {
 949        return None;
 950    }
 951
 952    let mut lock = state_ptr.state.borrow_mut();
 953    if let Some(mut callback) = lock.callbacks.input.take() {
 954        let scale_factor = lock.scale_factor;
 955        drop(lock);
 956        let mut cursor_point = POINT {
 957            x: lparam.signed_loword().into(),
 958            y: lparam.signed_hiword().into(),
 959        };
 960        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 961        let event = MouseUpEvent {
 962            button,
 963            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 964            modifiers: current_modifiers(),
 965            click_count: 1,
 966        };
 967        let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
 968            Some(0)
 969        } else {
 970            None
 971        };
 972        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 973        if result.is_some() {
 974            return result;
 975        }
 976    } else {
 977        drop(lock);
 978    }
 979
 980    if button == MouseButton::Left {
 981        match wparam.0 as u32 {
 982            HTMINBUTTON => unsafe {
 983                ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err();
 984            },
 985            HTMAXBUTTON => unsafe {
 986                if state_ptr.state.borrow().is_maximized() {
 987                    ShowWindowAsync(handle, SW_NORMAL).ok().log_err();
 988                } else {
 989                    ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err();
 990                }
 991            },
 992            HTCLOSE => unsafe {
 993                PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err();
 994            },
 995            _ => return None,
 996        };
 997        return Some(0);
 998    }
 999
1000    None
1001}
1002
1003fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1004    state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
1005    Some(0)
1006}
1007
1008fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1009    if matches!(
1010        lparam.loword() as u32,
1011        HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
1012    ) {
1013        return None;
1014    }
1015    unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
1016    Some(1)
1017}
1018
1019fn handle_system_settings_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1020    let mut lock = state_ptr.state.borrow_mut();
1021    // mouse wheel
1022    lock.system_settings.mouse_wheel_settings.update();
1023    Some(0)
1024}
1025
1026fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1027    let modifiers = current_modifiers();
1028    if !modifiers.alt {
1029        // on Windows, F10 can trigger this event, not just the alt key
1030        // and we just don't care about F10
1031        return None;
1032    }
1033
1034    let vk_code = wparam.loword();
1035    let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1036    if basic_key.is_some() {
1037        return basic_key;
1038    }
1039
1040    let key = match VIRTUAL_KEY(vk_code) {
1041        VK_BACK => Some("backspace"),
1042        VK_RETURN => Some("enter"),
1043        VK_TAB => Some("tab"),
1044        VK_UP => Some("up"),
1045        VK_DOWN => Some("down"),
1046        VK_RIGHT => Some("right"),
1047        VK_LEFT => Some("left"),
1048        VK_HOME => Some("home"),
1049        VK_END => Some("end"),
1050        VK_PRIOR => Some("pageup"),
1051        VK_NEXT => Some("pagedown"),
1052        VK_ESCAPE => Some("escape"),
1053        VK_INSERT => Some("insert"),
1054        _ => None,
1055    };
1056
1057    if let Some(key) = key {
1058        Some(Keystroke {
1059            modifiers,
1060            key: key.to_string(),
1061            ime_key: None,
1062        })
1063    } else {
1064        None
1065    }
1066}
1067
1068fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1069    let vk_code = wparam.loword();
1070
1071    let modifiers = current_modifiers();
1072    if modifiers.control || modifiers.alt {
1073        let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1074        if basic_key.is_some() {
1075            return basic_key;
1076        }
1077    }
1078
1079    if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
1080        let offset = vk_code - VK_F1.0;
1081        return Some(Keystroke {
1082            modifiers,
1083            key: format!("f{}", offset + 1),
1084            ime_key: None,
1085        });
1086    }
1087
1088    let key = match VIRTUAL_KEY(vk_code) {
1089        VK_BACK => Some("backspace"),
1090        VK_RETURN => Some("enter"),
1091        VK_TAB => Some("tab"),
1092        VK_UP => Some("up"),
1093        VK_DOWN => Some("down"),
1094        VK_RIGHT => Some("right"),
1095        VK_LEFT => Some("left"),
1096        VK_HOME => Some("home"),
1097        VK_END => Some("end"),
1098        VK_PRIOR => Some("pageup"),
1099        VK_NEXT => Some("pagedown"),
1100        VK_ESCAPE => Some("escape"),
1101        VK_INSERT => Some("insert"),
1102        VK_DELETE => Some("delete"),
1103        _ => None,
1104    };
1105
1106    if let Some(key) = key {
1107        Some(Keystroke {
1108            modifiers,
1109            key: key.to_string(),
1110            ime_key: None,
1111        })
1112    } else {
1113        None
1114    }
1115}
1116
1117fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1118    let src = [wparam.0 as u16];
1119    let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
1120        return None;
1121    };
1122    if first_char.is_control() {
1123        None
1124    } else {
1125        let mut modifiers = current_modifiers();
1126        // for characters that use 'shift' to type it is expected that the
1127        // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
1128        if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
1129            modifiers.shift = false;
1130        }
1131        let key = match first_char {
1132            ' ' => "space".to_string(),
1133            first_char => first_char.to_lowercase().to_string(),
1134        };
1135        Some(Keystroke {
1136            modifiers,
1137            key,
1138            ime_key: Some(first_char.to_string()),
1139        })
1140    }
1141}
1142
1143fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
1144    unsafe {
1145        let ctx = ImmGetContext(handle);
1146        let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1147        let result = if string_len >= 0 {
1148            let mut buffer = vec![0u8; string_len as usize + 2];
1149            ImmGetCompositionStringW(
1150                ctx,
1151                GCS_COMPSTR,
1152                Some(buffer.as_mut_ptr() as _),
1153                string_len as _,
1154            );
1155            let wstring = std::slice::from_raw_parts::<u16>(
1156                buffer.as_mut_ptr().cast::<u16>(),
1157                string_len as usize / 2,
1158            );
1159            let string = String::from_utf16_lossy(wstring);
1160            Some((string, string_len as usize / 2))
1161        } else {
1162            None
1163        };
1164        ImmReleaseContext(handle, ctx).ok().log_err();
1165        result
1166    }
1167}
1168
1169fn retrieve_composition_cursor_position(handle: HWND) -> usize {
1170    unsafe {
1171        let ctx = ImmGetContext(handle);
1172        let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
1173        ImmReleaseContext(handle, ctx).ok().log_err();
1174        ret as usize
1175    }
1176}
1177
1178fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
1179    unsafe {
1180        let ctx = ImmGetContext(handle);
1181        let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1182        let result = if string_len >= 0 {
1183            let mut buffer = vec![0u8; string_len as usize + 2];
1184            ImmGetCompositionStringW(
1185                ctx,
1186                GCS_RESULTSTR,
1187                Some(buffer.as_mut_ptr() as _),
1188                string_len as _,
1189            );
1190            let wstring = std::slice::from_raw_parts::<u16>(
1191                buffer.as_mut_ptr().cast::<u16>(),
1192                string_len as usize / 2,
1193            );
1194            let string = String::from_utf16_lossy(wstring);
1195            Some(string)
1196        } else {
1197            None
1198        };
1199        ImmReleaseContext(handle, ctx).ok().log_err();
1200        result
1201    }
1202}
1203
1204fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1205    match code {
1206        // VK_0 - VK_9
1207        48..=57 => Some(Keystroke {
1208            modifiers,
1209            key: format!("{}", code - VK_0.0),
1210            ime_key: None,
1211        }),
1212        // VK_A - VK_Z
1213        65..=90 => Some(Keystroke {
1214            modifiers,
1215            key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1216            ime_key: None,
1217        }),
1218        // VK_F1 - VK_F24
1219        112..=135 => Some(Keystroke {
1220            modifiers,
1221            key: format!("f{}", code - VK_F1.0 + 1),
1222            ime_key: None,
1223        }),
1224        // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1225        _ => {
1226            if let Some(key) = oemkey_vkcode_to_string(code) {
1227                Some(Keystroke {
1228                    modifiers,
1229                    key,
1230                    ime_key: None,
1231                })
1232            } else {
1233                None
1234            }
1235        }
1236    }
1237}
1238
1239fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1240    match code {
1241        186 => Some(";".to_string()), // VK_OEM_1
1242        187 => Some("=".to_string()), // VK_OEM_PLUS
1243        188 => Some(",".to_string()), // VK_OEM_COMMA
1244        189 => Some("-".to_string()), // VK_OEM_MINUS
1245        190 => Some(".".to_string()), // VK_OEM_PERIOD
1246        // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1247        191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1248        192 => Some("`".to_string()),       // VK_OEM_3
1249        219 => Some("[".to_string()),       // VK_OEM_4
1250        220 => Some("\\".to_string()),      // VK_OEM_5
1251        221 => Some("]".to_string()),       // VK_OEM_6
1252        222 => Some("'".to_string()),       // VK_OEM_7
1253        _ => None,
1254    }
1255}
1256
1257#[inline]
1258fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1259    unsafe { GetKeyState(vkey.0 as i32) < 0 }
1260}
1261
1262#[inline]
1263fn current_modifiers() -> Modifiers {
1264    Modifiers {
1265        control: is_virtual_key_pressed(VK_CONTROL),
1266        alt: is_virtual_key_pressed(VK_MENU),
1267        shift: is_virtual_key_pressed(VK_SHIFT),
1268        platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1269        function: false,
1270    }
1271}