platform_util_mac.mm 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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 "atom/common/platform_util.h"
  5. #import <Carbon/Carbon.h>
  6. #import <Cocoa/Cocoa.h>
  7. #include "base/callback.h"
  8. #include "base/files/file_path.h"
  9. #include "base/files/file_util.h"
  10. #include "base/logging.h"
  11. #include "base/mac/mac_logging.h"
  12. #include "base/mac/scoped_aedesc.h"
  13. #include "base/strings/stringprintf.h"
  14. #include "base/strings/sys_string_conversions.h"
  15. #include "net/base/mac/url_conversions.h"
  16. #include "url/gurl.h"
  17. namespace {
  18. std::string MessageForOSStatus(OSStatus status, const char* default_message) {
  19. switch (status) {
  20. case kLSAppInTrashErr:
  21. return "The application cannot be run because it is inside a Trash "
  22. "folder.";
  23. case kLSUnknownErr:
  24. return "An unknown error has occurred.";
  25. case kLSNotAnApplicationErr:
  26. return "The item to be registered is not an application.";
  27. case kLSNotInitializedErr:
  28. return "Formerly returned by LSInit on initialization failure; "
  29. "no longer used.";
  30. case kLSDataUnavailableErr:
  31. return "Data of the desired type is not available (for example, there is "
  32. "no kind string).";
  33. case kLSApplicationNotFoundErr:
  34. return "No application in the Launch Services database matches the input "
  35. "criteria.";
  36. case kLSDataErr:
  37. return "Data is structured improperly (for example, an item’s "
  38. "information property list is malformed). Not used in macOS 10.4.";
  39. case kLSLaunchInProgressErr:
  40. return "A launch of the application is already in progress.";
  41. case kLSServerCommunicationErr:
  42. return "There is a problem communicating with the server process that "
  43. "maintains the Launch Services database.";
  44. case kLSCannotSetInfoErr:
  45. return "The filename extension to be hidden cannot be hidden.";
  46. case kLSIncompatibleSystemVersionErr:
  47. return "The application to be launched cannot run on the current Mac OS "
  48. "version.";
  49. case kLSNoLaunchPermissionErr:
  50. return "The user does not have permission to launch the application (on a"
  51. "managed network).";
  52. case kLSNoExecutableErr:
  53. return "The executable file is missing or has an unusable format.";
  54. case kLSNoClassicEnvironmentErr:
  55. return "The Classic emulation environment was required but is not "
  56. "available.";
  57. case kLSMultipleSessionsNotSupportedErr:
  58. return "The application to be launched cannot run simultaneously in two "
  59. "different user sessions.";
  60. default:
  61. return base::StringPrintf("%s (%d)", default_message, status);
  62. }
  63. }
  64. // This may be called from a global dispatch queue, the methods used here are
  65. // thread safe, including LSGetApplicationForURL (> 10.2) and
  66. // NSWorkspace#openURLs.
  67. std::string OpenURL(NSURL* ns_url, bool activate) {
  68. CFURLRef openingApp = NULL;
  69. OSStatus status = LSGetApplicationForURL((CFURLRef)ns_url,
  70. kLSRolesAll,
  71. NULL,
  72. &openingApp);
  73. if (status != noErr)
  74. return MessageForOSStatus(status, "Failed to open");
  75. CFRelease(openingApp); // NOT A BUG; LSGetApplicationForURL retains for us
  76. NSUInteger launchOptions = NSWorkspaceLaunchDefault;
  77. if (!activate)
  78. launchOptions |= NSWorkspaceLaunchWithoutActivation;
  79. bool opened = [[NSWorkspace sharedWorkspace]
  80. openURLs:@[ns_url]
  81. withAppBundleIdentifier:nil
  82. options:launchOptions
  83. additionalEventParamDescriptor:nil
  84. launchIdentifiers:nil];
  85. if (!opened)
  86. return "Failed to open URL";
  87. return "";
  88. }
  89. } // namespace
  90. namespace platform_util {
  91. bool ShowItemInFolder(const base::FilePath& path) {
  92. // The API only takes absolute path.
  93. base::FilePath full_path =
  94. path.IsAbsolute() ? path : base::MakeAbsoluteFilePath(path);
  95. DCHECK([NSThread isMainThread]);
  96. NSString* path_string = base::SysUTF8ToNSString(full_path.value());
  97. if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string
  98. inFileViewerRootedAtPath:@""]) {
  99. LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
  100. return false;
  101. }
  102. return true;
  103. }
  104. // This function opens a file. This doesn't use LaunchServices or NSWorkspace
  105. // because of two bugs:
  106. // 1. Incorrect app activation with com.apple.quarantine:
  107. // http://crbug.com/32921
  108. // 2. Silent no-op for unassociated file types: http://crbug.com/50263
  109. // Instead, an AppleEvent is constructed to tell the Finder to open the
  110. // document.
  111. bool OpenItem(const base::FilePath& full_path) {
  112. DCHECK([NSThread isMainThread]);
  113. NSString* path_string = base::SysUTF8ToNSString(full_path.value());
  114. if (!path_string)
  115. return false;
  116. // Create the target of this AppleEvent, the Finder.
  117. base::mac::ScopedAEDesc<AEAddressDesc> address;
  118. const OSType finderCreatorCode = 'MACS';
  119. OSErr status = AECreateDesc(typeApplSignature, // type
  120. &finderCreatorCode, // data
  121. sizeof(finderCreatorCode), // dataSize
  122. address.OutPointer()); // result
  123. if (status != noErr) {
  124. OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE target";
  125. return false;
  126. }
  127. // Build the AppleEvent data structure that instructs Finder to open files.
  128. base::mac::ScopedAEDesc<AppleEvent> theEvent;
  129. status = AECreateAppleEvent(kCoreEventClass, // theAEEventClass
  130. kAEOpenDocuments, // theAEEventID
  131. address, // target
  132. kAutoGenerateReturnID, // returnID
  133. kAnyTransactionID, // transactionID
  134. theEvent.OutPointer()); // result
  135. if (status != noErr) {
  136. OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE event";
  137. return false;
  138. }
  139. // Create the list of files (only ever one) to open.
  140. base::mac::ScopedAEDesc<AEDescList> fileList;
  141. status = AECreateList(NULL, // factoringPtr
  142. 0, // factoredSize
  143. false, // isRecord
  144. fileList.OutPointer()); // resultList
  145. if (status != noErr) {
  146. OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE file list";
  147. return false;
  148. }
  149. // Add the single path to the file list. C-style cast to avoid both a
  150. // static_cast and a const_cast to get across the toll-free bridge.
  151. CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string];
  152. FSRef pathRef;
  153. if (CFURLGetFSRef(pathURLRef, &pathRef)) {
  154. status = AEPutPtr(fileList.OutPointer(), // theAEDescList
  155. 0, // index
  156. typeFSRef, // typeCode
  157. &pathRef, // dataPtr
  158. sizeof(pathRef)); // dataSize
  159. if (status != noErr) {
  160. OSSTATUS_LOG(WARNING, status)
  161. << "Could not add file path to AE list in OpenItem()";
  162. return false;
  163. }
  164. } else {
  165. LOG(WARNING) << "Could not get FSRef for path URL in OpenItem()";
  166. return false;
  167. }
  168. // Attach the file list to the AppleEvent.
  169. status = AEPutParamDesc(theEvent.OutPointer(), // theAppleEvent
  170. keyDirectObject, // theAEKeyword
  171. fileList); // theAEDesc
  172. if (status != noErr) {
  173. OSSTATUS_LOG(WARNING, status)
  174. << "Could not put the AE file list the path in OpenItem()";
  175. return false;
  176. }
  177. // Send the actual event. Do not care about the reply.
  178. base::mac::ScopedAEDesc<AppleEvent> reply;
  179. status = AESend(theEvent, // theAppleEvent
  180. reply.OutPointer(), // reply
  181. kAENoReply + kAEAlwaysInteract, // sendMode
  182. kAENormalPriority, // sendPriority
  183. kAEDefaultTimeout, // timeOutInTicks
  184. NULL, // idleProc
  185. NULL); // filterProc
  186. if (status != noErr) {
  187. OSSTATUS_LOG(WARNING, status)
  188. << "Could not send AE to Finder in OpenItem()";
  189. }
  190. return status == noErr;
  191. }
  192. bool OpenExternal(const GURL& url, bool activate) {
  193. DCHECK([NSThread isMainThread]);
  194. NSURL* ns_url = net::NSURLWithGURL(url);
  195. if (ns_url)
  196. return OpenURL(ns_url, activate).empty();
  197. return false;
  198. }
  199. void OpenExternal(const GURL& url, bool activate,
  200. const OpenExternalCallback& callback) {
  201. NSURL* ns_url = net::NSURLWithGURL(url);
  202. if (!ns_url) {
  203. callback.Run("Invalid URL");
  204. return;
  205. }
  206. __block OpenExternalCallback c = callback;
  207. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  208. __block std::string error = OpenURL(ns_url, activate);
  209. dispatch_async(dispatch_get_main_queue(), ^{
  210. c.Run(error);
  211. });
  212. });
  213. }
  214. bool MoveItemToTrash(const base::FilePath& full_path) {
  215. NSString* path_string = base::SysUTF8ToNSString(full_path.value());
  216. BOOL status = [[NSFileManager defaultManager]
  217. trashItemAtURL:[NSURL fileURLWithPath:path_string]
  218. resultingItemURL:nil
  219. error:nil];
  220. if (!path_string || !status)
  221. LOG(WARNING) << "NSWorkspace failed to move file " << full_path.value()
  222. << " to trash";
  223. return status;
  224. }
  225. void Beep() {
  226. NSBeep();
  227. }
  228. } // namespace platform_util