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