Merge pull request #388 from webees/fix/macos-permission-grant-feedback

fix: improve macOS permission grant feedback
This commit is contained in:
andy
2026-05-28 17:46:34 -07:00
committed by GitHub
4 changed files with 64 additions and 42 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
### Maintenance
- chore: versiom bump
- chore: version bump
- chore: update flake.nix for v0.24.3 [skip ci] (#383)
+11
View File
@@ -21,6 +21,17 @@
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"opener:default",
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone"
},
{
"url": "x-apple.systempreferences:com.apple.preference.security?Privacy_Camera"
}
]
},
"fs:default",
"shell:allow-execute",
"shell:allow-kill",
+24 -6
View File
@@ -2,6 +2,7 @@
import { invoke } from "@tauri-apps/api/core";
import { writeText as writeClipboardText } from "@tauri-apps/plugin-clipboard-manager";
import { openUrl } from "@tauri-apps/plugin-opener";
import Color from "color";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -368,19 +369,36 @@ export function SettingsDialog({
async (permissionType: PermissionType) => {
setRequestingPermission(permissionType);
try {
await requestPermission(permissionType);
showSuccessToast(
t("settings.permissions.accessRequested", {
permission: getPermissionDisplayName(permissionType),
}),
const granted = await requestPermission(permissionType);
if (granted) {
showSuccessToast(
permissionType === "microphone"
? t("permissionDialog.grantedToastMicrophone")
: t("permissionDialog.grantedToastCamera"),
);
return;
}
await openUrl(
`x-apple.systempreferences:com.apple.preference.security?${
permissionType === "microphone"
? "Privacy_Microphone"
: "Privacy_Camera"
}`,
);
showErrorToast(
permissionType === "microphone"
? t("permissionDialog.stillNotGrantedMicrophone")
: t("permissionDialog.stillNotGrantedCamera"),
);
} catch (error) {
console.error("Failed to request permission:", error);
showErrorToast(t("permissionDialog.requestFailed"));
} finally {
setRequestingPermission(null);
}
},
[getPermissionDisplayName, requestPermission, t],
[requestPermission, t],
);
const handleSave = useCallback(async () => {
+28 -35
View File
@@ -21,7 +21,7 @@ const loadMacOSPermissions = async () => {
export type PermissionType = "microphone" | "camera";
interface UsePermissionsReturn {
requestPermission: (type: PermissionType) => Promise<void>;
requestPermission: (type: PermissionType) => Promise<boolean>;
isMicrophoneAccessGranted: boolean;
isCameraAccessGranted: boolean;
isInitialized: boolean;
@@ -68,51 +68,44 @@ export function usePermissions(): UsePermissionsReturn {
// Request permission
const requestPermission = useCallback(
async (type: PermissionType): Promise<void> => {
if (!currentPlatform || currentPlatform !== "macos") return;
async (type: PermissionType): Promise<boolean> => {
// Non-macOS platforms do not require this permission gate.
if (!currentPlatform || currentPlatform !== "macos") return true;
// macOS - use the permissions API
try {
const permissions = await loadMacOSPermissions();
if (!permissions) return;
if (!permissions) return false;
const readPermission = async () => {
const granted =
type === "microphone"
? await permissions.checkMicrophonePermission()
: await permissions.checkCameraPermission();
if (type === "microphone") {
setIsMicrophoneAccessGranted(granted);
} else {
setIsCameraAccessGranted(granted);
}
return granted;
};
if (type === "microphone") {
await permissions.requestMicrophonePermission();
// Poll for permission status change
const pollMicPermission = async () => {
const granted = await permissions.checkMicrophonePermission();
setIsMicrophoneAccessGranted(granted);
if (!granted) {
setTimeout(() => {
void pollMicPermission();
}, 1000);
}
};
await pollMicPermission();
}
if (type === "camera") {
} else {
await permissions.requestCameraPermission();
// Poll for permission status change
const pollCamPermission = async () => {
const granted = await permissions.checkCameraPermission();
setIsCameraAccessGranted(granted);
if (!granted) {
setTimeout(() => {
void pollCamPermission();
}, 1000);
}
};
await pollCamPermission();
}
for (let attempt = 0; attempt < 8; attempt += 1) {
const granted = await readPermission();
if (granted) return true;
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return readPermission();
} catch (error) {
console.error(`Failed to request ${type} permission on macOS:`, error);
return false;
}
},
[currentPlatform],