Change ephemeral settings to only persist in ephemeralConfig

This commit is contained in:
ayumi-signal 2024-03-07 09:36:08 -08:00 committed by GitHub
parent 07e2fb7f60
commit 73e8bec42f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 295 additions and 265 deletions

View file

@ -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<SystemTraySetting>;
constructor(
private readonly sql: Pick<MainSQL, 'sqlCall'>,
private readonly ephemeralConfig: Pick<ConfigType, 'get' | 'set'>,
private readonly argv: Array<string>,
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 {

View file

@ -292,22 +292,18 @@ const sql = new MainSQL();
const heicConverter = getHeicConverter();
async function getSpellCheckSetting(): Promise<boolean> {
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<ThemeSettingType> {
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<string | null> {
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<unknown> */) => {
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<string>) {
for (const arg of argv) {

View file

@ -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<void> {
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<void> {
);
}
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<void> {
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<void> {
(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<void> {
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<void> {
function setupAppState({
mainWindowStats,
menuOptions,
theme,
}: {
mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType;
theme: ThemeType;
}) {
initializeRedux({
callsHistory: getCallsHistoryForRedux(),
@ -1198,6 +1177,7 @@ export async function startApp(): Promise<void> {
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<void> {
}
}
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<void> => {
const { promise, resolve } = explodePromise<void>();

View file

@ -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<Key extends keyof SettingsValuesType> =
`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 extends keyof EphemeralSettings>(
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<keyof SettingsValuesType>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback: (...args: Array<any>) => void
): this;
public override on(
type: string | symbol,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
listener: (...args: Array<any>) => 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<keyof SettingsValuesType>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: Array<any>
): boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public override emit(type: string | symbol, ...args: Array<any>): boolean {
return super.emit(type, ...args);
}
}

View file

@ -3,9 +3,9 @@
import { getThemeType } from '../util/getThemeType';
export function themeChanged(): void {
export async function themeChanged(): Promise<void> {
if (window.reduxActions && window.reduxActions.user) {
const theme = getThemeType();
const theme = await getThemeType();
window.reduxActions.user.userChanged({ theme });
}
}

View file

@ -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<CallHistoryDetails>;
@ -58,6 +59,7 @@ export function getInitialState({
storyDistributionLists: Array<StoryDistributionListDataType>;
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()) {

View file

@ -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<CallHistoryDetails>;
callsHistoryUnreadCount: number;
@ -28,6 +30,7 @@ export function initializeRedux({
menuOptions: MenuOptionsType;
stories: Array<StoryDataType>;
storyDistributionLists: Array<StoryDistributionListDataType>;
theme: ThemeType;
}): void {
const initialState = getInitialState({
badges: initialBadgesState,
@ -37,6 +40,7 @@ export function initializeRedux({
menuOptions,
stories,
storyDistributionLists,
theme,
});
const store = createStore(initialState);

View file

@ -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<MainSQL, 'sqlCall'>;
let config: Pick<ConfigType, 'get' | 'set'>;
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);
});
});

View file

@ -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;

View file

@ -25,7 +25,6 @@ export const STORAGE_UI_KEYS: ReadonlyArray<keyof StorageAccessType> = [
'hasCompletedUsernameLinkOnboarding',
'hide-menu-bar',
'incoming-call-notification',
'localeOverride',
'navTabsCollapsed',
'notification-draw-attention',
'notification-setting',
@ -40,10 +39,7 @@ export const STORAGE_UI_KEYS: ReadonlyArray<keyof StorageAccessType> = [
'showStickerPickerHint',
'showStickersIntroduction',
'skinTone',
'spell-check',
'system-tray-setting',
'textFormatting',
'theme-setting',
'version',
'zoomFactor',
];

View file

@ -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<Key extends keyof IPCEventsValuesType> =
export type IPCEventsUpdatersType = {
[Key in keyof EphemeralSettings as IPCEventUpdaterType<Key>]?: (
value: EphemeralSettings[Key]
) => void;
};
export type IPCEventGetterType<Key extends keyof SettingsValuesType> =
`get${Capitalize<Key>}`;
export type IPCEventSetterType<Key extends keyof IPCEventsValuesType> =
export type IPCEventSetterType<Key extends keyof SettingsValuesType> =
`set${Capitalize<Key>}`;
export type IPCEventUpdaterType<Key extends keyof SettingsValuesType> =
`update${Capitalize<Key>}`;
export type IPCEventsGettersType = {
[Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key];
} & {
// Async
getZoomFactor: () => Promise<ZoomFactorType>;
getLocaleOverride: () => Promise<string | null>;
getSpellCheck: () => Promise<boolean>;
getSystemTraySetting: () => Promise<SystemTraySetting>;
getThemeSetting: () => Promise<ThemeType>;
// 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 extends keyof EphemeralSettings>(
name: Name
): Promise<EphemeralSettings[Name]> {
return ipcRenderer.invoke(`settings:get:${name}`);
}
function setEphemeralSetting<Name extends keyof EphemeralSettings>(
name: Name,
value: EphemeralSettings[Name]
): Promise<void> {
return ipcRenderer.invoke(`settings:set:${name}`, value);
}

View file

@ -3,8 +3,8 @@
import { ThemeType } from '../types/Util';
export function getThemeType(): ThemeType {
const themeSetting = window.Events.getThemeSetting();
export async function getThemeType(): Promise<ThemeType> {
const themeSetting = await window.Events.getThemeSetting();
if (themeSetting === 'light') {
return ThemeType.light;

View file

@ -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<Value> = Readonly<{
setValue: (value: Value) => Promise<Value>;
}>;
function capitalize<Name extends keyof IPCEventsValuesType>(
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<Key extends keyof SettingsValuesType> =
`get${Capitalize<Key>}`;
type SettingSetterType<Key extends keyof SettingsValuesType> =
`set${Capitalize<Key>}`;
type SettingUpdaterType<Key extends keyof SettingsValuesType> =
`update${Capitalize<Key>}`;
function capitalize<Name extends keyof SettingsValuesType>(
name: Name
): Capitalize<Name> {
const result = name.slice(0, 1).toUpperCase() + name.slice(1);
@ -31,21 +50,27 @@ function capitalize<Name extends keyof IPCEventsValuesType>(
return result as Capitalize<Name>;
}
function getSetterName<Key extends keyof IPCEventsValuesType>(
function getSetterName<Key extends keyof SettingsValuesType>(
name: Key
): IPCEventSetterType<Key> {
): SettingSetterType<Key> {
return `set${capitalize(name)}`;
}
function getGetterName<Key extends keyof IPCEventsValuesType>(
function getGetterName<Key extends keyof SettingsValuesType>(
name: Key
): IPCEventGetterType<Key> {
): SettingGetterType<Key> {
return `get${capitalize(name)}`;
}
function getUpdaterName<Key extends keyof EphemeralSettings>(
name: Key
): SettingUpdaterType<Key> {
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<Value> {
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 extends keyof IPCEventsCallbacksType>(
name: Name
): void {

2
ts/window.d.ts vendored
View file

@ -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<void>;
shutdown: () => void;
titleBarDoubleClick: () => void;
updateSystemTraySetting: (value: SystemTraySetting) => void;
updateTrayIcon: (count: number) => void;
};

View file

@ -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<IPCEventsValuesType['themeSetting']>;
themeSetting: SettingType<SettingsValuesType['themeSetting']>;
waitForChange: () => Promise<void>;
};
OS: {

View file

@ -127,11 +127,6 @@ const IPC: IPCType = {
titleBarDoubleClick: () => {
ipc.send('title-bar-double-click');
},
updateSystemTraySetting: (
systemTraySetting /* : Readonly<SystemTraySetting> */
) => {
void ipc.invoke('update-system-tray-setting', systemTraySetting);
},
updateTrayIcon: unreadCount => ipc.send('update-tray-icon', unreadCount),
};

View file

@ -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,
});
},

View file

@ -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');