mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-17 14:27:02 +08:00
feat(electron): meeting recording permissions checks (#11505)
fix AF-2472, AF-2446  
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 939 B |
@@ -473,7 +473,7 @@ function setupMediaListeners() {
|
||||
|
||||
// will be called when the app is ready or when the user has enabled the recording feature in settings
|
||||
export function setupRecordingFeature() {
|
||||
if (!MeetingsSettingsState.value.enabled || !checkRecordingAvailable()) {
|
||||
if (!MeetingsSettingsState.value.enabled || !checkCanRecordMeeting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -767,9 +767,24 @@ export const checkRecordingAvailable = () => {
|
||||
return (version.major === 14 && version.minor >= 2) || version.major > 14;
|
||||
};
|
||||
|
||||
export const checkScreenRecordingPermission = () => {
|
||||
export const checkMeetingPermissions = () => {
|
||||
if (!isMacOS()) {
|
||||
return false;
|
||||
return undefined;
|
||||
}
|
||||
return systemPreferences.getMediaAccessStatus('screen') === 'granted';
|
||||
const mediaTypes = ['screen', 'microphone'] as const;
|
||||
return Object.fromEntries(
|
||||
mediaTypes.map(mediaType => [
|
||||
mediaType,
|
||||
systemPreferences.getMediaAccessStatus(mediaType) === 'granted',
|
||||
])
|
||||
) as Record<(typeof mediaTypes)[number], boolean>;
|
||||
};
|
||||
|
||||
export const checkCanRecordMeeting = () => {
|
||||
const features = checkMeetingPermissions();
|
||||
return (
|
||||
checkRecordingAvailable() &&
|
||||
features &&
|
||||
Object.values(features).every(feature => feature)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ import { shell } from 'electron';
|
||||
import { isMacOS } from '../../shared/utils';
|
||||
import type { NamespaceHandlers } from '../type';
|
||||
import {
|
||||
checkMeetingPermissions,
|
||||
checkRecordingAvailable,
|
||||
checkScreenRecordingPermission,
|
||||
disableRecordingFeature,
|
||||
getRawAudioBuffers,
|
||||
getRecording,
|
||||
@@ -73,13 +73,17 @@ export const recordingHandlers = {
|
||||
disableRecordingFeature: async () => {
|
||||
return disableRecordingFeature();
|
||||
},
|
||||
checkScreenRecordingPermission: async () => {
|
||||
return checkScreenRecordingPermission();
|
||||
checkMeetingPermissions: async () => {
|
||||
return checkMeetingPermissions();
|
||||
},
|
||||
showScreenRecordingPermissionSetting: async () => {
|
||||
showRecordingPermissionSetting: async (_, type: 'screen' | 'microphone') => {
|
||||
const urlMap = {
|
||||
screen: 'Privacy_ScreenCapture',
|
||||
microphone: 'Privacy_Microphone',
|
||||
};
|
||||
if (isMacOS()) {
|
||||
return shell.openExternal(
|
||||
'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture'
|
||||
`x-apple.systempreferences:com.apple.preference.security?${urlMap[type]}`
|
||||
);
|
||||
}
|
||||
// this only available on MacOS
|
||||
|
||||
@@ -15,8 +15,8 @@ import { beforeAppQuit } from '../cleanup';
|
||||
import { logger } from '../logger';
|
||||
import {
|
||||
appGroups$,
|
||||
checkCanRecordMeeting,
|
||||
checkRecordingAvailable,
|
||||
checkScreenRecordingPermission,
|
||||
MeetingsSettingsState,
|
||||
recordingStatus$,
|
||||
startRecording,
|
||||
@@ -89,7 +89,7 @@ class TrayState implements Disposable {
|
||||
// tray's icon
|
||||
icon: NativeImage = nativeImage
|
||||
.createFromPath(icons.tray)
|
||||
.resize({ width: 16, height: 16 });
|
||||
.resize({ width: 18, height: 18 });
|
||||
|
||||
// tray's tooltip
|
||||
tooltip: string = 'AFFiNE';
|
||||
@@ -142,10 +142,17 @@ class TrayState implements Disposable {
|
||||
|
||||
const getConfig = () => {
|
||||
const items: TrayMenuConfig = [];
|
||||
if (
|
||||
checkScreenRecordingPermission() &&
|
||||
MeetingsSettingsState.value.enabled
|
||||
) {
|
||||
if (!MeetingsSettingsState.value.enabled) {
|
||||
items.push({
|
||||
label: 'Meetings are disabled',
|
||||
disabled: true,
|
||||
});
|
||||
} else if (!checkCanRecordMeeting()) {
|
||||
items.push({
|
||||
label: 'Required permissions not granted',
|
||||
disabled: true,
|
||||
});
|
||||
} else {
|
||||
const appGroups = appGroups$.value;
|
||||
const runningAppGroups = appGroups.filter(
|
||||
appGroup => appGroup.isRunning
|
||||
|
||||
@@ -75,6 +75,61 @@ const RecordingModeMenu = () => {
|
||||
);
|
||||
};
|
||||
|
||||
// Add the PermissionSettingRow component
|
||||
interface PermissionSettingRowProps {
|
||||
nameKey: string;
|
||||
descriptionKey: string;
|
||||
permissionSettingKey: string;
|
||||
hasPermission: boolean;
|
||||
onOpenPermissionSetting: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
const PermissionSettingRow = ({
|
||||
nameKey,
|
||||
descriptionKey,
|
||||
permissionSettingKey,
|
||||
hasPermission,
|
||||
onOpenPermissionSetting,
|
||||
}: PermissionSettingRowProps) => {
|
||||
const t = useI18n();
|
||||
|
||||
const handleClick = () => {
|
||||
const result = onOpenPermissionSetting();
|
||||
if (result instanceof Promise) {
|
||||
result.catch(error => {
|
||||
console.error('Error opening permission setting:', error);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingRow
|
||||
name={t[nameKey]()}
|
||||
desc={
|
||||
<>
|
||||
{t[descriptionKey]()}
|
||||
{!hasPermission && (
|
||||
<span onClick={handleClick} className={styles.permissionSetting}>
|
||||
{t[permissionSettingKey]()}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon={
|
||||
hasPermission ? (
|
||||
<DoneIcon />
|
||||
) : (
|
||||
<InformationFillDuotoneIcon className={styles.noPermissionIcon} />
|
||||
)
|
||||
}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</SettingRow>
|
||||
);
|
||||
};
|
||||
|
||||
export const MeetingsSettings = () => {
|
||||
const t = useI18n();
|
||||
const meetingSettingsService = useService(MeetingSettingsService);
|
||||
@@ -82,8 +137,10 @@ export const MeetingsSettings = () => {
|
||||
const [recordingFeatureAvailable, setRecordingFeatureAvailable] =
|
||||
useState(false);
|
||||
|
||||
const [screenRecordingPermission, setScreenRecordingPermission] =
|
||||
useState(false);
|
||||
const [permissions, setPermissions] = useState<{
|
||||
screen: boolean;
|
||||
microphone: boolean;
|
||||
}>();
|
||||
|
||||
const confirmModal = useConfirmModal();
|
||||
|
||||
@@ -97,9 +154,9 @@ export const MeetingsSettings = () => {
|
||||
setRecordingFeatureAvailable(false);
|
||||
});
|
||||
meetingSettingsService
|
||||
.checkScreenRecordingPermission()
|
||||
.checkMeetingPermissions()
|
||||
.then(permission => {
|
||||
setScreenRecordingPermission(permission ?? false);
|
||||
setPermissions(permission);
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
}, [meetingSettingsService]);
|
||||
@@ -121,7 +178,9 @@ export const MeetingsSettings = () => {
|
||||
'com.affine.settings.meetings.record.permission-modal.description'
|
||||
](),
|
||||
onConfirm: async () => {
|
||||
await meetingSettingsService.showScreenRecordingPermissionSetting();
|
||||
await meetingSettingsService.showRecordingPermissionSetting(
|
||||
'screen'
|
||||
);
|
||||
},
|
||||
cancelText: t['com.affine.recording.dismiss'](),
|
||||
confirmButtonOptions: {
|
||||
@@ -146,7 +205,12 @@ export const MeetingsSettings = () => {
|
||||
|
||||
const handleOpenScreenRecordingPermissionSetting =
|
||||
useAsyncCallback(async () => {
|
||||
await meetingSettingsService.showScreenRecordingPermissionSetting();
|
||||
await meetingSettingsService.showRecordingPermissionSetting('screen');
|
||||
}, [meetingSettingsService]);
|
||||
|
||||
const handleOpenMicrophoneRecordingPermissionSetting =
|
||||
useAsyncCallback(async () => {
|
||||
await meetingSettingsService.showRecordingPermissionSetting('microphone');
|
||||
}, [meetingSettingsService]);
|
||||
|
||||
const handleOpenSavedRecordings = useAsyncCallback(async () => {
|
||||
@@ -230,41 +294,24 @@ export const MeetingsSettings = () => {
|
||||
<SettingWrapper
|
||||
title={t['com.affine.settings.meetings.privacy.header']()}
|
||||
>
|
||||
<SettingRow
|
||||
name={t[
|
||||
'com.affine.settings.meetings.privacy.screen-system-audio-recording'
|
||||
]()}
|
||||
desc={
|
||||
<>
|
||||
{t[
|
||||
'com.affine.settings.meetings.privacy.screen-system-audio-recording.description'
|
||||
]()}
|
||||
{!screenRecordingPermission && (
|
||||
<span
|
||||
onClick={handleOpenScreenRecordingPermissionSetting}
|
||||
className={styles.permissionSetting}
|
||||
>
|
||||
{t[
|
||||
'com.affine.settings.meetings.privacy.screen-system-audio-recording.permission-setting'
|
||||
]()}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
<PermissionSettingRow
|
||||
nameKey="com.affine.settings.meetings.privacy.screen-system-audio-recording"
|
||||
descriptionKey="com.affine.settings.meetings.privacy.screen-system-audio-recording.description"
|
||||
permissionSettingKey="com.affine.settings.meetings.privacy.screen-system-audio-recording.permission-setting"
|
||||
hasPermission={permissions?.screen || false}
|
||||
onOpenPermissionSetting={
|
||||
handleOpenScreenRecordingPermissionSetting
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
icon={
|
||||
screenRecordingPermission ? (
|
||||
<DoneIcon />
|
||||
) : (
|
||||
<InformationFillDuotoneIcon
|
||||
className={styles.noPermissionIcon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onClick={handleOpenScreenRecordingPermissionSetting}
|
||||
/>
|
||||
</SettingRow>
|
||||
/>
|
||||
<PermissionSettingRow
|
||||
nameKey="com.affine.settings.meetings.privacy.microphone"
|
||||
descriptionKey="com.affine.settings.meetings.privacy.microphone.description"
|
||||
permissionSettingKey="com.affine.settings.meetings.privacy.microphone.permission-setting"
|
||||
hasPermission={permissions?.microphone || false}
|
||||
onOpenPermissionSetting={
|
||||
handleOpenMicrophoneRecordingPermissionSetting
|
||||
}
|
||||
/>
|
||||
</SettingWrapper>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -103,13 +103,15 @@ export class MeetingSettingsService extends Service {
|
||||
return this.desktopApiService?.handler.recording.checkRecordingAvailable();
|
||||
}
|
||||
|
||||
async checkScreenRecordingPermission() {
|
||||
return this.desktopApiService?.handler.recording.checkScreenRecordingPermission();
|
||||
async checkMeetingPermissions() {
|
||||
return this.desktopApiService?.handler.recording.checkMeetingPermissions();
|
||||
}
|
||||
|
||||
// the following methods are only available on MacOS right?
|
||||
async showScreenRecordingPermissionSetting() {
|
||||
return this.desktopApiService?.handler.recording.showScreenRecordingPermissionSetting();
|
||||
async showRecordingPermissionSetting(type: 'screen' | 'microphone') {
|
||||
return this.desktopApiService?.handler.recording.showRecordingPermissionSetting(
|
||||
type
|
||||
);
|
||||
}
|
||||
|
||||
setRecordingMode = (mode: MeetingSettingsSchema['recordingMode']) => {
|
||||
|
||||
@@ -5411,6 +5411,18 @@ export function useAFFiNEI18N(): {
|
||||
* `Click to allow`
|
||||
*/
|
||||
["com.affine.settings.meetings.privacy.screen-system-audio-recording.permission-setting"](): string;
|
||||
/**
|
||||
* `Microphone`
|
||||
*/
|
||||
["com.affine.settings.meetings.privacy.microphone"](): string;
|
||||
/**
|
||||
* `The Meeting feature requires permission to be used.`
|
||||
*/
|
||||
["com.affine.settings.meetings.privacy.microphone.description"](): string;
|
||||
/**
|
||||
* `Click to allow`
|
||||
*/
|
||||
["com.affine.settings.meetings.privacy.microphone.permission-setting"](): string;
|
||||
/**
|
||||
* `Do nothing`
|
||||
*/
|
||||
|
||||
@@ -1350,6 +1350,9 @@
|
||||
"com.affine.settings.meetings.privacy.screen-system-audio-recording": "Screen & System audio recording",
|
||||
"com.affine.settings.meetings.privacy.screen-system-audio-recording.description": "The Meeting feature requires permission to be used.",
|
||||
"com.affine.settings.meetings.privacy.screen-system-audio-recording.permission-setting": "Click to allow",
|
||||
"com.affine.settings.meetings.privacy.microphone": "Microphone",
|
||||
"com.affine.settings.meetings.privacy.microphone.description": "The Meeting feature requires permission to be used.",
|
||||
"com.affine.settings.meetings.privacy.microphone.permission-setting": "Click to allow",
|
||||
"com.affine.settings.meetings.record.recording-mode.none": "Do nothing",
|
||||
"com.affine.settings.meetings.record.recording-mode.auto-start": "Auto start recording",
|
||||
"com.affine.settings.meetings.record.recording-mode.prompt": "Show a recording prompt",
|
||||
|
||||
Reference in New Issue
Block a user