mirror of
https://github.com/bitwarden/clients.git
synced 2026-07-21 21:17:06 +08:00
[PM-38565] Replace tray settings with "Keep Bitwarden running in the background" (#21044)
* Implement new settings behavior * Copy changes * Migrate setting * Fix build * Fix
This commit is contained in:
parent
a2c9e34784
commit
dde73675e8
@ -100,34 +100,15 @@
|
||||
</bit-tab>
|
||||
|
||||
<bit-tab [label]="'appPreferences' | i18n">
|
||||
<bit-form-control>
|
||||
<input type="checkbox" bitCheckbox formControlName="enableTray" (change)="saveTray()" />
|
||||
<bit-label>{{ enableTrayText }}</bit-label>
|
||||
<bit-hint>{{ enableTrayDescText }}</bit-hint>
|
||||
</bit-form-control>
|
||||
|
||||
@if (showMinToTray) {
|
||||
<bit-form-control>
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
formControlName="enableMinToTray"
|
||||
(change)="saveMinToTray()"
|
||||
/>
|
||||
<bit-label>{{ enableMinToTrayText }}</bit-label>
|
||||
<bit-hint>{{ enableMinToTrayDescText }}</bit-hint>
|
||||
</bit-form-control>
|
||||
}
|
||||
|
||||
<bit-form-control>
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
formControlName="enableCloseToTray"
|
||||
(change)="saveCloseToTray()"
|
||||
formControlName="runInBackground"
|
||||
(change)="saveRunInBackground()"
|
||||
/>
|
||||
<bit-label>{{ enableCloseToTrayText }}</bit-label>
|
||||
<bit-hint>{{ enableCloseToTrayDescText }}</bit-hint>
|
||||
<bit-label>{{ runInBackgroundText }}</bit-label>
|
||||
<bit-hint>{{ runInBackgroundDescText }}</bit-hint>
|
||||
</bit-form-control>
|
||||
|
||||
@if (showOpenAtLoginOption) {
|
||||
@ -143,19 +124,6 @@
|
||||
</bit-form-control>
|
||||
}
|
||||
|
||||
@if (showAlwaysShowDock) {
|
||||
<bit-form-control>
|
||||
<input
|
||||
type="checkbox"
|
||||
bitCheckbox
|
||||
formControlName="alwaysShowDock"
|
||||
(change)="saveAlwaysShowDock()"
|
||||
/>
|
||||
<bit-label>{{ "alwaysShowDock" | i18n }}</bit-label>
|
||||
<bit-hint>{{ "alwaysShowDockDesc" | i18n }}</bit-hint>
|
||||
</bit-form-control>
|
||||
}
|
||||
|
||||
<bit-form-control>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@ -190,11 +190,8 @@ describe("SettingsDialogComponent", () => {
|
||||
biometricStateService.promptAutomatically$ = of(false);
|
||||
autofillSettingsServiceAbstraction.clearClipboardDelay$ = of(null);
|
||||
desktopSettingsService.minimizeOnCopy$ = of(false);
|
||||
desktopSettingsService.trayEnabled$ = of(false);
|
||||
desktopSettingsService.minimizeToTray$ = of(false);
|
||||
desktopSettingsService.closeToTray$ = of(false);
|
||||
desktopSettingsService.runInBackground$ = of(false);
|
||||
desktopSettingsService.openAtLogin$ = of(false);
|
||||
desktopSettingsService.alwaysShowDock$ = of(false);
|
||||
desktopSettingsService.browserIntegrationEnabled$ = of(false);
|
||||
desktopSettingsService.hardwareAcceleration$ = of(false);
|
||||
desktopSettingsService.sshAgentEnabled$ = of(false);
|
||||
|
||||
@ -136,20 +136,13 @@ export class SettingsDialogComponent implements OnInit {
|
||||
protected readonly isWindows: boolean;
|
||||
protected readonly isLinux: boolean;
|
||||
protected readonly isMac: boolean;
|
||||
protected readonly requireEnableTray: boolean;
|
||||
protected readonly showOpenAtLoginOption: boolean;
|
||||
protected readonly showDuckDuckGoIntegrationOption: boolean;
|
||||
protected readonly enableTrayText: string;
|
||||
protected readonly enableTrayDescText: string;
|
||||
protected readonly enableMinToTrayText: string;
|
||||
protected readonly enableMinToTrayDescText: string;
|
||||
protected readonly enableCloseToTrayText: string;
|
||||
protected readonly enableCloseToTrayDescText: string;
|
||||
protected readonly runInBackgroundText: string;
|
||||
protected readonly runInBackgroundDescText: string;
|
||||
|
||||
protected readonly supportsBiometric = signal(false);
|
||||
protected readonly showEnableAutotype = signal(false);
|
||||
protected readonly showMinToTray: boolean;
|
||||
protected readonly showAlwaysShowDock: boolean;
|
||||
private readonly activeAccount = toSignal(this.accountService.activeAccount$, {
|
||||
requireSync: true,
|
||||
});
|
||||
@ -182,11 +175,8 @@ export class SettingsDialogComponent implements OnInit {
|
||||
minimizeOnCopyToClipboard: false,
|
||||
enableFavicons: false,
|
||||
// App Settings
|
||||
enableTray: false,
|
||||
enableMinToTray: false,
|
||||
enableCloseToTray: false,
|
||||
runInBackground: false,
|
||||
openAtLogin: false,
|
||||
alwaysShowDock: false,
|
||||
enableBrowserIntegration: false,
|
||||
enableHardwareAcceleration: true,
|
||||
enableSshAgent: false,
|
||||
@ -206,23 +196,11 @@ export class SettingsDialogComponent implements OnInit {
|
||||
this.isMac = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
this.isLinux = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
|
||||
this.isWindows = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop;
|
||||
this.showMinToTray = !this.isLinux;
|
||||
this.showAlwaysShowDock = this.isMac;
|
||||
|
||||
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
|
||||
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
|
||||
|
||||
const trayKey = this.isMac ? "enableMenuBar" : "enableTray";
|
||||
this.enableTrayText = this.i18nService.t(trayKey);
|
||||
this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
|
||||
|
||||
const minToTrayKey = this.isMac ? "enableMinToMenuBar" : "enableMinToTray";
|
||||
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
|
||||
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
|
||||
|
||||
const closeToTrayKey = this.isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
|
||||
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
|
||||
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
|
||||
this.runInBackgroundText = this.i18nService.t("runInBackground");
|
||||
this.runInBackgroundDescText = this.i18nService.t(
|
||||
this.isMac ? "runInBackgroundDescMac" : "runInBackgroundDesc",
|
||||
);
|
||||
|
||||
this.showOpenAtLoginOption = this.showAutostartSetting();
|
||||
|
||||
@ -294,11 +272,8 @@ export class SettingsDialogComponent implements OnInit {
|
||||
clearClipboard: await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$),
|
||||
minimizeOnCopyToClipboard: await firstValueFrom(this.desktopSettingsService.minimizeOnCopy$),
|
||||
enableFavicons: await firstValueFrom(this.domainSettingsService.showFavicons$),
|
||||
enableTray: await firstValueFrom(this.desktopSettingsService.trayEnabled$),
|
||||
enableMinToTray: await firstValueFrom(this.desktopSettingsService.minimizeToTray$),
|
||||
enableCloseToTray: await firstValueFrom(this.desktopSettingsService.closeToTray$),
|
||||
runInBackground: await firstValueFrom(this.desktopSettingsService.runInBackground$),
|
||||
openAtLogin: await firstValueFrom(this.desktopSettingsService.openAtLogin$),
|
||||
alwaysShowDock: await firstValueFrom(this.desktopSettingsService.alwaysShowDock$),
|
||||
enableBrowserIntegration: await firstValueFrom(
|
||||
this.desktopSettingsService.browserIntegrationEnabled$,
|
||||
),
|
||||
@ -556,44 +531,8 @@ export class SettingsDialogComponent implements OnInit {
|
||||
this.messagingService.send("refreshCiphers");
|
||||
}
|
||||
|
||||
protected async saveMinToTray() {
|
||||
await this.desktopSettingsService.setMinimizeToTray(this.form.value.enableMinToTray);
|
||||
}
|
||||
|
||||
protected async saveCloseToTray() {
|
||||
if (this.requireEnableTray) {
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
await this.desktopSettingsService.setTrayEnabled(this.form.value.enableTray);
|
||||
}
|
||||
|
||||
await this.desktopSettingsService.setCloseToTray(this.form.value.enableCloseToTray);
|
||||
}
|
||||
|
||||
protected async saveTray() {
|
||||
if (
|
||||
this.requireEnableTray &&
|
||||
!this.form.value.enableTray &&
|
||||
this.form.value.enableCloseToTray
|
||||
) {
|
||||
const confirm = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "confirmTrayTitle" },
|
||||
content: { key: "confirmTrayDesc" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (confirm) {
|
||||
this.form.controls.enableCloseToTray.setValue(false, { emitEvent: false });
|
||||
await this.desktopSettingsService.setCloseToTray(this.form.value.enableCloseToTray);
|
||||
} else {
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.desktopSettingsService.setTrayEnabled(this.form.value.enableTray);
|
||||
// TODO: Ideally the DesktopSettingsService.trayEnabled$ could be subscribed to instead of using messaging.
|
||||
this.messagingService.send(this.form.value.enableTray ? "showTray" : "removeTray");
|
||||
protected async saveRunInBackground() {
|
||||
await this.desktopSettingsService.setRunInBackground(this.form.value.runInBackground);
|
||||
}
|
||||
|
||||
private async saveLocale(newValue: string) {
|
||||
@ -615,10 +554,6 @@ export class SettingsDialogComponent implements OnInit {
|
||||
await this.autofillSettingsService.setClearClipboardDelay(newValue);
|
||||
}
|
||||
|
||||
protected async saveAlwaysShowDock() {
|
||||
await this.desktopSettingsService.setAlwaysShowDock(this.form.value.alwaysShowDock);
|
||||
}
|
||||
|
||||
private showAutostartSetting(): boolean {
|
||||
// Windows store does not support autostart
|
||||
// Dev mode should not show auto-start, because it would result in an empty electron window starting on login
|
||||
|
||||
@ -183,51 +183,19 @@
|
||||
<ng-container *ngIf="showAppPreferences">
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="enableTray">
|
||||
<label for="runInBackground">
|
||||
<input
|
||||
id="enableTray"
|
||||
id="runInBackground"
|
||||
type="checkbox"
|
||||
aria-describedby="enableTrayHelp"
|
||||
formControlName="enableTray"
|
||||
(change)="saveTray()"
|
||||
aria-describedby="runInBackgroundHelp"
|
||||
formControlName="runInBackground"
|
||||
(change)="saveRunInBackground()"
|
||||
/>
|
||||
{{ enableTrayText }}
|
||||
{{ runInBackgroundText }}
|
||||
</label>
|
||||
</div>
|
||||
<small id="enableTrayHelp" class="help-block">{{ enableTrayDescText }}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showMinToTray">
|
||||
<div class="checkbox">
|
||||
<label for="enableMinToTray">
|
||||
<input
|
||||
id="enableMinToTray"
|
||||
type="checkbox"
|
||||
aria-describedby="enableMinToTrayHelp"
|
||||
formControlName="enableMinToTray"
|
||||
(change)="saveMinToTray()"
|
||||
/>
|
||||
{{ enableMinToTrayText }}
|
||||
</label>
|
||||
</div>
|
||||
<small id="enableMinToTrayHelp" class="help-block">{{
|
||||
enableMinToTrayDescText
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="checkbox">
|
||||
<label for="enableCloseToTray">
|
||||
<input
|
||||
id="enableCloseToTray"
|
||||
type="checkbox"
|
||||
aria-describedby="enableCloseToTrayHelp"
|
||||
formControlName="enableCloseToTray"
|
||||
(change)="saveCloseToTray()"
|
||||
/>
|
||||
{{ enableCloseToTrayText }}
|
||||
</label>
|
||||
</div>
|
||||
<small id="enableCloseToTrayHelp" class="help-block">{{
|
||||
enableCloseToTrayDescText
|
||||
<small id="runInBackgroundHelp" class="help-block">{{
|
||||
runInBackgroundDescText
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showOpenAtLoginOption">
|
||||
@ -247,23 +215,6 @@
|
||||
"openAtLoginDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showAlwaysShowDock">
|
||||
<div class="checkbox">
|
||||
<label for="alwaysShowDock">
|
||||
<input
|
||||
id="alwaysShowDock"
|
||||
type="checkbox"
|
||||
aria-describedby="alwaysShowDockHelp"
|
||||
formControlName="alwaysShowDock"
|
||||
(change)="saveAlwaysShowDock()"
|
||||
/>
|
||||
{{ "alwaysShowDock" | i18n }}
|
||||
</label>
|
||||
</div>
|
||||
<small id="alwaysShowDockHelp" class="help-block">{{
|
||||
"alwaysShowDockDesc" | i18n
|
||||
}}</small>
|
||||
</div>
|
||||
<div class="form-group" *ngIf="showEnableAutotype">
|
||||
<div class="checkbox">
|
||||
<label for="enableAutotype">
|
||||
|
||||
@ -171,11 +171,8 @@ describe("SettingsComponent", () => {
|
||||
biometricStateService.promptAutomatically$ = of(false);
|
||||
autofillSettingsServiceAbstraction.clearClipboardDelay$ = of(null);
|
||||
desktopSettingsService.minimizeOnCopy$ = of(false);
|
||||
desktopSettingsService.trayEnabled$ = of(false);
|
||||
desktopSettingsService.minimizeToTray$ = of(false);
|
||||
desktopSettingsService.closeToTray$ = of(false);
|
||||
desktopSettingsService.runInBackground$ = of(false);
|
||||
desktopSettingsService.openAtLogin$ = of(false);
|
||||
desktopSettingsService.alwaysShowDock$ = of(false);
|
||||
desktopSettingsService.browserIntegrationEnabled$ = of(false);
|
||||
desktopSettingsService.hardwareAcceleration$ = of(false);
|
||||
desktopSettingsService.sshAgentEnabled$ = of(false);
|
||||
|
||||
@ -99,15 +99,12 @@ import { NativeMessagingManifestService } from "../services/native-messaging-man
|
||||
],
|
||||
})
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
showMinToTray = false;
|
||||
localeOptions: any[];
|
||||
themeOptions: any[];
|
||||
clearClipboardOptions: any[];
|
||||
sshAgentPromptBehaviorOptions: any[];
|
||||
supportsBiometric: boolean;
|
||||
private timerId: any;
|
||||
showAlwaysShowDock = false;
|
||||
requireEnableTray = false;
|
||||
showDuckDuckGoIntegrationOption = false;
|
||||
showEnableAutotype = false;
|
||||
autotypeShortcut: string;
|
||||
@ -116,12 +113,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
isLinux: boolean;
|
||||
isMac: boolean;
|
||||
|
||||
enableTrayText: string;
|
||||
enableTrayDescText: string;
|
||||
enableMinToTrayText: string;
|
||||
enableMinToTrayDescText: string;
|
||||
enableCloseToTrayText: string;
|
||||
enableCloseToTrayDescText: string;
|
||||
runInBackgroundText: string;
|
||||
runInBackgroundDescText: string;
|
||||
|
||||
showSecurity = true;
|
||||
showAccountPreferences = true;
|
||||
@ -146,11 +139,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
minimizeOnCopyToClipboard: false,
|
||||
enableFavicons: false,
|
||||
// App Settings
|
||||
enableTray: false,
|
||||
enableMinToTray: false,
|
||||
enableCloseToTray: false,
|
||||
runInBackground: false,
|
||||
openAtLogin: false,
|
||||
alwaysShowDock: false,
|
||||
enableHardwareAcceleration: true,
|
||||
enableSshAgent: false,
|
||||
sshAgentPromptBehavior: SshAgentPromptType.Always,
|
||||
@ -199,20 +189,10 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.isLinux = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
|
||||
this.isWindows = this.platformUtilsService.getDevice() === DeviceType.WindowsDesktop;
|
||||
|
||||
// Workaround to avoid ghosting trays https://github.com/electron/electron/issues/17622
|
||||
this.requireEnableTray = this.platformUtilsService.getDevice() === DeviceType.LinuxDesktop;
|
||||
|
||||
const trayKey = this.isMac ? "enableMenuBar" : "enableTray";
|
||||
this.enableTrayText = this.i18nService.t(trayKey);
|
||||
this.enableTrayDescText = this.i18nService.t(trayKey + "Desc");
|
||||
|
||||
const minToTrayKey = this.isMac ? "enableMinToMenuBar" : "enableMinToTray";
|
||||
this.enableMinToTrayText = this.i18nService.t(minToTrayKey);
|
||||
this.enableMinToTrayDescText = this.i18nService.t(minToTrayKey + "Desc");
|
||||
|
||||
const closeToTrayKey = this.isMac ? "enableCloseToMenuBar" : "enableCloseToTray";
|
||||
this.enableCloseToTrayText = this.i18nService.t(closeToTrayKey);
|
||||
this.enableCloseToTrayDescText = this.i18nService.t(closeToTrayKey + "Desc");
|
||||
this.runInBackgroundText = this.i18nService.t("runInBackground");
|
||||
this.runInBackgroundDescText = this.i18nService.t(
|
||||
this.isMac ? "runInBackgroundDescMac" : "runInBackgroundDesc",
|
||||
);
|
||||
|
||||
this.showOpenAtLoginOption = this.showAutostartSetting();
|
||||
|
||||
@ -302,11 +282,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
clearClipboard: await firstValueFrom(this.autofillSettingsService.clearClipboardDelay$),
|
||||
minimizeOnCopyToClipboard: await firstValueFrom(this.desktopSettingsService.minimizeOnCopy$),
|
||||
enableFavicons: await firstValueFrom(this.domainSettingsService.showFavicons$),
|
||||
enableTray: await firstValueFrom(this.desktopSettingsService.trayEnabled$),
|
||||
enableMinToTray: await firstValueFrom(this.desktopSettingsService.minimizeToTray$),
|
||||
enableCloseToTray: await firstValueFrom(this.desktopSettingsService.closeToTray$),
|
||||
runInBackground: await firstValueFrom(this.desktopSettingsService.runInBackground$),
|
||||
openAtLogin: await firstValueFrom(this.desktopSettingsService.openAtLogin$),
|
||||
alwaysShowDock: await firstValueFrom(this.desktopSettingsService.alwaysShowDock$),
|
||||
enableDuckDuckGoBrowserIntegration: await firstValueFrom(
|
||||
this.desktopAutofillSettingsService.enableDuckDuckGoBrowserIntegration$,
|
||||
),
|
||||
@ -327,10 +304,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
this.form.setValue(initialValues, { emitEvent: false });
|
||||
|
||||
// Non-form values
|
||||
this.showMinToTray = this.platformUtilsService.getDevice() !== DeviceType.LinuxDesktop;
|
||||
this.showAlwaysShowDock = this.platformUtilsService.getDevice() === DeviceType.MacOsDesktop;
|
||||
|
||||
if (isWindows) {
|
||||
this.billingAccountProfileStateService
|
||||
.hasPremiumFromAnySource$(activeAccount.id)
|
||||
@ -535,44 +508,8 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
this.messagingService.send("refreshCiphers");
|
||||
}
|
||||
|
||||
async saveMinToTray() {
|
||||
await this.desktopSettingsService.setMinimizeToTray(this.form.value.enableMinToTray);
|
||||
}
|
||||
|
||||
async saveCloseToTray() {
|
||||
if (this.requireEnableTray) {
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
await this.desktopSettingsService.setTrayEnabled(this.form.value.enableTray);
|
||||
}
|
||||
|
||||
await this.desktopSettingsService.setCloseToTray(this.form.value.enableCloseToTray);
|
||||
}
|
||||
|
||||
async saveTray() {
|
||||
if (
|
||||
this.requireEnableTray &&
|
||||
!this.form.value.enableTray &&
|
||||
this.form.value.enableCloseToTray
|
||||
) {
|
||||
const confirm = await this.dialogService.openSimpleDialog({
|
||||
title: { key: "confirmTrayTitle" },
|
||||
content: { key: "confirmTrayDesc" },
|
||||
type: "warning",
|
||||
});
|
||||
|
||||
if (confirm) {
|
||||
this.form.controls.enableCloseToTray.setValue(false, { emitEvent: false });
|
||||
await this.desktopSettingsService.setCloseToTray(this.form.value.enableCloseToTray);
|
||||
} else {
|
||||
this.form.controls.enableTray.setValue(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.desktopSettingsService.setTrayEnabled(this.form.value.enableTray);
|
||||
// TODO: Ideally the DesktopSettingsService.trayEnabled$ could be subscribed to instead of using messaging.
|
||||
this.messagingService.send(this.form.value.enableTray ? "showTray" : "removeTray");
|
||||
async saveRunInBackground() {
|
||||
await this.desktopSettingsService.setRunInBackground(this.form.value.runInBackground);
|
||||
}
|
||||
|
||||
async saveLocale() {
|
||||
@ -594,10 +531,6 @@ export class SettingsComponent implements OnInit, OnDestroy {
|
||||
await this.autofillSettingsService.setClearClipboardDelay(this.form.value.clearClipboard);
|
||||
}
|
||||
|
||||
async saveAlwaysShowDock() {
|
||||
await this.desktopSettingsService.setAlwaysShowDock(this.form.value.alwaysShowDock);
|
||||
}
|
||||
|
||||
private showAutostartSetting(): boolean {
|
||||
// Windows store does not support autostart
|
||||
// Dev mode should not show auto-start, because it would result in an empty electron window starting on login
|
||||
|
||||
@ -1680,35 +1680,14 @@
|
||||
"showIconsChangePasswordUrls": {
|
||||
"message": "Show website icons and retrieve change password URLs"
|
||||
},
|
||||
"enableMinToTray": {
|
||||
"message": "Minimize to tray icon"
|
||||
"runInBackground": {
|
||||
"message": "Keep Bitwarden running in the background"
|
||||
},
|
||||
"enableMinToTrayDesc": {
|
||||
"message": "When minimizing the window, show an icon in the system tray instead."
|
||||
"runInBackgroundDesc": {
|
||||
"message": "When you close the window, Bitwarden is accessible from the tray. Recommended when using the SSH agent or browser integration."
|
||||
},
|
||||
"enableMinToMenuBar": {
|
||||
"message": "Minimize to menu bar"
|
||||
},
|
||||
"enableMinToMenuBarDesc": {
|
||||
"message": "When minimizing the window, show an icon in the menu bar instead."
|
||||
},
|
||||
"enableCloseToTray": {
|
||||
"message": "Close to tray icon"
|
||||
},
|
||||
"enableCloseToTrayDesc": {
|
||||
"message": "When closing the window, show an icon in the system tray instead."
|
||||
},
|
||||
"enableCloseToMenuBar": {
|
||||
"message": "Close to menu bar"
|
||||
},
|
||||
"enableCloseToMenuBarDesc": {
|
||||
"message": "When closing the window, show an icon in the menu bar instead."
|
||||
},
|
||||
"enableTray": {
|
||||
"message": "Show tray icon"
|
||||
},
|
||||
"enableTrayDesc": {
|
||||
"message": "Always show an icon in the system tray."
|
||||
"runInBackgroundDescMac": {
|
||||
"message": "When you close the window, Bitwarden is accessible from the menu bar. Recommended when using the SSH agent or browser integration."
|
||||
},
|
||||
"openAtLogin": {
|
||||
"message": "Start automatically on login"
|
||||
@ -1716,18 +1695,6 @@
|
||||
"openAtLoginDesc": {
|
||||
"message": "Start the Bitwarden desktop application automatically on login."
|
||||
},
|
||||
"alwaysShowDock": {
|
||||
"message": "Always show in the Dock"
|
||||
},
|
||||
"alwaysShowDockDesc": {
|
||||
"message": "Show the Bitwarden icon in the Dock even when minimized to the menu bar."
|
||||
},
|
||||
"confirmTrayTitle": {
|
||||
"message": "Confirm hiding tray"
|
||||
},
|
||||
"confirmTrayDesc": {
|
||||
"message": "Turning off this setting will also turn off all other tray related settings."
|
||||
},
|
||||
"language": {
|
||||
"message": "Language"
|
||||
},
|
||||
@ -2129,9 +2096,9 @@
|
||||
"exit": {
|
||||
"message": "Exit"
|
||||
},
|
||||
"showHide": {
|
||||
"message": "Show / Hide",
|
||||
"description": "Text for a button that toggles the visibility of the window. Shows the window when it is hidden or hides the window if it is currently open."
|
||||
"show": {
|
||||
"message": "Show",
|
||||
"description": "Tray menu item that brings the Bitwarden window to the foreground."
|
||||
},
|
||||
"hideToTray": {
|
||||
"message": "Hide to tray"
|
||||
@ -2335,12 +2302,6 @@
|
||||
"preferences": {
|
||||
"message": "Preferences"
|
||||
},
|
||||
"enableMenuBar": {
|
||||
"message": "Show menu bar icon"
|
||||
},
|
||||
"enableMenuBarDesc": {
|
||||
"message": "Always show an icon in the menu bar."
|
||||
},
|
||||
"hideToMenuBar": {
|
||||
"message": "Hide to menu bar"
|
||||
},
|
||||
|
||||
@ -238,6 +238,7 @@ export class Main {
|
||||
this.shell,
|
||||
(arg) => this.processDeepLink(arg),
|
||||
(win) => this.trayMain.setupWindowListeners(win),
|
||||
() => this.trayMain.restoreFromTray(),
|
||||
);
|
||||
|
||||
this.biometricsService = new MainBiometricsService(
|
||||
@ -388,9 +389,11 @@ export class Main {
|
||||
},
|
||||
]);
|
||||
|
||||
// Autostart should always start to tray. Any auto-start mechanism must provide this flag.
|
||||
if (isAutostart) {
|
||||
await this.trayMain.hideToTray();
|
||||
// Autostart starts to tray. Any auto-start mechanism must provide this flag.
|
||||
// Only hide to tray when running in the background is enabled; otherwise there
|
||||
// would be a hidden window with no tray icon to bring it back.
|
||||
if (isAutostart && (await firstValueFrom(this.desktopSettingsService.runInBackground$))) {
|
||||
this.trayMain.hideToTray();
|
||||
}
|
||||
|
||||
this.powerMonitorMain.init();
|
||||
|
||||
@ -68,15 +68,7 @@ export class MessagingMain {
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "showTray":
|
||||
this.main.trayMain.showTray();
|
||||
break;
|
||||
case "removeTray":
|
||||
this.main.trayMain.removeTray();
|
||||
break;
|
||||
case "hideToTray":
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.main.trayMain.hideToTray();
|
||||
break;
|
||||
case "addOpenAtLogin":
|
||||
@ -86,7 +78,7 @@ export class MessagingMain {
|
||||
this.removeOpenAtLogin();
|
||||
break;
|
||||
case "setFocus":
|
||||
this.setFocus();
|
||||
await this.setFocus();
|
||||
break;
|
||||
case "getWindowIsFocused":
|
||||
this.windowIsFocused();
|
||||
@ -218,8 +210,8 @@ export class MessagingMain {
|
||||
return path.join(app.getPath("home"), ".config", "autostart", "bitwarden.desktop");
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
this.main.trayMain.restoreFromTray();
|
||||
private async setFocus() {
|
||||
await this.main.trayMain.restoreFromTray();
|
||||
this.main.windowMain.win.focusOnWebView();
|
||||
}
|
||||
|
||||
|
||||
@ -48,8 +48,8 @@ export class TrayMain {
|
||||
|
||||
const menuItemOptions: MenuItemConstructorOptions[] = [
|
||||
{
|
||||
label: this.i18nService.t("showHide"),
|
||||
click: () => this.toggleWindow(),
|
||||
label: this.i18nService.t("show"),
|
||||
click: () => this.restoreFromTray(),
|
||||
},
|
||||
{
|
||||
visible: isDev(),
|
||||
@ -73,39 +73,36 @@ export class TrayMain {
|
||||
}
|
||||
|
||||
this.contextMenu = Menu.buildFromTemplate(menuItemOptions);
|
||||
if (await firstValueFrom(this.desktopSettingsService.trayEnabled$)) {
|
||||
this.showTray();
|
||||
}
|
||||
|
||||
// The tray icon is shown only while "keep running in the background" is enabled.
|
||||
// React to the setting so toggling it shows/removes the tray without a restart.
|
||||
this.desktopSettingsService.runInBackground$.subscribe((runInBackground) => {
|
||||
if (runInBackground) {
|
||||
this.showTray();
|
||||
} else {
|
||||
this.removeTray(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setupWindowListeners(win: BrowserWindow) {
|
||||
win.on("minimize", async () => {
|
||||
if (await firstValueFrom(this.desktopSettingsService.minimizeToTray$)) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.hideToTray();
|
||||
}
|
||||
});
|
||||
|
||||
win.on("restore", async () => {
|
||||
await this.biometricService.setShouldAutopromptNow(true);
|
||||
});
|
||||
|
||||
win.on("close", async (e: Event) => {
|
||||
if (await firstValueFrom(this.desktopSettingsService.closeToTray$)) {
|
||||
if (!this.windowMain.isQuitting) {
|
||||
e.preventDefault();
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.hideToTray();
|
||||
}
|
||||
if (this.windowMain.isQuitting) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
win.on("show", async () => {
|
||||
const enableTray = await firstValueFrom(this.desktopSettingsService.trayEnabled$);
|
||||
if (!enableTray) {
|
||||
setTimeout(() => this.removeTray(false), 100);
|
||||
if (await firstValueFrom(this.desktopSettingsService.runInBackground$)) {
|
||||
// Keep running in the background: closing the window hides it to the tray.
|
||||
e.preventDefault();
|
||||
this.hideToTray();
|
||||
} else {
|
||||
// Not running in the background: closing the window quits the application.
|
||||
// Setting isQuitting ensures macOS also quits via the window-all-closed handler.
|
||||
this.windowMain.isQuitting = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -123,21 +120,36 @@ export class TrayMain {
|
||||
}
|
||||
}
|
||||
|
||||
async hideToTray() {
|
||||
hideToTray() {
|
||||
this.showTray();
|
||||
if (this.windowMain.win != null) {
|
||||
this.windowMain.win.hide();
|
||||
}
|
||||
if (this.isDarwin() && !(await firstValueFrom(this.desktopSettingsService.alwaysShowDock$))) {
|
||||
if (this.isDarwin()) {
|
||||
this.hideDock();
|
||||
}
|
||||
}
|
||||
|
||||
restoreFromTray() {
|
||||
if (this.windowMain.win == null || !this.windowMain.win.isVisible()) {
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.toggleWindow();
|
||||
/**
|
||||
* Brings the application to the foreground. Recreates the window if it was destroyed
|
||||
* (macOS), unminimizes it if minimized, then shows and focuses it. This never hides
|
||||
* the window - foregrounding is not a toggle.
|
||||
*/
|
||||
async restoreFromTray() {
|
||||
if (this.windowMain.win == null) {
|
||||
// On macOS, closing the window via the red button destroys the BrowserWindow instance.
|
||||
await this.windowMain.createWindow();
|
||||
this.windowMain.win.show();
|
||||
} else {
|
||||
if (this.windowMain.win.isMinimized()) {
|
||||
this.windowMain.win.restore();
|
||||
}
|
||||
this.windowMain.show();
|
||||
this.windowMain.win.focus();
|
||||
}
|
||||
|
||||
if (this.isDarwin()) {
|
||||
this.showDock();
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +160,7 @@ export class TrayMain {
|
||||
|
||||
this.tray = new Tray(this.icon);
|
||||
this.tray.setToolTip(this.appName);
|
||||
this.tray.on("click", () => this.toggleWindow());
|
||||
this.tray.on("click", () => this.restoreFromTray());
|
||||
this.tray.on("right-click", () => this.tray.popUpContextMenu(this.contextMenu));
|
||||
|
||||
if (this.pressedIcon != null) {
|
||||
@ -183,32 +195,6 @@ export class TrayMain {
|
||||
return process.platform === "linux";
|
||||
}
|
||||
|
||||
private async toggleWindow() {
|
||||
if (this.windowMain.win == null) {
|
||||
if (this.isDarwin()) {
|
||||
// On MacOS, closing the window via the red button destroys the BrowserWindow instance.
|
||||
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.windowMain.createWindow().then(() => {
|
||||
this.windowMain.win.show();
|
||||
this.showDock();
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.windowMain.win.isVisible()) {
|
||||
this.windowMain.win.hide();
|
||||
if (this.isDarwin() && !(await firstValueFrom(this.desktopSettingsService.alwaysShowDock$))) {
|
||||
this.hideDock();
|
||||
}
|
||||
} else {
|
||||
this.windowMain.show();
|
||||
if (this.isDarwin()) {
|
||||
this.showDock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private closeWindow() {
|
||||
this.windowMain.isQuitting = true;
|
||||
if (this.windowMain.win != null) {
|
||||
|
||||
@ -52,6 +52,7 @@ export class WindowMain {
|
||||
private shell: SafeShell,
|
||||
private argvCallback: (argv: string[]) => void = null,
|
||||
private createWindowCallback: (win: BrowserWindow) => void,
|
||||
private focusWindowCallback: () => Promise<void> | void = null,
|
||||
) {}
|
||||
|
||||
init(show: boolean = true): Promise<any> {
|
||||
@ -134,8 +135,11 @@ export class WindowMain {
|
||||
return;
|
||||
} else {
|
||||
app.on("second-instance", (event, argv, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (this.win != null) {
|
||||
// Someone tried to run a second instance (e.g. launching from the desktop
|
||||
// icon or terminal while already running). Always bring us to the foreground.
|
||||
if (this.focusWindowCallback != null) {
|
||||
void this.focusWindowCallback();
|
||||
} else if (this.win != null) {
|
||||
if (this.win.isMinimized() || !this.win.isVisible()) {
|
||||
this.win.show();
|
||||
}
|
||||
@ -211,7 +215,9 @@ export class WindowMain {
|
||||
app.on("activate", async () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (this.win == null) {
|
||||
if (this.focusWindowCallback != null) {
|
||||
await this.focusWindowCallback();
|
||||
} else if (this.win == null) {
|
||||
await this.createWindow();
|
||||
} else {
|
||||
// Show the window when clicking on Dock icon
|
||||
|
||||
@ -34,15 +34,7 @@ const WINDOW_KEY = new KeyDefinition<WindowState | null>(DESKTOP_SETTINGS_DISK,
|
||||
deserializer: (s) => s,
|
||||
});
|
||||
|
||||
const CLOSE_TO_TRAY_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "closeToTray", {
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
|
||||
const MINIMIZE_TO_TRAY_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "minimizeToTray", {
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
|
||||
const TRAY_ENABLED_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "trayEnabled", {
|
||||
const RUN_IN_BACKGROUND_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "runInBackground", {
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
|
||||
@ -50,10 +42,6 @@ const OPEN_AT_LOGIN_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "ope
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
|
||||
const ALWAYS_SHOW_DOCK_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "alwaysShowDock", {
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
|
||||
const ALWAYS_ON_TOP_KEY = new KeyDefinition<boolean>(DESKTOP_SETTINGS_DISK, "alwaysOnTop", {
|
||||
deserializer: (b) => b,
|
||||
});
|
||||
@ -105,23 +93,16 @@ export class DesktopSettingsService {
|
||||
|
||||
private readonly windowState = this.stateProvider.getGlobal(WINDOW_KEY);
|
||||
|
||||
private readonly closeToTrayState = this.stateProvider.getGlobal(CLOSE_TO_TRAY_KEY);
|
||||
private readonly runInBackground = this.stateProvider.getGlobal(RUN_IN_BACKGROUND_KEY);
|
||||
/**
|
||||
* The applications setting for whether or not to close the application into the system tray.
|
||||
* The application setting for whether or not Bitwarden should keep running in the background.
|
||||
*
|
||||
* When enabled, the system tray icon is shown and closing the window hides the
|
||||
* application to the tray (keeping background features such as the SSH agent and
|
||||
* biometric unlock available) instead of quitting. When disabled, no tray is shown
|
||||
* and closing the window quits the application.
|
||||
*/
|
||||
closeToTray$ = this.closeToTrayState.state$.pipe(map((v) => v ?? !isDev()));
|
||||
|
||||
private readonly minimizeToTrayState = this.stateProvider.getGlobal(MINIMIZE_TO_TRAY_KEY);
|
||||
/**
|
||||
* The application setting for whether or not to minimize the applicaiton into the system tray.
|
||||
*/
|
||||
minimizeToTray$ = this.minimizeToTrayState.state$.pipe(map(Boolean));
|
||||
|
||||
private readonly trayEnabledState = this.stateProvider.getGlobal(TRAY_ENABLED_KEY);
|
||||
/**
|
||||
* Whether or not the system tray has been enabled.
|
||||
*/
|
||||
trayEnabled$ = this.trayEnabledState.state$.pipe(map((v) => v ?? !isDev()));
|
||||
runInBackground$ = this.runInBackground.state$.pipe(map((v) => v ?? !isDev()));
|
||||
|
||||
private readonly openAtLoginState = this.stateProvider.getGlobal(OPEN_AT_LOGIN_KEY);
|
||||
/**
|
||||
@ -129,12 +110,6 @@ export class DesktopSettingsService {
|
||||
*/
|
||||
openAtLogin$ = this.openAtLoginState.state$.pipe(map((v) => v ?? !isDev()));
|
||||
|
||||
private readonly alwaysShowDockState = this.stateProvider.getGlobal(ALWAYS_SHOW_DOCK_KEY);
|
||||
/**
|
||||
* The application setting for whether or not the application should show up in the dock.
|
||||
*/
|
||||
alwaysShowDock$ = this.alwaysShowDockState.state$.pipe(map(Boolean));
|
||||
|
||||
private readonly alwaysOnTopState = this.stateProvider.getGlobal(ALWAYS_ON_TOP_KEY);
|
||||
|
||||
alwaysOnTop$ = this.alwaysOnTopState.state$.pipe(map(Boolean));
|
||||
@ -210,27 +185,12 @@ export class DesktopSettingsService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the application should go into the system tray when closed.
|
||||
* @param value `true` if the application should go into the system tray when closed, `false` if it should not.
|
||||
* Sets the setting for whether or not Bitwarden should keep running in the background.
|
||||
* @param value `true` if the application should show the tray and hide to it when the window
|
||||
* is closed, `false` if closing the window should quit the application.
|
||||
*/
|
||||
async setCloseToTray(value: boolean) {
|
||||
await this.closeToTrayState.update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the application should go into the tray when minimized.
|
||||
* @param value `true` if the application should minimize into the system tray, `false` if it should not.
|
||||
*/
|
||||
async setMinimizeToTray(value: boolean) {
|
||||
await this.minimizeToTrayState.update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the application be shown in the system tray.
|
||||
* @param value `true` if the application should show in the tray, `false` if it should not.
|
||||
*/
|
||||
async setTrayEnabled(value: boolean) {
|
||||
await this.trayEnabledState.update(() => value);
|
||||
async setRunInBackground(value: boolean) {
|
||||
await this.runInBackground.update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,14 +201,6 @@ export class DesktopSettingsService {
|
||||
await this.openAtLoginState.update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the application should be shown in the dock.
|
||||
* @param value `true` if the application should show in the dock, `false` if it should not.
|
||||
*/
|
||||
async setAlwaysShowDock(value: boolean) {
|
||||
await this.alwaysShowDockState.update(() => value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the setting for whether or not the application should stay on top of all other windows.
|
||||
* @param value `true` if the application should stay on top, `false` if it should not.
|
||||
|
||||
@ -77,11 +77,12 @@ import { ClearClipboardDelayToStringMigrator } from "./migrations/77-clear-clipb
|
||||
import { MigrateSsoRequiredCache } from "./migrations/78-migrate-sso-required-cache";
|
||||
import { InitializeFeatureFlagOverridesMigrator } from "./migrations/79-initialize-feature-flag-overrides";
|
||||
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
|
||||
import { ConsolidateTrayToRunInBackground } from "./migrations/80-consolidate-tray-to-run-in-background";
|
||||
import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-settings-to-global";
|
||||
import { MinVersionMigrator } from "./migrations/min-version";
|
||||
|
||||
export const MIN_VERSION = 3;
|
||||
export const CURRENT_VERSION = 79;
|
||||
export const CURRENT_VERSION = 80;
|
||||
export type MinVersion = typeof MIN_VERSION;
|
||||
|
||||
export function createMigrationBuilder() {
|
||||
@ -162,7 +163,8 @@ export function createMigrationBuilder() {
|
||||
.with(MigratePopupWidthOptions, 75, 76)
|
||||
.with(ClearClipboardDelayToStringMigrator, 76, 77)
|
||||
.with(MigrateSsoRequiredCache, 77, 78)
|
||||
.with(InitializeFeatureFlagOverridesMigrator, 78, CURRENT_VERSION);
|
||||
.with(InitializeFeatureFlagOverridesMigrator, 78, 79)
|
||||
.with(ConsolidateTrayToRunInBackground, 79, CURRENT_VERSION);
|
||||
}
|
||||
|
||||
export async function currentVersion(
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
import { runMigrator } from "../migration-helper.spec";
|
||||
import { IRREVERSIBLE } from "../migrator";
|
||||
|
||||
import { ConsolidateTrayToRunInBackground } from "./80-consolidate-tray-to-run-in-background";
|
||||
|
||||
describe("ConsolidateTrayToRunInBackground", () => {
|
||||
const sut = new ConsolidateTrayToRunInBackground(79, 80);
|
||||
|
||||
describe("migrate", () => {
|
||||
it("keeps run in background enabled when closeToTray was enabled", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_desktopSettings_closeToTray: true,
|
||||
global_desktopSettings_trayEnabled: false,
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_desktopSettings_runInBackground: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("enables run in background when only trayEnabled was set", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_desktopSettings_closeToTray: false,
|
||||
global_desktopSettings_trayEnabled: true,
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_desktopSettings_runInBackground: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("disables run in background when both legacy settings were disabled", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_desktopSettings_closeToTray: false,
|
||||
global_desktopSettings_trayEnabled: false,
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_desktopSettings_runInBackground: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("removes the orphaned minimizeToTray and alwaysShowDock keys", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_desktopSettings_closeToTray: true,
|
||||
global_desktopSettings_trayEnabled: true,
|
||||
global_desktopSettings_minimizeToTray: true,
|
||||
global_desktopSettings_alwaysShowDock: true,
|
||||
});
|
||||
|
||||
expect(output).toEqual({
|
||||
global_desktopSettings_runInBackground: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not write runInBackground when neither legacy setting was present", async () => {
|
||||
const output = await runMigrator(sut, {
|
||||
global_desktopSettings_minimizeToTray: true,
|
||||
});
|
||||
|
||||
expect(output).toEqual({});
|
||||
});
|
||||
|
||||
it("does nothing when no desktop settings are present", async () => {
|
||||
const output = await runMigrator(sut, {});
|
||||
|
||||
expect(output).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("rollback", () => {
|
||||
it("is irreversible", async () => {
|
||||
await expect(runMigrator(sut, {}, "rollback")).rejects.toThrow(IRREVERSIBLE);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,62 @@
|
||||
import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper";
|
||||
import { IRREVERSIBLE, Migrator } from "../migrator";
|
||||
|
||||
const DESKTOP_SETTINGS_STATE: StateDefinitionLike = { name: "desktopSettings" };
|
||||
|
||||
const CLOSE_TO_TRAY_KEY: KeyDefinitionLike = {
|
||||
key: "closeToTray",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const RUN_IN_BACKGROUND_KEY: KeyDefinitionLike = {
|
||||
key: "runInBackground",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const TRAY_ENABLED_KEY: KeyDefinitionLike = {
|
||||
key: "trayEnabled",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const MINIMIZE_TO_TRAY_KEY: KeyDefinitionLike = {
|
||||
key: "minimizeToTray",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
const ALWAYS_SHOW_DOCK_KEY: KeyDefinitionLike = {
|
||||
key: "alwaysShowDock",
|
||||
stateDefinition: DESKTOP_SETTINGS_STATE,
|
||||
};
|
||||
|
||||
/**
|
||||
* Consolidates the legacy `trayEnabled`, `minimizeToTray`, and `closeToTray` desktop settings
|
||||
* into a single "run in the background" setting, stored under the new `runInBackground` key.
|
||||
* `runInBackground = closeToTray || trayEnabled`. The now-unused `closeToTray`, `trayEnabled`,
|
||||
* `minimizeToTray`, and `alwaysShowDock` keys are removed.
|
||||
*/
|
||||
export class ConsolidateTrayToRunInBackground extends Migrator<79, 80> {
|
||||
async migrate(helper: MigrationHelper): Promise<void> {
|
||||
const closeToTray = await helper.getFromGlobal<boolean>(CLOSE_TO_TRAY_KEY);
|
||||
const trayEnabled = await helper.getFromGlobal<boolean>(TRAY_ENABLED_KEY);
|
||||
|
||||
// Only write a consolidated value when at least one legacy value existed, so users who
|
||||
// never customized these keep falling through to the `!isDev()` default in the service.
|
||||
if (closeToTray != null || trayEnabled != null) {
|
||||
await helper.setToGlobal(RUN_IN_BACKGROUND_KEY, Boolean(closeToTray) || Boolean(trayEnabled));
|
||||
}
|
||||
|
||||
// Remove orphaned keys (guarded so absent keys aren't touched).
|
||||
if (closeToTray != null) {
|
||||
await helper.removeFromGlobal(CLOSE_TO_TRAY_KEY);
|
||||
}
|
||||
if (trayEnabled != null) {
|
||||
await helper.removeFromGlobal(TRAY_ENABLED_KEY);
|
||||
}
|
||||
if ((await helper.getFromGlobal<boolean>(MINIMIZE_TO_TRAY_KEY)) != null) {
|
||||
await helper.removeFromGlobal(MINIMIZE_TO_TRAY_KEY);
|
||||
}
|
||||
if ((await helper.getFromGlobal<boolean>(ALWAYS_SHOW_DOCK_KEY)) != null) {
|
||||
await helper.removeFromGlobal(ALWAYS_SHOW_DOCK_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
rollback(helper: MigrationHelper): Promise<void> {
|
||||
throw IRREVERSIBLE;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user