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 crate::settings::{get_settings, ClipboardHandling, PasteMethod};
|
||||||
use enigo::Enigo;
|
use enigo::Enigo;
|
||||||
use enigo::Key;
|
|
||||||
use enigo::Keyboard;
|
|
||||||
use enigo::Settings;
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use tauri::AppHandle;
|
use tauri::{AppHandle, Manager};
|
||||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|
@ -12,136 +10,19 @@ use crate::utils::is_wayland;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use std::process::Command;
|
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.
|
/// Pastes text using the clipboard: saves current content, writes text, sends paste keystroke, restores clipboard.
|
||||||
fn paste_via_clipboard(
|
fn paste_via_clipboard(
|
||||||
|
enigo: &mut Enigo,
|
||||||
text: &str,
|
text: &str,
|
||||||
app_handle: &AppHandle,
|
app_handle: &AppHandle,
|
||||||
send_paste: fn() -> Result<(), String>,
|
paste_method: &PasteMethod,
|
||||||
) -> Result<(), String> {
|
) -> 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 = app_handle.clipboard();
|
||||||
let clipboard_content = clipboard.read_text().unwrap_or_default();
|
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))?;
|
.map_err(|e| format!("Failed to write to clipboard: {}", e))?;
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
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));
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||||
|
|
||||||
clipboard
|
clipboard
|
||||||
|
|
@ -257,18 +145,23 @@ pub fn paste(text: String, app_handle: AppHandle) -> Result<(), String> {
|
||||||
|
|
||||||
info!("Using paste method: {:?}", paste_method);
|
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
|
// Perform the paste operation
|
||||||
match paste_method {
|
match paste_method {
|
||||||
PasteMethod::None => {
|
PasteMethod::None => {
|
||||||
info!("PasteMethod::None selected - skipping paste action");
|
info!("PasteMethod::None selected - skipping paste action");
|
||||||
}
|
}
|
||||||
PasteMethod::Direct => paste_via_direct_input(&text)?,
|
PasteMethod::Direct => input::paste_text_direct(&mut enigo, &text)?,
|
||||||
PasteMethod::CtrlV => paste_via_clipboard(&text, &app_handle, send_paste_ctrl_v)?,
|
PasteMethod::CtrlV | PasteMethod::CtrlShiftV | PasteMethod::ShiftInsert => {
|
||||||
PasteMethod::CtrlShiftV => {
|
paste_via_clipboard(&mut enigo, &text, &app_handle, &paste_method)?
|
||||||
paste_via_clipboard(&text, &app_handle, send_paste_ctrl_shift_v)?
|
|
||||||
}
|
|
||||||
PasteMethod::ShiftInsert => {
|
|
||||||
paste_via_clipboard(&text, &app_handle, send_paste_shift_insert)?
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
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 clipboard;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
mod input;
|
||||||
mod llm_client;
|
mod llm_client;
|
||||||
mod managers;
|
mod managers;
|
||||||
mod overlay;
|
mod overlay;
|
||||||
|
|
@ -108,7 +109,11 @@ fn show_main_window(app: &AppHandle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize_core_logic(app_handle: &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(
|
let recording_manager = Arc::new(
|
||||||
AudioRecordingManager::new(app_handle).expect("Failed to initialize recording manager"),
|
AudioRecordingManager::new(app_handle).expect("Failed to initialize recording manager"),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::input;
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
use crate::settings::OverlayPosition;
|
use crate::settings::OverlayPosition;
|
||||||
use enigo::{Enigo, Mouse};
|
|
||||||
use tauri::{AppHandle, Emitter, Manager, PhysicalPosition, PhysicalSize};
|
use tauri::{AppHandle, Emitter, Manager, PhysicalPosition, PhysicalSize};
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[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> {
|
fn get_monitor_with_cursor(app_handle: &AppHandle) -> Option<tauri::Monitor> {
|
||||||
let enigo = Enigo::new(&Default::default());
|
if let Some(mouse_location) = input::get_cursor_position(app_handle) {
|
||||||
if let Ok(enigo) = enigo {
|
if let Ok(monitors) = app_handle.available_monitors() {
|
||||||
if let Ok(mouse_location) = enigo.location() {
|
for monitor in monitors {
|
||||||
if let Ok(monitors) = app_handle.available_monitors() {
|
let is_within =
|
||||||
for monitor in monitors {
|
is_mouse_within_monitor(mouse_location, monitor.position(), monitor.size());
|
||||||
let is_within =
|
if is_within {
|
||||||
is_mouse_within_monitor(mouse_location, monitor.position(), monitor.size());
|
return Some(monitor);
|
||||||
if is_within {
|
|
||||||
return Some(monitor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue