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