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 Some(caret_range) = input_handler.selected_text_range() else {
 589            state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 590            return Some(0);
 591        };
 592        let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
 593        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 594        let config = CANDIDATEFORM {
 595            dwStyle: CFS_CANDIDATEPOS,
 596            // logical to physical
 597            ptCurrentPos: POINT {
 598                x: (caret_position.origin.x.0 * scale_factor) as i32,
 599                y: (caret_position.origin.y.0 * scale_factor) as i32
 600                    + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
 601            },
 602            ..Default::default()
 603        };
 604        ImmSetCandidateWindow(ctx, &config as _);
 605        ImmReleaseContext(handle, ctx);
 606        Some(0)
 607    }
 608}
 609
 610fn handle_ime_composition(
 611    handle: HWND,
 612    lparam: LPARAM,
 613    state_ptr: Rc<WindowsWindowStatePtr>,
 614) -> Option<isize> {
 615    let mut ime_input = None;
 616    if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
 617        let Some((string, string_len)) = parse_ime_compostion_string(handle) else {
 618            return None;
 619        };
 620        let mut lock = state_ptr.state.borrow_mut();
 621        let Some(mut input_handler) = lock.input_handler.take() else {
 622            return None;
 623        };
 624        drop(lock);
 625        input_handler.replace_and_mark_text_in_range(None, string.as_str(), Some(0..string_len));
 626        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 627        ime_input = Some(string);
 628    }
 629    if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
 630        let Some(ref comp_string) = ime_input else {
 631            return None;
 632        };
 633        let caret_pos = retrieve_composition_cursor_position(handle);
 634        let mut lock = state_ptr.state.borrow_mut();
 635        let Some(mut input_handler) = lock.input_handler.take() else {
 636            return None;
 637        };
 638        drop(lock);
 639        input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
 640        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 641    }
 642    if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
 643        let Some(comp_result) = parse_ime_compostion_result(handle) else {
 644            return None;
 645        };
 646        let mut lock = state_ptr.state.borrow_mut();
 647        let Some(mut input_handler) = lock.input_handler.take() else {
 648            return Some(1);
 649        };
 650        drop(lock);
 651        input_handler.replace_text_in_range(None, &comp_result);
 652        state_ptr.state.borrow_mut().input_handler = Some(input_handler);
 653        invalidate_client_area(handle);
 654        return Some(0);
 655    }
 656    // currently, we don't care other stuff
 657    None
 658}
 659
 660/// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
 661fn handle_calc_client_size(
 662    handle: HWND,
 663    wparam: WPARAM,
 664    lparam: LPARAM,
 665    state_ptr: Rc<WindowsWindowStatePtr>,
 666) -> Option<isize> {
 667    if !state_ptr.hide_title_bar || state_ptr.state.borrow().is_fullscreen() {
 668        return None;
 669    }
 670
 671    if wparam.0 == 0 {
 672        return None;
 673    }
 674
 675    let dpi = unsafe { GetDpiForWindow(handle) };
 676
 677    let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
 678    let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 679    let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
 680
 681    // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
 682    let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
 683    let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
 684
 685    requested_client_rect[0].right -= frame_x + padding;
 686    requested_client_rect[0].left += frame_x + padding;
 687    requested_client_rect[0].bottom -= frame_y + padding;
 688
 689    Some(0)
 690}
 691
 692fn handle_activate_msg(
 693    handle: HWND,
 694    wparam: WPARAM,
 695    state_ptr: Rc<WindowsWindowStatePtr>,
 696) -> Option<isize> {
 697    let activated = wparam.loword() > 0;
 698    if state_ptr.hide_title_bar {
 699        if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
 700            unsafe { InvalidateRect(handle, Some(&titlebar_rect), FALSE) };
 701        }
 702    }
 703    let this = state_ptr.clone();
 704    state_ptr
 705        .executor
 706        .spawn(async move {
 707            let mut lock = this.state.borrow_mut();
 708            if let Some(mut cb) = lock.callbacks.active_status_change.take() {
 709                drop(lock);
 710                cb(activated);
 711                this.state.borrow_mut().callbacks.active_status_change = Some(cb);
 712            }
 713        })
 714        .detach();
 715
 716    None
 717}
 718
 719fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 720    let mut size_rect = RECT::default();
 721    unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
 722
 723    let width = size_rect.right - size_rect.left;
 724    let height = size_rect.bottom - size_rect.top;
 725
 726    if state_ptr.hide_title_bar {
 727        unsafe {
 728            SetWindowPos(
 729                handle,
 730                None,
 731                size_rect.left,
 732                size_rect.top,
 733                width,
 734                height,
 735                SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
 736            )
 737            .log_err()
 738        };
 739    }
 740
 741    Some(0)
 742}
 743
 744fn handle_dpi_changed_msg(
 745    handle: HWND,
 746    wparam: WPARAM,
 747    lparam: LPARAM,
 748    state_ptr: Rc<WindowsWindowStatePtr>,
 749) -> Option<isize> {
 750    let new_dpi = wparam.loword() as f32;
 751    state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
 752
 753    let rect = unsafe { &*(lparam.0 as *const RECT) };
 754    let width = rect.right - rect.left;
 755    let height = rect.bottom - rect.top;
 756    // this will emit `WM_SIZE` and `WM_MOVE` right here
 757    // even before this function returns
 758    // the new size is handled in `WM_SIZE`
 759    unsafe {
 760        SetWindowPos(
 761            handle,
 762            None,
 763            rect.left,
 764            rect.top,
 765            width,
 766            height,
 767            SWP_NOZORDER | SWP_NOACTIVATE,
 768        )
 769        .context("unable to set window position after dpi has changed")
 770        .log_err();
 771    }
 772    invalidate_client_area(handle);
 773
 774    Some(0)
 775}
 776
 777fn handle_hit_test_msg(
 778    handle: HWND,
 779    msg: u32,
 780    wparam: WPARAM,
 781    lparam: LPARAM,
 782    state_ptr: Rc<WindowsWindowStatePtr>,
 783) -> Option<isize> {
 784    if !state_ptr.hide_title_bar {
 785        return None;
 786    }
 787
 788    // default handler for resize areas
 789    let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
 790    if matches!(
 791        hit.0 as u32,
 792        HTNOWHERE
 793            | HTRIGHT
 794            | HTLEFT
 795            | HTTOPLEFT
 796            | HTTOP
 797            | HTTOPRIGHT
 798            | HTBOTTOMRIGHT
 799            | HTBOTTOM
 800            | HTBOTTOMLEFT
 801    ) {
 802        return Some(hit.0);
 803    }
 804
 805    if state_ptr.state.borrow().is_fullscreen() {
 806        return Some(HTCLIENT as _);
 807    }
 808
 809    let dpi = unsafe { GetDpiForWindow(handle) };
 810    let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 811    let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
 812
 813    let mut cursor_point = POINT {
 814        x: lparam.signed_loword().into(),
 815        y: lparam.signed_hiword().into(),
 816    };
 817    unsafe { ScreenToClient(handle, &mut cursor_point) };
 818    if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
 819        return Some(HTTOP as _);
 820    }
 821
 822    let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
 823    if let Ok(titlebar_rect) = titlebar_rect {
 824        if cursor_point.y < titlebar_rect.bottom {
 825            let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
 826                * state_ptr.state.borrow().scale_factor) as i32;
 827            if cursor_point.x >= titlebar_rect.right - caption_btn_width {
 828                return Some(HTCLOSE as _);
 829            } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
 830                return Some(HTMAXBUTTON as _);
 831            } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
 832                return Some(HTMINBUTTON as _);
 833            }
 834
 835            return Some(HTCAPTION as _);
 836        }
 837    }
 838
 839    Some(HTCLIENT as _)
 840}
 841
 842fn handle_nc_mouse_move_msg(
 843    handle: HWND,
 844    lparam: LPARAM,
 845    state_ptr: Rc<WindowsWindowStatePtr>,
 846) -> Option<isize> {
 847    if !state_ptr.hide_title_bar {
 848        return None;
 849    }
 850
 851    let mut lock = state_ptr.state.borrow_mut();
 852    if let Some(mut callback) = lock.callbacks.input.take() {
 853        let scale_factor = lock.scale_factor;
 854        drop(lock);
 855        let mut cursor_point = POINT {
 856            x: lparam.signed_loword().into(),
 857            y: lparam.signed_hiword().into(),
 858        };
 859        unsafe { ScreenToClient(handle, &mut cursor_point) };
 860        let event = MouseMoveEvent {
 861            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 862            pressed_button: None,
 863            modifiers: current_modifiers(),
 864        };
 865        let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
 866            Some(0)
 867        } else {
 868            Some(1)
 869        };
 870        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 871
 872        result
 873    } else {
 874        None
 875    }
 876}
 877
 878fn handle_nc_mouse_down_msg(
 879    handle: HWND,
 880    button: MouseButton,
 881    wparam: WPARAM,
 882    lparam: LPARAM,
 883    state_ptr: Rc<WindowsWindowStatePtr>,
 884) -> Option<isize> {
 885    if !state_ptr.hide_title_bar {
 886        return None;
 887    }
 888
 889    let mut lock = state_ptr.state.borrow_mut();
 890    let result = if let Some(mut callback) = lock.callbacks.input.take() {
 891        let scale_factor = lock.scale_factor;
 892        let mut cursor_point = POINT {
 893            x: lparam.signed_loword().into(),
 894            y: lparam.signed_hiword().into(),
 895        };
 896        unsafe { ScreenToClient(handle, &mut cursor_point) };
 897        let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
 898        let click_count = lock.click_state.update(button, physical_point);
 899        drop(lock);
 900        let event = MouseDownEvent {
 901            button,
 902            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 903            modifiers: current_modifiers(),
 904            click_count,
 905            first_mouse: false,
 906        };
 907        let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
 908            Some(0)
 909        } else {
 910            None
 911        };
 912        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 913
 914        result
 915    } else {
 916        None
 917    };
 918
 919    // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
 920    result.or_else(|| matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0))
 921}
 922
 923fn handle_nc_mouse_up_msg(
 924    handle: HWND,
 925    button: MouseButton,
 926    wparam: WPARAM,
 927    lparam: LPARAM,
 928    state_ptr: Rc<WindowsWindowStatePtr>,
 929) -> Option<isize> {
 930    if !state_ptr.hide_title_bar {
 931        return None;
 932    }
 933
 934    let mut lock = state_ptr.state.borrow_mut();
 935    if let Some(mut callback) = lock.callbacks.input.take() {
 936        let scale_factor = lock.scale_factor;
 937        drop(lock);
 938        let mut cursor_point = POINT {
 939            x: lparam.signed_loword().into(),
 940            y: lparam.signed_hiword().into(),
 941        };
 942        unsafe { ScreenToClient(handle, &mut cursor_point) };
 943        let event = MouseUpEvent {
 944            button,
 945            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 946            modifiers: current_modifiers(),
 947            click_count: 1,
 948        };
 949        let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
 950            Some(0)
 951        } else {
 952            None
 953        };
 954        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 955        if result.is_some() {
 956            return result;
 957        }
 958    } else {
 959        drop(lock);
 960    }
 961
 962    if button == MouseButton::Left {
 963        match wparam.0 as u32 {
 964            HTMINBUTTON => unsafe {
 965                ShowWindowAsync(handle, SW_MINIMIZE);
 966            },
 967            HTMAXBUTTON => unsafe {
 968                if state_ptr.state.borrow().is_maximized() {
 969                    ShowWindowAsync(handle, SW_NORMAL);
 970                } else {
 971                    ShowWindowAsync(handle, SW_MAXIMIZE);
 972                }
 973            },
 974            HTCLOSE => unsafe {
 975                PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default()).log_err();
 976            },
 977            _ => return None,
 978        };
 979        return Some(0);
 980    }
 981
 982    None
 983}
 984
 985fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 986    state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
 987    Some(0)
 988}
 989
 990fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 991    if matches!(
 992        lparam.loword() as u32,
 993        HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
 994    ) {
 995        return None;
 996    }
 997    unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
 998    Some(1)
 999}
