mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
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
This commit is contained in:
parent
ba09eabfba
commit
315692d112
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -2406,16 +2406,6 @@ dependencies = [
|
|||||||
"worktree",
|
"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]]
|
[[package]]
|
||||||
name = "clock"
|
name = "clock"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -4883,7 +4873,6 @@ dependencies = [
|
|||||||
"calloop",
|
"calloop",
|
||||||
"calloop-wayland-source",
|
"calloop-wayland-source",
|
||||||
"cbindgen",
|
"cbindgen",
|
||||||
"clipboard-win",
|
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"collections",
|
"collections",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
@ -6068,12 +6057,6 @@ dependencies = [
|
|||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lazy-bytes-cast"
|
|
||||||
version = "5.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "10257499f089cd156ad82d0a9cd57d9501fa2c989068992a97eb3c27836f206b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -452,6 +452,7 @@ features = [
|
|||||||
"Win32_System_Com_StructuredStorage",
|
"Win32_System_Com_StructuredStorage",
|
||||||
"Win32_System_DataExchange",
|
"Win32_System_DataExchange",
|
||||||
"Win32_System_LibraryLoader",
|
"Win32_System_LibraryLoader",
|
||||||
|
"Win32_System_Memory",
|
||||||
"Win32_System_Ole",
|
"Win32_System_Ole",
|
||||||
"Win32_System_SystemInformation",
|
"Win32_System_SystemInformation",
|
||||||
"Win32_System_SystemServices",
|
"Win32_System_SystemServices",
|
||||||
|
@ -134,18 +134,22 @@ x11rb = { version = "0.13.0", features = [
|
|||||||
"resource_manager",
|
"resource_manager",
|
||||||
"sync",
|
"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 = [
|
xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
|
||||||
"x11rb-xcb",
|
"x11rb-xcb",
|
||||||
"x11rb-client",
|
"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"
|
x11-clipboard = "0.9.2"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows.workspace = true
|
windows.workspace = true
|
||||||
windows-core = "0.57"
|
windows-core = "0.57"
|
||||||
clipboard-win = "3.1.1"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hello_world"
|
name = "hello_world"
|
||||||
|
@ -406,6 +406,8 @@ pub(crate) trait PlatformTextSystem: Send + Sync {
|
|||||||
raster_bounds: Bounds<DevicePixels>,
|
raster_bounds: Bounds<DevicePixels>,
|
||||||
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
) -> Result<(Size<DevicePixels>, Vec<u8>)>;
|
||||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout;
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn destroy(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||||
|
@ -177,6 +177,9 @@ impl PlatformTextSystem for CosmicTextSystem {
|
|||||||
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
|
fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
|
||||||
self.0.write().layout_line(text, font_size, runs)
|
self.0.write().layout_line(text, font_size, runs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn destroy(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CosmicTextSystemState {
|
impl CosmicTextSystemState {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, mem::ManuallyDrop, sync::Arc};
|
||||||
|
|
||||||
use ::util::ResultExt;
|
use ::util::ResultExt;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
@ -39,7 +39,7 @@ pub(crate) struct DirectWriteTextSystem(RwLock<DirectWriteState>);
|
|||||||
struct DirectWriteComponent {
|
struct DirectWriteComponent {
|
||||||
locale: String,
|
locale: String,
|
||||||
factory: IDWriteFactory5,
|
factory: IDWriteFactory5,
|
||||||
bitmap_factory: IWICImagingFactory2,
|
bitmap_factory: ManuallyDrop<IWICImagingFactory2>,
|
||||||
d2d1_factory: ID2D1Factory,
|
d2d1_factory: ID2D1Factory,
|
||||||
in_memory_loader: IDWriteInMemoryFontFileLoader,
|
in_memory_loader: IDWriteInMemoryFontFileLoader,
|
||||||
builder: IDWriteFontSetBuilder1,
|
builder: IDWriteFontSetBuilder1,
|
||||||
@ -79,6 +79,7 @@ impl DirectWriteComponent {
|
|||||||
let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?;
|
let factory: IDWriteFactory5 = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED)?;
|
||||||
let bitmap_factory: IWICImagingFactory2 =
|
let bitmap_factory: IWICImagingFactory2 =
|
||||||
CoCreateInstance(&CLSID_WICImagingFactory2, None, CLSCTX_INPROC_SERVER)?;
|
CoCreateInstance(&CLSID_WICImagingFactory2, None, CLSCTX_INPROC_SERVER)?;
|
||||||
|
let bitmap_factory = ManuallyDrop::new(bitmap_factory);
|
||||||
let d2d1_factory: ID2D1Factory =
|
let d2d1_factory: ID2D1Factory =
|
||||||
D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?;
|
D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED, None)?;
|
||||||
// The `IDWriteInMemoryFontFileLoader` here is supported starting from
|
// The `IDWriteInMemoryFontFileLoader` here is supported starting from
|
||||||
@ -238,6 +239,11 @@ impl PlatformTextSystem for DirectWriteTextSystem {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn destroy(&self) {
|
||||||
|
let mut lock = self.0.write();
|
||||||
|
unsafe { ManuallyDrop::drop(&mut lock.components.bitmap_factory) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectWriteState {
|
impl DirectWriteState {
|
||||||
|
@ -12,7 +12,6 @@ use std::{
|
|||||||
|
|
||||||
use ::util::ResultExt;
|
use ::util::ResultExt;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use clipboard_win::{get_clipboard_string, set_clipboard_string};
|
|
||||||
use futures::channel::oneshot::{self, Receiver};
|
use futures::channel::oneshot::{self, Receiver};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
@ -22,9 +21,22 @@ use windows::{
|
|||||||
core::*,
|
core::*,
|
||||||
Win32::{
|
Win32::{
|
||||||
Foundation::*,
|
Foundation::*,
|
||||||
|
Globalization::u_memcpy,
|
||||||
Graphics::Gdi::*,
|
Graphics::Gdi::*,
|
||||||
Security::Credentials::*,
|
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::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
|
||||||
},
|
},
|
||||||
UI::ViewManagement::UISettings,
|
UI::ViewManagement::UISettings,
|
||||||
@ -40,6 +52,8 @@ pub(crate) struct WindowsPlatform {
|
|||||||
background_executor: BackgroundExecutor,
|
background_executor: BackgroundExecutor,
|
||||||
foreground_executor: ForegroundExecutor,
|
foreground_executor: ForegroundExecutor,
|
||||||
text_system: Arc<dyn PlatformTextSystem>,
|
text_system: Arc<dyn PlatformTextSystem>,
|
||||||
|
clipboard_hash_format: u32,
|
||||||
|
clipboard_metadata_format: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct WindowsPlatformState {
|
pub(crate) struct WindowsPlatformState {
|
||||||
@ -88,6 +102,9 @@ impl WindowsPlatform {
|
|||||||
let icon = load_icon().unwrap_or_default();
|
let icon = load_icon().unwrap_or_default();
|
||||||
let state = RefCell::new(WindowsPlatformState::new());
|
let state = RefCell::new(WindowsPlatformState::new());
|
||||||
let raw_window_handles = RwLock::new(SmallVec::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 {
|
Self {
|
||||||
state,
|
state,
|
||||||
@ -96,6 +113,8 @@ impl WindowsPlatform {
|
|||||||
background_executor,
|
background_executor,
|
||||||
foreground_executor,
|
foreground_executor,
|
||||||
text_system,
|
text_system,
|
||||||
|
clipboard_hash_format,
|
||||||
|
clipboard_metadata_format,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,17 +517,15 @@ impl Platform for WindowsPlatform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_clipboard(&self, item: ClipboardItem) {
|
fn write_to_clipboard(&self, item: ClipboardItem) {
|
||||||
if item.text.len() > 0 {
|
write_to_clipboard(
|
||||||
set_clipboard_string(item.text()).unwrap();
|
item,
|
||||||
}
|
self.clipboard_hash_format,
|
||||||
|
self.clipboard_metadata_format,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
fn read_from_clipboard(&self) -> Option<ClipboardItem> {
|
||||||
let text = get_clipboard_string().ok()?;
|
read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format)
|
||||||
Some(ClipboardItem {
|
|
||||||
text,
|
|
||||||
metadata: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task<Result<()>> {
|
||||||
@ -586,9 +603,8 @@ impl Platform for WindowsPlatform {
|
|||||||
|
|
||||||
impl Drop for WindowsPlatform {
|
impl Drop for WindowsPlatform {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
self.text_system.destroy();
|
||||||
OleUninitialize();
|
unsafe { OleUninitialize() };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,3 +696,133 @@ fn should_auto_hide_scrollbars() -> Result<bool> {
|
|||||||
let ui_settings = UISettings::new()?;
|
let ui_settings = UISettings::new()?;
|
||||||
Ok(ui_settings.AutoHideScrollBars()?)
|
Ok(ui_settings.AutoHideScrollBars()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn register_clipboard_format(format: PCWSTR) -> Result<u32> {
|
||||||
|
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::<u16>(), 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<ClipboardItem> {
|
||||||
|
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<ClipboardItem> {
|
||||||
|
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<u64> {
|
||||||
|
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::<u8>(), 8)
|
||||||
|
.to_vec()
|
||||||
|
.try_into()
|
||||||
|
.log_err()?;
|
||||||
|
Some(u64::from_ne_bytes(hash_bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_metadata_from_clipboard(metadata_format: u32) -> Option<String> {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user