Skip to content

Commit daecb0e

Browse files
🔧 refactor: streamline permission handling in Electron and UI enhancements
- Removed extraneous newlines in `electron/main.ts`, streamlining the code for better readability. - Implemented enhanced handling of permissions in the Electron layer. This includes a more detailed message sent to the renderer about missing permissions and the addition of a handler for requesting permissions. - Updated the `permissions-missing` handler to send a detailed message to the renderer about which permissions are missing. - Added a new `request-permission` IPC handler, enabling dynamic permission requests from the renderer process. - Enhanced `requestPermissions` function to handle permission requests more robustly and to open system preferences when necessary. - In `src/App.tsx`, updated the `Permissions` component to accept a permissions message, enabling dynamic display of permission statuses. - Refactored `src/components/Permissions.tsx` to display a detailed permissions card with actionable buttons for granting permissions. This provides a more intuitive and user-friendly interface for managing permissions within the application.
1 parent bb10fd3 commit daecb0e

File tree

4 files changed

+154
-57
lines changed

4 files changed

+154
-57
lines changed

‎electron/main.ts‎

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,6 @@ ipcMain.handle('get-account', async (_) => {
599599
}
600600
});
601601

602-
603602
const updatePreferences = async (newPreferences: Preference[]): Promise<boolean> => {
604603
try {
605604
// Write upserted preferences back to file
@@ -643,8 +642,6 @@ ipcMain.handle('update-preferences', async (_event, newPreferences: Preference[]
643642
}
644643
});
645644

646-
647-
648645
ipcMain.handle('get-device-code', async (_) => {
649646
try {
650647
const deviceCode = await getDeviceCode();
@@ -690,11 +687,13 @@ ipcMain.handle('logout', async (_) => {
690687
}
691688
});
692689

693-
694690
ipcMain.handle('permissions-missing', async (_) => {
695691
try {
692+
const permissionsMissing = await missingPermissions();
693+
696694
if (mainWindow) {
697-
mainWindow.webContents.send('set-window', 'permissions');
695+
const permissions = permissionsMissing.join(', ');
696+
mainWindow.webContents.send('set-window', 'permissions', permissions);
698697
}
699698
} catch (error: any) {
700699
Sentry.captureException(new Error(`Failed to get device code: ${error?.message}`), {
@@ -705,6 +704,24 @@ ipcMain.handle('permissions-missing', async (_) => {
705704
}
706705
});
707706

707+
ipcMain.handle('request-permission', async (_, permission: string) => {
708+
try {
709+
const wasApproved = await requestPermissions(permission);
710+
console.log({ wasApproved })
711+
const missing = await missingPermissions();
712+
if (wasApproved && mainWindow && missing.length > 0) mainWindow.webContents.send('set-window', 'permissions', missing.join(', '));
713+
714+
return wasApproved;
715+
} catch (error: any) {
716+
console.log(error)
717+
Sentry.captureException(new Error(`Failed to request permissions: ${error?.message}`), {
718+
tags: { module: "requestPermissionsIPC" },
719+
extra: { error }
720+
});
721+
return false;
722+
}
723+
});
724+
708725
// The built directory structure
709726
//
710727
// ├─┬─┬ dist
@@ -727,9 +744,9 @@ const missingPermissions = async () => {
727744
const microphoneStatus = await systemPreferences.getMediaAccessStatus('microphone')
728745

729746
let missingList = [];
730-
if (cameraStatus != 'granted') missingList.push('camera');
731-
if (microphoneStatus != 'granted') missingList.push('microphone');
732-
if (screenStatus != 'granted') missingList.push('screen');
747+
if (cameraStatus != 'granted') missingList.push('Camera');
748+
if (microphoneStatus != 'granted') missingList.push('Microphone');
749+
if (screenStatus != 'granted') missingList.push('Screen');
733750

734751
return missingList;
735752
}
@@ -738,23 +755,21 @@ const requestPermissions = async (permission: string): Promise<boolean> => {
738755
try {
739756
if (webcamWindow) webcamWindow.hide();
740757

741-
if (permission === 'camera' || permission === 'microphone') {
742-
const status = await systemPreferences.askForMediaAccess(permission);
758+
console.log('Requesting permissions: ', permission)
759+
760+
if (permission.toLowerCase() === 'camera' || permission.toLowerCase() === 'microphone') {
761+
console.log(`Requesting ${permission} permission`)
762+
const status = await systemPreferences.askForMediaAccess(permission.toLowerCase() as 'camera' | 'microphone');
763+
if (!status && process.platform === 'darwin') {
764+
shell.openExternal(`x-apple.systempreferences:com.apple.preference.security?Privacy_${permission}`);
765+
}
743766
return status;
744-
} else if (permission === 'screen') {
767+
} else if (permission.toLowerCase() === 'screen') {
745768
if (process.platform === 'darwin') {
746769
const screenAccessStatus = systemPreferences.getMediaAccessStatus('screen');
770+
console.log(`Requesting ${permission} permission: ${screenAccessStatus}`)
747771
if (screenAccessStatus !== 'granted') {
748-
dialog.showMessageBox({
749-
type: 'info',
750-
message: 'Please grant screen recording permissions in System Preferences.',
751-
buttons: ['Open System Preferences', 'Cancel']
752-
}).then(({ response }) => {
753-
if (response === 0) {
754-
// Open the Security & Privacy section of System Preferences
755-
shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
756-
}
757-
});
772+
shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
758773
} else {
759774
// already granted
760775
return true;
@@ -885,13 +900,8 @@ function createWindow() {
885900
mainWindow.loadURL(`${VITE_DEV_SERVER_URL}`).then(async () => {
886901
const permissionsMissing = await missingPermissions();
887902
if (permissionsMissing.length > 0) {
888-
mainWindow?.webContents.send('set-window', 'permissions');
889-
console.log({ permissionsMissing })
890-
permissionsMissing.forEach(async (permission: string) => {
891-
const wasApproved = await requestPermissions(permission);
892-
if (wasApproved) permissionsMissing.splice(permissionsMissing.indexOf(permission), 1);
893-
});
894-
903+
const permissions = permissionsMissing.join(', ');
904+
mainWindow?.webContents.send('set-window', 'permissions', permissions);
895905
} else {
896906
// Once the file is loaded, send a message to the renderer with the parameter
897907
if (!mainWindow) return console.log('No main window to send set-window to');
@@ -906,13 +916,8 @@ function createWindow() {
906916
.then(async () => {
907917
const permissionsMissing = await missingPermissions();
908918
if (permissionsMissing.length > 0) {
909-
mainWindow?.webContents.send('set-window', 'permissions');
910-
console.log({ permissionsMissing })
911-
permissionsMissing.forEach(async (permission: string) => {
912-
const wasApproved = await requestPermissions(permission);
913-
if (wasApproved) permissionsMissing.splice(permissionsMissing.indexOf(permission), 1);
914-
});
915-
919+
const permissions = permissionsMissing.join(', ');
920+
mainWindow?.webContents.send('set-window', 'permissions', permissions);
916921
} else {
917922
// Once the file is loaded, send a message to the renderer with the parameter
918923
if (!mainWindow) return console.log('No main window to send set-window to');

‎electron/preload.ts‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@ contextBridge.exposeInMainWorld('electron', {
7676
showCameraWindow: async (show: boolean = true): Promise<void> => {
7777
return await ipcRenderer.invoke('show-camera-window', show)
7878
},
79-
// setUpdatedCameraSource: async (_: any, source: MediaDeviceInfo | null): Promise<void> => {
8079
setUpdatedCameraSource: async (cameraSource: any): Promise<void> => {
8180
console.log({ cameraSource, in: 'preload.ts' })
8281
// console.log({ source, in: 'preload.ts' })
@@ -85,10 +84,9 @@ contextBridge.exposeInMainWorld('electron', {
8584
setPermissionsMissing: async (missing: boolean): Promise<void> => {
8685
return await ipcRenderer.invoke('permissions-missing', missing)
8786
},
88-
// setUpdatedAudioSource: async (audioSource: any): Promise<void> => {
89-
// console.log({ audioSource, in: 'preload.ts' })
90-
// return await ipcRenderer.invoke('set-audio-source', audioSource)
91-
// },
87+
requestPermission: async (permission: string): Promise<void> => {
88+
return await ipcRenderer.invoke('request-permission', permission)
89+
},
9290
})
9391

9492
// `exposeInMainWorld` can't detect attributes and methods of `prototype`, manually patching it.

‎src/App.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ function App() {
135135
}
136136

137137
if (windowType === "permissions") {
138-
return <Permissions />;
138+
return <Permissions permissionsMessage={windowMessage} />;
139139
}
140140

141141
if (windowType === "loading") {
Lines changed: 110 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,123 @@
11
import screenlinkLogo from "../assets/screenlink.svg";
2-
export const Permissions = () => {
2+
import screenlinkLogoDark from "../assets/screenlink-dark.svg";
3+
import { useTheme } from "./theme-provider";
4+
import {
5+
Card,
6+
CardContent,
7+
CardDescription,
8+
CardHeader,
9+
CardTitle,
10+
} from "./ui/card";
11+
import { Label } from "@radix-ui/react-dropdown-menu";
12+
import { Button } from "./ui/button";
13+
import { CheckCircle2, HelpCircle } from "lucide-react";
14+
15+
export const Permissions = ({
16+
permissionsMessage,
17+
}: {
18+
permissionsMessage: string | null;
19+
}) => {
20+
const { theme } = useTheme();
321
return (
422
<>
523
<div className="card">
6-
<div className="flex justify-center">
24+
<div className="sm:mx-auto sm:w-full sm:max-w-sm">
725
<img
8-
className="h-10 w-auto mb-2 -mt-4 cursor-pointer"
9-
src={screenlinkLogo}
26+
className="mx-auto h-10 w-auto"
27+
src={theme === "dark" ? screenlinkLogoDark : screenlinkLogo}
1028
alt="ScreenLink"
11-
onClick={() => {
12-
window.electron.openInBrowser("https://screenlink.io/app");
13-
}}
1429
/>
1530
</div>
16-
<div className="flex flex-col space-y-4 w-4/6 mx-auto">
17-
<span className="text-center flex-grow pb-4">
18-
Enable camera, microphone and screen recording access to get started
19-
</span>
31+
<div className="flex flex-col space-y-4 w-4/6 mx-auto mt-2">
32+
<div className="justify-center my-4">
33+
<p className="mt-2 text-center text-2xl font-bold leading-9 tracking-tight text-gray-300">
34+
Welcome to{" "}
35+
<span className="text-indigo-600 hover:text-indigo-500">
36+
ScreenLink
37+
</span>
38+
</p>
39+
<p className="text-center text-md font-bold leading-9 tracking-tight text-gray-300">
40+
The Open Source Screen Recorder! 🎉{" "}
41+
</p>
42+
</div>
43+
44+
<Card>
45+
<CardHeader>
46+
<CardTitle>Permissions</CardTitle>
47+
<CardDescription>
48+
We need to enable some permissions to get going
49+
</CardDescription>
50+
</CardHeader>
51+
<CardContent className="grid gap-6">
52+
<div className="flex items-center justify-between space-x-2 text-left">
53+
<Label className="flex flex-col space-y-1">
54+
<span>Screen Sharing</span>
55+
<span className="font-normal leading-snug text-muted-foreground">
56+
Allow access to your screen to record your screen
57+
</span>
58+
</Label>
59+
<GrantPermissions
60+
permissionsMessage={permissionsMessage}
61+
source="Screen"
62+
/>
63+
</div>
64+
<div className="flex items-center justify-between space-x-2 text-left">
65+
<Label className="flex flex-col space-y-1">
66+
<span>Camera</span>
67+
<span className="font-normal leading-snug text-muted-foreground">
68+
Allow access to your camera to record your camera
69+
</span>
70+
</Label>
71+
<GrantPermissions
72+
permissionsMessage={permissionsMessage}
73+
source="Camera"
74+
/>
75+
</div>
76+
<div className="flex items-center justify-between space-x-2 text-left">
77+
<Label className="flex flex-col space-y-1">
78+
<span>Microphone</span>
79+
<span className="font-normal leading-snug text-muted-foreground">
80+
Allow access to your microphone to record your microphone
81+
</span>
82+
</Label>
83+
<GrantPermissions
84+
permissionsMessage={permissionsMessage}
85+
source="Microphone"
86+
/>
87+
</div>
88+
</CardContent>
89+
</Card>
2090
</div>
21-
<p className="read-the-docs mb-4">
22-
Screenlink is an open-source video capture tool that lets you record
23-
your screen and camera to share with your team, customers, and
24-
friends.
25-
</p>
2691
</div>
2792
</>
2893
);
2994
};
95+
96+
const GrantPermissions = ({
97+
permissionsMessage,
98+
source,
99+
}: {
100+
permissionsMessage: string | null;
101+
source: string;
102+
}) => {
103+
return permissionsMessage?.includes(source) ? (
104+
<>
105+
<Button
106+
variant="outline"
107+
onClick={() => {
108+
window.electron.requestPermission(source);
109+
}}
110+
>
111+
<HelpCircle className="text-red-500 mr-2 h-4 w-4" />
112+
Grant Permission
113+
</Button>
114+
</>
115+
) : (
116+
<>
117+
<Button variant="outline" disabled>
118+
<CheckCircle2 className="text-green-500 mr-2 h-4 w-4" />
119+
Granted
120+
</Button>
121+
</>
122+
);
123+
};

0 commit comments

Comments
 (0)