1000
1001fn handle_mouse_wheel_settings_msg(
1002    wparam: WPARAM,
1003    lparam: LPARAM,
1004    state_ptr: Rc<WindowsWindowStatePtr>,
1005) -> Option<isize> {
1006    match lparam.0 {
1007        1 => {
1008            state_ptr
1009                .state
1010                .borrow_mut()
1011                .mouse_wheel_settings
1012                .wheel_scroll_chars = wparam.0 as u32
1013        }
1014        2 => {
1015            state_ptr
1016                .state
1017                .borrow_mut()
1018                .mouse_wheel_settings
1019                .wheel_scroll_lines = wparam.0 as u32
1020        }
1021        _ => unreachable!(),
1022    }
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
1143/// mark window client rect to be re-drawn
1144/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
1145pub(crate) fn invalidate_client_area(handle: HWND) {
1146    unsafe { InvalidateRect(handle, None, FALSE) };
1147}
1148
1149fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
1150    unsafe {
1151        let ctx = ImmGetContext(handle);
1152        let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1153        let result = if string_len >= 0 {
1154            let mut buffer = vec![0u8; string_len as usize + 2];
1155            ImmGetCompositionStringW(
1156                ctx,
1157                GCS_COMPSTR,
1158                Some(buffer.as_mut_ptr() as _),
1159                string_len as _,
1160            );
1161            let wstring = std::slice::from_raw_parts::<u16>(
1162                buffer.as_mut_ptr().cast::<u16>(),
1163                string_len as usize / 2,
1164            );
1165            let string = String::from_utf16_lossy(wstring);
1166            Some((string, string_len as usize / 2))
1167        } else {
1168            None
1169        };
1170        ImmReleaseContext(handle, ctx);
1171        result
1172    }
1173}
1174
1175fn retrieve_composition_cursor_position(handle: HWND) -> usize {
1176    unsafe {
1177        let ctx = ImmGetContext(handle);
1178        let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
1179        ImmReleaseContext(handle, ctx);
1180        ret as usize
1181    }
1182}
1183
1184fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
1185    unsafe {
1186        let ctx = ImmGetContext(handle);
1187        let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1188        let result = if string_len >= 0 {
1189            let mut buffer = vec![0u8; string_len as usize + 2];
1190            ImmGetCompositionStringW(
1191                ctx,
1192                GCS_RESULTSTR,
1193                Some(buffer.as_mut_ptr() as _),
1194                string_len as _,
1195            );
1196            let wstring = std::slice::from_raw_parts::<u16>(
1197                buffer.as_mut_ptr().cast::<u16>(),
1198                string_len as usize / 2,
1199            );
1200            let string = String::from_utf16_lossy(wstring);
1201            Some(string)
1202        } else {
1203            None
1204        };
1205        ImmReleaseContext(handle, ctx);
1206        result
1207    }
1208}
1209
1210fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1211    match code {
1212        // VK_0 - VK_9
1213        48..=57 => Some(Keystroke {
1214            modifiers,
1215            key: format!("{}", code - VK_0.0),
1216            ime_key: None,
1217        }),
1218        // VK_A - VK_Z
1219        65..=90 => Some(Keystroke {
1220            modifiers,
1221            key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1222            ime_key: None,
1223        }),
1224        // VK_F1 - VK_F24
1225        112..=135 => Some(Keystroke {
1226            modifiers,
1227            key: format!("f{}", code - VK_F1.0 + 1),
1228            ime_key: None,
1229        }),
1230        // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1231        _ => {
1232            if let Some(key) = oemkey_vkcode_to_string(code) {
1233                Some(Keystroke {
1234                    modifiers,
1235                    key,
1236                    ime_key: None,
1237                })
1238            } else {
1239                None
1240            }
1241        }
1242    }
1243}
1244
1245fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1246    match code {
1247        186 => Some(";".to_string()), // VK_OEM_1
1248        187 => Some("=".to_string()), // VK_OEM_PLUS
1249        188 => Some(",".to_string()), // VK_OEM_COMMA
1250        189 => Some("-".to_string()), // VK_OEM_MINUS
1251        190 => Some(".".to_string()), // VK_OEM_PERIOD
1252        // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1253        191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1254        192 => Some("`".to_string()),       // VK_OEM_3
1255        219 => Some("[".to_string()),       // VK_OEM_4
1256        220 => Some("\\".to_string()),      // VK_OEM_5
1257        221 => Some("]".to_string()),       // VK_OEM_6
1258        222 => Some("'".to_string()),       // VK_OEM_7
1259        _ => None,
1260    }
1261}
1262
1263#[inline]
1264fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1265    unsafe { GetKeyState(vkey.0 as i32) < 0 }
1266}
1267
1268#[inline]
1269fn current_modifiers() -> Modifiers {
1270    Modifiers {
1271        control: is_virtual_key_pressed(VK_CONTROL),
1272        alt: is_virtual_key_pressed(VK_MENU),
1273        shift: is_virtual_key_pressed(VK_SHIFT),
1274        platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1275        function: false,
1276    }
1277}