window.rs

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