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