events.rs

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