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    if state_ptr.state.borrow().is_maximized() {
 663        requested_client_rect[0].top += frame_y + padding;
 664    } else {
 665        // Magic number that calculates the width of the border
 666        requested_client_rect[0].top += frame_y - 3;
 667    }
 668
 669    Some(0)
 670}
 671
 672fn handle_activate_msg(
 673    handle: HWND,
 674    wparam: WPARAM,
 675    state_ptr: Rc<WindowsWindowStatePtr>,
 676) -> Option<isize> {
 677    let activated = wparam.loword() > 0;
 678    if state_ptr.hide_title_bar {
 679        if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
 680            unsafe {
 681                InvalidateRect(handle, Some(&titlebar_rect), FALSE)
 682                    .ok()
 683                    .log_err()
 684            };
 685        }
 686    }
 687    let this = state_ptr.clone();
 688    state_ptr
 689        .executor
 690        .spawn(async move {
 691            let mut lock = this.state.borrow_mut();
 692            if let Some(mut cb) = lock.callbacks.active_status_change.take() {
 693                drop(lock);
 694                cb(activated);
 695                this.state.borrow_mut().callbacks.active_status_change = Some(cb);
 696            }
 697        })
 698        .detach();
 699
 700    None
 701}
 702
 703fn handle_create_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 704    let mut size_rect = RECT::default();
 705    unsafe { GetWindowRect(handle, &mut size_rect).log_err() };
 706
 707    let width = size_rect.right - size_rect.left;
 708    let height = size_rect.bottom - size_rect.top;
 709
 710    if state_ptr.hide_title_bar {
 711        unsafe {
 712            SetWindowPos(
 713                handle,
 714                None,
 715                size_rect.left,
 716                size_rect.top,
 717                width,
 718                height,
 719                SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
 720            )
 721            .log_err()
 722        };
 723    }
 724
 725    Some(0)
 726}
 727
 728fn handle_dpi_changed_msg(
 729    handle: HWND,
 730    wparam: WPARAM,
 731    lparam: LPARAM,
 732    state_ptr: Rc<WindowsWindowStatePtr>,
 733) -> Option<isize> {
 734    let new_dpi = wparam.loword() as f32;
 735    state_ptr.state.borrow_mut().scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
 736
 737    let rect = unsafe { &*(lparam.0 as *const RECT) };
 738    let width = rect.right - rect.left;
 739    let height = rect.bottom - rect.top;
 740    // this will emit `WM_SIZE` and `WM_MOVE` right here
 741    // even before this function returns
 742    // the new size is handled in `WM_SIZE`
 743    unsafe {
 744        SetWindowPos(
 745            handle,
 746            None,
 747            rect.left,
 748            rect.top,
 749            width,
 750            height,
 751            SWP_NOZORDER | SWP_NOACTIVATE,
 752        )
 753        .context("unable to set window position after dpi has changed")
 754        .log_err();
 755    }
 756
 757    Some(0)
 758}
 759
 760/// The following conditions will trigger this event:
 761/// 1. The monitor on which the window is located goes offline or changes resolution.
 762/// 2. Another monitor goes offline, is plugged in, or changes resolution.
 763///
 764/// In either case, the window will only receive information from the monitor on which
 765/// it is located.
 766///
 767/// For example, in the case of condition 2, where the monitor on which the window is
 768/// located has actually changed nothing, it will still receive this event.
 769fn handle_display_change_msg(handle: HWND, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
 770    // NOTE:
 771    // Even the `lParam` holds the resolution of the screen, we just ignore it.
 772    // Because WM_DPICHANGED, WM_MOVE, WM_SIEZ will come first, window reposition and resize
 773    // are handled there.
 774    // So we only care about if monitor is disconnected.
 775    let previous_monitor = state_ptr.as_ref().state.borrow().display;
 776    if WindowsDisplay::is_connected(previous_monitor.handle) {
 777        // we are fine, other display changed
 778        return None;
 779    }
 780    // display disconnected
 781    // in this case, the OS will move our window to another monitor, and minimize it.
 782    // we deminimize the window and query the monitor after moving
 783    unsafe {
 784        let _ = ShowWindow(handle, SW_SHOWNORMAL);
 785    };
 786    let new_monitor = unsafe { MonitorFromWindow(handle, MONITOR_DEFAULTTONULL) };
 787    // all monitors disconnected
 788    if new_monitor.is_invalid() {
 789        log::error!("No monitor detected!");
 790        return None;
 791    }
 792    let new_display = WindowsDisplay::new_with_handle(new_monitor);
 793    state_ptr.as_ref().state.borrow_mut().display = new_display;
 794    Some(0)
 795}
 796
 797fn handle_hit_test_msg(
 798    handle: HWND,
 799    msg: u32,
 800    wparam: WPARAM,
 801    lparam: LPARAM,
 802    state_ptr: Rc<WindowsWindowStatePtr>,
 803) -> Option<isize> {
 804    if !state_ptr.hide_title_bar {
 805        return None;
 806    }
 807
 808    // default handler for resize areas
 809    let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
 810    if matches!(
 811        hit.0 as u32,
 812        HTNOWHERE
 813            | HTRIGHT
 814            | HTLEFT
 815            | HTTOPLEFT
 816            | HTTOP
 817            | HTTOPRIGHT
 818            | HTBOTTOMRIGHT
 819            | HTBOTTOM
 820            | HTBOTTOMLEFT
 821    ) {
 822        return Some(hit.0);
 823    }
 824
 825    if state_ptr.state.borrow().is_fullscreen() {
 826        return Some(HTCLIENT as _);
 827    }
 828
 829    let dpi = unsafe { GetDpiForWindow(handle) };
 830    let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 831
 832    let mut cursor_point = POINT {
 833        x: lparam.signed_loword().into(),
 834        y: lparam.signed_hiword().into(),
 835    };
 836    unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 837    if !state_ptr.state.borrow().is_maximized() && cursor_point.y >= 0 && cursor_point.y <= frame_y
 838    {
 839        return Some(HTTOP as _);
 840    }
 841
 842    let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
 843    if let Ok(titlebar_rect) = titlebar_rect {
 844        if cursor_point.y < titlebar_rect.bottom {
 845            let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
 846                * state_ptr.state.borrow().scale_factor) as i32;
 847            if cursor_point.x >= titlebar_rect.right - caption_btn_width {
 848                return Some(HTCLOSE as _);
 849            } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
 850                return Some(HTMAXBUTTON as _);
 851            } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
 852                return Some(HTMINBUTTON as _);
 853            }
 854
 855            return Some(HTCAPTION as _);
 856        }
 857    }
 858
 859    Some(HTCLIENT as _)
 860}
 861
 862fn handle_nc_mouse_move_msg(
 863    handle: HWND,
 864    lparam: LPARAM,
 865    state_ptr: Rc<WindowsWindowStatePtr>,
 866) -> Option<isize> {
 867    if !state_ptr.hide_title_bar {
 868        return None;
 869    }
 870
 871    let mut lock = state_ptr.state.borrow_mut();
 872    if let Some(mut callback) = lock.callbacks.input.take() {
 873        let scale_factor = lock.scale_factor;
 874        drop(lock);
 875        let mut cursor_point = POINT {
 876            x: lparam.signed_loword().into(),
 877            y: lparam.signed_hiword().into(),
 878        };
 879        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 880        let event = MouseMoveEvent {
 881            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 882            pressed_button: None,
 883            modifiers: current_modifiers(),
 884        };
 885        let result = if callback(PlatformInput::MouseMove(event)).default_prevented {
 886            Some(0)
 887        } else {
 888            Some(1)
 889        };
 890        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 891
 892        result
 893    } else {
 894        None
 895    }
 896}
 897
 898fn handle_nc_mouse_down_msg(
 899    handle: HWND,
 900    button: MouseButton,
 901    wparam: WPARAM,
 902    lparam: LPARAM,
 903    state_ptr: Rc<WindowsWindowStatePtr>,
 904) -> Option<isize> {
 905    if !state_ptr.hide_title_bar {
 906        return None;
 907    }
 908
 909    let mut lock = state_ptr.state.borrow_mut();
 910    if let Some(mut callback) = lock.callbacks.input.take() {
 911        let scale_factor = lock.scale_factor;
 912        let mut cursor_point = POINT {
 913            x: lparam.signed_loword().into(),
 914            y: lparam.signed_hiword().into(),
 915        };
 916        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 917        let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
 918        let click_count = lock.click_state.update(button, physical_point);
 919        drop(lock);
 920        let event = MouseDownEvent {
 921            button,
 922            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 923            modifiers: current_modifiers(),
 924            click_count,
 925            first_mouse: false,
 926        };
 927        let result = if callback(PlatformInput::MouseDown(event)).default_prevented {
 928            Some(0)
 929        } else {
 930            None
 931        };
 932        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 933
 934        if result.is_some() {
 935            return result;
 936        }
 937    } else {
 938        drop(lock);
 939    };
 940
 941    // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
 942    if button == MouseButton::Left {
 943        match wparam.0 as u32 {
 944            HTMINBUTTON => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTMINBUTTON),
 945            HTMAXBUTTON => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTMAXBUTTON),
 946            HTCLOSE => state_ptr.state.borrow_mut().nc_button_pressed = Some(HTCLOSE),
 947            _ => return None,
 948        };
 949        Some(0)
 950    } else {
 951        None
 952    }
 953}
 954
 955fn handle_nc_mouse_up_msg(
 956    handle: HWND,
 957    button: MouseButton,
 958    wparam: WPARAM,
 959    lparam: LPARAM,
 960    state_ptr: Rc<WindowsWindowStatePtr>,
 961) -> Option<isize> {
 962    if !state_ptr.hide_title_bar {
 963        return None;
 964    }
 965
 966    let mut lock = state_ptr.state.borrow_mut();
 967    if let Some(mut callback) = lock.callbacks.input.take() {
 968        let scale_factor = lock.scale_factor;
 969        drop(lock);
 970        let mut cursor_point = POINT {
 971            x: lparam.signed_loword().into(),
 972            y: lparam.signed_hiword().into(),
 973        };
 974        unsafe { ScreenToClient(handle, &mut cursor_point).ok().log_err() };
 975        let event = MouseUpEvent {
 976            button,
 977            position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 978            modifiers: current_modifiers(),
 979            click_count: 1,
 980        };
 981        let result = if callback(PlatformInput::MouseUp(event)).default_prevented {
 982            Some(0)
 983        } else {
 984            None
 985        };
 986        state_ptr.state.borrow_mut().callbacks.input = Some(callback);
 987        if result.is_some() {
 988            return result;
 989        }
 990    } else {
 991        drop(lock);
 992    }
 993
 994    let last_pressed = state_ptr.state.borrow_mut().nc_button_pressed.take();
 995    if button == MouseButton::Left && last_pressed.is_some() {
 996        let last_button = last_pressed.unwrap();
 997        let mut handled = false;
 998        match wparam.0 as u32 {
 999            HTMINBUTTON => {
1000                if last_button == HTMINBUTTON {
1001                    unsafe { ShowWindowAsync(handle, SW_MINIMIZE).ok().log_err() };
1002                    handled = true;
1003                }
1004            }
1005            HTMAXBUTTON => {
1006                if last_button == HTMAXBUTTON {
1007                    if state_ptr.state.borrow().is_maximized() {
1008                        unsafe { ShowWindowAsync(handle, SW_NORMAL).ok().log_err() };
1009                    } else {
1010                        unsafe { ShowWindowAsync(handle, SW_MAXIMIZE).ok().log_err() };
1011                    }
1012                    handled = true;
1013                }
1014            }
1015            HTCLOSE => {
1016                if last_button == HTCLOSE {
1017                    unsafe {
1018                        PostMessageW(handle, WM_CLOSE, WPARAM::default(), LPARAM::default())
1019                            .log_err()
1020                    };
1021                    handled = true;
1022                }
1023            }
1024            _ => {}
1025        };
1026        if handled {
1027            return Some(0);
1028        }
1029    }
1030
1031    None
1032}
1033
1034fn handle_cursor_changed(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1035    state_ptr.state.borrow_mut().current_cursor = HCURSOR(lparam.0);
1036    Some(0)
1037}
1038
1039fn handle_set_cursor(lparam: LPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1040    if matches!(
1041        lparam.loword() as u32,
1042        HTLEFT | HTRIGHT | HTTOP | HTTOPLEFT | HTTOPRIGHT | HTBOTTOM | HTBOTTOMLEFT | HTBOTTOMRIGHT
1043    ) {
1044        return None;
1045    }
1046    unsafe { SetCursor(state_ptr.state.borrow().current_cursor) };
1047    Some(1)
1048}
1049
1050fn handle_system_settings_changed(state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
1051    let mut lock = state_ptr.state.borrow_mut();
1052    // mouse wheel
1053    lock.system_settings.mouse_wheel_settings.update();
1054    // mouse double click
1055    lock.click_state.system_update();
1056    Some(0)
1057}
1058
1059fn parse_syskeydown_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1060    let modifiers = current_modifiers();
1061    if !modifiers.alt {
1062        // on Windows, F10 can trigger this event, not just the alt key
1063        // and we just don't care about F10
1064        return None;
1065    }
1066
1067    let vk_code = wparam.loword();
1068
1069    let key = match VIRTUAL_KEY(vk_code) {
1070        VK_BACK => "backspace",
1071        VK_RETURN => "enter",
1072        VK_TAB => "tab",
1073        VK_UP => "up",
1074        VK_DOWN => "down",
1075        VK_RIGHT => "right",
1076        VK_LEFT => "left",
1077        VK_HOME => "home",
1078        VK_END => "end",
1079        VK_PRIOR => "pageup",
1080        VK_NEXT => "pagedown",
1081        VK_ESCAPE => "escape",
1082        VK_INSERT => "insert",
1083        VK_DELETE => "delete",
1084        _ => return basic_vkcode_to_string(vk_code, modifiers),
1085    }
1086    .to_owned();
1087
1088    Some(Keystroke {
1089        modifiers,
1090        key,
1091        ime_key: None,
1092    })
1093}
1094
1095enum KeystrokeOrModifier {
1096    Keystroke(Keystroke),
1097    Modifier(Modifiers),
1098}
1099
1100fn parse_keydown_msg_keystroke(wparam: WPARAM) -> Option<KeystrokeOrModifier> {
1101    let vk_code = wparam.loword();
1102
1103    let modifiers = current_modifiers();
1104
1105    let key = match VIRTUAL_KEY(vk_code) {
1106        VK_BACK => "backspace",
1107        VK_RETURN => "enter",
1108        VK_TAB => "tab",
1109        VK_UP => "up",
1110        VK_DOWN => "down",
1111        VK_RIGHT => "right",
1112        VK_LEFT => "left",
1113        VK_HOME => "home",
1114        VK_END => "end",
1115        VK_PRIOR => "pageup",
1116        VK_NEXT => "pagedown",
1117        VK_ESCAPE => "escape",
1118        VK_INSERT => "insert",
1119        VK_DELETE => "delete",
1120        _ => {
1121            if is_modifier(VIRTUAL_KEY(vk_code)) {
1122                return Some(KeystrokeOrModifier::Modifier(modifiers));
1123            }
1124
1125            if modifiers.control || modifiers.alt {
1126                let basic_key = basic_vkcode_to_string(vk_code, modifiers);
1127                if let Some(basic_key) = basic_key {
1128                    return Some(KeystrokeOrModifier::Keystroke(basic_key));
1129                }
1130            }
1131
1132            if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
1133                let offset = vk_code - VK_F1.0;
1134                return Some(KeystrokeOrModifier::Keystroke(Keystroke {
1135                    modifiers,
1136                    key: format!("f{}", offset + 1),
1137                    ime_key: None,
1138                }));
1139            };
1140            return None;
1141        }
1142    }
1143    .to_owned();
1144
1145    Some(KeystrokeOrModifier::Keystroke(Keystroke {
1146        modifiers,
1147        key,
1148        ime_key: None,
1149    }))
1150}
1151
1152fn parse_char_msg_keystroke(wparam: WPARAM) -> Option<Keystroke> {
1153    let first_char = char::from_u32((wparam.0 as u16).into())?;
1154    if first_char.is_control() {
1155        None
1156    } else {
1157        let mut modifiers = current_modifiers();
1158        // for characters that use 'shift' to type it is expected that the
1159        // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
1160        if first_char.to_ascii_uppercase() == first_char.to_ascii_lowercase() {
1161            modifiers.shift = false;
1162        }
1163        let key = match first_char {
1164            ' ' => "space".to_string(),
1165            first_char => first_char.to_lowercase().to_string(),
1166        };
1167        Some(Keystroke {
1168            modifiers,
1169            key,
1170            ime_key: Some(first_char.to_string()),
1171        })
1172    }
1173}
1174
1175fn parse_ime_compostion_string(handle: HWND) -> Option<(String, usize)> {
1176    unsafe {
1177        let ctx = ImmGetContext(handle);
1178        let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
1179        let result = if string_len >= 0 {
1180            let mut buffer = vec![0u8; string_len as usize + 2];
1181            ImmGetCompositionStringW(
1182                ctx,
1183                GCS_COMPSTR,
1184                Some(buffer.as_mut_ptr() as _),
1185                string_len as _,
1186            );
1187            let wstring = std::slice::from_raw_parts::<u16>(
1188                buffer.as_mut_ptr().cast::<u16>(),
1189                string_len as usize / 2,
1190            );
1191            let string = String::from_utf16_lossy(wstring);
1192            Some((string, string_len as usize / 2))
1193        } else {
1194            None
1195        };
1196        ImmReleaseContext(handle, ctx).ok().log_err();
1197        result
1198    }
1199}
1200
1201fn retrieve_composition_cursor_position(handle: HWND) -> usize {
1202    unsafe {
1203        let ctx = ImmGetContext(handle);
1204        let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
1205        ImmReleaseContext(handle, ctx).ok().log_err();
1206        ret as usize
1207    }
1208}
1209
1210fn parse_ime_compostion_result(handle: HWND) -> Option<String> {
1211    unsafe {
1212        let ctx = ImmGetContext(handle);
1213        let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
1214        let result = if string_len >= 0 {
1215            let mut buffer = vec![0u8; string_len as usize + 2];
1216            ImmGetCompositionStringW(
1217                ctx,
1218                GCS_RESULTSTR,
1219                Some(buffer.as_mut_ptr() as _),
1220                string_len as _,
1221            );
1222            let wstring = std::slice::from_raw_parts::<u16>(
1223                buffer.as_mut_ptr().cast::<u16>(),
1224                string_len as usize / 2,
1225            );
1226            let string = String::from_utf16_lossy(wstring);
1227            Some(string)
1228        } else {
1229            None
1230        };
1231        ImmReleaseContext(handle, ctx).ok().log_err();
1232        result
1233    }
1234}
1235
1236fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1237    let mapped_code = unsafe { MapVirtualKeyW(code as u32, MAPVK_VK_TO_CHAR) };
1238
1239    let key = match mapped_code {
1240        0 => None,
1241        raw_code => char::from_u32(raw_code),
1242    }?
1243    .to_ascii_lowercase();
1244
1245    let key = if matches!(code as u32, 112..=135) {
1246        format!("f{key}")
1247    } else {
1248        key.to_string()
1249    };
1250
1251    Some(Keystroke {
1252        modifiers,
1253        key,
1254        ime_key: None,
1255    })
1256}
1257
1258#[inline]
1259fn is_virtual_key_pressed(vkey: VIRTUAL_KEY) -> bool {
1260    unsafe { GetKeyState(vkey.0 as i32) < 0 }
1261}
1262
1263fn is_modifier(virtual_key: VIRTUAL_KEY) -> bool {
1264    matches!(
1265        virtual_key,
1266        VK_CONTROL | VK_MENU | VK_SHIFT | VK_LWIN | VK_RWIN
1267    )
1268}
1269
1270#[inline]
1271pub(crate) fn current_modifiers() -> Modifiers {
1272    Modifiers {
1273        control: is_virtual_key_pressed(VK_CONTROL),
1274        alt: is_virtual_key_pressed(VK_MENU),
1275        shift: is_virtual_key_pressed(VK_SHIFT),
1276        platform: is_virtual_key_pressed(VK_LWIN) || is_virtual_key_pressed(VK_RWIN),
1277        function: false,
1278    }
1279}