diff --git a/atom.gyp b/atom.gyp index 4b67a4dd843b..73f8e8dbf385 100644 --- a/atom.gyp +++ b/atom.gyp @@ -60,6 +60,8 @@ 'common/node_bindings_mac.mm', 'common/options_switches.cc', 'common/options_switches.h', + 'common/platform_util_mac.mm', + 'common/platform_util.h', 'common/v8_value_converter_impl.cc', 'common/v8_value_converter_impl.h', 'renderer/api/atom_api_renderer_ipc.cc', diff --git a/common/platform_util.h b/common/platform_util.h new file mode 100644 index 000000000000..9878b770a90c --- /dev/null +++ b/common/platform_util.h @@ -0,0 +1,30 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef ATOM_COMMON_PLATFORM_UTIL_H_ +#define ATOM_COMMON_PLATFORM_UTIL_H_ + +class GURL; + +namespace base { +class FilePath; +} + +namespace platform_util { + +// Show the given file in a file manager. If possible, select the file. +// Must be called from the UI thread. +void ShowItemInFolder(const base::FilePath& full_path); + +// Open the given file in the desktop's default manner. +// Must be called from the UI thread. +void OpenItem(const base::FilePath& full_path); + +// Open the given external protocol URL in the desktop's default manner. +// (For example, mailto: URLs in the default mail user agent.) +void OpenExternal(const GURL& url); + +} // platform_util + +#endif // ATOM_COMMON_PLATFORM_UTIL_H_ diff --git a/common/platform_util_mac.mm b/common/platform_util_mac.mm new file mode 100644 index 000000000000..2d4cfa0ff9ae --- /dev/null +++ b/common/platform_util_mac.mm @@ -0,0 +1,129 @@ +// Copyright (c) 2013 GitHub, Inc. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "common/platform_util.h" + +#include +#import + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/mac/mac_logging.h" +#include "base/mac/scoped_aedesc.h" +#include "base/strings/sys_string_conversions.h" +#include "googleurl/src/gurl.h" + +namespace platform_util { + +void ShowItemInFolder(const base::FilePath& full_path) { + DCHECK([NSThread isMainThread]); + NSString* path_string = base::SysUTF8ToNSString(full_path.value()); + if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string + inFileViewerRootedAtPath:nil]) + LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value(); +} + +// This function opens a file. This doesn't use LaunchServices or NSWorkspace +// because of two bugs: +// 1. Incorrect app activation with com.apple.quarantine: +// http://crbug.com/32921 +// 2. Silent no-op for unassociated file types: http://crbug.com/50263 +// Instead, an AppleEvent is constructed to tell the Finder to open the +// document. +void OpenItem(const base::FilePath& full_path) { + DCHECK([NSThread isMainThread]); + NSString* path_string = base::SysUTF8ToNSString(full_path.value()); + if (!path_string) + return; + + // Create the target of this AppleEvent, the Finder. + base::mac::ScopedAEDesc address; + const OSType finderCreatorCode = 'MACS'; + OSErr status = AECreateDesc(typeApplSignature, // type + &finderCreatorCode, // data + sizeof(finderCreatorCode), // dataSize + address.OutPointer()); // result + if (status != noErr) { + OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE target"; + return; + } + + // Build the AppleEvent data structure that instructs Finder to open files. + base::mac::ScopedAEDesc theEvent; + status = AECreateAppleEvent(kCoreEventClass, // theAEEventClass + kAEOpenDocuments, // theAEEventID + address, // target + kAutoGenerateReturnID, // returnID + kAnyTransactionID, // transactionID + theEvent.OutPointer()); // result + if (status != noErr) { + OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE event"; + return; + } + + // Create the list of files (only ever one) to open. + base::mac::ScopedAEDesc fileList; + status = AECreateList(NULL, // factoringPtr + 0, // factoredSize + false, // isRecord + fileList.OutPointer()); // resultList + if (status != noErr) { + OSSTATUS_LOG(WARNING, status) << "Could not create OpenItem() AE file list"; + return; + } + + // Add the single path to the file list. C-style cast to avoid both a + // static_cast and a const_cast to get across the toll-free bridge. + CFURLRef pathURLRef = (CFURLRef)[NSURL fileURLWithPath:path_string]; + FSRef pathRef; + if (CFURLGetFSRef(pathURLRef, &pathRef)) { + status = AEPutPtr(fileList.OutPointer(), // theAEDescList + 0, // index + typeFSRef, // typeCode + &pathRef, // dataPtr + sizeof(pathRef)); // dataSize + if (status != noErr) { + OSSTATUS_LOG(WARNING, status) + << "Could not add file path to AE list in OpenItem()"; + return; + } + } else { + LOG(WARNING) << "Could not get FSRef for path URL in OpenItem()"; + return; + } + + // Attach the file list to the AppleEvent. + status = AEPutParamDesc(theEvent.OutPointer(), // theAppleEvent + keyDirectObject, // theAEKeyword + fileList); // theAEDesc + if (status != noErr) { + OSSTATUS_LOG(WARNING, status) + << "Could not put the AE file list the path in OpenItem()"; + return; + } + + // Send the actual event. Do not care about the reply. + base::mac::ScopedAEDesc reply; + status = AESend(theEvent, // theAppleEvent + reply.OutPointer(), // reply + kAENoReply + kAEAlwaysInteract, // sendMode + kAENormalPriority, // sendPriority + kAEDefaultTimeout, // timeOutInTicks + NULL, // idleProc + NULL); // filterProc + if (status != noErr) { + OSSTATUS_LOG(WARNING, status) + << "Could not send AE to Finder in OpenItem()"; + } +} + +void OpenExternal(const GURL& url) { + DCHECK([NSThread isMainThread]); + NSString* url_string = base::SysUTF8ToNSString(url.spec()); + NSURL* ns_url = [NSURL URLWithString:url_string]; + if (!ns_url || ![[NSWorkspace sharedWorkspace] openURL:ns_url]) + LOG(WARNING) << "NSWorkspace failed to open URL " << url; +} + +} // namespace platform_util