diff --git a/crates/gpui/src/platform/windows.rs b/crates/gpui/src/platform/windows.rs index aa484eccdc..84cf107c70 100644 --- a/crates/gpui/src/platform/windows.rs +++ b/crates/gpui/src/platform/windows.rs @@ -1,3 +1,4 @@ +mod clipboard; mod direct_write; mod dispatcher; mod display; @@ -8,6 +9,7 @@ mod util; mod window; mod wrapper; +pub(crate) use clipboard::*; pub(crate) use direct_write::*; pub(crate) use dispatcher::*; pub(crate) use display::*; diff --git a/crates/gpui/src/platform/windows/clipboard.rs b/crates/gpui/src/platform/windows/clipboard.rs new file mode 100644 index 0000000000..7e607b6a8c --- /dev/null +++ b/crates/gpui/src/platform/windows/clipboard.rs @@ -0,0 +1,366 @@ +use std::sync::LazyLock; + +use anyhow::Result; +use collections::{FxHashMap, FxHashSet}; +use itertools::Itertools; +use util::ResultExt; +use windows::Win32::{ + Foundation::HANDLE, + System::{ + DataExchange::{ + CloseClipboard, CountClipboardFormats, EmptyClipboard, EnumClipboardFormats, + GetClipboardData, GetClipboardFormatNameW, IsClipboardFormatAvailable, OpenClipboard, + RegisterClipboardFormatW, SetClipboardData, + }, + Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE}, + Ole::{CF_HDROP, CF_UNICODETEXT}, + }, + UI::Shell::{DragQueryFileW, HDROP}, +}; +use windows_core::PCWSTR; + +use crate::{ + hash, ClipboardEntry, ClipboardItem, ClipboardString, Image, ImageFormat, SmartGlobal, +}; + +// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew +const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF; + +// Clipboard formats +static CLIPBOARD_HASH_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal text hash"))); +static CLIPBOARD_METADATA_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("GPUI internal metadata"))); +static CLIPBOARD_SVG_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("image/svg+xml"))); +static CLIPBOARD_GIF_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("GIF"))); +static CLIPBOARD_PNG_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("PNG"))); +static CLIPBOARD_JPG_FORMAT: LazyLock = + LazyLock::new(|| register_clipboard_format(windows::core::w!("JFIF"))); + +// Helper maps and sets +static FORMATS_MAP: LazyLock> = LazyLock::new(|| { + let mut formats_map = FxHashMap::default(); + formats_map.insert(CF_UNICODETEXT.0 as u32, ClipboardFormatType::Text); + formats_map.insert(*CLIPBOARD_PNG_FORMAT, ClipboardFormatType::Image); + formats_map.insert(*CLIPBOARD_GIF_FORMAT, ClipboardFormatType::Image); + formats_map.insert(*CLIPBOARD_JPG_FORMAT, ClipboardFormatType::Image); + formats_map.insert(*CLIPBOARD_SVG_FORMAT, ClipboardFormatType::Image); + formats_map.insert(CF_HDROP.0 as u32, ClipboardFormatType::Files); + formats_map +}); +static FORMATS_SET: LazyLock> = LazyLock::new(|| { + let mut formats_map = FxHashSet::default(); + formats_map.insert(CF_UNICODETEXT.0 as u32); + formats_map.insert(*CLIPBOARD_PNG_FORMAT); + formats_map.insert(*CLIPBOARD_GIF_FORMAT); + formats_map.insert(*CLIPBOARD_JPG_FORMAT); + formats_map.insert(*CLIPBOARD_SVG_FORMAT); + formats_map.insert(CF_HDROP.0 as u32); + formats_map +}); +static IMAGE_FORMATS_MAP: LazyLock> = LazyLock::new(|| { + let mut formats_map = FxHashMap::default(); + formats_map.insert(*CLIPBOARD_PNG_FORMAT, ImageFormat::Png); + formats_map.insert(*CLIPBOARD_GIF_FORMAT, ImageFormat::Gif); + formats_map.insert(*CLIPBOARD_JPG_FORMAT, ImageFormat::Jpeg); + formats_map.insert(*CLIPBOARD_SVG_FORMAT, ImageFormat::Svg); + formats_map +}); + +#[derive(Debug, Clone, Copy)] +enum ClipboardFormatType { + Text, + Image, + Files, +} + +pub(crate) fn write_to_clipboard(item: ClipboardItem) { + write_to_clipboard_inner(item).log_err(); + unsafe { CloseClipboard().log_err() }; +} + +pub(crate) fn read_from_clipboard() -> Option { + let result = read_from_clipboard_inner(); + unsafe { CloseClipboard().log_err() }; + result +} + +pub(crate) fn with_file_names(hdrop: HDROP, mut f: F) +where + F: FnMut(String), +{ + let file_count = unsafe { DragQueryFileW(hdrop, DRAGDROP_GET_FILES_COUNT, None) }; + for file_index in 0..file_count { + let filename_length = unsafe { DragQueryFileW(hdrop, file_index, None) } as usize; + let mut buffer = vec![0u16; filename_length + 1]; + let ret = unsafe { DragQueryFileW(hdrop, file_index, Some(buffer.as_mut_slice())) }; + if ret == 0 { + log::error!("unable to read file name"); + continue; + } + if let Some(file_name) = String::from_utf16(&buffer[0..filename_length]).log_err() { + f(file_name); + } + } +} + +fn register_clipboard_format(format: PCWSTR) -> u32 { + let ret = unsafe { RegisterClipboardFormatW(format) }; + if ret == 0 { + panic!( + "Error when registering clipboard format: {}", + std::io::Error::last_os_error() + ); + } + ret +} + +#[inline] +fn format_to_type(item_format: u32) -> &'static ClipboardFormatType { + FORMATS_MAP.get(&item_format).unwrap() +} + +// Currently, we only write the first item. +fn write_to_clipboard_inner(item: ClipboardItem) -> Result<()> { + unsafe { + OpenClipboard(None)?; + EmptyClipboard()?; + } + match item.entries().first() { + Some(entry) => match entry { + ClipboardEntry::String(string) => { + write_string_to_clipboard(string)?; + } + ClipboardEntry::Image(image) => { + write_image_to_clipboard(image)?; + } + }, + None => { + // Writing an empty list of entries just clears the clipboard. + } + } + Ok(()) +} + +fn write_string_to_clipboard(item: &ClipboardString) -> Result<()> { + 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(metadata) = item.metadata.as_ref() { + let hash_result = { + let hash = ClipboardString::text_hash(&item.text); + hash.to_ne_bytes() + }; + let encode_wide = + unsafe { std::slice::from_raw_parts(hash_result.as_ptr().cast::(), 4) }; + set_data_to_clipboard(encode_wide, *CLIPBOARD_HASH_FORMAT)?; + + let metadata_wide = metadata.encode_utf16().chain(Some(0)).collect_vec(); + set_data_to_clipboard(&metadata_wide, *CLIPBOARD_METADATA_FORMAT)?; + } + Ok(()) +} + +fn set_data_to_clipboard(data: &[T], format: u32) -> Result<()> { + unsafe { + let global = GlobalAlloc(GMEM_MOVEABLE, std::mem::size_of_val(data))?; + let handle = GlobalLock(global); + std::ptr::copy_nonoverlapping(data.as_ptr(), handle as _, data.len()); + let _ = GlobalUnlock(global); + SetClipboardData(format, HANDLE(global.0))?; + } + Ok(()) +} + +// Here writing PNG to the clipboard to better support other apps. For more info, please ref to +// the PR. +fn write_image_to_clipboard(item: &Image) -> Result<()> { + match item.format { + ImageFormat::Svg => set_data_to_clipboard(item.bytes(), *CLIPBOARD_SVG_FORMAT)?, + ImageFormat::Gif => { + set_data_to_clipboard(item.bytes(), *CLIPBOARD_GIF_FORMAT)?; + let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Gif)?; + set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?; + } + ImageFormat::Png => { + set_data_to_clipboard(item.bytes(), *CLIPBOARD_PNG_FORMAT)?; + let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Png)?; + set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?; + } + ImageFormat::Jpeg => { + set_data_to_clipboard(item.bytes(), *CLIPBOARD_JPG_FORMAT)?; + let png_bytes = convert_image_to_png_format(item.bytes(), ImageFormat::Jpeg)?; + set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?; + } + other => { + log::warn!( + "Clipboard unsupported image format: {:?}, convert to PNG instead.", + item.format + ); + let png_bytes = convert_image_to_png_format(item.bytes(), other)?; + set_data_to_clipboard(&png_bytes, *CLIPBOARD_PNG_FORMAT)?; + } + } + Ok(()) +} + +fn convert_image_to_png_format(bytes: &[u8], image_format: ImageFormat) -> Result> { + let image = image::load_from_memory_with_format(bytes, image_format.into())?; + let mut output_buf = Vec::new(); + image.write_to( + &mut std::io::Cursor::new(&mut output_buf), + image::ImageFormat::Png, + )?; + Ok(output_buf) +} + +fn read_from_clipboard_inner() -> Option { + unsafe { OpenClipboard(None) }.log_err()?; + with_best_match_format(|item_format| match format_to_type(item_format) { + ClipboardFormatType::Text => read_string_from_clipboard(), + ClipboardFormatType::Image => read_image_from_clipboard(item_format), + ClipboardFormatType::Files => read_files_from_clipboard(), + }) +} + +// Here, we enumerate all formats on the clipboard and find the first one that we can process. +// The reason we don't use `GetPriorityClipboardFormat` is that it sometimes returns the +// wrong format. +// For instance, when copying a JPEG image from Microsoft Word, there may be several formats +// on the clipboard: Jpeg, Png, Svg. +// If we use `GetPriorityClipboardFormat`, it will return Svg, which is not what we want. +fn with_best_match_format(f: F) -> Option +where + F: Fn(u32) -> Option, +{ + let count = unsafe { CountClipboardFormats() }; + let mut clipboard_format = 0; + for _ in 0..count { + clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) }; + let Some(item_format) = FORMATS_SET.get(&clipboard_format) else { + continue; + }; + if let Some(entry) = f(*item_format) { + return Some(ClipboardItem { + entries: vec![entry], + }); + } + } + // log the formats that we don't support yet. + { + clipboard_format = 0; + for _ in 0..count { + clipboard_format = unsafe { EnumClipboardFormats(clipboard_format) }; + let mut buffer = [0u16; 64]; + unsafe { GetClipboardFormatNameW(clipboard_format, &mut buffer) }; + let format_name = String::from_utf16_lossy(&buffer); + log::warn!( + "Try to paste with unsupported clipboard format: {}, {}.", + clipboard_format, + format_name + ); + } + } + None +} + +fn read_string_from_clipboard() -> Option { + let text = { + let global = SmartGlobal::from_raw_ptr( + unsafe { GetClipboardData(CF_UNICODETEXT.0 as u32).log_err() }?.0, + ); + let text = PCWSTR(global.lock() as *const u16); + String::from_utf16_lossy(unsafe { text.as_wide() }) + }; + let Some(hash) = read_hash_from_clipboard() else { + return Some(ClipboardEntry::String(ClipboardString::new(text))); + }; + let Some(metadata) = read_metadata_from_clipboard() else { + return Some(ClipboardEntry::String(ClipboardString::new(text))); + }; + if hash == ClipboardString::text_hash(&text) { + Some(ClipboardEntry::String(ClipboardString { + text, + metadata: Some(metadata), + })) + } else { + Some(ClipboardEntry::String(ClipboardString::new(text))) + } +} + +fn read_hash_from_clipboard() -> Option { + if unsafe { IsClipboardFormatAvailable(*CLIPBOARD_HASH_FORMAT).is_err() } { + return None; + } + let global = + SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(*CLIPBOARD_HASH_FORMAT).log_err() }?.0); + let raw_ptr = global.lock() as *const u16; + let hash_bytes: [u8; 8] = unsafe { + 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() -> Option { + unsafe { IsClipboardFormatAvailable(*CLIPBOARD_METADATA_FORMAT).log_err()? }; + let global = SmartGlobal::from_raw_ptr( + unsafe { GetClipboardData(*CLIPBOARD_METADATA_FORMAT).log_err() }?.0, + ); + let text = PCWSTR(global.lock() as *const u16); + Some(String::from_utf16_lossy(unsafe { text.as_wide() })) +} + +fn read_image_from_clipboard(format: u32) -> Option { + let image_format = format_number_to_image_format(format)?; + read_image_for_type(format, *image_format) +} + +#[inline] +fn format_number_to_image_format(format_number: u32) -> Option<&'static ImageFormat> { + IMAGE_FORMATS_MAP.get(&format_number) +} + +fn read_image_for_type(format_number: u32, format: ImageFormat) -> Option { + let global = SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(format_number).log_err() }?.0); + let image_ptr = global.lock(); + let iamge_size = global.size(); + let bytes = + unsafe { std::slice::from_raw_parts(image_ptr as *mut u8 as _, iamge_size).to_vec() }; + let id = hash(&bytes); + Some(ClipboardEntry::Image(Image { format, bytes, id })) +} + +fn read_files_from_clipboard() -> Option { + let global = + SmartGlobal::from_raw_ptr(unsafe { GetClipboardData(CF_HDROP.0 as u32).log_err() }?.0); + let hdrop = HDROP(global.lock()); + let mut filenames = String::new(); + with_file_names(hdrop, |file_name| { + filenames.push_str(&file_name); + }); + Some(ClipboardEntry::String(ClipboardString { + text: filenames, + metadata: None, + })) +} + +impl From for image::ImageFormat { + fn from(value: ImageFormat) -> Self { + match value { + ImageFormat::Png => image::ImageFormat::Png, + ImageFormat::Jpeg => image::ImageFormat::Jpeg, + ImageFormat::Webp => image::ImageFormat::WebP, + ImageFormat::Gif => image::ImageFormat::Gif, + // ImageFormat::Svg => todo!(), + ImageFormat::Bmp => image::ImageFormat::Bmp, + ImageFormat::Tiff => image::ImageFormat::Tiff, + _ => unreachable!(), + } + } +} diff --git a/crates/gpui/src/platform/windows/platform.rs b/crates/gpui/src/platform/windows/platform.rs index a900d0114b..30e7c402d2 100644 --- a/crates/gpui/src/platform/windows/platform.rs +++ b/crates/gpui/src/platform/windows/platform.rs @@ -17,24 +17,12 @@ use windows::{ core::*, Win32::{ Foundation::*, - Globalization::u_memcpy, Graphics::{ Gdi::*, Imaging::{CLSID_WICImagingFactory, IWICImagingFactory}, }, Security::Credentials::*, - System::{ - Com::*, - DataExchange::{ - CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard, - RegisterClipboardFormatW, SetClipboardData, - }, - LibraryLoader::*, - Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE}, - Ole::*, - SystemInformation::*, - Threading::*, - }, + System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*}, UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*}, }, UI::ViewManagement::UISettings, @@ -52,8 +40,6 @@ pub(crate) struct WindowsPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, text_system: Arc, - clipboard_hash_format: u32, - clipboard_metadata_format: u32, windows_version: WindowsVersion, bitmap_factory: ManuallyDrop, validation_number: usize, @@ -108,9 +94,6 @@ 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(); let windows_version = WindowsVersion::new().expect("Error retrieve windows version"); let validation_number = rand::random::(); @@ -123,8 +106,6 @@ impl WindowsPlatform { background_executor, foreground_executor, text_system, - clipboard_hash_format, - clipboard_metadata_format, windows_version, bitmap_factory, validation_number, @@ -487,15 +468,11 @@ impl Platform for WindowsPlatform { } fn write_to_clipboard(&self, item: ClipboardItem) { - write_to_clipboard( - item, - self.clipboard_hash_format, - self.clipboard_metadata_format, - ); + write_to_clipboard(item); } fn read_from_clipboard(&self) -> Option { - read_from_clipboard(self.clipboard_hash_format, self.clipboard_metadata_format) + read_from_clipboard() } fn write_credentials(&self, url: &str, username: &str, password: &[u8]) -> Task> { @@ -725,117 +702,6 @@ fn should_auto_hide_scrollbars() -> Result { 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() - .unwrap_or_default() - .encode_utf16() - .chain(Some(0)) - .collect_vec(); - set_data_to_clipboard(&encode_wide, CF_UNICODETEXT.0 as u32)?; - - if let Some((metadata, text)) = item.metadata().zip(item.text()) { - let hash_result = { - let hash = ClipboardString::text_hash(&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))?; - } - 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 Some(hash) = read_hash_from_clipboard(hash_format) else { - return Ok(ClipboardItem::new_string(text)); - }; - let Some(metadata) = read_metadata_from_clipboard(metadata_format) else { - return Ok(ClipboardItem::new_string(text)); - }; - if hash == ClipboardString::text_hash(&text) { - Ok(ClipboardItem::new_string_with_metadata(text, metadata)) - } else { - Ok(ClipboardItem::new_string(text)) - } - } -} - -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}; diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index d5ea3be6ca..b212a03a98 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -735,23 +735,11 @@ impl IDropTarget_Impl for WindowsDragDropHandler_Impl { } let hdrop = idata.u.hGlobal.0 as *mut HDROP; let mut paths = SmallVec::<[PathBuf; 2]>::new(); - let file_count = DragQueryFileW(*hdrop, DRAGDROP_GET_FILES_COUNT, None); - for file_index in 0..file_count { - let filename_length = DragQueryFileW(*hdrop, file_index, None) as usize; - let mut buffer = vec![0u16; filename_length + 1]; - let ret = DragQueryFileW(*hdrop, file_index, Some(buffer.as_mut_slice())); - if ret == 0 { - log::error!("unable to read file name"); - continue; + with_file_names(*hdrop, |file_name| { + if let Some(path) = PathBuf::from_str(&file_name).log_err() { + paths.push(path); } - if let Some(file_name) = - String::from_utf16(&buffer[0..filename_length]).log_err() - { - if let Some(path) = PathBuf::from_str(&file_name).log_err() { - paths.push(path); - } - } - } + }); ReleaseStgMedium(&mut idata); let mut cursor_position = POINT { x: pt.x, y: pt.y }; ScreenToClient(self.0.hwnd, &mut cursor_position) @@ -1069,9 +1057,6 @@ fn calculate_client_rect( } } -// https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-dragqueryfilew -const DRAGDROP_GET_FILES_COUNT: u32 = 0xFFFFFFFF; - mod windows_renderer { use std::{num::NonZeroIsize, sync::Arc}; diff --git a/crates/gpui/src/platform/windows/wrapper.rs b/crates/gpui/src/platform/windows/wrapper.rs index 6015dffdab..e6e645e61a 100644 --- a/crates/gpui/src/platform/windows/wrapper.rs +++ b/crates/gpui/src/platform/windows/wrapper.rs @@ -1,6 +1,11 @@ use std::ops::Deref; -use windows::Win32::{Foundation::HANDLE, UI::WindowsAndMessaging::HCURSOR}; +use util::ResultExt; +use windows::Win32::{ + Foundation::{HANDLE, HGLOBAL}, + System::Memory::{GlobalLock, GlobalSize, GlobalUnlock}, + UI::WindowsAndMessaging::HCURSOR, +}; #[derive(Debug, Clone, Copy)] pub(crate) struct SafeHandle { @@ -45,3 +50,30 @@ impl Deref for SafeCursor { &self.raw } } + +#[derive(Debug, Clone)] +pub(crate) struct SmartGlobal { + raw: HGLOBAL, +} + +impl SmartGlobal { + pub(crate) fn from_raw_ptr(ptr: *mut std::ffi::c_void) -> Self { + Self { raw: HGLOBAL(ptr) } + } + + pub(crate) fn lock(&self) -> *mut std::ffi::c_void { + unsafe { GlobalLock(self.raw) } + } + + pub(crate) fn size(&self) -> usize { + unsafe { GlobalSize(self.raw) } + } +} + +impl Drop for SmartGlobal { + fn drop(&mut self) { + unsafe { + GlobalUnlock(self.raw).log_err(); + } + } +}