use crate::clipboard::{hash_content, LastHash}; use crate::db::{ClipboardEntry, Database, Settings}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tauri::{Manager, State}; pub struct DbState(pub Arc); pub struct PausedState(pub Arc); pub struct LastHashState(pub LastHash); #[tauri::command] pub fn get_entries(db: State<'_, DbState>, limit: Option) -> Result, String> { db.0.get_entries(limit.unwrap_or(10000)) .map_err(|e| e.to_string()) } #[tauri::command] pub fn search_entries(db: State<'_, DbState>, query: String, limit: Option) -> Result, String> { if query.trim().is_empty() { return get_entries(db, limit); } db.0.search_entries(&query, limit.unwrap_or(10000)) .map_err(|e| e.to_string()) } #[tauri::command] pub fn delete_entry(db: State<'_, DbState>, id: i64) -> Result<(), String> { db.0.delete_entry(id).map_err(|e| e.to_string()) } #[tauri::command] pub fn toggle_pin(db: State<'_, DbState>, id: i64) -> Result { db.0.toggle_pin(id).map_err(|e| e.to_string()) } #[tauri::command] pub fn clear_all(db: State<'_, DbState>) -> Result<(), String> { db.0.clear_all().map_err(|e| e.to_string()) } #[tauri::command] pub fn get_settings(db: State<'_, DbState>) -> Result { db.0.get_settings().map_err(|e| e.to_string()) } #[tauri::command] pub fn set_setting(db: State<'_, DbState>, key: String, value: String) -> Result<(), String> { db.0.set_setting(&key, &value).map_err(|e| e.to_string()) } #[tauri::command] pub fn get_paused(paused: State<'_, PausedState>) -> bool { paused.0.load(Ordering::Relaxed) } #[tauri::command] pub fn set_paused(paused: State<'_, PausedState>, value: bool) { paused.0.store(value, Ordering::Relaxed); } #[tauri::command] pub fn save_window_size(db: State<'_, DbState>, width: i64, height: i64) -> Result<(), String> { db.0.set_setting("window_width", &width.to_string()) .map_err(|e| e.to_string())?; db.0.set_setting("window_height", &height.to_string()) .map_err(|e| e.to_string()) } /// Write content to the system clipboard, update the shared hash so the /// polling thread doesn't re-insert it, hide the window, then simulate /// Cmd+V via AppleScript to paste into the previously-focused app. #[tauri::command] pub async fn paste_and_refocus( app: tauri::AppHandle, last_hash: State<'_, LastHashState>, content: String, ) -> Result<(), String> { // Write to clipboard and update the shared hash BEFORE hiding, // so the polling thread never sees a "new" entry. { let h = hash_content(content.as_bytes()); *last_hash.0.lock().unwrap() = h; } // arboard must be created on the current thread let mut clipboard = arboard::Clipboard::new() .map_err(|e| format!("clipboard open failed: {}", e))?; clipboard .set_text(&content) .map_err(|e| format!("clipboard write failed: {}", e))?; if let Some(window) = app.get_webview_window("main") { let _ = window.hide(); } tokio::time::sleep(std::time::Duration::from_millis(300)).await; #[cfg(target_os = "macos")] { let script = r#" tell application "System Events" set frontApp to name of first application process whose frontmost is true end tell tell application frontApp to activate delay 0.1 tell application "System Events" keystroke "v" using command down end tell "#; let output = tokio::process::Command::new("osascript") .arg("-e") .arg(script) .output() .await .map_err(|e| format!("osascript spawn failed: {}", e))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr); log::warn!("osascript paste failed: {}", stderr); } } Ok(()) }