refactor: reduce code complexity

This commit is contained in:
Gustavo Carvalho
2025-12-11 20:28:55 -03:00
parent 2766851c3a
commit a701d95769
4 changed files with 225 additions and 468 deletions

View File

@@ -359,261 +359,17 @@ impl ClipboardManager {
}
// Simulate Ctrl+V to paste
simulate_paste()?;
crate::input_simulator::simulate_paste_keystroke()?;
Ok(())
}
}
/// Simulate Ctrl+V keypress for paste injection
#[cfg(target_os = "linux")]
fn simulate_paste() -> Result<(), String> {
// Longer delay to ensure focus is properly restored and clipboard is ready
std::thread::sleep(std::time::Duration::from_millis(10));
eprintln!("[SimulatePaste] Sending Ctrl+V...");
// Try uinput first - works for ALL apps (X11, XWayland, native Wayland)
match simulate_paste_uinput() {
Ok(()) => {
eprintln!("[SimulatePaste] Ctrl+V sent via uinput");
return Ok(());
}
Err(e) => {
eprintln!("[SimulatePaste] uinput failed: {}, trying fallbacks...", e);
}
}
// Fallback to enigo for XWayland apps
match simulate_paste_enigo() {
Ok(()) => {
eprintln!("[SimulatePaste] Ctrl+V sent via enigo");
return Ok(());
}
Err(e) => {
eprintln!("[SimulatePaste] enigo failed: {}", e);
}
}
// Last fallback to xdotool
if std::env::var("DISPLAY").is_ok() {
if let Ok(output) = std::process::Command::new("xdotool")
.args(["key", "--clearmodifiers", "ctrl+v"])
.output()
{
if output.status.success() {
eprintln!("[SimulatePaste] Ctrl+V sent via xdotool");
return Ok(());
}
}
}
Err("All paste methods failed".to_string())
}
/// Simulate paste using uinput (works for ALL apps including native Wayland)
#[cfg(target_os = "linux")]
fn simulate_paste_uinput() -> Result<(), String> {
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::io::AsRawFd;
// Linux input event codes
const EV_SYN: u16 = 0x00;
const EV_KEY: u16 = 0x01;
const SYN_REPORT: u16 = 0x00;
const KEY_LEFTCTRL: u16 = 29;
const KEY_V: u16 = 47;
// input_event struct layout for x86_64:
// struct timeval { long tv_sec; long tv_usec; } = 16 bytes
// __u16 type = 2 bytes
// __u16 code = 2 bytes
// __s32 value = 4 bytes
// Total = 24 bytes
fn make_event(type_: u16, code: u16, value: i32) -> [u8; 24] {
let mut event = [0u8; 24];
// timeval (16 bytes) - leave as zeros
// type (2 bytes at offset 16)
event[16..18].copy_from_slice(&type_.to_ne_bytes());
// code (2 bytes at offset 18)
event[18..20].copy_from_slice(&code.to_ne_bytes());
// value (4 bytes at offset 20)
event[20..24].copy_from_slice(&value.to_ne_bytes());
event
}
// Open uinput device
let mut uinput = OpenOptions::new()
.write(true)
.open("/dev/uinput")
.map_err(|e| format!("Failed to open /dev/uinput: {}", e))?;
// Set up uinput device
// UI_SET_EVBIT = 0x40045564
// UI_SET_KEYBIT = 0x40045565
const UI_SET_EVBIT: libc::c_ulong = 0x40045564;
const UI_SET_KEYBIT: libc::c_ulong = 0x40045565;
const UI_DEV_SETUP: libc::c_ulong = 0x405c5503;
const UI_DEV_CREATE: libc::c_ulong = 0x5501;
const UI_DEV_DESTROY: libc::c_ulong = 0x5502;
unsafe {
// Enable EV_KEY events
if libc::ioctl(uinput.as_raw_fd(), UI_SET_EVBIT, EV_KEY as libc::c_int) < 0 {
return Err("Failed to set EV_KEY".to_string());
}
// Enable the keys we need
if libc::ioctl(
uinput.as_raw_fd(),
UI_SET_KEYBIT,
KEY_LEFTCTRL as libc::c_int,
) < 0
{
return Err("Failed to set KEY_LEFTCTRL".to_string());
}
if libc::ioctl(uinput.as_raw_fd(), UI_SET_KEYBIT, KEY_V as libc::c_int) < 0 {
return Err("Failed to set KEY_V".to_string());
}
// Setup device info
#[repr(C)]
struct UinputSetup {
id: [u16; 4], // bus, vendor, product, version
name: [u8; 80],
ff_effects_max: u32,
}
let mut setup = UinputSetup {
id: [0x03, 0x1234, 0x5678, 0x0001], // BUS_USB
name: [0; 80],
ff_effects_max: 0,
};
let name = b"clipboard-paste-helper";
setup.name[..name.len()].copy_from_slice(name);
if libc::ioctl(uinput.as_raw_fd(), UI_DEV_SETUP, &setup) < 0 {
return Err("Failed to setup uinput device".to_string());
}
// Create the device
if libc::ioctl(uinput.as_raw_fd(), UI_DEV_CREATE) < 0 {
return Err("Failed to create uinput device".to_string());
}
}
// Longer delay for device to be fully ready and recognized by the system
std::thread::sleep(std::time::Duration::from_millis(100));
// Send Ctrl+V with proper timing
// Press Ctrl first and wait for it to register
uinput
.write_all(&make_event(EV_KEY, KEY_LEFTCTRL, 1))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
// Wait for Ctrl to be fully registered
std::thread::sleep(std::time::Duration::from_millis(30));
// Press V while Ctrl is held
uinput
.write_all(&make_event(EV_KEY, KEY_V, 1))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Release V
uinput
.write_all(&make_event(EV_KEY, KEY_V, 0))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Release Ctrl last
uinput
.write_all(&make_event(EV_KEY, KEY_LEFTCTRL, 0))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
// Wait for events to be processed before destroying device
std::thread::sleep(std::time::Duration::from_millis(100));
// Destroy the device
unsafe {
libc::ioctl(uinput.as_raw_fd(), UI_DEV_DESTROY);
}
Ok(())
}
/// Fallback paste simulation using enigo (X11/XWayland only)
#[cfg(target_os = "linux")]
fn simulate_paste_enigo() -> Result<(), String> {
use enigo::{Direction, Enigo, Key, Keyboard, Settings};
let mut enigo = Enigo::new(&Settings::default()).map_err(|e| {
eprintln!("[SimulatePaste] Failed to create Enigo: {}", e);
e.to_string()
})?;
// Press Ctrl
enigo.key(Key::Control, Direction::Press).map_err(|e| {
eprintln!("[SimulatePaste] Ctrl press failed: {}", e);
e.to_string()
})?;
std::thread::sleep(std::time::Duration::from_millis(20));
// Press and release V
enigo
.key(Key::Unicode('v'), Direction::Press)
.map_err(|e| {
eprintln!("[SimulatePaste] V press failed: {}", e);
e.to_string()
})?;
std::thread::sleep(std::time::Duration::from_millis(20));
enigo
.key(Key::Unicode('v'), Direction::Release)
.map_err(|e| {
eprintln!("[SimulatePaste] V release failed: {}", e);
e.to_string()
})?;
std::thread::sleep(std::time::Duration::from_millis(20));
// Release Ctrl
enigo.key(Key::Control, Direction::Release).map_err(|e| {
eprintln!("[SimulatePaste] Ctrl release failed: {}", e);
e.to_string()
})?;
eprintln!("[SimulatePaste] Ctrl+V sent via enigo");
Ok(())
}
#[cfg(not(target_os = "linux"))]
fn simulate_paste() -> Result<(), String> {
// Fallback for other platforms - just set clipboard
Ok(())
}
impl Default for ClipboardManager {
fn default() -> Self {

View File

@@ -0,0 +1,177 @@
#[cfg(target_os = "linux")]
pub fn simulate_paste_keystroke() -> Result<(), String> {
// Small delay before paste
std::thread::sleep(std::time::Duration::from_millis(10));
eprintln!("[SimulatePaste] Sending Ctrl+V...");
// Try uinput first
if let Ok(()) = simulate_paste_uinput() {
eprintln!("[SimulatePaste] Ctrl+V sent via uinput");
return Ok(());
}
// Fallback to enigo
if let Ok(()) = simulate_paste_enigo() {
eprintln!("[SimulatePaste] Ctrl+V sent via enigo");
return Ok(());
}
// Last fallback to xdotool
if std::env::var("DISPLAY").is_ok() {
if let Ok(output) = std::process::Command::new("xdotool")
.args(["key", "--clearmodifiers", "ctrl+v"])
.output()
{
if output.status.success() {
eprintln!("[SimulatePaste] Ctrl+V sent via xdotool");
return Ok(());
}
}
}
Err("All paste methods failed".to_string())
}
#[cfg(not(target_os = "linux"))]
pub fn simulate_paste_keystroke() -> Result<(), String> {
Ok(())
}
#[cfg(target_os = "linux")]
fn simulate_paste_uinput() -> Result<(), String> {
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::io::AsRawFd;
const EV_SYN: u16 = 0x00;
const EV_KEY: u16 = 0x01;
const SYN_REPORT: u16 = 0x00;
const KEY_LEFTCTRL: u16 = 29;
const KEY_V: u16 = 47;
fn make_event(type_: u16, code: u16, value: i32) -> [u8; 24] {
let mut event = [0u8; 24];
event[16..18].copy_from_slice(&type_.to_ne_bytes());
event[18..20].copy_from_slice(&code.to_ne_bytes());
event[20..24].copy_from_slice(&value.to_ne_bytes());
event
}
let mut uinput = OpenOptions::new()
.write(true)
.open("/dev/uinput")
.map_err(|e| format!("Failed to open /dev/uinput: {}", e))?;
const UI_SET_EVBIT: libc::c_ulong = 0x40045564;
const UI_SET_KEYBIT: libc::c_ulong = 0x40045565;
const UI_DEV_SETUP: libc::c_ulong = 0x405c5503;
const UI_DEV_CREATE: libc::c_ulong = 0x5501;
const UI_DEV_DESTROY: libc::c_ulong = 0x5502;
unsafe {
if libc::ioctl(uinput.as_raw_fd(), UI_SET_EVBIT, EV_KEY as libc::c_int) < 0 {
return Err("Failed to set EV_KEY".to_string());
}
if libc::ioctl(
uinput.as_raw_fd(),
UI_SET_KEYBIT,
KEY_LEFTCTRL as libc::c_int,
) < 0
{
return Err("Failed to set KEY_LEFTCTRL".to_string());
}
if libc::ioctl(uinput.as_raw_fd(), UI_SET_KEYBIT, KEY_V as libc::c_int) < 0 {
return Err("Failed to set KEY_V".to_string());
}
#[repr(C)]
struct UinputSetup {
id: [u16; 4],
name: [u8; 80],
ff_effects_max: u32,
}
let mut setup = UinputSetup {
id: [0x03, 0x1234, 0x5678, 0x0001],
name: [0; 80],
ff_effects_max: 0,
};
let name = b"emoji-paste-helper";
setup.name[..name.len()].copy_from_slice(name);
if libc::ioctl(uinput.as_raw_fd(), UI_DEV_SETUP, &setup) < 0 {
return Err("Failed to setup uinput device".to_string());
}
if libc::ioctl(uinput.as_raw_fd(), UI_DEV_CREATE) < 0 {
return Err("Failed to create uinput device".to_string());
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
// Press Ctrl
uinput
.write_all(&make_event(EV_KEY, KEY_LEFTCTRL, 1))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Press V
uinput
.write_all(&make_event(EV_KEY, KEY_V, 1))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Release V
uinput
.write_all(&make_event(EV_KEY, KEY_V, 0))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Release Ctrl
uinput
.write_all(&make_event(EV_KEY, KEY_LEFTCTRL, 0))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(100));
unsafe {
libc::ioctl(uinput.as_raw_fd(), UI_DEV_DESTROY);
}
Ok(())
}
#[cfg(target_os = "linux")]
fn simulate_paste_enigo() -> Result<(), String> {
use enigo::{Direction, Enigo, Key, Keyboard, Settings};
let mut enigo = Enigo::new(&Settings::default()).map_err(|e| e.to_string())?;
enigo
.key(Key::Control, Direction::Press)
.map_err(|e| e.to_string())?;
enigo
.key(Key::Unicode('v'), Direction::Click)
.map_err(|e| e.to_string())?;
enigo
.key(Key::Control, Direction::Release)
.map_err(|e| e.to_string())?;
Ok(())
}

View File

@@ -5,6 +5,7 @@ pub mod clipboard_manager;
pub mod emoji_manager;
pub mod focus_manager;
pub mod hotkey_manager;
pub mod input_simulator;
pub use clipboard_manager::{ClipboardContent, ClipboardItem, ClipboardManager};
pub use emoji_manager::{EmojiManager, EmojiUsage};

View File

@@ -13,6 +13,7 @@ use win11_clipboard_history_lib::clipboard_manager::{ClipboardItem, ClipboardMan
use win11_clipboard_history_lib::emoji_manager::{EmojiManager, EmojiUsage};
use win11_clipboard_history_lib::focus_manager::{restore_focused_window, save_focused_window};
use win11_clipboard_history_lib::hotkey_manager::{HotkeyAction, HotkeyManager};
use win11_clipboard_history_lib::input_simulator::simulate_paste_keystroke;
/// Application state shared across all handlers
pub struct AppState {
@@ -84,8 +85,49 @@ fn get_recent_emojis(state: State<AppState>) -> Vec<EmojiUsage> {
state.emoji_manager.lock().get_recent()
}
/// Helper to paste text via clipboard pipeline
/// Pipeline: Mark as pasted -> Write to clipboard -> Hide window -> Restore focus -> Simulate Ctrl+V
async fn paste_text_via_clipboard(
app: &AppHandle,
state: &State<'_, AppState>,
text: &str,
) -> Result<(), String> {
// Step 1: Mark text as "pasted by us" so clipboard watcher ignores it
{
let mut clipboard_manager = state.clipboard_manager.lock();
clipboard_manager.mark_text_as_pasted(text);
}
// Step 2: Write to system clipboard (transport only)
{
use arboard::Clipboard;
let mut clipboard = Clipboard::new().map_err(|e| format!("Clipboard error: {}", e))?;
clipboard
.set_text(text)
.map_err(|e| format!("Failed to set clipboard: {}", e))?;
}
// Step 3: Hide window
if let Some(window) = app.get_webview_window("main") {
let _ = window.hide();
}
// Step 4: Restore focus
if let Err(e) = restore_focused_window() {
eprintln!("Warning: Failed to restore focus: {}", e);
}
// Step 5: Wait for focus to be fully restored
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
// Step 6: Simulate Ctrl+V
simulate_paste_keystroke()?;
Ok(())
}
/// Paste an emoji character
/// Pipeline: Write to clipboard -> Hide window -> Restore focus -> Simulate Ctrl+V
/// Pipeline: Record usage -> Delegate to paste_text_via_clipboard
#[tauri::command]
async fn paste_emoji(
app: AppHandle,
@@ -94,238 +136,19 @@ async fn paste_emoji(
) -> Result<(), String> {
eprintln!("[PasteEmoji] Starting paste for emoji: {}", char);
// Step 1: Record usage in LRU cache (for "recently used" section in emoji picker)
// Record usage in LRU cache
{
let mut emoji_manager = state.emoji_manager.lock();
emoji_manager.record_usage(&char);
}
// Step 2: Mark this emoji as "pasted by us" so clipboard watcher ignores it
{
let mut clipboard_manager = state.clipboard_manager.lock();
clipboard_manager.mark_text_as_pasted(&char);
}
// Step 3: Write emoji to system clipboard (transport only, not history)
{
use arboard::Clipboard;
let mut clipboard = Clipboard::new().map_err(|e| format!("Clipboard error: {}", e))?;
clipboard
.set_text(&char)
.map_err(|e| format!("Failed to set clipboard: {}", e))?;
eprintln!("[PasteEmoji] Emoji written to clipboard");
}
// Step 4: Hide our window
if let Some(window) = app.get_webview_window("main") {
let _ = window.hide();
eprintln!("[PasteEmoji] Window hidden");
}
// Step 5: Wait and restore focus to previous window
if let Err(e) = restore_focused_window() {
eprintln!("[PasteEmoji] Warning: Failed to restore focus: {}", e);
}
// Step 6: Wait for focus to be fully restored
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
eprintln!("[PasteEmoji] Focus restored, simulating paste...");
// Step 7: Simulate Ctrl+V to paste the emoji
simulate_paste_keystroke()?;
// Delegate to shared pipeline
paste_text_via_clipboard(&app, &state, &char).await?;
eprintln!("[PasteEmoji] Paste complete");
Ok(())
}
/// Simulate Ctrl+V keystroke (extracted from clipboard_manager for reuse)
#[cfg(target_os = "linux")]
fn simulate_paste_keystroke() -> Result<(), String> {
// Small delay before paste
std::thread::sleep(std::time::Duration::from_millis(10));
eprintln!("[SimulatePaste] Sending Ctrl+V...");
// Try uinput first
if let Ok(()) = simulate_paste_uinput() {
eprintln!("[SimulatePaste] Ctrl+V sent via uinput");
return Ok(());
}
// Fallback to enigo
if let Ok(()) = simulate_paste_enigo() {
eprintln!("[SimulatePaste] Ctrl+V sent via enigo");
return Ok(());
}
// Last fallback to xdotool
if std::env::var("DISPLAY").is_ok() {
if let Ok(output) = std::process::Command::new("xdotool")
.args(["key", "--clearmodifiers", "ctrl+v"])
.output()
{
if output.status.success() {
eprintln!("[SimulatePaste] Ctrl+V sent via xdotool");
return Ok(());
}
}
}
Err("All paste methods failed".to_string())
}
#[cfg(target_os = "linux")]
fn simulate_paste_uinput() -> Result<(), String> {
use std::fs::OpenOptions;
use std::io::Write;
use std::os::unix::io::AsRawFd;
const EV_SYN: u16 = 0x00;
const EV_KEY: u16 = 0x01;
const SYN_REPORT: u16 = 0x00;
const KEY_LEFTCTRL: u16 = 29;
const KEY_V: u16 = 47;
fn make_event(type_: u16, code: u16, value: i32) -> [u8; 24] {
let mut event = [0u8; 24];
event[16..18].copy_from_slice(&type_.to_ne_bytes());
event[18..20].copy_from_slice(&code.to_ne_bytes());
event[20..24].copy_from_slice(&value.to_ne_bytes());
event
}
let mut uinput = OpenOptions::new()
.write(true)
.open("/dev/uinput")
.map_err(|e| format!("Failed to open /dev/uinput: {}", e))?;
const UI_SET_EVBIT: libc::c_ulong = 0x40045564;
const UI_SET_KEYBIT: libc::c_ulong = 0x40045565;
const UI_DEV_SETUP: libc::c_ulong = 0x405c5503;
const UI_DEV_CREATE: libc::c_ulong = 0x5501;
const UI_DEV_DESTROY: libc::c_ulong = 0x5502;
unsafe {
if libc::ioctl(uinput.as_raw_fd(), UI_SET_EVBIT, EV_KEY as libc::c_int) < 0 {
return Err("Failed to set EV_KEY".to_string());
}
if libc::ioctl(
uinput.as_raw_fd(),
UI_SET_KEYBIT,
KEY_LEFTCTRL as libc::c_int,
) < 0
{
return Err("Failed to set KEY_LEFTCTRL".to_string());
}
if libc::ioctl(uinput.as_raw_fd(), UI_SET_KEYBIT, KEY_V as libc::c_int) < 0 {
return Err("Failed to set KEY_V".to_string());
}
#[repr(C)]
struct UinputSetup {
id: [u16; 4],
name: [u8; 80],
ff_effects_max: u32,
}
let mut setup = UinputSetup {
id: [0x03, 0x1234, 0x5678, 0x0001],
name: [0; 80],
ff_effects_max: 0,
};
let name = b"emoji-paste-helper";
setup.name[..name.len()].copy_from_slice(name);
if libc::ioctl(uinput.as_raw_fd(), UI_DEV_SETUP, &setup) < 0 {
return Err("Failed to setup uinput device".to_string());
}
if libc::ioctl(uinput.as_raw_fd(), UI_DEV_CREATE) < 0 {
return Err("Failed to create uinput device".to_string());
}
}
std::thread::sleep(std::time::Duration::from_millis(100));
// Press Ctrl
uinput
.write_all(&make_event(EV_KEY, KEY_LEFTCTRL, 1))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Press V
uinput
.write_all(&make_event(EV_KEY, KEY_V, 1))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Release V
uinput
.write_all(&make_event(EV_KEY, KEY_V, 0))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(30));
// Release Ctrl
uinput
.write_all(&make_event(EV_KEY, KEY_LEFTCTRL, 0))
.map_err(|e| e.to_string())?;
uinput
.write_all(&make_event(EV_SYN, SYN_REPORT, 0))
.map_err(|e| e.to_string())?;
uinput.flush().map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(100));
unsafe {
libc::ioctl(uinput.as_raw_fd(), UI_DEV_DESTROY);
}
Ok(())
}
#[cfg(target_os = "linux")]
fn simulate_paste_enigo() -> Result<(), String> {
use enigo::{Direction, Enigo, Key, Keyboard, Settings};
let mut enigo = Enigo::new(&Settings::default()).map_err(|e| e.to_string())?;
enigo
.key(Key::Control, Direction::Press)
.map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(20));
enigo
.key(Key::Unicode('v'), Direction::Press)
.map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(20));
enigo
.key(Key::Unicode('v'), Direction::Release)
.map_err(|e| e.to_string())?;
std::thread::sleep(std::time::Duration::from_millis(20));
enigo
.key(Key::Control, Direction::Release)
.map_err(|e| e.to_string())?;
Ok(())
}
#[cfg(not(target_os = "linux"))]
fn simulate_paste_keystroke() -> Result<(), String> {
Ok(())
}
/// Show the clipboard window at cursor position
fn show_window_at_cursor(window: &WebviewWindow) {
use tauri::{PhysicalPosition, PhysicalSize};