Skip to content
Merged
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = [
"https://github.com/GalaxyRuler/plugins-workspace",
"https://github.com/GalaxyRuler/rodio.git",
"https://github.com/GalaxyRuler/tauri.git",
"https://github.com/GalaxyRuler/vad-rs",
Expand Down
12 changes: 6 additions & 6 deletions src-tauri/Cargo.lock

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

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ gtk = "0.18"
transcribe-rs = { version = "0.3.3", optional = true, features = ["whisper-vulkan"] }

[patch.crates-io]
tauri-plugin-store = { git = "https://github.com/GalaxyRuler/plugins-workspace", rev = "78a7bdaade6f93252228532065fc0d20b3f2d4df" }
tauri-runtime = { git = "https://github.com/GalaxyRuler/tauri.git", rev = "ad9ba9448b82b1a1628333ae06459e4def470db3" }
tauri-runtime-wry = { git = "https://github.com/GalaxyRuler/tauri.git", rev = "ad9ba9448b82b1a1628333ae06459e4def470db3" }
tauri-utils = { git = "https://github.com/GalaxyRuler/tauri.git", rev = "ad9ba9448b82b1a1628333ae06459e4def470db3" }
Expand Down
15 changes: 11 additions & 4 deletions src-tauri/src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,11 @@ fn complete_adaptive_insertion(request: AdaptiveInsertionRequest) {
let verify_adaptive_target =
should_verify_adaptive_target(&context) && should_capture_adaptive_context(&settings);
let target_verified = if verify_adaptive_target {
let current_context =
crate::adaptive::context::capture_context(&settings.adaptive_private_app_patterns);
let context_runtime = crate::runtime_settings::context_runtime(&settings);
let current_context = crate::adaptive::context::capture_context(
context_runtime.private_app_patterns(),
context_runtime.should_capture_nearby_text(),
);
adaptive_target_verified(&context, &current_context)
} else {
true
Expand Down Expand Up @@ -972,8 +975,11 @@ fn classic_target_verified(
return true;
}

let current_context =
crate::adaptive::context::capture_context(&settings.adaptive_private_app_patterns);
let context_runtime = crate::runtime_settings::context_runtime(settings);
let current_context = crate::adaptive::context::capture_context(
context_runtime.private_app_patterns(),
context_runtime.should_capture_nearby_text(),
);
adaptive_target_verified(context, &current_context)
}

Expand Down Expand Up @@ -1771,6 +1777,7 @@ impl ShortcutAction for TranscribeAction {
{
let context = crate::adaptive::context::capture_context(
context_runtime.private_app_patterns(),
context_runtime.should_capture_nearby_text(),
);
if target_privacy_exclusion_blocks_recording(&context) {
target_privacy_excluded = true;
Expand Down
53 changes: 48 additions & 5 deletions src-tauri/src/adaptive/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,21 @@ struct PersistedContextMetadata {
is_sensitive: bool,
}

pub fn capture_context(private_patterns: &[String]) -> CapturedContext {
let context = capture_platform_context();
pub fn capture_context(private_patterns: &[String], capture_nearby_text: bool) -> CapturedContext {
let context = capture_platform_context(capture_nearby_text);
finalize_captured_context(context, private_patterns, capture_nearby_text)
}

fn finalize_captured_context(
mut context: CapturedContext,
private_patterns: &[String],
capture_nearby_text: bool,
) -> CapturedContext {
if !capture_nearby_text {
context.window_title = None;
context.window_title_hash = None;
}

sanitize_context(context, private_patterns)
}

Expand Down Expand Up @@ -139,7 +152,7 @@ fn hash_text(text: &str) -> String {
}

#[cfg(not(target_os = "windows"))]
fn capture_platform_context() -> CapturedContext {
fn capture_platform_context(_capture_nearby_text: bool) -> CapturedContext {
CapturedContext {
captured_at_ms: Utc::now().timestamp_millis(),
process_name: None,
Expand All @@ -153,7 +166,7 @@ fn capture_platform_context() -> CapturedContext {
}

#[cfg(target_os = "windows")]
fn capture_platform_context() -> CapturedContext {
fn capture_platform_context(capture_nearby_text: bool) -> CapturedContext {
use std::path::Path;
use windows::core::PWSTR;
use windows::Win32::Foundation::{CloseHandle, HWND};
Expand All @@ -170,7 +183,9 @@ fn capture_platform_context() -> CapturedContext {
GetWindowThreadProcessId(hwnd, Some(&mut process_id));
}

let window_title = read_window_title(hwnd);
let window_title = capture_nearby_text
.then(|| read_window_title(hwnd))
.flatten();
let window_class = read_window_class(hwnd);
let process_name = if process_id == 0 {
None
Expand Down Expand Up @@ -366,6 +381,34 @@ mod tests {
assert!(context.window_title_hash.is_some());
}

#[test]
fn nearby_text_opt_out_removes_window_title_but_keeps_target_metadata() {
let context = finalize_captured_context(
CapturedContext {
captured_at_ms: 1,
process_name: Some("Code.exe".to_string()),
window_title: Some("task notes beside the cursor".to_string()),
window_title_hash: Some("old-title-hash".to_string()),
window_class: Some("Chrome_WidgetWin_1".to_string()),
target_kind: TargetKind::Technical,
target_fingerprint: Some("code.exe|chrome_widgetwin_1".to_string()),
is_sensitive: false,
},
&[],
false,
);

assert!(context.window_title.is_none());
assert!(context.window_title_hash.is_none());
assert_eq!(context.process_name.as_deref(), Some("Code.exe"));
assert_eq!(context.window_class.as_deref(), Some("Chrome_WidgetWin_1"));
assert_eq!(
context.target_fingerprint.as_deref(),
Some("code.exe|chrome_widgetwin_1")
);
assert_eq!(context.target_kind, TargetKind::Technical);
}

#[test]
fn history_metadata_omits_window_title() {
let context = CapturedContext {
Expand Down
25 changes: 24 additions & 1 deletion src-tauri/src/audio_feedback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,13 @@ fn play_sound_blocking(app: &AppHandle, path: &Path) {
}
}

fn audio_feedback_volume_for_playback(app_settings: &AppSettings) -> f32 {
settings::clamp_audio_feedback_volume(app_settings.audio_feedback_volume)
}

fn play_sound_at_path(app: &AppHandle, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let settings = settings::get_settings(app);
let volume = settings.audio_feedback_volume;
let volume = audio_feedback_volume_for_playback(&settings);
let selected_device = settings.selected_output_device.clone();
play_audio_file(path, selected_device, volume)
}
Expand Down Expand Up @@ -152,3 +156,22 @@ fn play_audio_file(

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn audio_feedback_volume_read_clamps_legacy_value() {
let mut legacy = crate::settings::get_default_settings();

legacy.audio_feedback_volume = f32::NAN;
assert_eq!(audio_feedback_volume_for_playback(&legacy), 1.0);

legacy.audio_feedback_volume = -3.0;
assert_eq!(audio_feedback_volume_for_playback(&legacy), 0.0);

legacy.audio_feedback_volume = 9.0;
assert_eq!(audio_feedback_volume_for_playback(&legacy), 1.0);
}
}
30 changes: 25 additions & 5 deletions src-tauri/src/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use crate::input::{self, EnigoState};
use crate::post_paste_learning::{FocusedTextSnapshot, MAX_FOCUSED_TEXT_CHARS};
#[cfg(target_os = "linux")]
use crate::settings::TypingTool;
use crate::settings::{get_settings, AutoSubmitKey, ClipboardHandling, PasteMethod};
use crate::settings::{
self, get_settings, AppSettings, AutoSubmitKey, ClipboardHandling, PasteMethod,
};
use enigo::{Direction, Enigo, Key, Keyboard};
use log::{info, warn};
use serde::Serialize;
Expand Down Expand Up @@ -738,6 +740,10 @@ fn clipboard_payload_wait_timeout(paste_delay_ms: u64) -> Duration {
Duration::from_millis(paste_delay_ms.max(CLIPBOARD_PAYLOAD_POLL_INTERVAL_MS))
}

fn paste_delay_ms_for_runtime(app_settings: &AppSettings) -> u64 {
settings::clamp_paste_delay_ms(app_settings.paste_delay_ms)
}

fn wait_until_clipboard_owns_payload(
app_handle: &AppHandle,
payload: &str,
Expand Down Expand Up @@ -785,7 +791,7 @@ pub(crate) fn paste_exact_preserving_clipboard_with_cancellation(
) -> Result<(), String> {
let settings = get_settings(app_handle);
let paste_method = settings.paste_method;
let paste_delay_ms = settings.paste_delay_ms;
let paste_delay_ms = paste_delay_ms_for_runtime(&settings);

info!(
"Using exact replacement paste method: {:?}, delay: {}ms",
Expand Down Expand Up @@ -1556,7 +1562,7 @@ fn should_dispatch_paste(expected_target: Option<&str>, current_target: Option<&
}

fn target_still_focused(expected_target: &str) -> bool {
let current_context = crate::adaptive::context::capture_context(&[]);
let current_context = crate::adaptive::context::capture_context(&[], false);
should_dispatch_paste(
Some(expected_target),
current_context.target_fingerprint.as_deref(),
Expand Down Expand Up @@ -1646,7 +1652,7 @@ fn paste_with_auto_learn(
) -> Result<(), String> {
let settings = get_settings(&app_handle);
let paste_method = settings.paste_method;
let paste_delay_ms = settings.paste_delay_ms;
let paste_delay_ms = paste_delay_ms_for_runtime(&settings);
let private_session_enabled = crate::private_session::is_enabled(&app_handle);

// Append trailing space if setting is enabled
Expand Down Expand Up @@ -1736,7 +1742,7 @@ fn paste_with_auto_learn(
}

if should_send_auto_submit(settings.auto_submit, paste_method) {
std::thread::sleep(Duration::from_millis(settings.paste_delay_ms.max(50)));
std::thread::sleep(Duration::from_millis(paste_delay_ms.max(50)));
ensure_not_cancelled(is_cancelled, "auto-submit")?;
send_return_key(&mut enigo, settings.auto_submit_key)?;
}
Expand Down Expand Up @@ -2103,6 +2109,20 @@ mod tests {
);
}

#[test]
fn paste_delay_read_clamps_legacy_value_before_clipboard_wait() {
let mut legacy = crate::settings::get_default_settings();
legacy.paste_delay_ms = 60_000;

let delay_ms = paste_delay_ms_for_runtime(&legacy);

assert_eq!(delay_ms, 2_000);
assert_eq!(
clipboard_payload_wait_timeout(delay_ms),
Duration::from_millis(2_000)
);
}

#[test]
fn receipt_method_matches_paste_method() {
assert_eq!(
Expand Down
8 changes: 4 additions & 4 deletions src-tauri/src/commands/adaptive.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::adaptive::profile::{find_profile_or_default, AdaptiveProfile};
use crate::managers::history::{AdaptiveHistoryMetadata, HistoryEntry, HistoryManager};
use crate::settings::{get_settings, mutate_settings_locked};
use crate::settings::{get_settings, try_write_settings_domain, SettingsWriteDomain};
use std::sync::Arc;
use tauri::{AppHandle, State};

Expand Down Expand Up @@ -65,10 +65,10 @@ pub fn reset_adaptive_correction_memory(_app: AppHandle) -> Result<(), String> {
#[tauri::command]
#[specta::specta]
pub fn set_adaptive_correction_memory_enabled(app: AppHandle, enabled: bool) -> Result<(), String> {
mutate_settings_locked(&app, |settings| {
try_write_settings_domain(&app, SettingsWriteDomain::Adaptive, |settings| {
settings.adaptive_correction_memory_enabled = enabled;
});
Ok(())
Ok(())
})
}

#[tauri::command]
Expand Down
23 changes: 20 additions & 3 deletions src-tauri/src/managers/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ use tauri::{Emitter, Manager};
const STREAM_IDLE_TIMEOUT: Duration = Duration::from_secs(30);
const SELECTED_MICROPHONE_UNAVAILABLE_PREFIX: &str = "Selected microphone unavailable";

fn extra_recording_buffer_delay(settings: &AppSettings) -> Option<Duration> {
let delay_ms =
crate::settings::clamp_extra_recording_buffer_ms(settings.extra_recording_buffer_ms);
(delay_ms > 0).then(|| Duration::from_millis(delay_ms))
}

fn set_mute(mute: bool) {
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
let _ = mute;
Expand Down Expand Up @@ -744,12 +750,12 @@ impl AudioRecordingManager {

// Optionally keep recording for a bit longer to capture trailing audio
let settings = get_settings(&self.app_handle);
if settings.extra_recording_buffer_ms > 0 {
if let Some(delay) = extra_recording_buffer_delay(&settings) {
debug!(
"Extra recording buffer: sleeping {}ms before stopping",
settings.extra_recording_buffer_ms
delay.as_millis()
);
std::thread::sleep(Duration::from_millis(settings.extra_recording_buffer_ms));
std::thread::sleep(delay);
}

let stop_output = if let Some(rec) = self
Expand Down Expand Up @@ -1144,4 +1150,15 @@ mod tests {
assert!(is_default_microphone_selection("Default"));
assert!(!is_default_microphone_selection("Default Array Microphone"));
}

#[test]
fn extra_recording_buffer_read_clamps_legacy_value() {
let mut legacy = crate::settings::get_default_settings();
legacy.extra_recording_buffer_ms = 99_999_999;

assert_eq!(
extra_recording_buffer_delay(&legacy),
Some(Duration::from_millis(2_000))
);
}
}
Loading
Loading