diff --git a/Cargo.lock b/Cargo.lock index 41aa25e9b3..ce408e1017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4249,6 +4249,7 @@ dependencies = [ "wayland-backend", "wayland-client", "wayland-protocols", + "windows 0.53.0", "xcb", "xkbcommon", ] @@ -11732,6 +11733,35 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "windows" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +dependencies = [ + "windows-core", + "windows-targets 0.52.3", +] + +[[package]] +name = "windows-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result", + "windows-targets 0.52.3", +] + +[[package]] +name = "windows-result" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +dependencies = [ + "windows-targets 0.52.3", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -11756,7 +11786,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.3", ] [[package]] @@ -11791,17 +11821,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.3", + "windows_aarch64_msvc 0.52.3", + "windows_i686_gnu 0.52.3", + "windows_i686_msvc 0.52.3", + "windows_x86_64_gnu 0.52.3", + "windows_x86_64_gnullvm 0.52.3", + "windows_x86_64_msvc 0.52.3", ] [[package]] @@ -11818,9 +11848,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" @@ -11836,9 +11866,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" @@ -11854,9 +11884,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" @@ -11872,9 +11902,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" @@ -11890,9 +11920,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" @@ -11908,9 +11938,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" @@ -11926,9 +11956,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" [[package]] name = "winnow" diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index 70d4fe6e57..297c8a06ed 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -92,8 +92,16 @@ media.workspace = true metal = "0.25" objc = "0.2" -[target.'cfg(target_os = "linux")'.dependencies] +[target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies] flume = "0.11" +#TODO: use these on all platforms +blade-graphics.workspace = true +blade-macros.workspace = true +blade-rwh.workspace = true +bytemuck = "1" +cosmic-text = "0.10.0" + +[target.'cfg(target_os = "linux")'.dependencies] open = "5.0.1" ashpd = "0.7.0" xcb = { version = "1.3", features = ["as-raw-xcb-connection", "randr", "xkb"] } @@ -104,12 +112,15 @@ xkbcommon = { version = "0.7", features = ["wayland", "x11"] } as-raw-xcb-connection = "1" calloop = "0.12.4" calloop-wayland-source = "0.2.0" -#TODO: use these on all platforms -blade-graphics.workspace = true -blade-macros.workspace = true -blade-rwh.workspace = true -bytemuck = "1" -cosmic-text = "0.10.0" + +[target.'cfg(windows)'.dependencies.windows] +version = "0.53.0" +features = [ + "Win32_Graphics_Gdi", + "Win32_UI_WindowsAndMessaging", + "Win32_Security", + "Win32_System_Threading", +] [[example]] name = "hello_world" diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 346687bbf2..8222b88fe1 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -12,12 +12,15 @@ mod linux; #[cfg(target_os = "macos")] mod mac; -#[cfg(any(target_os = "linux", feature = "macos-blade"))] +#[cfg(any(target_os = "linux", target_os = "windows", feature = "macos-blade"))] mod blade; #[cfg(any(test, feature = "test-support"))] mod test; +#[cfg(target_os = "windows")] +mod windows; + use crate::{ Action, AnyWindowHandle, AsyncWindowContext, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, @@ -54,6 +57,8 @@ pub(crate) use mac::*; pub(crate) use test::*; use time::UtcOffset; pub use util::SemanticVersion; +#[cfg(target_os = "windows")] +pub(crate) use windows::*; #[cfg(target_os = "macos")] pub(crate) fn current_platform() -> Rc { @@ -66,7 +71,7 @@ pub(crate) fn current_platform() -> Rc { // todo("windows") #[cfg(target_os = "windows")] pub(crate) fn current_platform() -> Rc { - unimplemented!() + Rc::new(WindowsPlatform::new()) } pub(crate) trait Platform: 'static { diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs new file mode 100644 index 0000000000..4347b9169a --- /dev/null +++ b/crates/gpui/src/platform/windows.rs @@ -0,0 +1,13 @@ +mod dispatcher; +mod display; +mod platform; +mod text_system; +mod util; +mod window; + +pub(crate) use dispatcher::*; +pub(crate) use display::*; +pub(crate) use platform::*; +pub(crate) use text_system::*; +pub(crate) use util::*; +pub(crate) use window::*; diff --git a/crates/gpui/src/platform/windows/dispatcher.rs b/crates/gpui/src/platform/windows/dispatcher.rs new file mode 100644 index 0000000000..16fdcd2b85 --- /dev/null +++ b/crates/gpui/src/platform/windows/dispatcher.rs @@ -0,0 +1,159 @@ +use std::{ + cmp::Ordering, + thread::{current, JoinHandle, ThreadId}, + time::{Duration, Instant}, +}; + +use async_task::Runnable; +use collections::BinaryHeap; +use flume::{RecvTimeoutError, Sender}; +use parking::Parker; +use parking_lot::Mutex; +use windows::Win32::{Foundation::HANDLE, System::Threading::SetEvent}; + +use crate::{PlatformDispatcher, TaskLabel}; + +pub(crate) struct WindowsDispatcher { + background_sender: Sender<(Runnable, Option)>, + main_sender: Sender, + timer_sender: Sender<(Runnable, Duration)>, + background_threads: Vec>, + timer_thread: JoinHandle<()>, + parker: Mutex, + main_thread_id: ThreadId, + event: HANDLE, +} + +impl WindowsDispatcher { + pub(crate) fn new(main_sender: Sender, event: HANDLE) -> Self { + let parker = Mutex::new(Parker::new()); + let (background_sender, background_receiver) = + flume::unbounded::<(Runnable, Option)>(); + let background_threads = (0..std::thread::available_parallelism() + .map(|i| i.get()) + .unwrap_or(1)) + .map(|_| { + let receiver = background_receiver.clone(); + std::thread::spawn(move || { + for (runnable, label) in receiver { + if let Some(label) = label { + log::debug!("TaskLabel: {label:?}"); + } + runnable.run(); + } + }) + }) + .collect::>(); + let (timer_sender, timer_receiver) = flume::unbounded::<(Runnable, Duration)>(); + let timer_thread = std::thread::spawn(move || { + let mut runnables = BinaryHeap::::new(); + let mut timeout_dur = None; + loop { + let recv = if let Some(dur) = timeout_dur { + match timer_receiver.recv_timeout(dur) { + Ok(recv) => Some(recv), + Err(RecvTimeoutError::Timeout) => None, + Err(RecvTimeoutError::Disconnected) => break, + } + } else if let Ok(recv) = timer_receiver.recv() { + Some(recv) + } else { + break; + }; + let now = Instant::now(); + if let Some((runnable, dur)) = recv { + runnables.push(RunnableAfter { + runnable, + instant: now + dur, + }); + while let Ok((runnable, dur)) = timer_receiver.try_recv() { + runnables.push(RunnableAfter { + runnable, + instant: now + dur, + }) + } + } + while runnables.peek().is_some_and(|entry| entry.instant <= now) { + runnables.pop().unwrap().runnable.run(); + } + timeout_dur = runnables.peek().map(|entry| entry.instant - now); + } + }); + let main_thread_id = current().id(); + Self { + background_sender, + main_sender, + timer_sender, + background_threads, + timer_thread, + parker, + main_thread_id, + event, + } + } +} + +impl PlatformDispatcher for WindowsDispatcher { + fn is_main_thread(&self) -> bool { + current().id() == self.main_thread_id + } + + fn dispatch(&self, runnable: Runnable, label: Option) { + self.background_sender + .send((runnable, label)) + .inspect_err(|e| log::error!("Dispatch failed: {e}")) + .ok(); + } + + fn dispatch_on_main_thread(&self, runnable: Runnable) { + self.main_sender + .send(runnable) + .inspect_err(|e| log::error!("Dispatch failed: {e}")) + .ok(); + unsafe { SetEvent(self.event) }.ok(); + } + + fn dispatch_after(&self, duration: std::time::Duration, runnable: Runnable) { + self.timer_sender + .send((runnable, duration)) + .inspect_err(|e| log::error!("Dispatch failed: {e}")) + .ok(); + } + + fn tick(&self, _background_only: bool) -> bool { + false + } + + fn park(&self) { + self.parker.lock().park(); + } + + fn unparker(&self) -> parking::Unparker { + self.parker.lock().unparker() + } +} + +struct RunnableAfter { + runnable: Runnable, + instant: Instant, +} + +impl PartialEq for RunnableAfter { + fn eq(&self, other: &Self) -> bool { + self.instant == other.instant + } +} + +impl Eq for RunnableAfter {} + +impl Ord for RunnableAfter { + fn cmp(&self, other: &Self) -> Ordering { + self.instant.cmp(&other.instant).reverse() + } +} + +impl PartialOrd for RunnableAfter { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} diff --git a/crates/gpui/src/platform/windows/display.rs b/crates/gpui/src/platform/windows/display.rs new file mode 100644 index 0000000000..448433b6fb --- /dev/null +++ b/crates/gpui/src/platform/windows/display.rs @@ -0,0 +1,36 @@ +use anyhow::{anyhow, Result}; +use uuid::Uuid; + +use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point, Size}; + +#[derive(Debug)] +pub(crate) struct WindowsDisplay; + +impl WindowsDisplay { + pub(crate) fn new() -> Self { + Self + } +} + +impl PlatformDisplay for WindowsDisplay { + // todo!("windows") + fn id(&self) -> DisplayId { + DisplayId(1) + } + + // todo!("windows") + fn uuid(&self) -> Result { + Err(anyhow!("not implemented yet.")) + } + + // todo!("windows") + fn bounds(&self) -> Bounds { + Bounds::new( + Point::new(0.0.into(), 0.0.into()), + Size { + width: 1920.0.into(), + height: 1280.0.into(), + }, + ) + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs new file mode 100644 index 0000000000..e7751579c9 --- /dev/null +++ b/crates/gpui/src/platform/windows/platform.rs @@ -0,0 +1,317 @@ +// todo!("windows"): remove +#![allow(unused_variables)] + +use std::{ + cell::RefCell, + collections::HashSet, + path::{Path, PathBuf}, + rc::Rc, + sync::Arc, + time::Duration, +}; + +use anyhow::{anyhow, Result}; +use async_task::Runnable; +use futures::channel::oneshot::Receiver; +use parking_lot::Mutex; +use time::UtcOffset; +use util::SemanticVersion; +use windows::Win32::{ + Foundation::{CloseHandle, HANDLE, HWND}, + System::Threading::{CreateEventW, INFINITE}, + UI::WindowsAndMessaging::{ + DispatchMessageW, MsgWaitForMultipleObjects, PeekMessageW, PostQuitMessage, + TranslateMessage, MSG, PM_REMOVE, QS_ALLINPUT, WM_QUIT, + }, +}; + +use crate::{ + Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, + Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, + PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher, WindowsDisplay, + WindowsTextSystem, WindowsWindow, +}; + +pub(crate) struct WindowsPlatform { + inner: Rc, +} + +pub(crate) struct WindowsPlatformInner { + background_executor: BackgroundExecutor, + pub(crate) foreground_executor: ForegroundExecutor, + main_receiver: flume::Receiver, + text_system: Arc, + callbacks: Mutex, + pub(crate) window_handles: RefCell>, + pub(crate) event: HANDLE, +} + +impl Drop for WindowsPlatformInner { + fn drop(&mut self) { + unsafe { CloseHandle(self.event) }.ok(); + } +} + +#[derive(Default)] +struct Callbacks { + open_urls: Option)>>, + become_active: Option>, + resign_active: Option>, + quit: Option>, + reopen: Option>, + event: Option bool>>, + app_menu_action: Option>, + will_open_app_menu: Option>, + validate_app_menu_command: Option bool>>, +} + +impl WindowsPlatform { + pub(crate) fn new() -> Self { + let (main_sender, main_receiver) = flume::unbounded::(); + let event = unsafe { CreateEventW(None, false, false, None) }.unwrap(); + let dispatcher = Arc::new(WindowsDispatcher::new(main_sender, event)); + let background_executor = BackgroundExecutor::new(dispatcher.clone()); + let foreground_executor = ForegroundExecutor::new(dispatcher); + let text_system = Arc::new(WindowsTextSystem::new()); + let callbacks = Mutex::new(Callbacks::default()); + let window_handles = RefCell::new(HashSet::new()); + let inner = Rc::new(WindowsPlatformInner { + background_executor, + foreground_executor, + main_receiver, + text_system, + callbacks, + window_handles, + event, + }); + Self { inner } + } +} + +impl Platform for WindowsPlatform { + fn background_executor(&self) -> BackgroundExecutor { + self.inner.background_executor.clone() + } + + fn foreground_executor(&self) -> ForegroundExecutor { + self.inner.foreground_executor.clone() + } + + fn text_system(&self) -> Arc { + self.inner.text_system.clone() + } + + fn run(&self, on_finish_launching: Box) { + on_finish_launching(); + 'a: loop { + unsafe { + MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT) + }; + let mut msg = MSG::default(); + while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool() { + if msg.message == WM_QUIT { + break 'a; + } + unsafe { TranslateMessage(&msg) }; + unsafe { DispatchMessageW(&msg) }; + } + while let Ok(runnable) = self.inner.main_receiver.try_recv() { + runnable.run(); + } + } + let mut callbacks = self.inner.callbacks.lock(); + if let Some(callback) = callbacks.quit.as_mut() { + callback() + } + } + + fn quit(&self) { + self.foreground_executor() + .spawn(async { unsafe { PostQuitMessage(0) } }) + .detach(); + } + + // todo!("windows") + fn restart(&self) { + unimplemented!() + } + + // todo!("windows") + fn activate(&self, ignoring_other_apps: bool) {} + + // todo!("windows") + fn hide(&self) { + unimplemented!() + } + + // todo!("windows") + fn hide_other_apps(&self) { + unimplemented!() + } + + // todo!("windows") + fn unhide_other_apps(&self) { + unimplemented!() + } + + // todo!("windows") + fn displays(&self) -> Vec> { + vec![Rc::new(WindowsDisplay::new())] + } + + // todo!("windows") + fn display(&self, id: crate::DisplayId) -> Option> { + Some(Rc::new(WindowsDisplay::new())) + } + + // todo!("windows") + fn active_window(&self) -> Option { + unimplemented!() + } + + fn open_window( + &self, + handle: AnyWindowHandle, + options: WindowOptions, + ) -> Box { + Box::new(WindowsWindow::new(self.inner.clone(), handle, options)) + } + + // todo!("windows") + fn window_appearance(&self) -> WindowAppearance { + WindowAppearance::Dark + } + + // todo!("windows") + fn open_url(&self, url: &str) { + // todo!("windows") + } + + // todo!("windows") + fn on_open_urls(&self, callback: Box)>) { + self.inner.callbacks.lock().open_urls = Some(callback); + } + + // todo!("windows") + fn prompt_for_paths(&self, options: PathPromptOptions) -> Receiver>> { + unimplemented!() + } + + // todo!("windows") + fn prompt_for_new_path(&self, directory: &Path) -> Receiver> { + unimplemented!() + } + + // todo!("windows") + fn reveal_path(&self, path: &Path) { + unimplemented!() + } + + fn on_become_active(&self, callback: Box) { + self.inner.callbacks.lock().become_active = Some(callback); + } + + fn on_resign_active(&self, callback: Box) { + self.inner.callbacks.lock().resign_active = Some(callback); + } + + fn on_quit(&self, callback: Box) { + self.inner.callbacks.lock().quit = Some(callback); + } + + fn on_reopen(&self, callback: Box) { + self.inner.callbacks.lock().reopen = Some(callback); + } + + fn on_event(&self, callback: Box bool>) { + self.inner.callbacks.lock().event = Some(callback); + } + + // todo!("windows") + fn set_menus(&self, menus: Vec, keymap: &Keymap) {} + + fn on_app_menu_action(&self, callback: Box) { + self.inner.callbacks.lock().app_menu_action = Some(callback); + } + + fn on_will_open_app_menu(&self, callback: Box) { + self.inner.callbacks.lock().will_open_app_menu = Some(callback); + } + + fn on_validate_app_menu_command(&self, callback: Box bool>) { + self.inner.callbacks.lock().validate_app_menu_command = Some(callback); + } + + fn os_name(&self) -> &'static str { + "Windows" + } + + fn os_version(&self) -> Result { + Ok(SemanticVersion { + major: 1, + minor: 0, + patch: 0, + }) + } + + fn app_version(&self) -> Result { + Ok(SemanticVersion { + major: 1, + minor: 0, + patch: 0, + }) + } + + // todo!("windows") + fn app_path(&self) -> Result { + Err(anyhow!("not yet implemented")) + } + + // todo!("windows") + fn local_timezone(&self) -> UtcOffset { + UtcOffset::from_hms(9, 0, 0).unwrap() + } + + // todo!("windows") + fn double_click_interval(&self) -> Duration { + Duration::from_millis(100) + } + + // todo!("windows") + fn path_for_auxiliary_executable(&self, name: &str) -> Result { + Err(anyhow!("not yet implemented")) + } + + // todo!("windows") + fn set_cursor_style(&self, style: CursorStyle) {} + + // todo!("windows") + fn should_auto_hide_scrollbars(&self) -> bool { + false + } + + // todo!("windows") + fn write_to_clipboard(&self, item: ClipboardItem) { + unimplemented!() + } + + // todo!("windows") + fn read_from_clipboard(&self) -> Option { + unimplemented!() + } + + // todo!("windows") + fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { + Task::Ready(Some(Err(anyhow!("not implemented yet.")))) + } + + // todo!("windows") + fn read_credentials(&self, url: &str) -> Task)>>> { + Task::Ready(Some(Err(anyhow!("not implemented yet.")))) + } + + // todo!("windows") + fn delete_credentials(&self, url: &str) -> Task> { + Task::Ready(Some(Err(anyhow!("not implemented yet.")))) + } +} diff --git a/crates/gpui/src/platform/windows/text_system.rs b/crates/gpui/src/platform/windows/text_system.rs new file mode 100644 index 0000000000..411e1a8d28 --- /dev/null +++ b/crates/gpui/src/platform/windows/text_system.rs @@ -0,0 +1,440 @@ +use crate::{ + point, size, Bounds, DevicePixels, Font, FontFeatures, FontId, FontMetrics, FontRun, FontStyle, + FontWeight, GlyphId, LineLayout, Pixels, PlatformTextSystem, Point, RenderGlyphParams, + ShapedGlyph, SharedString, Size, +}; +use anyhow::{anyhow, Context, Ok, Result}; +use collections::HashMap; +use cosmic_text::{ + fontdb::Query, Attrs, AttrsList, BufferLine, CacheKey, Family, Font as CosmicTextFont, + FontSystem, SwashCache, +}; +use parking_lot::{RwLock, RwLockUpgradableReadGuard}; +use pathfinder_geometry::{ + rect::{RectF, RectI}, + vector::{Vector2F, Vector2I}, +}; +use smallvec::SmallVec; +use std::{borrow::Cow, sync::Arc}; + +pub(crate) struct WindowsTextSystem(RwLock); + +struct WindowsTextSystemState { + swash_cache: SwashCache, + font_system: FontSystem, + fonts: Vec>, + font_selections: HashMap, + font_ids_by_family_name: HashMap>, + postscript_names_by_font_id: HashMap, +} + +impl WindowsTextSystem { + pub(crate) fn new() -> Self { + let mut font_system = FontSystem::new(); + + // todo!("windows") make font loading non-blocking + font_system.db_mut().load_system_fonts(); + + Self(RwLock::new(WindowsTextSystemState { + font_system, + swash_cache: SwashCache::new(), + fonts: Vec::new(), + font_selections: HashMap::default(), + // font_ids_by_postscript_name: HashMap::default(), + font_ids_by_family_name: HashMap::default(), + postscript_names_by_font_id: HashMap::default(), + })) + } +} + +impl Default for WindowsTextSystem { + fn default() -> Self { + Self::new() + } +} + +#[allow(unused)] +impl PlatformTextSystem for WindowsTextSystem { + fn add_fonts(&self, fonts: Vec>) -> Result<()> { + self.0.write().add_fonts(fonts) + } + + // todo!("windows") ensure that this integrates with platform font loading + // do we need to do more than call load_system_fonts()? + fn all_font_names(&self) -> Vec { + self.0 + .read() + .font_system + .db() + .faces() + .map(|face| face.post_script_name.clone()) + .collect() + } + + // todo!("windows") + fn all_font_families(&self) -> Vec { + Vec::new() + } + + fn font_id(&self, font: &Font) -> Result { + // todo!("windows"): Do we need to use CosmicText's Font APIs? Can we consolidate this to use font_kit? + let lock = self.0.upgradable_read(); + if let Some(font_id) = lock.font_selections.get(font) { + Ok(*font_id) + } else { + let mut lock = RwLockUpgradableReadGuard::upgrade(lock); + let candidates = if let Some(font_ids) = lock.font_ids_by_family_name.get(&font.family) + { + font_ids.as_slice() + } else { + let font_ids = lock.load_family(&font.family, font.features)?; + lock.font_ids_by_family_name + .insert(font.family.clone(), font_ids); + lock.font_ids_by_family_name[&font.family].as_ref() + }; + + let id = lock + .font_system + .db() + .query(&Query { + families: &[Family::Name(&font.family)], + weight: font.weight.into(), + style: font.style.into(), + stretch: Default::default(), + }) + .context("no font")?; + + let font_id = if let Some(font_id) = lock.fonts.iter().position(|font| font.id() == id) + { + FontId(font_id) + } else { + // Font isn't in fonts so add it there, this is because we query all the fonts in the db + // and maybe we haven't loaded it yet + let font_id = FontId(lock.fonts.len()); + let font = lock.font_system.get_font(id).unwrap(); + lock.fonts.push(font); + font_id + }; + + lock.font_selections.insert(font.clone(), font_id); + Ok(font_id) + } + } + + fn font_metrics(&self, font_id: FontId) -> FontMetrics { + let metrics = self.0.read().fonts[font_id.0].as_swash().metrics(&[]); + + FontMetrics { + units_per_em: metrics.units_per_em as u32, + ascent: metrics.ascent, + descent: -metrics.descent, // todo!("windows") confirm this is correct + line_gap: metrics.leading, + underline_position: metrics.underline_offset, + underline_thickness: metrics.stroke_size, + cap_height: metrics.cap_height, + x_height: metrics.x_height, + // todo!("windows"): Compute this correctly + bounding_box: Bounds { + origin: point(0.0, 0.0), + size: size(metrics.max_width, metrics.ascent + metrics.descent), + }, + } + } + + fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + let lock = self.0.read(); + let metrics = lock.fonts[font_id.0].as_swash().metrics(&[]); + let glyph_metrics = lock.fonts[font_id.0].as_swash().glyph_metrics(&[]); + let glyph_id = glyph_id.0 as u16; + // todo!("windows"): Compute this correctly + // see https://github.com/servo/font-kit/blob/master/src/loaders/freetype.rs#L614-L620 + Ok(Bounds { + origin: point(0.0, 0.0), + size: size( + glyph_metrics.advance_width(glyph_id), + glyph_metrics.advance_height(glyph_id), + ), + }) + } + + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + self.0.read().advance(font_id, glyph_id) + } + + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { + self.0.read().glyph_for_char(font_id, ch) + } + + fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result> { + self.0.write().raster_bounds(params) + } + + fn rasterize_glyph( + &self, + params: &RenderGlyphParams, + raster_bounds: Bounds, + ) -> Result<(Size, Vec)> { + self.0.write().rasterize_glyph(params, raster_bounds) + } + + fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout { + self.0.write().layout_line(text, font_size, runs) + } + + // todo!("windows") Confirm that this has been superseded by the LineWrapper + fn wrap_line( + &self, + text: &str, + font_id: FontId, + font_size: Pixels, + width: Pixels, + ) -> Vec { + unimplemented!() + } +} + +impl WindowsTextSystemState { + #[profiling::function] + fn add_fonts(&mut self, fonts: Vec>) -> Result<()> { + let db = self.font_system.db_mut(); + for bytes in fonts { + match bytes { + Cow::Borrowed(embedded_font) => { + db.load_font_data(embedded_font.to_vec()); + } + Cow::Owned(bytes) => { + db.load_font_data(bytes); + } + } + } + Ok(()) + } + + #[profiling::function] + fn load_family( + &mut self, + name: &SharedString, + _features: FontFeatures, + ) -> Result> { + let mut font_ids = SmallVec::new(); + let family = self + .font_system + .get_font_matches(Attrs::new().family(cosmic_text::Family::Name(name))); + for font in family.as_ref() { + let font = self.font_system.get_font(*font).unwrap(); + if font.as_swash().charmap().map('m') == 0 { + self.font_system.db_mut().remove_face(font.id()); + continue; + }; + + let font_id = FontId(self.fonts.len()); + font_ids.push(font_id); + self.fonts.push(font); + } + Ok(font_ids) + } + + fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result> { + let width = self.fonts[font_id.0] + .as_swash() + .glyph_metrics(&[]) + .advance_width(glyph_id.0 as u16); + let height = self.fonts[font_id.0] + .as_swash() + .glyph_metrics(&[]) + .advance_height(glyph_id.0 as u16); + Ok(Size { width, height }) + } + + fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option { + let glyph_id = self.fonts[font_id.0].as_swash().charmap().map(ch); + if glyph_id == 0 { + None + } else { + Some(GlyphId(glyph_id.into())) + } + } + + fn is_emoji(&self, font_id: FontId) -> bool { + // todo!("windows"): implement this correctly + self.postscript_names_by_font_id + .get(&font_id) + .map_or(false, |postscript_name| { + postscript_name == "AppleColorEmoji" + }) + } + + // todo!("windows") both raster functions have problems because I am not sure this is the correct mapping from cosmic text to gpui system + fn raster_bounds(&mut self, params: &RenderGlyphParams) -> Result> { + let font = &self.fonts[params.font_id.0]; + let font_system = &mut self.font_system; + let image = self + .swash_cache + .get_image( + font_system, + CacheKey::new( + font.id(), + params.glyph_id.0 as u16, + (params.font_size * params.scale_factor).into(), + (0.0, 0.0), + ) + .0, + ) + .clone() + .unwrap(); + Ok(Bounds { + origin: point(image.placement.left.into(), (-image.placement.top).into()), + size: size(image.placement.width.into(), image.placement.height.into()), + }) + } + + #[profiling::function] + fn rasterize_glyph( + &mut self, + params: &RenderGlyphParams, + glyph_bounds: Bounds, + ) -> Result<(Size, Vec)> { + if glyph_bounds.size.width.0 == 0 || glyph_bounds.size.height.0 == 0 { + Err(anyhow!("glyph bounds are empty")) + } else { + // todo!("windows") handle subpixel variants + let bitmap_size = glyph_bounds.size; + let font = &self.fonts[params.font_id.0]; + let font_system = &mut self.font_system; + let image = self + .swash_cache + .get_image( + font_system, + CacheKey::new( + font.id(), + params.glyph_id.0 as u16, + (params.font_size * params.scale_factor).into(), + (0.0, 0.0), + ) + .0, + ) + .clone() + .unwrap(); + + Ok((bitmap_size, image.data)) + } + } + + // todo!("windows") This is all a quick first pass, maybe we should be using cosmic_text::Buffer + #[profiling::function] + fn layout_line(&mut self, text: &str, font_size: Pixels, font_runs: &[FontRun]) -> LineLayout { + let mut attrs_list = AttrsList::new(Attrs::new()); + let mut offs = 0; + for run in font_runs { + // todo!("windows") We need to check we are doing utf properly + let font = &self.fonts[run.font_id.0]; + let font = self.font_system.db().face(font.id()).unwrap(); + attrs_list.add_span( + offs..offs + run.len, + Attrs::new() + .family(Family::Name(&font.families.first().unwrap().0)) + .stretch(font.stretch) + .style(font.style) + .weight(font.weight), + ); + offs += run.len; + } + let mut line = BufferLine::new(text, attrs_list, cosmic_text::Shaping::Advanced); + let layout = line.layout( + &mut self.font_system, + font_size.0, + f32::MAX, // todo!("windows") we don't have a width cause this should technically not be wrapped I believe + cosmic_text::Wrap::None, + ); + let mut runs = Vec::new(); + // todo!("windows") what I think can happen is layout returns possibly multiple lines which means we should be probably working with it higher up in the text rendering + let layout = layout.first().unwrap(); + for glyph in &layout.glyphs { + let font_id = glyph.font_id; + let font_id = FontId( + self.fonts + .iter() + .position(|font| font.id() == font_id) + .unwrap(), + ); + let mut glyphs = SmallVec::new(); + // todo!("windows") this is definitely wrong, each glyph in glyphs from cosmic-text is a cluster with one glyph, ShapedRun takes a run of glyphs with the same font and direction + glyphs.push(ShapedGlyph { + id: GlyphId(glyph.glyph_id as u32), + position: point((glyph.x).into(), glyph.y.into()), + index: glyph.start, + is_emoji: self.is_emoji(font_id), + }); + runs.push(crate::ShapedRun { font_id, glyphs }); + } + LineLayout { + font_size, + width: layout.w.into(), + ascent: layout.max_ascent.into(), + descent: layout.max_descent.into(), + runs, + len: text.len(), + } + } +} + +impl From for Bounds { + fn from(rect: RectF) -> Self { + Bounds { + origin: point(rect.origin_x(), rect.origin_y()), + size: size(rect.width(), rect.height()), + } + } +} + +impl From for Bounds { + fn from(rect: RectI) -> Self { + Bounds { + origin: point(DevicePixels(rect.origin_x()), DevicePixels(rect.origin_y())), + size: size(DevicePixels(rect.width()), DevicePixels(rect.height())), + } + } +} + +impl From for Size { + fn from(value: Vector2I) -> Self { + size(value.x().into(), value.y().into()) + } +} + +impl From for Bounds { + fn from(rect: RectI) -> Self { + Bounds { + origin: point(rect.origin_x(), rect.origin_y()), + size: size(rect.width(), rect.height()), + } + } +} + +impl From> for Vector2I { + fn from(size: Point) -> Self { + Vector2I::new(size.x as i32, size.y as i32) + } +} + +impl From for Size { + fn from(vec: Vector2F) -> Self { + size(vec.x(), vec.y()) + } +} + +impl From for cosmic_text::Weight { + fn from(value: FontWeight) -> Self { + cosmic_text::Weight(value.0 as u16) + } +} + +impl From for cosmic_text::Style { + fn from(style: FontStyle) -> Self { + match style { + FontStyle::Normal => cosmic_text::Style::Normal, + FontStyle::Italic => cosmic_text::Style::Italic, + FontStyle::Oblique => cosmic_text::Style::Oblique, + } + } +} diff --git a/crates/gpui/src/platform/windows/util.rs b/crates/gpui/src/platform/windows/util.rs new file mode 100644 index 0000000000..bba6f132ab --- /dev/null +++ b/crates/gpui/src/platform/windows/util.rs @@ -0,0 +1,26 @@ +use windows::Win32::Foundation::{LPARAM, WPARAM}; + +pub(crate) trait HiLoWord { + fn hiword(&self) -> u16; + fn loword(&self) -> u16; +} + +impl HiLoWord for WPARAM { + fn hiword(&self) -> u16 { + ((self.0 >> 16) & 0xFFFF) as u16 + } + + fn loword(&self) -> u16 { + (self.0 & 0xFFFF) as u16 + } +} + +impl HiLoWord for LPARAM { + fn hiword(&self) -> u16 { + ((self.0 >> 16) & 0xFFFF) as u16 + } + + fn loword(&self) -> u16 { + (self.0 & 0xFFFF) as u16 + } +} diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs new file mode 100644 index 0000000000..f43ac7cec8 --- /dev/null +++ b/crates/gpui/src/platform/windows/window.rs @@ -0,0 +1,535 @@ +#![deny(unsafe_op_in_unsafe_fn)] +// todo!("windows"): remove +#![allow(unused_variables)] + +use std::{ + any::Any, + cell::{Cell, RefCell}, + ffi::c_void, + num::NonZeroIsize, + rc::{Rc, Weak}, + sync::{Arc, Once}, +}; + +use blade_graphics as gpu; +use futures::channel::oneshot::Receiver; +use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; +use windows::{ + core::{w, HSTRING, PCWSTR}, + Win32::{ + Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM}, + UI::WindowsAndMessaging::{ + CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, + RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW, + CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WINDOW_EX_STYLE, + WINDOW_LONG_PTR_INDEX, WM_CLOSE, WM_DESTROY, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, + WM_PAINT, WM_SIZE, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE, + }, + }, +}; + +use crate::{ + platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, Modifiers, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, + Point, PromptLevel, Scene, Size, WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay, + WindowsPlatformInner, +}; + +struct WindowsWindowInner { + hwnd: HWND, + origin: Cell>, + size: Cell>, + mouse_position: Cell>, + input_handler: Cell>, + renderer: RefCell, + callbacks: RefCell, + platform_inner: Rc, + handle: AnyWindowHandle, +} + +impl WindowsWindowInner { + fn new( + hwnd: HWND, + cs: &CREATESTRUCTW, + platform_inner: Rc, + handle: AnyWindowHandle, + ) -> Self { + let origin = Cell::new(Point::new((cs.x as f64).into(), (cs.y as f64).into())); + let size = Cell::new(Size { + width: (cs.cx as f64).into(), + height: (cs.cy as f64).into(), + }); + let mouse_position = Cell::new(Point::default()); + let input_handler = Cell::new(None); + struct RawWindow { + hwnd: *mut c_void, + } + unsafe impl blade_rwh::HasRawWindowHandle for RawWindow { + fn raw_window_handle(&self) -> blade_rwh::RawWindowHandle { + let mut handle = blade_rwh::Win32WindowHandle::empty(); + handle.hwnd = self.hwnd; + handle.into() + } + } + unsafe impl blade_rwh::HasRawDisplayHandle for RawWindow { + fn raw_display_handle(&self) -> blade_rwh::RawDisplayHandle { + blade_rwh::WindowsDisplayHandle::empty().into() + } + } + let raw = RawWindow { hwnd: hwnd.0 as _ }; + let gpu = Arc::new( + unsafe { + gpu::Context::init_windowed( + &raw, + gpu::ContextDesc { + validation: false, + capture: false, + }, + ) + } + .unwrap(), + ); + let extent = gpu::Extent { + width: 1, + height: 1, + depth: 1, + }; + let renderer = RefCell::new(BladeRenderer::new(gpu, extent)); + let callbacks = RefCell::new(Callbacks::default()); + Self { + hwnd, + origin, + size, + mouse_position, + input_handler, + renderer, + callbacks, + platform_inner, + handle, + } + } + + fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0); + match msg { + WM_MOVE => { + let x = lparam.loword() as f64; + let y = lparam.hiword() as f64; + self.origin.set(Point::new(x.into(), y.into())); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.moved.as_mut() { + callback() + } + } + WM_SIZE => { + // todo!("windows"): handle maximized or minimized + let width = lparam.loword().max(1) as f64; + let height = lparam.hiword().max(1) as f64; + self.renderer + .borrow_mut() + .update_drawable_size(Size { width, height }); + let width = width.into(); + let height = height.into(); + self.size.set(Size { width, height }); + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.resize.as_mut() { + callback( + Size { + width: Pixels(width.0), + height: Pixels(height.0), + }, + 1.0, + ) + } + } + WM_PAINT => { + let mut callbacks = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.request_frame.as_mut() { + callback() + } + } + WM_CLOSE => { + let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.should_close.as_mut() { + if callback() { + return LRESULT(0); + } + } + drop(callbacks); + return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }; + } + WM_DESTROY => { + let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut(); + if let Some(callback) = callbacks.close.take() { + callback() + } + let mut window_handles = self.platform_inner.window_handles.borrow_mut(); + window_handles.remove(&self.handle); + if window_handles.is_empty() { + self.platform_inner + .foreground_executor + .spawn(async { + unsafe { PostQuitMessage(0) }; + }) + .detach(); + } + return LRESULT(1); + } + _ => return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }, + } + LRESULT(0) + } +} + +#[derive(Default)] +struct Callbacks { + request_frame: Option>, + input: Option bool>>, + active_status_change: Option>, + resize: Option, f32)>>, + fullscreen: Option>, + moved: Option>, + should_close: Option bool>>, + close: Option>, + appearance_changed: Option>, +} + +pub(crate) struct WindowsWindow { + inner: Rc, +} + +struct WindowCreateContext { + inner: Option>, + platform_inner: Rc, + handle: AnyWindowHandle, +} + +impl WindowsWindow { + pub(crate) fn new( + platform_inner: Rc, + handle: AnyWindowHandle, + options: WindowOptions, + ) -> Self { + let dwexstyle = WINDOW_EX_STYLE::default(); + let classname = register_wnd_class(); + let windowname = HSTRING::from( + options + .titlebar + .as_ref() + .and_then(|titlebar| titlebar.title.as_ref()) + .map(|title| title.as_ref()) + .unwrap_or(""), + ); + let dwstyle = WS_OVERLAPPEDWINDOW & !WS_VISIBLE; + let mut x = CW_USEDEFAULT; + let mut y = CW_USEDEFAULT; + let mut nwidth = CW_USEDEFAULT; + let mut nheight = CW_USEDEFAULT; + match options.bounds { + WindowBounds::Fullscreen => {} + WindowBounds::Maximized => {} + WindowBounds::Fixed(bounds) => { + x = bounds.origin.x.0 as i32; + y = bounds.origin.y.0 as i32; + nwidth = bounds.size.width.0 as i32; + nheight = bounds.size.height.0 as i32; + } + }; + let hwndparent = HWND::default(); + let hmenu = HMENU::default(); + let hinstance = HINSTANCE::default(); + let mut context = WindowCreateContext { + inner: None, + platform_inner: platform_inner.clone(), + handle, + }; + let lpparam = Some(&context as *const _ as *const _); + unsafe { + CreateWindowExW( + dwexstyle, + classname, + &windowname, + dwstyle, + x, + y, + nwidth, + nheight, + hwndparent, + hmenu, + hinstance, + lpparam, + ) + }; + let wnd = Self { + inner: context.inner.unwrap(), + }; + platform_inner.window_handles.borrow_mut().insert(handle); + match options.bounds { + WindowBounds::Fullscreen => wnd.toggle_full_screen(), + WindowBounds::Maximized => wnd.maximize(), + WindowBounds::Fixed(_) => {} + } + unsafe { ShowWindow(wnd.inner.hwnd, SW_SHOW) }; + wnd + } + + fn maximize(&self) { + unsafe { ShowWindow(self.inner.hwnd, SW_MAXIMIZE) }; + } +} + +impl HasWindowHandle for WindowsWindow { + fn window_handle( + &self, + ) -> Result, raw_window_handle::HandleError> { + let raw = raw_window_handle::Win32WindowHandle::new(unsafe { + NonZeroIsize::new_unchecked(self.inner.hwnd.0) + }) + .into(); + Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(raw) }) + } +} + +// todo!("windows") +impl HasDisplayHandle for WindowsWindow { + fn display_handle( + &self, + ) -> Result, raw_window_handle::HandleError> { + unimplemented!() + } +} + +impl PlatformWindow for WindowsWindow { + fn bounds(&self) -> WindowBounds { + WindowBounds::Fixed(Bounds { + origin: self.inner.origin.get(), + size: self.inner.size.get(), + }) + } + + // todo!("windows") + fn content_size(&self) -> Size { + let size = self.inner.size.get(); + Size { + width: size.width.0.into(), + height: size.height.0.into(), + } + } + + // todo!("windows") + fn scale_factor(&self) -> f32 { + 1.0 + } + + // todo!("windows") + fn titlebar_height(&self) -> Pixels { + 20.0.into() + } + + // todo!("windows") + fn appearance(&self) -> WindowAppearance { + WindowAppearance::Dark + } + + // todo!("windows") + fn display(&self) -> Rc { + Rc::new(WindowsDisplay::new()) + } + + fn mouse_position(&self) -> Point { + self.inner.mouse_position.get() + } + + // todo!("windows") + fn modifiers(&self) -> Modifiers { + Modifiers::none() + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + // todo!("windows") + fn set_input_handler(&mut self, input_handler: PlatformInputHandler) { + self.inner.input_handler.set(Some(input_handler)); + } + + // todo!("windows") + fn take_input_handler(&mut self) -> Option { + self.inner.input_handler.take() + } + + // todo!("windows") + fn prompt( + &self, + level: PromptLevel, + msg: &str, + detail: Option<&str>, + answers: &[&str], + ) -> Receiver { + unimplemented!() + } + + // todo!("windows") + fn activate(&self) {} + + // todo!("windows") + fn set_title(&mut self, title: &str) { + unsafe { SetWindowTextW(self.inner.hwnd, &HSTRING::from(title)) } + .inspect_err(|e| log::error!("Set title failed: {e}")) + .ok(); + } + + // todo!("windows") + fn set_edited(&mut self, edited: bool) {} + + // todo!("windows") + fn show_character_palette(&self) {} + + // todo!("windows") + fn minimize(&self) {} + + // todo!("windows") + fn zoom(&self) {} + + // todo!("windows") + fn toggle_full_screen(&self) {} + + // todo!("windows") + fn on_request_frame(&self, callback: Box) { + self.inner.callbacks.borrow_mut().request_frame = Some(callback); + } + + // todo!("windows") + fn on_input(&self, callback: Box bool>) { + self.inner.callbacks.borrow_mut().input = Some(callback); + } + + // todo!("windows") + fn on_active_status_change(&self, callback: Box) { + self.inner.callbacks.borrow_mut().active_status_change = Some(callback); + } + + // todo!("windows") + fn on_resize(&self, callback: Box, f32)>) { + self.inner.callbacks.borrow_mut().resize = Some(callback); + } + + // todo!("windows") + fn on_fullscreen(&self, callback: Box) { + self.inner.callbacks.borrow_mut().fullscreen = Some(callback); + } + + // todo!("windows") + fn on_moved(&self, callback: Box) { + self.inner.callbacks.borrow_mut().moved = Some(callback); + } + + // todo!("windows") + fn on_should_close(&self, callback: Box bool>) { + self.inner.callbacks.borrow_mut().should_close = Some(callback); + } + + // todo!("windows") + fn on_close(&self, callback: Box) { + self.inner.callbacks.borrow_mut().close = Some(callback); + } + + // todo!("windows") + fn on_appearance_changed(&self, callback: Box) { + self.inner.callbacks.borrow_mut().appearance_changed = Some(callback); + } + + // todo!("windows") + fn is_topmost_for_position(&self, position: Point) -> bool { + true + } + + // todo!("windows") + fn draw(&self, scene: &Scene) { + self.inner.renderer.borrow_mut().draw(scene) + } + + // todo!("windows") + fn sprite_atlas(&self) -> Arc { + self.inner.renderer.borrow().sprite_atlas().clone() + } +} + +fn register_wnd_class() -> PCWSTR { + const CLASS_NAME: PCWSTR = w!("Zed::Window"); + + static ONCE: Once = Once::new(); + ONCE.call_once(|| { + let wc = WNDCLASSW { + lpfnWndProc: Some(wnd_proc), + hCursor: unsafe { LoadCursorW(None, IDC_ARROW).ok().unwrap() }, + lpszClassName: PCWSTR(CLASS_NAME.as_ptr()), + ..Default::default() + }; + unsafe { RegisterClassW(&wc) }; + }); + + CLASS_NAME +} + +unsafe extern "system" fn wnd_proc( + hwnd: HWND, + msg: u32, + wparam: WPARAM, + lparam: LPARAM, +) -> LRESULT { + if msg == WM_NCCREATE { + let cs = lparam.0 as *const CREATESTRUCTW; + let cs = unsafe { &*cs }; + let ctx = cs.lpCreateParams as *mut WindowCreateContext; + let ctx = unsafe { &mut *ctx }; + let inner = Rc::new(WindowsWindowInner::new( + hwnd, + cs, + ctx.platform_inner.clone(), + ctx.handle, + )); + let weak = Box::new(Rc::downgrade(&inner)); + unsafe { set_window_long(hwnd, GWLP_USERDATA, Box::into_raw(weak) as isize) }; + ctx.inner = Some(inner); + return LRESULT(1); + } + let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak; + if ptr.is_null() { + return unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) }; + } + let inner = unsafe { &*ptr }; + let r = if let Some(inner) = inner.upgrade() { + inner.handle_msg(msg, wparam, lparam) + } else { + unsafe { DefWindowProcW(hwnd, msg, wparam, lparam) } + }; + if msg == WM_NCDESTROY { + unsafe { set_window_long(hwnd, GWLP_USERDATA, 0) }; + unsafe { std::mem::drop(Box::from_raw(ptr)) }; + } + r +} + +unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize { + #[cfg(target_pointer_width = "64")] + unsafe { + GetWindowLongPtrW(hwnd, nindex) + } + #[cfg(target_pointer_width = "32")] + unsafe { + GetWindowLongW(hwnd, nindex) as isize + } +} + +unsafe fn set_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX, dwnewlong: isize) -> isize { + #[cfg(target_pointer_width = "64")] + unsafe { + SetWindowLongPtrW(hwnd, nindex, dwnewlong) + } + #[cfg(target_pointer_width = "32")] + unsafe { + SetWindowLongW(hwnd, nindex, dwnewlong as i32) as isize + } +} diff --git a/crates/zed/build.rs b/crates/zed/build.rs index 6905d492e1..daea199e21 100644 --- a/crates/zed/build.rs +++ b/crates/zed/build.rs @@ -43,4 +43,9 @@ fn main() { } } } + + // todo!("windows"): This is to avoid stack overflow. Remove it when solved. + if std::env::var("CARGO_CFG_TARGET_ENV").ok() == Some("msvc".to_string()) { + println!("cargo:rustc-link-arg=/stack:{}", 8 * 1024 * 1024); + } }