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