@lobehub/lobehub 2.0.0-next.252 → 2.0.0-next.254
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/apps/desktop/build/entitlements.mac.plist +9 -0
- package/apps/desktop/resources/locales/zh-CN/dialog.json +5 -1
- package/apps/desktop/resources/locales/zh-CN/menu.json +7 -0
- package/apps/desktop/src/main/controllers/SystemCtr.ts +186 -94
- package/apps/desktop/src/main/controllers/__tests__/SystemCtr.test.ts +200 -31
- package/apps/desktop/src/main/core/browser/Browser.ts +9 -0
- package/apps/desktop/src/main/locales/default/dialog.ts +7 -2
- package/apps/desktop/src/main/locales/default/menu.ts +7 -0
- package/apps/desktop/src/main/menus/impls/macOS.ts +44 -1
- package/apps/desktop/src/main/utils/fullDiskAccess.ts +121 -0
- package/changelog/v1.json +14 -0
- package/locales/ar/discover.json +3 -0
- package/locales/ar/file.json +2 -0
- package/locales/ar/models.json +38 -10
- package/locales/ar/providers.json +0 -1
- package/locales/ar/setting.json +42 -0
- package/locales/bg-BG/discover.json +3 -0
- package/locales/bg-BG/file.json +2 -0
- package/locales/bg-BG/models.json +36 -7
- package/locales/bg-BG/providers.json +0 -1
- package/locales/bg-BG/setting.json +43 -1
- package/locales/de-DE/discover.json +3 -0
- package/locales/de-DE/file.json +2 -0
- package/locales/de-DE/models.json +45 -10
- package/locales/de-DE/providers.json +0 -1
- package/locales/de-DE/setting.json +43 -1
- package/locales/en-US/discover.json +3 -0
- package/locales/en-US/models.json +10 -10
- package/locales/en-US/providers.json +0 -1
- package/locales/en-US/setting.json +43 -1
- package/locales/es-ES/discover.json +3 -0
- package/locales/es-ES/file.json +2 -0
- package/locales/es-ES/models.json +49 -10
- package/locales/es-ES/providers.json +0 -1
- package/locales/es-ES/setting.json +43 -1
- package/locales/fa-IR/discover.json +3 -0
- package/locales/fa-IR/file.json +2 -0
- package/locales/fa-IR/models.json +39 -10
- package/locales/fa-IR/providers.json +0 -1
- package/locales/fa-IR/setting.json +43 -1
- package/locales/fr-FR/discover.json +3 -0
- package/locales/fr-FR/file.json +2 -0
- package/locales/fr-FR/models.json +36 -7
- package/locales/fr-FR/providers.json +0 -1
- package/locales/fr-FR/setting.json +42 -0
- package/locales/it-IT/discover.json +3 -0
- package/locales/it-IT/file.json +2 -0
- package/locales/it-IT/models.json +45 -10
- package/locales/it-IT/providers.json +0 -1
- package/locales/it-IT/setting.json +42 -0
- package/locales/ja-JP/discover.json +3 -0
- package/locales/ja-JP/file.json +2 -0
- package/locales/ja-JP/models.json +42 -7
- package/locales/ja-JP/providers.json +0 -1
- package/locales/ja-JP/setting.json +42 -0
- package/locales/ko-KR/discover.json +3 -0
- package/locales/ko-KR/file.json +2 -0
- package/locales/ko-KR/models.json +48 -7
- package/locales/ko-KR/providers.json +0 -1
- package/locales/ko-KR/setting.json +42 -0
- package/locales/nl-NL/discover.json +3 -0
- package/locales/nl-NL/file.json +2 -0
- package/locales/nl-NL/models.json +4 -6
- package/locales/nl-NL/providers.json +0 -1
- package/locales/nl-NL/setting.json +42 -0
- package/locales/pl-PL/discover.json +3 -0
- package/locales/pl-PL/file.json +2 -0
- package/locales/pl-PL/models.json +36 -7
- package/locales/pl-PL/providers.json +0 -1
- package/locales/pl-PL/setting.json +43 -1
- package/locales/pt-BR/discover.json +3 -0
- package/locales/pt-BR/file.json +2 -0
- package/locales/pt-BR/models.json +47 -6
- package/locales/pt-BR/providers.json +0 -1
- package/locales/pt-BR/setting.json +42 -0
- package/locales/ru-RU/discover.json +3 -0
- package/locales/ru-RU/file.json +2 -0
- package/locales/ru-RU/models.json +36 -7
- package/locales/ru-RU/providers.json +0 -1
- package/locales/ru-RU/setting.json +42 -0
- package/locales/tr-TR/discover.json +3 -0
- package/locales/tr-TR/file.json +2 -0
- package/locales/tr-TR/models.json +36 -5
- package/locales/tr-TR/providers.json +0 -1
- package/locales/tr-TR/setting.json +43 -1
- package/locales/vi-VN/discover.json +3 -0
- package/locales/vi-VN/file.json +2 -0
- package/locales/vi-VN/models.json +5 -5
- package/locales/vi-VN/providers.json +0 -1
- package/locales/vi-VN/setting.json +42 -0
- package/locales/zh-CN/discover.json +3 -3
- package/locales/zh-CN/models.json +38 -9
- package/locales/zh-CN/providers.json +0 -1
- package/locales/zh-CN/setting.json +42 -0
- package/locales/zh-TW/discover.json +3 -0
- package/locales/zh-TW/file.json +2 -0
- package/locales/zh-TW/models.json +54 -10
- package/locales/zh-TW/providers.json +0 -1
- package/locales/zh-TW/setting.json +42 -0
- package/package.json +1 -1
- package/packages/electron-client-ipc/src/events/system.ts +1 -0
- package/src/app/[variants]/(desktop)/desktop-onboarding/features/PermissionsStep.tsx +16 -30
- package/src/app/[variants]/(desktop)/desktop-onboarding/index.tsx +19 -9
- package/src/app/[variants]/(desktop)/desktop-onboarding/storage.ts +49 -0
- package/src/features/ChatInput/ActionBar/Params/Controls.tsx +7 -6
- package/apps/desktop/src/main/controllers/scripts/full-disk-access.applescript +0 -85
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.254](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.253...v2.0.0-next.254)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-10**</sup>
|
|
8
|
+
|
|
9
|
+
#### 💄 Styles
|
|
10
|
+
|
|
11
|
+
- **misc**: Update i18n.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Styles
|
|
19
|
+
|
|
20
|
+
- **misc**: Update i18n, closes [#11360](https://github.com/lobehub/lobe-chat/issues/11360) ([da09825](https://github.com/lobehub/lobe-chat/commit/da09825))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
## [Version 2.0.0-next.253](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.252...v2.0.0-next.253)
|
|
31
|
+
|
|
32
|
+
<sup>Released on **2026-01-09**</sup>
|
|
33
|
+
|
|
34
|
+
#### ✨ Features
|
|
35
|
+
|
|
36
|
+
- **desktop**: Improve macOS permission requests and Full Disk Access detection.
|
|
37
|
+
|
|
38
|
+
#### 🐛 Bug Fixes
|
|
39
|
+
|
|
40
|
+
- **controls**: Update checkbox toggle behavior and pass value to ParamControlWrapper.
|
|
41
|
+
|
|
42
|
+
<br/>
|
|
43
|
+
|
|
44
|
+
<details>
|
|
45
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
46
|
+
|
|
47
|
+
#### What's improved
|
|
48
|
+
|
|
49
|
+
- **desktop**: Improve macOS permission requests and Full Disk Access detection, closes [#11380](https://github.com/lobehub/lobe-chat/issues/11380) ([2d5868f](https://github.com/lobehub/lobe-chat/commit/2d5868f))
|
|
50
|
+
|
|
51
|
+
#### What's fixed
|
|
52
|
+
|
|
53
|
+
- **controls**: Update checkbox toggle behavior and pass value to ParamControlWrapper, closes [#11363](https://github.com/lobehub/lobe-chat/issues/11363) ([1f1ef94](https://github.com/lobehub/lobe-chat/commit/1f1ef94))
|
|
54
|
+
|
|
55
|
+
</details>
|
|
56
|
+
|
|
57
|
+
<div align="right">
|
|
58
|
+
|
|
59
|
+
[](#readme-top)
|
|
60
|
+
|
|
61
|
+
</div>
|
|
62
|
+
|
|
5
63
|
## [Version 2.0.0-next.252](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.251...v2.0.0-next.252)
|
|
6
64
|
|
|
7
65
|
<sup>Released on **2026-01-09**</sup>
|
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
3
|
<plist version="1.0">
|
|
4
4
|
<dict>
|
|
5
|
+
<!-- Hardened Runtime exceptions for Electron -->
|
|
5
6
|
<key>com.apple.security.cs.allow-jit</key>
|
|
6
7
|
<true/>
|
|
7
8
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
8
9
|
<true/>
|
|
9
10
|
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
|
10
11
|
<true/>
|
|
12
|
+
|
|
13
|
+
<!-- Microphone access for voice interactions -->
|
|
14
|
+
<key>com.apple.security.device.audio-input</key>
|
|
15
|
+
<true/>
|
|
16
|
+
|
|
17
|
+
<!-- Camera access (for future video features) -->
|
|
18
|
+
<key>com.apple.security.device.camera</key>
|
|
19
|
+
<true/>
|
|
11
20
|
</dict>
|
|
12
21
|
</plist>
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
"error.detail": "操作未完成。你可以重试,或稍后再试。",
|
|
12
12
|
"error.message": "发生错误",
|
|
13
13
|
"error.title": "错误",
|
|
14
|
+
"fullDiskAccess.message": "LobeHub 需要完全磁盘访问权限来读取文件并启用知识库功能。请在系统设置中授予权限。",
|
|
15
|
+
"fullDiskAccess.openSettings": "打开设置",
|
|
16
|
+
"fullDiskAccess.skip": "稍后",
|
|
17
|
+
"fullDiskAccess.title": "需要完全磁盘访问权限",
|
|
14
18
|
"update.checkingUpdate": "检查新版本",
|
|
15
19
|
"update.checkingUpdateDesc": "正在获取版本信息…",
|
|
16
20
|
"update.downloadAndInstall": "下载并安装",
|
|
@@ -41,4 +45,4 @@
|
|
|
41
45
|
"waitingOAuth.helpText": "如果浏览器没有自动打开,请点击取消后重新尝试",
|
|
42
46
|
"waitingOAuth.retry": "重试",
|
|
43
47
|
"waitingOAuth.title": "等待授权连接"
|
|
44
|
-
}
|
|
48
|
+
}
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
"dev.openStore": "打开本地数据目录",
|
|
8
8
|
"dev.openUpdaterCacheDir": "更新缓存目录",
|
|
9
9
|
"dev.openUserDataDir": "用户配置目录",
|
|
10
|
+
"dev.permissions.accessibility.request": "请求辅助功能权限",
|
|
11
|
+
"dev.permissions.fullDisk.open": "打开「完全磁盘访问」设置",
|
|
12
|
+
"dev.permissions.fullDisk.request": "请求完全磁盘访问权限",
|
|
13
|
+
"dev.permissions.microphone.request": "请求麦克风权限",
|
|
14
|
+
"dev.permissions.notification.request": "请求通知权限",
|
|
15
|
+
"dev.permissions.screen.request": "请求屏幕录制权限",
|
|
16
|
+
"dev.permissions.title": "权限",
|
|
10
17
|
"dev.refreshMenu": "刷新菜单",
|
|
11
18
|
"dev.reload": "重新加载",
|
|
12
19
|
"dev.simulateAutoDownload": "模拟启动后台自动下载更新(3s 下完)",
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { ElectronAppState, ThemeMode } from '@lobechat/electron-client-ipc';
|
|
2
|
-
import { app, dialog, nativeTheme, shell, systemPreferences } from 'electron';
|
|
2
|
+
import { app, desktopCapturer, dialog, nativeTheme, shell, systemPreferences } from 'electron';
|
|
3
3
|
import { macOS } from 'electron-is';
|
|
4
|
-
import { spawn } from 'node:child_process';
|
|
5
|
-
import path from 'node:path';
|
|
6
4
|
import process from 'node:process';
|
|
7
5
|
|
|
6
|
+
import { checkFullDiskAccess, openFullDiskAccessSettings } from '@/utils/fullDiskAccess';
|
|
8
7
|
import { createLogger } from '@/utils/logger';
|
|
9
8
|
|
|
10
9
|
import { ControllerModule, IpcMethod } from './index';
|
|
11
|
-
import fullDiskAccessAutoAddScript from './scripts/full-disk-access.applescript?raw';
|
|
12
10
|
|
|
13
11
|
const logger = createLogger('controllers:SystemCtr');
|
|
14
12
|
|
|
@@ -57,14 +55,98 @@ export default class SystemController extends ControllerModule {
|
|
|
57
55
|
|
|
58
56
|
@IpcMethod()
|
|
59
57
|
requestAccessibilityAccess() {
|
|
60
|
-
if (!macOS())
|
|
61
|
-
|
|
58
|
+
if (!macOS()) {
|
|
59
|
+
logger.info('[Accessibility] Not macOS, returning true');
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
logger.info('[Accessibility] Requesting accessibility access (will prompt if not granted)...');
|
|
63
|
+
// Pass true to prompt user if not already trusted
|
|
64
|
+
const result = systemPreferences.isTrustedAccessibilityClient(true);
|
|
65
|
+
logger.info(`[Accessibility] isTrustedAccessibilityClient(true) returned: ${result}`);
|
|
66
|
+
return result;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
@IpcMethod()
|
|
65
70
|
getAccessibilityStatus() {
|
|
66
|
-
if (!macOS())
|
|
67
|
-
|
|
71
|
+
if (!macOS()) {
|
|
72
|
+
logger.info('[Accessibility] Not macOS, returning true');
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
// Pass false to just check without prompting
|
|
76
|
+
const status = systemPreferences.isTrustedAccessibilityClient(false);
|
|
77
|
+
logger.info(`[Accessibility] Current status: ${status}`);
|
|
78
|
+
return status;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if Full Disk Access is granted.
|
|
83
|
+
* This works by attempting to read a protected system directory.
|
|
84
|
+
* Calling this also registers the app in the TCC database, making it appear
|
|
85
|
+
* in System Settings > Privacy & Security > Full Disk Access.
|
|
86
|
+
*/
|
|
87
|
+
@IpcMethod()
|
|
88
|
+
getFullDiskAccessStatus(): boolean {
|
|
89
|
+
return checkFullDiskAccess();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Prompt the user with a native dialog if Full Disk Access is not granted.
|
|
94
|
+
* Based on https://github.com/inket/FullDiskAccess
|
|
95
|
+
*
|
|
96
|
+
* @param options - Dialog options
|
|
97
|
+
* @returns 'granted' if already granted, 'opened_settings' if user chose to open settings,
|
|
98
|
+
* 'skipped' if user chose to skip, 'cancelled' if dialog was cancelled
|
|
99
|
+
*/
|
|
100
|
+
@IpcMethod()
|
|
101
|
+
async promptFullDiskAccessIfNotGranted(options?: {
|
|
102
|
+
message?: string;
|
|
103
|
+
openSettingsButtonText?: string;
|
|
104
|
+
skipButtonText?: string;
|
|
105
|
+
title?: string;
|
|
106
|
+
}): Promise<'cancelled' | 'granted' | 'opened_settings' | 'skipped'> {
|
|
107
|
+
// Check if already granted
|
|
108
|
+
if (checkFullDiskAccess()) {
|
|
109
|
+
logger.info('[FullDiskAccess] Already granted, skipping prompt');
|
|
110
|
+
|
|
111
|
+
return 'granted';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!macOS()) {
|
|
115
|
+
logger.info('[FullDiskAccess] Not macOS, returning granted');
|
|
116
|
+
return 'granted';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const mainWindow = this.app.browserManager.getMainWindow()?.browserWindow;
|
|
120
|
+
|
|
121
|
+
// Get localized strings
|
|
122
|
+
const t = this.app.i18n.ns('dialog');
|
|
123
|
+
const title = options?.title || t('fullDiskAccess.title');
|
|
124
|
+
const message = options?.message || t('fullDiskAccess.message');
|
|
125
|
+
const openSettingsButtonText =
|
|
126
|
+
options?.openSettingsButtonText || t('fullDiskAccess.openSettings');
|
|
127
|
+
const skipButtonText = options?.skipButtonText || t('fullDiskAccess.skip');
|
|
128
|
+
|
|
129
|
+
logger.info('[FullDiskAccess] Showing native prompt dialog');
|
|
130
|
+
|
|
131
|
+
const result = await dialog.showMessageBox(mainWindow!, {
|
|
132
|
+
buttons: [openSettingsButtonText, skipButtonText],
|
|
133
|
+
cancelId: 1,
|
|
134
|
+
defaultId: 0,
|
|
135
|
+
message: message,
|
|
136
|
+
title: title,
|
|
137
|
+
type: 'info',
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.response === 0) {
|
|
141
|
+
// User chose to open settings
|
|
142
|
+
logger.info('[FullDiskAccess] User chose to open settings');
|
|
143
|
+
await this.openFullDiskAccessSettings();
|
|
144
|
+
return 'opened_settings';
|
|
145
|
+
} else {
|
|
146
|
+
// User chose to skip or cancelled
|
|
147
|
+
logger.info('[FullDiskAccess] User chose to skip');
|
|
148
|
+
return 'skipped';
|
|
149
|
+
}
|
|
68
150
|
}
|
|
69
151
|
|
|
70
152
|
@IpcMethod()
|
|
@@ -75,119 +157,129 @@ export default class SystemController extends ControllerModule {
|
|
|
75
157
|
|
|
76
158
|
@IpcMethod()
|
|
77
159
|
async requestMicrophoneAccess(): Promise<boolean> {
|
|
78
|
-
if (!macOS())
|
|
79
|
-
|
|
160
|
+
if (!macOS()) {
|
|
161
|
+
logger.info('[Microphone] Not macOS, returning true');
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const status = systemPreferences.getMediaAccessStatus('microphone');
|
|
166
|
+
logger.info(`[Microphone] Current status: ${status}`);
|
|
167
|
+
|
|
168
|
+
// Only ask for access if status is 'not-determined'
|
|
169
|
+
// If already denied/restricted, the system won't show a prompt
|
|
170
|
+
if (status === 'not-determined') {
|
|
171
|
+
logger.info('[Microphone] Status is not-determined, calling askForMediaAccess...');
|
|
172
|
+
try {
|
|
173
|
+
const result = await systemPreferences.askForMediaAccess('microphone');
|
|
174
|
+
logger.info(`[Microphone] askForMediaAccess result: ${result}`);
|
|
175
|
+
return result;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
logger.error('[Microphone] askForMediaAccess failed:', error);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (status === 'granted') {
|
|
183
|
+
logger.info('[Microphone] Already granted');
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// If denied or restricted, open System Settings for manual enable
|
|
188
|
+
logger.info(`[Microphone] Status is ${status}, opening System Settings...`);
|
|
189
|
+
await shell.openExternal(
|
|
190
|
+
'x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone',
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return false;
|
|
80
194
|
}
|
|
81
195
|
|
|
82
196
|
@IpcMethod()
|
|
83
197
|
async requestScreenAccess(): Promise<boolean> {
|
|
84
|
-
if (!macOS())
|
|
198
|
+
if (!macOS()) {
|
|
199
|
+
logger.info('[Screen] Not macOS, returning true');
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const status = systemPreferences.getMediaAccessStatus('screen');
|
|
204
|
+
logger.info(`[Screen] Current status: ${status}`);
|
|
205
|
+
|
|
206
|
+
// If already granted, no need to do anything
|
|
207
|
+
if (status === 'granted') {
|
|
208
|
+
logger.info('[Screen] Already granted');
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
85
211
|
|
|
86
212
|
// IMPORTANT:
|
|
87
213
|
// On macOS, the app may NOT appear in "Screen Recording" list until it actually
|
|
88
214
|
// requests the permission once (TCC needs to register this app).
|
|
89
|
-
//
|
|
90
|
-
// 1
|
|
215
|
+
// We use multiple approaches to ensure TCC registration:
|
|
216
|
+
// 1. desktopCapturer.getSources() in main process
|
|
217
|
+
// 2. getDisplayMedia() in renderer as fallback
|
|
218
|
+
|
|
219
|
+
// Approach 1: Use desktopCapturer in main process
|
|
220
|
+
logger.info('[Screen] Attempting TCC registration via desktopCapturer.getSources...');
|
|
91
221
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
222
|
+
// Using a reasonable thumbnail size and both types to ensure TCC registration
|
|
223
|
+
const sources = await desktopCapturer.getSources({
|
|
224
|
+
fetchWindowIcons: true,
|
|
225
|
+
thumbnailSize: { height: 144, width: 256 },
|
|
226
|
+
types: ['screen', 'window'],
|
|
227
|
+
});
|
|
228
|
+
// Access the sources to ensure the capture actually happens
|
|
229
|
+
logger.info(`[Screen] desktopCapturer.getSources returned ${sources.length} sources`);
|
|
97
230
|
} catch (error) {
|
|
98
|
-
logger.warn('
|
|
231
|
+
logger.warn('[Screen] desktopCapturer.getSources failed:', error);
|
|
99
232
|
}
|
|
100
233
|
|
|
101
|
-
// 2
|
|
102
|
-
// This
|
|
234
|
+
// Approach 2: Trigger getDisplayMedia in renderer as additional attempt
|
|
235
|
+
// This shows the OS capture picker which definitely registers with TCC
|
|
236
|
+
logger.info('[Screen] Attempting TCC registration via getDisplayMedia in renderer...');
|
|
103
237
|
try {
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
238
|
+
const mainWindow = this.app.browserManager.getMainWindow()?.browserWindow;
|
|
239
|
+
if (mainWindow && !mainWindow.isDestroyed()) {
|
|
240
|
+
const script = `
|
|
241
|
+
(async () => {
|
|
242
|
+
try {
|
|
243
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
|
|
244
|
+
stream.getTracks().forEach(t => t.stop());
|
|
245
|
+
return true;
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.error('[Screen] getDisplayMedia error:', e);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
116
250
|
})()
|
|
117
|
-
|
|
251
|
+
`.trim();
|
|
118
252
|
|
|
119
|
-
|
|
120
|
-
}
|
|
253
|
+
const result = await mainWindow.webContents.executeJavaScript(script, true);
|
|
254
|
+
logger.info(`[Screen] getDisplayMedia result: ${result}`);
|
|
255
|
+
} else {
|
|
256
|
+
logger.warn('[Screen] Main window not available for getDisplayMedia');
|
|
121
257
|
}
|
|
122
258
|
} catch (error) {
|
|
123
|
-
logger.warn('
|
|
259
|
+
logger.warn('[Screen] getDisplayMedia failed:', error);
|
|
124
260
|
}
|
|
125
261
|
|
|
262
|
+
// Check status after attempts
|
|
263
|
+
const newStatus = systemPreferences.getMediaAccessStatus('screen');
|
|
264
|
+
logger.info(`[Screen] Status after TCC attempts: ${newStatus}`);
|
|
265
|
+
|
|
266
|
+
// Open System Settings for user to manually enable screen recording
|
|
267
|
+
logger.info('[Screen] Opening System Settings for Screen Recording...');
|
|
126
268
|
await shell.openExternal(
|
|
127
269
|
'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture',
|
|
128
270
|
);
|
|
129
271
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
@IpcMethod()
|
|
134
|
-
openFullDiskAccessSettings(payload?: { autoAdd?: boolean }) {
|
|
135
|
-
if (!macOS()) return;
|
|
136
|
-
const { autoAdd = false } = payload || {};
|
|
137
|
-
|
|
138
|
-
// NOTE:
|
|
139
|
-
// - Full Disk Access cannot be requested programmatically like microphone/screen.
|
|
140
|
-
// - On macOS 13+ (Ventura), System Preferences is replaced by System Settings,
|
|
141
|
-
// and deep links may differ. We try multiple known schemes for compatibility.
|
|
142
|
-
const candidates = [
|
|
143
|
-
// macOS 13+ (System Settings)
|
|
144
|
-
'com.apple.settings:Privacy&path=FullDiskAccess',
|
|
145
|
-
// Older macOS (System Preferences)
|
|
146
|
-
'x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles',
|
|
147
|
-
];
|
|
148
|
-
if (autoAdd) this.tryAutoAddFullDiskAccess();
|
|
149
|
-
|
|
150
|
-
(async () => {
|
|
151
|
-
for (const url of candidates) {
|
|
152
|
-
try {
|
|
153
|
-
await shell.openExternal(url);
|
|
154
|
-
return;
|
|
155
|
-
} catch (error) {
|
|
156
|
-
logger.warn(`Failed to open Full Disk Access settings via ${url}`, error);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
})();
|
|
272
|
+
const finalStatus = systemPreferences.getMediaAccessStatus('screen');
|
|
273
|
+
logger.info(`[Screen] Final status: ${finalStatus}`);
|
|
274
|
+
return finalStatus === 'granted';
|
|
160
275
|
}
|
|
161
276
|
|
|
162
277
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* Limitations:
|
|
166
|
-
* - This uses AppleScript UI scripting (System Events) and may require the user to grant
|
|
167
|
-
* additional "Automation" permission (to control System Settings).
|
|
168
|
-
* - UI structure differs across macOS versions/languages; we fall back silently.
|
|
278
|
+
* Open Full Disk Access settings page
|
|
169
279
|
*/
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const exePath = app.getPath('exe');
|
|
174
|
-
// /Applications/App.app/Contents/MacOS/App -> /Applications/App.app
|
|
175
|
-
const appBundlePath = path.resolve(path.dirname(exePath), '..', '..');
|
|
176
|
-
|
|
177
|
-
// Keep the script minimal and resilient; failure should not break onboarding flow.
|
|
178
|
-
const script = fullDiskAccessAutoAddScript.trim();
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const child = spawn('osascript', ['-e', script, appBundlePath], { env: process.env });
|
|
182
|
-
child.on('error', (error) => {
|
|
183
|
-
logger.warn('Full Disk Access auto-add (osascript) failed to start', error);
|
|
184
|
-
});
|
|
185
|
-
child.on('exit', (code) => {
|
|
186
|
-
logger.debug('Full Disk Access auto-add (osascript) exited', { code });
|
|
187
|
-
});
|
|
188
|
-
} catch (error) {
|
|
189
|
-
logger.warn('Full Disk Access auto-add failed', error);
|
|
190
|
-
}
|
|
280
|
+
@IpcMethod()
|
|
281
|
+
async openFullDiskAccessSettings() {
|
|
282
|
+
return openFullDiskAccessSettings();
|
|
191
283
|
}
|
|
192
284
|
|
|
193
285
|
@IpcMethod()
|