2019-12-06 19:17:34 +01:00
/ *
Copyright 2016 Aviral Dasgupta
Copyright 2016 OpenMarket Ltd
Copyright 2017 , 2019 Michael Telatynski < 7t3chguy @ gmail.com >
2021-01-13 16:21:00 +01:00
Copyright 2018 - 2021 New Vector Ltd
2019-12-06 19:17:34 +01:00
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
2021-07-27 12:47:44 +02:00
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
import "./squirrelhooks" ;
2022-02-11 11:38:19 +01:00
import {
app ,
BrowserWindow ,
Menu ,
autoUpdater ,
protocol ,
dialog ,
} from "electron" ;
2021-06-25 15:35:58 +02:00
import AutoLaunch from "auto-launch" ;
import path from "path" ;
import windowStateKeeper from 'electron-window-state' ;
import Store from 'electron-store' ;
import fs , { promises as afs } from "fs" ;
import { URL } from "url" ;
2021-07-01 10:22:57 +02:00
import minimist from "minimist" ;
2021-06-25 15:35:58 +02:00
2022-07-01 21:17:40 +02:00
import "./ipc" ;
import "./keytar" ;
import "./seshat" ;
import "./settings" ;
2021-06-25 15:35:58 +02:00
import * as tray from "./tray" ;
import { buildMenuTemplate } from './vectormenu' ;
import webContentsHandler from './webcontents-handler' ;
import * as updater from './updater' ;
2022-07-01 21:17:40 +02:00
import { getProfileFromDeeplink , protocolInit } from './protocol' ;
2021-06-25 15:35:58 +02:00
import { _t , AppLocalization } from './language-helper' ;
2022-05-23 16:44:29 +02:00
import Input = Electron . Input ;
2019-12-06 19:17:34 +01:00
2021-07-01 10:22:57 +02:00
const argv = minimist ( process . argv , {
alias : { help : "h" } ,
} ) ;
2019-12-10 18:40:17 +01:00
// Things we need throughout the file but need to be created
// async to are initialised in setupGlobals()
2022-05-23 16:44:29 +02:00
let asarPath : string ;
let resPath : string ;
let iconPath : string ;
2019-12-06 19:17:34 +01:00
if ( argv [ "help" ] ) {
console . log ( "Options:" ) ;
console . log ( " --profile-dir {path}: Path to where to store the profile." ) ;
console . log ( " --profile {name}: Name of alternate profile to use, allows for running multiple accounts." ) ;
console . log ( " --devtools: Install and use react-devtools and react-perf." ) ;
console . log ( " --no-update: Disable automatic updating." ) ;
console . log ( " --hidden: Start the application hidden in the system tray." ) ;
console . log ( " --help: Displays this help message." ) ;
console . log ( "And more such as --proxy, see:" +
2020-05-14 12:27:26 +02:00
"https://electronjs.org/docs/api/command-line-switches" ) ;
2019-12-06 19:17:34 +01:00
app . exit ( ) ;
}
2020-07-15 12:41:30 +02:00
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
// as soon as the app path is set, so pick a random path in it that must exist if it's a
// real user data directory.
2022-05-23 16:44:29 +02:00
function isRealUserDataDir ( d : string ) : boolean {
2020-07-15 12:41:30 +02:00
return fs . existsSync ( path . join ( d , 'IndexedDB' ) ) ;
}
2020-04-14 14:29:47 +02:00
// check if we are passed a profile in the SSO callback url
2022-05-23 16:44:29 +02:00
let userDataPath : string ;
2020-07-07 19:57:29 +02:00
2020-04-14 14:29:47 +02:00
const userDataPathInProtocol = getProfileFromDeeplink ( argv [ "_" ] ) ;
if ( userDataPathInProtocol ) {
2020-07-07 19:57:29 +02:00
userDataPath = userDataPathInProtocol ;
2020-04-14 14:29:47 +02:00
} else if ( argv [ 'profile-dir' ] ) {
2020-07-07 19:57:29 +02:00
userDataPath = argv [ 'profile-dir' ] ;
2020-07-02 14:30:11 +02:00
} else {
2020-07-21 18:57:54 +02:00
let newUserDataPath = app . getPath ( 'userData' ) ;
2020-07-02 14:30:11 +02:00
if ( argv [ 'profile' ] ) {
newUserDataPath += '-' + argv [ 'profile' ] ;
}
const newUserDataPathExists = isRealUserDataDir ( newUserDataPath ) ;
2020-07-21 18:57:54 +02:00
let oldUserDataPath = path . join ( app . getPath ( 'appData' ) , app . getName ( ) . replace ( 'Element' , 'Riot' ) ) ;
if ( argv [ 'profile' ] ) {
oldUserDataPath += '-' + argv [ 'profile' ] ;
}
2020-07-02 14:30:11 +02:00
const oldUserDataPathExists = isRealUserDataDir ( oldUserDataPath ) ;
console . log ( newUserDataPath + " exists: " + ( newUserDataPathExists ? 'yes' : 'no' ) ) ;
console . log ( oldUserDataPath + " exists: " + ( oldUserDataPathExists ? 'yes' : 'no' ) ) ;
if ( ! newUserDataPathExists && oldUserDataPathExists ) {
console . log ( "Using legacy user data path: " + oldUserDataPath ) ;
2020-07-07 19:57:29 +02:00
userDataPath = oldUserDataPath ;
} else {
userDataPath = newUserDataPath ;
2020-07-02 14:30:11 +02:00
}
2019-12-06 19:17:34 +01:00
}
2020-07-07 19:57:29 +02:00
app . setPath ( 'userData' , userDataPath ) ;
2019-12-06 19:17:34 +01:00
2022-05-23 16:44:29 +02:00
async function tryPaths ( name : string , root : string , rawPaths : string [ ] ) : Promise < string > {
2020-04-16 14:08:16 +02:00
// Make everything relative to root
const paths = rawPaths . map ( p = > path . join ( root , p ) ) ;
2019-12-10 18:40:17 +01:00
for ( const p of paths ) {
try {
await afs . stat ( p ) ;
return p + '/' ;
} catch ( e ) {
}
}
2020-04-16 14:08:16 +02:00
console . log ( ` Couldn't find ${ name } files in any of: ` ) ;
2019-12-10 18:40:17 +01:00
for ( const p of paths ) {
console . log ( "\t" + path . resolve ( p ) ) ;
}
2020-04-16 14:08:16 +02:00
throw new Error ( ` Failed to find ${ name } files ` ) ;
2019-12-06 19:17:34 +01:00
}
2019-12-10 18:40:17 +01:00
// Find the webapp resources and set up things that require them
2022-05-23 16:44:29 +02:00
async function setupGlobals ( ) : Promise < void > {
2019-12-10 18:40:17 +01:00
// find the webapp asar.
2020-04-16 14:08:16 +02:00
asarPath = await tryPaths ( "webapp" , __dirname , [
2019-12-10 18:40:17 +01:00
// If run from the source checkout, this will be in the directory above
'../webapp.asar' ,
// but if run from a packaged application, electron-main.js will be in
// a different asar file so it will be two levels above
'../../webapp.asar' ,
2020-04-16 14:08:16 +02:00
// also try without the 'asar' suffix to allow symlinking in a directory
2019-12-10 18:40:17 +01:00
'../webapp' ,
2020-04-16 14:08:16 +02:00
// from a packaged application
'../../webapp' ,
2019-12-10 18:40:17 +01:00
] ) ;
2020-04-16 14:08:16 +02:00
2019-12-10 18:40:17 +01:00
// we assume the resources path is in the same place as the asar
2020-04-16 14:08:16 +02:00
resPath = await tryPaths ( "res" , path . dirname ( asarPath ) , [
// If run from the source checkout
'res' ,
// if run from packaged application
'' ,
] ) ;
2019-12-10 18:40:17 +01:00
try {
2021-07-01 10:22:57 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2022-07-01 21:17:40 +02:00
global . vectorConfig = require ( asarPath + 'config.json' ) ;
2019-12-10 18:40:17 +01:00
} catch ( e ) {
// it would be nice to check the error code here and bail if the config
2020-11-14 22:54:07 +01:00
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
2019-12-10 18:40:17 +01:00
// file or invalid json, so node is just very unhelpful.
// Continue with the defaults (ie. an empty config)
2022-07-01 21:17:40 +02:00
global . vectorConfig = { } ;
2019-12-06 19:17:34 +01:00
}
2019-12-10 18:40:17 +01:00
try {
// Load local config and use it to override values from the one baked with the build
2021-07-01 10:24:02 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2019-12-10 18:40:17 +01:00
const localConfig = require ( path . join ( app . getPath ( 'userData' ) , 'config.json' ) ) ;
// If the local config has a homeserver defined, don't use the homeserver from the build
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
// defined, and panics as a result.
const homeserverProps = [ 'default_is_url' , 'default_hs_url' , 'default_server_name' , 'default_server_config' ] ;
if ( Object . keys ( localConfig ) . find ( k = > homeserverProps . includes ( k ) ) ) {
// Rip out all the homeserver options from the vector config
2022-07-01 21:17:40 +02:00
global . vectorConfig = Object . keys ( global . vectorConfig )
2019-12-10 18:40:17 +01:00
. filter ( k = > ! homeserverProps . includes ( k ) )
2022-07-01 21:17:40 +02:00
. reduce ( ( obj , key ) = > { obj [ key ] = global . vectorConfig [ key ] ; return obj ; } , { } ) ;
2019-12-10 18:40:17 +01:00
}
2022-07-01 21:17:40 +02:00
global . vectorConfig = Object . assign ( global . vectorConfig , localConfig ) ;
2019-12-10 18:40:17 +01:00
} catch ( e ) {
2021-06-04 06:39:29 +02:00
if ( e instanceof SyntaxError ) {
dialog . showMessageBox ( {
type : "error" ,
2022-07-01 21:17:40 +02:00
title : ` Your ${ global . vectorConfig . brand || 'Element' } is misconfigured ` ,
message : ` Your custom ${ global . vectorConfig . brand || 'Element' } configuration contains invalid JSON. ` +
` Please correct the problem and reopen ${ global . vectorConfig . brand || 'Element' } . ` ,
2021-06-04 06:39:29 +02:00
detail : e.message || "" ,
} ) ;
}
2019-12-10 18:40:17 +01:00
// Could not load local config, this is expected in most cases.
}
// The tray icon
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
2020-07-15 14:50:00 +02:00
const iconFile = ` element. ${ process . platform === 'win32' ? 'ico' : 'png' } ` ;
2019-12-10 18:40:17 +01:00
iconPath = path . join ( resPath , "img" , iconFile ) ;
2022-07-01 21:17:40 +02:00
global . trayConfig = {
2019-12-10 18:40:17 +01:00
icon_path : iconPath ,
2022-07-01 21:17:40 +02:00
brand : global.vectorConfig.brand || 'Element' ,
2019-12-10 18:40:17 +01:00
} ;
// launcher
2022-07-01 21:17:40 +02:00
global . launcher = new AutoLaunch ( {
name : global.vectorConfig.brand || 'Element' ,
2019-12-10 18:40:17 +01:00
isHidden : true ,
mac : {
useLaunchAgent : true ,
} ,
} ) ;
2019-12-06 19:17:34 +01:00
}
2022-05-23 16:44:29 +02:00
async function moveAutoLauncher ( ) : Promise < void > {
2020-07-02 15:42:45 +02:00
// Look for an auto-launcher under 'Riot' and if we find one, port it's
2022-05-23 16:44:29 +02:00
// enabled/disabled-ness over to the new 'Element' launcher
2022-07-01 21:17:40 +02:00
if ( ! global . vectorConfig . brand || global . vectorConfig . brand === 'Element' ) {
2020-07-02 14:30:11 +02:00
const oldLauncher = new AutoLaunch ( {
name : 'Riot' ,
isHidden : true ,
mac : {
useLaunchAgent : true ,
} ,
} ) ;
const wasEnabled = await oldLauncher . isEnabled ( ) ;
if ( wasEnabled ) {
await oldLauncher . disable ( ) ;
2022-07-01 21:17:40 +02:00
await global . launcher . enable ( ) ;
2020-07-02 14:30:11 +02:00
}
}
}
2022-07-01 21:17:40 +02:00
global . store = new Store ( { name : "electron-config" } ) ;
2019-12-06 19:17:34 +01:00
global . appQuitting = false ;
2022-05-23 16:44:29 +02:00
const exitShortcuts : Array < ( input : Input , platform : string ) = > boolean > = [
2021-04-15 17:50:35 +02:00
( input , platform ) = > platform !== 'darwin' && input . alt && input . key . toUpperCase ( ) === 'F4' ,
( input , platform ) = > platform !== 'darwin' && input . control && input . key . toUpperCase ( ) === 'Q' ,
( input , platform ) = > platform === 'darwin' && input . meta && input . key . toUpperCase ( ) === 'Q' ,
2021-03-31 09:58:24 +02:00
] ;
2022-05-23 16:44:29 +02:00
const warnBeforeExit = ( event : Event , input : Input ) : void = > {
2022-07-01 21:17:40 +02:00
const shouldWarnBeforeExit = global . store . get ( 'warnBeforeExit' , true ) ;
2021-06-21 15:17:28 +02:00
const exitShortcutPressed =
input . type === 'keyDown' && exitShortcuts . some ( shortcutFn = > shortcutFn ( input , process . platform ) ) ;
2021-03-31 09:58:24 +02:00
2021-03-31 18:18:39 +02:00
if ( shouldWarnBeforeExit && exitShortcutPressed ) {
2022-07-01 21:17:40 +02:00
const shouldCancelCloseRequest = dialog . showMessageBoxSync ( global . mainWindow , {
2021-03-29 13:10:27 +02:00
type : "question" ,
2021-04-26 15:50:18 +02:00
buttons : [ _t ( "Cancel" ) , _t ( "Close Element" ) ] ,
message : _t ( "Are you sure you want to quit?" ) ,
2021-03-29 13:10:27 +02:00
defaultId : 1 ,
cancelId : 0 ,
} ) === 0 ;
if ( shouldCancelCloseRequest ) {
event . preventDefault ( ) ;
}
}
} ;
2020-06-23 16:42:27 +02:00
2019-12-06 19:17:34 +01:00
// handle uncaught errors otherwise it displays
// stack traces in popup dialogs, which is terrible (which
// it will do any time the auto update poke fails, and there's
// no other way to catch this error).
// Assuming we generally run from the console when developing,
// this is far preferable.
2022-05-23 16:44:29 +02:00
process . on ( 'uncaughtException' , function ( error : Error ) : void {
2019-12-06 19:17:34 +01:00
console . log ( 'Unhandled exception' , error ) ;
} ) ;
app . commandLine . appendSwitch ( '--enable-usermedia-screen-capturing' ) ;
2021-09-06 16:18:16 +02:00
if ( ! app . commandLine . hasSwitch ( 'enable-features' ) ) {
app . commandLine . appendSwitch ( 'enable-features' , 'WebRTCPipeWireCapturer' ) ;
}
2019-12-06 19:17:34 +01:00
const gotLock = app . requestSingleInstanceLock ( ) ;
if ( ! gotLock ) {
console . log ( 'Other instance detected: exiting' ) ;
app . exit ( ) ;
}
2020-03-02 16:04:51 +01:00
// do this after we know we are the primary instance of the app
protocolInit ( ) ;
2019-12-06 19:17:34 +01:00
// Register the scheme the app is served from as 'standard'
// which allows things like relative URLs and IndexedDB to
// work.
// Also mark it as secure (ie. accessing resources from this
// protocol and HTTPS won't trigger mixed content warnings).
protocol . registerSchemesAsPrivileged ( [ {
scheme : 'vector' ,
privileges : {
standard : true ,
secure : true ,
supportFetchAPI : true ,
} ,
} ] ) ;
2020-05-21 00:16:57 +02:00
// Turn the sandbox on for *all* windows we might generate. Doing this means we don't
// have to specify a `sandbox: true` to each BrowserWindow.
//
// This also fixes an issue with window.open where if we only specified the sandbox
// on the main window we'd run into cryptic "ipc_renderer be broke" errors. Turns out
// it's trying to jump the sandbox and make some calls into electron, which it can't
// do when half of it is sandboxed. By turning on the sandbox for everything, the new
// window (no matter how temporary it may be) is also sandboxed, allowing for a clean
// transition into the user's browser.
app . enableSandbox ( ) ;
2021-04-03 14:10:11 +02:00
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
2021-04-02 16:27:32 +02:00
app . commandLine . appendSwitch ( 'disable-features' , 'HardwareMediaKeyHandling,MediaSessionService' ) ;
2020-05-21 00:16:57 +02:00
2022-05-20 14:26:16 +02:00
// Disable hardware acceleration if the setting has been set.
2022-07-01 21:17:40 +02:00
if ( global . store . get ( 'disableHardwareAcceleration' , false ) === true ) {
2022-05-20 14:37:58 +02:00
console . log ( "Disabling hardware acceleration." ) ;
2022-05-20 14:26:16 +02:00
app . disableHardwareAcceleration ( ) ;
}
2019-12-10 18:40:17 +01:00
app . on ( 'ready' , async ( ) = > {
try {
await setupGlobals ( ) ;
2020-07-02 14:30:11 +02:00
await moveAutoLauncher ( ) ;
2019-12-10 18:40:17 +01:00
} catch ( e ) {
console . log ( "App setup failed: exiting" , e ) ;
process . exit ( 1 ) ;
// process.exit doesn't cause node to stop running code immediately,
// so return (we could let the exception propagate but then we end up
// with node printing all sorts of stuff about unhandled exceptions
// when we want the actual error to be as obvious as possible).
return ;
}
2019-12-06 19:17:34 +01:00
if ( argv [ 'devtools' ] ) {
try {
2021-07-01 10:24:02 +02:00
// eslint-disable-next-line @typescript-eslint/no-var-requires
2019-12-06 19:17:34 +01:00
const { default : installExt , REACT_DEVELOPER_TOOLS , REACT_PERF } = require ( 'electron-devtools-installer' ) ;
installExt ( REACT_DEVELOPER_TOOLS )
. then ( ( name ) = > console . log ( ` Added Extension: ${ name } ` ) )
. catch ( ( err ) = > console . log ( 'An error occurred: ' , err ) ) ;
installExt ( REACT_PERF )
. then ( ( name ) = > console . log ( ` Added Extension: ${ name } ` ) )
. catch ( ( err ) = > console . log ( 'An error occurred: ' , err ) ) ;
} catch ( e ) {
console . log ( e ) ;
}
}
protocol . registerFileProtocol ( 'vector' , ( request , callback ) = > {
if ( request . method !== 'GET' ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 322 } ) ; // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
2019-12-06 19:17:34 +01:00
return null ;
}
const parsedUrl = new URL ( request . url ) ;
if ( parsedUrl . protocol !== 'vector:' ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 302 } ) ; // UNKNOWN_URL_SCHEME
2019-12-06 19:17:34 +01:00
return ;
}
if ( parsedUrl . host !== 'vector' ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 105 } ) ; // NAME_NOT_RESOLVED
2019-12-06 19:17:34 +01:00
return ;
}
const target = parsedUrl . pathname . split ( '/' ) ;
// path starts with a '/'
if ( target [ 0 ] !== '' ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 6 } ) ; // FILE_NOT_FOUND
2019-12-06 19:17:34 +01:00
return ;
}
if ( target [ target . length - 1 ] == '' ) {
target [ target . length - 1 ] = 'index.html' ;
}
2022-05-23 16:44:29 +02:00
let baseDir : string ;
2019-12-09 13:49:41 +01:00
if ( target [ 1 ] === 'webapp' ) {
2019-12-10 18:40:17 +01:00
baseDir = asarPath ;
2019-12-06 19:17:34 +01:00
} else {
2021-05-27 15:39:26 +02:00
callback ( { error : - 6 } ) ; // FILE_NOT_FOUND
2019-12-06 19:17:34 +01:00
return ;
}
// Normalise the base dir and the target path separately, then make sure
// the target path isn't trying to back out beyond its root
baseDir = path . normalize ( baseDir ) ;
const relTarget = path . normalize ( path . join ( . . . target . slice ( 2 ) ) ) ;
if ( relTarget . startsWith ( '..' ) ) {
2021-05-27 15:39:26 +02:00
callback ( { error : - 6 } ) ; // FILE_NOT_FOUND
2019-12-06 19:17:34 +01:00
return ;
}
const absTarget = path . join ( baseDir , relTarget ) ;
callback ( {
path : absTarget ,
} ) ;
} ) ;
if ( argv [ 'no-update' ] ) {
console . log ( 'Auto update disabled via command line flag "--no-update"' ) ;
2022-07-01 21:17:40 +02:00
} else if ( global . vectorConfig [ 'update_base_url' ] ) {
console . log ( ` Starting auto update with base URL: ${ global . vectorConfig [ 'update_base_url' ] } ` ) ;
updater . start ( global . vectorConfig [ 'update_base_url' ] ) ;
2019-12-06 19:17:34 +01:00
} else {
console . log ( 'No update_base_url is defined: auto update is disabled' ) ;
}
// Load the previous window state with fallback to defaults
const mainWindowState = windowStateKeeper ( {
defaultWidth : 1024 ,
defaultHeight : 768 ,
} ) ;
const preloadScript = path . normalize ( ` ${ __dirname } /preload.js ` ) ;
2022-07-01 21:17:40 +02:00
global . mainWindow = new BrowserWindow ( {
2020-02-21 12:17:18 +01:00
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
backgroundColor : '#fff' ,
2019-12-06 19:17:34 +01:00
icon : iconPath ,
show : false ,
2022-07-01 21:17:40 +02:00
autoHideMenuBar : global.store.get ( 'autoHideMenuBar' , true ) ,
2019-12-06 19:17:34 +01:00
x : mainWindowState.x ,
y : mainWindowState.y ,
width : mainWindowState.width ,
height : mainWindowState.height ,
webPreferences : {
preload : preloadScript ,
nodeIntegration : false ,
2020-05-21 00:16:57 +02:00
//sandbox: true, // We enable sandboxing from app.enableSandbox() above
2021-01-13 16:21:00 +01:00
contextIsolation : true ,
2021-12-10 15:55:35 +01:00
webgl : true ,
2019-12-06 19:17:34 +01:00
} ,
} ) ;
2022-07-01 21:17:40 +02:00
global . mainWindow . loadURL ( 'vector://vector/webapp/' ) ;
2019-12-06 19:17:34 +01:00
2021-04-02 12:07:33 +02:00
// Handle spellchecker
2022-07-01 21:17:40 +02:00
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
global . mainWindow . webContents . session . setSpellCheckerEnabled ( global . store . get ( "spellCheckerEnabled" , true ) ) ;
2021-04-02 12:07:33 +02:00
2019-12-06 19:17:34 +01:00
// Create trayIcon icon
2022-07-01 21:17:40 +02:00
if ( global . store . get ( 'minimizeToTray' , true ) ) tray . create ( global . trayConfig ) ;
2019-12-06 19:17:34 +01:00
2022-07-01 21:17:40 +02:00
global . mainWindow . once ( 'ready-to-show' , ( ) = > {
mainWindowState . manage ( global . mainWindow ) ;
2019-12-06 19:17:34 +01:00
if ( ! argv [ 'hidden' ] ) {
2022-07-01 21:17:40 +02:00
global . mainWindow . show ( ) ;
2019-12-06 19:17:34 +01:00
} else {
// hide here explicitly because window manage above sometimes shows it
2022-07-01 21:17:40 +02:00
global . mainWindow . hide ( ) ;
2019-12-06 19:17:34 +01:00
}
} ) ;
2022-07-01 21:17:40 +02:00
global . mainWindow . webContents . on ( 'before-input-event' , warnBeforeExit ) ;
2021-03-29 13:10:27 +02:00
2022-07-01 21:17:40 +02:00
global . mainWindow . on ( 'closed' , ( ) = > {
global . mainWindow = null ;
2019-12-06 19:17:34 +01:00
} ) ;
2022-07-01 21:17:40 +02:00
global . mainWindow . on ( 'close' , async ( e ) = > {
2021-03-25 15:50:33 +01:00
// If we are not quitting and have a tray icon then minimize to tray
if ( ! global . appQuitting && ( tray . hasTray ( ) || process . platform === 'darwin' ) ) {
// On Mac, closing the window just hides it
// (this is generally how single-window Mac apps
// behave, eg. Mail.app)
e . preventDefault ( ) ;
2021-05-05 04:55:28 +02:00
2022-07-01 21:17:40 +02:00
if ( global . mainWindow . isFullScreen ( ) ) {
global . mainWindow . once ( 'leave-full-screen' , ( ) = > global . mainWindow . hide ( ) ) ;
2021-05-05 04:55:28 +02:00
2022-07-01 21:17:40 +02:00
global . mainWindow . setFullScreen ( false ) ;
2021-05-05 04:55:28 +02:00
} else {
2022-07-01 21:17:40 +02:00
global . mainWindow . hide ( ) ;
2021-05-05 04:55:28 +02:00
}
2021-03-25 15:50:33 +01:00
return false ;
}
2019-12-06 19:17:34 +01:00
} ) ;
if ( process . platform === 'win32' ) {
// Handle forward/backward mouse buttons in Windows
2022-07-01 21:17:40 +02:00
global . mainWindow . on ( 'app-command' , ( e , cmd ) = > {
if ( cmd === 'browser-backward' && global . mainWindow . webContents . canGoBack ( ) ) {
global . mainWindow . webContents . goBack ( ) ;
} else if ( cmd === 'browser-forward' && global . mainWindow . webContents . canGoForward ( ) ) {
global . mainWindow . webContents . goForward ( ) ;
2019-12-06 19:17:34 +01:00
}
} ) ;
}
2022-07-01 21:17:40 +02:00
webContentsHandler ( global . mainWindow . webContents ) ;
2021-04-26 14:58:29 +02:00
2022-07-01 21:17:40 +02:00
global . appLocalization = new AppLocalization ( {
store : global.store ,
2021-04-26 14:58:29 +02:00
components : [
( ) = > tray . initApplicationMenu ( ) ,
( ) = > Menu . setApplicationMenu ( buildMenuTemplate ( ) ) ,
] ,
} ) ;
2019-12-06 19:17:34 +01:00
} ) ;
app . on ( 'window-all-closed' , ( ) = > {
app . quit ( ) ;
} ) ;
app . on ( 'activate' , ( ) = > {
2022-07-01 21:17:40 +02:00
global . mainWindow . show ( ) ;
2019-12-06 19:17:34 +01:00
} ) ;
2022-05-23 16:44:29 +02:00
function beforeQuit ( ) : void {
2019-12-06 19:17:34 +01:00
global . appQuitting = true ;
2022-07-01 21:17:40 +02:00
global . mainWindow ? . webContents . send ( 'before-quit' ) ;
2020-05-23 12:52:49 +02:00
}
app . on ( 'before-quit' , beforeQuit ) ;
2021-06-25 15:35:58 +02:00
autoUpdater . on ( 'before-quit-for-update' , beforeQuit ) ;
2019-12-06 19:17:34 +01:00
app . on ( 'second-instance' , ( ev , commandLine , workingDirectory ) = > {
// If other instance launched with --hidden then skip showing window
if ( commandLine . includes ( '--hidden' ) ) return ;
// Someone tried to run a second instance, we should focus our window.
2022-07-01 21:17:40 +02:00
if ( global . mainWindow ) {
if ( ! global . mainWindow . isVisible ( ) ) global . mainWindow . show ( ) ;
if ( global . mainWindow . isMinimized ( ) ) global . mainWindow . restore ( ) ;
global . mainWindow . focus ( ) ;
2019-12-06 19:17:34 +01:00
}
} ) ;
// Set the App User Model ID to match what the squirrel
// installer uses for the shortcut icon.
// This makes notifications work on windows 8.1 (and is
// a noop on other platforms).
2020-07-01 16:30:53 +02:00
app . setAppUserModelId ( 'com.squirrel.element-desktop.Element' ) ;