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:
Shelley Vohr 2023-10-16 18:25:11 +02:00 committed by GitHub
parent 6d0d350e13
commit f7b1c75c72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 321 additions and 89 deletions

View file

@ -1278,10 +1278,10 @@ Returns `boolean` - Whether the current desktop environment is Unity launcher.
### `app.getLoginItemSettings([options])` _macOS_ _Windows_ ### `app.getLoginItemSettings([options])` _macOS_ _Windows_
* `options` Object (optional) * `options` Object (optional)
* `path` string (optional) _Windows_ - The executable path to compare against. * `type` string (optional) _macOS_ - Can be one of `mainAppService`, `agentService`, `daemonService`, or `loginItemService`. Defaults to `mainAppService`. Only available on macOS 13 and up. See [app.setLoginItemSettings](app.md#appsetloginitemsettingssettings-macos-windows) for more information about each type.
Defaults to `process.execPath`. * `serviceName` string (optional) _macOS_ - The name of the service. Required if `type` is non-default. Only available on macOS 13 and up.
* `args` string[] (optional) _Windows_ - The command-line arguments to compare * `path` string (optional) _Windows_ - The executable path to compare against. Defaults to `process.execPath`.
against. Defaults to an empty array. * `args` string[] (optional) _Windows_ - The command-line arguments to compare against. Defaults to an empty array.
If you provided `path` and `args` options to `app.setLoginItemSettings`, then you If you provided `path` and `args` options to `app.setLoginItemSettings`, then you
need to pass the same arguments here for `openAtLogin` to be set correctly. need to pass the same arguments here for `openAtLogin` to be set correctly.
@ -1289,17 +1289,11 @@ need to pass the same arguments here for `openAtLogin` to be set correctly.
Returns `Object`: Returns `Object`:
* `openAtLogin` boolean - `true` if the app is set to open at login. * `openAtLogin` boolean - `true` if the app is set to open at login.
* `openAsHidden` boolean _macOS_ - `true` if the app is set to open as hidden at login. * `openAsHidden` boolean _macOS_ _Deprecated_ - `true` if the app is set to open as hidden at login. This does not work on macOS 13 and up.
This setting is not available on [MAS builds][mas-builds]. * `wasOpenedAtLogin` boolean _macOS_ _Deprecated_ - `true` if the app was opened at login automatically. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
* `wasOpenedAtLogin` boolean _macOS_ - `true` if the app was opened at login * `wasOpenedAsHidden` boolean _macOS_ _Deprecated_ - `true` if the app was opened as a hidden login item. This indicates that the app should not open any windows at startup. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
automatically. This setting is not available on [MAS builds][mas-builds]. * `restoreState` boolean _macOS_ _Deprecated_ - `true` if the app was opened as a login item that should restore the state from the previous session. This indicates that the app should restore the windows that were open the last time the app was closed. This setting is not available on [MAS builds][mas-builds] or on macOS 13 and up.
* `wasOpenedAsHidden` boolean _macOS_ - `true` if the app was opened as a hidden login * `status` string _macOS_ - can be one of `not-registered`, `enabled`, `requires-approval`, or `not-found`.
item. This indicates that the app should not open any windows at startup.
This setting is not available on [MAS builds][mas-builds].
* `restoreState` boolean _macOS_ - `true` if the app was opened as a login item that
should restore the state from the previous session. This indicates that the
app should restore the windows that were open the last time the app was
closed. This setting is not available on [MAS builds][mas-builds].
* `executableWillLaunchAtLogin` boolean _Windows_ - `true` if app is set to open at login and its run key is not deactivated. This differs from `openAtLogin` as it ignores the `args` option, this property will be true if the given executable would be launched at login with **any** arguments. * `executableWillLaunchAtLogin` boolean _Windows_ - `true` if app is set to open at login and its run key is not deactivated. This differs from `openAtLogin` as it ignores the `args` option, this property will be true if the given executable would be launched at login with **any** arguments.
* `launchItems` Object[] _Windows_ * `launchItems` Object[] _Windows_
* `name` string _Windows_ - name value of a registry entry. * `name` string _Windows_ - name value of a registry entry.
@ -1313,10 +1307,14 @@ Returns `Object`:
* `settings` Object * `settings` Object
* `openAtLogin` boolean (optional) - `true` to open the app at login, `false` to remove * `openAtLogin` boolean (optional) - `true` to open the app at login, `false` to remove
the app as a login item. Defaults to `false`. the app as a login item. Defaults to `false`.
* `openAsHidden` boolean (optional) _macOS_ - `true` to open the app as hidden. Defaults to * `openAsHidden` boolean (optional) _macOS_ _Deprecated_ - `true` to open the app as hidden. Defaults to `false`. The user can edit this setting from the System Preferences so `app.getLoginItemSettings().wasOpenedAsHidden` should be checked when the app is opened to know the current value. This setting is not available on [MAS build
`false`. The user can edit this setting from the System Preferences so s][mas-builds] or on macOS 13 and up.
`app.getLoginItemSettings().wasOpenedAsHidden` should be checked when the app * `type` string (optional) _macOS_ - The type of service to add as a login item. Defaults to `mainAppService`. Only available on macOS 13 and up.
is opened to know the current value. This setting is not available on [MAS builds][mas-builds]. * `mainAppService` - The primary application.
* `agentService` - The property list name for a launch agent. The property list name must correspond to a property list in the apps `Contents/Library/LaunchAgents` directory.
* `daemonService` string (optional) _macOS_ - The property list name for a launch agent. The property list name must correspond to a property list in the apps `Contents/Library/LaunchDaemons` directory.
* `loginItemService` string (optional) _macOS_ - The property list name for a login item service. The property list name must correspond to a property list in the apps `Contents/Library/LoginItems` directory.
* `serviceName` string (optional) _macOS_ - The name of the service. Required if `type` is non-default. Only available on macOS 13 and up.
* `path` string (optional) _Windows_ - The executable to launch at login. * `path` string (optional) _Windows_ - The executable to launch at login.
Defaults to `process.execPath`. Defaults to `process.execPath`.
* `args` string[] (optional) _Windows_ - The command-line arguments to pass to * `args` string[] (optional) _Windows_ - The command-line arguments to pass to
@ -1325,6 +1323,7 @@ Returns `Object`:
* `enabled` boolean (optional) _Windows_ - `true` will change the startup approved registry key and `enable / disable` the App in Task Manager and Windows Settings. * `enabled` boolean (optional) _Windows_ - `true` will change the startup approved registry key and `enable / disable` the App in Task Manager and Windows Settings.
Defaults to `true`. Defaults to `true`.
* `name` string (optional) _Windows_ - value name to write into registry. Defaults to the app's AppUserModelId(). * `name` string (optional) _Windows_ - value name to write into registry. Defaults to the app's AppUserModelId().
Set the app's login item settings. Set the app's login item settings.
To work with Electron's `autoUpdater` on Windows, which uses [Squirrel][Squirrel-Windows], To work with Electron's `autoUpdater` on Windows, which uses [Squirrel][Squirrel-Windows],
@ -1349,6 +1348,8 @@ app.setLoginItemSettings({
}) })
``` ```
For more information about setting different services as login items on macOS 13 and up, see [`SMAppService`](https://developer.apple.com/documentation/servicemanagement/smappservice?language=objc).
### `app.isAccessibilitySupportEnabled()` _macOS_ _Windows_ ### `app.isAccessibilitySupportEnabled()` _macOS_ _Windows_
Returns `boolean` - `true` if Chrome's accessibility support is enabled, Returns `boolean` - `true` if Chrome's accessibility support is enabled,

View file

@ -85,6 +85,7 @@
#if BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_MAC)
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include "base/mac/mac_util.h"
#include "shell/browser/ui/cocoa/electron_bundle_mover.h" #include "shell/browser/ui/cocoa/electron_bundle_mover.h"
#endif #endif
@ -364,8 +365,11 @@ struct Converter<Browser::LoginItemSettings> {
dict.Get("path", &(out->path)); dict.Get("path", &(out->path));
dict.Get("args", &(out->args)); dict.Get("args", &(out->args));
#if BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_WIN)
dict.Get("enabled", &(out->enabled));
dict.Get("name", &(out->name)); 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 #endif
return true; return true;
} }
@ -373,16 +377,19 @@ struct Converter<Browser::LoginItemSettings> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate, static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
Browser::LoginItemSettings val) { Browser::LoginItemSettings val) {
auto dict = gin_helper::Dictionary::CreateEmpty(isolate); 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("openAtLogin", val.open_at_login);
dict.Set("openAsHidden", val.open_as_hidden); dict.Set("openAsHidden", val.open_as_hidden);
dict.Set("restoreState", val.restore_state); dict.Set("restoreState", val.restore_state);
dict.Set("wasOpenedAtLogin", val.opened_at_login); dict.Set("wasOpenedAtLogin", val.opened_at_login);
dict.Set("wasOpenedAsHidden", val.opened_as_hidden); 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(); return dict.GetHandle();
} }
}; };

