window.rs

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