From da4e2a4ed836650eae50c6141419fbe0587e2cc2 Mon Sep 17 00:00:00 2001 From: David Armstrong Date: Thu, 9 Sep 2021 23:41:15 -0300 Subject: [PATCH] Massive complication attempting to fix Excel --- Cargo.lock | 107 ++++++++++++++++++++++++++ Cargo.toml | 3 +- src/clipboard_extras.rs | 129 +++++++++++++++++++++++++++++++ src/lib.rs | 150 +++++++++++++++++++++++++++++++------ src/winapi_abstractions.rs | 44 +++++++++++ src/winapi_functions.rs | 28 ++++++- 6 files changed, 437 insertions(+), 24 deletions(-) create mode 100644 src/clipboard_extras.rs create mode 100644 src/winapi_abstractions.rs diff --git a/Cargo.lock b/Cargo.lock index ba258bc..5a425ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "3.0.0-beta.4" @@ -67,6 +73,56 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "error-code" version = "2.3.0" @@ -84,6 +140,7 @@ dependencies = [ "clap", "clipboard-win", "error-code", + "rayon", "winapi", ] @@ -133,6 +190,25 @@ version = "0.2.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" +[[package]] +name = "memoffset" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "os_str_bytes" version = "3.1.0" @@ -181,6 +257,37 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "str-buf" version = "1.0.5" diff --git a/Cargo.toml b/Cargo.toml index d97bc85..fdc235a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,5 @@ edition = "2018" clipboard-win = "4.2.1" winapi = {version = "0.3.9", features = ["winuser", "std", "impl-default"]} error-code = "2.3.0" -clap = "3.0.0-beta.4" \ No newline at end of file +clap = "3.0.0-beta.4" +rayon = "1.5.1" \ No newline at end of file diff --git a/src/clipboard_extras.rs b/src/clipboard_extras.rs new file mode 100644 index 0000000..a77ea3e --- /dev/null +++ b/src/clipboard_extras.rs @@ -0,0 +1,129 @@ +use clipboard_win::{empty, SysResult}; +use winapi::um::winuser::SetClipboardData; + +use core::{mem, ptr}; + +use winapi::ctypes::c_void; + +const GHND: winapi::ctypes::c_uint = 0x42; + +const BYTES_LAYOUT: std::alloc::Layout = std::alloc::Layout::new::(); + +#[inline] +fn noop(_: *mut c_void) {} + +#[inline] +fn free_rust_mem(data: *mut c_void) { + unsafe { std::alloc::dealloc(data as _, BYTES_LAYOUT) } +} + +#[inline] +fn unlock_data(data: *mut c_void) { + unsafe { + winapi::um::winbase::GlobalUnlock(data); + } +} + +#[inline] +fn free_global_mem(data: *mut c_void) { + unsafe { + winapi::um::winbase::GlobalFree(data); + } +} + +pub struct Scope(pub T, pub fn(T)); + +impl Drop for Scope { + #[inline(always)] + fn drop(&mut self) { + (self.1)(self.0) + } +} + +pub struct RawMem(Scope<*mut c_void>); + +impl RawMem { + #[inline(always)] + pub fn new_rust_mem(size: usize) -> Self { + let mem = unsafe { + std::alloc::alloc_zeroed( + std::alloc::Layout::array::(size).expect("To create layout for bytes"), + ) + }; + debug_assert!(!mem.is_null()); + Self(Scope(mem as _, free_rust_mem)) + } + + #[inline(always)] + pub fn new_global_mem(size: usize) -> SysResult { + unsafe { + let mem = winapi::um::winbase::GlobalAlloc(GHND, size as _); + if mem.is_null() { + Err(error_code::SystemError::last()) + } else { + Ok(Self(Scope(mem, free_global_mem))) + } + } + } + + #[inline(always)] + pub fn from_borrowed(ptr: ptr::NonNull) -> Self { + Self(Scope(ptr.as_ptr(), noop)) + } + + #[inline(always)] + pub fn get(&self) -> *mut c_void { + (self.0).0 + } + + #[inline(always)] + pub fn release(self) { + mem::forget(self) + } + + pub fn lock(&self) -> SysResult<(ptr::NonNull, Scope<*mut c_void>)> { + let ptr = unsafe { winapi::um::winbase::GlobalLock(self.get()) }; + + match ptr::NonNull::new(ptr) { + Some(ptr) => Ok((ptr, Scope(self.get(), unlock_data))), + None => Err(error_code::SystemError::last()), + } + } +} + +#[derive(PartialEq, Debug, Default)] +pub struct ClipboardItem { + pub format: u32, + pub content: Vec, +} + +///Copies raw bytes onto clipboard with specified `format`, returning whether it was successful. +pub fn set_all(clipbard_items: &[ClipboardItem]) -> Vec> { + let _ = empty(); + + clipbard_items + .iter() + .map(|item| { + let data = &item.content; + let format = item.format; + + let size = data.len(); + debug_assert!(size > 0); + + let mem = RawMem::new_global_mem(size)?; + + { + let (ptr, _lock) = mem.lock()?; + unsafe { ptr::copy_nonoverlapping(data.as_ptr(), ptr.as_ptr() as _, size) }; + } + + if unsafe { !SetClipboardData(format, mem.get()).is_null() } { + //SetClipboardData takes ownership + mem.release(); + return Ok(()); + } + + Err(error_code::SystemError::last()) + }) + .collect() +} diff --git a/src/lib.rs b/src/lib.rs index b8c4cf4..59b318e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ pub mod cli; +pub mod clipboard_extras; pub mod key_utils; +pub mod winapi_abstractions; pub mod winapi_functions; use cli::Opts; -use clipboard_win::{formats, get_clipboard, set_clipboard}; +use clipboard_win::{formats, get_clipboard, set_clipboard, Clipboard, EnumFormats, Getter}; use core::ptr; use key_utils::is_key_pressed; use std::collections::VecDeque; @@ -11,6 +13,8 @@ use std::ffi::CString; use std::mem; use winapi::um::winuser; +use crate::clipboard_extras::{set_all, ClipboardItem}; +use crate::winapi_abstractions::{ClipboardListener, HotkeyListener}; use crate::{ key_utils::trigger_keys, winapi_functions::{ @@ -20,6 +24,42 @@ use crate::{ }; const MAX_RETRIES: u8 = 10; +const SIMILARITY_THRESHOLD: u8 = 230; + +#[derive(Debug, PartialEq)] +enum ComparisonResult { + Same, + Similar, + Different, +} + +fn compare_data( + cb_data: &[ClipboardItem], + prev_cb_data: &[ClipboardItem], + threshold: u8, +) -> ComparisonResult { + match dbg!(cb_data.len(), prev_cb_data.len()) { + (0, 0) => ComparisonResult::Same, + (0, _) | (_, 0) => ComparisonResult::Different, + _ => { + let count_eq = cb_data + .iter() + .zip(prev_cb_data.iter()) + .filter(|(x, y)| x == y) + .count(); + + let max_eq = *[cb_data.len(), prev_cb_data.len()].iter().max().unwrap(); + + if count_eq == max_eq { + ComparisonResult::Same + } else if count_eq * 255 >= max_eq * threshold as usize { + ComparisonResult::Similar + } else { + ComparisonResult::Different + } + } + } +} pub fn run(opts: Opts) { // Create and register a class @@ -63,6 +103,7 @@ pub fn run(opts: Opts) { // Register the clipboard listener to the message window add_clipboard_format_listener(h_wnd).unwrap(); + // let _clipboard_listener = ClipboardListener::add(h_wnd); // Register the hotkey listener to the message window register_hotkey( @@ -72,32 +113,81 @@ pub fn run(opts: Opts) { 'V' as u32, ) .unwrap(); + // let _hotkey_listener = HotkeyListener::add( + // h_wnd, + // 1, + // (winuser::MOD_CONTROL | winuser::MOD_SHIFT) as u32, + // 'V' as u32, + // ); // Event loop - let mut cb_history = VecDeque::::new(); - let mut internal_update = false; + let mut cb_history = VecDeque::>::new(); + let mut last_internal_update: Option> = None; + let mut skip_clipboard = false; let mut lp_msg = winuser::MSG::default(); #[cfg(debug_assertions)] println!("Ready"); while unsafe { winuser::GetMessageA(&mut lp_msg, h_wnd, 0, 0) != 0 } { - // unsafe { winuser::TranslateMessage(&lp_msg) }; - match lp_msg.message { winuser::WM_CLIPBOARDUPDATE => { - if !internal_update { - if let Ok(clipboard_data) = get_clipboard::(formats::Unicode) { - cb_history.push_front(clipboard_data); - cb_history.truncate(opts.max_history); + if dbg!(skip_clipboard) { + skip_clipboard = false; + } else if let Ok(_clip) = Clipboard::new_attempts(10) { + let cb_data: Vec<_> = EnumFormats::new() + .filter_map(|format| { + let mut clipboard_data = Vec::new(); + if let Ok(bytes) = + formats::RawData(format).read_clipboard(&mut clipboard_data) + { + if bytes != 0 { + return Some(ClipboardItem { + format, + content: clipboard_data, + }); + } + } + None + }) + .collect(); + if !cb_data.is_empty() { + //If let chains would do this far more neatly + let prev_item_similarity = last_internal_update + .as_ref() + .map(|last_update| { + compare_data(&cb_data, last_update, SIMILARITY_THRESHOLD) + }) + .unwrap_or(ComparisonResult::Different); + let current_item_similarity = cb_history + .front() + .map(|last_update| { + compare_data(&cb_data, last_update, SIMILARITY_THRESHOLD) + }) + .unwrap_or(ComparisonResult::Different); + + match (prev_item_similarity, current_item_similarity) { + (_, ComparisonResult::Same) | (ComparisonResult::Same, _) => { + dbg!(1); + } + (_, ComparisonResult::Similar) | (ComparisonResult::Similar, _) => { + dbg!(2); + *cb_history.front_mut().unwrap() = cb_data; + last_internal_update = None; + } + (ComparisonResult::Different, ComparisonResult::Different) => { + dbg!(3); + cb_history.push_front(cb_data); + cb_history.truncate(opts.max_history); + last_internal_update = None; + } + } } - } else { - internal_update = false; } } winuser::WM_HOTKEY => { - if lp_msg.wParam == 1 - /*Ctrl + Shift + V*/ - { + if lp_msg.wParam == 1 { + /*Ctrl + Shift + V*/ + dbg!("Ctrl+Shift+V"); fn old_state(v_key: i32) -> u32 { match is_key_pressed(v_key) { Ok(false) => winuser::KEYEVENTF_KEYUP, @@ -105,6 +195,9 @@ pub fn run(opts: Opts) { } } + let old_control = old_state(winuser::VK_CONTROL); + let old_v = old_state('V' as i32); + match trigger_keys( &[ winuser::VK_SHIFT as u16, @@ -116,20 +209,33 @@ pub fn run(opts: Opts) { ], &[ winuser::KEYEVENTF_KEYUP, - winuser::KEYEVENTF_KEYUP, - winuser::KEYEVENTF_KEYUP, - old_state(winuser::VK_CONTROL), - old_state('V' as i32), + if old_control == 0 { + winuser::KEYEVENTF_KEYUP + } else { + 0 + }, + if old_v == 0 { + winuser::KEYEVENTF_KEYUP + } else { + 0 + }, + old_control, + old_v, old_state(winuser::VK_SHIFT), ], ) { Ok(_) => { // Sleep for less time than the lowest possible automatic keystroke repeat ((1000ms / 30) * 0.8) sleep(25); - cb_history.pop_front(); - if let Some(last_addition) = cb_history.front() { - internal_update = true; - let _ = set_clipboard(formats::Unicode, last_addition); + last_internal_update = cb_history.pop_front(); //This + if let Some(prev_item) = cb_history.front() { + // last_internal_update = cb_history_front; + skip_clipboard = true; + //Was here + if let Ok(_clip) = Clipboard::new_attempts(10) { + dbg!("Setting clipboard to next value"); + let _ = set_all(prev_item); + } } } Err(_) => { diff --git a/src/winapi_abstractions.rs b/src/winapi_abstractions.rs new file mode 100644 index 0000000..cf5819d --- /dev/null +++ b/src/winapi_abstractions.rs @@ -0,0 +1,44 @@ +use crate::winapi_functions::{ + add_clipboard_format_listener, register_hotkey, remove_clipboard_format_listener, + unregister_hotkey, +}; + +pub struct ClipboardListener<'a> { + h_wnd: &'a mut winapi::shared::windef::HWND__, +} + +impl<'a> ClipboardListener<'a> { + pub fn add(h_wnd: &'a mut winapi::shared::windef::HWND__) -> Self { + add_clipboard_format_listener(h_wnd).unwrap(); + Self { h_wnd } + } +} + +impl Drop for ClipboardListener<'_> { + fn drop(&mut self) { + let _ = remove_clipboard_format_listener(self.h_wnd); + } +} + +pub struct HotkeyListener<'a> { + h_wnd: &'a mut winapi::shared::windef::HWND__, + id: i32, +} + +impl<'a> HotkeyListener<'a> { + pub fn add( + h_wnd: &'a mut winapi::shared::windef::HWND__, + id: i32, + fs_modifiers: u32, + key_code: u32, + ) -> Self { + register_hotkey(h_wnd, id, fs_modifiers, key_code).unwrap(); + Self { h_wnd, id } + } +} + +impl Drop for HotkeyListener<'_> { + fn drop(&mut self) { + let _ = unregister_hotkey(self.h_wnd, self.id); + } +} diff --git a/src/winapi_functions.rs b/src/winapi_functions.rs index 42db876..53f9e72 100644 --- a/src/winapi_functions.rs +++ b/src/winapi_functions.rs @@ -29,7 +29,8 @@ pub fn create_window_ex_a<'a>( h_menu: Option<&'a mut winapi::shared::windef::HMENU__>, h_instance: Option<&'a mut winapi::shared::minwindef::HINSTANCE__>, lp_param: Option<&'a mut std::ffi::c_void>, -) -> Result<&'a mut winapi::shared::windef::HWND__, error_code::ErrorCode> { +) -> Result<&'a mut winapi::shared::windef::HWND__, error_code::ErrorCode> +{ //Lifetimes assuming worst case scenario let class_name = CString::new(lp_class_name).unwrap(); let window_name = CString::new(lp_window_name).unwrap(); @@ -124,4 +125,29 @@ pub fn get_async_key_state( 0 => Err(SystemError::last()), state => Ok(state), } +} + +pub fn open_clipboard( + h_wnd: &mut winapi::shared::windef::HWND__, +) -> Result<(), error_code::ErrorCode> { + match unsafe { winuser::OpenClipboard(h_wnd) } { + 0 => Err(SystemError::last()), + _ => Ok(()), + } +} + +pub fn close_clipboard() -> Result<(), error_code::ErrorCode> { + match unsafe { winuser::CloseClipboard() } { + 0 => Err(SystemError::last()), + _ => Ok(()), + } +} + +pub fn get_clipboard_data( + u_format: u32, +) -> Result<*mut std::ffi::c_void, error_code::ErrorCode> { + match unsafe { winuser::GetClipboardData(u_format) } { + handle if handle.is_null() => Err(SystemError::last()), + handle => Ok(handle), + } } \ No newline at end of file