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