Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ vad-rs = { git = "https://github.com/cjpais/vad-rs", default-features = false }
enigo = "0.6.1"
rodio = { git = "https://github.com/cjpais/rodio.git" }
reqwest = { version = "0.12", features = ["json", "stream"] }
unicode-normalization = "0.1.23"
futures-util = "0.3"
rustfft = "6.4.0"
strsim = "0.11.0"
Expand Down Expand Up @@ -97,6 +98,7 @@ windows = { version = "0.61.3", features = [
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2.1" }

[target.'cfg(target_os = "linux")'.dependencies]
ashpd = "0.12.1"
gtk-layer-shell = { version = "0.8", features = ["v0_6"] }
gtk = "0.18"

Expand Down
28 changes: 26 additions & 2 deletions src-tauri/src/clipboard.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::input::{self, EnigoState};
use crate::settings::{get_settings, ClipboardHandling, PasteMethod};
use enigo::Enigo;
use log::info;
use log::{info, warn};
use std::time::Duration;
use tauri::{AppHandle, Manager};
use tauri_plugin_clipboard_manager::ClipboardExt;

#[cfg(target_os = "linux")]
use crate::remote_desktop;
#[cfg(target_os = "linux")]
use crate::utils::{is_kde_wayland, is_wayland};
#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -121,13 +123,18 @@ fn try_send_key_combo_linux(paste_method: &PasteMethod) -> Result<bool, String>
#[cfg(target_os = "linux")]
fn try_direct_typing_linux(text: &str) -> Result<bool, String> {
if is_wayland() {
// Wayland: prefer remote_desktop, then wtype, then dotool, then ydotool
if is_remote_desktop_available() {
info!("Using Remote Desktop portal for direct text input");
type_text_via_remote_desktop(text)?;
return Ok(true);
}
// KDE Wayland: prefer kwtype (uses KDE Fake Input protocol, supports umlauts)
if is_kde_wayland() && is_kwtype_available() {
info!("Using kwtype for direct text input on KDE Wayland");
type_text_via_kwtype(text)?;
return Ok(true);
}
// Wayland: prefer wtype, then dotool, then ydotool
// Note: wtype doesn't work on KDE (no zwp_virtual_keyboard_manager_v1 support)
if !is_kde_wayland() && is_wtype_available() {
info!("Using wtype for direct text input");
Expand Down Expand Up @@ -220,6 +227,23 @@ fn is_wl_copy_available() -> bool {
.unwrap_or(false)
}

#[cfg(target_os = "linux")]
fn is_remote_desktop_available() -> bool {
remote_desktop::is_available()
}

/// Type text directly via the Remote Desktop portal.
#[cfg(target_os = "linux")]
fn type_text_via_remote_desktop(text: &str) -> Result<(), String> {
match remote_desktop::send_type_text(text) {
Ok(()) => Ok(()),
Err(err) => {
warn!("Remote Desktop direct input failed: {}", err);
Err(format!("remote_desktop failed: {}", err))
}
}
}

/// Type text directly via wtype on Wayland.
#[cfg(target_os = "linux")]
fn type_text_via_wtype(text: &str) -> Result<(), String> {
Expand Down
69 changes: 69 additions & 0 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,72 @@ pub fn initialize_shortcuts(app: AppHandle) -> Result<(), String> {
log::info!("Shortcuts initialized successfully");
Ok(())
}

#[specta::specta]
#[tauri::command]
pub fn is_wayland_active() -> bool {
#[cfg(target_os = "linux")]
{
crate::utils::is_wayland()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}

#[specta::specta]
#[tauri::command]
pub async fn request_remote_desktop_authorization() -> Result<bool, String> {
#[cfg(target_os = "linux")]
{
if !crate::utils::is_wayland() {
return Ok(false);
}

// Run the blocking portal request on a blocking thread to avoid freezing the UI.
match tauri::async_runtime::spawn_blocking(|| {
crate::remote_desktop::request_authorization()
})
.await
{
Ok(Ok(())) => Ok(true),
Ok(Err(err)) => Err(err),
Err(join_err) => Err(format!("remote_desktop join error: {}", join_err)),
}
}
#[cfg(not(target_os = "linux"))]
{
Ok(false)
}
}

#[specta::specta]
#[tauri::command]
pub fn delete_remote_desktop_authorization() -> Result<bool, String> {
#[cfg(target_os = "linux")]
{
if !crate::utils::is_wayland() {
return Ok(false);
}
crate::remote_desktop::delete_authorization();
Ok(true)
}
#[cfg(not(target_os = "linux"))]
{
Ok(false)
}
}

#[specta::specta]
#[tauri::command]
pub fn get_remote_desktop_authorization() -> bool {
#[cfg(target_os = "linux")]
{
crate::remote_desktop::get_authorization()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
11 changes: 11 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ mod input;
mod llm_client;
mod managers;
mod overlay;
#[cfg(target_os = "linux")]
mod remote_desktop;
mod settings;
mod shortcut;
mod signal_handle;
Expand Down Expand Up @@ -115,6 +117,11 @@ fn initialize_core_logic(app_handle: &AppHandle) {
// after onboarding completes. This avoids triggering permission dialogs
// on macOS before the user is ready.

#[cfg(target_os = "linux")]
{
crate::remote_desktop::init_authorization(app_handle);
}

// Initialize the managers
let recording_manager = Arc::new(
AudioRecordingManager::new(app_handle).expect("Failed to initialize recording manager"),
Expand Down Expand Up @@ -288,6 +295,10 @@ pub fn run() {
commands::check_apple_intelligence_available,
commands::initialize_enigo,
commands::initialize_shortcuts,
commands::is_wayland_active,
commands::request_remote_desktop_authorization,
commands::delete_remote_desktop_authorization,
commands::get_remote_desktop_authorization,
commands::models::get_available_models,
commands::models::get_model_info,
commands::models::download_model,
Expand Down
Loading