platform_util_mac.mm 10 KB

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