electron/atom/common/platform_util_mac.mm
Milan Burda a9475f3590 feat: add workingDirectory option to shell.openExternal() (#15065)
Allows passing `workingDirectory` to the underlying `ShellExecuteW` API on Windows.

the motivation is that by default `ShellExecute` would use the current working directory, which would get locked on Windows and can prevent autoUpdater from working correctly. We need to be able specify a different `workingDirectory` to prevent this situation.
2018-10-10 22:46:54 +02:00

207 lines
7.2 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2013 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/common/platform_util.h"
#import <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
#import <ServiceManagement/ServiceManagement.h>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/mac/foundation_util.h"
#include "base/mac/mac_logging.h"
#include "base/mac/scoped_aedesc.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "net/base/mac/url_conversions.h"
#include "url/gurl.h"
namespace {
std::string MessageForOSStatus(OSStatus status, const char* default_message) {
switch (status) {
case kLSAppInTrashErr:
return "The application cannot be run because it is inside a Trash "
"folder.";
case kLSUnknownErr:
return "An unknown error has occurred.";
case kLSNotAnApplicationErr:
return "The item to be registered is not an application.";
case kLSNotInitializedErr:
return "Formerly returned by LSInit on initialization failure; "
"no longer used.";
case kLSDataUnavailableErr:
return "Data of the desired type is not available (for example, there is "
"no kind string).";
case kLSApplicationNotFoundErr:
return "No application in the Launch Services database matches the input "
"criteria.";
case kLSDataErr:
return "Data is structured improperly (for example, an items "
"information property list is malformed). Not used in macOS 10.4.";
case kLSLaunchInProgressErr:
return "A launch of the application is already in progress.";
case kLSServerCommunicationErr:
return "There is a problem communicating with the server process that "
"maintains the Launch Services database.";
case kLSCannotSetInfoErr:
return "The filename extension to be hidden cannot be hidden.";
case kLSIncompatibleSystemVersionErr:
return "The application to be launched cannot run on the current Mac OS "
"version.";
case kLSNoLaunchPermissionErr:
return "The user does not have permission to launch the application (on a"
"managed network).";
case kLSNoExecutableErr:
return "The executable file is missing or has an unusable format.";
case kLSNoClassicEnvironmentErr:
return "The Classic emulation environment was required but is not "
"available.";
case kLSMultipleSessionsNotSupportedErr:
return "The application to be launched cannot run simultaneously in two "
"different user sessions.";
default:
return base::StringPrintf("%s (%d)", default_message, status);
}
}
// This may be called from a global dispatch queue, the methods used here are
// thread safe, including LSGetApplicationForURL (> 10.2) and
// NSWorkspace#openURLs.
std::string OpenURL(NSURL* ns_url, bool activate) {
CFURLRef openingApp = nullptr;
OSStatus status = LSGetApplicationForURL(base::mac::NSToCFCast(ns_url),
kLSRolesAll, nullptr, &openingApp);
if (status != noErr)
return MessageForOSStatus(status, "Failed to open");
CFRelease(openingApp); // NOT A BUG; LSGetApplicationForURL retains for us
NSUInteger launchOptions = NSWorkspaceLaunchDefault;
if (!activate)
launchOptions |= NSWorkspaceLaunchWithoutActivation;
bool opened = [[NSWorkspace sharedWorkspace] openURLs:@[ ns_url ]
withAppBundleIdentifier:nil
options:launchOptions
additionalEventParamDescriptor:nil
launchIdentifiers:nil];
if (!opened)
return "Failed to open URL";
return "";
}
NSString* GetLoginHelperBundleIdentifier() {
return [[[NSBundle mainBundle] bundleIdentifier]
stringByAppendingString:@".loginhelper"];
}
} // namespace
namespace platform_util {
bool ShowItemInFolder(const base::FilePath& path) {
// The API only takes absolute path.
base::FilePath full_path =
path.IsAbsolute() ? path : base::MakeAbsoluteFilePath(path);
DCHECK([NSThread isMainThread]);
NSString* path_string = base::SysUTF8ToNSString(full_path.value());
if (!path_string || ![[NSWorkspace sharedWorkspace] selectFile:path_string
inFileViewerRootedAtPath:@""]) {
LOG(WARNING) << "NSWorkspace failed to select file " << full_path.value();
return false;
}
return true;
}
bool OpenItem(const base::FilePath& full_path) {
DCHECK([NSThread isMainThread]);
NSString* path_string = base::SysUTF8ToNSString(full_path.value());
if (!path_string)
return false;
NSURL* url = [NSURL fileURLWithPath:path_string];
if (!url)
return false;
const NSWorkspaceLaunchOptions launch_options =
NSWorkspaceLaunchAsync | NSWorkspaceLaunchWithErrorPresentation;
return [[NSWorkspace sharedWorkspace] openURLs:@[ url ]
withAppBundleIdentifier:nil
options:launch_options
additionalEventParamDescriptor:nil
launchIdentifiers:NULL];
}
bool OpenExternal(const GURL& url, const OpenExternalOptions& options) {
DCHECK([NSThread isMainThread]);
NSURL* ns_url = net::NSURLWithGURL(url);
if (ns_url)
return OpenURL(ns_url, options.activate).empty();
return false;
}
void OpenExternal(const GURL& url,
const OpenExternalOptions& options,
const OpenExternalCallback& callback) {
NSURL* ns_url = net::NSURLWithGURL(url);
if (!ns_url) {
callback.Run("Invalid URL");
return;
}
__block OpenExternalCallback c = callback;
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block std::string error = OpenURL(ns_url, options.activate);
dispatch_async(dispatch_get_main_queue(), ^{
c.Run(error);
});
});
}
bool MoveItemToTrash(const base::FilePath& full_path) {
NSString* path_string = base::SysUTF8ToNSString(full_path.value());
BOOL status = [[NSFileManager defaultManager]
trashItemAtURL:[NSURL fileURLWithPath:path_string]
resultingItemURL:nil
error:nil];
if (!path_string || !status)
LOG(WARNING) << "NSWorkspace failed to move file " << full_path.value()
<< " to trash";
return status;
}
void Beep() {
NSBeep();
}
bool GetLoginItemEnabled() {
BOOL enabled = NO;
// SMJobCopyDictionary does not work in sandbox (see rdar://13626319)
CFArrayRef jobs = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
NSArray* jobs_ = CFBridgingRelease(jobs);
NSString* identifier = GetLoginHelperBundleIdentifier();
if (jobs_ && [jobs_ count] > 0) {
for (NSDictionary* job in jobs_) {
if ([identifier isEqualToString:[job objectForKey:@"Label"]]) {
enabled = [[job objectForKey:@"OnDemand"] boolValue];
break;
}
}
}
return enabled;
}
void SetLoginItemEnabled(bool enabled) {
NSString* identifier = GetLoginHelperBundleIdentifier();
SMLoginItemSetEnabled((__bridge CFStringRef)identifier, enabled);
}
} // namespace platform_util