feat: update app.{set|get}LoginItemSettings(settings)
(#37244)
* feat: update app.{set|get}LoginItemSettings(settings) * test: fixup and add tests * docs: add type link * chore: name -> serviceName
This commit is contained in:
parent
6d0d350e13
commit
f7b1c75c72
7 changed files with 321 additions and 89 deletions
|
@ -85,6 +85,7 @@
|
|||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "shell/browser/ui/cocoa/electron_bundle_mover.h"
|
||||
#endif
|
||||
|
||||
|
@ -364,8 +365,11 @@ struct Converter<Browser::LoginItemSettings> {
|
|||
dict.Get("path", &(out->path));
|
||||
dict.Get("args", &(out->args));
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
dict.Get("enabled", &(out->enabled));
|
||||
dict.Get("name", &(out->name));
|
||||
dict.Get("enabled", &(out->enabled));
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
dict.Get("serviceName", &(out->service_name));
|
||||
dict.Get("type", &(out->type));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
@ -373,16 +377,19 @@ struct Converter<Browser::LoginItemSettings> {
|
|||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
Browser::LoginItemSettings val) {
|
||||
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
dict.Set("launchItems", val.launch_items);
|
||||
dict.Set("executableWillLaunchAtLogin",
|
||||
val.executable_will_launch_at_login);
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
if (base::mac::MacOSMajorVersion() >= 13)
|
||||
dict.Set("status", val.status);
|
||||
#endif
|
||||
dict.Set("openAtLogin", val.open_at_login);
|
||||
dict.Set("openAsHidden", val.open_as_hidden);
|
||||
dict.Set("restoreState", val.restore_state);
|
||||
dict.Set("wasOpenedAtLogin", val.opened_at_login);
|
||||
dict.Set("wasOpenedAsHidden", val.opened_as_hidden);
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
dict.Set("launchItems", val.launch_items);
|
||||
dict.Set("executableWillLaunchAtLogin",
|
||||
val.executable_will_launch_at_login);
|
||||
#endif
|
||||
return dict.GetHandle();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -135,7 +135,11 @@ class Browser : public WindowListObserver {
|
|||
std::u16string path;
|
||||
std::vector<std::u16string> args;
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
std::string type = "mainAppService";
|
||||
std::string service_name;
|
||||
std::string status;
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
// used in browser::setLoginItemSettings
|
||||
bool enabled = true;
|
||||
std::wstring name;
|
||||
|
@ -205,9 +209,9 @@ class Browser : public WindowListObserver {
|
|||
void ApplyForcedRTL();
|
||||
|
||||
// Bounce the dock icon.
|
||||
enum class BounceType {
|
||||
kCritical = 0, // NSCriticalRequest
|
||||
kInformational = 10, // NSInformationalRequest
|
||||
enum class BounceType{
|
||||
kCritical = 0, // NSCriticalRequest
|
||||
kInformational = 10, // NSInformationalRequest
|
||||
};
|
||||
int DockBounce(BounceType type);
|
||||
void DockCancelBounce(int request_id);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#import <ServiceManagement/ServiceManagement.h>
|
||||
|
||||
#include "base/apple/bridging.h"
|
||||
#include "base/apple/bundle_locations.h"
|
||||
#include "base/apple/scoped_cftyperef.h"
|
||||
|
@ -19,6 +21,7 @@
|
|||
#include "chrome/browser/browser_process.h"
|
||||
#include "net/base/mac/url_conversions.h"
|
||||
#include "shell/browser/badging/badge_manager.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/mac/dict_util.h"
|
||||
#include "shell/browser/mac/electron_application.h"
|
||||
#include "shell/browser/mac/electron_application_delegate.h"
|
||||
|
@ -85,6 +88,15 @@ bool CheckLoginItemStatus(bool* is_hidden) {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
Browser::LoginItemSettings GetLoginItemSettingsDeprecated() {
|
||||
Browser::LoginItemSettings settings;
|
||||
settings.open_at_login = CheckLoginItemStatus(&settings.open_as_hidden);
|
||||
settings.restore_state = base::mac::WasLaunchedAsLoginItemRestoreState();
|
||||
settings.opened_at_login = base::mac::WasLaunchedAsLoginOrResumeItem();
|
||||
settings.opened_as_hidden = base::mac::WasLaunchedAsHiddenLoginItem();
|
||||
return settings;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
@ -367,28 +379,71 @@ void Browser::ApplyForcedRTL() {
|
|||
Browser::LoginItemSettings Browser::GetLoginItemSettings(
|
||||
const LoginItemSettings& options) {
|
||||
LoginItemSettings settings;
|
||||
if (options.type != "mainAppService" && options.service_name.empty()) {
|
||||
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
|
||||
.ThrowTypeError("'name' is required when type is not mainAppService");
|
||||
return settings;
|
||||
}
|
||||
|
||||
#if IS_MAS_BUILD()
|
||||
settings.open_at_login = platform_util::GetLoginItemEnabled();
|
||||
const std::string status =
|
||||
platform_util::GetLoginItemEnabled(options.type, options.service_name);
|
||||
settings.open_at_login =
|
||||
status == "enabled" || status == "enabled-deprecated";
|
||||
if (@available(macOS 13, *))
|
||||
settings.status = status;
|
||||
#else
|
||||
settings.open_at_login = CheckLoginItemStatus(&settings.open_as_hidden);
|
||||
settings.restore_state = base::mac::WasLaunchedAsLoginItemRestoreState();
|
||||
settings.opened_at_login = base::mac::WasLaunchedAsLoginOrResumeItem();
|
||||
settings.opened_as_hidden = base::mac::WasLaunchedAsHiddenLoginItem();
|
||||
// If the app was previously set as a LoginItem with the deprecated API,
|
||||
// we should report its LoginItemSettings via the old API.
|
||||
LoginItemSettings settings_deprecated = GetLoginItemSettingsDeprecated();
|
||||
if (@available(macOS 13, *)) {
|
||||
const std::string status =
|
||||
platform_util::GetLoginItemEnabled(options.type, options.service_name);
|
||||
if (status == "enabled-deprecated") {
|
||||
settings = settings_deprecated;
|
||||
} else {
|
||||
settings.open_at_login = status == "enabled";
|
||||
settings.status = status;
|
||||
}
|
||||
} else {
|
||||
settings = settings_deprecated;
|
||||
}
|
||||
#endif
|
||||
return settings;
|
||||
}
|
||||
|
||||
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
#if IS_MAS_BUILD()
|
||||
if (!platform_util::SetLoginItemEnabled(settings.open_at_login)) {
|
||||
LOG(ERROR) << "Unable to set login item enabled on sandboxed app.";
|
||||
if (settings.type != "mainAppService" && settings.service_name.empty()) {
|
||||
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
|
||||
.ThrowTypeError("'name' is required when type is not mainAppService");
|
||||
return;
|
||||
}
|
||||
#if IS_MAS_BUILD()
|
||||
platform_util::SetLoginItemEnabled(settings.type, settings.service_name,
|
||||
settings.open_at_login);
|
||||
#else
|
||||
if (settings.open_at_login) {
|
||||
base::mac::AddToLoginItems(base::apple::MainBundlePath(),
|
||||
settings.open_as_hidden);
|
||||
const base::FilePath bundle_path = base::apple::MainBundlePath();
|
||||
if (@available(macOS 13, *)) {
|
||||
// If the app was previously set as a LoginItem with the old API, remove it
|
||||
// as a LoginItem via the old API before re-enabling with the new API.
|
||||
const std::string status =
|
||||
platform_util::GetLoginItemEnabled("mainAppService", "");
|
||||
if (status == "enabled-deprecated") {
|
||||
base::mac::RemoveFromLoginItems(bundle_path);
|
||||
if (settings.open_at_login) {
|
||||
platform_util::SetLoginItemEnabled(settings.type, settings.service_name,
|
||||
settings.open_at_login);
|
||||
}
|
||||
} else {
|
||||
platform_util::SetLoginItemEnabled(settings.type, settings.service_name,
|
||||
settings.open_at_login);
|
||||
}
|
||||
} else {
|
||||
base::mac::RemoveFromLoginItems(base::apple::MainBundlePath());
|
||||
if (settings.open_at_login) {
|
||||
base::mac::AddToLoginItems(bundle_path, settings.open_as_hidden);
|
||||
} else {
|
||||
base::mac::RemoveFromLoginItems(bundle_path);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -49,8 +49,11 @@ bool GetFolderPath(int key, base::FilePath* result);
|
|||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
bool GetLoginItemEnabled();
|
||||
bool SetLoginItemEnabled(bool enabled);
|
||||
std::string GetLoginItemEnabled(const std::string& type,
|
||||
const std::string& service_name);
|
||||
bool SetLoginItemEnabled(const std::string& type,
|
||||
const std::string& service_name,
|
||||
bool enabled);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
|
|
|
@ -77,6 +77,81 @@ std::string OpenPathOnThread(const base::FilePath& full_path) {
|
|||
return success ? "" : "Failed to open path";
|
||||
}
|
||||
|
||||
// https://developer.apple.com/documentation/servicemanagement/1561515-service_management_errors?language=objc
|
||||
std::string GetLaunchStringForError(NSError* error) {
|
||||
if (@available(macOS 13, *)) {
|
||||
switch ([error code]) {
|
||||
case kSMErrorAlreadyRegistered:
|
||||
return "The application is already registered";
|
||||
case kSMErrorAuthorizationFailure:
|
||||
return "The authorization requested failed";
|
||||
case kSMErrorLaunchDeniedByUser:
|
||||
return "The user denied the app's launch request";
|
||||
case kSMErrorInternalFailure:
|
||||
return "An internal failure has occurred";
|
||||
case kSMErrorInvalidPlist:
|
||||
return "The app's property list is invalid";
|
||||
case kSMErrorInvalidSignature:
|
||||
return "The app's code signature doesn't meet the requirements to "
|
||||
"perform the operation";
|
||||
case kSMErrorJobMustBeEnabled:
|
||||
return "The specified job is not enabled";
|
||||
case kSMErrorJobNotFound:
|
||||
return "The system can't find the specified job";
|
||||
case kSMErrorJobPlistNotFound:
|
||||
return "The app's property list cannot be found";
|
||||
case kSMErrorServiceUnavailable:
|
||||
return "The service necessary to perform this operation is unavailable "
|
||||
"or is no longer accepting requests";
|
||||
case kSMErrorToolNotValid:
|
||||
return "The specified path doesn't exist or the helper tool at the "
|
||||
"specified path isn't valid";
|
||||
default:
|
||||
return "Failed to register the login item";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
SMAppService* GetServiceForType(const std::string& type,
|
||||
const std::string& name)
|
||||
API_AVAILABLE(macosx(13.0)) {
|
||||
NSString* service_name = [NSString stringWithUTF8String:name.c_str()];
|
||||
if (type == "mainAppService") {
|
||||
return [SMAppService mainAppService];
|
||||
} else if (type == "agentService") {
|
||||
return [SMAppService agentServiceWithPlistName:service_name];
|
||||
} else if (type == "daemonService") {
|
||||
return [SMAppService daemonServiceWithPlistName:service_name];
|
||||
} else if (type == "loginService") {
|
||||
return [SMAppService loginItemServiceWithIdentifier:service_name];
|
||||
} else {
|
||||
LOG(ERROR) << "Unrecognized login item type";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool GetLoginItemEnabledDeprecated() {
|
||||
BOOL enabled = NO;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
// SMJobCopyDictionary does not work in sandbox (see rdar://13626319)
|
||||
CFArrayRef jobs = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
|
||||
#pragma clang diagnostic pop
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace platform_util {
|
||||
|
@ -167,29 +242,50 @@ void Beep() {
|
|||
NSBeep();
|
||||
}
|
||||
|
||||
bool GetLoginItemEnabled() {
|
||||
BOOL enabled = NO;
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
// SMJobCopyDictionary does not work in sandbox (see rdar://13626319)
|
||||
CFArrayRef jobs = SMCopyAllJobDictionaries(kSMDomainUserLaunchd);
|
||||
#pragma clang diagnostic pop
|
||||
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;
|
||||
}
|
||||
std::string GetLoginItemEnabled(const std::string& type,
|
||||
const std::string& service_name) {
|
||||
bool enabled = GetLoginItemEnabledDeprecated();
|
||||
if (@available(macOS 13, *)) {
|
||||
SMAppService* service = GetServiceForType(type, service_name);
|
||||
SMAppServiceStatus status = [service status];
|
||||
if (status == SMAppServiceStatusNotRegistered)
|
||||
return "not-registered";
|
||||
else if (status == SMAppServiceStatusEnabled)
|
||||
return "enabled";
|
||||
else if (status == SMAppServiceStatusRequiresApproval)
|
||||
return "requires-approval";
|
||||
else if (status == SMAppServiceStatusNotFound) {
|
||||
// If the login item was enabled with the old API, return that.
|
||||
return enabled ? "enabled-deprecated" : "not-found";
|
||||
}
|
||||
}
|
||||
return enabled;
|
||||
return enabled ? "enabled" : "not-registered";
|
||||
}
|
||||
|
||||
bool SetLoginItemEnabled(bool enabled) {
|
||||
NSString* identifier = GetLoginHelperBundleIdentifier();
|
||||
return SMLoginItemSetEnabled((__bridge CFStringRef)identifier, enabled);
|
||||
bool SetLoginItemEnabled(const std::string& type,
|
||||
const std::string& service_name,
|
||||
bool enabled) {
|
||||
if (@available(macOS 13, *)) {
|
||||
#if IS_MAS_BUILD()
|
||||
// If the app was previously set as a LoginItem with the old API, remove it
|
||||
// as a LoginItem via the old API before re-enabling with the new API.
|
||||
if (GetLoginItemEnabledDeprecated() && enabled) {
|
||||
NSString* identifier = GetLoginHelperBundleIdentifier();
|
||||
SMLoginItemSetEnabled((__bridge CFStringRef)identifier, false);
|
||||
}
|
||||
#endif
|
||||
SMAppService* service = GetServiceForType(type, service_name);
|
||||
NSError* error = nil;
|
||||
bool result = enabled ? [service registerAndReturnError:&error]
|
||||
: [service unregisterAndReturnError:&error];
|
||||
if (error != nil)
|
||||
LOG(ERROR) << "Unable to set login item: "
|
||||
<< GetLaunchStringForError(error);
|
||||
return result;
|
||||
} else {
|
||||
NSString* identifier = GetLoginHelperBundleIdentifier();
|
||||
return SMLoginItemSetEnabled((__bridge CFStringRef)identifier, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace platform_util
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue