Skip to content

Commit db8903b

Browse files
committed
feat: integrate electron-audio-loopback and fix permissions
- Replace Swift-based system audio capture with electron-audio-loopback - Add microphone permission handling for macOS - Fix screen recording permission loop issue - Add NSScreenCaptureUsageDescription to Info.plist - Create Audio Test page for testing mic and system audio - Add navigation links between Audio Test, Agent V1, and Agent V2 - Update MainV2 to use electron-audio-loopback for system audio - Add microphone entitlement com.apple.security.device.audio-input - Create API endpoints for checking and requesting media permissions - Add comprehensive troubleshooting documentation
1 parent ddf83a8 commit db8903b

File tree

8 files changed

+679
-111
lines changed

8 files changed

+679
-111
lines changed

SCREEN_RECORDING_PERMISSION_FIX.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Screen Recording Permission Loop Fix
2+
3+
## Problem
4+
The app repeatedly asks for screen recording permission even when it's already granted in System Preferences.
5+
6+
## Root Causes
7+
8+
### 1. Missing NSScreenCaptureUsageDescription ✅ FIXED
9+
- **Issue**: The Info.plist was missing the required usage description for screen capture
10+
- **Fix**: Added to electron-builder.js:
11+
```javascript
12+
NSScreenCaptureUsageDescription: "Application requests access to screen recording to capture system audio."
13+
```
14+
15+
### 2. TCC Database Issues
16+
The macOS TCC (Transparency, Consent, and Control) database might not be properly tracking your app due to:
17+
18+
#### A. Code Signing Issues
19+
- Development builds are often ad-hoc signed or unsigned
20+
- Each build might appear as a different app to macOS
21+
22+
#### B. Bundle Identifier Changes
23+
- If the bundle ID changes between builds, permissions are reset
24+
- Check that `appId` in electron-builder.js remains consistent
25+
26+
#### C. Hardened Runtime
27+
- Screen recording requires hardened runtime with proper entitlements
28+
29+
## Solutions
30+
31+
### 1. Rebuild the App
32+
After adding NSScreenCaptureUsageDescription:
33+
```bash
34+
composer native:build
35+
```
36+
37+
### 2. Reset TCC Database (for testing)
38+
```bash
39+
# Reset screen recording permissions for your app
40+
tccutil reset ScreenCapture com.clueless.app
41+
42+
# Or reset all permissions
43+
tccutil reset All com.clueless.app
44+
```
45+
46+
### 3. For Development Builds
47+
Add hardened runtime and consistent signing:
48+
49+
1. Edit electron-builder.js and add:
50+
```javascript
51+
mac: {
52+
hardenedRuntime: true,
53+
gatekeeperAssess: false,
54+
entitlementsInherit: 'build/entitlements.mac.plist',
55+
// ... rest of config
56+
}
57+
```
58+
59+
2. For development, you can disable code signing checks:
60+
```bash
61+
# Before running the app
62+
export CSC_IDENTITY_AUTO_DISCOVERY=false
63+
```
64+
65+
### 4. Verify Entitlements
66+
The app needs these entitlements in `build/entitlements.mac.plist`:
67+
-`com.apple.security.cs.allow-jit`
68+
-`com.apple.security.cs.allow-unsigned-executable-memory`
69+
-`com.apple.security.cs.allow-dyld-environment-variables`
70+
-`com.apple.security.device.audio-input`
71+
72+
### 5. Check Bundle ID Consistency
73+
Ensure the bundle ID remains the same across builds:
74+
- Check `appId` in electron-builder.js
75+
- Should be: `com.clueless.app`
76+
77+
## Testing the Fix
78+
79+
1. **Clean Build**:
80+
```bash
81+
rm -rf dist/
82+
composer native:build
83+
```
84+
85+
2. **Install Fresh**:
86+
- Remove old app from Applications
87+
- Install new build
88+
- Grant permissions when prompted
89+
90+
3. **Verify Permissions**:
91+
- System Preferences → Security & Privacy → Screen Recording
92+
- Ensure Clueless is listed and checked
93+
94+
4. **Test Audio Capture**:
95+
- Start a call
96+
- Should not prompt again if permissions are already granted
97+
98+
## Additional Debugging
99+
100+
If the issue persists:
101+
102+
1. **Check Console Logs**:
103+
```bash
104+
# Watch for TCC errors
105+
log stream --predicate 'subsystem == "com.apple.TCC"' | grep -i clueless
106+
```
107+
108+
2. **Verify App Signature**:
109+
```bash
110+
codesign -dv --verbose=4 /Applications/Clueless.app
111+
```
112+
113+
3. **Check Entitlements**:
114+
```bash
115+
codesign -d --entitlements :- /Applications/Clueless.app
116+
```
117+
118+
## Preventing Future Issues
119+
120+
1. **Use Consistent Signing**:
121+
- For development: Use a development certificate
122+
- For production: Use proper distribution certificate
123+
124+
2. **Maintain Bundle ID**:
125+
- Never change `appId` between builds
126+
- Use same ID for development and production
127+
128+
3. **Include All Usage Descriptions**:
129+
- NSMicrophoneUsageDescription ✅
130+
- NSScreenCaptureUsageDescription ✅
131+
- NSCameraUsageDescription ✅
132+
133+
4. **Test Permission Flow**:
134+
- Always test on a clean macOS user account
135+
- Or reset TCC database before testing
136+
137+
## Code Changes Made
138+
139+
1. **electron-builder.js**: Added NSScreenCaptureUsageDescription
140+
2. **entitlements.mac.plist**: Already has required entitlements
141+
3. **system.ts**: Already has permission check APIs
142+
143+
The main fix was adding the missing NSScreenCaptureUsageDescription. This should resolve the permission loop issue after rebuilding the app.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use Inertia\Inertia;
6+
7+
class AudioTestController extends Controller
8+
{
9+
public function index()
10+
{
11+
return Inertia::render('AudioTest/Index');
12+
}
13+
}

