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