events.rs

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