1// Copyright 2013 The Chromium Authors 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "base/mac/launch_application.h" 6 7#include "base/command_line.h" 8#include "base/functional/callback.h" 9#include "base/logging.h" 10#include "base/mac/foundation_util.h" 11#include "base/strings/sys_string_conversions.h" 12#include "base/types/expected.h" 13 14namespace base::mac { 15 16namespace { 17 18NSArray* CommandLineArgsToArgsArray(const CommandLineArgs& command_line_args) { 19 if (const CommandLine* command_line = 20 absl::get_if<CommandLine>(&command_line_args)) { 21 const auto& argv = command_line->argv(); 22 size_t argc = argv.size(); 23 DCHECK_GT(argc, 0lu); 24 25 NSMutableArray* args_array = [NSMutableArray arrayWithCapacity:argc - 1]; 26 // NSWorkspace automatically adds the binary path as the first argument and 27 // thus it should not be included in the list. 28 for (size_t i = 1; i < argc; ++i) { 29 [args_array addObject:base::SysUTF8ToNSString(argv[i])]; 30 } 31 32 return args_array; 33 } 34 35 if (const std::vector<std::string>* string_vector = 36 absl::get_if<std::vector<std::string>>(&command_line_args)) { 37 NSMutableArray* args_array = 38 [NSMutableArray arrayWithCapacity:string_vector->size()]; 39 for (const auto& arg : *string_vector) { 40 [args_array addObject:base::SysUTF8ToNSString(arg)]; 41 } 42 } 43 44 return @[]; 45} 46 47NSWorkspaceOpenConfiguration* GetOpenConfiguration( 48 LaunchApplicationOptions options, 49 const CommandLineArgs& command_line_args) API_AVAILABLE(macos(10.15)) { 50 NSWorkspaceOpenConfiguration* config = 51 [NSWorkspaceOpenConfiguration configuration]; 52 53 config.activates = options.activate; 54 config.createsNewApplicationInstance = options.create_new_instance; 55 config.promptsUserIfNeeded = options.prompt_user_if_needed; 56 config.arguments = CommandLineArgsToArgsArray(command_line_args); 57 58 return config; 59} 60 61NSWorkspaceLaunchOptions GetLaunchOptions(LaunchApplicationOptions options) { 62 NSWorkspaceLaunchOptions launch_options = NSWorkspaceLaunchDefault; 63 64 if (!options.activate) { 65 launch_options |= NSWorkspaceLaunchWithoutActivation; 66 } 67 if (options.create_new_instance) { 68 launch_options |= NSWorkspaceLaunchNewInstance; 69 } 70 if (options.prompt_user_if_needed) { 71 launch_options |= NSWorkspaceLaunchWithErrorPresentation; 72 } 73 74 return launch_options; 75} 76 77} // namespace 78 79void LaunchApplication(const base::FilePath& app_bundle_path, 80 const CommandLineArgs& command_line_args, 81 const std::vector<std::string>& url_specs, 82 LaunchApplicationOptions options, 83 LaunchApplicationCallback callback) { 84 __block LaunchApplicationCallback callback_block_access = std::move(callback); 85 86 NSURL* bundle_url = FilePathToNSURL(app_bundle_path); 87 if (!bundle_url) { 88 dispatch_async(dispatch_get_main_queue(), ^{ 89 std::move(callback_block_access) 90 .Run(base::unexpected([NSError errorWithDomain:NSCocoaErrorDomain 91 code:NSFileNoSuchFileError 92 userInfo:nil])); 93 }); 94 return; 95 } 96 97 NSMutableArray* ns_urls = nil; 98 if (!url_specs.empty()) { 99 ns_urls = [NSMutableArray arrayWithCapacity:url_specs.size()]; 100 for (const auto& url_spec : url_specs) { 101 [ns_urls 102 addObject:[NSURL URLWithString:base::SysUTF8ToNSString(url_spec)]]; 103 } 104 } 105 106 if (@available(macOS 10.15, *)) { 107 void (^action_block)(NSRunningApplication*, NSError*) = 108 ^void(NSRunningApplication* app, NSError* error) { 109 dispatch_async(dispatch_get_main_queue(), ^{ 110 if (error) { 111 LOG(ERROR) << base::SysNSStringToUTF8(error.localizedDescription); 112 std::move(callback_block_access).Run(base::unexpected(error)); 113 } else { 114 std::move(callback_block_access).Run(app); 115 } 116 }); 117 }; 118 119 NSWorkspaceOpenConfiguration* configuration = 120 GetOpenConfiguration(options, command_line_args); 121 122 if (ns_urls) { 123 [NSWorkspace.sharedWorkspace openURLs:ns_urls 124 withApplicationAtURL:bundle_url 125 configuration:configuration 126 completionHandler:action_block]; 127 } else { 128 [NSWorkspace.sharedWorkspace openApplicationAtURL:bundle_url 129 configuration:configuration 130 completionHandler:action_block]; 131 } 132 } else { 133 NSDictionary* configuration = @{ 134 NSWorkspaceLaunchConfigurationArguments : 135 CommandLineArgsToArgsArray(command_line_args), 136 }; 137 138 NSWorkspaceLaunchOptions launch_options = GetLaunchOptions(options); 139 140 NSError* error = nil; 141 NSRunningApplication* app; 142 if (ns_urls) { 143 app = [NSWorkspace.sharedWorkspace openURLs:ns_urls 144 withApplicationAtURL:bundle_url 145 options:launch_options 146 configuration:configuration 147 error:&error]; 148 } else { 149 app = [NSWorkspace.sharedWorkspace launchApplicationAtURL:bundle_url 150 options:launch_options 151 configuration:configuration 152 error:&error]; 153 } 154 155 dispatch_async(dispatch_get_main_queue(), ^{ 156 if (error) { 157 LOG(ERROR) << base::SysNSStringToUTF8(error.localizedDescription); 158 std::move(callback_block_access).Run(base::unexpected(error)); 159 } else { 160 std::move(callback_block_access).Run(app); 161 } 162 }); 163 } 164} 165 166} // namespace base::mac 167