// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! The system clipboard as `CF_UNICODETEXT`, whose lines Windows ends with CR LF. use windows_sys::Win32::Foundation::{ GlobalFree, HWND }; use windows_sys::Win32::System::DataExchange::{ CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard, SetClipboardData }; use windows_sys::Win32::System::Memory::{ GlobalAlloc, GlobalLock, GlobalSize, GlobalUnlock, GMEM_MOVEABLE }; use windows_sys::Win32::System::Ole::CF_UNICODETEXT; /// Offer `text` to other applications as the clipboard. pub( super ) fn set( hwnd: HWND, text: &str ) { let wide: Vec = text.replace( "\r\n", "\n" ).replace( '\n', "\r\n" ).encode_utf16().chain( std::iter::once( 0 ) ).collect(); // SAFETY: the clipboard is opened and closed here; the memory handed to it is a movable global block of the text's size, which the clipboard owns once `SetClipboardData` succeeds. unsafe { if OpenClipboard( hwnd ) == 0 { return; } EmptyClipboard(); let memory = GlobalAlloc( GMEM_MOVEABLE, wide.len() * 2 ); if !memory.is_null() { let target = GlobalLock( memory ).cast::(); if target.is_null() { GlobalFree( memory ); } else { std::ptr::copy_nonoverlapping( wide.as_ptr(), target, wide.len() ); GlobalUnlock( memory ); if SetClipboardData( CF_UNICODETEXT as u32, memory ).is_null() { GlobalFree( memory ); } } } CloseClipboard(); } } /// The clipboard's text; `None` when it holds none or another application has it open. pub( super ) fn text( hwnd: HWND ) -> Option { // SAFETY: the clipboard is opened and closed here; its block is read only while locked, up to its size or its terminating NUL. unsafe { if OpenClipboard( hwnd ) == 0 { return None; } let text = read(); CloseClipboard(); text } } unsafe fn read() -> Option { let memory = unsafe { GetClipboardData( CF_UNICODETEXT as u32 ) }; if memory.is_null() { return None; } let units = unsafe { GlobalSize( memory ) } / 2; let start = unsafe { GlobalLock( memory ) }.cast::().cast_const(); if start.is_null() { return None; } let all = unsafe { std::slice::from_raw_parts( start, units ) }; let len = all.iter().position( |&u| u == 0 ).unwrap_or( units ); let text = String::from_utf16_lossy( &all[ ..len ] ); unsafe { GlobalUnlock( memory ); } Some( text.replace( "\r\n", "\n" ) ) }