window.rs

   1#![deny(unsafe_op_in_unsafe_fn)]
   2
   3use std::{
   4    cell::{Cell, RefCell},
   5    iter::once,
   6    num::NonZeroIsize,
   7    path::PathBuf,
   8    rc::{Rc, Weak},
   9    str::FromStr,
  10    sync::{Arc, Once},
  11    time::{Duration, Instant},
  12};
  13
  14use ::util::ResultExt;
  15use anyhow::Context;
  16use blade_graphics as gpu;
  17use futures::channel::oneshot::{self, Receiver};
  18use itertools::Itertools;
  19use raw_window_handle as rwh;
  20use smallvec::SmallVec;
  21use std::result::Result;
  22use windows::{
  23    core::*,
  24    Win32::{
  25        Foundation::*,
  26        Graphics::Gdi::*,
  27        System::{Com::*, LibraryLoader::*, Ole::*, SystemServices::*},
  28        UI::{
  29            Controls::*,
  30            HiDpi::*,
  31            Input::{Ime::*, KeyboardAndMouse::*},
  32            Shell::*,
  33            WindowsAndMessaging::*,
  34        },
  35    },
  36};
  37
  38use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
  39use crate::*;
  40
  41pub(crate) struct WindowsWindowInner {
  42    hwnd: HWND,
  43    origin: Cell<Point<DevicePixels>>,
  44    physical_size: Cell<Size<DevicePixels>>,
  45    scale_factor: Cell<f32>,
  46    input_handler: Cell<Option<PlatformInputHandler>>,
  47    renderer: RefCell<BladeRenderer>,
  48    callbacks: RefCell<Callbacks>,
  49    platform_inner: Rc<WindowsPlatformInner>,
  50    pub(crate) handle: AnyWindowHandle,
  51    hide_title_bar: bool,
  52    display: RefCell<Rc<WindowsDisplay>>,
  53    click_state: RefCell<ClickState>,
  54    fullscreen: Cell<Option<StyleAndBounds>>,
  55}
  56
  57impl WindowsWindowInner {
  58    fn new(
  59        hwnd: HWND,
  60        cs: &CREATESTRUCTW,
  61        platform_inner: Rc<WindowsPlatformInner>,
  62        handle: AnyWindowHandle,
  63        hide_title_bar: bool,
  64        display: Rc<WindowsDisplay>,
  65        transparent: bool,
  66    ) -> Self {
  67        let monitor_dpi = unsafe { GetDpiForWindow(hwnd) } as f32;
  68        let origin = Cell::new(Point {
  69            x: DevicePixels(cs.x),
  70            y: DevicePixels(cs.y),
  71        });
  72        let physical_size = Cell::new(Size {
  73            width: DevicePixels(cs.cx),
  74            height: DevicePixels(cs.cy),
  75        });
  76        let scale_factor = Cell::new(monitor_dpi / USER_DEFAULT_SCREEN_DPI as f32);
  77        let input_handler = Cell::new(None);
  78        struct RawWindow {
  79            hwnd: isize,
  80        }
  81        impl rwh::HasWindowHandle for RawWindow {
  82            fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
  83                Ok(unsafe {
  84                    let hwnd = NonZeroIsize::new_unchecked(self.hwnd);
  85                    let mut handle = rwh::Win32WindowHandle::new(hwnd);
  86                    let hinstance = get_window_long(HWND(self.hwnd), GWLP_HINSTANCE);
  87                    handle.hinstance = NonZeroIsize::new(hinstance);
  88                    rwh::WindowHandle::borrow_raw(handle.into())
  89                })
  90            }
  91        }
  92        impl rwh::HasDisplayHandle for RawWindow {
  93            fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
  94                let handle = rwh::WindowsDisplayHandle::new();
  95                Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
  96            }
  97        }
  98
  99        let raw = RawWindow { hwnd: hwnd.0 };
 100        let gpu = Arc::new(
 101            unsafe {
 102                gpu::Context::init_windowed(
 103                    &raw,
 104                    gpu::ContextDesc {
 105                        validation: false,
 106                        capture: false,
 107                        overlay: false,
 108                    },
 109                )
 110            }
 111            .unwrap(),
 112        );
 113        let config = BladeSurfaceConfig {
 114            size: gpu::Extent::default(),
 115            transparent,
 116        };
 117        let renderer = RefCell::new(BladeRenderer::new(gpu, config));
 118        let callbacks = RefCell::new(Callbacks::default());
 119        let display = RefCell::new(display);
 120        let click_state = RefCell::new(ClickState::new());
 121        let fullscreen = Cell::new(None);
 122        Self {
 123            hwnd,
 124            origin,
 125            physical_size,
 126            scale_factor,
 127            input_handler,
 128            renderer,
 129            callbacks,
 130            platform_inner,
 131            handle,
 132            hide_title_bar,
 133            display,
 134            click_state,
 135            fullscreen,
 136        }
 137    }
 138
 139    fn is_maximized(&self) -> bool {
 140        !self.is_fullscreen() && unsafe { IsZoomed(self.hwnd) }.as_bool()
 141    }
 142
 143    fn is_minimized(&self) -> bool {
 144        unsafe { IsIconic(self.hwnd) }.as_bool()
 145    }
 146
 147    fn is_fullscreen(&self) -> bool {
 148        let fullscreen = self.fullscreen.take();
 149        let is_fullscreen = fullscreen.is_some();
 150        self.fullscreen.set(fullscreen);
 151        is_fullscreen
 152    }
 153
 154    async fn toggle_fullscreen(self: Rc<Self>) {
 155        let StyleAndBounds {
 156            style,
 157            x,
 158            y,
 159            cx,
 160            cy,
 161        } = if let Some(state) = self.fullscreen.take() {
 162            state
 163        } else {
 164            let style = WINDOW_STYLE(unsafe { get_window_long(self.hwnd, GWL_STYLE) } as _);
 165            let mut rc = RECT::default();
 166            unsafe { GetWindowRect(self.hwnd, &mut rc) }.log_err();
 167            self.fullscreen.set(Some(StyleAndBounds {
 168                style,
 169                x: rc.left,
 170                y: rc.top,
 171                cx: rc.right - rc.left,
 172                cy: rc.bottom - rc.top,
 173            }));
 174            let style = style
 175                & !(WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_CAPTION);
 176            let bounds = self.display.borrow().clone().bounds();
 177            StyleAndBounds {
 178                style,
 179                x: bounds.left().0,
 180                y: bounds.top().0,
 181                cx: bounds.size.width.0,
 182                cy: bounds.size.height.0,
 183            }
 184        };
 185        unsafe { set_window_long(self.hwnd, GWL_STYLE, style.0 as isize) };
 186        unsafe {
 187            SetWindowPos(
 188                self.hwnd,
 189                HWND::default(),
 190                x,
 191                y,
 192                cx,
 193                cy,
 194                SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOZORDER,
 195            )
 196        }
 197        .log_err();
 198    }
 199
 200    pub(crate) fn title_bar_padding(&self) -> Pixels {
 201        // using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
 202        let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
 203        px(padding as f32)
 204    }
 205
 206    pub(crate) fn title_bar_top_offset(&self) -> Pixels {
 207        if self.is_maximized() {
 208            self.title_bar_padding() * 2
 209        } else {
 210            px(0.)
 211        }
 212    }
 213
 214    pub(crate) fn title_bar_height(&self) -> Pixels {
 215        // todo(windows) this is hard set to match the ui title bar
 216        //               in the future the ui title bar component will report the size
 217        px(32.) + self.title_bar_top_offset()
 218    }
 219
 220    pub(crate) fn caption_button_width(&self) -> Pixels {
 221        // todo(windows) this is hard set to match the ui title bar
 222        //               in the future the ui title bar component will report the size
 223        px(36.)
 224    }
 225
 226    fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
 227        let height = self.title_bar_height();
 228        let mut rect = RECT::default();
 229        unsafe { GetClientRect(self.hwnd, &mut rect) }?;
 230        rect.bottom = rect.top + ((height.0 * self.scale_factor.get()).round() as i32);
 231        Ok(rect)
 232    }
 233
 234    fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
 235        unsafe { GetKeyState(vkey.0 as i32) < 0 }
 236    }
 237
 238    fn current_modifiers(&self) -> Modifiers {
 239        Modifiers {
 240            control: self.is_virtual_key_pressed(VK_CONTROL),
 241            alt: self.is_virtual_key_pressed(VK_MENU),
 242            shift: self.is_virtual_key_pressed(VK_SHIFT),
 243            platform: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
 244            function: false,
 245        }
 246    }
 247
 248    /// mark window client rect to be re-drawn
 249    /// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-invalidaterect
 250    pub(crate) fn invalidate_client_area(&self) {
 251        unsafe { InvalidateRect(self.hwnd, None, FALSE) };
 252    }
 253
 254    fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
 255        let handled = match msg {
 256            WM_ACTIVATE => self.handle_activate_msg(wparam),
 257            WM_CREATE => self.handle_create_msg(lparam),
 258            WM_MOVE => self.handle_move_msg(lparam),
 259            WM_SIZE => self.handle_size_msg(lparam),
 260            WM_ENTERSIZEMOVE | WM_ENTERMENULOOP => self.handle_size_move_loop(),
 261            WM_EXITSIZEMOVE | WM_EXITMENULOOP => self.handle_size_move_loop_exit(),
 262            WM_TIMER => self.handle_timer_msg(wparam),
 263            WM_NCCALCSIZE => self.handle_calc_client_size(wparam, lparam),
 264            WM_DPICHANGED => self.handle_dpi_changed_msg(wparam, lparam),
 265            WM_NCHITTEST => self.handle_hit_test_msg(msg, wparam, lparam),
 266            WM_PAINT => self.handle_paint_msg(),
 267            WM_CLOSE => self.handle_close_msg(),
 268            WM_DESTROY => self.handle_destroy_msg(),
 269            WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
 270            WM_NCMOUSEMOVE => self.handle_nc_mouse_move_msg(lparam),
 271            WM_NCLBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Left, wparam, lparam),
 272            WM_NCRBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Right, wparam, lparam),
 273            WM_NCMBUTTONDOWN => self.handle_nc_mouse_down_msg(MouseButton::Middle, wparam, lparam),
 274            WM_NCLBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Left, wparam, lparam),
 275            WM_NCRBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Right, wparam, lparam),
 276            WM_NCMBUTTONUP => self.handle_nc_mouse_up_msg(MouseButton::Middle, wparam, lparam),
 277            WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
 278            WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
 279            WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
 280            WM_XBUTTONDOWN => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_down_msg),
 281            WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
 282            WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
 283            WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
 284            WM_XBUTTONUP => self.handle_xbutton_msg(wparam, lparam, Self::handle_mouse_up_msg),
 285            WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
 286            WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
 287            WM_SYSKEYDOWN => self.handle_syskeydown_msg(wparam, lparam),
 288            WM_SYSKEYUP => self.handle_syskeyup_msg(wparam),
 289            WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam),
 290            WM_KEYUP => self.handle_keyup_msg(msg, wparam),
 291            WM_CHAR => self.handle_char_msg(msg, wparam, lparam),
 292            WM_IME_STARTCOMPOSITION => self.handle_ime_position(),
 293            WM_IME_COMPOSITION => self.handle_ime_composition(lparam),
 294            WM_SETCURSOR => self.handle_set_cursor(lparam),
 295            _ => None,
 296        };
 297        if let Some(n) = handled {
 298            LRESULT(n)
 299        } else {
 300            unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
 301        }
 302    }
 303
 304    fn handle_move_msg(&self, lparam: LPARAM) -> Option<isize> {
 305        let x = lparam.signed_loword() as i32;
 306        let y = lparam.signed_hiword() as i32;
 307        self.origin.set(Point {
 308            x: DevicePixels(x),
 309            y: DevicePixels(y),
 310        });
 311        let size = self.physical_size.get();
 312        let center_x = x + size.width.0 / 2;
 313        let center_y = y + size.height.0 / 2;
 314        let monitor_bounds = self.display.borrow().bounds();
 315        if center_x < monitor_bounds.left().0
 316            || center_x > monitor_bounds.right().0
 317            || center_y < monitor_bounds.top().0
 318            || center_y > monitor_bounds.bottom().0
 319        {
 320            // center of the window may have moved to another monitor
 321            let monitor = unsafe { MonitorFromWindow(self.hwnd, MONITOR_DEFAULTTONULL) };
 322            if !monitor.is_invalid() && self.display.borrow().handle != monitor {
 323                // we will get the same monitor if we only have one
 324                (*self.display.borrow_mut()) = Rc::new(WindowsDisplay::new_with_handle(monitor));
 325            }
 326        }
 327        let mut callbacks = self.callbacks.borrow_mut();
 328        if let Some(callback) = callbacks.moved.as_mut() {
 329            callback()
 330        }
 331        Some(0)
 332    }
 333
 334    fn handle_size_msg(&self, lparam: LPARAM) -> Option<isize> {
 335        let width = lparam.loword().max(1) as i32;
 336        let height = lparam.hiword().max(1) as i32;
 337        let scale_factor = self.scale_factor.get();
 338        let new_physical_size = Size {
 339            width: DevicePixels(width),
 340            height: DevicePixels(height),
 341        };
 342        self.physical_size.set(new_physical_size);
 343        self.renderer.borrow_mut().update_drawable_size(Size {
 344            width: width as f64,
 345            height: height as f64,
 346        });
 347        let mut callbacks = self.callbacks.borrow_mut();
 348        if let Some(callback) = callbacks.resize.as_mut() {
 349            let logical_size = logical_size(new_physical_size, scale_factor);
 350            callback(logical_size, scale_factor);
 351        }
 352        Some(0)
 353    }
 354
 355    fn handle_size_move_loop(&self) -> Option<isize> {
 356        unsafe {
 357            let ret = SetTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID, USER_TIMER_MINIMUM, None);
 358            if ret == 0 {
 359                log::error!(
 360                    "unable to create timer: {}",
 361                    std::io::Error::last_os_error()
 362                );
 363            }
 364        }
 365        None
 366    }
 367
 368    fn handle_size_move_loop_exit(&self) -> Option<isize> {
 369        unsafe {
 370            KillTimer(self.hwnd, SIZE_MOVE_LOOP_TIMER_ID).log_err();
 371        }
 372        None
 373    }
 374
 375    fn handle_timer_msg(&self, wparam: WPARAM) -> Option<isize> {
 376        if wparam.0 == SIZE_MOVE_LOOP_TIMER_ID {
 377            self.platform_inner.run_foreground_tasks();
 378            self.handle_paint_msg();
 379            return Some(0);
 380        }
 381        None
 382    }
 383
 384    fn handle_paint_msg(&self) -> Option<isize> {
 385        let mut paint_struct = PAINTSTRUCT::default();
 386        let _hdc = unsafe { BeginPaint(self.hwnd, &mut paint_struct) };
 387        let mut callbacks = self.callbacks.borrow_mut();
 388        if let Some(request_frame) = callbacks.request_frame.as_mut() {
 389            request_frame();
 390        }
 391        unsafe { EndPaint(self.hwnd, &paint_struct) };
 392        Some(0)
 393    }
 394
 395    fn handle_close_msg(&self) -> Option<isize> {
 396        let mut callbacks = self.callbacks.borrow_mut();
 397        if let Some(callback) = callbacks.should_close.as_mut() {
 398            if callback() {
 399                return Some(0);
 400            }
 401        }
 402        None
 403    }
 404
 405    fn handle_destroy_msg(&self) -> Option<isize> {
 406        let mut callbacks = self.callbacks.borrow_mut();
 407        if let Some(callback) = callbacks.close.take() {
 408            callback()
 409        }
 410        let index = self
 411            .platform_inner
 412            .raw_window_handles
 413            .read()
 414            .iter()
 415            .position(|handle| *handle == self.hwnd)
 416            .unwrap();
 417        self.platform_inner.raw_window_handles.write().remove(index);
 418        if self.platform_inner.raw_window_handles.read().is_empty() {
 419            self.platform_inner
 420                .foreground_executor
 421                .spawn(async {
 422                    unsafe { PostQuitMessage(0) };
 423                })
 424                .detach();
 425        }
 426        Some(1)
 427    }
 428
 429    fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> Option<isize> {
 430        let mut callbacks = self.callbacks.borrow_mut();
 431        if let Some(callback) = callbacks.input.as_mut() {
 432            let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
 433                flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
 434                flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
 435                flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
 436                flags if flags.contains(MK_XBUTTON1) => {
 437                    Some(MouseButton::Navigate(NavigationDirection::Back))
 438                }
 439                flags if flags.contains(MK_XBUTTON2) => {
 440                    Some(MouseButton::Navigate(NavigationDirection::Forward))
 441                }
 442                _ => None,
 443            };
 444            let x = lparam.signed_loword() as f32;
 445            let y = lparam.signed_hiword() as f32;
 446            let scale_factor = self.scale_factor.get();
 447            let event = MouseMoveEvent {
 448                position: logical_point(x, y, scale_factor),
 449                pressed_button,
 450                modifiers: self.current_modifiers(),
 451            };
 452            if callback(PlatformInput::MouseMove(event)).default_prevented {
 453                return Some(0);
 454            }
 455        }
 456        Some(1)
 457    }
 458
 459    fn parse_syskeydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
 460        let modifiers = self.current_modifiers();
 461        if !modifiers.alt {
 462            // on Windows, F10 can trigger this event, not just the alt key
 463            // and we just don't care about F10
 464            return None;
 465        }
 466
 467        let vk_code = wparam.loword();
 468        let basic_key = basic_vkcode_to_string(vk_code, modifiers);
 469        if basic_key.is_some() {
 470            return basic_key;
 471        }
 472
 473        let key = match VIRTUAL_KEY(vk_code) {
 474            VK_BACK => Some("backspace"),
 475            VK_RETURN => Some("enter"),
 476            VK_TAB => Some("tab"),
 477            VK_UP => Some("up"),
 478            VK_DOWN => Some("down"),
 479            VK_RIGHT => Some("right"),
 480            VK_LEFT => Some("left"),
 481            VK_HOME => Some("home"),
 482            VK_END => Some("end"),
 483            VK_PRIOR => Some("pageup"),
 484            VK_NEXT => Some("pagedown"),
 485            VK_ESCAPE => Some("escape"),
 486            VK_INSERT => Some("insert"),
 487            _ => None,
 488        };
 489
 490        if let Some(key) = key {
 491            Some(Keystroke {
 492                modifiers,
 493                key: key.to_string(),
 494                ime_key: None,
 495            })
 496        } else {
 497            None
 498        }
 499    }
 500
 501    fn parse_keydown_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
 502        let vk_code = wparam.loword();
 503
 504        let modifiers = self.current_modifiers();
 505        if modifiers.control || modifiers.alt {
 506            let basic_key = basic_vkcode_to_string(vk_code, modifiers);
 507            if basic_key.is_some() {
 508                return basic_key;
 509            }
 510        }
 511
 512        if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
 513            let offset = vk_code - VK_F1.0;
 514            return Some(Keystroke {
 515                modifiers,
 516                key: format!("f{}", offset + 1),
 517                ime_key: None,
 518            });
 519        }
 520
 521        let key = match VIRTUAL_KEY(vk_code) {
 522            VK_BACK => Some("backspace"),
 523            VK_RETURN => Some("enter"),
 524            VK_TAB => Some("tab"),
 525            VK_UP => Some("up"),
 526            VK_DOWN => Some("down"),
 527            VK_RIGHT => Some("right"),
 528            VK_LEFT => Some("left"),
 529            VK_HOME => Some("home"),
 530            VK_END => Some("end"),
 531            VK_PRIOR => Some("pageup"),
 532            VK_NEXT => Some("pagedown"),
 533            VK_ESCAPE => Some("escape"),
 534            VK_INSERT => Some("insert"),
 535            VK_DELETE => Some("delete"),
 536            _ => None,
 537        };
 538
 539        if let Some(key) = key {
 540            Some(Keystroke {
 541                modifiers,
 542                key: key.to_string(),
 543                ime_key: None,
 544            })
 545        } else {
 546            None
 547        }
 548    }
 549
 550    fn parse_char_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
 551        let src = [wparam.0 as u16];
 552        let Ok(first_char) = char::decode_utf16(src).collect::<Vec<_>>()[0] else {
 553            return None;
 554        };
 555        if first_char.is_control() {
 556            None
 557        } else {
 558            let mut modifiers = self.current_modifiers();
 559            // for characters that use 'shift' to type it is expected that the
 560            // shift is not reported if the uppercase/lowercase are the same and instead only the key is reported
 561            if first_char.to_lowercase().to_string() == first_char.to_uppercase().to_string() {
 562                modifiers.shift = false;
 563            }
 564            let key = match first_char {
 565                ' ' => "space".to_string(),
 566                first_char => first_char.to_lowercase().to_string(),
 567            };
 568            Some(Keystroke {
 569                modifiers,
 570                key,
 571                ime_key: Some(first_char.to_string()),
 572            })
 573        }
 574    }
 575
 576    fn handle_syskeydown_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 577        // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
 578        // shortcuts.
 579        let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
 580            return None;
 581        };
 582        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 583            return None;
 584        };
 585        let event = KeyDownEvent {
 586            keystroke,
 587            is_held: lparam.0 & (0x1 << 30) > 0,
 588        };
 589        if func(PlatformInput::KeyDown(event)).default_prevented {
 590            self.invalidate_client_area();
 591            return Some(0);
 592        }
 593        None
 594    }
 595
 596    fn handle_syskeyup_msg(&self, wparam: WPARAM) -> Option<isize> {
 597        // we need to call `DefWindowProcW`, or we will lose the system-wide `Alt+F4`, `Alt+{other keys}`
 598        // shortcuts.
 599        let Some(keystroke) = self.parse_syskeydown_msg_keystroke(wparam) else {
 600            return None;
 601        };
 602        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 603            return None;
 604        };
 605        let event = KeyUpEvent { keystroke };
 606        if func(PlatformInput::KeyUp(event)).default_prevented {
 607            self.invalidate_client_area();
 608            return Some(0);
 609        }
 610        None
 611    }
 612
 613    fn handle_keydown_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 614        let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
 615            return Some(1);
 616        };
 617        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 618            return Some(1);
 619        };
 620        let event = KeyDownEvent {
 621            keystroke,
 622            is_held: lparam.0 & (0x1 << 30) > 0,
 623        };
 624        if func(PlatformInput::KeyDown(event)).default_prevented {
 625            self.invalidate_client_area();
 626            return Some(0);
 627        }
 628        Some(1)
 629    }
 630
 631    fn handle_keyup_msg(&self, _msg: u32, wparam: WPARAM) -> Option<isize> {
 632        let Some(keystroke) = self.parse_keydown_msg_keystroke(wparam) else {
 633            return Some(1);
 634        };
 635        let Some(ref mut func) = self.callbacks.borrow_mut().input else {
 636            return Some(1);
 637        };
 638        let event = KeyUpEvent { keystroke };
 639        if func(PlatformInput::KeyUp(event)).default_prevented {
 640            self.invalidate_client_area();
 641            return Some(0);
 642        }
 643        Some(1)
 644    }
 645
 646    fn handle_char_msg(&self, _msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 647        let Some(keystroke) = self.parse_char_msg_keystroke(wparam) else {
 648            return Some(1);
 649        };
 650        let mut callbacks = self.callbacks.borrow_mut();
 651        let Some(ref mut func) = callbacks.input else {
 652            return Some(1);
 653        };
 654        let ime_key = keystroke.ime_key.clone();
 655        let event = KeyDownEvent {
 656            keystroke,
 657            is_held: lparam.0 & (0x1 << 30) > 0,
 658        };
 659
 660        let dispatch_event_result = func(PlatformInput::KeyDown(event));
 661        if dispatch_event_result.default_prevented || !dispatch_event_result.propagate {
 662            self.invalidate_client_area();
 663            return Some(0);
 664        }
 665        drop(callbacks);
 666        let Some(ime_char) = ime_key else {
 667            return Some(1);
 668        };
 669        let Some(mut input_handler) = self.input_handler.take() else {
 670            return Some(1);
 671        };
 672        input_handler.replace_text_in_range(None, &ime_char);
 673        self.input_handler.set(Some(input_handler));
 674        self.invalidate_client_area();
 675        Some(0)
 676    }
 677
 678    fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
 679        let mut callbacks = self.callbacks.borrow_mut();
 680        if let Some(callback) = callbacks.input.as_mut() {
 681            let x = lparam.signed_loword() as f32;
 682            let y = lparam.signed_hiword() as f32;
 683            let physical_point = point(DevicePixels(x as i32), DevicePixels(y as i32));
 684            let click_count = self.click_state.borrow_mut().update(button, physical_point);
 685            let scale_factor = self.scale_factor.get();
 686            let event = MouseDownEvent {
 687                button,
 688                position: logical_point(x, y, scale_factor),
 689                modifiers: self.current_modifiers(),
 690                click_count,
 691                first_mouse: false,
 692            };
 693            if callback(PlatformInput::MouseDown(event)).default_prevented {
 694                return Some(0);
 695            }
 696        }
 697        Some(1)
 698    }
 699
 700    fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> Option<isize> {
 701        let mut callbacks = self.callbacks.borrow_mut();
 702        if let Some(callback) = callbacks.input.as_mut() {
 703            let x = lparam.signed_loword() as f32;
 704            let y = lparam.signed_hiword() as f32;
 705            let click_count = self.click_state.borrow().current_count;
 706            let scale_factor = self.scale_factor.get();
 707            let event = MouseUpEvent {
 708                button,
 709                position: logical_point(x, y, scale_factor),
 710                modifiers: self.current_modifiers(),
 711                click_count,
 712            };
 713            if callback(PlatformInput::MouseUp(event)).default_prevented {
 714                return Some(0);
 715            }
 716        }
 717        Some(1)
 718    }
 719
 720    fn handle_xbutton_msg(
 721        &self,
 722        wparam: WPARAM,
 723        lparam: LPARAM,
 724        handler: impl Fn(&Self, MouseButton, LPARAM) -> Option<isize>,
 725    ) -> Option<isize> {
 726        let nav_dir = match wparam.hiword() {
 727            XBUTTON1 => NavigationDirection::Back,
 728            XBUTTON2 => NavigationDirection::Forward,
 729            _ => return Some(1),
 730        };
 731        handler(self, MouseButton::Navigate(nav_dir), lparam)
 732    }
 733
 734    fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 735        let mut callbacks = self.callbacks.borrow_mut();
 736        if let Some(callback) = callbacks.input.as_mut() {
 737            let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
 738                * self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
 739            let mut cursor_point = POINT {
 740                x: lparam.signed_loword().into(),
 741                y: lparam.signed_hiword().into(),
 742            };
 743            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
 744            let scale_factor = self.scale_factor.get();
 745            let event = crate::ScrollWheelEvent {
 746                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 747                delta: ScrollDelta::Lines(Point {
 748                    x: 0.0,
 749                    y: wheel_distance,
 750                }),
 751                modifiers: self.current_modifiers(),
 752                touch_phase: TouchPhase::Moved,
 753            };
 754            callback(PlatformInput::ScrollWheel(event));
 755            return Some(0);
 756        }
 757        Some(1)
 758    }
 759
 760    fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 761        let mut callbacks = self.callbacks.borrow_mut();
 762        if let Some(callback) = callbacks.input.as_mut() {
 763            let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
 764                * self.platform_inner.settings.borrow().wheel_scroll_chars as f32;
 765            let mut cursor_point = POINT {
 766                x: lparam.signed_loword().into(),
 767                y: lparam.signed_hiword().into(),
 768            };
 769            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
 770            let scale_factor = self.scale_factor.get();
 771            let event = crate::ScrollWheelEvent {
 772                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
 773                delta: ScrollDelta::Lines(Point {
 774                    x: wheel_distance,
 775                    y: 0.0,
 776                }),
 777                modifiers: self.current_modifiers(),
 778                touch_phase: TouchPhase::Moved,
 779            };
 780            if callback(PlatformInput::ScrollWheel(event)).default_prevented {
 781                return Some(0);
 782            }
 783        }
 784        Some(1)
 785    }
 786
 787    fn handle_ime_position(&self) -> Option<isize> {
 788        unsafe {
 789            let ctx = ImmGetContext(self.hwnd);
 790            let Some(mut input_handler) = self.input_handler.take() else {
 791                return Some(1);
 792            };
 793            let caret_range = input_handler.selected_text_range().unwrap_or_default();
 794            let caret_position = input_handler.bounds_for_range(caret_range).unwrap();
 795            self.input_handler.set(Some(input_handler));
 796            let scale_factor = self.scale_factor.get();
 797            let config = CANDIDATEFORM {
 798                dwStyle: CFS_CANDIDATEPOS,
 799                // logical to physical
 800                ptCurrentPos: POINT {
 801                    x: (caret_position.origin.x.0 * scale_factor) as i32,
 802                    y: (caret_position.origin.y.0 * scale_factor) as i32
 803                        + ((caret_position.size.height.0 * scale_factor) as i32 / 2),
 804                },
 805                ..Default::default()
 806            };
 807            ImmSetCandidateWindow(ctx, &config as _);
 808            ImmReleaseContext(self.hwnd, ctx);
 809            Some(0)
 810        }
 811    }
 812
 813    fn parse_ime_compostion_string(&self) -> Option<(String, usize)> {
 814        unsafe {
 815            let ctx = ImmGetContext(self.hwnd);
 816            let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0);
 817            let result = if string_len >= 0 {
 818                let mut buffer = vec![0u8; string_len as usize + 2];
 819                ImmGetCompositionStringW(
 820                    ctx,
 821                    GCS_COMPSTR,
 822                    Some(buffer.as_mut_ptr() as _),
 823                    string_len as _,
 824                );
 825                let wstring = std::slice::from_raw_parts::<u16>(
 826                    buffer.as_mut_ptr().cast::<u16>(),
 827                    string_len as usize / 2,
 828                );
 829                let string = String::from_utf16_lossy(wstring);
 830                Some((string, string_len as usize / 2))
 831            } else {
 832                None
 833            };
 834            ImmReleaseContext(self.hwnd, ctx);
 835            result
 836        }
 837    }
 838
 839    fn retrieve_composition_cursor_position(&self) -> usize {
 840        unsafe {
 841            let ctx = ImmGetContext(self.hwnd);
 842            let ret = ImmGetCompositionStringW(ctx, GCS_CURSORPOS, None, 0);
 843            ImmReleaseContext(self.hwnd, ctx);
 844            ret as usize
 845        }
 846    }
 847
 848    fn parse_ime_compostion_result(&self) -> Option<String> {
 849        unsafe {
 850            let ctx = ImmGetContext(self.hwnd);
 851            let string_len = ImmGetCompositionStringW(ctx, GCS_RESULTSTR, None, 0);
 852            let result = if string_len >= 0 {
 853                let mut buffer = vec![0u8; string_len as usize + 2];
 854                ImmGetCompositionStringW(
 855                    ctx,
 856                    GCS_RESULTSTR,
 857                    Some(buffer.as_mut_ptr() as _),
 858                    string_len as _,
 859                );
 860                let wstring = std::slice::from_raw_parts::<u16>(
 861                    buffer.as_mut_ptr().cast::<u16>(),
 862                    string_len as usize / 2,
 863                );
 864                let string = String::from_utf16_lossy(wstring);
 865                Some(string)
 866            } else {
 867                None
 868            };
 869            ImmReleaseContext(self.hwnd, ctx);
 870            result
 871        }
 872    }
 873
 874    fn handle_ime_composition(&self, lparam: LPARAM) -> Option<isize> {
 875        let mut ime_input = None;
 876        if lparam.0 as u32 & GCS_COMPSTR.0 > 0 {
 877            let Some((string, string_len)) = self.parse_ime_compostion_string() else {
 878                return None;
 879            };
 880            let Some(mut input_handler) = self.input_handler.take() else {
 881                return None;
 882            };
 883            input_handler.replace_and_mark_text_in_range(
 884                None,
 885                string.as_str(),
 886                Some(0..string_len),
 887            );
 888            self.input_handler.set(Some(input_handler));
 889            ime_input = Some(string);
 890        }
 891        if lparam.0 as u32 & GCS_CURSORPOS.0 > 0 {
 892            let Some(ref comp_string) = ime_input else {
 893                return None;
 894            };
 895            let caret_pos = self.retrieve_composition_cursor_position();
 896            let Some(mut input_handler) = self.input_handler.take() else {
 897                return None;
 898            };
 899            input_handler.replace_and_mark_text_in_range(None, comp_string, Some(0..caret_pos));
 900            self.input_handler.set(Some(input_handler));
 901        }
 902        if lparam.0 as u32 & GCS_RESULTSTR.0 > 0 {
 903            let Some(comp_result) = self.parse_ime_compostion_result() else {
 904                return None;
 905            };
 906            let Some(mut input_handler) = self.input_handler.take() else {
 907                return Some(1);
 908            };
 909            input_handler.replace_text_in_range(None, &comp_result);
 910            self.input_handler.set(Some(input_handler));
 911            self.invalidate_client_area();
 912            return Some(0);
 913        }
 914        // currently, we don't care other stuff
 915        None
 916    }
 917
 918    fn handle_drag_drop(&self, input: PlatformInput) {
 919        let mut callbacks = self.callbacks.borrow_mut();
 920        let Some(ref mut func) = callbacks.input else {
 921            return;
 922        };
 923        func(input);
 924    }
 925
 926    /// SEE: https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-nccalcsize
 927    fn handle_calc_client_size(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
 928        if !self.hide_title_bar || self.is_fullscreen() {
 929            return None;
 930        }
 931
 932        if wparam.0 == 0 {
 933            return None;
 934        }
 935
 936        let dpi = unsafe { GetDpiForWindow(self.hwnd) };
 937
 938        let frame_x = unsafe { GetSystemMetricsForDpi(SM_CXFRAME, dpi) };
 939        let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
 940        let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
 941
 942        // wparam is TRUE so lparam points to an NCCALCSIZE_PARAMS structure
 943        let mut params = lparam.0 as *mut NCCALCSIZE_PARAMS;
 944        let mut requested_client_rect = unsafe { &mut ((*params).rgrc) };
 945
 946        requested_client_rect[0].right -= frame_x + padding;
 947        requested_client_rect[0].left += frame_x + padding;
 948        requested_client_rect[0].bottom -= frame_y + padding;
 949
 950        Some(0)
 951    }
 952
 953    fn handle_activate_msg(&self, wparam: WPARAM) -> Option<isize> {
 954        if self.hide_title_bar {
 955            if let Some(titlebar_rect) = self.get_titlebar_rect().log_err() {
 956                unsafe { InvalidateRect(self.hwnd, Some(&titlebar_rect), FALSE) };
 957            }
 958        }
 959        let activated = wparam.loword() > 0;
 960        let mut callbacks = self.callbacks.borrow_mut();
 961        if let Some(mut cb) = callbacks.active_status_change.as_mut() {
 962            cb(activated);
 963        }
 964        None
 965    }
 966
 967    fn handle_create_msg(&self, _lparam: LPARAM) -> Option<isize> {
 968        let mut size_rect = RECT::default();
 969        unsafe { GetWindowRect(self.hwnd, &mut size_rect).log_err() };
 970
 971        let width = size_rect.right - size_rect.left;
 972        let height = size_rect.bottom - size_rect.top;
 973
 974        self.physical_size.set(Size {
 975            width: DevicePixels(width),
 976            height: DevicePixels(height),
 977        });
 978
 979        if self.hide_title_bar {
 980            // Inform the application of the frame change to force redrawing with the new
 981            // client area that is extended into the title bar
 982            unsafe {
 983                SetWindowPos(
 984                    self.hwnd,
 985                    HWND::default(),
 986                    size_rect.left,
 987                    size_rect.top,
 988                    width,
 989                    height,
 990                    SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE,
 991                )
 992                .log_err()
 993            };
 994        }
 995
 996        Some(0)
 997    }
 998
 999    fn handle_dpi_changed_msg(&self, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
1000        let new_dpi = wparam.loword() as f32;
1001        let scale_factor = new_dpi / USER_DEFAULT_SCREEN_DPI as f32;
1002        self.scale_factor.set(scale_factor);
1003        let rect = unsafe { &*(lparam.0 as *const RECT) };
1004        let width = rect.right - rect.left;
1005        let height = rect.bottom - rect.top;
1006        // this will emit `WM_SIZE` and `WM_MOVE` right here
1007        // even before this function returns
1008        // the new size is handled in `WM_SIZE`
1009        unsafe {
1010            SetWindowPos(
1011                self.hwnd,
1012                None,
1013                rect.left,
1014                rect.top,
1015                width,
1016                height,
1017                SWP_NOZORDER | SWP_NOACTIVATE,
1018            )
1019            .context("unable to set window position after dpi has changed")
1020            .log_err();
1021        }
1022        self.invalidate_client_area();
1023        Some(0)
1024    }
1025
1026    fn handle_hit_test_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> Option<isize> {
1027        if !self.hide_title_bar {
1028            return None;
1029        }
1030
1031        // default handler for resize areas
1032        let hit = unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
1033        if matches!(
1034            hit.0 as u32,
1035            HTNOWHERE
1036                | HTRIGHT
1037                | HTLEFT
1038                | HTTOPLEFT
1039                | HTTOP
1040                | HTTOPRIGHT
1041                | HTBOTTOMRIGHT
1042                | HTBOTTOM
1043                | HTBOTTOMLEFT
1044        ) {
1045            return Some(hit.0);
1046        }
1047
1048        if self.is_fullscreen() {
1049            return Some(HTCLIENT as _);
1050        }
1051
1052        let dpi = unsafe { GetDpiForWindow(self.hwnd) };
1053        let frame_y = unsafe { GetSystemMetricsForDpi(SM_CYFRAME, dpi) };
1054        let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, dpi) };
1055
1056        let mut cursor_point = POINT {
1057            x: lparam.signed_loword().into(),
1058            y: lparam.signed_hiword().into(),
1059        };
1060        unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1061        if cursor_point.y > 0 && cursor_point.y < frame_y + padding {
1062            return Some(HTTOP as _);
1063        }
1064
1065        let titlebar_rect = self.get_titlebar_rect();
1066        if let Ok(titlebar_rect) = titlebar_rect {
1067            if cursor_point.y < titlebar_rect.bottom {
1068                let caption_btn_width =
1069                    (self.caption_button_width().0 * self.scale_factor.get()) as i32;
1070                if cursor_point.x >= titlebar_rect.right - caption_btn_width {
1071                    return Some(HTCLOSE as _);
1072                } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
1073                    return Some(HTMAXBUTTON as _);
1074                } else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
1075                    return Some(HTMINBUTTON as _);
1076                }
1077
1078                return Some(HTCAPTION as _);
1079            }
1080        }
1081
1082        Some(HTCLIENT as _)
1083    }
1084
1085    fn handle_nc_mouse_move_msg(&self, lparam: LPARAM) -> Option<isize> {
1086        if !self.hide_title_bar {
1087            return None;
1088        }
1089
1090        let mut callbacks = self.callbacks.borrow_mut();
1091        if let Some(callback) = callbacks.input.as_mut() {
1092            let mut cursor_point = POINT {
1093                x: lparam.signed_loword().into(),
1094                y: lparam.signed_hiword().into(),
1095            };
1096            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1097            let scale_factor = self.scale_factor.get();
1098            let event = MouseMoveEvent {
1099                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1100                pressed_button: None,
1101                modifiers: self.current_modifiers(),
1102            };
1103            if callback(PlatformInput::MouseMove(event)).default_prevented {
1104                return Some(0);
1105            }
1106        }
1107        None
1108    }
1109
1110    fn handle_nc_mouse_down_msg(
1111        &self,
1112        button: MouseButton,
1113        wparam: WPARAM,
1114        lparam: LPARAM,
1115    ) -> Option<isize> {
1116        if !self.hide_title_bar {
1117            return None;
1118        }
1119
1120        let mut callbacks = self.callbacks.borrow_mut();
1121        if let Some(callback) = callbacks.input.as_mut() {
1122            let mut cursor_point = POINT {
1123                x: lparam.signed_loword().into(),
1124                y: lparam.signed_hiword().into(),
1125            };
1126            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1127            let physical_point = point(DevicePixels(cursor_point.x), DevicePixels(cursor_point.y));
1128            let click_count = self.click_state.borrow_mut().update(button, physical_point);
1129            let scale_factor = self.scale_factor.get();
1130            let event = MouseDownEvent {
1131                button,
1132                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1133                modifiers: self.current_modifiers(),
1134                click_count,
1135                first_mouse: false,
1136            };
1137            if callback(PlatformInput::MouseDown(event)).default_prevented {
1138                return Some(0);
1139            }
1140        }
1141
1142        // Since these are handled in handle_nc_mouse_up_msg we must prevent the default window proc
1143        matches!(wparam.0 as u32, HTMINBUTTON | HTMAXBUTTON | HTCLOSE).then_some(0)
1144    }
1145
1146    fn handle_nc_mouse_up_msg(
1147        &self,
1148        button: MouseButton,
1149        wparam: WPARAM,
1150        lparam: LPARAM,
1151    ) -> Option<isize> {
1152        if !self.hide_title_bar {
1153            return None;
1154        }
1155
1156        let mut callbacks = self.callbacks.borrow_mut();
1157        if let Some(callback) = callbacks.input.as_mut() {
1158            let mut cursor_point = POINT {
1159                x: lparam.signed_loword().into(),
1160                y: lparam.signed_hiword().into(),
1161            };
1162            unsafe { ScreenToClient(self.hwnd, &mut cursor_point) };
1163            let scale_factor = self.scale_factor.get();
1164            let event = MouseUpEvent {
1165                button,
1166                position: logical_point(cursor_point.x as f32, cursor_point.y as f32, scale_factor),
1167                modifiers: self.current_modifiers(),
1168                click_count: 1,
1169            };
1170            if callback(PlatformInput::MouseUp(event)).default_prevented {
1171                return Some(0);
1172            }
1173        }
1174        drop(callbacks);
1175
1176        if button == MouseButton::Left {
1177            match wparam.0 as u32 {
1178                HTMINBUTTON => unsafe {
1179                    ShowWindowAsync(self.hwnd, SW_MINIMIZE);
1180                },
1181                HTMAXBUTTON => unsafe {
1182                    if self.is_maximized() {
1183                        ShowWindowAsync(self.hwnd, SW_NORMAL);
1184                    } else {
1185                        ShowWindowAsync(self.hwnd, SW_MAXIMIZE);
1186                    }
1187                },
1188                HTCLOSE => unsafe {
1189                    PostMessageW(self.hwnd, WM_CLOSE, WPARAM::default(), LPARAM::default())
1190                        .log_err();
1191                },
1192                _ => return None,
1193            };
1194            return Some(0);
1195        }
1196
1197        None
1198    }
1199
1200    fn handle_set_cursor(&self, lparam: LPARAM) -> Option<isize> {
1201        if matches!(
1202            lparam.loword() as u32,
1203            HTLEFT
1204                | HTRIGHT
1205                | HTTOP
1206                | HTTOPLEFT
1207                | HTTOPRIGHT
1208                | HTBOTTOM
1209                | HTBOTTOMLEFT
1210                | HTBOTTOMRIGHT
1211        ) {
1212            return None;
1213        }
1214        unsafe { SetCursor(self.platform_inner.current_cursor.get()) };
1215        Some(1)
1216    }
1217}
1218
1219#[derive(Default)]
1220struct Callbacks {
1221    request_frame: Option<Box<dyn FnMut()>>,
1222    input: Option<Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>>,
1223    active_status_change: Option<Box<dyn FnMut(bool)>>,
1224    resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
1225    moved: Option<Box<dyn FnMut()>>,
1226    should_close: Option<Box<dyn FnMut() -> bool>>,
1227    close: Option<Box<dyn FnOnce()>>,
1228    appearance_changed: Option<Box<dyn FnMut()>>,
1229}
1230
1231pub(crate) struct WindowsWindow {
1232    inner: Rc<WindowsWindowInner>,
1233    drag_drop_handler: IDropTarget,
1234}
1235
1236struct WindowCreateContext {
1237    inner: Option<Rc<WindowsWindowInner>>,
1238    platform_inner: Rc<WindowsPlatformInner>,
1239    handle: AnyWindowHandle,
1240    hide_title_bar: bool,
1241    display: Rc<WindowsDisplay>,
1242    transparent: bool,
1243}
1244
1245impl WindowsWindow {
1246    pub(crate) fn new(
1247        platform_inner: Rc<WindowsPlatformInner>,
1248        handle: AnyWindowHandle,
1249        options: WindowParams,
1250    ) -> Self {
1251        let classname = register_wnd_class(platform_inner.icon);
1252        let hide_title_bar = options
1253            .titlebar
1254            .as_ref()
1255            .map(|titlebar| titlebar.appears_transparent)
1256            .unwrap_or(false);
1257        let windowname = HSTRING::from(
1258            options
1259                .titlebar
1260                .as_ref()
1261                .and_then(|titlebar| titlebar.title.as_ref())
1262                .map(|title| title.as_ref())
1263                .unwrap_or(""),
1264        );
1265        let dwstyle = WS_THICKFRAME | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX;
1266        let x = options.bounds.origin.x.0;
1267        let y = options.bounds.origin.y.0;
1268        let nwidth = options.bounds.size.width.0;
1269        let nheight = options.bounds.size.height.0;
1270        let hwndparent = HWND::default();
1271        let hmenu = HMENU::default();
1272        let hinstance = get_module_handle();
1273        let mut context = WindowCreateContext {
1274            inner: None,
1275            platform_inner: platform_inner.clone(),
1276            handle,
1277            hide_title_bar,
1278            // todo(windows) move window to target monitor
1279            // options.display_id
1280            display: Rc::new(WindowsDisplay::primary_monitor().unwrap()),
1281            transparent: options.window_background != WindowBackgroundAppearance::Opaque,
1282        };
1283        let lpparam = Some(&context as *const _ as *const _);
1284        unsafe {
1285            CreateWindowExW(
1286                WS_EX_APPWINDOW,
1287                classname,
1288                &windowname,
1289                dwstyle,
1290                x,
1291                y,
1292                nwidth,
1293                nheight,
1294                hwndparent,
1295                hmenu,
1296                hinstance,
1297                lpparam,
1298            )
1299        };
1300        let drag_drop_handler = {
1301            let inner = context.inner.as_ref().unwrap();
1302            let handler = WindowsDragDropHandler(Rc::clone(inner));
1303            let drag_drop_handler: IDropTarget = handler.into();
1304            unsafe {
1305                RegisterDragDrop(inner.hwnd, &drag_drop_handler)
1306                    .expect("unable to register drag-drop event")
1307            };
1308            drag_drop_handler
1309        };
1310        let wnd = Self {
1311            inner: context.inner.unwrap(),
1312            drag_drop_handler,
1313        };
1314        platform_inner
1315            .raw_window_handles
1316            .write()
1317            .push(wnd.inner.hwnd);
1318
1319        unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) };
1320        wnd
1321    }
1322}
1323
1324impl rwh::HasWindowHandle for WindowsWindow {
1325    fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
1326        let raw =
1327            rwh::Win32WindowHandle::new(unsafe { NonZeroIsize::new_unchecked(self.inner.hwnd.0) })
1328                .into();
1329        Ok(unsafe { rwh::WindowHandle::borrow_raw(raw) })
1330    }
1331}
1332
1333// todo(windows)
1334impl rwh::HasDisplayHandle for WindowsWindow {
1335    fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
1336        unimplemented!()
1337    }
1338}
1339
1340impl Drop for WindowsWindow {
1341    fn drop(&mut self) {
1342        unsafe {
1343            let _ = RevokeDragDrop(self.inner.hwnd);
1344            self.inner.renderer.borrow_mut().destroy();
1345        }
1346    }
1347}
1348
1349impl PlatformWindow for WindowsWindow {
1350    fn bounds(&self) -> Bounds<DevicePixels> {
1351        Bounds {
1352            origin: self.inner.origin.get(),
1353            size: self.inner.physical_size.get(),
1354        }
1355    }
1356
1357    fn is_maximized(&self) -> bool {
1358        self.inner.is_maximized()
1359    }
1360
1361    fn is_minimized(&self) -> bool {
1362        self.inner.is_minimized()
1363    }
1364
1365    /// get the logical size of the app's drawable area.
1366    ///
1367    /// Currently, GPUI uses logical size of the app to handle mouse interactions (such as
1368    /// whether the mouse collides with other elements of GPUI).
1369    fn content_size(&self) -> Size<Pixels> {
1370        logical_size(
1371            self.inner.physical_size.get(),
1372            self.inner.scale_factor.get(),
1373        )
1374    }
1375
1376    fn scale_factor(&self) -> f32 {
1377        self.inner.scale_factor.get()
1378    }
1379
1380    // todo(windows)
1381    fn appearance(&self) -> WindowAppearance {
1382        WindowAppearance::Dark
1383    }
1384
1385    fn display(&self) -> Rc<dyn PlatformDisplay> {
1386        self.inner.display.borrow().clone()
1387    }
1388
1389    fn mouse_position(&self) -> Point<Pixels> {
1390        let point = unsafe {
1391            let mut point: POINT = std::mem::zeroed();
1392            GetCursorPos(&mut point)
1393                .context("unable to get cursor position")
1394                .log_err();
1395            ScreenToClient(self.inner.hwnd, &mut point);
1396            point
1397        };
1398        logical_point(
1399            point.x as f32,
1400            point.y as f32,
1401            self.inner.scale_factor.get(),
1402        )
1403    }
1404
1405    // todo(windows)
1406    fn modifiers(&self) -> Modifiers {
1407        Modifiers::none()
1408    }
1409
1410    // todo(windows)
1411    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1412        self.inner.input_handler.set(Some(input_handler));
1413    }
1414
1415    // todo(windows)
1416    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1417        self.inner.input_handler.take()
1418    }
1419
1420    fn prompt(
1421        &self,
1422        level: PromptLevel,
1423        msg: &str,
1424        detail: Option<&str>,
1425        answers: &[&str],
1426    ) -> Option<Receiver<usize>> {
1427        let (done_tx, done_rx) = oneshot::channel();
1428        let msg = msg.to_string();
1429        let detail_string = match detail {
1430            Some(info) => Some(info.to_string()),
1431            None => None,
1432        };
1433        let answers = answers.iter().map(|s| s.to_string()).collect::<Vec<_>>();
1434        let handle = self.inner.hwnd;
1435        self.inner
1436            .platform_inner
1437            .foreground_executor
1438            .spawn(async move {
1439                unsafe {
1440                    let mut config;
1441                    config = std::mem::zeroed::<TASKDIALOGCONFIG>();
1442                    config.cbSize = std::mem::size_of::<TASKDIALOGCONFIG>() as _;
1443                    config.hwndParent = handle;
1444                    let title;
1445                    let main_icon;
1446                    match level {
1447                        crate::PromptLevel::Info => {
1448                            title = windows::core::w!("Info");
1449                            main_icon = TD_INFORMATION_ICON;
1450                        }
1451                        crate::PromptLevel::Warning => {
1452                            title = windows::core::w!("Warning");
1453                            main_icon = TD_WARNING_ICON;
1454                        }
1455                        crate::PromptLevel::Critical | crate::PromptLevel::Destructive => {
1456                            title = windows::core::w!("Critical");
1457                            main_icon = TD_ERROR_ICON;
1458                        }
1459                    };
1460                    config.pszWindowTitle = title;
1461                    config.Anonymous1.pszMainIcon = main_icon;
1462                    let instruction = msg.encode_utf16().chain(once(0)).collect_vec();
1463                    config.pszMainInstruction = PCWSTR::from_raw(instruction.as_ptr());
1464                    let hints_encoded;
1465                    if let Some(ref hints) = detail_string {
1466                        hints_encoded = hints.encode_utf16().chain(once(0)).collect_vec();
1467                        config.pszContent = PCWSTR::from_raw(hints_encoded.as_ptr());
1468                    };
1469                    let mut buttons = Vec::new();
1470                    let mut btn_encoded = Vec::new();
1471                    for (index, btn_string) in answers.iter().enumerate() {
1472                        let encoded = btn_string.encode_utf16().chain(once(0)).collect_vec();
1473                        buttons.push(TASKDIALOG_BUTTON {
1474                            nButtonID: index as _,
1475                            pszButtonText: PCWSTR::from_raw(encoded.as_ptr()),
1476                        });
1477                        btn_encoded.push(encoded);
1478                    }
1479                    config.cButtons = buttons.len() as _;
1480                    config.pButtons = buttons.as_ptr();
1481
1482                    config.pfCallback = None;
1483                    let mut res = std::mem::zeroed();
1484                    let _ = TaskDialogIndirect(&config, Some(&mut res), None, None)
1485                        .inspect_err(|e| log::error!("unable to create task dialog: {}", e));
1486
1487                    let _ = done_tx.send(res as usize);
1488                }
1489            })
1490            .detach();
1491
1492        Some(done_rx)
1493    }
1494
1495    fn activate(&self) {
1496        unsafe { SetActiveWindow(self.inner.hwnd) };
1497        unsafe { SetFocus(self.inner.hwnd) };
1498        unsafe { SetForegroundWindow(self.inner.hwnd) };
1499    }
1500
1501    fn is_active(&self) -> bool {
1502        self.inner.hwnd == unsafe { GetActiveWindow() }
1503    }
1504
1505    // todo(windows)
1506    fn set_title(&mut self, title: &str) {
1507        unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) }
1508            .inspect_err(|e| log::error!("Set title failed: {e}"))
1509            .ok();
1510    }
1511
1512    fn set_app_id(&mut self, _app_id: &str) {}
1513
1514    fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
1515        self.inner
1516            .renderer
1517            .borrow_mut()
1518            .update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
1519    }
1520
1521    // todo(windows)
1522    fn set_edited(&mut self, _edited: bool) {}
1523
1524    // todo(windows)
1525    fn show_character_palette(&self) {}
1526
1527    fn minimize(&self) {
1528        unsafe { ShowWindowAsync(self.inner.hwnd, SW_MINIMIZE) };
1529    }
1530
1531    fn zoom(&self) {
1532        unsafe { ShowWindowAsync(self.inner.hwnd, SW_MAXIMIZE) };
1533    }
1534
1535    fn toggle_fullscreen(&self) {
1536        self.inner
1537            .platform_inner
1538            .foreground_executor
1539            .spawn(self.inner.clone().toggle_fullscreen())
1540            .detach();
1541    }
1542
1543    fn is_fullscreen(&self) -> bool {
1544        self.inner.is_fullscreen()
1545    }
1546
1547    // todo(windows)
1548    fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
1549        self.inner.callbacks.borrow_mut().request_frame = Some(callback);
1550    }
1551
1552    // todo(windows)
1553    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
1554        self.inner.callbacks.borrow_mut().input = Some(callback);
1555    }
1556
1557    // todo(windows)
1558    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1559        self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
1560    }
1561
1562    // todo(windows)
1563    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1564        self.inner.callbacks.borrow_mut().resize = Some(callback);
1565    }
1566
1567    // todo(windows)
1568    fn on_moved(&self, callback: Box<dyn FnMut()>) {
1569        self.inner.callbacks.borrow_mut().moved = Some(callback);
1570    }
1571
1572    // todo(windows)
1573    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1574        self.inner.callbacks.borrow_mut().should_close = Some(callback);
1575    }
1576
1577    // todo(windows)
1578    fn on_close(&self, callback: Box<dyn FnOnce()>) {
1579        self.inner.callbacks.borrow_mut().close = Some(callback);
1580    }
1581
1582    // todo(windows)
1583    fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1584        self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
1585    }
1586
1587    // todo(windows)
1588    fn draw(&self, scene: &Scene) {
1589        self.inner.renderer.borrow_mut().draw(scene)
1590    }
1591
1592    // todo(windows)
1593    fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1594        self.inner.renderer.borrow().sprite_atlas().clone()
1595    }
1596
1597    fn get_raw_handle(&self) -> HWND {
1598        self.inner.hwnd
1599    }
1600}
1601
1602#[implement(IDropTarget)]
1603struct WindowsDragDropHandler(pub Rc<WindowsWindowInner>);
1604
1605#[allow(non_snake_case)]
1606impl IDropTarget_Impl for WindowsDragDropHandler {
1607    fn DragEnter(
1608        &self,
1609        pdataobj: Option<&IDataObject>,
1610        _grfkeystate: MODIFIERKEYS_FLAGS,
1611        pt: &POINTL,
1612        pdweffect: *mut DROPEFFECT,
1613    ) -> windows::core::Result<()> {
1614        unsafe {
1615            let Some(idata_obj) = pdataobj else {
1616                log::info!("no dragging file or directory detected");
1617                return Ok(());
1618            };
1619            let config = FORMATETC {
1620                cfFormat: CF_HDROP.0,
1621                ptd: std::ptr::null_mut() as _,
1622                dwAspect: DVASPECT_CONTENT.0,
1623                lindex: -1,
1624                tymed: TYMED_HGLOBAL.0 as _,
1625            };
1626            if idata_obj.QueryGetData(&config as _) == S_OK {
1627                *pdweffect = DROPEFFECT_LINK;
1628                let Some(mut idata) = idata_obj.GetData(&config as _).log_err() else {
1629                    return Ok(());
1630                };
1631                if idata.u.hGlobal.is_invalid() {
1632                    return Ok(());
1633                }
1634                let hdrop = idata.u.hGlobal.0 as *mut HDROP;
1635                let mut paths = SmallVec::<[PathBuf; 2]>::new();
1636                let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None);
1637                for file_index in 0..file_count {
1638                    let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize;
1639                    let mut buffer = vec![0u16; filename_length + 1];
1640                    let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice()));
1641                    if ret == 0 {
1642                        log::error!("unable to read file name");
1643                        continue;
1644                    }
1645                    if let Some(file_name) =
1646                        String::from_utf16(&buffer[0..filename_length]).log_err()
1647                    {
1648                        if let Some(path) = PathBuf::from_str(&file_name).log_err() {
1649                            paths.push(path);
1650                        }
1651                    }
1652                }
1653                ReleaseStgMedium(&mut idata);
1654                let mut cursor_position = POINT { x: pt.x, y: pt.y };
1655                ScreenToClient(self.0.hwnd, &mut cursor_position);
1656                let scale_factor = self.0.scale_factor.get();
1657                let input = PlatformInput::FileDrop(FileDropEvent::Entered {
1658                    position: logical_point(
1659                        cursor_position.x as f32,
1660                        cursor_position.y as f32,
1661                        scale_factor,
1662                    ),
1663                    paths: ExternalPaths(paths),
1664                });
1665                self.0.handle_drag_drop(input);
1666            } else {
1667                *pdweffect = DROPEFFECT_NONE;
1668            }
1669        }
1670        Ok(())
1671    }
1672
1673    fn DragOver(
1674        &self,
1675        _grfkeystate: MODIFIERKEYS_FLAGS,
1676        pt: &POINTL,
1677        _pdweffect: *mut DROPEFFECT,
1678    ) -> windows::core::Result<()> {
1679        let mut cursor_position = POINT { x: pt.x, y: pt.y };
1680        unsafe {
1681            ScreenToClient(self.0.hwnd, &mut cursor_position);
1682        }
1683        let scale_factor = self.0.scale_factor.get();
1684        let input = PlatformInput::FileDrop(FileDropEvent::Pending {
1685            position: logical_point(
1686                cursor_position.x as f32,
1687                cursor_position.y as f32,
1688                scale_factor,
1689            ),
1690        });
1691        self.0.handle_drag_drop(input);
1692
1693        Ok(())
1694    }
1695
1696    fn DragLeave(&self) -> windows::core::Result<()> {
1697        let input = PlatformInput::FileDrop(FileDropEvent::Exited);
1698        self.0.handle_drag_drop(input);
1699
1700        Ok(())
1701    }
1702
1703    fn Drop(
1704        &self,
1705        _pdataobj: Option<&IDataObject>,
1706        _grfkeystate: MODIFIERKEYS_FLAGS,
1707        pt: &POINTL,
1708        _pdweffect: *mut DROPEFFECT,
1709    ) -> windows::core::Result<()> {
1710        let mut cursor_position = POINT { x: pt.x, y: pt.y };
1711        unsafe {
1712            ScreenToClient(self.0.hwnd, &mut cursor_position);
1713        }
1714        let scale_factor = self.0.scale_factor.get();
1715        let input = PlatformInput::FileDrop(FileDropEvent::Submit {
1716            position: logical_point(
1717                cursor_position.x as f32,
1718                cursor_position.y as f32,
1719                scale_factor,
1720            ),
1721        });
1722        self.0.handle_drag_drop(input);
1723
1724        Ok(())
1725    }
1726}
1727
1728#[derive(Debug)]
1729struct ClickState {
1730    button: MouseButton,
1731    last_click: Instant,
1732    last_position: Point<DevicePixels>,
1733    current_count: usize,
1734}
1735
1736impl ClickState {
1737    pub fn new() -> Self {
1738        ClickState {
1739            button: MouseButton::Left,
1740            last_click: Instant::now(),
1741            last_position: Point::default(),
1742            current_count: 0,
1743        }
1744    }
1745
1746    /// update self and return the needed click count
1747    pub fn update(&mut self, button: MouseButton, new_position: Point<DevicePixels>) -> usize {
1748        if self.button == button && self.is_double_click(new_position) {
1749            self.current_count += 1;
1750        } else {
1751            self.current_count = 1;
1752        }
1753        self.last_click = Instant::now();
1754        self.last_position = new_position;
1755        self.button = button;
1756
1757        self.current_count
1758    }
1759
1760    #[inline]
1761    fn is_double_click(&self, new_position: Point<DevicePixels>) -> bool {
1762        let diff = self.last_position - new_position;
1763
1764        self.last_click.elapsed() < DOUBLE_CLICK_INTERVAL
1765            && diff.x.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1766            && diff.y.0.abs() <= DOUBLE_CLICK_SPATIAL_TOLERANCE
1767    }
1768}
1769
1770fn register_wnd_class(icon_handle: HICON) -> PCWSTR {
1771    const CLASS_NAME: PCWSTR = w!("Zed::Window");
1772
1773    static ONCE: Once = Once::new();
1774    ONCE.call_once(|| {
1775        let wc = WNDCLASSW {
1776            lpfnWndProc: Some(wnd_proc),
1777            hIcon: icon_handle,
1778            lpszClassName: PCWSTR(CLASS_NAME.as_ptr()),
1779            style: CS_HREDRAW | CS_VREDRAW,
1780            hInstance: get_module_handle().into(),
1781            ..Default::default()
1782        };
1783        unsafe { RegisterClassW(&wc) };
1784    });
1785
1786    CLASS_NAME
1787}
1788
1789unsafe extern "system" fn wnd_proc(
1790    hwnd: HWND,
1791    msg: u32,
1792    wparam: WPARAM,
1793    lparam: LPARAM,
1794) -> LRESULT {
1795    if msg == WM_NCCREATE {
1796        let cs = lparam.0 as *const CREATESTRUCTW;
1797        let cs = unsafe { &*cs };
1798        let ctx = cs.lpCreateParams as *mut WindowCreateContext;
1799        let ctx = unsafe { &mut *ctx };
1800        let inner = Rc::new(WindowsWindowInner::new(
1801            hwnd,
1802            cs,
1803            ctx.platform_inner.clone(),
1804            ctx.handle,
1805            ctx.hide_title_bar,
1806            ctx.display.clone(),
1807            ctx.transparent,
1808        ));
1809        let weak = Box::new(Rc::downgrade(&inner));
1810        unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) };
1811        ctx.inner = Some(inner);
1812        return LRESULT(1);
1813    }
1814    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1815    if ptr.is_null() {
1816        return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) };
1817    }
1818    let inner = unsafe { &*ptr };
1819    let r = if let Some(inner) = inner.upgrade() {
1820        inner.handle_msg(msg, wparam, lparam)
1821    } else {
1822        unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }
1823    };
1824    if msg == WM_NCDESTROY {
1825        unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) };
1826        unsafe { drop(Box::from_raw(ptr)) };
1827    }
1828    r
1829}
1830
1831pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
1832    if hwnd == HWND(0) {
1833        return None;
1834    }
1835
1836    let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
1837    if !ptr.is_null() {
1838        let inner = unsafe { &*ptr };
1839        inner.upgrade()
1840    } else {
1841        None
1842    }
1843}
1844
1845fn basic_vkcode_to_string(code: u16, modifiers: Modifiers) -> Option<Keystroke> {
1846    match code {
1847        // VK_0 - VK_9
1848        48..=57 => Some(Keystroke {
1849            modifiers,
1850            key: format!("{}", code - VK_0.0),
1851            ime_key: None,
1852        }),
1853        // VK_A - VK_Z
1854        65..=90 => Some(Keystroke {
1855            modifiers,
1856            key: format!("{}", (b'a' + code as u8 - VK_A.0 as u8) as char),
1857            ime_key: None,
1858        }),
1859        // VK_F1 - VK_F24
1860        112..=135 => Some(Keystroke {
1861            modifiers,
1862            key: format!("f{}", code - VK_F1.0 + 1),
1863            ime_key: None,
1864        }),
1865        // OEM3: `/~, OEM_MINUS: -/_, OEM_PLUS: =/+, ...
1866        _ => {
1867            if let Some(key) = oemkey_vkcode_to_string(code) {
1868                Some(Keystroke {
1869                    modifiers,
1870                    key,
1871                    ime_key: None,
1872                })
1873            } else {
1874                None
1875            }
1876        }
1877    }
1878}
1879
1880fn oemkey_vkcode_to_string(code: u16) -> Option<String> {
1881    match code {
1882        186 => Some(";".to_string()), // VK_OEM_1
1883        187 => Some("=".to_string()), // VK_OEM_PLUS
1884        188 => Some(",".to_string()), // VK_OEM_COMMA
1885        189 => Some("-".to_string()), // VK_OEM_MINUS
1886        190 => Some(".".to_string()), // VK_OEM_PERIOD
1887        // https://kbdlayout.info/features/virtualkeys/VK_ABNT_C1
1888        191 | 193 => Some("/".to_string()), // VK_OEM_2 VK_ABNT_C1
1889        192 => Some("`".to_string()),       // VK_OEM_3
1890        219 => Some("[".to_string()),       // VK_OEM_4
1891        220 => Some("\\".to_string()),      // VK_OEM_5
1892        221 => Some("]".to_string()),       // VK_OEM_6
1893        222 => Some("'".to_string()),       // VK_OEM_7
1894        _ => None,
1895    }
1896}
1897
1898#[inline]
1899fn logical_size(physical_size: Size<DevicePixels>, scale_factor: f32) -> Size<Pixels> {
1900    Size {
1901        width: px(physical_size.width.0 as f32 / scale_factor),
1902        height: px(physical_size.height.0 as f32 / scale_factor),
1903    }
1904}
1905
1906#[inline]
1907fn logical_point(x: f32, y: f32, scale_factor: f32) -> Point<Pixels> {
1908    Point {
1909        x: px(x / scale_factor),
1910        y: px(y / scale_factor),
1911    }
1912}
1913
1914struct StyleAndBounds {
1915    style: WINDOW_STYLE,
1916    x: i32,
1917    y: i32,
1918    cx: i32,
1919    cy: i32,
1920}
1921
1922fn get_module_handle() -> HMODULE {
1923    unsafe {
1924        let mut h_module = std::mem::zeroed();
1925        GetModuleHandleExW(
1926            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
1927            windows::core::w!("ZedModule"),
1928            &mut h_module,
1929        )
1930        .expect("Unable to get module handle"); // this should never fail
1931
1932        h_module
1933    }
1934}
1935
1936// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew
1937const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF;
1938// https://learn.microsoft.com/en-us/windows/win32/controls/ttm-setdelaytime?redirectedfrom=MSDN
1939const DOUBLE_CLICK_INTERVAL: Duration = Duration::from_millis(500);
1940// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
1941const DOUBLE_CLICK_SPATIAL_TOLERANCE: i32 = 4;
1942const SIZE_MOVE_LOOP_TIMER_ID: usize = 1;
1943
1944#[cfg(test)]
1945mod tests {
1946    use super::ClickState;
1947    use crate::{point, DevicePixels, MouseButton};
1948    use std::time::Duration;
1949
1950    #[test]
1951    fn test_double_click_interval() {
1952        let mut state = ClickState::new();
1953        assert_eq!(
1954            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1955            1
1956        );
1957        assert_eq!(
1958            state.update(MouseButton::Right, point(DevicePixels(0), DevicePixels(0))),
1959            1
1960        );
1961        assert_eq!(
1962            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1963            1
1964        );
1965        assert_eq!(
1966            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1967            2
1968        );
1969        state.last_click -= Duration::from_millis(700);
1970        assert_eq!(
1971            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(0))),
1972            1
1973        );
1974    }
1975
1976    #[test]
1977    fn test_double_click_spatial_tolerance() {
1978        let mut state = ClickState::new();
1979        assert_eq!(
1980            state.update(MouseButton::Left, point(DevicePixels(-3), DevicePixels(0))),
1981            1
1982        );
1983        assert_eq!(
1984            state.update(MouseButton::Left, point(DevicePixels(0), DevicePixels(3))),
1985            2
1986        );
1987        assert_eq!(
1988            state.update(MouseButton::Right, point(DevicePixels(3), DevicePixels(2))),
1989            1
1990        );
1991        assert_eq!(
1992            state.update(MouseButton::Right, point(DevicePixels(10), DevicePixels(0))),
1993            1
1994        );
1995    }
1996}