electron_api_system_preferences_mac.mm 22 KB

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