mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
* sda-1657 printout of main windows position * sda-1657 Save height, width, x, y for full screen/maximized * sda-1657 Change from workArea to bounds because full screen use entire screen * sda-1657 remove save of position and size as before * sda-1657 keep track of the original position/size of window before it is maximized or full screen * Fit in middle of immediate display * Use the new width and height
311 lines
13 KiB
TypeScript
311 lines
13 KiB
TypeScript
import { BrowserWindow, dialog } from 'electron';
|
|
|
|
import { apiName, IBoundsChange, KeyCodes } from '../common/api-interface';
|
|
import { isLinux, isMac, isWindowsOS } from '../common/env';
|
|
import { i18n } from '../common/i18n';
|
|
import { logger } from '../common/logger';
|
|
import { throttle } from '../common/utils';
|
|
import { notification } from '../renderer/notification';
|
|
import { config } from './config-handler';
|
|
import { ICustomBrowserWindow, windowHandler } from './window-handler';
|
|
import { showPopupMenu, windowExists } from './window-utils';
|
|
|
|
enum Permissions {
|
|
MEDIA = 'media',
|
|
LOCATION = 'geolocation',
|
|
NOTIFICATIONS = 'notifications',
|
|
MIDI_SYSEX = 'midiSysex',
|
|
POINTER_LOCK = 'pointerLock',
|
|
FULL_SCREEN = 'fullscreen',
|
|
OPEN_EXTERNAL = 'openExternal',
|
|
}
|
|
const PERMISSIONS_NAMESPACE = 'Permissions';
|
|
|
|
const saveWindowSettings = async (): Promise<void> => {
|
|
const browserWindow = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow;
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
|
|
if (browserWindow && windowExists(browserWindow)) {
|
|
let [ x, y ] = browserWindow.getPosition();
|
|
let [ width, height ] = browserWindow.getSize();
|
|
if (x && y && width && height) {
|
|
// Only send bound changes over to client for pop-out windows
|
|
if (browserWindow.winName !== apiName.mainWindowName && mainWindow && windowExists(mainWindow)) {
|
|
mainWindow.webContents.send('boundsChange', { x, y, width, height, windowName: browserWindow.winName } as IBoundsChange);
|
|
}
|
|
|
|
// Update the config file
|
|
if (browserWindow.winName === apiName.mainWindowName) {
|
|
const isMaximized = browserWindow.isMaximized();
|
|
const isFullScreen = browserWindow.isFullScreen();
|
|
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
|
|
|
|
if (isMaximized || isFullScreen) {
|
|
// Keep the original size and position when window is maximized or full screen
|
|
if (mainWinPos !== undefined && mainWinPos.x !== undefined && mainWinPos.y !== undefined && mainWinPos.width !== undefined && mainWinPos.height !== undefined) {
|
|
x = mainWinPos.x;
|
|
y = mainWinPos.y;
|
|
width = mainWinPos.width;
|
|
height = mainWinPos.height;
|
|
}
|
|
}
|
|
|
|
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ height, width, x, y, isMaximized, isFullScreen } } });
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
const windowMaximized = async (): Promise<void> => {
|
|
const browserWindow = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow;
|
|
if (browserWindow && windowExists(browserWindow)) {
|
|
const isMaximized = browserWindow.isMaximized();
|
|
const isFullScreen = browserWindow.isFullScreen();
|
|
if (browserWindow.winName === apiName.mainWindowName) {
|
|
const { mainWinPos } = config.getUserConfigFields([ 'mainWinPos' ]);
|
|
await config.updateUserConfig({ mainWinPos: { ...mainWinPos, ...{ isMaximized, isFullScreen } } });
|
|
}
|
|
}
|
|
};
|
|
|
|
const throttledWindowChanges = throttle(async () => {
|
|
await saveWindowSettings();
|
|
await windowMaximized();
|
|
notification.moveNotificationToTop();
|
|
}, 1000);
|
|
|
|
const throttledWindowRestore = throttle(async () => {
|
|
notification.moveNotificationToTop();
|
|
}, 1000);
|
|
|
|
/**
|
|
* Sends initial bound changes for pop-out windows
|
|
*
|
|
* @param childWindow {BrowserWindow} - window created via new-window event
|
|
*/
|
|
export const sendInitialBoundChanges = (childWindow: BrowserWindow): void => {
|
|
logger.info(`window-actions: Sending initial bounds`);
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
if (!mainWindow || !windowExists(mainWindow)) {
|
|
return;
|
|
}
|
|
|
|
if (!childWindow || !windowExists(childWindow)) {
|
|
logger.error(`window-actions: child window has already been destroyed - not sending bound change`);
|
|
return;
|
|
}
|
|
const { x, y, width, height } = childWindow.getBounds();
|
|
const windowName = (childWindow as ICustomBrowserWindow).winName;
|
|
mainWindow.webContents.send('boundsChange', { x, y, width, height, windowName } as IBoundsChange);
|
|
logger.info(`window-actions: Initial bounds sent for ${(childWindow as ICustomBrowserWindow).winName}`, { x, y, width, height });
|
|
};
|
|
|
|
/**
|
|
* Tries finding a window we have created with given name. If found, then
|
|
* brings to front and gives focus.
|
|
*
|
|
* @param {string} windowName Name of target window. Note: main window has
|
|
* name 'main'.
|
|
* @param {Boolean} shouldFocus whether to get window to focus or just show
|
|
* without giving focus
|
|
*/
|
|
export const activate = (windowName: string, shouldFocus: boolean = true): void => {
|
|
|
|
// Electron-136: don't activate when the app is reloaded programmatically
|
|
if (windowHandler.isAutoReload) {
|
|
return;
|
|
}
|
|
|
|
const windows = windowHandler.getAllWindows();
|
|
for (const key in windows) {
|
|
if (Object.prototype.hasOwnProperty.call(windows, key)) {
|
|
const window = windows[ key ];
|
|
if (window && !window.isDestroyed() && window.winName === windowName) {
|
|
|
|
// Bring the window to the top without focusing
|
|
// Flash task bar icon in Windows for windows
|
|
if (!shouldFocus) {
|
|
return (isMac || isLinux) ? window.showInactive() : window.flashFrame(true);
|
|
}
|
|
|
|
// Note: On window just focusing will preserve window snapped state
|
|
// Hiding the window and just calling the focus() won't display the window
|
|
if (isWindowsOS) {
|
|
return window.isMinimized() ? window.restore() : window.focus();
|
|
}
|
|
|
|
return window.isMinimized() ? window.restore() : window.show();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sets always on top property based on isAlwaysOnTop
|
|
*
|
|
* @param shouldSetAlwaysOnTop
|
|
* @param shouldActivateMainWindow
|
|
*/
|
|
export const updateAlwaysOnTop = async (shouldSetAlwaysOnTop: boolean, shouldActivateMainWindow: boolean = true): Promise<void> => {
|
|
logger.info(`window-actions: Should we set always on top? ${shouldSetAlwaysOnTop}!`);
|
|
const browserWins: ICustomBrowserWindow[] = BrowserWindow.getAllWindows() as ICustomBrowserWindow[];
|
|
await config.updateUserConfig({ alwaysOnTop: shouldSetAlwaysOnTop });
|
|
if (browserWins.length > 0) {
|
|
browserWins
|
|
.filter((browser) => typeof browser.notificationData !== 'object')
|
|
.forEach((browser) => browser.setAlwaysOnTop(shouldSetAlwaysOnTop));
|
|
|
|
// An issue where changing the alwaysOnTop property
|
|
// focus the pop-out window
|
|
// Issue - Electron-209/470
|
|
const mainWindow = windowHandler.getMainWindow();
|
|
if (mainWindow && mainWindow.winName && shouldActivateMainWindow) {
|
|
activate(mainWindow.winName);
|
|
logger.info(`window-actions: activated main window!`);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Method that handles key press
|
|
*
|
|
* @param key {number}
|
|
*/
|
|
export const handleKeyPress = (key: number): void => {
|
|
switch (key) {
|
|
case KeyCodes.Esc: {
|
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
|
|
|
if (focusedWindow && !focusedWindow.isDestroyed() && focusedWindow.isFullScreen()) {
|
|
logger.info(`window-actions: exiting fullscreen by esc key action`);
|
|
focusedWindow.setFullScreen(false);
|
|
}
|
|
break;
|
|
}
|
|
case KeyCodes.Alt:
|
|
if (isMac || isLinux || windowHandler.isCustomTitleBar) {
|
|
return;
|
|
}
|
|
const browserWin = BrowserWindow.getFocusedWindow() as ICustomBrowserWindow;
|
|
if (browserWin && windowExists(browserWin) && browserWin.winName === apiName.mainWindowName) {
|
|
logger.info(`window-actions: popping up menu by alt key action`);
|
|
showPopupMenu({ window: browserWin });
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Monitors window actions
|
|
*
|
|
* @param window {BrowserWindow}
|
|
*/
|
|
export const monitorWindowActions = (window: BrowserWindow): void => {
|
|
if (!window || window.isDestroyed()) {
|
|
return;
|
|
}
|
|
const eventNames = [ 'move', 'resize' ];
|
|
eventNames.forEach((event: string) => {
|
|
if (window) {
|
|
// @ts-ignore
|
|
window.on(event, throttledWindowChanges);
|
|
}
|
|
});
|
|
window.on('enter-full-screen', throttledWindowChanges);
|
|
window.on('maximize', throttledWindowChanges);
|
|
|
|
window.on('leave-full-screen', throttledWindowChanges);
|
|
window.on('unmaximize', throttledWindowChanges);
|
|
|
|
if ((window as ICustomBrowserWindow).winName === apiName.mainWindowName) {
|
|
window.on('restore', throttledWindowRestore);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Removes attached event listeners
|
|
*
|
|
* @param window
|
|
*/
|
|
export const removeWindowEventListener = (window: BrowserWindow): void => {
|
|
if (!window || window.isDestroyed()) {
|
|
return;
|
|
}
|
|
const eventNames = [ 'move', 'resize' ];
|
|
eventNames.forEach((event: string) => {
|
|
if (window) {
|
|
// @ts-ignore
|
|
window.removeListener(event, throttledWindowChanges);
|
|
}
|
|
});
|
|
window.removeListener('enter-full-screen', throttledWindowChanges);
|
|
window.removeListener('maximize', throttledWindowChanges);
|
|
|
|
window.removeListener('leave-full-screen', throttledWindowChanges);
|
|
window.removeListener('unmaximize', throttledWindowChanges);
|
|
};
|
|
|
|
/**
|
|
* Verifies the permission and display a
|
|
* dialog if the action is not enabled
|
|
*
|
|
* @param permission {boolean} - config value to a specific permission
|
|
* @param message {string} - custom message displayed to the user
|
|
* @param callback {function}
|
|
*/
|
|
export const handleSessionPermissions = (permission: boolean, message: string, callback: (permission: boolean) => void): void => {
|
|
logger.info(`window-action: permission is ->`, { type: message, permission });
|
|
|
|
if (!permission) {
|
|
const browserWindow = BrowserWindow.getFocusedWindow();
|
|
if (browserWindow && !browserWindow.isDestroyed()) {
|
|
dialog.showMessageBox(browserWindow, { type: 'error', title: `${i18n.t('Permission Denied')()}!`, message });
|
|
}
|
|
}
|
|
|
|
return callback(permission);
|
|
};
|
|
|
|
/**
|
|
* Sets permission requests for the window
|
|
*
|
|
* @param webContents {Electron.webContents}
|
|
*/
|
|
export const handlePermissionRequests = (webContents: Electron.webContents): void => {
|
|
|
|
if (!webContents || !webContents.session) {
|
|
return;
|
|
}
|
|
const { session } = webContents;
|
|
|
|
const { permissions } = config.getGlobalConfigFields([ 'permissions' ]);
|
|
if (!permissions) {
|
|
logger.error('permissions configuration is invalid, so, everything will be true by default!');
|
|
return;
|
|
}
|
|
|
|
session.setPermissionRequestHandler((_webContents, permission, callback) => {
|
|
switch (permission) {
|
|
case Permissions.MEDIA:
|
|
return handleSessionPermissions(permissions.media, i18n.t('Your administrator has disabled sharing your camera, microphone, and speakers. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
case Permissions.LOCATION:
|
|
return handleSessionPermissions(permissions.geolocation, i18n.t('Your administrator has disabled sharing your location. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
case Permissions.NOTIFICATIONS:
|
|
return handleSessionPermissions(permissions.notifications, i18n.t('Your administrator has disabled notifications. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
case Permissions.MIDI_SYSEX:
|
|
return handleSessionPermissions(permissions.midiSysex, i18n.t('Your administrator has disabled MIDI Sysex. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
case Permissions.POINTER_LOCK:
|
|
return handleSessionPermissions(permissions.pointerLock, i18n.t('Your administrator has disabled Pointer Lock. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
case Permissions.FULL_SCREEN:
|
|
return handleSessionPermissions(permissions.fullscreen, i18n.t('Your administrator has disabled Full Screen. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
case Permissions.OPEN_EXTERNAL:
|
|
return handleSessionPermissions(permissions.openExternal, i18n.t('Your administrator has disabled Opening External App. Please contact your admin for help', PERMISSIONS_NAMESPACE)(), callback);
|
|
default:
|
|
return callback(false);
|
|
}
|
|
});
|
|
};
|