mirror of
https://github.com/gustavosett/Windows-11-Clipboard-History-For-Linux
synced 2026-04-25 17:15:35 +02:00
refactor: reduce code complexity
This commit is contained in:
@@ -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 {
|
||||
|
||||
177
src-tauri/src/input_simulator.rs
Normal file
177
src-tauri/src/input_simulator.rs
Normal 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(())
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user