feat: add capabilities to app.getLoginItemSettings() and app.setLoginItemSettings() (#24494)
* fixed * semantic commit * add comments to browser.h Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
This commit is contained in:
parent
75372e933f
commit
1b175a0609
6 changed files with 326 additions and 16 deletions
|
@ -1216,6 +1216,13 @@ Returns `Object`:
|
|||
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.
|
||||
* `launchItems` Object[] _Windows_
|
||||
* `name` String _Windows_ - name value of a registry entry.
|
||||
* `path` String _Windows_ - The executable to an app that corresponds to a registry entry.
|
||||
* `args` String[] _Windows_ - the command-line arguments to pass to the executable.
|
||||
* `scope` String _Windows_ - one of `user` or `machine`. Indicates whether the registry entry is under `HKEY_CURRENT USER` or `HKEY_LOCAL_MACHINE`.
|
||||
* `enabled` Boolean _Windows_ - `true` if the app registry key is startup approved and therfore shows as `enabled` in Task Manager and Windows settings.
|
||||
|
||||
### `app.setLoginItemSettings(settings)` _macOS_ _Windows_
|
||||
|
||||
|
@ -1231,7 +1238,9 @@ Returns `Object`:
|
|||
* `args` String[] (optional) _Windows_ - The command-line arguments to pass to
|
||||
the executable. Defaults to an empty array. Take care to wrap paths in
|
||||
quotes.
|
||||
|
||||
* `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`.
|
||||
* `name` String (optional) _Windows_ - value name to write into registry. Defaults to the app's AppUserModelId().
|
||||
Set the app's login item settings.
|
||||
|
||||
To work with Electron's `autoUpdater` on Windows, which uses [Squirrel][Squirrel-Windows],
|
||||
|
|
|
@ -334,6 +334,37 @@ struct Converter<JumpListResult> {
|
|||
};
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
template <>
|
||||
struct Converter<Browser::LaunchItem> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
Browser::LaunchItem* out) {
|
||||
gin_helper::Dictionary dict;
|
||||
if (!ConvertFromV8(isolate, val, &dict))
|
||||
return false;
|
||||
|
||||
dict.Get("name", &(out->name));
|
||||
dict.Get("path", &(out->path));
|
||||
dict.Get("args", &(out->args));
|
||||
dict.Get("scope", &(out->scope));
|
||||
dict.Get("enabled", &(out->enabled));
|
||||
return true;
|
||||
}
|
||||
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
Browser::LaunchItem val) {
|
||||
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("name", val.name);
|
||||
dict.Set("path", val.path);
|
||||
dict.Set("args", val.args);
|
||||
dict.Set("scope", val.scope);
|
||||
dict.Set("enabled", val.enabled);
|
||||
return dict.GetHandle();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <>
|
||||
struct Converter<Browser::LoginItemSettings> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
|
@ -347,6 +378,10 @@ struct Converter<Browser::LoginItemSettings> {
|
|||
dict.Get("openAsHidden", &(out->open_as_hidden));
|
||||
dict.Get("path", &(out->path));
|
||||
dict.Get("args", &(out->args));
|
||||
#if defined(OS_WIN)
|
||||
dict.Get("enabled", &(out->enabled));
|
||||
dict.Get("name", &(out->name));
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -358,6 +393,11 @@ struct Converter<Browser::LoginItemSettings> {
|
|||
dict.Set("restoreState", val.restore_state);
|
||||
dict.Set("wasOpenedAtLogin", val.opened_at_login);
|
||||
dict.Set("wasOpenedAsHidden", val.opened_as_hidden);
|
||||
#if defined(OS_WIN)
|
||||
dict.Set("launchItems", val.launch_items);
|
||||
dict.Set("executableWillLaunchAtLogin",
|
||||
val.executable_will_launch_at_login);
|
||||
#endif
|
||||
return dict.GetHandle();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -40,6 +40,12 @@ void RunQuitClosure(base::OnceClosure quit) {
|
|||
|
||||
} // namespace
|
||||
|
||||
#if defined(OS_WIN)
|
||||
Browser::LaunchItem::LaunchItem() = default;
|
||||
Browser::LaunchItem::~LaunchItem() = default;
|
||||
Browser::LaunchItem::LaunchItem(const LaunchItem& other) = default;
|
||||
#endif
|
||||
|
||||
Browser::LoginItemSettings::LoginItemSettings() = default;
|
||||
Browser::LoginItemSettings::~LoginItemSettings() = default;
|
||||
Browser::LoginItemSettings::LoginItemSettings(const LoginItemSettings& other) =
|
||||
|
|
|
@ -110,6 +110,20 @@ class Browser : public WindowListObserver {
|
|||
bool SetBadgeCount(int count);
|
||||
int GetBadgeCount();
|
||||
|
||||
#if defined(OS_WIN)
|
||||
struct LaunchItem {
|
||||
base::string16 name;
|
||||
base::string16 path;
|
||||
base::string16 scope;
|
||||
std::vector<base::string16> args;
|
||||
bool enabled = true;
|
||||
|
||||
LaunchItem();
|
||||
~LaunchItem();
|
||||
LaunchItem(const LaunchItem&);
|
||||
};
|
||||
#endif
|
||||
|
||||
// Set/Get the login item settings of the app
|
||||
struct LoginItemSettings {
|
||||
bool open_at_login = false;
|
||||
|
@ -120,6 +134,16 @@ class Browser : public WindowListObserver {
|
|||
base::string16 path;
|
||||
std::vector<base::string16> args;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// used in browser::setLoginItemSettings
|
||||
bool enabled = true;
|
||||
base::string16 name = base::string16();
|
||||
|
||||
// used in browser::getLoginItemSettings
|
||||
bool executable_will_launch_at_login = false;
|
||||
std::vector<LaunchItem> launch_items;
|
||||
#endif
|
||||
|
||||
LoginItemSettings();
|
||||
~LoginItemSettings();
|
||||
LoginItemSettings(const LoginItemSettings&);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "base/base_paths.h"
|
||||
#include "base/file_version_info.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
|
@ -183,6 +184,87 @@ bool FormatCommandLineString(base::string16* exe,
|
|||
return true;
|
||||
}
|
||||
|
||||
// Helper for GetLoginItemSettings().
|
||||
// iterates over all the entries in a windows registry path and returns
|
||||
// a list of launchItem with matching paths to our application.
|
||||
// if a launchItem with a matching path also has a matching entry within the
|
||||
// startup_approved_key_path, set executable_will_launch_at_login to be `true`
|
||||
std::vector<Browser::LaunchItem> GetLoginItemSettingsHelper(
|
||||
base::win::RegistryValueIterator* it,
|
||||
boolean* executable_will_launch_at_login,
|
||||
base::string16 scope,
|
||||
const Browser::LoginItemSettings& options) {
|
||||
std::vector<Browser::LaunchItem> launch_items;
|
||||
|
||||
while (it->Valid()) {
|
||||
base::string16 exe = options.path;
|
||||
if (FormatCommandLineString(&exe, options.args)) {
|
||||
// add launch item to vector if it has a matching path (case-insensitive)
|
||||
if ((base::CompareCaseInsensitiveASCII(it->Value(), exe.c_str())) == 0) {
|
||||
Browser::LaunchItem launch_item;
|
||||
base::string16 launch_path = options.path;
|
||||
if (!(launch_path.size() > 0)) {
|
||||
GetProcessExecPath(&launch_path);
|
||||
}
|
||||
launch_item.name = it->Name();
|
||||
launch_item.path = launch_path;
|
||||
launch_item.args = options.args;
|
||||
launch_item.scope = scope;
|
||||
launch_item.enabled = true;
|
||||
|
||||
// attempt to update launch_item.enabled if there is a matching key
|
||||
// value entry in the StartupApproved registry
|
||||
HKEY hkey;
|
||||
// StartupApproved registry path
|
||||
LPCTSTR path = TEXT(
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApp"
|
||||
"roved\\Run");
|
||||
LONG res;
|
||||
if (scope == L"user") {
|
||||
res =
|
||||
RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_QUERY_VALUE, &hkey);
|
||||
} else {
|
||||
res =
|
||||
RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, KEY_QUERY_VALUE, &hkey);
|
||||
}
|
||||
if (res == ERROR_SUCCESS) {
|
||||
DWORD type, size;
|
||||
wchar_t startup_binary[12];
|
||||
LONG result =
|
||||
RegQueryValueEx(hkey, it->Name(), nullptr, &type,
|
||||
reinterpret_cast<BYTE*>(&startup_binary),
|
||||
&(size = sizeof(startup_binary)));
|
||||
if (result == ERROR_SUCCESS) {
|
||||
if (type == REG_BINARY) {
|
||||
// any other binary other than this indicates that the program is
|
||||
// not set to launch at login
|
||||
wchar_t binary_accepted[12] = {0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00};
|
||||
wchar_t binary_accepted_alt[12] = {0x02, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00};
|
||||
std::string reg_binary(reinterpret_cast<char*>(binary_accepted));
|
||||
std::string reg_binary_alt(
|
||||
reinterpret_cast<char*>(binary_accepted_alt));
|
||||
std::string reg_startup_binary(
|
||||
reinterpret_cast<char*>(startup_binary));
|
||||
launch_item.enabled = (reg_binary == reg_startup_binary) ||
|
||||
(reg_binary == reg_binary_alt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*executable_will_launch_at_login =
|
||||
*executable_will_launch_at_login || launch_item.enabled;
|
||||
launch_items.push_back(launch_item);
|
||||
}
|
||||
}
|
||||
it->operator++();
|
||||
}
|
||||
return launch_items;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileVersionInfo> FetchFileVersionInfo() {
|
||||
base::FilePath path;
|
||||
|
||||
|
@ -515,16 +597,46 @@ bool Browser::SetBadgeCount(int count) {
|
|||
}
|
||||
|
||||
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
base::string16 keyPath = L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
base::win::RegKey key(HKEY_CURRENT_USER, keyPath.c_str(), KEY_ALL_ACCESS);
|
||||
base::string16 key_path =
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
||||
base::win::RegKey key(HKEY_CURRENT_USER, key_path.c_str(), KEY_ALL_ACCESS);
|
||||
|
||||
base::string16 startup_approved_key_path =
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApproved"
|
||||
L"\\Run";
|
||||
base::win::RegKey startup_approved_key(
|
||||
HKEY_CURRENT_USER, startup_approved_key_path.c_str(), KEY_ALL_ACCESS);
|
||||
PCWSTR key_name =
|
||||
settings.name.size() > 0 ? settings.name.c_str() : GetAppUserModelID();
|
||||
|
||||
if (settings.open_at_login) {
|
||||
base::string16 exe = settings.path;
|
||||
if (FormatCommandLineString(&exe, settings.args)) {
|
||||
key.WriteValue(GetAppUserModelID(), exe.c_str());
|
||||
key.WriteValue(key_name, exe.c_str());
|
||||
|
||||
if (settings.enabled) {
|
||||
startup_approved_key.DeleteValue(key_name);
|
||||
} else {
|
||||
HKEY hard_key;
|
||||
LPCTSTR path = TEXT(
|
||||
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\StartupApp"
|
||||
"roved\\Run");
|
||||
LONG res =
|
||||
RegOpenKeyEx(HKEY_CURRENT_USER, path, 0, KEY_ALL_ACCESS, &hard_key);
|
||||
|
||||
if (res == ERROR_SUCCESS) {
|
||||
UCHAR disable_startup_binary[] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
RegSetValueEx(hard_key, key_name, 0, REG_BINARY,
|
||||
reinterpret_cast<const BYTE*>(disable_startup_binary),
|
||||
sizeof(disable_startup_binary));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
key.DeleteValue(GetAppUserModelID());
|
||||
// if open at login is false, delete both values
|
||||
startup_approved_key.DeleteValue(key_name);
|
||||
key.DeleteValue(key_name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -535,6 +647,7 @@ Browser::LoginItemSettings Browser::GetLoginItemSettings(
|
|||
base::win::RegKey key(HKEY_CURRENT_USER, keyPath.c_str(), KEY_ALL_ACCESS);
|
||||
base::string16 keyVal;
|
||||
|
||||
// keep old openAtLogin behaviour
|
||||
if (!FAILED(key.ReadValue(GetAppUserModelID(), &keyVal))) {
|
||||
base::string16 exe = options.path;
|
||||
if (FormatCommandLineString(&exe, options.args)) {
|
||||
|
@ -542,6 +655,27 @@ Browser::LoginItemSettings Browser::GetLoginItemSettings(
|
|||
}
|
||||
}
|
||||
|
||||
// iterate over current user and machine registries and populate launch items
|
||||
// if there exists a launch entry with property enabled=='true',
|
||||
// set executable_will_launch_at_login to 'true'.
|
||||
boolean executable_will_launch_at_login = false;
|
||||
std::vector<Browser::LaunchItem> launch_items;
|
||||
base::win::RegistryValueIterator hkcu_iterator(HKEY_CURRENT_USER,
|
||||
keyPath.c_str());
|
||||
base::win::RegistryValueIterator hklm_iterator(HKEY_LOCAL_MACHINE,
|
||||
keyPath.c_str());
|
||||
|
||||
launch_items = GetLoginItemSettingsHelper(
|
||||
&hkcu_iterator, &executable_will_launch_at_login, L"user", options);
|
||||
std::vector<Browser::LaunchItem> launch_items_hklm =
|
||||
GetLoginItemSettingsHelper(&hklm_iterator,
|
||||
&executable_will_launch_at_login, L"machine",
|
||||
options);
|
||||
launch_items.insert(launch_items.end(), launch_items_hklm.begin(),
|
||||
launch_items_hklm.end());
|
||||
|
||||
settings.executable_will_launch_at_login = executable_will_launch_at_login;
|
||||
settings.launch_items = launch_items;
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
|
|
@ -619,7 +619,7 @@ describe('app module', () => {
|
|||
app.setLoginItemSettings({ openAtLogin: false, path: updateExe, args: processStartArgs });
|
||||
});
|
||||
|
||||
it('sets and returns the app as a login item', done => {
|
||||
ifit(process.platform !== 'win32')('sets and returns the app as a login item', function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true });
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: true,
|
||||
|
@ -628,10 +628,28 @@ describe('app module', () => {
|
|||
wasOpenedAsHidden: false,
|
||||
restoreState: false
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('adds a login item that loads in hidden mode', done => {
|
||||
ifit(process.platform === 'win32')('sets and returns the app as a login item (windows)', function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true });
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: true,
|
||||
openAsHidden: false,
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false,
|
||||
executableWillLaunchAtLogin: true,
|
||||
launchItems: [{
|
||||
name: 'electron.app.Electron',
|
||||
path: process.execPath,
|
||||
args: [],
|
||||
scope: 'user',
|
||||
enabled: true
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
ifit(process.platform !== 'win32')('adds a login item that loads in hidden mode', function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true });
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: true,
|
||||
|
@ -640,7 +658,25 @@ describe('app module', () => {
|
|||
wasOpenedAsHidden: false,
|
||||
restoreState: false
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
ifit(process.platform === 'win32')('adds a login item that loads in hidden mode (windows)', function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true, openAsHidden: true });
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: true,
|
||||
openAsHidden: false,
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false,
|
||||
executableWillLaunchAtLogin: true,
|
||||
launchItems: [{
|
||||
name: 'electron.app.Electron',
|
||||
path: process.execPath,
|
||||
args: [],
|
||||
scope: 'user',
|
||||
enabled: true
|
||||
}]
|
||||
});
|
||||
});
|
||||
|
||||
it('correctly sets and unsets the LoginItem', function () {
|
||||
|
@ -668,16 +704,77 @@ describe('app module', () => {
|
|||
expect(app.getLoginItemSettings().openAsHidden).to.equal(false);
|
||||
});
|
||||
|
||||
it('allows you to pass a custom executable and arguments', function () {
|
||||
if (process.platform !== 'win32') this.skip();
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs });
|
||||
|
||||
ifit(process.platform === 'win32')('allows you to pass a custom executable and arguments', function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs, enabled: true });
|
||||
expect(app.getLoginItemSettings().openAtLogin).to.equal(false);
|
||||
expect(app.getLoginItemSettings({
|
||||
const openAtLoginTrueEnabledTrue = app.getLoginItemSettings({
|
||||
path: updateExe,
|
||||
args: processStartArgs
|
||||
}).openAtLogin).to.equal(true);
|
||||
});
|
||||
|
||||
expect(openAtLoginTrueEnabledTrue.openAtLogin).to.equal(true);
|
||||
expect(openAtLoginTrueEnabledTrue.executableWillLaunchAtLogin).to.equal(true);
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: true, path: updateExe, args: processStartArgs, enabled: false });
|
||||
const openAtLoginTrueEnabledFalse = app.getLoginItemSettings({
|
||||
path: updateExe,
|
||||
args: processStartArgs
|
||||
});
|
||||
|
||||
expect(openAtLoginTrueEnabledFalse.openAtLogin).to.equal(true);
|
||||
expect(openAtLoginTrueEnabledFalse.executableWillLaunchAtLogin).to.equal(false);
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: false, path: updateExe, args: processStartArgs, enabled: false });
|
||||
const openAtLoginFalseEnabledFalse = app.getLoginItemSettings({
|
||||
path: updateExe,
|
||||
args: processStartArgs
|
||||
});
|
||||
|
||||
expect(openAtLoginFalseEnabledFalse.openAtLogin).to.equal(false);
|
||||
expect(openAtLoginFalseEnabledFalse.executableWillLaunchAtLogin).to.equal(false);
|
||||
});
|
||||
|
||||
ifit(process.platform === 'win32')('allows you to pass a custom name', function () {
|
||||
app.setLoginItemSettings({ openAtLogin: true });
|
||||
app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false });
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: true,
|
||||
openAsHidden: false,
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false,
|
||||
executableWillLaunchAtLogin: true,
|
||||
launchItems: [{
|
||||
name: 'additionalEntry',
|
||||
path: process.execPath,
|
||||
args: [],
|
||||
scope: 'user',
|
||||
enabled: false
|
||||
}, {
|
||||
name: 'electron.app.Electron',
|
||||
path: process.execPath,
|
||||
args: [],
|
||||
scope: 'user',
|
||||
enabled: true
|
||||
}]
|
||||
});
|
||||
|
||||
app.setLoginItemSettings({ openAtLogin: false, name: 'additionalEntry' });
|
||||
expect(app.getLoginItemSettings()).to.deep.equal({
|
||||
openAtLogin: true,
|
||||
openAsHidden: false,
|
||||
wasOpenedAtLogin: false,
|
||||
wasOpenedAsHidden: false,
|
||||
restoreState: false,
|
||||
executableWillLaunchAtLogin: true,
|
||||
launchItems: [{
|
||||
name: 'electron.app.Electron',
|
||||
path: process.execPath,
|
||||
args: [],
|
||||
scope: 'user',
|
||||
enabled: true
|
||||
}]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue