platform_util_mac.mm 9.3 KB

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