resources/js/components/RealtimeAgent/Navigation/MobileMenu.vue

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,15 +100,41 @@
100100
<span class="font-medium">{{ isOverlayMode ? 'ON' : 'OFF' }}</span>
101101
</button>
102102

103-
<!-- Call History Link -->
104-
<button
105-
@click="handleDashboardClick"
106-
:disabled="isActive"
107-
class="w-full border-t border-gray-100 pt-3 text-left text-xs text-gray-600 dark:border-gray-800 dark:text-gray-400"
108-
:class="{ 'cursor-not-allowed opacity-50': isActive }"
109-
>
110-
View Call History →
111-
</button>
103+
<!-- Navigation Links -->
104+
<div class="border-t border-gray-100 pt-3 space-y-2 dark:border-gray-800">
105+
<button
106+
@click="handleDashboardClick"
107+
:disabled="isActive"
108+
class="w-full text-left text-xs text-gray-600 dark:text-gray-400"
109+
:class="{ 'cursor-not-allowed opacity-50': isActive }"
110+
>
111+
View Call History →
112+
</button>
113+
114+
<a
115+
href="/audio-test"
116+
class="block w-full text-left text-xs text-gray-600 dark:text-gray-400"
117+
@click="closeMobileMenu"
118+
>
119+
Audio Test →
120+
</a>
121+
122+
<a
123+
href="/realtime-agent"
124+
class="block w-full text-left text-xs text-gray-600 dark:text-gray-400"
125+
@click="closeMobileMenu"
126+
>
127+
Agent V1 →
128+
</a>
129+
130+
<a
131+
href="/realtime-agent-v2"
132+
class="block w-full text-left text-xs text-gray-600 dark:text-gray-400"
133+
@click="closeMobileMenu"
134+
>
135+
Agent V2 →
136+
</a>
137+
</div>
112138
</div>
113139
</div>
114140
</template>
@@ -179,6 +205,10 @@ const handleDashboardClick = () => {
179205
settingsStore.closeAllDropdowns();
180206
};
181207
208+
const closeMobileMenu = () => {
209+
settingsStore.closeAllDropdowns();
210+
};
211+
182212
const getIconEmoji = (icon?: string) => {
183213
if (!icon) return '📋';
184214

resources/js/components/RealtimeAgent/Navigation/ScreenProtectionToggle.vue

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,6 @@
99
: 'text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100'
1010
: 'text-orange-600 hover:text-orange-700 dark:text-orange-400 dark:hover:text-orange-300',
1111
]"
12-
:title="
13-
!isProtectionSupported
14-
? 'Screen protection not available'
15-
: isProtectionEnabled
16-
? 'Screen protection is ON'
17-
: 'Screen protection is OFF'
18-
"
1912
>
2013
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
2114
<path

resources/js/components/RealtimeAgent/Navigation/TitleBar.vue

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@
6464
Call History
6565
</button>
6666

67+
<!-- Navigation Links -->
68+
<a
69+
href="/audio-test"
70+
class="text-xs font-medium text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
71+
>
72+
Audio Test
73+
</a>
74+
75+
<a
76+
href="/realtime-agent"
77+
class="text-xs font-medium text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
78+
>
79+
Agent V1
80+
</a>
81+
82+
<a
83+
href="/realtime-agent-v2"
84+
class="text-xs font-medium text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100"
85+
>
86+
Agent V2
87+
</a>
88+
6789
<button
6890
@click="toggleSession"
6991
class="rounded-md px-4 py-1.5 text-xs font-medium transition-colors"

0 commit comments

Comments
 (0)