platform_util_mac.mm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. // Copyright (c) 2013 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/common/platform_util.h"
  5. #include <string>
  6. #include <utility>
  7. #import <Carbon/Carbon.h>
  8. #import <Cocoa/Cocoa.h>
  9. #import <ServiceManagement/ServiceManagement.h>
  10. #include "base/apple/foundation_util.h"
  11. #include "base/apple/osstatus_logging.h"
  12. #include "base/files/file_path.h"
  13. #include "base/files/file_util.h"
  14. #include "base/functional/bind.h"
  15. #include "base/logging.h"
  16. #include "base/mac/scoped_aedesc.h"
  17. #include "base/strings/sys_string_conversions.h"
  18. #include "base/task/thread_pool.h"
  19. #include "content/public/browser/browser_task_traits.h"
  20. #include "net/base/apple/url_conversions.h"
  21. #include "ui/views/widget/widget.h"
  22. #include "url/gurl.h"
  23. // platform_util_mac.mm uses a lot of deprecated API.
  24. // https://github.com/electron/electron/issues/43126
  25. #pragma clang diagnostic push
  26. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  27. namespace {
  28. // This may be called from a global dispatch queue, the methods used here are
  29. // thread safe, including LSGetApplicationForURL (> 10.2) and
  30. // NSWorkspace#openURLs.
  31. std::string OpenURL(NSURL* ns_url, bool activate) {
  32. CFURLRef cf_url = (__bridge CFURLRef)(ns_url);
  33. CFURLRef ref =
  34. LSCopyDefaultApplicationURLForURL(cf_url, kLSRolesAll, nullptr);
  35. // If no application could be found, nullptr is returned and outError
  36. // (if not nullptr) is populated with kLSApplicationNotFoundErr.
  37. if (ref == nullptr)
  38. return "No application in the Launch Services database matches the input "
  39. "criteria.";
  40. NSUInteger launchOptions = NSWorkspaceLaunchDefault;
  41. if (!activate)
  42. launchOptions |= NSWorkspaceLaunchWithoutActivation;
  43. bool opened = [[NSWorkspace sharedWorkspace] openURLs:@[ ns_url ]
  44. withAppBundleIdentifier:nil
  45. options:launchOptions
  46. additionalEventParamDescriptor:nil
  47. launchIdentifiers:nil];
  48. if (!opened)
  49. return "Failed to open URL";
  50. return "";
  51. }
  52. NSString* GetLoginHelperBundleIdentifier() {
  53. return [[[NSBundle mainBundle] bundleIdentifier]
  54. stringByAppendingString:@".loginhelper"];
  55. }
  56. std::string OpenPathOnThread(const base::FilePath& full_path) {
  57. NSString* path_string = base::SysUTF8ToNSString(full_path.value());
  58. NSURL* url = [NSURL fileURLWithPath:path_string];
  59. if (!url)
  60. return "Invalid path";
  61. const NSWorkspaceLaunchOptions launch_options =
  62. NSWorkspaceLaunchAsync | NSWorkspaceLaunchWithErrorPresentation;
  63. BOOL success = [[NSWorkspace sharedWorkspace] openURLs:@[ url ]
  64. withAppBundleIdentifier:nil
  65. options:launch_options
  66. additionalEventParamDescriptor:nil
  67. launchIdentifiers:nil];
  68. return success ? "" : "Failed to open path";
  69. }
  70. // -Wdeprecated-declarations
  71. #pragma clang diagnostic pop
  72. // https://developer.apple.com/documentation/servicemanagement/1561515-service_management_errors?language=objc
  73. std::string GetLaunchStringForError(NSError* error) {
  74. if (@available(macOS 13, *)) {
  75. switch ([error code]) {
  76. case kSMErrorAlreadyRegistered:
  77. return "The application is already registered";
  78. case kSMErrorAuthorizationFailure:
  79. return "The authorization requested failed";
  80. case kSMErrorLaunchDeniedByUser:
  81. return "The user denied the app's launch request";
  82. case kSMErrorInternalFailure:
  83. return "An internal failure has occurred";
  84. case kSMErrorInvalidPlist:
  85. return "The app's property list is invalid";
  86. case kSMErrorInvalidSignature:
  87. return "The app's code signature doesn't meet the requirements to "
  88. "perform the operation";
  89. case kSMErrorJobMustBeEnabled:
  90. return "The specified job is not enabled";
  91. case kSMErrorJobNotFound:
  92. return "The system can't find the specified job";
  93. case kSMErrorJobPlistNotFound:
  94. return "The app's property list cannot be found";
  95. case kSMErrorServiceUnavailable:
  96. return "The service necessary to perform this operation is unavailable "
  97. "or is no longer accepting requests";
  98. case kSMErrorToolNotValid:
  99. return "The specified path doesn't exist or the helper tool at the "
  100. "specified path isn't valid";
  101. default:
  102. return base::SysNSStringToUTF8([error localizedDescription]);
  103. }
  104. }
  105. return "";
  106. }
  107. SMAppService* GetServiceForType(const std::string& type,
  108. const std::string& name)
  109. API_AVAILABLE(macosx(13.0)) {
  110. NSString* service_name = [NSString stringWithUTF8String:name.c_str()];
  111. if (type == "mainAppService") {
  112. return [SMAppService mainAppService];
  113. } else if (type == "agentService") {
  114. return [SMAppService agentServiceWithPlistName:service_name];
  115. } else if (type == "daemonService") {
  116. return [SMAppService daemonServiceWithPlistName:service_name];
  117. } else if (type == "loginItemService") {
  118. return [SMAppService loginItemServiceWithIdentifier:service_name];
  119. } else {
  120. LOG(ERROR) << "Unrecognized login item type";
  121. return nullptr;
  122. }
  123. }
  124. bool GetLoginItemEnabledDeprecated() {
  125. BOOL enabled = NO;
  126. #pragma clang diagnostic push
  127. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  128. // SMJobCopyDictionary does not work in sandbox (see rdar://13626319)
  129. CFArrayRef jobs = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
  130. #pragma clang diagnostic pop
  131. NSArray* jobs_ = CFBridgingRelease(jobs);
  132. NSString* identifier = GetLoginHelperBundleIdentifier();
  133. if (jobs_ && [jobs_ count] > 0) {
  134. for (NSDictionary* job in jobs_) {
  135. if ([identifier isEqualToString:[job objectForKey:@"Label"]]) {
  136. enabled = [[job objectForKey:@"OnDemand"] boolValue];
  137. break;
  138. }
  139. }
  140. }
  141. return enabled;
  142. }
  143. } // namespace
  144. namespace platform_util {
  145. void ShowItemInFolder(const base::FilePath& path) {
  146. // The API only takes absolute path.
  147. base::FilePath full_path =
  148. path.IsAbsolute() ? path : base::MakeAbsoluteFilePath(path);
  149. DCHECK([NSThread isMainThread]);
  150. NSString* path_string = base::SysUTF8ToNSString(full_path.value());
  151. if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string
  152. inFileViewerRootedAtPath:@""]) {
  153. LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
  154. }
  155. }
  156. void OpenPath(const base::FilePath& full_path, OpenCallback callback) {
  157. std::move(callback).Run(OpenPathOnThread(full_path));
  158. }
  159. void OpenExternal(const GURL& url,
  160. const OpenExternalOptions& options,
  161. OpenCallback callback) {
  162. DCHECK([NSThread isMainThread]);
  163. NSURL* ns_url = net::NSURLWithGURL(url);
  164. if (!ns_url) {
  165. std::move(callback).Run("Invalid URL");
  166. return;
  167. }
  168. base::ThreadPool::PostTaskAndReplyWithResult(
  169. FROM_HERE,
  170. {base::MayBlock(), base::WithBaseSyncPrimitives(),
  171. base::TaskPriority::USER_BLOCKING,
  172. base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
  173. base::BindOnce(&OpenURL, ns_url, options.activate), std::move(callback));
  174. }
  175. bool MoveItemToTrashWithError(const base::FilePath& full_path,
  176. bool delete_on_fail,
  177. std::string* error) {
  178. NSString* path_string = base::SysUTF8ToNSString(full_path.value());
  179. if (!path_string) {
  180. *error = "Invalid file path: " + full_path.value();
  181. LOG(WARNING) << *error;
  182. return false;
  183. }
  184. NSURL* url = [NSURL fileURLWithPath:path_string];
  185. NSError* err = nil;
  186. BOOL did_trash = [[NSFileManager defaultManager] trashItemAtURL:url
  187. resultingItemURL:nil
  188. error:&err];
  189. if (delete_on_fail) {
  190. // Some volumes may not support a Trash folder or it may be disabled
  191. // so these methods will report failure by returning NO or nil and
  192. // an NSError with NSFeatureUnsupportedError.
  193. // Handle this by deleting the item as a fallback.
  194. if (!did_trash && [err code] == NSFeatureUnsupportedError) {
  195. did_trash = [[NSFileManager defaultManager] removeItemAtURL:url
  196. error:&err];
  197. }
  198. }
  199. if (!did_trash) {
  200. *error = base::SysNSStringToUTF8([err localizedDescription]);
  201. LOG(WARNING) << "NSWorkspace failed to move file " << full_path.value()
  202. << " to trash: " << *error;
  203. }
  204. return did_trash;
  205. }
  206. namespace internal {
  207. bool PlatformTrashItem(const base::FilePath& full_path, std::string* error) {
  208. return MoveItemToTrashWithError(full_path, false, error);
  209. }
  210. } // namespace internal
  211. void Beep() {
  212. NSBeep();
  213. }
  214. std::string GetLoginItemEnabled(const std::string& type,
  215. const std::string& service_name) {
  216. bool enabled = GetLoginItemEnabledDeprecated();
  217. if (@available(macOS 13, *)) {
  218. SMAppService* service = GetServiceForType(type, service_name);
  219. SMAppServiceStatus status = [service status];
  220. if (status == SMAppServiceStatusNotRegistered)
  221. return "not-registered";
  222. else if (status == SMAppServiceStatusEnabled)
  223. return "enabled";
  224. else if (status == SMAppServiceStatusRequiresApproval)
  225. return "requires-approval";
  226. else if (status == SMAppServiceStatusNotFound) {
  227. // If the login item was enabled with the old API, return that.
  228. return enabled ? "enabled-deprecated" : "not-found";
  229. }
  230. }
  231. return enabled ? "enabled" : "not-registered";
  232. }
  233. bool SetLoginItemEnabled(const std::string& type,
  234. const std::string& service_name,
  235. bool enabled) {
  236. if (@available(macOS 13, *)) {
  237. #if IS_MAS_BUILD()
  238. // If the app was previously set as a LoginItem with the old API, remove it
  239. // as a LoginItem via the old API before re-enabling with the new API.
  240. if (GetLoginItemEnabledDeprecated() && enabled) {
  241. NSString* identifier = GetLoginHelperBundleIdentifier();
  242. SMLoginItemSetEnabled((__bridge CFStringRef)identifier, false);
  243. }
  244. #endif
  245. SMAppService* service = GetServiceForType(type, service_name);
  246. NSError* error = nil;
  247. bool result = enabled ? [service registerAndReturnError:&error]
  248. : [service unregisterAndReturnError:&error];
  249. if (error != nil)
  250. LOG(ERROR) << "Unable to set login item: "
  251. << GetLaunchStringForError(error);
  252. return result;
  253. } else {
  254. NSString* identifier = GetLoginHelperBundleIdentifier();
  255. return SMLoginItemSetEnabled((__bridge CFStringRef)identifier, enabled);
  256. }
  257. }
  258. } // namespace platform_util