View file

@ -135,7 +135,11 @@ class Browser : public WindowListObserver {
std::u16string path; std::u16string path;
std::vector<std::u16string> args; 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 // used in browser::setLoginItemSettings
bool enabled = true; bool enabled = true;
std::wstring name; std::wstring name;
@ -205,9 +209,9 @@ class Browser : public WindowListObserver {
void ApplyForcedRTL(); void ApplyForcedRTL();
// Bounce the dock icon. // Bounce the dock icon.
enum class BounceType { enum class BounceType{
kCritical = 0, // NSCriticalRequest kCritical = 0, // NSCriticalRequest
kInformational = 10, // NSInformationalRequest kInformational = 10, // NSInformationalRequest
}; };
int DockBounce(BounceType type); int DockBounce(BounceType type);
void DockCancelBounce(int request_id); void DockCancelBounce(int request_id);

View file

@ -8,6 +8,8 @@
#include <string> #include <string>
#include <utility> #include <utility>
#import <ServiceManagement/ServiceManagement.h>
#include "base/apple/bridging.h" #include "base/apple/bridging.h"
#include "base/apple/bundle_locations.h" #include "base/apple/bundle_locations.h"
#include "base/apple/scoped_cftyperef.h" #include "base/apple/scoped_cftyperef.h"
@ -19,6 +21,7 @@
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "net/base/mac/url_conversions.h" #include "net/base/mac/url_conversions.h"
#include "shell/browser/badging/badge_manager.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/dict_util.h"
#include "shell/browser/mac/electron_application.h" #include "shell/browser/mac/electron_application.h"
#include "shell/browser/mac/electron_application_delegate.h" #include "shell/browser/mac/electron_application_delegate.h"
@ -85,6 +88,15 @@ bool CheckLoginItemStatus(bool* is_hidden) {
return true; 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 #endif
} // namespace } // namespace
@ -367,28 +379,71 @@ void Browser::ApplyForcedRTL() {
Browser::LoginItemSettings Browser::GetLoginItemSettings( Browser::LoginItemSettings Browser::GetLoginItemSettings(
const LoginItemSettings& options) { const LoginItemSettings& options) {
LoginItemSettings settings; 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() #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 #else
settings.open_at_login = CheckLoginItemStatus(&settings.open_as_hidden); // If the app was previously set as a LoginItem with the deprecated API,
settings.restore_state = base::mac::WasLaunchedAsLoginItemRestoreState(); // we should report its LoginItemSettings via the old API.
settings.opened_at_login = base::mac::WasLaunchedAsLoginOrResumeItem(); LoginItemSettings settings_deprecated = GetLoginItemSettingsDeprecated();
settings.opened_as_hidden = base::mac::WasLaunchedAsHiddenLoginItem(); 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 #endif
return settings; return settings;
} }
void Browser::SetLoginItemSettings(LoginItemSettings settings) { void Browser::SetLoginItemSettings(LoginItemSettings settings) {
#if IS_MAS_BUILD() if (settings.type != "mainAppService" && settings.service_name.empty()) {
if (!platform_util::SetLoginItemEnabled(settings.open_at_login)) { gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
LOG(ERROR) << "Unable to set login item enabled on sandboxed app."; .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 #else
if (settings.open_at_login) { const base::FilePath bundle_path = base::apple::MainBundlePath();
base::mac::AddToLoginItems(base::apple::MainBundlePath(), if (@available(macOS 13, *)) {
settings.open_as_hidden); // 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 { } 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 #endif
} }

View file

@ -49,8 +49,11 @@ bool GetFolderPath(int key, base::FilePath* result);
#endif #endif
#if BUILDFLAG(IS_MAC) #if BUILDFLAG(IS_MAC)
bool GetLoginItemEnabled(); std::string GetLoginItemEnabled(const std::string& type,
bool SetLoginItemEnabled(bool enabled); const std::string& service_name);
bool SetLoginItemEnabled(const std::string& type,
const std::string& service_name,
bool enabled);
#endif #endif
#if BUILDFLAG(IS_LINUX) #if BUILDFLAG(IS_LINUX)

View file

@ -77,6 +77,81 @@ std::string OpenPathOnThread(const base::FilePath& full_path) {
return success ? "" : "Failed to open 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
namespace platform_util { namespace platform_util {
@ -167,29 +242,50 @@ void Beep() {
NSBeep(); NSBeep();
} }
bool GetLoginItemEnabled() { std::string GetLoginItemEnabled(const std::string& type,
BOOL enabled = NO; const std::string& service_name) {
#pragma clang diagnostic push bool enabled = GetLoginItemEnabledDeprecated();
#pragma clang diagnostic ignored "-Wdeprecated-declarations" if (@available(macOS 13, *)) {
// SMJobCopyDictionary does not work in sandbox (see rdar://13626319) SMAppService* service = GetServiceForType(type, service_name);
CFArrayRef jobs = SMCopyAllJobDictionaries(kSMDomainUserLaunchd); SMAppServiceStatus status = [service status];
#pragma clang diagnostic pop if (status == SMAppServiceStatusNotRegistered)
NSArray* jobs_ = CFBridgingRelease(jobs); return "not-registered";
NSString* identifier = GetLoginHelperBundleIdentifier(); else if (status == SMAppServiceStatusEnabled)
if (jobs_ && [jobs_ count] > 0) { return "enabled";
for (NSDictionary* job in jobs_) { else if (status == SMAppServiceStatusRequiresApproval)
if ([identifier isEqualToString:[job objectForKey:@"Label"]]) { return "requires-approval";
enabled = [[job objectForKey:@"OnDemand"] boolValue]; else if (status == SMAppServiceStatusNotFound) {
break; // 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) { bool SetLoginItemEnabled(const std::string& type,
NSString* identifier = GetLoginHelperBundleIdentifier(); const std::string& service_name,
return SMLoginItemSetEnabled((__bridge CFStringRef)identifier, enabled); 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 } // namespace platform_util

View file

@ -12,6 +12,7 @@ import { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers';
import { expectDeprecationMessages } from './lib/deprecate-helpers'; import { expectDeprecationMessages } from './lib/deprecate-helpers';
import { once } from 'node:events'; import { once } from 'node:events';
import split = require('split') import split = require('split')
import * as semver from 'semver';
const fixturesPath = path.resolve(__dirname, 'fixtures'); const fixturesPath = path.resolve(__dirname, 'fixtures');
@ -616,6 +617,9 @@ describe('app module', () => {
}); });
ifdescribe(process.platform !== 'linux' && !process.mas)('app.get/setLoginItemSettings API', function () { ifdescribe(process.platform !== 'linux' && !process.mas)('app.get/setLoginItemSettings API', function () {
const isMac = process.platform === 'darwin';
const isWin = process.platform === 'win32';
const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe'); const updateExe = path.resolve(path.dirname(process.execPath), '..', 'Update.exe');
const processStartArgs = [ const processStartArgs = [
'--processStart', `"${path.basename(process.execPath)}"`, '--processStart', `"${path.basename(process.execPath)}"`,
@ -631,6 +635,8 @@ describe('app module', () => {
'/f', '/f',
'/d' '/d'
]; ];
const productVersion = isMac ? cp.execSync('sw_vers -productVersion').toString().trim() : '';
const isVenturaOrHigher = semver.gt(semver.coerce(productVersion) || '0.0.0', '13.0.0');
beforeEach(() => { beforeEach(() => {
app.setLoginItemSettings({ openAtLogin: false }); app.setLoginItemSettings({ openAtLogin: false });
@ -644,18 +650,19 @@ describe('app module', () => {
app.setLoginItemSettings({ name: 'additionalEntry', openAtLogin: false }); app.setLoginItemSettings({ name: 'additionalEntry', openAtLogin: false });
}); });
ifit(process.platform !== 'win32')('sets and returns the app as a login item', function () { ifit(!isWin)('sets and returns the app as a login item', () => {
app.setLoginItemSettings({ openAtLogin: true }); app.setLoginItemSettings({ openAtLogin: true });
expect(app.getLoginItemSettings()).to.deep.equal({
openAtLogin: true, const settings = app.getLoginItemSettings();
openAsHidden: false, expect(settings.openAtLogin).to.equal(true);
wasOpenedAtLogin: false, expect(settings.openAsHidden).to.equal(false);
wasOpenedAsHidden: false, expect(settings.wasOpenedAtLogin).to.equal(false);
restoreState: false expect(settings.wasOpenedAsHidden).to.equal(false);
}); expect(settings.restoreState).to.equal(false);
if (isVenturaOrHigher) expect(settings.status).to.equal('enabled');
}); });
ifit(process.platform === 'win32')('sets and returns the app as a login item (windows)', function () { ifit(isWin)('sets and returns the app as a login item (windows)', () => {
app.setLoginItemSettings({ openAtLogin: true, enabled: true }); app.setLoginItemSettings({ openAtLogin: true, enabled: true });
expect(app.getLoginItemSettings()).to.deep.equal({ expect(app.getLoginItemSettings()).to.deep.equal({
openAtLogin: true, openAtLogin: true,
@ -692,18 +699,21 @@ describe('app module', () => {
}); });
}); });
ifit(process.platform !== 'win32')('adds a login item that loads in hidden mode', function () { ifit(!isWin)('adds a login item that loads in hidden mode', () => {
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true }); app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true });
expect(app.getLoginItemSettings()).to.deep.equal({
openAtLogin: true, const settings = app.getLoginItemSettings();
openAsHidden: process.platform === 'darwin' && !process.mas, // Only available on macOS expect(settings.openAtLogin).to.equal(true);
wasOpenedAtLogin: false,
wasOpenedAsHidden: false, const hasOpenAsHidden = process.platform === 'darwin' && !isVenturaOrHigher;
restoreState: false expect(settings.openAsHidden).to.equal(hasOpenAsHidden);
}); expect(settings.wasOpenedAtLogin).to.equal(false);
expect(settings.wasOpenedAsHidden).to.equal(false);
expect(settings.restoreState).to.equal(false);
if (isVenturaOrHigher) expect(settings.status).to.equal('enabled');
}); });
ifit(process.platform === 'win32')('adds a login item that loads in hidden mode (windows)', function () { ifit(isWin)('adds a login item that loads in hidden mode (windows)', () => {
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true }); app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true });
expect(app.getLoginItemSettings()).to.deep.equal({ expect(app.getLoginItemSettings()).to.deep.equal({
openAtLogin: true, openAtLogin: true,
@ -722,7 +732,7 @@ describe('app module', () => {
}); });
}); });
it('correctly sets and unsets the LoginItem', function () { it('correctly sets and unsets the LoginItem', () => {
expect(app.getLoginItemSettings().openAtLogin).to.equal(false); expect(app.getLoginItemSettings().openAtLogin).to.equal(false);
app.setLoginItemSettings({ openAtLogin: true }); app.setLoginItemSettings({ openAtLogin: true });
@ -732,20 +742,76 @@ describe('app module', () => {
expect(app.getLoginItemSettings().openAtLogin).to.equal(false); expect(app.getLoginItemSettings().openAtLogin).to.equal(false);
}); });
ifit(process.platform === 'darwin')('correctly sets and unsets the LoginItem as hidden', function () { ifit(isMac)('correctly sets and unsets the LoginItem as hidden', () => {
expect(app.getLoginItemSettings().openAtLogin).to.equal(false); expect(app.getLoginItemSettings().openAtLogin).to.equal(false);
expect(app.getLoginItemSettings().openAsHidden).to.equal(false); expect(app.getLoginItemSettings().openAsHidden).to.equal(false);
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true }); app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true });
expect(app.getLoginItemSettings().openAtLogin).to.equal(true); expect(app.getLoginItemSettings().openAtLogin).to.equal(true);
expect(app.getLoginItemSettings().openAsHidden).to.equal(true); expect(app.getLoginItemSettings().openAsHidden).to.equal(!isVenturaOrHigher);
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false }); app.setLoginItemSettings({ openAtLogin: true, openAsHidden: false });
expect(app.getLoginItemSettings().openAtLogin).to.equal(true); expect(app.getLoginItemSettings().openAtLogin).to.equal(true);
expect(app.getLoginItemSettings().openAsHidden).to.equal(false); expect(app.getLoginItemSettings().openAsHidden).to.equal(false);
}); });
ifit(process.platform === 'win32')('allows you to pass a custom executable and arguments', function () { ifdescribe(isMac)('using SMAppService', () => {
ifit(isVenturaOrHigher)('can set a login item', () => {
app.setLoginItemSettings({
openAtLogin: true,
type: 'mainAppService'
});
expect(app.getLoginItemSettings()).to.deep.equal({
status: 'enabled',
openAtLogin: true,
openAsHidden: false,
restoreState: false,
wasOpenedAtLogin: false,
wasOpenedAsHidden: false
});
});
ifit(isVenturaOrHigher)('throws when setting non-default type with no name', () => {
expect(() => {
app.setLoginItemSettings({
openAtLogin: true,
type: 'daemonService'
});
}).to.throw(/'name' is required when type is not mainAppService/);
});
ifit(isVenturaOrHigher)('throws when getting non-default type with no name', () => {
expect(() => {
app.getLoginItemSettings({
type: 'daemonService'
});
}).to.throw(/'name' is required when type is not mainAppService/);
});
ifit(isVenturaOrHigher)('can unset a login item', () => {
app.setLoginItemSettings({
openAtLogin: true,
type: 'mainAppService'
});
app.setLoginItemSettings({
openAtLogin: false,
type: 'mainAppService'
});
expect(app.getLoginItemSettings()).to.deep.equal({
status: 'not-registered',
openAtLogin: false,
openAsHidden: false,
restoreState: false,
wasOpenedAtLogin: false,
wasOpenedAsHidden: false
});
});
});
ifit(isWin)('allows you to pass a custom executable and arguments', () => {
app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs, enabled: true }); app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs, enabled: true });
expect(app.getLoginItemSettings().openAtLogin).to.equal(false); expect(app.getLoginItemSettings().openAtLogin).to.equal(false);
const openAtLoginTrueEnabledTrue = app.getLoginItemSettings({ const openAtLoginTrueEnabledTrue = app.getLoginItemSettings({
@ -775,7 +841,7 @@ describe('app module', () => {
expect(openAtLoginFalseEnabledFalse.executableWillLaunchAtLogin).to.equal(false); expect(openAtLoginFalseEnabledFalse.executableWillLaunchAtLogin).to.equal(false);
}); });
ifit(process.platform === 'win32')('allows you to pass a custom name', function () { ifit(isWin)('allows you to pass a custom name', () => {
app.setLoginItemSettings({ openAtLogin: true }); app.setLoginItemSettings({ openAtLogin: true });
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false }); app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false });
expect(app.getLoginItemSettings()).to.deep.equal({ expect(app.getLoginItemSettings()).to.deep.equal({
@ -818,7 +884,7 @@ describe('app module', () => {
}); });
}); });
ifit(process.platform === 'win32')('finds launch items independent of args', function () { ifit(isWin)('finds launch items independent of args', () => {
app.setLoginItemSettings({ openAtLogin: true, args: ['arg1'] }); app.setLoginItemSettings({ openAtLogin: true, args: ['arg1'] });
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false, args: ['arg2'] }); app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false, args: ['arg2'] });
expect(app.getLoginItemSettings()).to.deep.equal({ expect(app.getLoginItemSettings()).to.deep.equal({
@ -844,7 +910,7 @@ describe('app module', () => {
}); });
}); });
ifit(process.platform === 'win32')('finds launch items independent of path quotation or casing', function () { ifit(isWin)('finds launch items independent of path quotation or casing', () => {
const expectation = { const expectation = {
openAtLogin: false, openAtLogin: false,
openAsHidden: false, openAsHidden: false,
@ -880,7 +946,7 @@ describe('app module', () => {
}); });
}); });
ifit(process.platform === 'win32')('detects disabled by TaskManager', async function () { ifit(isWin)('detects disabled by TaskManager', async () => {
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: true, args: ['arg1'] }); app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: true, args: ['arg1'] });
const appProcess = cp.spawn('reg', [...regAddArgs, '030000000000000000000000']); const appProcess = cp.spawn('reg', [...regAddArgs, '030000000000000000000000']);
await once(appProcess, 'exit'); await once(appProcess, 'exit');
@ -901,7 +967,7 @@ describe('app module', () => {
}); });
}); });
ifit(process.platform === 'win32')('detects enabled by TaskManager', async function () { ifit(isWin)('detects enabled by TaskManager', async () => {
const expectation = { const expectation = {
openAtLogin: false, openAtLogin: false,
openAsHidden: false, openAsHidden: false,