From 0a2c9304a878a4965eaac4014455156972c5080f Mon Sep 17 00:00:00 2001 From: automated-signal <37887102+automated-signal@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:51:27 -0600 Subject: [PATCH] Change ephemeral settings to only persist in ephemeralConfig Co-authored-by: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com> --- app/SystemTraySettingCache.ts | 14 +-- app/main.ts | 118 ++++++++---------- ts/background.ts | 52 ++------ ts/main/settingsChannel.ts | 114 +++++++++++++---- ts/shims/themeChanged.ts | 4 +- ts/state/getInitialState.ts | 6 +- ts/state/initializeRedux.ts | 4 + .../app/SystemTraySettingCache_test.ts | 53 +------- ts/types/Storage.d.ts | 6 - ts/types/StorageUIKeys.ts | 4 - ts/util/createIPCEvents.ts | 92 +++++++++----- ts/util/getThemeType.ts | 4 +- ts/util/preload.ts | 60 +++++++-- ts/window.d.ts | 2 - ts/windows/context.ts | 5 +- ts/windows/main/phase1-ipc.ts | 5 - ts/windows/main/preload_test.ts | 2 + ts/windows/preload.ts | 15 ++- 18 files changed, 295 insertions(+), 265 deletions(-) diff --git a/app/SystemTraySettingCache.ts b/app/SystemTraySettingCache.ts index a6a8ec6f8f..4bd60a79b3 100644 --- a/app/SystemTraySettingCache.ts +++ b/app/SystemTraySettingCache.ts @@ -8,7 +8,6 @@ import { SystemTraySetting, } from '../ts/types/SystemTraySetting'; import { isSystemTraySupported } from '../ts/types/Settings'; -import type { MainSQL } from '../ts/sql/main'; import type { ConfigType } from './base_config'; /** @@ -21,7 +20,6 @@ export class SystemTraySettingCache { private getPromise: undefined | Promise; constructor( - private readonly sql: Pick, private readonly ephemeralConfig: Pick, private readonly argv: Array, private readonly appVersion: string @@ -56,15 +54,11 @@ export class SystemTraySettingCache { `getSystemTraySetting saw --use-tray-icon flag. Returning ${result}` ); } else if (isSystemTraySupported(OS, this.appVersion)) { - const fastValue = this.ephemeralConfig.get('system-tray-setting'); - if (fastValue !== undefined) { - log.info('getSystemTraySetting got fast value', fastValue); + const value = this.ephemeralConfig.get('system-tray-setting'); + if (value !== undefined) { + log.info('getSystemTraySetting got value', value); } - const value = - fastValue ?? - (await this.sql.sqlCall('getItemById', 'system-tray-setting'))?.value; - if (value !== undefined) { result = parseSystemTraySetting(value); log.info(`getSystemTraySetting returning ${result}`); @@ -73,7 +67,7 @@ export class SystemTraySettingCache { log.info(`getSystemTraySetting got no value, returning ${result}`); } - if (result !== fastValue) { + if (result !== value) { this.ephemeralConfig.set('system-tray-setting', result); } } else { diff --git a/app/main.ts b/app/main.ts index f87d8de70c..76049a5022 100644 --- a/app/main.ts +++ b/app/main.ts @@ -292,22 +292,18 @@ const sql = new MainSQL(); const heicConverter = getHeicConverter(); async function getSpellCheckSetting(): Promise { - const fastValue = ephemeralConfig.get('spell-check'); - if (typeof fastValue === 'boolean') { - getLogger().info('got fast spellcheck setting', fastValue); - return fastValue; + const value = ephemeralConfig.get('spell-check'); + if (typeof value === 'boolean') { + getLogger().info('got fast spellcheck setting', value); + return value; } - const json = await sql.sqlCall('getItemById', 'spell-check'); - // Default to `true` if setting doesn't exist yet - const slowValue = typeof json?.value === 'boolean' ? json.value : true; + ephemeralConfig.set('spell-check', true); - ephemeralConfig.set('spell-check', slowValue); + getLogger().info('initializing spellcheck setting', true); - getLogger().info('got slow spellcheck setting', slowValue); - - return slowValue; + return true; } type GetThemeSettingOptionsType = Readonly<{ @@ -317,29 +313,22 @@ type GetThemeSettingOptionsType = Readonly<{ async function getThemeSetting({ ephemeralOnly = false, }: GetThemeSettingOptionsType = {}): Promise { - let result: unknown; - - const fastValue = ephemeralConfig.get('theme-setting'); - if (fastValue !== undefined) { - getLogger().info('got fast theme-setting value', fastValue); - result = fastValue; + const value = ephemeralConfig.get('theme-setting'); + if (value !== undefined) { + getLogger().info('got fast theme-setting value', value); } else if (ephemeralOnly) { return 'system'; - } else { - const json = await sql.sqlCall('getItemById', 'theme-setting'); - - result = json?.value; } // Default to `system` if setting doesn't exist or is invalid const validatedResult = - result === 'light' || result === 'dark' || result === 'system' - ? result + value === 'light' || value === 'dark' || value === 'system' + ? value : 'system'; - if (fastValue !== validatedResult) { + if (value !== validatedResult) { ephemeralConfig.set('theme-setting', validatedResult); - getLogger().info('got slow theme-setting value', result); + getLogger().info('saving theme-setting value', validatedResult); } return validatedResult; @@ -372,23 +361,19 @@ async function getBackgroundColor( } async function getLocaleOverrideSetting(): Promise { - const fastValue = ephemeralConfig.get('localeOverride'); + const value = ephemeralConfig.get('localeOverride'); // eslint-disable-next-line eqeqeq -- Checking for null explicitly - if (typeof fastValue === 'string' || fastValue === null) { - getLogger().info('got fast localeOverride setting', fastValue); - return fastValue; + if (typeof value === 'string' || value === null) { + getLogger().info('got fast localeOverride setting', value); + return value; } - const json = await sql.sqlCall('getItemById', 'localeOverride'); - // Default to `null` if setting doesn't exist yet - const slowValue = typeof json?.value === 'string' ? json.value : null; + ephemeralConfig.set('localeOverride', null); - ephemeralConfig.set('localeOverride', slowValue); + getLogger().info('initializing localeOverride setting', null); - getLogger().info('got slow localeOverride setting', slowValue); - - return slowValue; + return null; } const zoomFactorService = new ZoomFactorService({ @@ -409,7 +394,6 @@ const zoomFactorService = new ZoomFactorService({ let systemTrayService: SystemTrayService | undefined; const systemTraySettingCache = new SystemTraySettingCache( - sql, ephemeralConfig, process.argv, app.getVersion() @@ -1811,12 +1795,7 @@ app.on('ready', async () => { getLogger().info(`app.ready: setting system-tray-setting to ${newValue}`); systemTraySettingCache.set(newValue); - // Update both stores ephemeralConfig.set('system-tray-setting', newValue); - await sql.sqlCall('createOrUpdateItem', { - id: 'system-tray-setting', - value: newValue, - }); if (OS.isWindows()) { getLogger().info('app.ready: enabling open at login'); @@ -1832,6 +1811,32 @@ app.on('ready', async () => { settingsChannel = new SettingsChannel(); settingsChannel.install(); + settingsChannel.on('change:systemTraySetting', async rawSystemTraySetting => { + const { openAtLogin } = app.getLoginItemSettings( + await getDefaultLoginItemSettings() + ); + + const systemTraySetting = parseSystemTraySetting(rawSystemTraySetting); + systemTraySettingCache.set(systemTraySetting); + + if (systemTrayService) { + const isEnabled = shouldMinimizeToSystemTray(systemTraySetting); + systemTrayService.setEnabled(isEnabled); + } + + // Default login item settings might have changed, so update the object. + getLogger().info('refresh-auto-launch: new value', openAtLogin); + app.setLoginItemSettings({ + ...(await getDefaultLoginItemSettings()), + openAtLogin, + }); + }); + + settingsChannel.on( + 'ephemeral-setting-changed', + sendPreferencesChangedEventToWindows + ); + // We use this event only a single time to log the startup time of the app // from when it's first ready until the loading screen disappears. ipc.once('signal-app-loaded', (event, info) => { @@ -2318,30 +2323,6 @@ ipc.on( } ); -ipc.handle( - 'update-system-tray-setting', - async (_event, rawSystemTraySetting /* : Readonly */) => { - const { openAtLogin } = app.getLoginItemSettings( - await getDefaultLoginItemSettings() - ); - - const systemTraySetting = parseSystemTraySetting(rawSystemTraySetting); - systemTraySettingCache.set(systemTraySetting); - - if (systemTrayService) { - const isEnabled = shouldMinimizeToSystemTray(systemTraySetting); - systemTrayService.setEnabled(isEnabled); - } - - // Default login item settings might have changed, so update the object. - getLogger().info('refresh-auto-launch: new value', openAtLogin); - app.setLoginItemSettings({ - ...(await getDefaultLoginItemSettings()), - openAtLogin, - }); - } -); - ipc.on('close-screen-share-controller', () => { if (screenShareWindow) { screenShareWindow.close(); @@ -2564,13 +2545,14 @@ ipc.on('get-user-data-path', event => { }); // Refresh the settings window whenever preferences change -ipc.on('preferences-changed', () => { +const sendPreferencesChangedEventToWindows = () => { for (const window of activeWindows) { if (window.webContents) { window.webContents.send('preferences-changed'); } } -}); +}; +ipc.on('preferences-changed', sendPreferencesChangedEventToWindows); function maybeGetIncomingSignalRoute(argv: Array) { for (const arg of argv) { diff --git a/ts/background.ts b/ts/background.ts index 7f4a562736..fc62f734a5 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -190,6 +190,7 @@ import { updateLocalGroupCallHistoryTimestamp, } from './util/callDisposition'; import { deriveStorageServiceKey } from './Crypto'; +import { getThemeType } from './util/getThemeType'; export function isOverHourIntoPast(timestamp: number): boolean { return isNumber(timestamp) && isOlderThan(timestamp, HOUR); @@ -697,25 +698,6 @@ export async function startApp(): Promise { log.info('Storage fetch'); drop(window.storage.fetch()); - function mapOldThemeToNew( - theme: Readonly< - 'system' | 'light' | 'dark' | 'android' | 'ios' | 'android-dark' - > - ): 'system' | 'light' | 'dark' { - switch (theme) { - case 'dark': - case 'light': - case 'system': - return theme; - case 'android-dark': - return 'dark'; - case 'android': - case 'ios': - default: - return 'light'; - } - } - // We need this 'first' check because we don't want to start the app up any other time // than the first time. And window.storage.fetch() will cause onready() to fire. let first = true; @@ -925,16 +907,6 @@ export async function startApp(): Promise { ); } - const themeSetting = window.Events.getThemeSetting(); - const newThemeSetting = mapOldThemeToNew(themeSetting); - if (window.isBeforeVersion(lastVersion, 'v1.25.0')) { - if (newThemeSetting === window.systemTheme) { - void window.Events.setThemeSetting('system'); - } else { - void window.Events.setThemeSetting(newThemeSetting); - } - } - if ( window.isBeforeVersion(lastVersion, 'v1.36.0-beta.1') && window.isAfterVersion(lastVersion, 'v1.35.0-beta.1') @@ -1133,6 +1105,8 @@ export async function startApp(): Promise { platform: 'unknown', }; + let theme: ThemeType = window.systemTheme; + try { // This needs to load before we prime the data because we expect // ConversationController to be loaded and ready to use by then. @@ -1153,6 +1127,9 @@ export async function startApp(): Promise { (async () => { menuOptions = await window.SignalContext.getMenuOptions(); })(), + (async () => { + theme = await getThemeType(); + })(), ]); await window.ConversationController.checkForConflicts(); } catch (error) { @@ -1161,7 +1138,7 @@ export async function startApp(): Promise { Errors.toLogFormat(error) ); } finally { - setupAppState({ mainWindowStats, menuOptions }); + setupAppState({ mainWindowStats, menuOptions, theme }); drop(start()); window.Signal.Services.initializeNetworkObserver( window.reduxActions.network @@ -1186,9 +1163,11 @@ export async function startApp(): Promise { function setupAppState({ mainWindowStats, menuOptions, + theme, }: { mainWindowStats: MainWindowStatsType; menuOptions: MenuOptionsType; + theme: ThemeType; }) { initializeRedux({ callsHistory: getCallsHistoryForRedux(), @@ -1198,6 +1177,7 @@ export async function startApp(): Promise { menuOptions, stories: getStoriesForRedux(), storyDistributionLists: getDistributionListsForRedux(), + theme, }); // Here we set up a full redux store with initial state for our LeftPane Root @@ -1847,18 +1827,6 @@ export async function startApp(): Promise { } } - const hasThemeSetting = Boolean(window.storage.get('theme-setting')); - if ( - !hasThemeSetting && - window.textsecure.storage.get('userAgent') === 'OWI' - ) { - await window.storage.put( - 'theme-setting', - await window.Events.getThemeSetting() - ); - themeChanged(); - } - const waitForEvent = createTaskWithTimeout( (event: string): Promise => { const { promise, resolve } = explodePromise(); diff --git a/ts/main/settingsChannel.ts b/ts/main/settingsChannel.ts index c81b3d710c..3abd702dfc 100644 --- a/ts/main/settingsChannel.ts +++ b/ts/main/settingsChannel.ts @@ -14,6 +14,7 @@ import type { IPCEventsValuesType, IPCEventsCallbacksType, } from '../util/createIPCEvents'; +import type { EphemeralSettings, SettingsValuesType } from '../util/preload'; const EPHEMERAL_NAME_MAP = new Map([ ['spellCheck', 'spell-check'], @@ -27,6 +28,9 @@ type ResponseQueueEntry = Readonly<{ reject(error: Error): void; }>; +type SettingChangeEventType = + `change:${Key}`; + export class SettingsChannel extends EventEmitter { private mainWindow?: BrowserWindow; @@ -70,17 +74,7 @@ export class SettingsChannel extends EventEmitter { this.installSetting('readReceiptSetting', { setter: false }); this.installSetting('typingIndicatorSetting', { setter: false }); - this.installSetting('themeSetting', { - isEphemeral: true, - }); this.installSetting('hideMenuBar'); - this.installSetting('systemTraySetting', { - isEphemeral: true, - }); - - this.installSetting('localeOverride', { - isEphemeral: true, - }); this.installSetting('notificationSetting'); this.installSetting('notificationDrawAttention'); this.installSetting('audioMessage'); @@ -88,9 +82,6 @@ export class SettingsChannel extends EventEmitter { this.installSetting('countMutedConversations'); this.installSetting('sentMediaQualitySetting'); - this.installSetting('spellCheck', { - isEphemeral: true, - }); this.installSetting('textFormatting'); this.installSetting('autoConvertEmoji'); @@ -116,6 +107,11 @@ export class SettingsChannel extends EventEmitter { this.installSetting('phoneNumberDiscoverabilitySetting'); this.installSetting('phoneNumberSharingSetting'); + this.installEphemeralSetting('themeSetting'); + this.installEphemeralSetting('systemTraySetting'); + this.installEphemeralSetting('localeOverride'); + this.installEphemeralSetting('spellCheck'); + installPermissionsHandler({ session: session.defaultSession, userConfig }); // These ones are different because its single source of truth is userConfig, @@ -234,8 +230,7 @@ export class SettingsChannel extends EventEmitter { { getter = true, setter = true, - isEphemeral = false, - }: { getter?: boolean; setter?: boolean; isEphemeral?: boolean } = {} + }: { getter?: boolean; setter?: boolean } = {} ): void { if (getter) { ipc.handle(`settings:get:${name}`, async () => { @@ -248,18 +243,89 @@ export class SettingsChannel extends EventEmitter { } ipc.handle(`settings:set:${name}`, async (_event, value) => { - if (isEphemeral) { - const ephemeralName = EPHEMERAL_NAME_MAP.get(name); - strictAssert( - ephemeralName !== undefined, - `${name} is not an ephemeral setting` - ); - ephemeralConfig.set(ephemeralName, value); - } - await this.setSettingInMainWindow(name, value); this.emit(`change:${name}`, value); }); } + + private installEphemeralSetting( + name: Name + ): void { + ipc.handle(`settings:get:${name}`, async () => { + const ephemeralName = EPHEMERAL_NAME_MAP.get(name); + strictAssert( + ephemeralName !== undefined, + `${name} is not an ephemeral setting` + ); + return ephemeralConfig.get(ephemeralName); + }); + + ipc.handle(`settings:set:${name}`, async (_event, value) => { + const ephemeralName = EPHEMERAL_NAME_MAP.get(name); + strictAssert( + ephemeralName !== undefined, + `${name} is not an ephemeral setting` + ); + ephemeralConfig.set(ephemeralName, value); + + this.emit(`change:${name}`, value); + + // Notify main to notify windows of preferences change. As for DB-backed + // settings, those are set by the renderer, and afterwards the renderer IPC sends + // to main the event 'preferences-changed'. + this.emit('ephemeral-setting-changed'); + + const { mainWindow } = this; + if (!mainWindow || !mainWindow.webContents) { + return; + } + + mainWindow.webContents.send(`settings:update:${name}`, value); + }); + } + + // EventEmitter types + + public override on( + type: 'change:systemTraySetting', + callback: (value: string) => void + ): this; + + public override on( + type: 'ephemeral-setting-changed', + callback: () => void + ): this; + + public override on( + type: SettingChangeEventType, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + callback: (...args: Array) => void + ): this; + + public override on( + type: string | symbol, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + listener: (...args: Array) => void + ): this { + return super.on(type, listener); + } + + public override emit( + type: 'change:systemTraySetting', + value: string + ): boolean; + + public override emit(type: 'ephemeral-setting-changed'): boolean; + + public override emit( + type: SettingChangeEventType, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...args: Array + ): boolean; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public override emit(type: string | symbol, ...args: Array): boolean { + return super.emit(type, ...args); + } } diff --git a/ts/shims/themeChanged.ts b/ts/shims/themeChanged.ts index 8b79e5706f..e996ce0961 100644 --- a/ts/shims/themeChanged.ts +++ b/ts/shims/themeChanged.ts @@ -3,9 +3,9 @@ import { getThemeType } from '../util/getThemeType'; -export function themeChanged(): void { +export async function themeChanged(): Promise { if (window.reduxActions && window.reduxActions.user) { - const theme = getThemeType(); + const theme = await getThemeType(); window.reduxActions.user.userChanged({ theme }); } } diff --git a/ts/state/getInitialState.ts b/ts/state/getInitialState.ts index f91b70a6f1..6eb4b567f4 100644 --- a/ts/state/getInitialState.ts +++ b/ts/state/getInitialState.ts @@ -37,10 +37,10 @@ import type { StoryDistributionListDataType } from './ducks/storyDistributionLis import OS from '../util/os/osMain'; import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis'; import { getInitialState as stickers } from '../types/Stickers'; -import { getThemeType } from '../util/getThemeType'; import { getInteractionMode } from '../services/InteractionMode'; import { makeLookup } from '../util/makeLookup'; import type { CallHistoryDetails } from '../types/CallDisposition'; +import type { ThemeType } from '../types/Util'; export function getInitialState({ badges, @@ -50,6 +50,7 @@ export function getInitialState({ storyDistributionLists, mainWindowStats, menuOptions, + theme, }: { badges: BadgesStateType; callsHistory: ReadonlyArray; @@ -58,6 +59,7 @@ export function getInitialState({ storyDistributionLists: Array; mainWindowStats: MainWindowStatsType; menuOptions: MenuOptionsType; + theme: ThemeType; }): StateType { const items = window.storage.getItemsState(); @@ -72,8 +74,6 @@ export function getInitialState({ window.ConversationController.getOurConversationId(); const ourDeviceId = window.textsecure.storage.user.getDeviceId(); - const theme = getThemeType(); - let osName: 'windows' | 'macos' | 'linux' | undefined; if (OS.isWindows()) { diff --git a/ts/state/initializeRedux.ts b/ts/state/initializeRedux.ts index 27ddab69a1..c0608e0381 100644 --- a/ts/state/initializeRedux.ts +++ b/ts/state/initializeRedux.ts @@ -11,6 +11,7 @@ import type { StoryDistributionListDataType } from './ducks/storyDistributionLis import { actionCreators } from './actions'; import { createStore } from './createStore'; import { getInitialState } from './getInitialState'; +import type { ThemeType } from '../types/Util'; export function initializeRedux({ callsHistory, @@ -20,6 +21,7 @@ export function initializeRedux({ menuOptions, stories, storyDistributionLists, + theme, }: { callsHistory: ReadonlyArray; callsHistoryUnreadCount: number; @@ -28,6 +30,7 @@ export function initializeRedux({ menuOptions: MenuOptionsType; stories: Array; storyDistributionLists: Array; + theme: ThemeType; }): void { const initialState = getInitialState({ badges: initialBadgesState, @@ -37,6 +40,7 @@ export function initializeRedux({ menuOptions, stories, storyDistributionLists, + theme, }); const store = createStore(initialState); diff --git a/ts/test-node/app/SystemTraySettingCache_test.ts b/ts/test-node/app/SystemTraySettingCache_test.ts index 60e58f9411..09f719bed0 100644 --- a/ts/test-node/app/SystemTraySettingCache_test.ts +++ b/ts/test-node/app/SystemTraySettingCache_test.ts @@ -3,7 +3,6 @@ import { assert } from 'chai'; import * as sinon from 'sinon'; -import type { MainSQL } from '../../sql/main'; import { SystemTraySetting } from '../../types/SystemTraySetting'; import type { ConfigType } from '../../../app/base_config'; @@ -12,18 +11,13 @@ import { SystemTraySettingCache } from '../../../app/SystemTraySettingCache'; describe('SystemTraySettingCache', () => { let sandbox: sinon.SinonSandbox; - let sqlCallStub: sinon.SinonStub; let configGetStub: sinon.SinonStub; let configSetStub: sinon.SinonStub; - let sql: Pick; let config: Pick; beforeEach(() => { sandbox = sinon.createSandbox(); - sqlCallStub = sandbox.stub().resolves(); - sql = { sqlCall: sqlCallStub }; - configGetStub = sandbox.stub().returns(undefined); configSetStub = sandbox.stub().returns(undefined); config = { get: configGetStub, set: configSetStub }; @@ -35,7 +29,6 @@ describe('SystemTraySettingCache', () => { it('returns MinimizeToAndStartInSystemTray if passed the --start-in-tray argument', async () => { const justOneArg = new SystemTraySettingCache( - sql, config, ['--start-in-tray'], '1.2.3' @@ -46,7 +39,6 @@ describe('SystemTraySettingCache', () => { ); const bothArgs = new SystemTraySettingCache( - sql, config, ['--start-in-tray', '--use-tray-icon'], '1.2.3' @@ -56,14 +48,12 @@ describe('SystemTraySettingCache', () => { SystemTraySetting.MinimizeToAndStartInSystemTray ); - sinon.assert.notCalled(sqlCallStub); sinon.assert.notCalled(configGetStub); sinon.assert.notCalled(configSetStub); }); it('returns MinimizeToSystemTray if passed the --use-tray-icon argument', async () => { const cache = new SystemTraySettingCache( - sql, config, ['--use-tray-icon'], '1.2.3' @@ -73,7 +63,6 @@ describe('SystemTraySettingCache', () => { SystemTraySetting.MinimizeToSystemTray ); - sinon.assert.notCalled(sqlCallStub); sinon.assert.notCalled(configGetStub); sinon.assert.notCalled(configSetStub); }); @@ -81,7 +70,7 @@ describe('SystemTraySettingCache', () => { it('returns Uninitialized if system tray is supported but no preference is stored', async () => { sandbox.stub(process, 'platform').value('win32'); - const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); + const cache = new SystemTraySettingCache(config, [], '1.2.3'); assert.strictEqual(await cache.get(), SystemTraySetting.Uninitialized); assert(configGetStub.calledOnceWith('system-tray-setting')); assert( @@ -95,9 +84,9 @@ describe('SystemTraySettingCache', () => { it('returns Uninitialized if system tray is supported but the stored preference is invalid', async () => { sandbox.stub(process, 'platform').value('win32'); - sqlCallStub.resolves({ value: 'garbage' }); + configGetStub.returns('garbage'); - const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); + const cache = new SystemTraySettingCache(config, [], '1.2.3'); assert.strictEqual(await cache.get(), SystemTraySetting.Uninitialized); assert(configGetStub.calledOnceWith('system-tray-setting')); assert( @@ -108,58 +97,26 @@ describe('SystemTraySettingCache', () => { ); }); - it('returns the stored preference if system tray is supported and something is stored', async () => { - sandbox.stub(process, 'platform').value('win32'); - - sqlCallStub.resolves({ value: 'MinimizeToSystemTray' }); - - const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); - assert.strictEqual( - await cache.get(), - SystemTraySetting.MinimizeToSystemTray - ); - assert(configGetStub.calledOnceWith('system-tray-setting')); - assert( - configSetStub.calledOnceWith( - 'system-tray-setting', - SystemTraySetting.MinimizeToSystemTray - ) - ); - }); - it('returns the cached preference if system tray is supported and something is stored', async () => { sandbox.stub(process, 'platform').value('win32'); configGetStub.returns('MinimizeToSystemTray'); - const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); + const cache = new SystemTraySettingCache(config, [], '1.2.3'); assert.strictEqual( await cache.get(), SystemTraySetting.MinimizeToSystemTray ); assert(configGetStub.calledOnceWith('system-tray-setting')); - sinon.assert.notCalled(sqlCallStub); - }); - - it('only kicks off one request to the database if multiple sources ask at once', async () => { - sandbox.stub(process, 'platform').value('win32'); - - const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); - - await Promise.all([cache.get(), cache.get(), cache.get()]); - - assert(configGetStub.calledOnceWith('system-tray-setting')); - sinon.assert.calledOnce(sqlCallStub); }); it('returns DoNotUseSystemTray if system tray is unsupported and there are no CLI flags', async () => { sandbox.stub(process, 'platform').value('darwin'); - const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); + const cache = new SystemTraySettingCache(config, [], '1.2.3'); assert.strictEqual(await cache.get(), SystemTraySetting.DoNotUseSystemTray); sinon.assert.notCalled(configGetStub); sinon.assert.notCalled(configSetStub); - sinon.assert.notCalled(sqlCallStub); }); }); diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index 56fbfb1ea9..5ffd070e67 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -11,7 +11,6 @@ import type { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverabil import type { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode'; import type { RetryItemType } from '../util/retryPlaceholders'; import type { ConfigMapType as RemoteConfigType } from '../RemoteConfig'; -import type { SystemTraySetting } from './SystemTraySetting'; import type { ExtendedStorageID, UnknownRecord } from './StorageService.d'; import type { GroupCredentialType } from '../textsecure/WebAPI'; @@ -19,7 +18,6 @@ import type { SessionResetsType, StorageServiceCredentials, } from '../textsecure/Types.d'; -import type { ThemeSettingType } from './StorageUIKeys'; import type { ServiceIdString } from './ServiceId'; import type { RegisteredChallengeType } from '../challenge'; @@ -57,14 +55,10 @@ export type StorageAccessType = { 'call-system-notification': boolean; 'hide-menu-bar': boolean; 'incoming-call-notification': boolean; - localeOverride: string | null; 'notification-draw-attention': boolean; 'notification-setting': NotificationSettingType; 'read-receipt-setting': boolean; 'sent-media-quality': SentMediaQualitySettingType; - 'spell-check': boolean; - 'system-tray-setting': SystemTraySetting; - 'theme-setting': ThemeSettingType; audioMessage: boolean; attachmentMigration_isComplete: boolean; attachmentMigration_lastProcessedIndex: number; diff --git a/ts/types/StorageUIKeys.ts b/ts/types/StorageUIKeys.ts index 5ddc6863a9..8312022805 100644 --- a/ts/types/StorageUIKeys.ts +++ b/ts/types/StorageUIKeys.ts @@ -25,7 +25,6 @@ export const STORAGE_UI_KEYS: ReadonlyArray = [ 'hasCompletedUsernameLinkOnboarding', 'hide-menu-bar', 'incoming-call-notification', - 'localeOverride', 'navTabsCollapsed', 'notification-draw-attention', 'notification-setting', @@ -40,10 +39,7 @@ export const STORAGE_UI_KEYS: ReadonlyArray = [ 'showStickerPickerHint', 'showStickersIntroduction', 'skinTone', - 'spell-check', - 'system-tray-setting', 'textFormatting', - 'theme-setting', 'version', 'zoomFactor', ]; diff --git a/ts/util/createIPCEvents.ts b/ts/util/createIPCEvents.ts index 9006dd8cda..7827479e62 100644 --- a/ts/util/createIPCEvents.ts +++ b/ts/util/createIPCEvents.ts @@ -14,8 +14,6 @@ import type { import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors'; import * as Errors from '../types/errors'; import * as Stickers from '../types/Stickers'; -import type { SystemTraySetting } from '../types/SystemTraySetting'; -import { parseSystemTraySetting } from '../types/SystemTraySetting'; import type { ConversationType } from '../state/ducks/conversations'; import type { AuthorizeArtCreatorDataType } from '../state/ducks/globalModals'; @@ -45,9 +43,15 @@ import { fromWebSafeBase64 } from './webSafeBase64'; import { getConversation } from './getConversation'; import { instance, PhoneNumberFormat } from './libphonenumberInstance'; import { showConfirmationDialog } from './showConfirmationDialog'; +import type { + EphemeralSettings, + SettingsValuesType, + ThemeType, +} from './preload'; +import type { SystemTraySetting } from '../types/SystemTraySetting'; +import { drop } from './drop'; type SentMediaQualityType = 'standard' | 'high'; -type ThemeType = 'light' | 'dark' | 'system'; type NotificationSettingType = 'message' | 'name' | 'count' | 'off'; export type IPCEventsValuesType = { @@ -64,17 +68,13 @@ export type IPCEventsValuesType = { hideMenuBar: boolean | undefined; incomingCallNotification: boolean; lastSyncTime: number | undefined; - localeOverride: string | null; notificationDrawAttention: boolean; notificationSetting: NotificationSettingType; preferredAudioInputDevice: AudioDevice | undefined; preferredAudioOutputDevice: AudioDevice | undefined; preferredVideoInputDevice: string | undefined; sentMediaQualitySetting: SentMediaQualityType; - spellCheck: boolean; - systemTraySetting: SystemTraySetting; textFormatting: boolean; - themeSetting: ThemeType; universalExpireTimer: DurationInSeconds; zoomFactor: ZoomFactorType; storyViewReceiptsEnabled: boolean; @@ -144,17 +144,21 @@ export type IPCEventsCallbacksType = { }; type ValuesWithGetters = Omit< - IPCEventsValuesType, + SettingsValuesType, // Async | 'zoomFactor' + | 'localeOverride' + | 'spellCheck' + | 'themeSetting' // Optional | 'mediaPermissions' | 'mediaCameraPermissions' | 'autoLaunch' + | 'systemTraySetting' >; type ValuesWithSetters = Omit< - IPCEventsValuesType, + SettingsValuesType, | 'blockedCount' | 'defaultConversationColor' | 'linkPreviewSetting' @@ -166,19 +170,37 @@ type ValuesWithSetters = Omit< // Optional | 'mediaPermissions' | 'mediaCameraPermissions' + + // Only set in the Settings window + | 'localeOverride' + | 'spellCheck' + | 'systemTraySetting' >; -export type IPCEventGetterType = +export type IPCEventsUpdatersType = { + [Key in keyof EphemeralSettings as IPCEventUpdaterType]?: ( + value: EphemeralSettings[Key] + ) => void; +}; + +export type IPCEventGetterType = `get${Capitalize}`; -export type IPCEventSetterType = +export type IPCEventSetterType = `set${Capitalize}`; +export type IPCEventUpdaterType = + `update${Capitalize}`; + export type IPCEventsGettersType = { [Key in keyof ValuesWithGetters as IPCEventGetterType]: () => ValuesWithGetters[Key]; } & { // Async getZoomFactor: () => Promise; + getLocaleOverride: () => Promise; + getSpellCheck: () => Promise; + getSystemTraySetting: () => Promise; + getThemeSetting: () => Promise; // Events onZoomFactorChange: (callback: (zoomFactor: ZoomFactorType) => void) => void; // Optional @@ -198,6 +220,7 @@ export type IPCEventsSettersType = { export type IPCEventsType = IPCEventsGettersType & IPCEventsSettersType & + IPCEventsUpdatersType & IPCEventsCallbacksType; export function createIPCEvents( @@ -393,11 +416,14 @@ export function createIPCEvents( window.storage.get('sent-media-quality', 'standard'), setSentMediaQualitySetting: value => window.storage.put('sent-media-quality', value), - getThemeSetting: () => window.storage.get('theme-setting', 'system'), - setThemeSetting: value => { - const promise = window.storage.put('theme-setting', value); - themeChanged(); - return promise; + getThemeSetting: async () => { + return getEphemeralSetting('themeSetting') ?? null; + }, + setThemeSetting: async value => { + drop(setEphemeralSetting('themeSetting', value)); + }, + updateThemeSetting: _theme => { + drop(themeChanged()); }, getHideMenuBar: () => window.storage.get('hide-menu-bar'), setHideMenuBar: value => { @@ -406,19 +432,9 @@ export function createIPCEvents( window.IPC.setMenuBarVisibility(!value); return promise; }, - getSystemTraySetting: () => - parseSystemTraySetting(window.storage.get('system-tray-setting')), - setSystemTraySetting: value => { - const promise = window.storage.put('system-tray-setting', value); - window.IPC.updateSystemTraySetting(value); - return promise; - }, - - getLocaleOverride: () => { - return window.storage.get('localeOverride') ?? null; - }, - setLocaleOverride: async (locale: string | null) => { - await window.storage.put('localeOverride', locale); + getSystemTraySetting: () => getEphemeralSetting('systemTraySetting'), + getLocaleOverride: async () => { + return getEphemeralSetting('localeOverride') ?? null; }, getNotificationSetting: () => window.storage.get('notification-setting', 'message'), @@ -456,8 +472,9 @@ export function createIPCEvents( setIncomingCallNotification: value => window.storage.put('incoming-call-notification', value), - getSpellCheck: () => window.storage.get('spell-check', true), - setSpellCheck: value => window.storage.put('spell-check', value), + getSpellCheck: () => { + return getEphemeralSetting('spellCheck'); + }, getTextFormatting: () => window.storage.get('textFormatting', true), setTextFormatting: value => window.storage.put('textFormatting', value), @@ -709,3 +726,16 @@ function showUnknownSgnlLinkModal(): void { description: window.i18n('icu:unknown-sgnl-link'), }); } + +function getEphemeralSetting( + name: Name +): Promise { + return ipcRenderer.invoke(`settings:get:${name}`); +} + +function setEphemeralSetting( + name: Name, + value: EphemeralSettings[Name] +): Promise { + return ipcRenderer.invoke(`settings:set:${name}`, value); +} diff --git a/ts/util/getThemeType.ts b/ts/util/getThemeType.ts index e745801a83..78d5eeae29 100644 --- a/ts/util/getThemeType.ts +++ b/ts/util/getThemeType.ts @@ -3,8 +3,8 @@ import { ThemeType } from '../types/Util'; -export function getThemeType(): ThemeType { - const themeSetting = window.Events.getThemeSetting(); +export async function getThemeType(): Promise { + const themeSetting = await window.Events.getThemeSetting(); if (themeSetting === 'light') { return ThemeType.light; diff --git a/ts/util/preload.ts b/ts/util/preload.ts index a5ee89ce0e..f75c7a02b8 100644 --- a/ts/util/preload.ts +++ b/ts/util/preload.ts @@ -9,9 +9,8 @@ import type { UnwrapPromise } from '../types/Util'; import type { IPCEventsValuesType, IPCEventsCallbacksType, - IPCEventGetterType, - IPCEventSetterType, } from './createIPCEvents'; +import type { SystemTraySetting } from '../types/SystemTraySetting'; type SettingOptionsType = { getter?: boolean; @@ -23,7 +22,27 @@ export type SettingType = Readonly<{ setValue: (value: Value) => Promise; }>; -function capitalize( +export type ThemeType = 'light' | 'dark' | 'system'; + +export type EphemeralSettings = { + spellCheck: boolean; + systemTraySetting: SystemTraySetting; + themeSetting: ThemeType; + localeOverride: string | null; +}; + +export type SettingsValuesType = IPCEventsValuesType & EphemeralSettings; + +type SettingGetterType = + `get${Capitalize}`; + +type SettingSetterType = + `set${Capitalize}`; + +type SettingUpdaterType = + `update${Capitalize}`; + +function capitalize( name: Name ): Capitalize { const result = name.slice(0, 1).toUpperCase() + name.slice(1); @@ -31,21 +50,27 @@ function capitalize( return result as Capitalize; } -function getSetterName( +function getSetterName( name: Key -): IPCEventSetterType { +): SettingSetterType { return `set${capitalize(name)}`; } -function getGetterName( +function getGetterName( name: Key -): IPCEventGetterType { +): SettingGetterType { return `get${capitalize(name)}`; } +function getUpdaterName( + name: Key +): SettingUpdaterType { + return `update${capitalize(name)}`; +} + export function createSetting< - Name extends keyof IPCEventsValuesType, - Value extends IPCEventsValuesType[Name] + Name extends keyof SettingsValuesType, + Value extends SettingsValuesType[Name] >(name: Name, overrideOptions: SettingOptionsType = {}): SettingType { const options = { getter: true, @@ -86,7 +111,7 @@ export function createCallback< } export function installSetting( - name: keyof IPCEventsValuesType, + name: keyof SettingsValuesType, { getter = true, setter = true }: { getter?: boolean; setter?: boolean } = {} ): void { const getterName = getGetterName(name); @@ -135,6 +160,21 @@ export function installSetting( } } +export function installEphemeralSetting(name: keyof EphemeralSettings): void { + installSetting(name); + + const updaterName = getUpdaterName(name); + + ipcRenderer.on(`settings:update:${name}`, async (_event, value) => { + const updateFn = window.Events[updaterName] as (value: unknown) => void; + if (!updateFn) { + return; + } + + await updateFn(value); + }); +} + export function installCallback( name: Name ): void { diff --git a/ts/window.d.ts b/ts/window.d.ts index b98495946d..9942de9dff 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -41,7 +41,6 @@ import type { SocketStatus } from './types/SocketStatus'; import type SyncRequest from './textsecure/SyncRequest'; import type { MessageCache } from './services/MessageCache'; import type { StateType } from './state/reducer'; -import type { SystemTraySetting } from './types/SystemTraySetting'; import type { Address } from './types/Address'; import type { QualifiedAddress } from './types/QualifiedAddress'; import type { CIType } from './CI'; @@ -88,7 +87,6 @@ export type IPCType = { showWindowsNotification: (data: WindowsNotificationData) => Promise; shutdown: () => void; titleBarDoubleClick: () => void; - updateSystemTraySetting: (value: SystemTraySetting) => void; updateTrayIcon: (count: number) => void; }; diff --git a/ts/windows/context.ts b/ts/windows/context.ts index 8ffa69d7b6..70ca5778ac 100644 --- a/ts/windows/context.ts +++ b/ts/windows/context.ts @@ -5,11 +5,10 @@ import { ipcRenderer } from 'electron'; import type { MenuItemConstructorOptions } from 'electron'; import type { MenuOptionsType } from '../types/menu'; -import type { IPCEventsValuesType } from '../util/createIPCEvents'; import type { LocalizerType } from '../types/Util'; import type { LoggerType } from '../types/Logging'; import type { NativeThemeType } from '../context/createNativeThemeListener'; -import type { SettingType } from '../util/preload'; +import type { SettingType, SettingsValuesType } from '../util/preload'; import type { RendererConfigType } from '../types/RendererConfig'; import { Bytes } from '../context/Bytes'; @@ -58,7 +57,7 @@ export type MinimalSignalContextType = { nativeThemeListener: NativeThemeType; restartApp: () => void; Settings: { - themeSetting: SettingType; + themeSetting: SettingType; waitForChange: () => Promise; }; OS: { diff --git a/ts/windows/main/phase1-ipc.ts b/ts/windows/main/phase1-ipc.ts index 210a50e4bf..99d8e4cfc0 100644 --- a/ts/windows/main/phase1-ipc.ts +++ b/ts/windows/main/phase1-ipc.ts @@ -127,11 +127,6 @@ const IPC: IPCType = { titleBarDoubleClick: () => { ipc.send('title-bar-double-click'); }, - updateSystemTraySetting: ( - systemTraySetting /* : Readonly */ - ) => { - void ipc.invoke('update-system-tray-setting', systemTraySetting); - }, updateTrayIcon: unreadCount => ipc.send('update-tray-icon', unreadCount), }; diff --git a/ts/windows/main/preload_test.ts b/ts/windows/main/preload_test.ts index 941893f263..43b0a75d90 100644 --- a/ts/windows/main/preload_test.ts +++ b/ts/windows/main/preload_test.ts @@ -14,6 +14,7 @@ import { initMessageCleanup } from '../../services/messageStateCleanup'; import { initializeMessageCounter } from '../../util/incrementMessageCounter'; import { initializeRedux } from '../../state/initializeRedux'; import * as Stickers from '../../types/Stickers'; +import { ThemeType } from '../../types/Util'; window.assert = assert; @@ -51,6 +52,7 @@ window.testUtilities = { }, stories: [], storyDistributionLists: [], + theme: ThemeType.dark, }); }, diff --git a/ts/windows/preload.ts b/ts/windows/preload.ts index 5c111a56a0..507e8688c1 100644 --- a/ts/windows/preload.ts +++ b/ts/windows/preload.ts @@ -1,7 +1,11 @@ // Copyright 2017 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { installCallback, installSetting } from '../util/preload'; +import { + installCallback, + installSetting, + installEphemeralSetting, +} from '../util/preload'; // ChatColorPicker redux hookups installCallback('getCustomColors'); @@ -48,14 +52,10 @@ installSetting('hasStoriesDisabled'); installSetting('hideMenuBar'); installSetting('incomingCallNotification'); installSetting('lastSyncTime'); -installSetting('localeOverride'); installSetting('notificationDrawAttention'); installSetting('notificationSetting'); -installSetting('spellCheck'); -installSetting('systemTraySetting'); installSetting('sentMediaQualitySetting'); installSetting('textFormatting'); -installSetting('themeSetting'); installSetting('universalExpireTimer'); installSetting('zoomFactor'); installSetting('phoneNumberDiscoverabilitySetting'); @@ -66,3 +66,8 @@ installCallback('getAvailableIODevices'); installSetting('preferredAudioInputDevice'); installSetting('preferredAudioOutputDevice'); installSetting('preferredVideoInputDevice'); + +installEphemeralSetting('themeSetting'); +installEphemeralSetting('systemTraySetting'); +installEphemeralSetting('localeOverride'); +installEphemeralSetting('spellCheck');