[MM-59841] Migrate to titleBarOverlay for Windows (#3111)

This commit is contained in:
Devin Binnie 2024-07-31 14:58:33 -04:00 committed by GitHub
parent b2cd2dc60d
commit 8ff64d285a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 33 additions and 194 deletions

View file

@ -1,3 +0,0 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.16763 0.00233459L9.98984 0.825763L5.8259 4.99587L9.99767 9.16147L9.17424 9.98368L5.0037 5.8193L0.831506 9.99768L0.0092966 9.17425L4.18027 4.99709L0.00232887 0.82533L0.825757 0.00311899L5.00248 4.17366L9.16763 0.00233459Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 392 B

View file

@ -1,3 +0,0 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="9" height="9" stroke="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 163 B

View file

@ -1,3 +0,0 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="4" width="10" height="1" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 152 B

View file

@ -1,3 +0,0 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 8V10H0V2H2V0H10V8H8ZM7 3H1V9H7V3ZM3 2H8V7H9V1H3V2Z" fill="black"/>
</svg>

Before

Width:  |  Height:  |  Size: 222 B

View file

@ -45,10 +45,7 @@ export const MODAL_RESULT = 'modal-result';
export const MODAL_OPEN = 'modal-open'; export const MODAL_OPEN = 'modal-open';
export const MODAL_CLOSE = 'modal-close'; export const MODAL_CLOSE = 'modal-close';
export const NOTIFY_MENTION = 'notify_mention'; export const NOTIFY_MENTION = 'notify_mention';
export const WINDOW_CLOSE = 'window_close'; export const EXIT_FULLSCREEN = 'exit-fullscreen';
export const WINDOW_MINIMIZE = 'window_minimize';
export const WINDOW_MAXIMIZE = 'window_maximize';
export const WINDOW_RESTORE = 'window_restore';
export const GET_FULL_SCREEN_STATUS = 'get-full-screen-status'; export const GET_FULL_SCREEN_STATUS = 'get-full-screen-status';
export const UPDATE_TARGET_URL = 'update_target_url'; export const UPDATE_TARGET_URL = 'update_target_url';

View file

@ -319,7 +319,6 @@ describe('common/config', () => {
const config = new Config(); const config = new Config();
config.reload = jest.fn(); config.reload = jest.fn();
config.init(configPath, appName, appPath); config.init(configPath, appName, appPath);
config.useNativeWindow = false;
config.defaultConfigData = {defaultSetting: 'default', otherDefaultSetting: 'default'}; config.defaultConfigData = {defaultSetting: 'default', otherDefaultSetting: 'default'};
config.localConfigData = {otherDefaultSetting: 'local', localSetting: 'local', otherLocalSetting: 'local'}; config.localConfigData = {otherDefaultSetting: 'local', localSetting: 'local', otherLocalSetting: 'local'};
config.buildConfigData = {otherLocalSetting: 'build', buildSetting: 'build', otherBuildSetting: 'build'}; config.buildConfigData = {otherLocalSetting: 'build', buildSetting: 'build', otherBuildSetting: 'build'};
@ -329,7 +328,6 @@ describe('common/config', () => {
config.combinedData.darkMode = false; config.combinedData.darkMode = false;
expect(config.combinedData).toStrictEqual({ expect(config.combinedData).toStrictEqual({
appName: 'app-name', appName: 'app-name',
useNativeWindow: false,
darkMode: false, darkMode: false,
otherBuildSetting: 'registry', otherBuildSetting: 'registry',
registrySetting: 'registry', registrySetting: 'registry',
@ -350,7 +348,6 @@ describe('common/config', () => {
config.buildConfigData = {enableServerManagement: true}; config.buildConfigData = {enableServerManagement: true};
config.registryConfigData = {}; config.registryConfigData = {};
config.predefinedServers.push(server, server); config.predefinedServers.push(server, server);
config.useNativeWindow = false;
config.localConfigData = {teams: [ config.localConfigData = {teams: [
server, server,
{ {
@ -370,7 +367,6 @@ describe('common/config', () => {
config.combinedData.darkMode = false; config.combinedData.darkMode = false;
expect(config.combinedData).toStrictEqual({ expect(config.combinedData).toStrictEqual({
appName: 'app-name', appName: 'app-name',
useNativeWindow: false,
darkMode: false, darkMode: false,
enableServerManagement: true, enableServerManagement: true,
}); });

View file

@ -2,13 +2,12 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import fs from 'fs'; import fs from 'fs';
import os from 'os';
import path from 'path'; import path from 'path';
import {EventEmitter} from 'events'; import {EventEmitter} from 'events';
import {Logger} from 'common/log'; import {Logger} from 'common/log';
import Utils, {copy} from 'common/utils/util'; import {copy} from 'common/utils/util';
import * as Validator from 'common/Validator'; import * as Validator from 'common/Validator';
import {getDefaultViewsForConfigServer} from 'common/views/View'; import {getDefaultViewsForConfigServer} from 'common/views/View';
@ -36,7 +35,6 @@ export class Config extends EventEmitter {
private registryConfig: RegistryConfig; private registryConfig: RegistryConfig;
private _predefinedServers: ConfigServer[]; private _predefinedServers: ConfigServer[];
private useNativeWindow: boolean;
private combinedData?: CombinedConfig; private combinedData?: CombinedConfig;
private localConfigData?: ConfigType; private localConfigData?: ConfigType;
@ -52,11 +50,6 @@ export class Config extends EventEmitter {
if (buildConfig.defaultServers) { if (buildConfig.defaultServers) {
this._predefinedServers.push(...buildConfig.defaultServers.map((server, index) => getDefaultViewsForConfigServer({...server, order: index}))); this._predefinedServers.push(...buildConfig.defaultServers.map((server, index) => getDefaultViewsForConfigServer({...server, order: index})));
} }
try {
this.useNativeWindow = os.platform() === 'win32' && !Utils.isVersionGreaterThanOrEqualTo(os.release(), '6.2');
} catch {
this.useNativeWindow = false;
}
} }
init = (configFilePath: string, appName: string, appPath: string) => { init = (configFilePath: string, appName: string, appPath: string) => {
@ -370,7 +363,6 @@ export class Config extends EventEmitter {
this.localConfigData, this.localConfigData,
this.buildConfigData, this.buildConfigData,
this.registryConfigData, this.registryConfigData,
{useNativeWindow: this.useNativeWindow},
); );
// We don't want to include the servers in the combined config, they should only be accesible via the ServerManager // We don't want to include the servers in the combined config, they should only be accesible via the ServerManager

View file

@ -25,10 +25,6 @@ import {
UPDATE_PATHS, UPDATE_PATHS,
SERVERS_URL_MODIFIED, SERVERS_URL_MODIFIED,
GET_DARK_MODE, GET_DARK_MODE,
WINDOW_CLOSE,
WINDOW_MAXIMIZE,
WINDOW_MINIMIZE,
WINDOW_RESTORE,
DOUBLE_CLICK_ON_WINDOW, DOUBLE_CLICK_ON_WINDOW,
TOGGLE_SECURE_INPUT, TOGGLE_SECURE_INPUT,
GET_APP_INFO, GET_APP_INFO,
@ -97,12 +93,8 @@ import {
flushCookiesStore, flushCookiesStore,
} from './utils'; } from './utils';
import { import {
handleClose,
handleDoubleClick, handleDoubleClick,
handleGetDarkMode, handleGetDarkMode,
handleMaximize,
handleMinimize,
handleRestore,
} from './windows'; } from './windows';
import {protocols} from '../../../electron-builder.json'; import {protocols} from '../../../electron-builder.json';
@ -283,10 +275,6 @@ function initializeInterCommunicationEventListeners() {
ipcMain.on(UPDATE_CONFIGURATION, updateConfiguration); ipcMain.on(UPDATE_CONFIGURATION, updateConfiguration);
ipcMain.handle(GET_DARK_MODE, handleGetDarkMode); ipcMain.handle(GET_DARK_MODE, handleGetDarkMode);
ipcMain.on(WINDOW_CLOSE, handleClose);
ipcMain.on(WINDOW_MAXIMIZE, handleMaximize);
ipcMain.on(WINDOW_MINIMIZE, handleMinimize);
ipcMain.on(WINDOW_RESTORE, handleRestore);
ipcMain.on(DOUBLE_CLICK_ON_WINDOW, handleDoubleClick); ipcMain.on(DOUBLE_CLICK_ON_WINDOW, handleDoubleClick);
ipcMain.on(TOGGLE_SECURE_INPUT, handleToggleSecureInput); ipcMain.on(TOGGLE_SECURE_INPUT, handleToggleSecureInput);

View file

@ -13,20 +13,6 @@ export const handleGetDarkMode = () => {
return Config.darkMode; return Config.darkMode;
}; };
export const handleClose = (event: IpcMainEvent) => BrowserWindow.fromWebContents(event.sender)?.close();
export const handleMaximize = (event: IpcMainEvent) => BrowserWindow.fromWebContents(event.sender)?.maximize();
export const handleMinimize = (event: IpcMainEvent) => BrowserWindow.fromWebContents(event.sender)?.minimize();
export const handleRestore = (event: IpcMainEvent) => {
const window = BrowserWindow.fromWebContents(event.sender);
if (!window) {
return;
}
window.restore();
if (window.isFullScreen()) {
window.setFullScreen(false);
}
};
export const handleDoubleClick = (event: IpcMainEvent, windowType?: string) => { export const handleDoubleClick = (event: IpcMainEvent, windowType?: string) => {
log.debug('handleDoubleClick', windowType); log.debug('handleDoubleClick', windowType);

View file

@ -14,10 +14,7 @@ import {
OPEN_SERVERS_DROPDOWN, OPEN_SERVERS_DROPDOWN,
SWITCH_TAB, SWITCH_TAB,
CLOSE_VIEW, CLOSE_VIEW,
WINDOW_CLOSE, EXIT_FULLSCREEN,
WINDOW_MINIMIZE,
WINDOW_MAXIMIZE,
WINDOW_RESTORE,
DOUBLE_CLICK_ON_WINDOW, DOUBLE_CLICK_ON_WINDOW,
FOCUS_BROWSERVIEW, FOCUS_BROWSERVIEW,
RELOAD_CURRENT_VIEW, RELOAD_CURRENT_VIEW,
@ -117,10 +114,7 @@ contextBridge.exposeInMainWorld('desktop', {
openServersDropdown: () => ipcRenderer.send(OPEN_SERVERS_DROPDOWN), openServersDropdown: () => ipcRenderer.send(OPEN_SERVERS_DROPDOWN),
switchTab: (viewId) => ipcRenderer.send(SWITCH_TAB, viewId), switchTab: (viewId) => ipcRenderer.send(SWITCH_TAB, viewId),
closeView: (viewId) => ipcRenderer.send(CLOSE_VIEW, viewId), closeView: (viewId) => ipcRenderer.send(CLOSE_VIEW, viewId),
closeWindow: () => ipcRenderer.send(WINDOW_CLOSE), exitFullScreen: () => ipcRenderer.send(EXIT_FULLSCREEN),
minimizeWindow: () => ipcRenderer.send(WINDOW_MINIMIZE),
maximizeWindow: () => ipcRenderer.send(WINDOW_MAXIMIZE),
restoreWindow: () => ipcRenderer.send(WINDOW_RESTORE),
doubleClickOnWindow: (windowName) => ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, windowName), doubleClickOnWindow: (windowName) => ipcRenderer.send(DOUBLE_CLICK_ON_WINDOW, windowName),
focusCurrentView: () => ipcRenderer.send(FOCUS_BROWSERVIEW), focusCurrentView: () => ipcRenderer.send(FOCUS_BROWSERVIEW),
reloadCurrentView: () => ipcRenderer.send(RELOAD_CURRENT_VIEW), reloadCurrentView: () => ipcRenderer.send(RELOAD_CURRENT_VIEW),

View file

@ -18,13 +18,13 @@ import {
SERVERS_UPDATE, SERVERS_UPDATE,
UPDATE_APPSTATE_FOR_VIEW_ID, UPDATE_APPSTATE_FOR_VIEW_ID,
UPDATE_MENTIONS, UPDATE_MENTIONS,
MAXIMIZE_CHANGE,
MAIN_WINDOW_CREATED, MAIN_WINDOW_CREATED,
MAIN_WINDOW_RESIZED, MAIN_WINDOW_RESIZED,
MAIN_WINDOW_FOCUSED, MAIN_WINDOW_FOCUSED,
VIEW_FINISHED_RESIZING, VIEW_FINISHED_RESIZING,
TOGGLE_SECURE_INPUT, TOGGLE_SECURE_INPUT,
EMIT_CONFIGURATION, EMIT_CONFIGURATION,
EXIT_FULLSCREEN,
} from 'common/communication'; } from 'common/communication';
import Config from 'common/config'; import Config from 'common/config';
import {Logger} from 'common/log'; import {Logger} from 'common/log';
@ -50,7 +50,6 @@ export class MainWindow extends EventEmitter {
private ready: boolean; private ready: boolean;
private isResizing: boolean; private isResizing: boolean;
private lastEmittedBounds?: Electron.Rectangle; private lastEmittedBounds?: Electron.Rectangle;
private isMaximized: boolean;
constructor() { constructor() {
super(); super();
@ -58,11 +57,11 @@ export class MainWindow extends EventEmitter {
// Create the browser window. // Create the browser window.
this.ready = false; this.ready = false;
this.isResizing = false; this.isResizing = false;
this.isMaximized = false;
ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen()); ipcMain.handle(GET_FULL_SCREEN_STATUS, () => this.win?.isFullScreen());
ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing); ipcMain.on(VIEW_FINISHED_RESIZING, this.handleViewFinishedResizing);
ipcMain.on(EMIT_CONFIGURATION, this.handleUpdateTitleBarOverlay); ipcMain.on(EMIT_CONFIGURATION, this.handleUpdateTitleBarOverlay);
ipcMain.on(EXIT_FULLSCREEN, this.handleExitFullScreen);
ServerManager.on(SERVERS_UPDATE, this.handleUpdateConfig); ServerManager.on(SERVERS_UPDATE, this.handleUpdateConfig);
@ -83,7 +82,7 @@ export class MainWindow extends EventEmitter {
frame: !this.isFramelessWindow(), frame: !this.isFramelessWindow(),
fullscreen: this.shouldStartFullScreen(), fullscreen: this.shouldStartFullScreen(),
titleBarStyle: 'hidden' as const, titleBarStyle: 'hidden' as const,
titleBarOverlay: process.platform === 'linux' ? this.getTitleBarOverlay() : false, titleBarOverlay: this.getTitleBarOverlay(),
trafficLightPosition: {x: 12, y: 12}, trafficLightPosition: {x: 12, y: 12},
backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do backgroundColor: '#fff', // prevents blurry text: https://electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
webPreferences: { webPreferences: {
@ -129,9 +128,6 @@ export class MainWindow extends EventEmitter {
this.win.on('focus', this.onFocus); this.win.on('focus', this.onFocus);
this.win.on('blur', this.onBlur); this.win.on('blur', this.onBlur);
this.win.on('unresponsive', this.onUnresponsive); this.win.on('unresponsive', this.onUnresponsive);
this.win.on('maximize', this.onMaximize);
this.win.on('unmaximize', this.onUnmaximize);
this.win.on('restore', this.onRestore);
this.win.on('enter-full-screen', this.onEnterFullScreen); this.win.on('enter-full-screen', this.onEnterFullScreen);
this.win.on('leave-full-screen', this.onLeaveFullScreen); this.win.on('leave-full-screen', this.onLeaveFullScreen);
this.win.on('will-resize', this.onWillResize); this.win.on('will-resize', this.onWillResize);
@ -252,6 +248,7 @@ export class MainWindow extends EventEmitter {
private getTitleBarOverlay = () => { private getTitleBarOverlay = () => {
return { return {
color: Config.darkMode ? '#2e2e2e' : '#efefef', color: Config.darkMode ? '#2e2e2e' : '#efefef',
symbolColor: Config.darkMode ? '#c1c1c1' : '#474747',
height: TAB_BAR_HEIGHT, height: TAB_BAR_HEIGHT,
}; };
}; };
@ -465,24 +462,6 @@ export class MainWindow extends EventEmitter {
}, 10); }, 10);
}; };
private onMaximize = () => {
this.isMaximized = true;
this.win?.webContents.send(MAXIMIZE_CHANGE, true);
this.emitBounds();
};
private onUnmaximize = () => {
this.isMaximized = false;
this.win?.webContents.send(MAXIMIZE_CHANGE, false);
this.emitBounds();
};
private onRestore = () => {
if (this.isMaximized && !this.win?.isMaximized()) {
this.win?.maximize();
}
};
private onEnterFullScreen = () => { private onEnterFullScreen = () => {
this.win?.webContents.send('enter-full-screen'); this.win?.webContents.send('enter-full-screen');
this.emitBounds(); this.emitBounds();
@ -544,6 +523,12 @@ export class MainWindow extends EventEmitter {
this.isResizing = false; this.isResizing = false;
}; };
private handleExitFullScreen = () => {
if (this.win?.isFullScreen()) {
this.win.setFullScreen(false);
}
};
/** /**
* Server Manager update handler * Server Manager update handler
*/ */

View file

@ -18,10 +18,6 @@ import ExtraBar from './ExtraBar';
import ServerDropdownButton from './ServerDropdownButton'; import ServerDropdownButton from './ServerDropdownButton';
import TabBar from './TabBar'; import TabBar from './TabBar';
import closeButton from '../../assets/titlebar/chrome-close.svg';
import maximizeButton from '../../assets/titlebar/chrome-maximize.svg';
import minimizeButton from '../../assets/titlebar/chrome-minimize.svg';
import restoreButton from '../../assets/titlebar/chrome-restore.svg';
import {playSound} from '../notificationSounds'; import {playSound} from '../notificationSounds';
import '../css/components/UpgradeButton.scss'; import '../css/components/UpgradeButton.scss';
@ -38,7 +34,6 @@ type Props = {
openMenu: () => void; openMenu: () => void;
darkMode: boolean; darkMode: boolean;
appName: string; appName: string;
useNativeWindow: boolean;
intl: IntlShape; intl: IntlShape;
}; };
@ -326,23 +321,13 @@ class MainPage extends React.PureComponent<Props, State> {
this.handleSelectTab(tab[0].id!); this.handleSelectTab(tab[0].id!);
}; };
handleClose = (e: React.MouseEvent<HTMLDivElement>) => { handleExitFullScreen = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back. e.stopPropagation(); // since it is our button, the event goes into MainPage's onclick event, getting focus back.
window.desktop.closeWindow();
};
handleMinimize = (e: React.MouseEvent<HTMLDivElement>) => { if (!this.state.fullScreen) {
e.stopPropagation(); return;
window.desktop.minimizeWindow(); }
}; window.desktop.exitFullScreen();
handleMaximize = (e: React.MouseEvent<HTMLDivElement>) => {
e.stopPropagation();
window.desktop.maximizeWindow();
};
handleRestore = () => {
window.desktop.restoreWindow();
}; };
openMenu = () => { openMenu = () => {
@ -430,60 +415,6 @@ class MainPage extends React.PureComponent<Props, State> {
/> />
) : null; ) : null;
let maxButton;
if (this.state.maximized || this.state.fullScreen) {
maxButton = (
<div
className='button restore-button'
onClick={this.handleRestore}
>
<img
src={restoreButton}
draggable={false}
/>
</div>
);
} else {
maxButton = (
<div
className='button max-button'
onClick={this.handleMaximize}
>
<img
src={maximizeButton}
draggable={false}
/>
</div>
);
}
let titleBarButtons;
if (window.process.platform === 'win32' && !this.props.useNativeWindow) {
titleBarButtons = (
<span className='title-bar-btns'>
<div
className='button min-button'
onClick={this.handleMinimize}
>
<img
src={minimizeButton}
draggable={false}
/>
</div>
{maxButton}
<div
className='button close-button'
onClick={this.handleClose}
>
<img
src={closeButton}
draggable={false}
/>
</div>
</span>
);
}
const totalMentionCount = Object.keys(this.state.mentionCounts).reduce((sum, key) => { const totalMentionCount = Object.keys(this.state.mentionCounts).reduce((sum, key) => {
// Strip out current server from unread and mention counts // Strip out current server from unread and mention counts
if (this.state.tabs.get(this.state.activeServerId!)?.map((tab) => tab.id).includes(key)) { if (this.state.tabs.get(this.state.activeServerId!)?.map((tab) => tab.id).includes(key)) {
@ -541,7 +472,16 @@ class MainPage extends React.PureComponent<Props, State> {
)} )}
{tabsRow} {tabsRow}
{downloadsDropdownButton} {downloadsDropdownButton}
{titleBarButtons} {window.process.platform !== 'darwin' && this.state.fullScreen &&
<span className='title-bar-btns'>
<div
className='button full-screen-button'
onClick={this.handleExitFullScreen}
>
<i className='icon icon-arrow-collapse'/>
</div>
</span>
}
</div> </div>
</Row> </Row>
); );

View file

@ -176,23 +176,8 @@ body {
background: rgba(0,0,0,0.2); background: rgba(0,0,0,0.2);
} }
.topBar .title-bar-btns>.close-button:hover { .topBar .title-bar-btns>.full-screen-button {
background: #E81123 !important; font-size: 18px;
}
.topBar .title-bar-btns>.close-button:hover>img {
filter: invert(100%);
-webkit-filter: invert(100%);
opacity: 1;
}
.topBar .title-bar-btns>.close-button:active {
background: #f1707a !important;
}
.topBar .title-bar-btns>.close-button:active>img {
filter: invert(100%);
-webkit-filter: invert(100%);
} }
.topBar .title-bar-btns img { .topBar .title-bar-btns img {
@ -204,14 +189,7 @@ body {
-webkit-filter: invert(100%); -webkit-filter: invert(100%);
} }
.topBar .title-bar-btns>.min-button { .topBar .title-bar-btns>.close-button, .topBar .title-bar-btns>.full-screen-button {
grid-column: 1;
}
.topBar .title-bar-btns>.max-button, .topBar .title-bar-btns>.restore-button {
grid-column: 2;
}
.topBar .title-bar-btns>.close-button {
grid-column: 3; grid-column: 3;
} }

View file

@ -81,7 +81,6 @@ class Root extends React.PureComponent<Record<string, never>, State> {
openMenu={this.openMenu} openMenu={this.openMenu}
darkMode={config.darkMode} darkMode={config.darkMode}
appName={config.appName} appName={config.appName}
useNativeWindow={config.useNativeWindow}
/> />
</IntlProvider> </IntlProvider>
); );

View file

@ -119,7 +119,6 @@ export type RegistryConfig = {
export type CombinedConfig = Omit<Config, 'teams'> & Omit<BuildConfig, 'defaultServers'> & { export type CombinedConfig = Omit<Config, 'teams'> & Omit<BuildConfig, 'defaultServers'> & {
appName: string; appName: string;
useNativeWindow: boolean;
} }
export type LocalConfiguration = Config & { export type LocalConfiguration = Config & {

View file

@ -34,10 +34,7 @@ declare global {
openServersDropdown: () => void; openServersDropdown: () => void;
switchTab: (viewId: string) => void; switchTab: (viewId: string) => void;
closeView: (viewId: string) => void; closeView: (viewId: string) => void;
closeWindow: () => void; exitFullScreen: () => void;
minimizeWindow: () => void;
maximizeWindow: () => void;
restoreWindow: () => void;
doubleClickOnWindow: (windowName?: string) => void; doubleClickOnWindow: (windowName?: string) => void;
focusCurrentView: () => void; focusCurrentView: () => void;
reloadCurrentView: () => void; reloadCurrentView: () => void;