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, SystemTraySetting,
} from '../ts/types/SystemTraySetting'; } from '../ts/types/SystemTraySetting';
import { isSystemTraySupported } from '../ts/types/Settings'; import { isSystemTraySupported } from '../ts/types/Settings';
import type { MainSQL } from '../ts/sql/main';
import type { ConfigType } from './base_config'; import type { ConfigType } from './base_config';
/** /**
@ -21,7 +20,6 @@ export class SystemTraySettingCache {
private getPromise: undefined | Promise<SystemTraySetting>; private getPromise: undefined | Promise<SystemTraySetting>;
constructor( constructor(
private readonly sql: Pick<MainSQL, 'sqlCall'>,
private readonly ephemeralConfig: Pick<ConfigType, 'get' | 'set'>, private readonly ephemeralConfig: Pick<ConfigType, 'get' | 'set'>,
private readonly argv: Array<string>, private readonly argv: Array<string>,
private readonly appVersion: string private readonly appVersion: string
@ -56,15 +54,11 @@ export class SystemTraySettingCache {
`getSystemTraySetting saw --use-tray-icon flag. Returning ${result}` `getSystemTraySetting saw --use-tray-icon flag. Returning ${result}`
); );
} else if (isSystemTraySupported(OS, this.appVersion)) { } else if (isSystemTraySupported(OS, this.appVersion)) {
const fastValue = this.ephemeralConfig.get('system-tray-setting'); const value = this.ephemeralConfig.get('system-tray-setting');
if (fastValue !== undefined) { if (value !== undefined) {
log.info('getSystemTraySetting got fast value', fastValue); log.info('getSystemTraySetting got value', value);
} }
const value =
fastValue ??
(await this.sql.sqlCall('getItemById', 'system-tray-setting'))?.value;
if (value !== undefined) { if (value !== undefined) {
result = parseSystemTraySetting(value); result = parseSystemTraySetting(value);
log.info(`getSystemTraySetting returning ${result}`); log.info(`getSystemTraySetting returning ${result}`);
@ -73,7 +67,7 @@ export class SystemTraySettingCache {
log.info(`getSystemTraySetting got no value, returning ${result}`); log.info(`getSystemTraySetting got no value, returning ${result}`);
} }
if (result !== fastValue) { if (result !== value) {
this.ephemeralConfig.set('system-tray-setting', result); this.ephemeralConfig.set('system-tray-setting', result);
} }
} else { } else {

View file

@ -292,22 +292,18 @@ const sql = new MainSQL();
const heicConverter = getHeicConverter(); const heicConverter = getHeicConverter();
async function getSpellCheckSetting(): Promise<boolean> { async function getSpellCheckSetting(): Promise<boolean> {
const fastValue = ephemeralConfig.get('spell-check'); const value = ephemeralConfig.get('spell-check');
if (typeof fastValue === 'boolean') { if (typeof value === 'boolean') {
getLogger().info('got fast spellcheck setting', fastValue); getLogger().info('got fast spellcheck setting', value);
return fastValue; return value;
} }
const json = await sql.sqlCall('getItemById', 'spell-check');
// Default to `true` if setting doesn't exist yet // 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 true;
return slowValue;
} }
type GetThemeSettingOptionsType = Readonly<{ type GetThemeSettingOptionsType = Readonly<{
@ -317,29 +313,22 @@ type GetThemeSettingOptionsType = Readonly<{
async function getThemeSetting({ async function getThemeSetting({
ephemeralOnly = false, ephemeralOnly = false,
}: GetThemeSettingOptionsType = {}): Promise<ThemeSettingType> { }: GetThemeSettingOptionsType = {}): Promise<ThemeSettingType> {
let result: unknown; const value = ephemeralConfig.get('theme-setting');
if (value !== undefined) {
const fastValue = ephemeralConfig.get('theme-setting'); getLogger().info('got fast theme-setting value', value);
if (fastValue !== undefined) {
getLogger().info('got fast theme-setting value', fastValue);
result = fastValue;
} else if (ephemeralOnly) { } else if (ephemeralOnly) {
return 'system'; 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 // Default to `system` if setting doesn't exist or is invalid
const validatedResult = const validatedResult =
result === 'light' || result === 'dark' || result === 'system' value === 'light' || value === 'dark' || value === 'system'
? result ? value
: 'system'; : 'system';
if (fastValue !== validatedResult) { if (value !== validatedResult) {
ephemeralConfig.set('theme-setting', validatedResult); ephemeralConfig.set('theme-setting', validatedResult);
getLogger().info('got slow theme-setting value', result); getLogger().info('saving theme-setting value', validatedResult);
} }
return validatedResult; return validatedResult;
@ -372,23 +361,19 @@ async function getBackgroundColor(
} }
async function getLocaleOverrideSetting(): Promise<string | null> { 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 // eslint-disable-next-line eqeqeq -- Checking for null explicitly
if (typeof fastValue === 'string' || fastValue === null) { if (typeof value === 'string' || value === null) {
getLogger().info('got fast localeOverride setting', fastValue); getLogger().info('got fast localeOverride setting', value);
return fastValue; return value;
} }
const json = await sql.sqlCall('getItemById', 'localeOverride');
// Default to `null` if setting doesn't exist yet // 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 null;
return slowValue;
} }
const zoomFactorService = new ZoomFactorService({ const zoomFactorService = new ZoomFactorService({
@ -409,7 +394,6 @@ const zoomFactorService = new ZoomFactorService({
let systemTrayService: SystemTrayService | undefined; let systemTrayService: SystemTrayService | undefined;
const systemTraySettingCache = new SystemTraySettingCache( const systemTraySettingCache = new SystemTraySettingCache(
sql,
ephemeralConfig, ephemeralConfig,
process.argv, process.argv,
app.getVersion() app.getVersion()
@ -1811,12 +1795,7 @@ app.on('ready', async () => {
getLogger().info(`app.ready: setting system-tray-setting to ${newValue}`); getLogger().info(`app.ready: setting system-tray-setting to ${newValue}`);
systemTraySettingCache.set(newValue); systemTraySettingCache.set(newValue);
// Update both stores
ephemeralConfig.set('system-tray-setting', newValue); ephemeralConfig.set('system-tray-setting', newValue);
await sql.sqlCall('createOrUpdateItem', {
id: 'system-tray-setting',
value: newValue,
});
if (OS.isWindows()) { if (OS.isWindows()) {
getLogger().info('app.ready: enabling open at login'); getLogger().info('app.ready: enabling open at login');
@ -1832,6 +1811,32 @@ app.on('ready', async () => {
settingsChannel = new SettingsChannel(); settingsChannel = new SettingsChannel();
settingsChannel.install(); 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 // 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. // from when it's first ready until the loading screen disappears.
ipc.once('signal-app-loaded', (event, info) => { 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', () => { ipc.on('close-screen-share-controller', () => {
if (screenShareWindow) { if (screenShareWindow) {
screenShareWindow.close(); screenShareWindow.close();
@ -2564,13 +2545,14 @@ ipc.on('get-user-data-path', event => {
}); });
// Refresh the settings window whenever preferences change // Refresh the settings window whenever preferences change
ipc.on('preferences-changed', () => { const sendPreferencesChangedEventToWindows = () => {
for (const window of activeWindows) { for (const window of activeWindows) {
if (window.webContents) { if (window.webContents) {
window.webContents.send('preferences-changed'); window.webContents.send('preferences-changed');
} }
} }
}); };
ipc.on('preferences-changed', sendPreferencesChangedEventToWindows);
function maybeGetIncomingSignalRoute(argv: Array<string>) { function maybeGetIncomingSignalRoute(argv: Array<string>) {
for (const arg of argv) { for (const arg of argv) {

View file

@ -190,6 +190,7 @@ import {
updateLocalGroupCallHistoryTimestamp, updateLocalGroupCallHistoryTimestamp,
} from './util/callDisposition'; } from './util/callDisposition';
import { deriveStorageServiceKey } from './Crypto'; import { deriveStorageServiceKey } from './Crypto';
import { getThemeType } from './util/getThemeType';
export function isOverHourIntoPast(timestamp: number): boolean { export function isOverHourIntoPast(timestamp: number): boolean {
return isNumber(timestamp) && isOlderThan(timestamp, HOUR); return isNumber(timestamp) && isOlderThan(timestamp, HOUR);
@ -697,25 +698,6 @@ export async function startApp(): Promise<void> {
log.info('Storage fetch'); log.info('Storage fetch');
drop(window.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 // 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. // than the first time. And window.storage.fetch() will cause onready() to fire.
let first = true; 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 ( if (
window.isBeforeVersion(lastVersion, 'v1.36.0-beta.1') && window.isBeforeVersion(lastVersion, 'v1.36.0-beta.1') &&
window.isAfterVersion(lastVersion, 'v1.35.0-beta.1') window.isAfterVersion(lastVersion, 'v1.35.0-beta.1')
@ -1133,6 +1105,8 @@ export async function startApp(): Promise<void> {
platform: 'unknown', platform: 'unknown',
}; };
let theme: ThemeType = window.systemTheme;
try { try {
// This needs to load before we prime the data because we expect // This needs to load before we prime the data because we expect
// ConversationController to be loaded and ready to use by then. // ConversationController to be loaded and ready to use by then.
@ -1153,6 +1127,9 @@ export async function startApp(): Promise<void> {
(async () => { (async () => {
menuOptions = await window.SignalContext.getMenuOptions(); menuOptions = await window.SignalContext.getMenuOptions();
})(), })(),
(async () => {
theme = await getThemeType();
})(),
]); ]);
await window.ConversationController.checkForConflicts(); await window.ConversationController.checkForConflicts();
} catch (error) { } catch (error) {
@ -1161,7 +1138,7 @@ export async function startApp(): Promise<void> {
Errors.toLogFormat(error) Errors.toLogFormat(error)
); );
} finally { } finally {
setupAppState({ mainWindowStats, menuOptions }); setupAppState({ mainWindowStats, menuOptions, theme });
drop(start()); drop(start());
window.Signal.Services.initializeNetworkObserver( window.Signal.Services.initializeNetworkObserver(
window.reduxActions.network window.reduxActions.network
@ -1186,9 +1163,11 @@ export async function startApp(): Promise<void> {
function setupAppState({ function setupAppState({
mainWindowStats, mainWindowStats,
menuOptions, menuOptions,
theme,
}: { }: {
mainWindowStats: MainWindowStatsType; mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType; menuOptions: MenuOptionsType;
theme: ThemeType;
}) { }) {
initializeRedux({ initializeRedux({
callsHistory: getCallsHistoryForRedux(), callsHistory: getCallsHistoryForRedux(),
@ -1198,6 +1177,7 @@ export async function startApp(): Promise<void> {
menuOptions, menuOptions,
stories: getStoriesForRedux(), stories: getStoriesForRedux(),
storyDistributionLists: getDistributionListsForRedux(), storyDistributionLists: getDistributionListsForRedux(),
theme,
}); });
// Here we set up a full redux store with initial state for our LeftPane Root // 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( const waitForEvent = createTaskWithTimeout(
(event: string): Promise<void> => { (event: string): Promise<void> => {
const { promise, resolve } = explodePromise<void>(); const { promise, resolve } = explodePromise<void>();

View file

@ -14,6 +14,7 @@ import type {
IPCEventsValuesType, IPCEventsValuesType,
IPCEventsCallbacksType, IPCEventsCallbacksType,
} from '../util/createIPCEvents'; } from '../util/createIPCEvents';
import type { EphemeralSettings, SettingsValuesType } from '../util/preload';
const EPHEMERAL_NAME_MAP = new Map([ const EPHEMERAL_NAME_MAP = new Map([
['spellCheck', 'spell-check'], ['spellCheck', 'spell-check'],
@ -27,6 +28,9 @@ type ResponseQueueEntry = Readonly<{
reject(error: Error): void; reject(error: Error): void;
}>; }>;
type SettingChangeEventType<Key extends keyof SettingsValuesType> =
`change:${Key}`;
export class SettingsChannel extends EventEmitter { export class SettingsChannel extends EventEmitter {
private mainWindow?: BrowserWindow; private mainWindow?: BrowserWindow;
@ -70,17 +74,7 @@ export class SettingsChannel extends EventEmitter {
this.installSetting('readReceiptSetting', { setter: false }); this.installSetting('readReceiptSetting', { setter: false });
this.installSetting('typingIndicatorSetting', { setter: false }); this.installSetting('typingIndicatorSetting', { setter: false });
this.installSetting('themeSetting', {
isEphemeral: true,
});
this.installSetting('hideMenuBar'); this.installSetting('hideMenuBar');
this.installSetting('systemTraySetting', {
isEphemeral: true,
});
this.installSetting('localeOverride', {
isEphemeral: true,
});
this.installSetting('notificationSetting'); this.installSetting('notificationSetting');
this.installSetting('notificationDrawAttention'); this.installSetting('notificationDrawAttention');
this.installSetting('audioMessage'); this.installSetting('audioMessage');
@ -88,9 +82,6 @@ export class SettingsChannel extends EventEmitter {
this.installSetting('countMutedConversations'); this.installSetting('countMutedConversations');
this.installSetting('sentMediaQualitySetting'); this.installSetting('sentMediaQualitySetting');
this.installSetting('spellCheck', {
isEphemeral: true,
});
this.installSetting('textFormatting'); this.installSetting('textFormatting');
this.installSetting('autoConvertEmoji'); this.installSetting('autoConvertEmoji');
@ -116,6 +107,11 @@ export class SettingsChannel extends EventEmitter {
this.installSetting('phoneNumberDiscoverabilitySetting'); this.installSetting('phoneNumberDiscoverabilitySetting');
this.installSetting('phoneNumberSharingSetting'); this.installSetting('phoneNumberSharingSetting');
this.installEphemeralSetting('themeSetting');
this.installEphemeralSetting('systemTraySetting');
this.installEphemeralSetting('localeOverride');
this.installEphemeralSetting('spellCheck');
installPermissionsHandler({ session: session.defaultSession, userConfig }); installPermissionsHandler({ session: session.defaultSession, userConfig });
// These ones are different because its single source of truth is userConfig, // These ones are different because its single source of truth is userConfig,
@ -234,8 +230,7 @@ export class SettingsChannel extends EventEmitter {
{ {
getter = true, getter = true,
setter = true, setter = true,
isEphemeral = false, }: { getter?: boolean; setter?: boolean } = {}
}: { getter?: boolean; setter?: boolean; isEphemeral?: boolean } = {}
): void { ): void {
if (getter) { if (getter) {
ipc.handle(`settings:get:${name}`, async () => { ipc.handle(`settings:get:${name}`, async () => {
@ -248,18 +243,89 @@ export class SettingsChannel extends EventEmitter {
} }
ipc.handle(`settings:set:${name}`, async (_event, value) => { 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); await this.setSettingInMainWindow(name, value);
this.emit(`change:${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'; import { getThemeType } from '../util/getThemeType';
export function themeChanged(): void { export async function themeChanged(): Promise<void> {
if (window.reduxActions && window.reduxActions.user) { if (window.reduxActions && window.reduxActions.user) {
const theme = getThemeType(); const theme = await getThemeType();
window.reduxActions.user.userChanged({ theme }); window.reduxActions.user.userChanged({ theme });
} }
} }

View file

@ -37,10 +37,10 @@ import type { StoryDistributionListDataType } from './ducks/storyDistributionLis
import OS from '../util/os/osMain'; import OS from '../util/os/osMain';
import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis'; import { getEmojiReducerState as emojis } from '../util/loadRecentEmojis';
import { getInitialState as stickers } from '../types/Stickers'; import { getInitialState as stickers } from '../types/Stickers';
import { getThemeType } from '../util/getThemeType';
import { getInteractionMode } from '../services/InteractionMode'; import { getInteractionMode } from '../services/InteractionMode';
import { makeLookup } from '../util/makeLookup'; import { makeLookup } from '../util/makeLookup';
import type { CallHistoryDetails } from '../types/CallDisposition'; import type { CallHistoryDetails } from '../types/CallDisposition';
import type { ThemeType } from '../types/Util';
export function getInitialState({ export function getInitialState({
badges, badges,
@ -50,6 +50,7 @@ export function getInitialState({
storyDistributionLists, storyDistributionLists,
mainWindowStats, mainWindowStats,
menuOptions, menuOptions,
theme,
}: { }: {
badges: BadgesStateType; badges: BadgesStateType;
callsHistory: ReadonlyArray<CallHistoryDetails>; callsHistory: ReadonlyArray<CallHistoryDetails>;
@ -58,6 +59,7 @@ export function getInitialState({
storyDistributionLists: Array<StoryDistributionListDataType>; storyDistributionLists: Array<StoryDistributionListDataType>;
mainWindowStats: MainWindowStatsType; mainWindowStats: MainWindowStatsType;
menuOptions: MenuOptionsType; menuOptions: MenuOptionsType;
theme: ThemeType;
}): StateType { }): StateType {
const items = window.storage.getItemsState(); const items = window.storage.getItemsState();
@ -72,8 +74,6 @@ export function getInitialState({
window.ConversationController.getOurConversationId(); window.ConversationController.getOurConversationId();
const ourDeviceId = window.textsecure.storage.user.getDeviceId(); const ourDeviceId = window.textsecure.storage.user.getDeviceId();
const theme = getThemeType();
let osName: 'windows' | 'macos' | 'linux' | undefined; let osName: 'windows' | 'macos' | 'linux' | undefined;
if (OS.isWindows()) { if (OS.isWindows()) {

View file

@ -11,6 +11,7 @@ import type { StoryDistributionListDataType } from './ducks/storyDistributionLis
import { actionCreators } from './actions'; import { actionCreators } from './actions';
import { createStore } from './createStore'; import { createStore } from './createStore';
import { getInitialState } from './getInitialState'; import { getInitialState } from './getInitialState';
import type { ThemeType } from '../types/Util';
export function initializeRedux({ export function initializeRedux({
callsHistory, callsHistory,
@ -20,6 +21,7 @@ export function initializeRedux({
menuOptions, menuOptions,
stories, stories,
storyDistributionLists, storyDistributionLists,
theme,
}: { }: {
callsHistory: ReadonlyArray<CallHistoryDetails>; callsHistory: ReadonlyArray<CallHistoryDetails>;
callsHistoryUnreadCount: number; callsHistoryUnreadCount: number;
@ -28,6 +30,7 @@ export function initializeRedux({
menuOptions: MenuOptionsType; menuOptions: MenuOptionsType;
stories: Array<StoryDataType>; stories: Array<StoryDataType>;
storyDistributionLists: Array<StoryDistributionListDataType>; storyDistributionLists: Array<StoryDistributionListDataType>;
theme: ThemeType;
}): void { }): void {
const initialState = getInitialState({ const initialState = getInitialState({
badges: initialBadgesState, badges: initialBadgesState,
@ -37,6 +40,7 @@ export function initializeRedux({
menuOptions, menuOptions,
stories, stories,
storyDistributionLists, storyDistributionLists,
theme,
}); });
const store = createStore(initialState); const store = createStore(initialState);

View file

@ -3,7 +3,6 @@
import { assert } from 'chai'; import { assert } from 'chai';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import type { MainSQL } from '../../sql/main';
import { SystemTraySetting } from '../../types/SystemTraySetting'; import { SystemTraySetting } from '../../types/SystemTraySetting';
import type { ConfigType } from '../../../app/base_config'; import type { ConfigType } from '../../../app/base_config';
@ -12,18 +11,13 @@ import { SystemTraySettingCache } from '../../../app/SystemTraySettingCache';
describe('SystemTraySettingCache', () => { describe('SystemTraySettingCache', () => {
let sandbox: sinon.SinonSandbox; let sandbox: sinon.SinonSandbox;
let sqlCallStub: sinon.SinonStub;
let configGetStub: sinon.SinonStub; let configGetStub: sinon.SinonStub;
let configSetStub: sinon.SinonStub; let configSetStub: sinon.SinonStub;
let sql: Pick<MainSQL, 'sqlCall'>;
let config: Pick<ConfigType, 'get' | 'set'>; let config: Pick<ConfigType, 'get' | 'set'>;
beforeEach(() => { beforeEach(() => {
sandbox = sinon.createSandbox(); sandbox = sinon.createSandbox();
sqlCallStub = sandbox.stub().resolves();
sql = { sqlCall: sqlCallStub };
configGetStub = sandbox.stub().returns(undefined); configGetStub = sandbox.stub().returns(undefined);
configSetStub = sandbox.stub().returns(undefined); configSetStub = sandbox.stub().returns(undefined);
config = { get: configGetStub, set: configSetStub }; config = { get: configGetStub, set: configSetStub };
@ -35,7 +29,6 @@ describe('SystemTraySettingCache', () => {
it('returns MinimizeToAndStartInSystemTray if passed the --start-in-tray argument', async () => { it('returns MinimizeToAndStartInSystemTray if passed the --start-in-tray argument', async () => {
const justOneArg = new SystemTraySettingCache( const justOneArg = new SystemTraySettingCache(
sql,
config, config,
['--start-in-tray'], ['--start-in-tray'],
'1.2.3' '1.2.3'
@ -46,7 +39,6 @@ describe('SystemTraySettingCache', () => {
); );
const bothArgs = new SystemTraySettingCache( const bothArgs = new SystemTraySettingCache(
sql,
config, config,
['--start-in-tray', '--use-tray-icon'], ['--start-in-tray', '--use-tray-icon'],
'1.2.3' '1.2.3'
@ -56,14 +48,12 @@ describe('SystemTraySettingCache', () => {
SystemTraySetting.MinimizeToAndStartInSystemTray SystemTraySetting.MinimizeToAndStartInSystemTray
); );
sinon.assert.notCalled(sqlCallStub);
sinon.assert.notCalled(configGetStub); sinon.assert.notCalled(configGetStub);
sinon.assert.notCalled(configSetStub); sinon.assert.notCalled(configSetStub);
}); });
it('returns MinimizeToSystemTray if passed the --use-tray-icon argument', async () => { it('returns MinimizeToSystemTray if passed the --use-tray-icon argument', async () => {
const cache = new SystemTraySettingCache( const cache = new SystemTraySettingCache(
sql,
config, config,
['--use-tray-icon'], ['--use-tray-icon'],
'1.2.3' '1.2.3'
@ -73,7 +63,6 @@ describe('SystemTraySettingCache', () => {
SystemTraySetting.MinimizeToSystemTray SystemTraySetting.MinimizeToSystemTray
); );
sinon.assert.notCalled(sqlCallStub);
sinon.assert.notCalled(configGetStub); sinon.assert.notCalled(configGetStub);
sinon.assert.notCalled(configSetStub); sinon.assert.notCalled(configSetStub);
}); });
@ -81,7 +70,7 @@ describe('SystemTraySettingCache', () => {
it('returns Uninitialized if system tray is supported but no preference is stored', async () => { it('returns Uninitialized if system tray is supported but no preference is stored', async () => {
sandbox.stub(process, 'platform').value('win32'); 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.strictEqual(await cache.get(), SystemTraySetting.Uninitialized);
assert(configGetStub.calledOnceWith('system-tray-setting')); assert(configGetStub.calledOnceWith('system-tray-setting'));
assert( assert(
@ -95,9 +84,9 @@ describe('SystemTraySettingCache', () => {
it('returns Uninitialized if system tray is supported but the stored preference is invalid', async () => { it('returns Uninitialized if system tray is supported but the stored preference is invalid', async () => {
sandbox.stub(process, 'platform').value('win32'); 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.strictEqual(await cache.get(), SystemTraySetting.Uninitialized);
assert(configGetStub.calledOnceWith('system-tray-setting')); assert(configGetStub.calledOnceWith('system-tray-setting'));
assert( 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 () => { it('returns the cached preference if system tray is supported and something is stored', async () => {
sandbox.stub(process, 'platform').value('win32'); sandbox.stub(process, 'platform').value('win32');
configGetStub.returns('MinimizeToSystemTray'); configGetStub.returns('MinimizeToSystemTray');
const cache = new SystemTraySettingCache(sql, config, [], '1.2.3'); const cache = new SystemTraySettingCache(config, [], '1.2.3');
assert.strictEqual( assert.strictEqual(
await cache.get(), await cache.get(),
SystemTraySetting.MinimizeToSystemTray SystemTraySetting.MinimizeToSystemTray
); );
assert(configGetStub.calledOnceWith('system-tray-setting')); 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 () => { it('returns DoNotUseSystemTray if system tray is unsupported and there are no CLI flags', async () => {
sandbox.stub(process, 'platform').value('darwin'); 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); assert.strictEqual(await cache.get(), SystemTraySetting.DoNotUseSystemTray);
sinon.assert.notCalled(configGetStub); sinon.assert.notCalled(configGetStub);
sinon.assert.notCalled(configSetStub); 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 { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode';
import type { RetryItemType } from '../util/retryPlaceholders'; import type { RetryItemType } from '../util/retryPlaceholders';
import type { ConfigMapType as RemoteConfigType } from '../RemoteConfig'; import type { ConfigMapType as RemoteConfigType } from '../RemoteConfig';
import type { SystemTraySetting } from './SystemTraySetting';
import type { ExtendedStorageID, UnknownRecord } from './StorageService.d'; import type { ExtendedStorageID, UnknownRecord } from './StorageService.d';
import type { GroupCredentialType } from '../textsecure/WebAPI'; import type { GroupCredentialType } from '../textsecure/WebAPI';
@ -19,7 +18,6 @@ import type {
SessionResetsType, SessionResetsType,
StorageServiceCredentials, StorageServiceCredentials,
} from '../textsecure/Types.d'; } from '../textsecure/Types.d';
import type { ThemeSettingType } from './StorageUIKeys';
import type { ServiceIdString } from './ServiceId'; import type { ServiceIdString } from './ServiceId';
import type { RegisteredChallengeType } from '../challenge'; import type { RegisteredChallengeType } from '../challenge';
@ -57,14 +55,10 @@ export type StorageAccessType = {
'call-system-notification': boolean; 'call-system-notification': boolean;
'hide-menu-bar': boolean; 'hide-menu-bar': boolean;
'incoming-call-notification': boolean; 'incoming-call-notification': boolean;
localeOverride: string | null;
'notification-draw-attention': boolean; 'notification-draw-attention': boolean;
'notification-setting': NotificationSettingType; 'notification-setting': NotificationSettingType;
'read-receipt-setting': boolean; 'read-receipt-setting': boolean;
'sent-media-quality': SentMediaQualitySettingType; 'sent-media-quality': SentMediaQualitySettingType;
'spell-check': boolean;
'system-tray-setting': SystemTraySetting;
'theme-setting': ThemeSettingType;
audioMessage: boolean; audioMessage: boolean;
attachmentMigration_isComplete: boolean; attachmentMigration_isComplete: boolean;
attachmentMigration_lastProcessedIndex: number; attachmentMigration_lastProcessedIndex: number;

View file

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

View file

@ -14,8 +14,6 @@ import type {
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors'; import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
import * as Errors from '../types/errors'; import * as Errors from '../types/errors';
import * as Stickers from '../types/Stickers'; 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 { ConversationType } from '../state/ducks/conversations';
import type { AuthorizeArtCreatorDataType } from '../state/ducks/globalModals'; import type { AuthorizeArtCreatorDataType } from '../state/ducks/globalModals';
@ -45,9 +43,15 @@ import { fromWebSafeBase64 } from './webSafeBase64';
import { getConversation } from './getConversation'; import { getConversation } from './getConversation';
import { instance, PhoneNumberFormat } from './libphonenumberInstance'; import { instance, PhoneNumberFormat } from './libphonenumberInstance';
import { showConfirmationDialog } from './showConfirmationDialog'; 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 SentMediaQualityType = 'standard' | 'high';
type ThemeType = 'light' | 'dark' | 'system';
type NotificationSettingType = 'message' | 'name' | 'count' | 'off'; type NotificationSettingType = 'message' | 'name' | 'count' | 'off';
export type IPCEventsValuesType = { export type IPCEventsValuesType = {
@ -64,17 +68,13 @@ export type IPCEventsValuesType = {
hideMenuBar: boolean | undefined; hideMenuBar: boolean | undefined;
incomingCallNotification: boolean; incomingCallNotification: boolean;
lastSyncTime: number | undefined; lastSyncTime: number | undefined;
localeOverride: string | null;
notificationDrawAttention: boolean; notificationDrawAttention: boolean;
notificationSetting: NotificationSettingType; notificationSetting: NotificationSettingType;
preferredAudioInputDevice: AudioDevice | undefined; preferredAudioInputDevice: AudioDevice | undefined;
preferredAudioOutputDevice: AudioDevice | undefined; preferredAudioOutputDevice: AudioDevice | undefined;
preferredVideoInputDevice: string | undefined; preferredVideoInputDevice: string | undefined;
sentMediaQualitySetting: SentMediaQualityType; sentMediaQualitySetting: SentMediaQualityType;
spellCheck: boolean;
systemTraySetting: SystemTraySetting;
textFormatting: boolean; textFormatting: boolean;
themeSetting: ThemeType;
universalExpireTimer: DurationInSeconds; universalExpireTimer: DurationInSeconds;
zoomFactor: ZoomFactorType; zoomFactor: ZoomFactorType;
storyViewReceiptsEnabled: boolean; storyViewReceiptsEnabled: boolean;
@ -144,17 +144,21 @@ export type IPCEventsCallbacksType = {
}; };
type ValuesWithGetters = Omit< type ValuesWithGetters = Omit<
IPCEventsValuesType, SettingsValuesType,
// Async // Async
| 'zoomFactor' | 'zoomFactor'
| 'localeOverride'
| 'spellCheck'
| 'themeSetting'
// Optional // Optional
| 'mediaPermissions' | 'mediaPermissions'
| 'mediaCameraPermissions' | 'mediaCameraPermissions'
| 'autoLaunch' | 'autoLaunch'
| 'systemTraySetting'
>; >;
type ValuesWithSetters = Omit< type ValuesWithSetters = Omit<
IPCEventsValuesType, SettingsValuesType,
| 'blockedCount' | 'blockedCount'
| 'defaultConversationColor' | 'defaultConversationColor'
| 'linkPreviewSetting' | 'linkPreviewSetting'
@ -166,19 +170,37 @@ type ValuesWithSetters = Omit<
// Optional // Optional
| 'mediaPermissions' | 'mediaPermissions'
| 'mediaCameraPermissions' | '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>}`; `get${Capitalize<Key>}`;
export type IPCEventSetterType<Key extends keyof IPCEventsValuesType> = export type IPCEventSetterType<Key extends keyof SettingsValuesType> =
`set${Capitalize<Key>}`; `set${Capitalize<Key>}`;
export type IPCEventUpdaterType<Key extends keyof SettingsValuesType> =
`update${Capitalize<Key>}`;
export type IPCEventsGettersType = { export type IPCEventsGettersType = {
[Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key]; [Key in keyof ValuesWithGetters as IPCEventGetterType<Key>]: () => ValuesWithGetters[Key];
} & { } & {
// Async // Async
getZoomFactor: () => Promise<ZoomFactorType>; getZoomFactor: () => Promise<ZoomFactorType>;
getLocaleOverride: () => Promise<string | null>;
getSpellCheck: () => Promise<boolean>;
getSystemTraySetting: () => Promise<SystemTraySetting>;
getThemeSetting: () => Promise<ThemeType>;
// Events // Events
onZoomFactorChange: (callback: (zoomFactor: ZoomFactorType) => void) => void; onZoomFactorChange: (callback: (zoomFactor: ZoomFactorType) => void) => void;
// Optional // Optional
@ -198,6 +220,7 @@ export type IPCEventsSettersType = {
export type IPCEventsType = IPCEventsGettersType & export type IPCEventsType = IPCEventsGettersType &
IPCEventsSettersType & IPCEventsSettersType &
IPCEventsUpdatersType &
IPCEventsCallbacksType; IPCEventsCallbacksType;
export function createIPCEvents( export function createIPCEvents(
@ -393,11 +416,14 @@ export function createIPCEvents(
window.storage.get('sent-media-quality', 'standard'), window.storage.get('sent-media-quality', 'standard'),
setSentMediaQualitySetting: value => setSentMediaQualitySetting: value =>
window.storage.put('sent-media-quality', value), window.storage.put('sent-media-quality', value),
getThemeSetting: () => window.storage.get('theme-setting', 'system'), getThemeSetting: async () => {
setThemeSetting: value => { return getEphemeralSetting('themeSetting') ?? null;
const promise = window.storage.put('theme-setting', value); },
themeChanged(); setThemeSetting: async value => {
return promise; drop(setEphemeralSetting('themeSetting', value));
},
updateThemeSetting: _theme => {
drop(themeChanged());
}, },
getHideMenuBar: () => window.storage.get('hide-menu-bar'), getHideMenuBar: () => window.storage.get('hide-menu-bar'),
setHideMenuBar: value => { setHideMenuBar: value => {
@ -406,19 +432,9 @@ export function createIPCEvents(
window.IPC.setMenuBarVisibility(!value); window.IPC.setMenuBarVisibility(!value);
return promise; return promise;
}, },
getSystemTraySetting: () => getSystemTraySetting: () => getEphemeralSetting('systemTraySetting'),
parseSystemTraySetting(window.storage.get('system-tray-setting')), getLocaleOverride: async () => {
setSystemTraySetting: value => { return getEphemeralSetting('localeOverride') ?? null;
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);
}, },
getNotificationSetting: () => getNotificationSetting: () =>
window.storage.get('notification-setting', 'message'), window.storage.get('notification-setting', 'message'),
@ -456,8 +472,9 @@ export function createIPCEvents(
setIncomingCallNotification: value => setIncomingCallNotification: value =>
window.storage.put('incoming-call-notification', value), window.storage.put('incoming-call-notification', value),
getSpellCheck: () => window.storage.get('spell-check', true), getSpellCheck: () => {
setSpellCheck: value => window.storage.put('spell-check', value), return getEphemeralSetting('spellCheck');
},
getTextFormatting: () => window.storage.get('textFormatting', true), getTextFormatting: () => window.storage.get('textFormatting', true),
setTextFormatting: value => window.storage.put('textFormatting', value), setTextFormatting: value => window.storage.put('textFormatting', value),
@ -709,3 +726,16 @@ function showUnknownSgnlLinkModal(): void {
description: window.i18n('icu:unknown-sgnl-link'), 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'; import { ThemeType } from '../types/Util';
export function getThemeType(): ThemeType { export async function getThemeType(): Promise<ThemeType> {
const themeSetting = window.Events.getThemeSetting(); const themeSetting = await window.Events.getThemeSetting();
if (themeSetting === 'light') { if (themeSetting === 'light') {
return ThemeType.light; return ThemeType.light;

View file

@ -9,9 +9,8 @@ import type { UnwrapPromise } from '../types/Util';
import type { import type {
IPCEventsValuesType, IPCEventsValuesType,
IPCEventsCallbacksType, IPCEventsCallbacksType,
IPCEventGetterType,
IPCEventSetterType,
} from './createIPCEvents'; } from './createIPCEvents';
import type { SystemTraySetting } from '../types/SystemTraySetting';
type SettingOptionsType = { type SettingOptionsType = {
getter?: boolean; getter?: boolean;
@ -23,7 +22,27 @@ export type SettingType<Value> = Readonly<{
setValue: (value: Value) => Promise<Value>; 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 name: Name
): Capitalize<Name> { ): Capitalize<Name> {
const result = name.slice(0, 1).toUpperCase() + name.slice(1); 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>; return result as Capitalize<Name>;
} }
function getSetterName<Key extends keyof IPCEventsValuesType>( function getSetterName<Key extends keyof SettingsValuesType>(
name: Key name: Key
): IPCEventSetterType<Key> { ): SettingSetterType<Key> {
return `set${capitalize(name)}`; return `set${capitalize(name)}`;
} }
function getGetterName<Key extends keyof IPCEventsValuesType>( function getGetterName<Key extends keyof SettingsValuesType>(
name: Key name: Key
): IPCEventGetterType<Key> { ): SettingGetterType<Key> {
return `get${capitalize(name)}`; return `get${capitalize(name)}`;
} }
function getUpdaterName<Key extends keyof EphemeralSettings>(
name: Key
): SettingUpdaterType<Key> {
return `update${capitalize(name)}`;
}
export function createSetting< export function createSetting<
Name extends keyof IPCEventsValuesType, Name extends keyof SettingsValuesType,
Value extends IPCEventsValuesType[Name] Value extends SettingsValuesType[Name]
>(name: Name, overrideOptions: SettingOptionsType = {}): SettingType<Value> { >(name: Name, overrideOptions: SettingOptionsType = {}): SettingType<Value> {
const options = { const options = {
getter: true, getter: true,
@ -86,7 +111,7 @@ export function createCallback<
} }
export function installSetting( export function installSetting(
name: keyof IPCEventsValuesType, name: keyof SettingsValuesType,
{ getter = true, setter = true }: { getter?: boolean; setter?: boolean } = {} { getter = true, setter = true }: { getter?: boolean; setter?: boolean } = {}
): void { ): void {
const getterName = getGetterName(name); 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>( export function installCallback<Name extends keyof IPCEventsCallbacksType>(
name: Name name: Name
): void { ): 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 SyncRequest from './textsecure/SyncRequest';
import type { MessageCache } from './services/MessageCache'; import type { MessageCache } from './services/MessageCache';
import type { StateType } from './state/reducer'; import type { StateType } from './state/reducer';
import type { SystemTraySetting } from './types/SystemTraySetting';
import type { Address } from './types/Address'; import type { Address } from './types/Address';
import type { QualifiedAddress } from './types/QualifiedAddress'; import type { QualifiedAddress } from './types/QualifiedAddress';
import type { CIType } from './CI'; import type { CIType } from './CI';
@ -88,7 +87,6 @@ export type IPCType = {
showWindowsNotification: (data: WindowsNotificationData) => Promise<void>; showWindowsNotification: (data: WindowsNotificationData) => Promise<void>;
shutdown: () => void; shutdown: () => void;
titleBarDoubleClick: () => void; titleBarDoubleClick: () => void;
updateSystemTraySetting: (value: SystemTraySetting) => void;
updateTrayIcon: (count: number) => void; updateTrayIcon: (count: number) => void;
}; };

View file

@ -5,11 +5,10 @@ import { ipcRenderer } from 'electron';
import type { MenuItemConstructorOptions } from 'electron'; import type { MenuItemConstructorOptions } from 'electron';
import type { MenuOptionsType } from '../types/menu'; import type { MenuOptionsType } from '../types/menu';
import type { IPCEventsValuesType } from '../util/createIPCEvents';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import type { LoggerType } from '../types/Logging'; import type { LoggerType } from '../types/Logging';
import type { NativeThemeType } from '../context/createNativeThemeListener'; 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 type { RendererConfigType } from '../types/RendererConfig';
import { Bytes } from '../context/Bytes'; import { Bytes } from '../context/Bytes';
@ -58,7 +57,7 @@ export type MinimalSignalContextType = {
nativeThemeListener: NativeThemeType; nativeThemeListener: NativeThemeType;
restartApp: () => void; restartApp: () => void;
Settings: { Settings: {
themeSetting: SettingType<IPCEventsValuesType['themeSetting']>; themeSetting: SettingType<SettingsValuesType['themeSetting']>;
waitForChange: () => Promise<void>; waitForChange: () => Promise<void>;
}; };
OS: { OS: {

View file

@ -127,11 +127,6 @@ const IPC: IPCType = {
titleBarDoubleClick: () => { titleBarDoubleClick: () => {
ipc.send('title-bar-double-click'); 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), 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 { initializeMessageCounter } from '../../util/incrementMessageCounter';
import { initializeRedux } from '../../state/initializeRedux'; import { initializeRedux } from '../../state/initializeRedux';
import * as Stickers from '../../types/Stickers'; import * as Stickers from '../../types/Stickers';
import { ThemeType } from '../../types/Util';
window.assert = assert; window.assert = assert;
@ -51,6 +52,7 @@ window.testUtilities = {
}, },
stories: [], stories: [],
storyDistributionLists: [], storyDistributionLists: [],
theme: ThemeType.dark,
}); });
}, },

View file

@ -1,7 +1,11 @@
// Copyright 2017 Signal Messenger, LLC // Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { installCallback, installSetting } from '../util/preload'; import {
installCallback,
installSetting,
installEphemeralSetting,
} from '../util/preload';
// ChatColorPicker redux hookups // ChatColorPicker redux hookups
installCallback('getCustomColors'); installCallback('getCustomColors');
@ -48,14 +52,10 @@ installSetting('hasStoriesDisabled');
installSetting('hideMenuBar'); installSetting('hideMenuBar');
installSetting('incomingCallNotification'); installSetting('incomingCallNotification');
installSetting('lastSyncTime'); installSetting('lastSyncTime');
installSetting('localeOverride');
installSetting('notificationDrawAttention'); installSetting('notificationDrawAttention');
installSetting('notificationSetting'); installSetting('notificationSetting');
installSetting('spellCheck');
installSetting('systemTraySetting');
installSetting('sentMediaQualitySetting'); installSetting('sentMediaQualitySetting');
installSetting('textFormatting'); installSetting('textFormatting');
installSetting('themeSetting');
installSetting('universalExpireTimer'); installSetting('universalExpireTimer');
installSetting('zoomFactor'); installSetting('zoomFactor');
installSetting('phoneNumberDiscoverabilitySetting'); installSetting('phoneNumberDiscoverabilitySetting');
@ -66,3 +66,8 @@ installCallback('getAvailableIODevices');
installSetting('preferredAudioInputDevice'); installSetting('preferredAudioInputDevice');
installSetting('preferredAudioOutputDevice'); installSetting('preferredAudioOutputDevice');
installSetting('preferredVideoInputDevice'); installSetting('preferredVideoInputDevice');
installEphemeralSetting('themeSetting');
installEphemeralSetting('systemTraySetting');
installEphemeralSetting('localeOverride');
installEphemeralSetting('spellCheck');