From c17c419c10aee0d7bf2d7da8575746c98febe657 Mon Sep 17 00:00:00 2001 From: akbash Date: Mon, 6 Jul 2026 17:13:36 +0300 Subject: [PATCH] fix: write pane copies to native windows clipboard (#1082) refs #1067 Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com> --- Cargo.toml | 3 ++ src/platform/windows.rs | 66 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b96c7b4..d2137d66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,10 +49,13 @@ portable-pty = { path = "vendor/portable-pty" } windows-sys = { version = "0.61.2", features = [ "Wdk_System_Threading", "Win32_Foundation", + "Win32_System_DataExchange", "Win32_System_Diagnostics_Debug", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Console", "Win32_System_Kernel", + "Win32_System_Memory", + "Win32_System_Ole", "Win32_System_Pipes", "Win32_System_Threading", "Win32_UI_Shell", diff --git a/src/platform/windows.rs b/src/platform/windows.rs index cc486ace..6ac05767 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -3,17 +3,19 @@ use std::{ ffi::c_void, mem::{size_of, MaybeUninit}, path::PathBuf, - ptr::null_mut, + ptr::{copy_nonoverlapping, null_mut}, }; use windows_sys::{ Wdk::System::Threading::{NtQueryInformationProcess, ProcessBasicInformation}, Win32::{ Foundation::{ - CloseHandle, LocalFree, HANDLE, INVALID_HANDLE_VALUE, NTSTATUS, STATUS_SUCCESS, - UNICODE_STRING, + CloseHandle, GlobalFree, LocalFree, HANDLE, INVALID_HANDLE_VALUE, NTSTATUS, + STATUS_SUCCESS, UNICODE_STRING, }, System::{ + Console::GetConsoleWindow, + DataExchange::{CloseClipboard, EmptyClipboard, OpenClipboard, SetClipboardData}, Diagnostics::{ Debug::ReadProcessMemory, ToolHelp::{ @@ -21,6 +23,8 @@ use windows_sys::{ TH32CS_SNAPPROCESS, }, }, + Memory::{GlobalAlloc, GlobalLock, GlobalUnlock, GMEM_MOVEABLE}, + Ole::CF_UNICODETEXT, Threading::{ GetExitCodeProcess, OpenProcess, TerminateProcess, PROCESS_BASIC_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ, @@ -420,8 +424,50 @@ pub fn process_exists(pid: u32) -> bool { ok && exit_code == STILL_ACTIVE } -pub fn write_clipboard(_bytes: &[u8]) -> bool { - false +pub fn write_clipboard(bytes: &[u8]) -> bool { + let Ok(text) = std::str::from_utf8(bytes) else { + return false; + }; + if text.contains('\0') { + return false; + } + let mut utf16: Vec = text.encode_utf16().collect(); + utf16.push(0); + let Some(byte_len) = utf16.len().checked_mul(size_of::()) else { + return false; + }; + + unsafe { + let owner = GetConsoleWindow(); + if owner.is_null() || OpenClipboard(owner) == 0 { + return false; + } + let _clipboard = ClipboardGuard; + + if EmptyClipboard() == 0 { + return false; + } + + let memory = GlobalAlloc(GMEM_MOVEABLE, byte_len); + if memory.is_null() { + return false; + } + + let locked = GlobalLock(memory); + if locked.is_null() { + GlobalFree(memory); + return false; + } + copy_nonoverlapping(utf16.as_ptr(), locked.cast::(), utf16.len()); + GlobalUnlock(memory); + + if SetClipboardData(CF_UNICODETEXT as u32, memory).is_null() { + GlobalFree(memory); + return false; + } + + true + } } pub fn read_clipboard_text() -> Option { @@ -467,6 +513,16 @@ fn wide_null(value: &str) -> Vec { struct ProcessHandle(HANDLE); +struct ClipboardGuard; + +impl Drop for ClipboardGuard { + fn drop(&mut self) { + unsafe { + CloseClipboard(); + } + } +} + impl ProcessHandle { fn open(pid: u32, access: u32) -> Option { if pid == 0 {