electron_api_system_preferences_mac.mm 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. // Copyright (c) 2016 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "shell/browser/api/electron_api_system_preferences.h"
  5. #include <string>
  6. #include <utility>
  7. #import <AVFoundation/AVFoundation.h>
  8. #import <Cocoa/Cocoa.h>
  9. #import <LocalAuthentication/LocalAuthentication.h>
  10. #import <Security/Security.h>
  11. #include "base/apple/scoped_cftyperef.h"
  12. #include "base/containers/flat_map.h"
  13. #include "base/strings/sys_string_conversions.h"
  14. #include "base/task/sequenced_task_runner.h"
  15. #include "base/values.h"
  16. #include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
  17. #include "net/base/apple/url_conversions.h"
  18. #include "shell/browser/mac/dict_util.h"
  19. #include "shell/browser/mac/electron_application.h"
  20. #include "shell/common/color_util.h"
  21. #include "shell/common/gin_converters/gurl_converter.h"
  22. #include "shell/common/gin_converters/value_converter.h"
  23. #include "shell/common/gin_helper/promise.h"
  24. #include "shell/common/node_includes.h"
  25. #include "shell/common/process_util.h"
  26. #include "skia/ext/skia_utils_mac.h"
  27. namespace gin {
  28. template <>
  29. struct Converter<NSAppearance*> {
  30. static bool FromV8(v8::Isolate* isolate,
  31. v8::Local<v8::Value> val,
  32. NSAppearance* __strong* out) {
  33. if (val->IsNull()) {
  34. *out = nil;
  35. return true;
  36. }
  37. std::string name;
  38. if (!gin::ConvertFromV8(isolate, val, &name)) {
  39. return false;
  40. }
  41. if (name == "light") {
  42. *out = [NSAppearance appearanceNamed:NSAppearanceNameAqua];
  43. return true;
  44. } else if (name == "dark") {
  45. *out = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua];
  46. return true;
  47. }
  48. return false;
  49. }
  50. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, NSAppearance* val) {
  51. if (val == nil)
  52. return v8::Null(isolate);
  53. if ([val.name isEqualToString:NSAppearanceNameAqua]) {
  54. return gin::ConvertToV8(isolate, "light");
  55. } else if ([val.name isEqualToString:NSAppearanceNameDarkAqua]) {
  56. return gin::ConvertToV8(isolate, "dark");
  57. }
  58. return gin::ConvertToV8(isolate, "unknown");
  59. }
  60. };
  61. } // namespace gin
  62. namespace electron::api {
  63. namespace {
  64. int g_next_id = 0;
  65. // The map to convert |id| to |int|.
  66. base::flat_map<int, id> g_id_map;
  67. AVMediaType ParseMediaType(const std::string& media_type) {
  68. if (media_type == "camera") {
  69. return AVMediaTypeVideo;
  70. } else if (media_type == "microphone") {
  71. return AVMediaTypeAudio;
  72. } else {
  73. return nil;
  74. }
  75. }
  76. std::string ConvertSystemPermission(
  77. system_permission_settings::SystemPermission value) {
  78. using SystemPermission = system_permission_settings::SystemPermission;
  79. switch (value) {
  80. case SystemPermission::kNotDetermined:
  81. return "not-determined";
  82. case SystemPermission::kRestricted:
  83. return "restricted";
  84. case SystemPermission::kDenied:
  85. return "denied";
  86. case SystemPermission::kAllowed:
  87. return "granted";
  88. default:
  89. return "unknown";
  90. }
  91. }
  92. NSNotificationCenter* GetNotificationCenter(NotificationCenterKind kind) {
  93. switch (kind) {
  94. case NotificationCenterKind::kNSDistributedNotificationCenter:
  95. return [NSDistributedNotificationCenter defaultCenter];
  96. case NotificationCenterKind::kNSNotificationCenter:
  97. return [NSNotificationCenter defaultCenter];
  98. case NotificationCenterKind::kNSWorkspaceNotificationCenter:
  99. return [[NSWorkspace sharedWorkspace] notificationCenter];
  100. default:
  101. return nil;
  102. }
  103. }
  104. } // namespace
  105. void SystemPreferences::PostNotification(const std::string& name,
  106. base::Value::Dict user_info,
  107. gin::Arguments* args) {
  108. bool immediate = false;
  109. args->GetNext(&immediate);
  110. NSDistributedNotificationCenter* center =
  111. [NSDistributedNotificationCenter defaultCenter];
  112. [center
  113. postNotificationName:base::SysUTF8ToNSString(name)
  114. object:nil
  115. userInfo:DictionaryValueToNSDictionary(std::move(user_info))
  116. deliverImmediately:immediate];
  117. }
  118. int SystemPreferences::SubscribeNotification(
  119. v8::Local<v8::Value> maybe_name,
  120. const NotificationCallback& callback) {
  121. return DoSubscribeNotification(
  122. maybe_name, callback,
  123. NotificationCenterKind::kNSDistributedNotificationCenter);
  124. }
  125. void SystemPreferences::UnsubscribeNotification(int request_id) {
  126. DoUnsubscribeNotification(
  127. request_id, NotificationCenterKind::kNSDistributedNotificationCenter);
  128. }
  129. void SystemPreferences::PostLocalNotification(const std::string& name,
  130. base::Value::Dict user_info) {
  131. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  132. [center
  133. postNotificationName:base::SysUTF8ToNSString(name)
  134. object:nil
  135. userInfo:DictionaryValueToNSDictionary(std::move(user_info))];
  136. }
  137. int SystemPreferences::SubscribeLocalNotification(
  138. v8::Local<v8::Value> maybe_name,
  139. const NotificationCallback& callback) {
  140. return DoSubscribeNotification(maybe_name, callback,
  141. NotificationCenterKind::kNSNotificationCenter);
  142. }
  143. void SystemPreferences::UnsubscribeLocalNotification(int request_id) {
  144. DoUnsubscribeNotification(request_id,
  145. NotificationCenterKind::kNSNotificationCenter);
  146. }
  147. void SystemPreferences::PostWorkspaceNotification(const std::string& name,
  148. base::Value::Dict user_info) {
  149. NSNotificationCenter* center =
  150. [[NSWorkspace sharedWorkspace] notificationCenter];
  151. [center
  152. postNotificationName:base::SysUTF8ToNSString(name)
  153. object:nil
  154. userInfo:DictionaryValueToNSDictionary(std::move(user_info))];
  155. }
  156. int SystemPreferences::SubscribeWorkspaceNotification(
  157. v8::Local<v8::Value> maybe_name,
  158. const NotificationCallback& callback) {
  159. return DoSubscribeNotification(
  160. maybe_name, callback,
  161. NotificationCenterKind::kNSWorkspaceNotificationCenter);
  162. }
  163. void SystemPreferences::UnsubscribeWorkspaceNotification(int request_id) {
  164. DoUnsubscribeNotification(
  165. request_id, NotificationCenterKind::kNSWorkspaceNotificationCenter);
  166. }
  167. int SystemPreferences::DoSubscribeNotification(
  168. v8::Local<v8::Value> maybe_name,
  169. const NotificationCallback& callback,
  170. NotificationCenterKind kind) {
  171. int request_id = g_next_id++;
  172. __block NotificationCallback copied_callback = callback;
  173. v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
  174. std::string name_str;
  175. if (!(maybe_name->IsNull() ||
  176. gin::ConvertFromV8(isolate, maybe_name, &name_str))) {
  177. isolate->ThrowException(v8::Exception::Error(
  178. gin::StringToV8(isolate, "Must pass null or a string")));
  179. return -1;
  180. }
  181. auto* name = maybe_name->IsNull() ? nil : base::SysUTF8ToNSString(name_str);
  182. g_id_map[request_id] = [GetNotificationCenter(kind)
  183. addObserverForName:name
  184. object:nil
  185. queue:nil
  186. usingBlock:^(NSNotification* notification) {
  187. std::string object = "";
  188. if ([notification.object isKindOfClass:[NSString class]]) {
  189. object = base::SysNSStringToUTF8(notification.object);
  190. }
  191. if (notification.userInfo) {
  192. copied_callback.Run(
  193. base::SysNSStringToUTF8(notification.name),
  194. base::Value(NSDictionaryToValue(notification.userInfo)),
  195. object);
  196. } else {
  197. copied_callback.Run(
  198. base::SysNSStringToUTF8(notification.name),
  199. base::Value(base::Value::Dict()), object);
  200. }
  201. }];
  202. return request_id;
  203. }
  204. void SystemPreferences::DoUnsubscribeNotification(int request_id,
  205. NotificationCenterKind kind) {
  206. auto iter = g_id_map.find(request_id);
  207. if (iter != g_id_map.end()) {
  208. id observer = iter->second;
  209. [GetNotificationCenter(kind) removeObserver:observer];
  210. g_id_map.erase(iter);
  211. }
  212. }
  213. v8::Local<v8::Value> SystemPreferences::GetUserDefault(
  214. v8::Isolate* isolate,
  215. const std::string& name,
  216. const std::string& type) {
  217. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  218. NSString* key = base::SysUTF8ToNSString(name);
  219. if (type == "string") {
  220. return gin::StringToV8(
  221. isolate, base::SysNSStringToUTF8([defaults stringForKey:key]));
  222. } else if (type == "boolean") {
  223. return v8::Boolean::New(isolate, [defaults boolForKey:key]);
  224. } else if (type == "float") {
  225. return v8::Number::New(isolate, [defaults floatForKey:key]);
  226. } else if (type == "integer") {
  227. return v8::Integer::New(isolate, [defaults integerForKey:key]);
  228. } else if (type == "double") {
  229. return v8::Number::New(isolate, [defaults doubleForKey:key]);
  230. } else if (type == "url") {
  231. return gin::ConvertToV8(isolate,
  232. net::GURLWithNSURL([defaults URLForKey:key]));
  233. } else if (type == "array") {
  234. return gin::ConvertToV8(
  235. isolate, base::Value(NSArrayToValue([defaults arrayForKey:key])));
  236. } else if (type == "dictionary") {
  237. return gin::ConvertToV8(
  238. isolate,
  239. base::Value(NSDictionaryToValue([defaults dictionaryForKey:key])));
  240. } else {
  241. return v8::Undefined(isolate);
  242. }
  243. }
  244. void SystemPreferences::RegisterDefaults(gin::Arguments* args) {
  245. base::Value::Dict dict_value;
  246. if (!args->GetNext(&dict_value)) {
  247. args->ThrowError();
  248. return;
  249. }
  250. @try {
  251. NSDictionary* dict = DictionaryValueToNSDictionary(std::move(dict_value));
  252. for (id key in dict) {
  253. id value = [dict objectForKey:key];
  254. if ([value isKindOfClass:[NSNull class]] || value == nil) {
  255. args->ThrowError();
  256. return;
  257. }
  258. }
  259. [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
  260. } @catch (NSException* exception) {
  261. args->ThrowError();
  262. }
  263. }
  264. void SystemPreferences::SetUserDefault(const std::string& name,
  265. const std::string& type,
  266. gin::Arguments* args) {
  267. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  268. NSString* key = base::SysUTF8ToNSString(name);
  269. if (type == "string") {
  270. std::string value;
  271. if (args->GetNext(&value)) {
  272. [defaults setObject:base::SysUTF8ToNSString(value) forKey:key];
  273. return;
  274. }
  275. } else if (type == "boolean") {
  276. bool value;
  277. v8::Local<v8::Value> next = args->PeekNext();
  278. if (!next.IsEmpty() && next->IsBoolean() && args->GetNext(&value)) {
  279. [defaults setBool:value forKey:key];
  280. return;
  281. }
  282. } else if (type == "float") {
  283. float value;
  284. if (args->GetNext(&value)) {
  285. [defaults setFloat:value forKey:key];
  286. return;
  287. }
  288. } else if (type == "integer") {
  289. int value;
  290. if (args->GetNext(&value)) {
  291. [defaults setInteger:value forKey:key];
  292. return;
  293. }
  294. } else if (type == "double") {
  295. double value;
  296. if (args->GetNext(&value)) {
  297. [defaults setDouble:value forKey:key];
  298. return;
  299. }
  300. } else if (type == "url") {
  301. GURL value;
  302. if (args->GetNext(&value)) {
  303. if (NSURL* url = net::NSURLWithGURL(value)) {
  304. [defaults setURL:url forKey:key];
  305. return;
  306. }
  307. }
  308. } else if (type == "array") {
  309. base::Value value;
  310. if (args->GetNext(&value) && value.is_list()) {
  311. if (NSArray* array = ListValueToNSArray(value.GetList())) {
  312. [defaults setObject:array forKey:key];
  313. return;
  314. }
  315. }
  316. } else if (type == "dictionary") {
  317. base::Value value;
  318. if (args->GetNext(&value) && value.is_dict()) {
  319. if (NSDictionary* dict = DictionaryValueToNSDictionary(value.GetDict())) {
  320. [defaults setObject:dict forKey:key];
  321. return;
  322. }
  323. }
  324. } else {
  325. gin_helper::ErrorThrower(args->isolate())
  326. .ThrowTypeError("Invalid type: " + type);
  327. return;
  328. }
  329. gin_helper::ErrorThrower(args->isolate())
  330. .ThrowTypeError("Unable to convert value to: " + type);
  331. }
  332. std::string SystemPreferences::GetAccentColor() {
  333. NSColor* sysColor = sysColor = [NSColor controlAccentColor];
  334. return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor),
  335. false /* include_hash */);
  336. }
  337. std::string SystemPreferences::GetSystemColor(gin_helper::ErrorThrower thrower,
  338. const std::string& color) {
  339. NSColor* sysColor = nil;
  340. if (color == "blue") {
  341. sysColor = [NSColor systemBlueColor];
  342. } else if (color == "brown") {
  343. sysColor = [NSColor systemBrownColor];
  344. } else if (color == "gray") {
  345. sysColor = [NSColor systemGrayColor];
  346. } else if (color == "green") {
  347. sysColor = [NSColor systemGreenColor];
  348. } else if (color == "orange") {
  349. sysColor = [NSColor systemOrangeColor];
  350. } else if (color == "pink") {
  351. sysColor = [NSColor systemPinkColor];
  352. } else if (color == "purple") {
  353. sysColor = [NSColor systemPurpleColor];
  354. } else if (color == "red") {
  355. sysColor = [NSColor systemRedColor];
  356. } else if (color == "yellow") {
  357. sysColor = [NSColor systemYellowColor];
  358. } else {
  359. thrower.ThrowError("Unknown system color: " + color);
  360. return "";
  361. }
  362. return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor));
  363. }
  364. bool SystemPreferences::CanPromptTouchID() {
  365. LAContext* context = [[LAContext alloc] init];
  366. LAPolicy auth_policy = LAPolicyDeviceOwnerAuthenticationWithBiometricsOrWatch;
  367. if (![context canEvaluatePolicy:auth_policy error:nil])
  368. return false;
  369. return [context biometryType] == LABiometryTypeTouchID;
  370. }
  371. v8::Local<v8::Promise> SystemPreferences::PromptTouchID(
  372. v8::Isolate* isolate,
  373. const std::string& reason) {
  374. gin_helper::Promise<void> promise(isolate);
  375. v8::Local<v8::Promise> handle = promise.GetHandle();
  376. LAContext* context = [[LAContext alloc] init];
  377. base::apple::ScopedCFTypeRef<SecAccessControlRef> access_control =
  378. base::apple::ScopedCFTypeRef<SecAccessControlRef>(
  379. SecAccessControlCreateWithFlags(
  380. kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
  381. kSecAccessControlPrivateKeyUsage | kSecAccessControlUserPresence,
  382. nullptr));
  383. scoped_refptr<base::SequencedTaskRunner> runner =
  384. base::SequencedTaskRunner::GetCurrentDefault();
  385. __block gin_helper::Promise<void> p = std::move(promise);
  386. [context
  387. evaluateAccessControl:access_control.get()
  388. operation:LAAccessControlOperationUseKeySign
  389. localizedReason:[NSString stringWithUTF8String:reason.c_str()]
  390. reply:^(BOOL success, NSError* error) {
  391. // NOLINTBEGIN(bugprone-use-after-move)
  392. if (!success) {
  393. std::string err_msg = std::string(
  394. [error.localizedDescription UTF8String]);
  395. runner->PostTask(
  396. FROM_HERE,
  397. base::BindOnce(
  398. gin_helper::Promise<void>::RejectPromise,
  399. std::move(p), std::move(err_msg)));
  400. } else {
  401. runner->PostTask(
  402. FROM_HERE,
  403. base::BindOnce(
  404. gin_helper::Promise<void>::ResolvePromise,
  405. std::move(p)));
  406. }
  407. // NOLINTEND(bugprone-use-after-move)
  408. }];
  409. return handle;
  410. }
  411. // static
  412. bool SystemPreferences::IsTrustedAccessibilityClient(bool prompt) {
  413. NSDictionary* options =
  414. @{(__bridge id)kAXTrustedCheckOptionPrompt : @(prompt)};
  415. return AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
  416. }
  417. std::string SystemPreferences::GetColor(gin_helper::ErrorThrower thrower,
  418. const std::string& color) {
  419. NSColor* sysColor = nil;
  420. if (color == "control-background") {
  421. sysColor = [NSColor controlBackgroundColor];
  422. } else if (color == "control") {
  423. sysColor = [NSColor controlColor];
  424. } else if (color == "control-text") {
  425. sysColor = [NSColor controlTextColor];
  426. } else if (color == "disabled-control-text") {
  427. sysColor = [NSColor disabledControlTextColor];
  428. } else if (color == "find-highlight") {
  429. sysColor = [NSColor findHighlightColor];
  430. } else if (color == "grid") {
  431. sysColor = [NSColor gridColor];
  432. } else if (color == "header-text") {
  433. sysColor = [NSColor headerTextColor];
  434. } else if (color == "highlight") {
  435. sysColor = [NSColor highlightColor];
  436. } else if (color == "keyboard-focus-indicator") {
  437. sysColor = [NSColor keyboardFocusIndicatorColor];
  438. } else if (color == "label") {
  439. sysColor = [NSColor labelColor];
  440. } else if (color == "link") {
  441. sysColor = [NSColor linkColor];
  442. } else if (color == "placeholder-text") {
  443. sysColor = [NSColor placeholderTextColor];
  444. } else if (color == "quaternary-label") {
  445. sysColor = [NSColor quaternaryLabelColor];
  446. } else if (color == "scrubber-textured-background") {
  447. sysColor = [NSColor scrubberTexturedBackgroundColor];
  448. } else if (color == "secondary-label") {
  449. sysColor = [NSColor secondaryLabelColor];
  450. } else if (color == "selected-content-background") {
  451. sysColor = [NSColor selectedContentBackgroundColor];
  452. } else if (color == "selected-control") {
  453. sysColor = [NSColor selectedControlColor];
  454. } else if (color == "selected-control-text") {
  455. sysColor = [NSColor selectedControlTextColor];
  456. } else if (color == "selected-menu-item-text") {
  457. sysColor = [NSColor selectedMenuItemTextColor];
  458. } else if (color == "selected-text-background") {
  459. sysColor = [NSColor selectedTextBackgroundColor];
  460. } else if (color == "selected-text") {
  461. sysColor = [NSColor selectedTextColor];
  462. } else if (color == "separator") {
  463. sysColor = [NSColor separatorColor];
  464. } else if (color == "shadow") {
  465. sysColor = [NSColor shadowColor];
  466. } else if (color == "tertiary-label") {
  467. sysColor = [NSColor tertiaryLabelColor];
  468. } else if (color == "text-background") {
  469. sysColor = [NSColor textBackgroundColor];
  470. } else if (color == "text") {
  471. sysColor = [NSColor textColor];
  472. } else if (color == "under-page-background") {
  473. sysColor = [NSColor underPageBackgroundColor];
  474. } else if (color == "unemphasized-selected-content-background") {
  475. sysColor = [NSColor unemphasizedSelectedContentBackgroundColor];
  476. } else if (color == "unemphasized-selected-text-background") {
  477. sysColor = [NSColor unemphasizedSelectedTextBackgroundColor];
  478. } else if (color == "unemphasized-selected-text") {
  479. sysColor = [NSColor unemphasizedSelectedTextColor];
  480. } else if (color == "window-background") {
  481. sysColor = [NSColor windowBackgroundColor];
  482. } else if (color == "window-frame-text") {
  483. sysColor = [NSColor windowFrameTextColor];
  484. } else {
  485. thrower.ThrowError("Unknown color: " + color);
  486. }
  487. if (sysColor)
  488. return ToRGBAHex(skia::NSSystemColorToSkColor(sysColor));
  489. return "";
  490. }
  491. std::string SystemPreferences::GetMediaAccessStatus(
  492. gin_helper::ErrorThrower thrower,
  493. const std::string& media_type) {
  494. if (media_type == "camera") {
  495. return ConvertSystemPermission(
  496. system_permission_settings::CheckSystemVideoCapturePermission());
  497. } else if (media_type == "microphone") {
  498. return ConvertSystemPermission(
  499. system_permission_settings::CheckSystemAudioCapturePermission());
  500. } else if (media_type == "screen") {
  501. return ConvertSystemPermission(
  502. system_permission_settings::CheckSystemScreenCapturePermission());
  503. } else {
  504. thrower.ThrowError("Invalid media type");
  505. return std::string();
  506. }
  507. }
  508. v8::Local<v8::Promise> SystemPreferences::AskForMediaAccess(
  509. v8::Isolate* isolate,
  510. const std::string& media_type) {
  511. gin_helper::Promise<bool> promise(isolate);
  512. v8::Local<v8::Promise> handle = promise.GetHandle();
  513. if (auto type = ParseMediaType(media_type)) {
  514. __block gin_helper::Promise<bool> p = std::move(promise);
  515. [AVCaptureDevice requestAccessForMediaType:type
  516. completionHandler:^(BOOL granted) {
  517. dispatch_async(dispatch_get_main_queue(), ^{
  518. p.Resolve(!!granted);
  519. });
  520. }];
  521. } else {
  522. promise.RejectWithErrorMessage("Invalid media type");
  523. }
  524. return handle;
  525. }
  526. void SystemPreferences::RemoveUserDefault(const std::string& name) {
  527. NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  528. [defaults removeObjectForKey:base::SysUTF8ToNSString(name)];
  529. }
  530. bool SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled() {
  531. return [NSEvent isSwipeTrackingFromScrollEventsEnabled];
  532. }
  533. v8::Local<v8::Value> SystemPreferences::GetEffectiveAppearance(
  534. v8::Isolate* isolate) {
  535. return gin::ConvertToV8(
  536. isolate, [NSApplication sharedApplication].effectiveAppearance);
  537. }
  538. bool SystemPreferences::AccessibilityDisplayShouldReduceTransparency() {
  539. return [[NSWorkspace sharedWorkspace]
  540. accessibilityDisplayShouldReduceTransparency];
  541. }
  542. } // namespace electron::api