feat: add “mute while recording” setting (#257)
* feat: mute while recording * refactor: move MuteWhileRecording from AdvancedSettings to DebugSettings
This commit is contained in:
parent
65c1e2dbcb
commit
7c04ee85a7
10 changed files with 112 additions and 0 deletions
46
src-tauri/Cargo.lock
generated
46
src-tauri/Cargo.lock
generated
|
|
@ -947,6 +947,21 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpvc"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30cefc7f947361fa9b9b2709391ac9ab8970f8150bcdbc7dab6287399f5fda34"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"core-foundation 0.10.1",
|
||||
"libpulse-binding",
|
||||
"libpulse-sys",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"windows 0.61.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.3.0"
|
||||
|
|
@ -2264,6 +2279,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"chrono",
|
||||
"cpal",
|
||||
"cpvc",
|
||||
"enigo",
|
||||
"env_logger 0.11.8",
|
||||
"flate2",
|
||||
|
|
@ -3090,6 +3106,33 @@ version = "0.2.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libpulse-binding"
|
||||
version = "2.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "909eb3049e16e373680fe65afe6e2a722ace06b671250cc4849557bc57d6a397"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"libpulse-sys",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libpulse-sys"
|
||||
version = "1.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d74371848b22e989f829cc1621d2ebd74960711557d8b45cfe740f60d0a05e61"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"pkg-config",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.10"
|
||||
|
|
@ -3671,10 +3714,13 @@ version = "0.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2 0.6.3",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ rusqlite = { version = "0.32.1", features = ["bundled"] }
|
|||
tar = "0.4.44"
|
||||
flate2 = "1.0"
|
||||
transcribe-rs = "0.1.4"
|
||||
cpvc = "0.4.1"
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||
tauri-plugin-autostart = "2.5.1"
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ pub fn run() {
|
|||
shortcut::update_custom_words,
|
||||
shortcut::suspend_binding,
|
||||
shortcut::resume_binding,
|
||||
shortcut::change_mute_while_recording_setting,
|
||||
trigger_update_check,
|
||||
commands::cancel_operation,
|
||||
commands::get_app_dir_path,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ pub struct AudioRecordingManager {
|
|||
recorder: Arc<Mutex<Option<AudioRecorder>>>,
|
||||
is_open: Arc<Mutex<bool>>,
|
||||
is_recording: Arc<Mutex<bool>>,
|
||||
initial_volume: Arc<Mutex<Option<u8>>>,
|
||||
}
|
||||
|
||||
impl AudioRecordingManager {
|
||||
|
|
@ -79,6 +80,7 @@ impl AudioRecordingManager {
|
|||
recorder: Arc::new(Mutex::new(None)),
|
||||
is_open: Arc::new(Mutex::new(false)),
|
||||
is_recording: Arc::new(Mutex::new(false)),
|
||||
initial_volume: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
// Always-on? Open immediately.
|
||||
|
|
@ -100,6 +102,16 @@ impl AudioRecordingManager {
|
|||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let settings = get_settings(&self.app_handle);
|
||||
let mut initial_volume_guard = self.initial_volume.lock().unwrap();
|
||||
|
||||
if settings.mute_while_recording {
|
||||
*initial_volume_guard = Some(cpvc::get_system_volume());
|
||||
cpvc::set_system_volume(0);
|
||||
} else {
|
||||
*initial_volume_guard = None;
|
||||
}
|
||||
|
||||
let vad_path = self
|
||||
.app_handle
|
||||
.path()
|
||||
|
|
@ -154,6 +166,12 @@ impl AudioRecordingManager {
|
|||
return;
|
||||
}
|
||||
|
||||
let mut initial_volume_guard = self.initial_volume.lock().unwrap();
|
||||
if let Some(vol) = *initial_volume_guard {
|
||||
cpvc::set_system_volume(vol);
|
||||
}
|
||||
*initial_volume_guard = None;
|
||||
|
||||
if let Some(rec) = self.recorder.lock().unwrap().as_mut() {
|
||||
// If still recording, stop first.
|
||||
if *self.is_recording.lock().unwrap() {
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ pub struct AppSettings {
|
|||
pub paste_method: PasteMethod,
|
||||
#[serde(default)]
|
||||
pub clipboard_handling: ClipboardHandling,
|
||||
#[serde(default)]
|
||||
pub mute_while_recording: bool,
|
||||
}
|
||||
|
||||
fn default_model() -> String {
|
||||
|
|
@ -260,6 +262,7 @@ pub fn get_default_settings() -> AppSettings {
|
|||
history_limit: default_history_limit(),
|
||||
paste_method: PasteMethod::default(),
|
||||
clipboard_handling: ClipboardHandling::default(),
|
||||
mute_while_recording: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -292,6 +292,15 @@ pub fn change_clipboard_handling_setting(app: AppHandle, handling: String) -> Re
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn change_mute_while_recording_setting(app: AppHandle, enabled: bool) -> Result<(), String> {
|
||||
let mut settings = settings::get_settings(&app);
|
||||
settings.mute_while_recording = enabled;
|
||||
settings::write_settings(&app, settings);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determine whether a shortcut string contains at least one non-modifier key.
|
||||
/// We allow single non-modifier keys (e.g. "f5" or "space") but disallow
|
||||
/// modifier-only combos (e.g. "ctrl" or "ctrl+shift").
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { PasteMethodSetting } from "./PasteMethod";
|
|||
import { ClipboardHandlingSetting } from "./ClipboardHandling";
|
||||
import { AlwaysOnMicrophone } from "./AlwaysOnMicrophone";
|
||||
import { SoundPicker } from "./SoundPicker";
|
||||
import { MuteWhileRecording } from "./MuteWhileRecording";
|
||||
|
||||
export const DebugSettings: React.FC = () => {
|
||||
return (
|
||||
|
|
@ -22,6 +23,7 @@ export const DebugSettings: React.FC = () => {
|
|||
<AppDataDirectory descriptionMode="tooltip" grouped={true} />
|
||||
<HistoryLimit descriptionMode="tooltip" grouped={true} />
|
||||
<AlwaysOnMicrophone descriptionMode="tooltip" grouped={true} />
|
||||
<MuteWhileRecording descriptionMode="tooltip" grouped={true} />
|
||||
</SettingsGroup>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
28
src/components/settings/MuteWhileRecording.tsx
Normal file
28
src/components/settings/MuteWhileRecording.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import React from "react";
|
||||
import { ToggleSwitch } from "../ui/ToggleSwitch";
|
||||
import { useSettings } from "../../hooks/useSettings";
|
||||
|
||||
interface MuteWhileRecordingToggleProps {
|
||||
descriptionMode?: "inline" | "tooltip";
|
||||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const MuteWhileRecording: React.FC<MuteWhileRecordingToggleProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||
|
||||
const muteEnabled = getSetting("mute_while_recording") ?? false;
|
||||
|
||||
return (
|
||||
<ToggleSwitch
|
||||
checked={muteEnabled}
|
||||
onChange={(enabled) => updateSetting("mute_while_recording", enabled)}
|
||||
isUpdating={isUpdating("mute_while_recording")}
|
||||
label="Mute while recording"
|
||||
description="Automatically mute all sound output while Handy is recording, then restore it when finished."
|
||||
descriptionMode={descriptionMode}
|
||||
grouped={grouped}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
@ -65,6 +65,7 @@ export const SettingsSchema = z.object({
|
|||
history_limit: z.number().optional().default(5),
|
||||
paste_method: PasteMethodSchema.optional().default("ctrl_v"),
|
||||
clipboard_handling: ClipboardHandlingSchema.optional().default("dont_modify"),
|
||||
mute_while_recording: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
export const BindingResponseSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ const DEFAULT_SETTINGS: Partial<Settings> = {
|
|||
debug_mode: false,
|
||||
custom_words: [],
|
||||
history_limit: 5,
|
||||
mute_while_recording: false,
|
||||
};
|
||||
|
||||
const DEFAULT_AUDIO_DEVICE: AudioDevice = {
|
||||
|
|
@ -101,6 +102,8 @@ const settingUpdaters: {
|
|||
clipboard_handling: (value) =>
|
||||
invoke("change_clipboard_handling_setting", { handling: value }),
|
||||
history_limit: (value) => invoke("update_history_limit", { limit: value }),
|
||||
mute_while_recording: (value) =>
|
||||
invoke("change_mute_while_recording_setting", { enabled: value }),
|
||||
};
|
||||
|
||||
export const useSettingsStore = create<SettingsStore>()(
|
||||
|
|
|
|||
Loading…
Reference in a new issue