From 315692d1126659e7f6fe0b377f28843a132d2005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Mon, 15 Jul 2024 10:40:41 +0800 Subject: [PATCH] windows: Refactor clipboard implementation (#14347) This PR provides a similar implementation to the macOS clipboard implementation, adds support for metadata and includes tests. Release Notes: - N/A --- Cargo.lock | 17 -- Cargo.toml | 1 + crates/gpui/Cargo.toml | 10 +- crates/gpui/src/platform.rs | 2 + .../src/platform/cosmic_text/text_system.rs | 3 + .../gpui/src/platform/windows/direct_write.rs | 10 +- crates/gpui/src/platform/windows/platform.rs | 172 ++++++++++++++++-- 7 files changed, 180 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f11a62c85..2eed789433 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2406,16 +2406,6 @@ dependencies = [ "worktree", ] -[[package]] -name = "clipboard-win" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" -dependencies = [ - "lazy-bytes-cast", - "winapi", -] - [[package]] name = "clock" version = "0.1.0" @@ -4883,7 +4873,6 @@ dependencies = [ "calloop", "calloop-wayland-source", "cbindgen", - "clipboard-win", "cocoa", "collections", "core-foundation", @@ -6068,12 +6057,6 @@ dependencies = [ "workspace", ] -[[package]] -name = "lazy-bytes-cast" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b" - [[package]] name = "lazy_static" version = "1.4.0" diff --git a/Cargo.toml b/Cargo.toml index 3667418ac8..3e46369976 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -452,6 +452,7 @@ features = [ "Win32_System_Com_StructuredStorage", "Win32_System_DataExchange", "Win32_System_LibraryLoader", + "Win32_System_Memory", "Win32_System_Ole", "Win32_System_SystemInformation", "Win32_System_SystemServices", diff --git a/crates/gpui/Cargo.toml b/crates/gpui/Cargo.toml index fe53754093..07186377e0 100644 --- a/crates/gpui/Cargo.toml +++ b/crates/gpui/Cargo.toml @@ -134,18 +134,22 @@ x11rb = { version = "0.13.0", features = [ "resource_manager", "sync", ] } -xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "2d4c4439160c7846ede0f0ece93bf73b1e613339", features = ["wayland", "x11"] } +xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "2d4c4439160c7846ede0f0ece93bf73b1e613339", features = [ + "wayland", + "x11", +] } xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [ "x11rb-xcb", "x11rb-client", ] } -font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = ["source-fontconfig-dlopen"] } +font-kit = { git = "https://github.com/zed-industries/font-kit", rev = "5a5c4d4", features = [ + "source-fontconfig-dlopen", +] } x11-clipboard = "0.9.2" [target.'cfg(windows)'.dependencies] windows.workspace = true windows-core = "0.57" -clipboard-win = "3.1.1" [[example]] name = "hello_world" diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index b998325e72..0181e0565b 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -406,6 +406,8 @@ pub(crate) trait PlatformTextSystem: Send + Sync { raster_bounds: Bounds, ) -> Result<(Size, Vec)>; fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout; + #[cfg(target_os = "windows")] + fn destroy(&self); } #[derive(PartialEq, Eq, Hash, Clone)] diff --git a/crates/gpui/src/platform/cosmic_text/text_system.rs b/crates/gpui/src/platform/cosmic_text/text_system.rs index dc7bf6ca5f..0735804858 100644 --- a/crates/gpui/src/platform/cosmic_text/text_system.rs +++ b/crates/gpui/src/platform/cosmic_text/text_system.rs @@ -177,6 +177,9 @@ impl PlatformTextSystem for CosmicTextSystem { fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout { self.0.write().layout_line(text, font_size, runs) } + + #[cfg(target_os = "windows")] + fn destroy(&self) {} } impl CosmicTextSystemState { diff --git a/crates/gpui/src/platform/windows/direct_write.rs b/crates/gpui/src/platform/windows/direct_write.rs index 461bae8858..f721834c7c 100644 --- a/crates/gpui/src/platform/windows/direct_write.rs +++ b/crates/gpui/src/platform/windows/direct_write.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc}; use ::util::ResultExt; use anyhow::{anyhow, Result}; @@ -39,7 +39,7 @@ pub(crate) struct DirectWriteTextSystem(RwLock); struct DirectWriteComponent { locale: String, factory: IDWriteFactory5, - bitmap_factory: IWICImagingFactory2, + bitmap_factory: ManuallyDrop, d2d1_factory: ID2D1Factory, in_memory_loader: IDWriteInMemoryFontFileLoader, builder: IDWriteFontSetBuilder1, @@ -79,6 +79,7 @@ impl DirectWriteComponent { let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?; let bitmap_factory: IWICImagingFactory2 = CoCreateInstance(&CLSID_WICImagingFactory2, None, CLSCTX_INPROC_SERVER)?; + let bitmap_factory = ManuallyDrop::new(bitmap_factory); let d2d1_factory: ID2D1Factory = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?; // The `IDWriteInMemoryFontFileLoader` here is supported starting from @@ -238,6 +239,11 @@ impl PlatformTextSystem for DirectWriteTextSystem { ..Default::default() }) } + + fn destroy(&self) { + let mut lock = self.0.write(); + unsafe { ManuallyDrop::drop(&mut lock.components.bitmap_factory) }; + } } impl DirectWriteState { diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index 8a6cf09e9d..dae72cba75 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -12,7 +12,6 @@ use std::{ use ::util::ResultExt; use anyhow::{anyhow, Context, Result}; -use clipboard_win::{get_clipboard_string, set_clipboard_string}; use futures::channel::oneshot::{self, Receiver}; use itertools::Itertools; use parking_lot::RwLock; @@ -22,9 +21,22 @@ use windows::{ core::*, Win32::{ Foundation::*, + Globalization::u_memcpy, Graphics::Gdi::*, Security::Credentials::*, - System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*}, + System::{ + Com::*, + DataExchange::{ + CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard, + RegisterClipboardFormatW, SetClipboardData, + }, + LibraryLoader::*, + Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE}, + Ole::*, + SystemInformation::*, + Threading::*, + Time::*, + }, UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, }, UI::ViewManagement::UISettings, @@ -40,6 +52,8 @@ pub(crate) struct WindowsPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, text_system: Arc, + clipboard_hash_format: u32, + clipboard_metadata_format: u32, } pub(crate) struct WindowsPlatformState { @@ -88,6 +102,9 @@ impl WindowsPlatform { let icon = load_icon().unwrap_or_default(); let state = RefCell::new(WindowsPlatformState::new()); let raw_window_handles = RwLock::new(SmallVec::new()); + let clipboard_hash_format = register_clipboard_format(CLIPBOARD_HASH_FORMAT).unwrap(); + let clipboard_metadata_format = + register_clipboard_format(CLIPBOARD_METADATA_FORMAT).unwrap(); Self { state, @@ -96,6 +113,8 @@ impl WindowsPlatform { background_executor, foreground_executor, text_system, + clipboard_hash_format, + clipboard_metadata_format, } } @@ -498,17 +517,15 @@ impl Platform for WindowsPlatform { } fn write_to_clipboard(&self, item: ClipboardItem) { - if item.text.len() > 0 { - set_clipboard_string(item.text()).unwrap(); - } + write_to_clipboard( + item, + self.clipboard_hash_format, + self.clipboard_metadata_format, + ); } fn read_from_clipboard(&self) -> Option { - let text = get_clipboard_string().ok()?; - Some(ClipboardItem { - text, - metadata: None, - }) + read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format) } fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { @@ -586,9 +603,8 @@ impl Platform for WindowsPlatform { impl Drop for WindowsPlatform { fn drop(&mut self) { - unsafe { - OleUninitialize(); - } + self.text_system.destroy(); + unsafe { OleUninitialize() }; } } @@ -680,3 +696,133 @@ fn should_auto_hide_scrollbars() -> Result { let ui_settings = UISettings::new()?; Ok(ui_settings.AutoHideScrollBars()?) } + +fn register_clipboard_format(format: PCWSTR) -> Result { + let ret = unsafe { RegisterClipboardFormatW(format) }; + if ret == 0 { + Err(anyhow::anyhow!( + "Error when registering clipboard format: {}", + std::io::Error::last_os_error() + )) + } else { + Ok(ret) + } +} + +fn write_to_clipboard(item: ClipboardItem, hash_format: u32, metadata_format: u32) { + write_to_clipboard_inner(item, hash_format, metadata_format).log_err(); + unsafe { CloseClipboard().log_err() }; +} + +fn write_to_clipboard_inner( + item: ClipboardItem, + hash_format: u32, + metadata_format: u32, +) -> Result<()> { + unsafe { + OpenClipboard(None)?; + EmptyClipboard()?; + let encode_wide = item.text.encode_utf16().chain(Some(0)).collect_vec(); + set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?; + + if let Some(ref metadata) = item.metadata { + let hash_result = { + let hash = ClipboardItem::text_hash(&item.text); + hash.to_ne_bytes() + }; + let encode_wide = std::slice::from_raw_parts(hash_result.as_ptr().cast::(), 4); + set_data_to_clipboard(encode_wide, hash_format)?; + + let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec(); + set_data_to_clipboard(&metadata_wide, metadata_format)?; + } + } + Ok(()) +} + +fn set_data_to_clipboard(data: &[u16], format: u32) -> Result<()> { + unsafe { + let global = GlobalAlloc(GMEM_MOVEABLE, data.len() * 2)?; + let handle = GlobalLock(global); + u_memcpy(handle as _, data.as_ptr(), data.len() as _); + let _ = GlobalUnlock(global); + SetClipboardData(format, HANDLE(global.0 as isize))?; + } + Ok(()) +} + +fn read_from_clipboard(hash_format: u32, metadata_format: u32) -> Option { + let result = read_from_clipboard_inner(hash_format, metadata_format).log_err(); + unsafe { CloseClipboard().log_err() }; + result +} + +fn read_from_clipboard_inner(hash_format: u32, metadata_format: u32) -> Result { + unsafe { + OpenClipboard(None)?; + let text = { + let handle = GetClipboardData(CF_UNICODETEXT.0 as u32)?; + let text = PCWSTR(handle.0 as *const u16); + String::from_utf16_lossy(text.as_wide()) + }; + let mut item = ClipboardItem { + text, + metadata: None, + }; + let Some(hash) = read_hash_from_clipboard(hash_format) else { + return Ok(item); + }; + let Some(metadata) = read_metadata_from_clipboard(metadata_format) else { + return Ok(item); + }; + if hash == ClipboardItem::text_hash(&item.text) { + item.metadata = Some(metadata); + } + Ok(item) + } +} + +fn read_hash_from_clipboard(hash_format: u32) -> Option { + unsafe { + let handle = GetClipboardData(hash_format).log_err()?; + let raw_ptr = handle.0 as *const u16; + let hash_bytes: [u8; 8] = std::slice::from_raw_parts(raw_ptr.cast::(), 8) + .to_vec() + .try_into() + .log_err()?; + Some(u64::from_ne_bytes(hash_bytes)) + } +} + +fn read_metadata_from_clipboard(metadata_format: u32) -> Option { + unsafe { + let handle = GetClipboardData(metadata_format).log_err()?; + let text = PCWSTR(handle.0 as *const u16); + Some(String::from_utf16_lossy(text.as_wide())) + } +} + +// clipboard +pub const CLIPBOARD_HASH_FORMAT: PCWSTR = windows::core::w!("zed-text-hash"); +pub const CLIPBOARD_METADATA_FORMAT: PCWSTR = windows::core::w!("zed-metadata"); + +#[cfg(test)] +mod tests { + use crate::{ClipboardItem, Platform, WindowsPlatform}; + + #[test] + fn test_clipboard() { + let platform = WindowsPlatform::new(); + let item = ClipboardItem::new("你好".to_string()); + platform.write_to_clipboard(item.clone()); + assert_eq!(platform.read_from_clipboard(), Some(item)); + + let item = ClipboardItem::new("12345".to_string()); + platform.write_to_clipboard(item.clone()); + assert_eq!(platform.read_from_clipboard(), Some(item)); + + let item = ClipboardItem::new("abcdef".to_string()).with_metadata(vec![3, 4]); + platform.write_to_clipboard(item.clone()); + assert_eq!(platform.read_from_clipboard(), Some(item)); + } +}