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