parent
0477c6a888
commit
0cb88994f0
4 changed files with 167 additions and 149 deletions
|
|
@ -1,10 +1,8 @@
|
|||
use crate::input::{self, EnigoState};
|
||||
use crate::settings::{get_settings, ClipboardHandling, PasteMethod};
|
||||
use enigo::Enigo;
|
||||
use enigo::Key;
|
||||
use enigo::Keyboard;
|
||||
use enigo::Settings;
|
||||
use log::info;
|
||||
use tauri::AppHandle;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
@ -12,136 +10,19 @@ use crate::utils::is_wayland;
|
|||
#[cfg(target_os = "linux")]
|
||||
use std::process::Command;
|
||||
|
||||
/// Sends a Ctrl+V or Cmd+V paste command using platform-specific virtual key codes.
|
||||
/// This ensures the paste works regardless of keyboard layout (e.g., Russian, AZERTY, DVORAK).
|
||||
fn send_paste_ctrl_v() -> Result<(), String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
if try_wayland_send_paste(&PasteMethod::CtrlV)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Platform-specific key definitions
|
||||
#[cfg(target_os = "macos")]
|
||||
let (modifier_key, v_key_code) = (Key::Meta, Key::Other(9));
|
||||
#[cfg(target_os = "windows")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Other(0x56)); // VK_V
|
||||
#[cfg(target_os = "linux")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Unicode('v'));
|
||||
|
||||
let mut enigo = Enigo::new(&Settings::default())
|
||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||
|
||||
// Press modifier + V
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press modifier key: {}", e))?;
|
||||
enigo
|
||||
.key(v_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click V key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release modifier key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a Ctrl+Shift+V paste command.
|
||||
/// This is commonly used in terminal applications on Linux to paste without formatting.
|
||||
fn send_paste_ctrl_shift_v() -> Result<(), String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
if try_wayland_send_paste(&PasteMethod::CtrlShiftV)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Platform-specific key definitions
|
||||
#[cfg(target_os = "macos")]
|
||||
let (modifier_key, v_key_code) = (Key::Meta, Key::Other(9)); // Cmd+Shift+V on macOS
|
||||
#[cfg(target_os = "windows")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Other(0x56)); // VK_V
|
||||
#[cfg(target_os = "linux")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Unicode('v'));
|
||||
|
||||
let mut enigo = Enigo::new(&Settings::default())
|
||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||
|
||||
// Press Ctrl/Cmd + Shift + V
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press modifier key: {}", e))?;
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(v_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click V key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release modifier key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a Shift+Insert paste command (Windows and Linux only).
|
||||
/// This is more universal for terminal applications and legacy software.
|
||||
fn send_paste_shift_insert() -> Result<(), String> {
|
||||
#[cfg(target_os = "linux")]
|
||||
if try_wayland_send_paste(&PasteMethod::ShiftInsert)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let insert_key_code = Key::Other(0x2D); // VK_INSERT
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let insert_key_code = Key::Other(0x76); // XK_Insert (keycode 118 / 0x76, also used as fallback)
|
||||
|
||||
let mut enigo = Enigo::new(&Settings::default())
|
||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||
|
||||
// Press Shift + Insert
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(insert_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click Insert key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release Shift key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pastes text directly using the enigo text method.
|
||||
/// This tries to use system input methods if possible, otherwise simulates keystrokes one by one.
|
||||
fn paste_via_direct_input(text: &str) -> Result<(), String> {
|
||||
let mut enigo = Enigo::new(&Settings::default())
|
||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||
|
||||
enigo
|
||||
.text(text)
|
||||
.map_err(|e| format!("Failed to send text directly: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pastes text using the clipboard: saves current content, writes text, sends paste keystroke, restores clipboard.
|
||||
fn paste_via_clipboard(
|
||||
enigo: &mut Enigo,
|
||||
text: &str,
|
||||
app_handle: &AppHandle,
|
||||
send_paste: fn() -> Result<(), String>,
|
||||
paste_method: &PasteMethod,
|
||||
) -> Result<(), String> {
|
||||
// Check for Wayland first
|
||||
#[cfg(target_os = "linux")]
|
||||
if try_wayland_send_paste(paste_method)? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let clipboard = app_handle.clipboard();
|
||||
let clipboard_content = clipboard.read_text().unwrap_or_default();
|
||||
|
||||
|
|
@ -150,7 +31,14 @@ fn paste_via_clipboard(
|
|||
.map_err(|e| format!("Failed to write to clipboard: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
send_paste()?;
|
||||
|
||||
match paste_method {
|
||||
PasteMethod::CtrlV => input::send_paste_ctrl_v(enigo)?,
|
||||
PasteMethod::CtrlShiftV => input::send_paste_ctrl_shift_v(enigo)?,
|
||||
PasteMethod::ShiftInsert => input::send_paste_shift_insert(enigo)?,
|
||||
_ => return Err("Invalid paste method for clipboard paste".into()),
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
clipboard
|
||||
|
|
@ -257,18 +145,23 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
|
|||
|
||||
info!("Using paste method: {:?}", paste_method);
|
||||
|
||||
// Get the managed Enigo instance
|
||||
let enigo_state = app_handle
|
||||
.try_state::<EnigoState>()
|
||||
.ok_or("Enigo state not initialized")?;
|
||||
let mut enigo = enigo_state
|
||||
.0
|
||||
.lock()
|
||||
.map_err(|e| format!("Failed to lock Enigo: {}", e))?;
|
||||
|
||||
// Perform the paste operation
|
||||
match paste_method {
|
||||
PasteMethod::None => {
|
||||
info!("PasteMethod::None selected - skipping paste action");
|
||||
}
|
||||
PasteMethod::Direct => paste_via_direct_input(&text)?,
|
||||
PasteMethod::CtrlV => paste_via_clipboard(&text, &app_handle, send_paste_ctrl_v)?,
|
||||
PasteMethod::CtrlShiftV => {
|
||||
paste_via_clipboard(&text, &app_handle, send_paste_ctrl_shift_v)?
|
||||
}
|
||||
PasteMethod::ShiftInsert => {
|
||||
paste_via_clipboard(&text, &app_handle, send_paste_shift_insert)?
|
||||
PasteMethod::Direct => input::paste_text_direct(&mut enigo, &text)?,
|
||||
PasteMethod::CtrlV | PasteMethod::CtrlShiftV | PasteMethod::ShiftInsert => {
|
||||
paste_via_clipboard(&mut enigo, &text, &app_handle, &paste_method)?
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
123
src-tauri/src/input.rs
Normal file
123
src-tauri/src/input.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
use enigo::{Enigo, Key, Keyboard, Mouse, Settings};
|
||||
use std::sync::Mutex;
|
||||
use tauri::{AppHandle, Manager};
|
||||
|
||||
/// Wrapper for Enigo to store in Tauri's managed state.
|
||||
/// Enigo is wrapped in a Mutex since it requires mutable access.
|
||||
pub struct EnigoState(pub Mutex<Enigo>);
|
||||
|
||||
impl EnigoState {
|
||||
pub fn new() -> Result<Self, String> {
|
||||
let enigo = Enigo::new(&Settings::default())
|
||||
.map_err(|e| format!("Failed to initialize Enigo: {}", e))?;
|
||||
Ok(Self(Mutex::new(enigo)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current mouse cursor position using the managed Enigo instance.
|
||||
/// Returns None if the state is not available or if getting the location fails.
|
||||
pub fn get_cursor_position(app_handle: &AppHandle) -> Option<(i32, i32)> {
|
||||
let enigo_state = app_handle.try_state::<EnigoState>()?;
|
||||
let enigo = enigo_state.0.lock().ok()?;
|
||||
enigo.location().ok()
|
||||
}
|
||||
|
||||
/// Sends a Ctrl+V or Cmd+V paste command using platform-specific virtual key codes.
|
||||
/// This ensures the paste works regardless of keyboard layout (e.g., Russian, AZERTY, DVORAK).
|
||||
/// Note: On Wayland, this may not work - callers should check for Wayland and use alternative methods.
|
||||
pub fn send_paste_ctrl_v(enigo: &mut Enigo) -> Result<(), String> {
|
||||
// Platform-specific key definitions
|
||||
#[cfg(target_os = "macos")]
|
||||
let (modifier_key, v_key_code) = (Key::Meta, Key::Other(9));
|
||||
#[cfg(target_os = "windows")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Other(0x56)); // VK_V
|
||||
#[cfg(target_os = "linux")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Unicode('v'));
|
||||
|
||||
// Press modifier + V
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press modifier key: {}", e))?;
|
||||
enigo
|
||||
.key(v_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click V key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release modifier key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a Ctrl+Shift+V paste command.
|
||||
/// This is commonly used in terminal applications on Linux to paste without formatting.
|
||||
/// Note: On Wayland, this may not work - callers should check for Wayland and use alternative methods.
|
||||
pub fn send_paste_ctrl_shift_v(enigo: &mut Enigo) -> Result<(), String> {
|
||||
// Platform-specific key definitions
|
||||
#[cfg(target_os = "macos")]
|
||||
let (modifier_key, v_key_code) = (Key::Meta, Key::Other(9)); // Cmd+Shift+V on macOS
|
||||
#[cfg(target_os = "windows")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Other(0x56)); // VK_V
|
||||
#[cfg(target_os = "linux")]
|
||||
let (modifier_key, v_key_code) = (Key::Control, Key::Unicode('v'));
|
||||
|
||||
// Press Ctrl/Cmd + Shift + V
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press modifier key: {}", e))?;
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(v_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click V key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(modifier_key, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release modifier key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sends a Shift+Insert paste command (Windows and Linux only).
|
||||
/// This is more universal for terminal applications and legacy software.
|
||||
/// Note: On Wayland, this may not work - callers should check for Wayland and use alternative methods.
|
||||
pub fn send_paste_shift_insert(enigo: &mut Enigo) -> Result<(), String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let insert_key_code = Key::Other(0x2D); // VK_INSERT
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let insert_key_code = Key::Other(0x76); // XK_Insert (keycode 118 / 0x76, also used as fallback)
|
||||
|
||||
// Press Shift + Insert
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Press)
|
||||
.map_err(|e| format!("Failed to press Shift key: {}", e))?;
|
||||
enigo
|
||||
.key(insert_key_code, enigo::Direction::Click)
|
||||
.map_err(|e| format!("Failed to click Insert key: {}", e))?;
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
enigo
|
||||
.key(Key::Shift, enigo::Direction::Release)
|
||||
.map_err(|e| format!("Failed to release Shift key: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pastes text directly using the enigo text method.
|
||||
/// This tries to use system input methods if possible, otherwise simulates keystrokes one by one.
|
||||
pub fn paste_text_direct(enigo: &mut Enigo, text: &str) -> Result<(), String> {
|
||||
enigo
|
||||
.text(text)
|
||||
.map_err(|e| format!("Failed to send text directly: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ pub mod audio_toolkit;
|
|||
mod clipboard;
|
||||
mod commands;
|
||||
mod helpers;
|
||||
mod input;
|
||||
mod llm_client;
|
||||
mod managers;
|
||||
mod overlay;
|
||||
|
|
@ -108,7 +109,11 @@ fn show_main_window(app: &AppHandle) {
|
|||
}
|
||||
|
||||
fn initialize_core_logic(app_handle: &AppHandle) {
|
||||
// First, initialize the managers
|
||||
// Initialize the input state (Enigo singleton for keyboard/mouse simulation)
|
||||
let enigo_state = input::EnigoState::new().expect("Failed to initialize input state (Enigo)");
|
||||
app_handle.manage(enigo_state);
|
||||
|
||||
// Initialize the managers
|
||||
let recording_manager = Arc::new(
|
||||
AudioRecordingManager::new(app_handle).expect("Failed to initialize recording manager"),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::input;
|
||||
use crate::settings;
|
||||
use crate::settings::OverlayPosition;
|
||||
use enigo::{Enigo, Mouse};
|
||||
use tauri::{AppHandle, Emitter, Manager, PhysicalPosition, PhysicalSize};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
|
@ -70,16 +70,13 @@ fn force_overlay_topmost(overlay_window: &tauri::webview::WebviewWindow) {
|
|||
}
|
||||
|
||||
fn get_monitor_with_cursor(app_handle: &AppHandle) -> Option<tauri::Monitor> {
|
||||
let enigo = Enigo::new(&Default::default());
|
||||
if let Ok(enigo) = enigo {
|
||||
if let Ok(mouse_location) = enigo.location() {
|
||||
if let Ok(monitors) = app_handle.available_monitors() {
|
||||
for monitor in monitors {
|
||||
let is_within =
|
||||
is_mouse_within_monitor(mouse_location, monitor.position(), monitor.size());
|
||||
if is_within {
|
||||
return Some(monitor);
|
||||
}
|
||||
if let Some(mouse_location) = input::get_cursor_position(app_handle) {
|
||||
if let Ok(monitors) = app_handle.available_monitors() {
|
||||
for monitor in monitors {
|
||||
let is_within =
|
||||
is_mouse_within_monitor(mouse_location, monitor.position(), monitor.size());
|
||||
if is_within {
|
||||
return Some(monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue