mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-28 18:06:30 +02:00
wait on screen recording permissions
This commit is contained in:
parent
30e1785fe2
commit
d30cb88651
4 changed files with 70 additions and 33 deletions
|
|
@ -11,18 +11,18 @@ module.exports = {
|
|||
icon: './icons/icon', // .icns extension added automatically
|
||||
appBundleId: 'com.rowboat.app',
|
||||
appCategoryType: 'public.app-category.productivity',
|
||||
osxSign: {
|
||||
batchCodesignCalls: true,
|
||||
optionsForFile: () => ({
|
||||
entitlements: path.join(__dirname, 'entitlements.plist'),
|
||||
'entitlements-inherit': path.join(__dirname, 'entitlements.plist'),
|
||||
}),
|
||||
},
|
||||
osxNotarize: {
|
||||
appleId: process.env.APPLE_ID,
|
||||
appleIdPassword: process.env.APPLE_PASSWORD,
|
||||
teamId: process.env.APPLE_TEAM_ID
|
||||
},
|
||||
// osxSign: {
|
||||
// batchCodesignCalls: true,
|
||||
// optionsForFile: () => ({
|
||||
// entitlements: path.join(__dirname, 'entitlements.plist'),
|
||||
// 'entitlements-inherit': path.join(__dirname, 'entitlements.plist'),
|
||||
// }),
|
||||
// },
|
||||
// osxNotarize: {
|
||||
// appleId: process.env.APPLE_ID,
|
||||
// appleIdPassword: process.env.APPLE_PASSWORD,
|
||||
// teamId: process.env.APPLE_TEAM_ID
|
||||
// },
|
||||
// Since we bundle everything with esbuild, we don't need node_modules at all.
|
||||
// These settings prevent Forge's dependency walker (flora-colossus) from trying
|
||||
// to analyze/copy node_modules, which fails with pnpm's symlinked workspaces.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ipcMain, BrowserWindow, shell, dialog } from 'electron';
|
||||
import { ipcMain, BrowserWindow, shell, dialog, systemPreferences } from 'electron';
|
||||
import { ipc } from '@x/shared';
|
||||
import path from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
|
@ -719,6 +719,16 @@ export function setupIpcHandlers() {
|
|||
|
||||
return { success: false, error: 'Unknown format' };
|
||||
},
|
||||
'meeting:checkScreenPermission': async () => {
|
||||
if (process.platform !== 'darwin') return { granted: true };
|
||||
const status = systemPreferences.getMediaAccessStatus('screen');
|
||||
console.log('[meeting] Screen recording permission status:', status);
|
||||
return { granted: status === 'granted' };
|
||||
},
|
||||
'meeting:openScreenRecordingSettings': async () => {
|
||||
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
|
||||
return { success: true };
|
||||
},
|
||||
'meeting:summarize': async (_event, args) => {
|
||||
const notes = await summarizeMeeting(args.transcript, args.meetingStartTime, args.calendarEventJson);
|
||||
return { notes };
|
||||
|
|
|
|||
|
|
@ -3417,9 +3417,9 @@ function App() {
|
|||
const [meetingSummarizing, setMeetingSummarizing] = useState(false)
|
||||
const [showMeetingPermissions, setShowMeetingPermissions] = useState(false)
|
||||
|
||||
const startMeetingAfterPermissions = useCallback(async () => {
|
||||
setShowMeetingPermissions(false)
|
||||
localStorage.setItem('meeting-permissions-acknowledged', '1')
|
||||
const [checkingPermission, setCheckingPermission] = useState(false)
|
||||
|
||||
const startMeetingNow = useCallback(async () => {
|
||||
const calEvent = pendingCalendarEventRef.current
|
||||
pendingCalendarEventRef.current = undefined
|
||||
const notePath = await meetingTranscription.start(calEvent)
|
||||
|
|
@ -3429,6 +3429,23 @@ function App() {
|
|||
}
|
||||
}, [meetingTranscription, handleVoiceNoteCreated])
|
||||
|
||||
const handleCheckPermissionAndRetry = useCallback(async () => {
|
||||
setCheckingPermission(true)
|
||||
try {
|
||||
const { granted } = await window.ipc.invoke('meeting:checkScreenPermission', null)
|
||||
if (granted) {
|
||||
setShowMeetingPermissions(false)
|
||||
await startMeetingNow()
|
||||
}
|
||||
} finally {
|
||||
setCheckingPermission(false)
|
||||
}
|
||||
}, [startMeetingNow])
|
||||
|
||||
const handleOpenScreenRecordingSettings = useCallback(async () => {
|
||||
await window.ipc.invoke('meeting:openScreenRecordingSettings', null)
|
||||
}, [])
|
||||
|
||||
const handleToggleMeeting = useCallback(async () => {
|
||||
if (meetingTranscription.state === 'recording') {
|
||||
await meetingTranscription.stop()
|
||||
|
|
@ -3477,20 +3494,18 @@ function App() {
|
|||
meetingNotePathRef.current = null
|
||||
}
|
||||
} else if (meetingTranscription.state === 'idle') {
|
||||
// Show permissions modal on first use (macOS only — Windows works out of the box)
|
||||
if (isMac && !localStorage.getItem('meeting-permissions-acknowledged')) {
|
||||
setShowMeetingPermissions(true)
|
||||
return
|
||||
}
|
||||
const calEvent = pendingCalendarEventRef.current
|
||||
pendingCalendarEventRef.current = undefined
|
||||
const notePath = await meetingTranscription.start(calEvent)
|
||||
if (notePath) {
|
||||
meetingNotePathRef.current = notePath
|
||||
await handleVoiceNoteCreated(notePath)
|
||||
// On macOS, check screen recording permission before starting
|
||||
if (isMac) {
|
||||
const result = await window.ipc.invoke('meeting:checkScreenPermission', null)
|
||||
console.log('[meeting] Permission check result:', result)
|
||||
if (!result.granted) {
|
||||
setShowMeetingPermissions(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
await startMeetingNow()
|
||||
}
|
||||
}, [meetingTranscription, handleVoiceNoteCreated])
|
||||
}, [meetingTranscription, handleVoiceNoteCreated, startMeetingNow])
|
||||
handleToggleMeetingRef.current = handleToggleMeeting
|
||||
|
||||
// Listen for calendar block "join meeting & take notes" events
|
||||
|
|
@ -4421,23 +4436,25 @@ function App() {
|
|||
<Dialog open={showMeetingPermissions} onOpenChange={setShowMeetingPermissions}>
|
||||
<DialogContent showCloseButton={false}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Meeting transcription setup</DialogTitle>
|
||||
<DialogTitle>Screen recording permission required</DialogTitle>
|
||||
<DialogDescription>
|
||||
Rowboat needs <strong>Screen Recording</strong> permission to capture meeting audio from other apps (Zoom, Meet, etc.).
|
||||
Rowboat needs <strong>Screen Recording</strong> permission to capture meeting audio from other apps (Zoom, Meet, etc.). This feature won't work without it.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3 text-sm text-muted-foreground">
|
||||
<p>To enable this:</p>
|
||||
<ol className="list-decimal list-inside space-y-1.5">
|
||||
<li>Open <strong>System Settings</strong> → <strong>Privacy & Security</strong></li>
|
||||
<li>Click <strong>Screen Recording</strong></li>
|
||||
<li>Open <strong>System Settings</strong> → <strong>Privacy & Security</strong> → <strong>Screen Recording</strong></li>
|
||||
<li>Toggle on <strong>Rowboat</strong></li>
|
||||
<li>You may need to restart the app after granting permission</li>
|
||||
</ol>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setShowMeetingPermissions(false)}>Cancel</Button>
|
||||
<Button onClick={() => { void startMeetingAfterPermissions() }}>Continue</Button>
|
||||
<Button variant="outline" onClick={() => { void handleOpenScreenRecordingSettings() }}>Open System Settings</Button>
|
||||
<Button onClick={() => { void handleCheckPermissionAndRetry() }} disabled={checkingPermission}>
|
||||
{checkingPermission ? 'Checking...' : 'Check Again'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -501,6 +501,16 @@ const ipcSchemas = {
|
|||
mimeType: z.string(),
|
||||
}),
|
||||
},
|
||||
'meeting:checkScreenPermission': {
|
||||
req: z.null(),
|
||||
res: z.object({
|
||||
granted: z.boolean(),
|
||||
}),
|
||||
},
|
||||
'meeting:openScreenRecordingSettings': {
|
||||
req: z.null(),
|
||||
res: z.object({ success: z.boolean() }),
|
||||
},
|
||||
'meeting:summarize': {
|
||||
req: z.object({
|
||||
transcript: z.string(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue