首页
/ Electron systemPreferences 模块深度解析:读取系统偏好、主题色、媒体权限与 macOS 原生通知桥接

Electron systemPreferences 模块深度解析:读取系统偏好、主题色、媒体权限与 macOS 原生通知桥接

2026-09-06 17:51:52作者:鲍丁臣Ursa

本篇以 Electron 仓库的 systemPreferences API 文档为主体,完整覆盖其全部方法、事件与属性的用途、参数、返回值和平台限制,并结合仓库中 shell/browser/api/electron_api_system_preferences.cc 等 C++ 源码与 spec/api-system-preferences-spec.ts 测试用例,解释每个 API 在底层如何映射到 Windows 系统消息、macOS 的 NSNotificationCenter 族、Linux 的 ui::NativeTheme,帮助你在主进程(以及 Utility 进程)中可靠地读取系统外观、主题色、媒体访问状态与系统通知。

一、模块定位与总体架构

systemPreferences 是 Electron 提供在 Main 进程和 Utility 进程 中的系统偏好读取模块,典型用法如下:

const { systemPreferences } = require('electron')

console.log(systemPreferences.getEffectiveAppearance())

从源码结构看,该模块的实现分为三层:

  1. 公共层 shell/browser/api/electron_api_system_preferences.cc:负责用 gin 的 ObjectTemplateBuilder 注册 JS 方法(getColorgetAccentColorgetAnimationSettings 等),并按 BUILDFLAG 编译条件决定哪些方法在哪个平台暴露。例如 getColorgetMediaAccessStatus 仅在 IS_WIN || IS_MAC 时注册,而整个 macOS 通知订阅族(postNotificationsubscribeNotification 等)仅在 IS_MAC 时注册。
  2. 平台层
  3. 注入层Initialize 函数把单例 SystemPreferences 挂到 exports.systemPreferences 上(见 electron_api_system_preferences.cc#L186-L193),该实例通过 cppgc::Persistent 全局驻留,是进程级单例。

此外还有一个值得注意的细节:GetAnimationSettings 是所有平台都注册的方法(见 electron_api_system_preferences.cc#L161-L162),因此它是少数可以跨平台调用的接口之一。

二、Events:系统外观变更的两个信号

Event: accent-color-changed Windows Linux

返回 eventnewColor(string,用户设置的新系统主题色 RGBA 十六进制字符串)。

从源码看三端的实现路径:

  • WindowsSystemPreferences::InitializeWindow() 注册了一个窗口类 Electron_SystemPreferencesHostWindow 并创建一个隐藏弹出窗口(electron_api_system_preferences_win.cc#L156-L186)。注释明确解释了原因:HWND_MESSAGE 窗口收不到广播消息,只有顶层窗口(如 WS_POPUP)才能接收 WM_DWMCOLORIZATIONCOLORCHANGEDWndProc 收到该消息后,把 wparam 中的颜色 DWORD 转成 RGBA 十六进制并 Emit("accent-color-changed", ...),且仅在颜色实际变化时触发(electron_api_system_preferences_win.cc#L200-L213)。
  • LinuxOnNativeThemeUpdated 通过 weak cell 把回调投递到 UI 线程,比对 current_accent_color_ 后发出事件,避免重复派发(electron_api_system_preferences.cc#L85-L100)。
  • macOS:此事件当前未实现(源码中无对应 Emit 分支)。

Event: color-changed Windows

仅 Windows 实现:OnWndProc 在收到 WM_SYSCOLORCHANGEWM_SETTINGCHANGE(且 wparam == SPI_SETHIGHCONTRAST,即高对比度模式切换)时发出(electron_api_system_preferences_win.cc#L215-L224)。适合用来在系统高对比度或系统色变化时重新取用 getColor() 的缓存值。

三、颜色与外观类 API

systemPreferences.getAccentColor()

返回用户当前系统级主题色的 RGBA 十六进制字符串(不带 # 前缀,源码中 ToRGBAHex(..., false) 传入了 include_hash = false):

const color = systemPreferences.getAccentColor() // "aabbccdd"
const red = color.substr(0, 2) // "aa"
const green = color.substr(2, 2) // "bb"
const blue = color.substr(4, 2) // "cc"
const alpha = color.substr(6, 2) // "dd"

文档标注此 API 需要 macOS 10.14 Mojave 或更新版本(底层读取 NSColor.controlAccentColor)。但当前仓库源码已把它扩展到 Windows(GetSystemAccentColor(),见 electron_api_system_preferences_win.cc#L87-L94)与 Linux(读取 ui_theme_->user_color()),且测试用例 spec/api-system-preferences-spec.ts#L8-L19 正是在 win32/linux 平台上验证它返回字符串。因此实际可用性为:Windows、macOS 10.14+、Linux;在取不到主题色时返回空字符串。

systemPreferences.getColor(color) Windows macOS

返回系统颜色设置,格式为带 # 的 RGBA 十六进制(#RRGGBBAA)。测试用例验证其输出匹配 /^#[0-9A-F]{8}$/i(见 spec/api-system-preferences-spec.ts#L28-L30)。

Windows 可取值(对应 Win32 COLOR_* 常量,映射表见 electron_api_system_preferences_win.cc#L98-L129):

名称 含义
3d-dark-shadow / 3d-face / 3d-highlight / 3d-light / 3d-shadow 三维显示元素的阴影/面/高亮等
active-border 活动窗口边框
active-caption / active-caption-gradient 活动窗口标题栏(渐变左/右侧)
app-workspace MDI 应用背景
button-text / caption-text 按钮文字 / 标题栏、滚动条箭头框文字
desktop / disabled-text 桌面背景 / 灰色禁用文字
highlight / highlight-text / hotlight 控件选中项及其文字 / 超链接色
inactive-border / inactive-caption / inactive-caption-gradient / inactive-caption-text 非活动窗口边框、标题栏(渐变左/右)与文字
info-background / info-text tooltip 背景与文字
menu / menu-highlight / menubar / menu-text 菜单背景、平铺菜单高亮、菜单条背景与文字
scrollbar / window / window-frame / window-text 滚动条灰区、窗口背景、边框与文字

macOS 可取值(映射到 NSColor 动态系统色,实现见 electron_api_system_preferences_mac.mm#L533-L607):

control-backgroundcontrolcontrol-textdisabled-control-textfind-highlightgridheader-texthighlightkeyboard-focus-indicatorlabellinkplaceholder-textquaternary-labelscrubber-textured-backgroundsecondary-labelselected-content-backgroundselected-controlselected-control-textselected-menu-item-textselected-text-backgroundselected-textseparatorshadowtertiary-labeltext-backgroundtextunder-page-backgroundunemphasized-selected-content-backgroundunemphasized-selected-text-backgroundunemphasized-selected-textwindow-backgroundwindow-frame-text

其中 find-highlightselected-content-backgroundseparatorunemphasized-selected-content-backgroundunemphasized-selected-text-backgroundunemphasized-selected-text 仅 macOS 10.14 起可用。

两个平台对未知颜色名都会抛出 Unknown color: <name> 错误(测试见 spec/api-system-preferences-spec.ts#L21-L31L177-L183)。

systemPreferences.getSystemColor(color) macOS

返回九个标准系统色之一,格式为 #RRGGBBAAbluebrowngraygreenorangepinkpurpleredyellow

这些颜色会自动适配 vibrancy 以及"增强对比度"、"降低透明度"等辅助功能设置。实现见 electron_api_system_preferences_mac.mm#L433-L460:字符串名映射到 systemBlueColorNSColor 类属性,再经 NSSystemColorToSkColor 转成 SkColor 后输出十六进制;非法名称抛出 Unknown system color: <name>(测试见 spec/api-system-preferences-spec.ts#L159-L175)。

systemPreferences.getEffectiveAppearance() macOS

返回 darklightunknown 三种字符串之一,映射到 NSApplication.effectiveAppearance。源码中通过一个 NSAppearance* 的 gin Converter 完成双向转换:NSAppearanceNameAqua → "light"NSAppearanceNameDarkAqua → "dark"、其它 → "unknown"(见 electron_api_system_preferences_mac.mm#L33-L75L657-L661)。

对应的只读属性 systemPreferences.effectiveAppearance 返回同样的字符串,二者等价(测试同时覆盖属性与函数两种访问方式,见 spec/api-system-preferences-spec.ts#L228-L243)。

systemPreferences.getAnimationSettings()

跨平台方法,返回包含三个布尔字段的对象:

  • shouldRenderRichAnimation:是否应渲染丰富动画,综合会话类型(如远程桌面)与辅助功能设置给出建议;
  • scrollAnimationsEnabledBySystem:按平台决定滚动动画(如 home/end 键产生的动画)是否启用;
  • prefersReducedMotion:基于平台 API 判断用户是否希望减少动态效果。

实现非常直接,三个字段分别取自 Chromium 的 gfx::Animation::ShouldRenderRichAnimation()ScrollAnimationsEnabledBySystem()PrefersReducedMotion()(见 electron_api_system_preferences.cc#L65-L75)。测试确认三个属性都存在且为 boolean(见 spec/api-system-preferences-spec.ts#L305-L313)。

四、macOS 原生通知桥接

Electron 把 macOS 三套通知中心完整暴露给 JS,三者结构一致,只是"总线"不同:

API 族 底层通知中心 典型事件
subscribeNotification / postNotification / unsubscribeNotification NSDistributedNotificationCenter AppleInterfaceThemeChangedNotificationAppleAquaColorVariantChangedAppleColorPreferencesChangedNotificationAppleShowScrollBarsSettingChanged
subscribeLocalNotification / postLocalNotification / unsubscribeLocalNotification NSNotificationCenter(本地) NSUserDefaultsDidChangeNotification
subscribeWorkspaceNotification / postWorkspaceNotification / unsubscribeWorkspaceNotification NSWorkspace.sharedWorkspace.notificationCenter NSWorkspaceDidActivateApplicationNotification

订阅:subscribeNotification(event, callback) macOS

  • event:string 或 null。传 null 时,通知中心不把名称作为投递条件(即接收所有通知);
  • callback(event, userInfo, object)userInfo 是随通知发送的用户信息字典,object 是发送者,目前仅支持 NSString 值(源码中只有当 notification.objectNSString 类时才转换,否则为空字符串,见 electron_api_system_preferences_mac.mm#L261-L281);
  • 返回 number 订阅 ID,用于对应的 unsubscribeXxx(id) 移除。

实现要点:每路订阅分配一个自增 request_id,通过 addObserverForName:object:queue:usingBlock: 注册 block observer,并把 request_id → observer token 存入全局 GetIdMap()queuenil 表示在发通知所在线程同步回调。若 event 既不是 string 也不是 null,会抛出 Must pass null or a string(测试见 spec/api-system-preferences-spec.ts#L126-L157)。SystemPreferences 销毁时会通过 ClearNotificationSubscriptions() 清理全部订阅,防止悬挂回调(见 electron_api_system_preferences_mac.mm#L297-L304)。

发布:postNotification(event, userInfo[, deliverImmediately]) macOS

userInfoRecord<string, any>;可选的 deliverImmediatelytrue 时,即使订阅方应用处于非活动状态也会立即投递。postLocalNotificationpostWorkspaceNotification 签名相同但没有立即投递参数。实现分别是向三个中心调用 postNotificationName:object:userInfo:deliverImmediately: 系列方法(见 electron_api_system_preferences_mac.mm#L171-L241)。

五、macOS NSUserDefaults 访问

这一组 API 让 Electron 应用与 macOS 系统的用户默认值(即 defaults 命令所管理的键值)直接交互。

registerDefaults(defaults) macOS

把一批 key: value 默认值注册进应用的 NSUserDefaults(值类型限定为 string | boolean | number)。实现会把字典中所有值取出,若发现 null/NSNull 会抛错,否则调用 [NSUserDefaults registerDefaults:](见 electron_api_system_preferences_mac.mm#L338-L358)。测试验证了 string/integer/array 的注册与回读,以及非法输入抛错(见 spec/api-system-preferences-spec.ts#L33-L64)。

getUserDefault<Type>(key, type) macOS

type 可取 stringbooleanintegerfloatdoubleurlarraydictionary,返回类型即 UserDefaultTypes 的对应成员。常见键值示例:

  • AppleInterfaceStyle: string
  • AppleAquaColorVariant: integer
  • AppleHighlightColor: string
  • AppleShowScrollBars: string
  • NSNavRecentPlaces: array
  • NSPreferredWebServices: dictionary
  • NSUserDictionaryReplacementItems: array

从实现看,未知键的返回遵循 Cocoa 的"零值"约定:boolean → falseinteger/float/double → 0string/url → ''array → []dictionary → {};而完全非法的 type 返回 undefined——测试用例 spec/api-system-preferences-spec.ts#L75-L87 逐条验证了这些行为。

setUserDefault<Type>(key, type, value) macOS

key 写入 NSUserDefaultstype 必须与 value 的真实类型一致,否则抛出 Unable to convert value to: <type>type 非法时抛出 Invalid type: <type>(实现见 electron_api_system_preferences_mac.mm#L360-L426,测试见 spec/api-system-preferences-spec.ts#L90-L124)。文档给出的示例键是 ApplePressAndHoldEnabledboolean)。

removeUserDefault(key) macOS

移除 key,可用来恢复之前用 setUserDefault 设置的默认/全局值;键不存在时不会抛错(测试见 spec/api-system-preferences-spec.ts#L245-L256)。

六、Touch ID 与辅助功能(macOS

canPromptTouchID()

返回 boolean,表示设备是否具备 Touch ID 能力。实现基于 LAContext:先用 LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch 策略判断可评估性,再确认 biometryType == LABiometryTypeTouchID(见 electron_api_system_preferences_mac.mm#L462-L468)。

promptTouchID(reason)

  • reason:向用户说明请求 Touch ID 认证的原因;
  • 返回 Promise<void>,认证成功则 resolve。
const { systemPreferences } = require('electron')

systemPreferences.promptTouchID('To get consent for a Security-Gated Thing').then(() => {
  console.log('You have successfully authenticated with Touch ID!')
}).catch(err => {
  console.log(err)
})

注意:该 API 本身不提供数据保护,它只是生物识别同意机制。真正保护数据需要你在 Keychain 条目上设置 kSecAccessControlUserPresence 等访问控制常量,例如用 node-keytar 存储加密密钥、仅在 promptTouchID() resolve 后再取用。从源码看,reason 为空会 reject reason must be non-empty,非法 UTF-8(如孤立代理对)会 reject reason must be valid UTF-8(实现见 electron_api_system_preferences_mac.mm#L470-L524,测试见 spec/api-system-preferences-spec.ts#L264-L274)。底层实际调用了 evaluateAccessControl:operation:LAAccessControlOperationUseKeySign,即模拟了一次带用户存在性要求的私钥签名操作。

isTrustedAccessibilityClient(prompt)

  • prompt boolean:若当前进程未获得信任,是否向用户弹出提示;
  • 返回当前进程是否是受信任的辅助功能客户端。

底层是单行调用 AXIsProcessTrustedWithOptions,把 prompt 放入 kAXTrustedCheckOptionPrompt 选项字典(见 electron_api_system_preferences_mac.mm#L526-L531)。典型用途:需要全局快捷键/自动化能力的应用启动时检测并引导用户到"系统设置 → 隐私 → 辅助功能"中勾选授权。

七、媒体访问权限查询

getMediaAccessStatus(mediaType) Windows macOS

  • mediaTypemicrophonecamerascreen
  • 返回 not-determinedgranteddeniedrestrictedunknown 之一。

各平台行为差异(务必按系统版本理解):

  • macOS 10.13 High Sierra 及更早:无需用户同意,microphone/camera 总是返回 granted;10.14 Mojave 起要求同意,10.15 Catalina 起 screen 也需要同意;
  • Windows 10:存在针对所有 win32 应用的全局麦克风/摄像头设置;screen 与旧版 Windows 的所有类型总是返回 granted

实现上,macOS 调用 system_permission_settings::CheckSystemVideoCapturePermission() 等(见 electron_api_system_preferences_mac.mm#L609-L625);Windows 通过 WinRT DeviceAccessInformation.CreateFromDeviceClass 查询 DeviceClass_VideoCapture/DeviceClass_AudioCaptureCurrentStatus,并把 DeviceAccessStatus 映射成上述字符串(见 electron_api_system_preferences_win.cc#L37-L74L138-L154)。

askForMediaAccess(mediaType) macOS

  • mediaTypemicrophonecamera(无效值会 reject);
  • 返回 Promise<boolean>:同意 resolve true,拒绝 resolve false
const granted = await systemPreferences.askForMediaAccess('microphone')

两个关键使用约束:

  1. 必须在 Info.plist 中设置用途说明NSMicrophoneUsageDescriptionNSCameraUsageDescription,这些值会填充到系统权限对话框中,向用户说明请求目的。在 Electron 打包语境下,参考 Application Distribution 的 Rebranding 一节 了解如何把这些键写进已下载的 Electron 二进制的 plist。
  2. 授权状态的变更规则:一旦用户拒绝过,之后不会再次弹窗,Promise 会按既有状态 resolve;如果用户后来在"系统设置"中改回允许,需要重启应用才能生效。macOS 10.13 及更早无需同意,此方法总是 resolve true

实现是把 microphone/camera 解析为 AVMediaTypeAudio/AVMediaTypeVideo 后调用 [AVCaptureDevice requestAccessForMediaType:completionHandler:](见 electron_api_system_preferences_mac.mm#L627-L646)。

八、其他方法

systemPreferences.isSwipeTrackingFromScrollEventsEnabled() macOS

返回 boolean,即系统"Swipe between pages"(在页面间轻扫切换)设置是否开启,直接映射 [NSEvent isSwipeTrackingFromScrollEventsEnabled](见 electron_api_system_preferences_mac.mm#L653-L655)。适合做多页文档类应用的手势适配判断。

Properties

  • systemPreferences.accessibilityDisplayShouldReduceTransparency macOS Deprecated:boolean,对应 NSWorkspace.accessibilityDisplayShouldReduceTransparency已弃用,请改用 nativeTheme.prefersReducedTransparency
  • systemPreferences.effectiveAppearance macOS Readonlydark / light / unknown,与 getEffectiveAppearance() 等价。

九、测试用例与平台可用性速查

仓库的完整测试位于 spec/api-system-preferences-spec.ts,覆盖了:accent color(win32/linux)、getColor(win32/darwin,含非法值抛错与 #RRGGBBAA 格式断言)、registerDefaults/getUserDefault/setUserDefault/removeUserDefault(darwin,含类型冲突与非法类型的异常断言)、三个订阅 API 的非法参数断言、getSystemColor/getColor(darwin 全量颜色名遍历)、effectiveAppearancecanPromptTouchID/promptTouchID(darwin)、isTrustedAccessibilityClientgetMediaAccessStatus(win32/darwin)与 getAnimationSettings(全平台)。

按平台归纳的可用性矩阵:

API Windows macOS Linux
getAccentColor() ✔(10.14+)
getColor() / getMediaAccessStatus()
getSystemColor()
getEffectiveAppearance() / effectiveAppearance
post/subscribe/unsubscribe(Notification/Local/Workspace)
registerDefaults / get/set/removeUserDefault
isSwipeTrackingFromScrollEventsEnabled()
canPromptTouchID() / promptTouchID()
isTrustedAccessibilityClient()
askForMediaAccess()
事件 accent-color-changed
事件 color-changed
getAnimationSettings()

在跨平台应用中使用 systemPreferences 时的实用建议:用 typeof systemPreferences.getColor === 'function' 之类的运行时探测替代纯 process.platform 判断(因为方法注册受编译条件控制,探测更贴近真实能力);把 accent-color-changed 事件与 getAccentColor() 组合,实现主题色的"读一次 + 变更时刷新"缓存模式;在需要摄像头/麦克风的 macOS 应用启动早期就调用 getMediaAccessStatus 决定 UI 引导,并保证 Info.plist 中的用途说明完整。

登录后查看全文
热门项目推荐
相关项目推荐