From 0a08743b0e8ddad9e2c46980a33e565650cfbdfd Mon Sep 17 00:00:00 2001 From: Cooper-X-Oak Date: Thu, 30 Apr 2026 12:55:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(windows):=20=E6=A0=A1=E5=87=86=E9=BA=A6?= =?UTF-8?q?=E5=85=8B=E9=A3=8E=E6=9D=83=E9=99=90=E6=8E=A2=E6=B5=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openless-all/app/src-tauri/src/permissions.rs | 50 +++++++++++++++++++ openless-all/app/src/pages/Settings.tsx | 27 ++++++---- 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/openless-all/app/src-tauri/src/permissions.rs b/openless-all/app/src-tauri/src/permissions.rs index 0511c880..723b594e 100644 --- a/openless-all/app/src-tauri/src/permissions.rs +++ b/openless-all/app/src-tauri/src/permissions.rs @@ -264,6 +264,11 @@ mod platform { /// Windows 的麦克风权限走系统设置 → 隐私 → 麦克风; /// 这里用 cpal 建立一次短生命周期输入流,避免只查设备格式时误报已授权。 pub fn check_microphone() -> PermissionStatus { + if windows_microphone_registry_denied() { + log::warn!("[mic] Windows microphone privacy registry is denied"); + return PermissionStatus::Denied; + } + let host = cpal::default_host(); let Some(device) = host.default_input_device() else { log::warn!("[mic] no default input device"); @@ -334,6 +339,51 @@ mod platform { drop(stream); Ok(()) } + + fn windows_microphone_registry_denied() -> bool { + candidate_microphone_registry_paths() + .into_iter() + .any(|path| registry_value_is_deny(&path)) + } + + fn candidate_microphone_registry_paths() -> Vec { + let mut paths = vec![ + r"HKCU\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone".to_string(), + r"HKCU\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged".to_string(), + r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone".to_string(), + ]; + + if let Ok(exe) = std::env::current_exe() { + if let Some(encoded) = exe.to_str().map(|path| path.replace('\\', "#")) { + paths.push(format!( + r"HKCU\Software\Microsoft\Windows\CurrentVersion\CapabilityAccessManager\ConsentStore\microphone\NonPackaged\{encoded}" + )); + } + } + + paths + } + + fn registry_value_is_deny(path: &str) -> bool { + let output = match std::process::Command::new("reg") + .args(["query", path, "/v", "Value"]) + .output() + { + Ok(output) => output, + Err(err) => { + log::warn!("[mic] reg query failed for {path}: {err}"); + return false; + } + }; + + if !output.status.success() { + return false; + } + + String::from_utf8_lossy(&output.stdout) + .lines() + .any(|line| line.contains("REG_SZ") && line.split_whitespace().any(|part| part == "Deny")) + } } pub use platform::{ diff --git a/openless-all/app/src/pages/Settings.tsx b/openless-all/app/src/pages/Settings.tsx index 5a9aa2f9..e4e38011 100644 --- a/openless-all/app/src/pages/Settings.tsx +++ b/openless-all/app/src/pages/Settings.tsx @@ -502,37 +502,46 @@ function PermissionsSection() { const [hotkey, setHotkey] = useState(null); const { capability } = useHotkeySettings(); - const refresh = async () => { + const refreshPermissions = async () => { const [a, m] = await Promise.all([ checkAccessibilityPermission(), checkMicrophonePermission(), ]); setAccessibility(a); setMicrophone(m); + }; + + const refreshHotkey = async () => { setHotkey(await getHotkeyStatus()); }; useEffect(() => { - refresh(); - const id = window.setInterval(refresh, 1000); - // 用户从系统设置切回来时立刻刷新(不等下一个 1s tick) - const onFocus = () => refresh(); + refreshPermissions(); + refreshHotkey(); + const hotkeyId = window.setInterval(refreshHotkey, 1000); + // 麦克风检查会短暂打开输入流,避免每秒探测导致隐私指示器频繁闪烁。 + const permissionId = window.setInterval(refreshPermissions, 10000); + const onFocus = () => { + refreshPermissions(); + refreshHotkey(); + }; window.addEventListener('focus', onFocus); return () => { - window.clearInterval(id); + window.clearInterval(hotkeyId); + window.clearInterval(permissionId); window.removeEventListener('focus', onFocus); }; }, []); const reRequestAccessibility = async () => { await requestAccessibilityPermission(); - refresh(); + refreshPermissions(); }; const reRequestMicrophone = async () => { if (microphone === 'denied' || microphone === 'restricted') { await openSystemSettings('microphone'); - refresh(); + refreshPermissions(); return; } const status = await requestMicrophonePermission(); @@ -540,7 +549,7 @@ function PermissionsSection() { if (status === 'denied' || status === 'restricted') { await openSystemSettings('microphone'); } - refresh(); + refreshPermissions(); }; const desc = capability?.requiresAccessibilityPermission