feat(snippets): use clipboard to type text

This commit changes the `inject_text` method in both `RdevInputManager` and `EvdevInputManager` to utilize clipboard functionality for text input. This allows for both instantaneous snippet resolving as well as the ability to type symbols not on the keyboard (e.g. ⌘)
This commit is contained in:
ByteAtATime 2025-06-25 10:07:49 -07:00
parent 9667dd62fb
commit a34968f851
No known key found for this signature in database

View file

@ -1,8 +1,10 @@
use crate::clipboard_history::manager::INTERNAL_CLIPBOARD_CHANGE;
use anyhow::Result;
use arboard::Clipboard;
use enigo::{Enigo, Key as EnigoKey, Keyboard};
use lazy_static::lazy_static;
use rdev::Key;
use std::sync::{Arc, Mutex};
use std::sync::{atomic::Ordering, Arc, Mutex};
use std::thread;
#[cfg(target_os = "linux")]
@ -23,6 +25,21 @@ pub enum InputEvent {
KeyPress(char),
}
struct InternalClipboardGuard;
impl InternalClipboardGuard {
fn new() -> Self {
INTERNAL_CLIPBOARD_CHANGE.store(true, Ordering::SeqCst);
Self
}
}
impl Drop for InternalClipboardGuard {
fn drop(&mut self) {
INTERNAL_CLIPBOARD_CHANGE.store(false, Ordering::SeqCst);
}
}
pub trait InputManager: Send + Sync {
fn start_listening(&self, callback: Box<dyn Fn(InputEvent) + Send + Sync>) -> Result<()>;
fn inject_text(&self, text: &str) -> Result<()>;
@ -72,23 +89,32 @@ impl InputManager for RdevInputManager {
}
fn inject_text(&self, text: &str) -> Result<()> {
if text.chars().all(|c| c == '\u{8}') {
let mut enigo = ENIGO.lock().unwrap();
let mut buffer = String::new();
for c in text.chars() {
if c == '\u{8}' {
if !buffer.is_empty() {
enigo.text(&buffer)?;
buffer.clear();
}
for _ in 0..text.len() {
enigo.key(enigo::Key::Backspace, enigo::Direction::Click)?;
} else {
buffer.push(c);
}
return Ok(());
}
if !buffer.is_empty() {
enigo.text(&buffer)?;
let _guard = InternalClipboardGuard::new();
let mut clipboard = Clipboard::new().map_err(|e| anyhow::anyhow!(e))?;
let original_content = clipboard.get_text().ok();
clipboard.set_text(text)?;
thread::sleep(std::time::Duration::from_millis(50));
let mut enigo = ENIGO.lock().unwrap();
enigo.key(EnigoKey::Control, enigo::Direction::Press)?;
enigo.key(EnigoKey::Unicode('v'), enigo::Direction::Click)?;
enigo.key(EnigoKey::Control, enigo::Direction::Release)?;
thread::sleep(std::time::Duration::from_millis(50));
if let Some(original) = original_content {
clipboard.set_text(original)?;
} else {
clipboard.set_text("")?;
}
Ok(())
@ -115,6 +141,8 @@ impl EvdevInputManager {
let mut key_codes = HashSet::new();
key_codes.extend([
KeyCode::KEY_LEFTSHIFT,
KeyCode::KEY_LEFTCTRL,
KeyCode::KEY_V,
KeyCode::KEY_ENTER,
KeyCode::KEY_TAB,
KeyCode::KEY_SPACE,
@ -407,14 +435,53 @@ impl InputManager for EvdevInputManager {
}
fn inject_text(&self, text: &str) -> Result<()> {
if text.chars().all(|c| c == '\u{8}') {
let mut device = self.virtual_device.lock().unwrap();
for ch in text.chars() {
if ch == '\u{8}' {
for _ in 0..text.len() {
self.inject_key_click(&mut *device, KeyCode::KEY_BACKSPACE)?;
}
return Ok(());
}
let _guard = InternalClipboardGuard::new();
let mut clipboard = Clipboard::new()?;
let original_content = clipboard.get_text().ok();
clipboard.set_text(text)?;
thread::sleep(Duration::from_millis(50));
let mut device = self.virtual_device.lock().unwrap();
let syn = evdev::InputEvent::new(
evdev::EventType::SYNCHRONIZATION.0,
evdev::SynchronizationCode::SYN_REPORT.0,
0,
);
device.emit(&[
evdev::InputEvent::new(evdev::EventType::KEY.0, KeyCode::KEY_LEFTCTRL.0, 1),
syn.clone(),
])?;
device.emit(&[
evdev::InputEvent::new(evdev::EventType::KEY.0, KeyCode::KEY_V.0, 1),
syn.clone(),
])?;
device.emit(&[
evdev::InputEvent::new(evdev::EventType::KEY.0, KeyCode::KEY_V.0, 0),
syn.clone(),
])?;
device.emit(&[
evdev::InputEvent::new(evdev::EventType::KEY.0, KeyCode::KEY_LEFTCTRL.0, 0),
syn.clone(),
])?;
thread::sleep(Duration::from_millis(50));
if let Some(original) = original_content {
clipboard.set_text(original)?;
} else {
self.inject_char(&mut *device, ch)?;
}
clipboard.set_text("")?;
}
Ok(())
}