Add ZoomFactorService to keep windows in sync
This commit is contained in:
parent
5354b23d08
commit
95842c6e0b
10 changed files with 223 additions and 101 deletions
116
app/main.ts
116
app/main.ts
|
@ -117,6 +117,7 @@ import { HourCyclePreference } from '../ts/types/I18N';
|
||||||
import { DBVersionFromFutureError } from '../ts/sql/migrations';
|
import { DBVersionFromFutureError } from '../ts/sql/migrations';
|
||||||
import type { ParsedSignalRoute } from '../ts/util/signalRoutes';
|
import type { ParsedSignalRoute } from '../ts/util/signalRoutes';
|
||||||
import { parseSignalRoute } from '../ts/util/signalRoutes';
|
import { parseSignalRoute } from '../ts/util/signalRoutes';
|
||||||
|
import { ZoomFactorService } from '../ts/services/ZoomFactorService';
|
||||||
|
|
||||||
const STICKER_CREATOR_PARTITION = 'sticker-creator';
|
const STICKER_CREATOR_PARTITION = 'sticker-creator';
|
||||||
|
|
||||||
|
@ -200,6 +201,7 @@ const defaultWebPrefs = {
|
||||||
'CSSPseudoDir', // status=experimental, needed for RTL (ex: :dir(rtl))
|
'CSSPseudoDir', // status=experimental, needed for RTL (ex: :dir(rtl))
|
||||||
'CSSLogical', // status=experimental, needed for RTL (ex: margin-inline-start)
|
'CSSLogical', // status=experimental, needed for RTL (ex: margin-inline-start)
|
||||||
].join(','),
|
].join(','),
|
||||||
|
enablePreferredSizeMode: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DISABLE_GPU =
|
const DISABLE_GPU =
|
||||||
|
@ -394,6 +396,22 @@ async function getLocaleOverrideSetting(): Promise<string | null> {
|
||||||
return slowValue;
|
return slowValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const zoomFactorService = new ZoomFactorService({
|
||||||
|
async getZoomFactorSetting() {
|
||||||
|
const item = await sql.sqlCall('getItemById', 'zoomFactor');
|
||||||
|
if (typeof item?.value !== 'number') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return item.value;
|
||||||
|
},
|
||||||
|
async setZoomFactorSetting(zoomFactor) {
|
||||||
|
await sql.sqlCall('createOrUpdateItem', {
|
||||||
|
id: 'zoomFactor',
|
||||||
|
value: zoomFactor,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let systemTrayService: SystemTrayService | undefined;
|
let systemTrayService: SystemTrayService | undefined;
|
||||||
const systemTraySettingCache = new SystemTraySettingCache(
|
const systemTraySettingCache = new SystemTraySettingCache(
|
||||||
sql,
|
sql,
|
||||||
|
@ -523,7 +541,7 @@ async function handleUrl(rawTarget: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCommonWindowEvents(
|
async function handleCommonWindowEvents(
|
||||||
window: BrowserWindow,
|
window: BrowserWindow,
|
||||||
titleBarOverlay: TitleBarOverlayOptions | false = false
|
titleBarOverlay: TitleBarOverlayOptions | false = false
|
||||||
) {
|
) {
|
||||||
|
@ -557,54 +575,7 @@ function handleCommonWindowEvents(
|
||||||
const focusInterval = setInterval(setWindowFocus, 10000);
|
const focusInterval = setInterval(setWindowFocus, 10000);
|
||||||
window.on('closed', () => clearInterval(focusInterval));
|
window.on('closed', () => clearInterval(focusInterval));
|
||||||
|
|
||||||
// Works only for mainWindow and settings because they have `enablePreferredSizeMode`
|
await zoomFactorService.syncWindow(window);
|
||||||
let lastZoomFactor = window.webContents.getZoomFactor();
|
|
||||||
const onZoomChanged = () => {
|
|
||||||
if (
|
|
||||||
window.isDestroyed() ||
|
|
||||||
!window.webContents ||
|
|
||||||
window.webContents.isDestroyed()
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const zoomFactor = window.webContents.getZoomFactor();
|
|
||||||
if (lastZoomFactor === zoomFactor) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastZoomFactor = zoomFactor;
|
|
||||||
if (!mainWindow) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window === mainWindow) {
|
|
||||||
drop(
|
|
||||||
settingsChannel?.invokeCallbackInMainWindow('persistZoomFactor', [
|
|
||||||
zoomFactor,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
mainWindow.webContents.setZoomFactor(zoomFactor);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.on('show', () => {
|
|
||||||
// Install handler here after we init zoomFactor otherwise an initial
|
|
||||||
// preferred-size-changed event emits with an undesired zoomFactor.
|
|
||||||
window.webContents.on('preferred-size-changed', onZoomChanged);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Workaround to apply zoomFactor because webPreferences does not handle it
|
|
||||||
// https://github.com/electron/electron/issues/10572
|
|
||||||
// But main window emits ready-to-show before window.Events is available
|
|
||||||
// so set main window zoom in background.ts
|
|
||||||
if (window !== mainWindow) {
|
|
||||||
window.once('ready-to-show', async () => {
|
|
||||||
const zoomFactor =
|
|
||||||
(await settingsChannel?.getSettingFromMainWindow('zoomFactor')) ?? 1;
|
|
||||||
window.webContents.setZoomFactor(zoomFactor);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
nativeThemeNotifier.addWindow(window);
|
nativeThemeNotifier.addWindow(window);
|
||||||
|
|
||||||
|
@ -788,7 +759,6 @@ async function createWindow() {
|
||||||
),
|
),
|
||||||
spellcheck: await getSpellCheckSetting(),
|
spellcheck: await getSpellCheckSetting(),
|
||||||
backgroundThrottling: true,
|
backgroundThrottling: true,
|
||||||
enablePreferredSizeMode: true,
|
|
||||||
disableBlinkFeatures: 'Accelerated2dCanvas,AcceleratedSmallCanvases',
|
disableBlinkFeatures: 'Accelerated2dCanvas,AcceleratedSmallCanvases',
|
||||||
},
|
},
|
||||||
icon: windowIcon,
|
icon: windowIcon,
|
||||||
|
@ -917,7 +887,7 @@ async function createWindow() {
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCommonWindowEvents(mainWindow, titleBarOverlay);
|
await handleCommonWindowEvents(mainWindow, titleBarOverlay);
|
||||||
|
|
||||||
// App dock icon bounce
|
// App dock icon bounce
|
||||||
bounce.init(mainWindow);
|
bounce.init(mainWindow);
|
||||||
|
@ -1293,7 +1263,7 @@ async function showScreenShareWindow(sourceName: string) {
|
||||||
|
|
||||||
screenShareWindow = new BrowserWindow(options);
|
screenShareWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(screenShareWindow);
|
await handleCommonWindowEvents(screenShareWindow);
|
||||||
|
|
||||||
screenShareWindow.on('closed', () => {
|
screenShareWindow.on('closed', () => {
|
||||||
screenShareWindow = undefined;
|
screenShareWindow = undefined;
|
||||||
|
@ -1343,7 +1313,7 @@ async function showAbout() {
|
||||||
|
|
||||||
aboutWindow = new BrowserWindow(options);
|
aboutWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(aboutWindow, titleBarOverlay);
|
await handleCommonWindowEvents(aboutWindow, titleBarOverlay);
|
||||||
|
|
||||||
aboutWindow.on('closed', () => {
|
aboutWindow.on('closed', () => {
|
||||||
aboutWindow = undefined;
|
aboutWindow = undefined;
|
||||||
|
@ -1389,13 +1359,12 @@ async function showSettingsWindow() {
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
preload: join(__dirname, '../bundles/settings/preload.js'),
|
preload: join(__dirname, '../bundles/settings/preload.js'),
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: true,
|
||||||
enablePreferredSizeMode: true,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
settingsWindow = new BrowserWindow(options);
|
settingsWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(settingsWindow, titleBarOverlay);
|
await handleCommonWindowEvents(settingsWindow, titleBarOverlay);
|
||||||
|
|
||||||
settingsWindow.on('closed', () => {
|
settingsWindow.on('closed', () => {
|
||||||
settingsWindow = undefined;
|
settingsWindow = undefined;
|
||||||
|
@ -1490,7 +1459,7 @@ async function showDebugLogWindow() {
|
||||||
|
|
||||||
debugLogWindow = new BrowserWindow(options);
|
debugLogWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(debugLogWindow, titleBarOverlay);
|
await handleCommonWindowEvents(debugLogWindow, titleBarOverlay);
|
||||||
|
|
||||||
debugLogWindow.on('closed', () => {
|
debugLogWindow.on('closed', () => {
|
||||||
debugLogWindow = undefined;
|
debugLogWindow = undefined;
|
||||||
|
@ -1550,7 +1519,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
|
||||||
|
|
||||||
permissionsPopupWindow = new BrowserWindow(options);
|
permissionsPopupWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(permissionsPopupWindow);
|
await handleCommonWindowEvents(permissionsPopupWindow);
|
||||||
|
|
||||||
permissionsPopupWindow.on('closed', () => {
|
permissionsPopupWindow.on('closed', () => {
|
||||||
removeDarkOverlay();
|
removeDarkOverlay();
|
||||||
|
@ -2122,6 +2091,9 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
|
||||||
showKeyboardShortcuts,
|
showKeyboardShortcuts,
|
||||||
showSettings: showSettingsWindow,
|
showSettings: showSettingsWindow,
|
||||||
showWindow,
|
showWindow,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
zoomReset,
|
||||||
|
|
||||||
// overrides
|
// overrides
|
||||||
...options,
|
...options,
|
||||||
|
@ -2815,16 +2787,6 @@ ipc.handle('executeMenuRole', async ({ sender }, untypedRole) => {
|
||||||
sender.toggleDevTools();
|
sender.toggleDevTools();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'resetZoom':
|
|
||||||
sender.setZoomLevel(0);
|
|
||||||
break;
|
|
||||||
case 'zoomIn':
|
|
||||||
sender.setZoomLevel(sender.getZoomLevel() + 1);
|
|
||||||
break;
|
|
||||||
case 'zoomOut':
|
|
||||||
sender.setZoomLevel(sender.getZoomLevel() - 1);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'togglefullscreen':
|
case 'togglefullscreen':
|
||||||
senderWindow?.setFullScreen(!senderWindow?.isFullScreen());
|
senderWindow?.setFullScreen(!senderWindow?.isFullScreen());
|
||||||
break;
|
break;
|
||||||
|
@ -2862,6 +2824,18 @@ ipc.handle('getMenuOptions', async () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function zoomIn() {
|
||||||
|
await zoomFactorService.zoomIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function zoomOut() {
|
||||||
|
await zoomFactorService.zoomOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function zoomReset() {
|
||||||
|
await zoomFactorService.zoomReset();
|
||||||
|
}
|
||||||
|
|
||||||
ipc.handle('executeMenuAction', async (_event, action: MenuActionType) => {
|
ipc.handle('executeMenuAction', async (_event, action: MenuActionType) => {
|
||||||
if (action === 'forceUpdate') {
|
if (action === 'forceUpdate') {
|
||||||
drop(forceUpdate());
|
drop(forceUpdate());
|
||||||
|
@ -2891,6 +2865,12 @@ ipc.handle('executeMenuAction', async (_event, action: MenuActionType) => {
|
||||||
drop(showSettingsWindow());
|
drop(showSettingsWindow());
|
||||||
} else if (action === 'showWindow') {
|
} else if (action === 'showWindow') {
|
||||||
showWindow();
|
showWindow();
|
||||||
|
} else if (action === 'zoomIn') {
|
||||||
|
drop(zoomIn());
|
||||||
|
} else if (action === 'zoomOut') {
|
||||||
|
drop(zoomOut());
|
||||||
|
} else if (action === 'zoomReset') {
|
||||||
|
drop(zoomReset());
|
||||||
} else {
|
} else {
|
||||||
throw missingCaseError(action);
|
throw missingCaseError(action);
|
||||||
}
|
}
|
||||||
|
@ -2940,7 +2920,7 @@ async function showStickerCreatorWindow() {
|
||||||
|
|
||||||
stickerCreatorWindow = new BrowserWindow(options);
|
stickerCreatorWindow = new BrowserWindow(options);
|
||||||
|
|
||||||
handleCommonWindowEvents(stickerCreatorWindow);
|
await handleCommonWindowEvents(stickerCreatorWindow);
|
||||||
|
|
||||||
stickerCreatorWindow.once('ready-to-show', () => {
|
stickerCreatorWindow.once('ready-to-show', () => {
|
||||||
stickerCreatorWindow?.show();
|
stickerCreatorWindow?.show();
|
||||||
|
|
11
app/menu.ts
11
app/menu.ts
|
@ -38,6 +38,9 @@ export const createTemplate = (
|
||||||
showKeyboardShortcuts,
|
showKeyboardShortcuts,
|
||||||
showSettings,
|
showSettings,
|
||||||
openArtCreator,
|
openArtCreator,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
zoomReset,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const template: MenuListType = [
|
const template: MenuListType = [
|
||||||
|
@ -106,17 +109,19 @@ export const createTemplate = (
|
||||||
label: i18n('icu:mainMenuView'),
|
label: i18n('icu:mainMenuView'),
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
role: 'resetZoom',
|
accelerator: 'CmdOrCtrl+0',
|
||||||
label: i18n('icu:viewMenuResetZoom'),
|
label: i18n('icu:viewMenuResetZoom'),
|
||||||
|
click: zoomReset,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accelerator: 'CmdOrCtrl+=',
|
accelerator: 'CmdOrCtrl+=',
|
||||||
role: 'zoomIn',
|
|
||||||
label: i18n('icu:viewMenuZoomIn'),
|
label: i18n('icu:viewMenuZoomIn'),
|
||||||
|
click: zoomIn,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: 'zoomOut',
|
accelerator: 'CmdOrCtrl+-',
|
||||||
label: i18n('icu:viewMenuZoomOut'),
|
label: i18n('icu:viewMenuZoomOut'),
|
||||||
|
click: zoomOut,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: 'separator',
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { webFrame } from 'electron';
|
|
||||||
import { isNumber, throttle, groupBy } from 'lodash';
|
import { isNumber, throttle, groupBy } from 'lodash';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import { batch as batchDispatch } from 'react-redux';
|
import { batch as batchDispatch } from 'react-redux';
|
||||||
|
@ -819,14 +818,13 @@ export async function startApp(): Promise<void> {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const zoomFactor = window.Events.getZoomFactor();
|
const zoomFactor = await window.Events.getZoomFactor();
|
||||||
webFrame.setZoomFactor(zoomFactor);
|
|
||||||
document.body.style.setProperty('--zoom-factor', zoomFactor.toString());
|
document.body.style.setProperty('--zoom-factor', zoomFactor.toString());
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.Events.onZoomFactorChange(newZoomFactor => {
|
||||||
document.body.style.setProperty(
|
document.body.style.setProperty(
|
||||||
'--zoom-factor',
|
'--zoom-factor',
|
||||||
webFrame.getZoomFactor().toString()
|
newZoomFactor.toString()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -238,6 +238,9 @@ export function TitleBarContainer(props: PropsType): JSX.Element {
|
||||||
showKeyboardShortcuts: () => executeMenuAction('showKeyboardShortcuts'),
|
showKeyboardShortcuts: () => executeMenuAction('showKeyboardShortcuts'),
|
||||||
showSettings: () => executeMenuAction('showSettings'),
|
showSettings: () => executeMenuAction('showSettings'),
|
||||||
showWindow: () => executeMenuAction('showWindow'),
|
showWindow: () => executeMenuAction('showWindow'),
|
||||||
|
zoomIn: () => executeMenuAction('zoomIn'),
|
||||||
|
zoomOut: () => executeMenuAction('zoomOut'),
|
||||||
|
zoomReset: () => executeMenuAction('zoomReset'),
|
||||||
},
|
},
|
||||||
i18n
|
i18n
|
||||||
);
|
);
|
||||||
|
|
123
ts/services/ZoomFactorService.ts
Normal file
123
ts/services/ZoomFactorService.ts
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright 2023 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
import type { BrowserWindow } from 'electron';
|
||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
|
const DEFAULT_ZOOM_FACTOR = 1.0;
|
||||||
|
|
||||||
|
// https://chromium.googlesource.com/chromium/src/+/938b37a6d2886bf8335fc7db792f1eb46c65b2ae/third_party/blink/common/page/page_zoom.cc
|
||||||
|
const ZOOM_LEVEL_MULTIPLIER_RATIO = 1.2;
|
||||||
|
|
||||||
|
function zoomLevelToZoomFactor(zoomLevel: number): number {
|
||||||
|
return ZOOM_LEVEL_MULTIPLIER_RATIO ** zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomFactorToZoomLevel(zoomFactor: number) {
|
||||||
|
return Math.log(zoomFactor) / Math.log(ZOOM_LEVEL_MULTIPLIER_RATIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
function zoomFactorEquals(a: number, b: number): boolean {
|
||||||
|
return Math.abs(a - b) <= 0.001;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ZoomFactorServiceConfig = Readonly<{
|
||||||
|
getZoomFactorSetting: () => Promise<number | null>;
|
||||||
|
setZoomFactorSetting: (zoomFactor: number) => Promise<void>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class ZoomFactorService extends EventEmitter {
|
||||||
|
#config: ZoomFactorServiceConfig;
|
||||||
|
#cachedZoomFactor: number | null = null;
|
||||||
|
|
||||||
|
constructor(config: ZoomFactorServiceConfig) {
|
||||||
|
super();
|
||||||
|
this.#config = config;
|
||||||
|
ipcMain.handle('getZoomFactor', () => {
|
||||||
|
return this.getZoomFactor();
|
||||||
|
});
|
||||||
|
ipcMain.on('setZoomFactor', (_event, zoomFactor) => {
|
||||||
|
return this.setZoomFactor(zoomFactor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZoomFactor(): Promise<number> {
|
||||||
|
if (this.#cachedZoomFactor != null) {
|
||||||
|
return this.#cachedZoomFactor;
|
||||||
|
}
|
||||||
|
const zoomFactorSetting = await this.#config.getZoomFactorSetting();
|
||||||
|
const zoomFactor = zoomFactorSetting ?? DEFAULT_ZOOM_FACTOR;
|
||||||
|
this.#cachedZoomFactor = zoomFactor;
|
||||||
|
return zoomFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getZoomLevel(): Promise<number> {
|
||||||
|
const zoomFactor = await this.getZoomFactor();
|
||||||
|
return zoomFactorToZoomLevel(zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setZoomFactor(zoomFactor: number): Promise<void> {
|
||||||
|
if (
|
||||||
|
this.#cachedZoomFactor != null &&
|
||||||
|
zoomFactorEquals(this.#cachedZoomFactor, zoomFactor)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#cachedZoomFactor = zoomFactor;
|
||||||
|
await this.#config.setZoomFactorSetting(zoomFactor);
|
||||||
|
this.emit('zoomFactorChanged', zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setZoomLevel(zoomLevel: number): Promise<void> {
|
||||||
|
const zoomFactor = zoomLevelToZoomFactor(zoomLevel);
|
||||||
|
await this.setZoomFactor(zoomFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async zoomIn(): Promise<void> {
|
||||||
|
const zoomLevel = await this.getZoomLevel();
|
||||||
|
await this.setZoomLevel(zoomLevel + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async zoomOut(): Promise<void> {
|
||||||
|
const zoomLevel = await this.getZoomLevel();
|
||||||
|
await this.setZoomLevel(zoomLevel - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async zoomReset(): Promise<void> {
|
||||||
|
await this.setZoomLevel(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call this after creating a new window before you show it
|
||||||
|
async syncWindow(window: BrowserWindow): Promise<void> {
|
||||||
|
const onWindowChange = async () => {
|
||||||
|
const zoomFactor = window.webContents.getZoomFactor();
|
||||||
|
await this.setZoomFactor(zoomFactor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onServiceChange = (zoomFactor: number) => {
|
||||||
|
window.webContents.setZoomFactor(zoomFactor);
|
||||||
|
window.webContents.send('zoomFactorChanged', zoomFactor);
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialZoomFactor = await this.getZoomFactor();
|
||||||
|
window.once('ready-to-show', () => {
|
||||||
|
// Workaround to apply zoomFactor because webPreferences does not handle it
|
||||||
|
// https://github.com/electron/electron/issues/10572
|
||||||
|
window.webContents.setZoomFactor(initialZoomFactor);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.once('show', async () => {
|
||||||
|
// Install handler here after we init zoomFactor otherwise an initial
|
||||||
|
// preferred-size-changed event emits with an undesired zoomFactor.
|
||||||
|
window.webContents.on('preferred-size-changed', onWindowChange);
|
||||||
|
window.webContents.on('zoom-changed', onWindowChange);
|
||||||
|
this.on('zoomFactorChanged', onServiceChange);
|
||||||
|
});
|
||||||
|
|
||||||
|
window.on('close', () => {
|
||||||
|
window.webContents.off('preferred-size-changed', onWindowChange);
|
||||||
|
window.webContents.off('zoom-changed', onWindowChange);
|
||||||
|
this.off('zoomFactorChanged', onServiceChange);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,9 @@ const showDebugLog = stub();
|
||||||
const showKeyboardShortcuts = stub();
|
const showKeyboardShortcuts = stub();
|
||||||
const showSettings = stub();
|
const showSettings = stub();
|
||||||
const showWindow = stub();
|
const showWindow = stub();
|
||||||
|
const zoomIn = stub();
|
||||||
|
const zoomOut = stub();
|
||||||
|
const zoomReset = stub();
|
||||||
|
|
||||||
const getExpectedEditMenu = (
|
const getExpectedEditMenu = (
|
||||||
includeSpeech: boolean
|
includeSpeech: boolean
|
||||||
|
@ -58,9 +61,9 @@ const getExpectedEditMenu = (
|
||||||
const getExpectedViewMenu = (): MenuItemConstructorOptions => ({
|
const getExpectedViewMenu = (): MenuItemConstructorOptions => ({
|
||||||
label: '&View',
|
label: '&View',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ label: 'Actual Size', role: 'resetZoom' },
|
{ accelerator: 'CmdOrCtrl+0', label: 'Actual Size', click: zoomReset },
|
||||||
{ accelerator: 'CmdOrCtrl+=', label: 'Zoom In', role: 'zoomIn' },
|
{ accelerator: 'CmdOrCtrl+=', label: 'Zoom In', click: zoomIn },
|
||||||
{ label: 'Zoom Out', role: 'zoomOut' },
|
{ accelerator: 'CmdOrCtrl+-', label: 'Zoom Out', click: zoomOut },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ label: 'Toggle Full Screen', role: 'togglefullscreen' },
|
{ label: 'Toggle Full Screen', role: 'togglefullscreen' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
|
@ -227,6 +230,9 @@ describe('createTemplate', () => {
|
||||||
showKeyboardShortcuts,
|
showKeyboardShortcuts,
|
||||||
showSettings,
|
showSettings,
|
||||||
showWindow,
|
showWindow,
|
||||||
|
zoomIn,
|
||||||
|
zoomOut,
|
||||||
|
zoomReset,
|
||||||
};
|
};
|
||||||
|
|
||||||
PLATFORMS.forEach(({ label, platform, expectedDefault }) => {
|
PLATFORMS.forEach(({ label, platform, expectedDefault }) => {
|
||||||
|
|
|
@ -28,6 +28,9 @@ export type MenuActionsType = Readonly<{
|
||||||
showKeyboardShortcuts: () => unknown;
|
showKeyboardShortcuts: () => unknown;
|
||||||
showSettings: () => unknown;
|
showSettings: () => unknown;
|
||||||
showWindow: () => unknown;
|
showWindow: () => unknown;
|
||||||
|
zoomIn: () => unknown;
|
||||||
|
zoomOut: () => unknown;
|
||||||
|
zoomReset: () => unknown;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type MenuActionType = keyof MenuActionsType;
|
export type MenuActionType = keyof MenuActionsType;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2020 Signal Messenger, LLC
|
// Copyright 2020 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { webFrame } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
import type { AudioDevice } from '@signalapp/ringrtc';
|
import type { AudioDevice } from '@signalapp/ringrtc';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
|
@ -135,13 +135,16 @@ export type IPCEventsCallbacksType = {
|
||||||
customColor?: { id: string; value: CustomColorType }
|
customColor?: { id: string; value: CustomColorType }
|
||||||
) => void;
|
) => void;
|
||||||
getDefaultConversationColor: () => DefaultConversationColorType;
|
getDefaultConversationColor: () => DefaultConversationColorType;
|
||||||
persistZoomFactor: (factor: number) => Promise<void>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type ValuesWithGetters = Omit<
|
type ValuesWithGetters = Omit<
|
||||||
IPCEventsValuesType,
|
IPCEventsValuesType,
|
||||||
|
// Async
|
||||||
|
| 'zoomFactor'
|
||||||
// Optional
|
// Optional
|
||||||
'mediaPermissions' | 'mediaCameraPermissions' | 'autoLaunch'
|
| 'mediaPermissions'
|
||||||
|
| 'mediaCameraPermissions'
|
||||||
|
| 'autoLaunch'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type ValuesWithSetters = Omit<
|
type ValuesWithSetters = Omit<
|
||||||
|
@ -167,6 +170,11 @@ export type IPCEventSetterType<Key extends keyof IPCEventsValuesType> =
|
||||||
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
|
||||||
|
getZoomFactor: () => Promise<ZoomFactorType>;
|
||||||
|
// Events
|
||||||
|
onZoomFactorChange: (callback: (zoomFactor: ZoomFactorType) => void) => void;
|
||||||
|
// Optional
|
||||||
getMediaPermissions?: () => Promise<boolean>;
|
getMediaPermissions?: () => Promise<boolean>;
|
||||||
getMediaCameraPermissions?: () => Promise<boolean>;
|
getMediaCameraPermissions?: () => Promise<boolean>;
|
||||||
getAutoLaunch?: () => Promise<boolean>;
|
getAutoLaunch?: () => Promise<boolean>;
|
||||||
|
@ -212,9 +220,16 @@ export function createIPCEvents(
|
||||||
|
|
||||||
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
getDeviceName: () => window.textsecure.storage.user.getDeviceName(),
|
||||||
|
|
||||||
getZoomFactor: () => window.storage.get('zoomFactor', 1),
|
getZoomFactor: () => {
|
||||||
setZoomFactor: async (zoomFactor: ZoomFactorType) => {
|
return ipcRenderer.invoke('getZoomFactor');
|
||||||
webFrame.setZoomFactor(zoomFactor);
|
},
|
||||||
|
setZoomFactor: async zoomFactor => {
|
||||||
|
ipcRenderer.send('setZoomFactor', zoomFactor);
|
||||||
|
},
|
||||||
|
onZoomFactorChange: callback => {
|
||||||
|
ipcRenderer.on('zoomFactorChanged', (_event, zoomFactor) => {
|
||||||
|
callback(zoomFactor);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setPhoneNumberDiscoverabilitySetting,
|
setPhoneNumberDiscoverabilitySetting,
|
||||||
|
@ -615,9 +630,6 @@ export function createIPCEvents(
|
||||||
getMediaPermissions: window.IPC.getMediaPermissions,
|
getMediaPermissions: window.IPC.getMediaPermissions,
|
||||||
getMediaCameraPermissions: window.IPC.getMediaCameraPermissions,
|
getMediaCameraPermissions: window.IPC.getMediaCameraPermissions,
|
||||||
|
|
||||||
persistZoomFactor: zoomFactor =>
|
|
||||||
window.storage.put('zoomFactor', zoomFactor),
|
|
||||||
|
|
||||||
...overrideEvents,
|
...overrideEvents,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ installCallback('resetAllChatColors');
|
||||||
installCallback('resetDefaultChatColor');
|
installCallback('resetDefaultChatColor');
|
||||||
installCallback('setGlobalDefaultConversationColor');
|
installCallback('setGlobalDefaultConversationColor');
|
||||||
installCallback('getDefaultConversationColor');
|
installCallback('getDefaultConversationColor');
|
||||||
installCallback('persistZoomFactor');
|
|
||||||
|
|
||||||
// Getters only. These are set by the primary device
|
// Getters only. These are set by the primary device
|
||||||
installSetting('blockedCount', {
|
installSetting('blockedCount', {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import { contextBridge, ipcRenderer, webFrame } from 'electron';
|
import { contextBridge, ipcRenderer } from 'electron';
|
||||||
import { MinimalSignalContext } from '../minimalContext';
|
import { MinimalSignalContext } from '../minimalContext';
|
||||||
|
|
||||||
import type { PropsPreloadType } from '../../components/Preferences';
|
import type { PropsPreloadType } from '../../components/Preferences';
|
||||||
|
@ -423,16 +423,8 @@ async function renderPreferences() {
|
||||||
onWhoCanSeeMeChange: attachRenderCallback(
|
onWhoCanSeeMeChange: attachRenderCallback(
|
||||||
settingPhoneNumberSharing.setValue
|
settingPhoneNumberSharing.setValue
|
||||||
),
|
),
|
||||||
|
onZoomFactorChange: (zoomFactorValue: number) => {
|
||||||
// Zoom factor change doesn't require immediate rerender since it will:
|
ipcRenderer.send('setZoomFactor', zoomFactorValue);
|
||||||
// 1. Update the zoom factor in the main window
|
|
||||||
// 2. Trigger `preferred-size-changed` in the main process
|
|
||||||
// 3. Finally result in `window.storage` update which will cause the
|
|
||||||
// rerender.
|
|
||||||
onZoomFactorChange: (value: number) => {
|
|
||||||
// Update Settings window zoom factor to match the selected value.
|
|
||||||
webFrame.setZoomFactor(value);
|
|
||||||
return settingZoomFactor.setValue(value);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hasCustomTitleBar: MinimalSignalContext.OS.hasCustomTitleBar(),
|
hasCustomTitleBar: MinimalSignalContext.OS.hasCustomTitleBar(),
|
||||||
|
@ -442,7 +434,8 @@ async function renderPreferences() {
|
||||||
renderInBrowser(props);
|
renderInBrowser(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
ipcRenderer.on('preferences-changed', () => renderPreferences());
|
ipcRenderer.on('preferences-changed', renderPreferences);
|
||||||
|
ipcRenderer.on('zoomFactorChanged', renderPreferences);
|
||||||
|
|
||||||
const Signal = {
|
const Signal = {
|
||||||
SettingsWindowProps: {
|
SettingsWindowProps: {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue