/**
* @file Contains all main code
* @author yafp
* @namespace main
*/
'use strict'
// -----------------------------------------------------------------------------
// REQUIRE: 3rd PARTY
// -----------------------------------------------------------------------------
const { app, BrowserWindow, Menu, Tray, ipcMain, globalShortcut } = require('electron')
const shell = require('electron').shell // for: opening external urls in default browser
const isOnline = require('is-online') // for online connectivity checks
const path = require('path')
const fs = require('fs')
const os = require('os') // for: check os.platform()
const openAboutWindow = require('about-window').default // for: about-window
require('v8-compile-cache') // via: https://dev.to/xxczaki/how-to-make-your-electron-app-faster-4ifb
// -----------------------------------------------------------------------------
// Shared object
// -----------------------------------------------------------------------------
//
const consoleOutput = false // can be changed using --verbose
// Settings Tab
const settingDefaultView = ''
const settingTheme = 'default'
const settingAutostart = ''
const settingDisableTray = false
const settingUrgentWindow = false
const settingEnableErrorReporting = true
const settingEnablePrereleases = false
global.sharedObj = {
// verbose mode aka console Output
consoleOutput: consoleOutput,
// settings
settingDefaultView: settingDefaultView,
settingTheme: settingTheme,
settingAutostart: settingAutostart,
settingDisableTray: settingDisableTray,
settingUrgentWindow: settingUrgentWindow,
settingEnableErrorReporting: settingEnableErrorReporting,
settingEnablePrereleases: settingEnablePrereleases
}
// ----------------------------------------------------------------------------
// REQUIRE: TTTH MODULES
// ----------------------------------------------------------------------------
const urls = require('./app/js/ttth/modules/urlsGithub.js') // the github urls
const crash = require('./app/js/ttth/modules/crashReporter.js') // crashReporter
const sentry = require('./app/js/ttth/modules/sentry.js') // sentry
const unhandled = require('./app/js/ttth/modules/unhandled.js') // electron-unhandled
// ----------------------------------------------------------------------------
// COMMAND-LINE-ARGS
// ----------------------------------------------------------------------------
const yargs = require('yargs')
.strict(true) // arguments must be valid
.usage('Usage: $0 <command> [options]')
// define arguments
.option('gpu', {
alias: 'g',
type: 'boolean',
description: 'Starts ttth with gpu-acceleration enabled'
})
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Starts ttth with verbose output'
})
.option('help', {
alias: 'h',
type: 'boolean',
description: 'Shows the ttth help'
})
.option('version', {
type: 'boolean',
description: 'Shows the ttth version'
})
// displayed in case of invalid parameters
.showHelpOnFail(false, 'Specify --help to display the available options')
// show the project url
.epilog('Project URL: https://github.com/yafp/ttth')
// Show an example
// .example('$0 --help', 'Shows the media-dupes help')
.argv
if (yargs.verbose === true) {
global.sharedObj.consoleOutput = true // update the global object
}
// # 188
if (yargs.gpu === true) {
// nothing to do here as it is enabled by default
// writeLog('info', 'GPU acceleration is now enabled')
} else {
app.disableHardwareAcceleration() // https://www.electronjs.org/docs/api/app#appdisablehardwareacceleration
// writeLog('info', 'GPU acceleration is now disabled')
}
// ----------------------------------------------------------------------------
// ERROR-HANDLING:
// ----------------------------------------------------------------------------
crash.initCrashReporter()
unhandled.initUnhandled()
sentry.enableSentry() // sentry is enabled by default
// ----------------------------------------------------------------------------
// HARDWARE ACCELERATION:
// ----------------------------------------------------------------------------
// #188 - added in 1.9.0
// for testing use: --disable-gpu
//
// Disables hardware acceleration for current app. This method can only be called before app is ready.
// app.disableHardwareAcceleration() // https://www.electronjs.org/docs/api/app#appdisablehardwareacceleration
// var foo = app.getAppMetrics()
// console.error(foo)
// -----------------------------------------------------------------------------
// VARIABLES
// -----------------------------------------------------------------------------
// Keep a global reference of the window objects, if you don't, the window
// will be closed automatically when the JavaScript object is garbage collected.
let mainWindow = null
let configWindow = null
// let windowConfig = null // gonna implement this in 1.10.0
const gotTheLock = app.requestSingleInstanceLock() // for: single-instance handling
const defaultUserDataPath = app.getPath('userData') // for: storing window position and size
const userOSPlatform = os.platform() // for BadgeCount support - see #152
const defaultMainWindowWidth = 800
const defaultMainWindowHeight = 600
// -----------------------------------------------------------------------------
// FUNCTIONS
// -----------------------------------------------------------------------------
/**
* @function writeLog
* @summary Writes console output for the main process
* @description Writes console output for the main process
* @memberof main
* @param {string} type - The log type
* @param {string} message - The log message
* @param {string} optionalObject - An optional object which might contain additional informations
*/
function writeLog (type, message, optionalObject = '') {
const logM = require('electron-log')
const prefix = '[ Main ] '
if (global.sharedObj.consoleOutput === false) {
logM.transports.console.level = false // disable Terminal output. Still logs to DevTools and LogFile
}
// important: https://github.com/megahertz/electron-log/issues/189
// electron-log can: error, warn, info, verbose, debug, silly
switch (type) {
case 'info':
logM.info(prefix + message, optionalObject)
break
case 'warn':
logM.warn(prefix + message, optionalObject)
break
case 'error':
logM.error(prefix + message, optionalObject)
break
default:
logM.silly(prefix + message, optionalObject)
break
}
}
/**
* @function checkNetworkConnectivity
* @summary Checks if internet is accessible
* @description Checks if the internet is accessible, if not triggers an error in the mainWindow
* @memberof main
*/
function checkNetworkConnectivity () {
(async () => {
if (await isOnline() === true) {
writeLog('info', 'checkNetworkConnectivity ::: Got access to the internet.')
} else {
writeLog('error', 'checkNetworkConnectivity ::: Got NO access to the internet.')
mainWindow.webContents.send('showNoConnectivityError') // app should show an error
}
})()
}
/**
* @function showDialog
* @summary Shows a dialog
* @description Displays a dialog - see https://electronjs.org/docs/api/dialog
* @memberof main
* @param {string} dialogType - Can be "none", "info", "error", "question" or "warning"
* @param {string} dialogTitle - The title text
* @param {string} dialogMessage - The message of the dialog
* @param {string} dialogDetail - The detail text
*/
function showDialog (dialogType, dialogTitle, dialogMessage, dialogDetail) {
const { dialog } = require('electron')
const options = {
type: dialogType,
buttons: ['OK'],
defaultId: 2,
title: dialogTitle,
message: dialogMessage,
detail: dialogDetail
}
dialog.showMessageBox(null, options, (response, checkboxChecked) => {
// console.log(response);
})
}
/**
* @function createTray
* @summary Creates the tray of the app
* @description Creates the tray and the related menu.
* @memberof main
*/
function createTray () {
writeLog('info', 'createTray ::: Starting to create a tray item')
let tray = null
tray = new Tray(path.join(__dirname, 'app/img/tray/tray_default.png'))
const contextMenu = Menu.buildFromTemplate([
{
// Window focus
id: 'show',
label: 'Show',
click: function () {
if (mainWindow === null) {
// #134
// do nothing, as no mainWindow exists. Most likely on macOS
} else {
// focus the main window
if (mainWindow.isMinimized()) {
mainWindow.restore()
} else {
// is not minimized. Was maybe: hidden via hide()
mainWindow.show()
}
mainWindow.focus()
}
},
enabled: true
},
{
type: 'separator',
enabled: false
},
{
// Quit
id: 'exit',
label: 'Exit',
enabled: true,
click: function () {
app.quit()
}
}
])
tray.setToolTip('ttth')
tray.setContextMenu(contextMenu)
writeLog('info', 'createTray ::: Finished creating tray')
// Call from renderer: Change Tray Icon to UnreadMessages
ipcMain.on('changeTrayIconToUnreadMessages', function () {
if (tray.isDestroyed() === false) {
tray.setImage(path.join(__dirname, 'app/img/tray/tray_unread.png'))
}
})
// Call from renderer: Change Tray Icon to Default
ipcMain.on('changeTrayIconToDefault', function () {
if (tray.isDestroyed() === false) {
tray.setImage(path.join(__dirname, 'app/img/tray/tray_default.png'))
}
})
// Call from renderer: Option: Urgent window - see #110
ipcMain.on('makeWindowUrgent', function () {
mainWindow.flashFrame(true) // #110 - urgent window
})
// Call from renderer: Option: DisableTray
ipcMain.on('disableTray', function () {
writeLog('info', 'ipcMain.disableTray ::: Disabling tray (ipcMain)')
tray.destroy()
if (tray.isDestroyed() === true) {
writeLog('info', 'ipcMain.disableTray ::: Disabling tray was working')
} else {
writeLog('error', 'ipcMain.disableTray ::: Disabling tray failed')
}
})
}
/**
* @function createWindowConfig
* @summary Creates the config window of the app
* @description Creates the config window
* @memberof main
*/
/*
function createWindowConfig () {
writeLog('info', 'createWindow ::: Starting to create the application windows')
// Create the browser window.
windowConfig = new BrowserWindow({
// parent: mainWindow,
modal: true,
frame: true, // false results in a borderless window. Needed for custom titlebar
titleBarStyle: 'default', // needed for custom-electron-titlebar. See: https://electronjs.org/docs/api/frameless-window
backgroundColor: '#ffffff',
show: true,
center: true, // Show window in the center of the screen
width: 800,
minWidth: 800,
// resizable: false, // this conflickts with opening dev tools
minimizable: false, // not implemented on linux
maximizable: false, // not implemented on linux
height: 700,
minHeight: 700,
icon: path.join(__dirname, 'app/img/icon/icon.png'),
webPreferences: {
nodeIntegration: true,
webSecurity: true // introduced in 0.3.0
}
})
// and load the setting.html of the app.
windowConfig.loadFile('app/configWindow.html')
// window needs no menu
windowConfig.removeMenu()
// Call from renderer: Settings UI - toggle dev tools
ipcMain.on('settingsToggleDevTools', function () {
settingsWindow.webContents.toggleDevTools()
})
// Emitted before the window is closed.
windowConfig.on('close', function () {
writeLog('info', 'createWindowConfig ::: windowConfig will close (event: close)')
})
// Emitted when the window is closed.
windowConfig.on('closed', function (event) {
writeLog('info', 'createWindowConfig ::: windowConfig is closed (event: closed)')
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
windowConfig = null
// unblur main UI
mainWindow.webContents.send('unblurMainUI')
})
}
*/
/**
* @function createWindow
* @summary Creates the main window of the app
* @description Creates the main window, restores window position and size of possible
* @memberof main
*/
function createWindow () {
writeLog('info', 'createWindow ::: Starting to create the application windows')
// Variables for window position and size
let windowWidth
let windowHeight
let windowPositionX
let windowPositionY
// Try to read stored last window position and size
const customUserDataPath = path.join(defaultUserDataPath, 'ttthMainWindowPosSize.json')
let data
try {
data = JSON.parse(fs.readFileSync(customUserDataPath, 'utf8'))
// size
windowWidth = data.bounds.width
windowHeight = data.bounds.height
// position
windowPositionX = data.bounds.x
windowPositionY = data.bounds.y
writeLog('info', 'createWindow ::: Got last window position and size information from _' + customUserDataPath + '_.')
} catch (e) {
writeLog('warn', 'createWindow ::: No last window position and size information found in _' + customUserDataPath + '_. Using fallback values')
// set some default values for window size
windowWidth = defaultMainWindowWidth
windowHeight = defaultMainWindowHeight
}
// Create the browser window.
mainWindow = new BrowserWindow({
// title: '${productName}',
frame: false, // false results in a borderless window
show: false, // hide until: ready-to-show
titleBarStyle: 'hidden', // needed for custom-electron-titlebar
width: windowWidth,
height: windowHeight,
minWidth: defaultMainWindowWidth,
minHeight: defaultMainWindowHeight,
center: true, // Show window in the center of the screen. (since 1.7.0)
backgroundColor: '#ffffff',
icon: path.join(__dirname, 'app/img/icon/icon.png'),
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true,
webSecurity: true, // introduced in 1.8.0
experimentalFeatures: false, // introduced in 1.8.0
webviewTag: true, // # see #37
devTools: true, // should be possible to open them
partition: 'ttth'
}
})
writeLog('info', 'createWindow ::: Finished creating the mainWindow')
// Restore window position if possible
// requirements: found values in .ttthMainWindowPosSize.json from the previous session
//
if ((typeof windowPositionX !== 'undefined') && (typeof windowPositionY !== 'undefined')) {
writeLog('info', 'createWindow ::: Restoring last stored window-position of mainWindow')
mainWindow.setPosition(windowPositionX, windowPositionY)
}
// Call from renderer: Update property from globalObj
ipcMain.on('globalObjectSet', function (event, property, value) {
writeLog('info', 'ipcMain.globalObjectSet ::: Set property _' + property + '_ to new value: _' + value + '_')
global.sharedObj[property] = value
writeLog('info', 'ipcMain.globalObjectSet ::: sharedObj: ', global.sharedObj)
})
// Load the UI (mainWindow.html) of the app.
mainWindow.loadFile('./app/mainWindow.html')
writeLog('info', 'createWindow ::: Loading mainWindow.html to mainWindow')
// show the formerly hidden main window as it is fully ready now
mainWindow.on('ready-to-show', function () {
mainWindow.show()
mainWindow.focus()
writeLog('info', 'mainWindow.on.ready-to-show ::: mainWindow is now ready, so show it and then focus it')
checkNetworkConnectivity() // check network access
})
// Emitted when the application has finished basic startup.
mainWindow.on('will-finish-launching', function () {
writeLog('info', 'mainWindow.on.will-finish-launching ::: mainWindow will finish launching')
})
// When dom is ready
mainWindow.webContents.once('dom-ready', () => {
writeLog('info', 'mainWindow.on.ready ::: mainwWindow DOM is now ready')
})
// When page title gets changed
mainWindow.webContents.once('page-title-updated', () => {
writeLog('info', 'mainWindow.on.page-title-updated ::: mainWindow got new title')
})
// when the app is shown
mainWindow.on('show', function () {
writeLog('info', 'mainWindow.on.show ::: mainWindow is visible')
})
// when the app loses focus / aka blur
mainWindow.on('blur', function () {
writeLog('info', 'mainWindow.on.blur ::: mainWindow lost focus')
})
// when the app gets focus
mainWindow.on('focus', function () {
writeLog('info', 'mainWindow.on.focus ::: mainWindow got focus')
})
// when the app goes fullscreen
mainWindow.on('enter-full-screen', function () {
writeLog('info', 'mainWindow.on.enter-full-screen ::: mainWindow is now in fullscreen')
})
// when the app goes leaves fullscreen
mainWindow.on('leave-full-screen', function () {
// disabled to reduce clutter
})
// when the app gets resized
mainWindow.on('resize', function () {
// disabled to reduce clutter
})
// when the app gets hidden
mainWindow.on('hide', function () {
writeLog('info', 'mainWindow.on.hide ::: mainWindow is now hidden')
})
// when the app gets maximized
mainWindow.on('maximize', function () {
writeLog('info', 'mainWindow.on.maximize ::: mainWindow is now maximized')
})
// when the app gets unmaximized
mainWindow.on('unmaximize', function () {
writeLog('info', 'mainWindow.on.unmaximize ::: mainWindow is now unmaximized')
})
// when the app gets minimized
mainWindow.on('minimize', function () {
writeLog('info', 'mainWindow.on.minimize ::: mainWindow is now minimized')
})
// when the app gets restored from minimized mode
mainWindow.on('restore', function () {
writeLog('info', 'mainWindow.on.restore ::: mainWindow is now restored')
})
mainWindow.on('app-command', function () {
writeLog('info', 'mainWindow.on.app-command ::: mainWindow got app-command')
})
// Emitted before the window is closed.
mainWindow.on('close', function () {
writeLog('info', 'mainWindow.on.close ::: mainWindow will close')
// get current window position and size
const data = {
bounds: mainWindow.getBounds()
}
// define target path (in user data) to store rthe values
const customUserDataPath = path.join(defaultUserDataPath, 'ttthMainWindowPosSize.json')
// try to write the window position and size to preference file
fs.writeFile(customUserDataPath, JSON.stringify(data), function (error) {
if (error) {
writeLog('error', 'mainWindow.on.close ::: storing window-position and -size of mainWindow in _' + customUserDataPath + '_ failed with error: _' + error + '_.')
writeLog('error', 'mainWindow.on.close ::: Error: ', error)
return console.log(error)
}
writeLog('info', 'mainWindow.on.close ::: Successfully stored window-position and -size in _' + customUserDataPath + '_.')
})
})
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
writeLog('info', 'mainWindow.on.closed ::: mainWindow is now closed')
})
// When the app is unresponsive
mainWindow.on('unresponsive', function () {
writeLog('error', 'mainWindow.on.unresponsive ::: mainWindow is now unresponsive')
showDialog('error', 'Alert', 'ttth seems unresponsive', 'Consider restarting the app')
})
// When the app gets responsive again
mainWindow.on('responsive', function () {
writeLog('info', 'mainWindow.on.responsive ::: mainWindow is now responsive again')
})
// When the app is crashed
mainWindow.webContents.on('crashed', function () {
writeLog('info', 'mainWindow.on.crashed ::: mainWindow crashed')
showDialog('error', 'Alert', 'ttth just crashed', 'Consider reporting this issue')
})
// Call from renderer: Reload mainWindow
ipcMain.on('reloadMainWindow', (event) => {
mainWindow.reload()
writeLog('info', 'ipcMain.reloadMainWindow ::: mainWindow is now reloaded (ipcMain)')
})
// Call from renderer: Open folder with user configured services
ipcMain.on('openUserServicesConfigFolder', (event) => {
const customUserDataPath = path.join(defaultUserDataPath, 'storage')
if (shell.openPath(customUserDataPath) === true) {
writeLog('info', 'ipcMain.openUserServicesConfigFolder ::: ServiceConfigs: Opened the folder _' + customUserDataPath + '_ which contains all user-configured services')
} else {
writeLog('warn', 'ipcMain.openUserServicesConfigFolder ::: ServiceConfigs: Failed to open the folder _' + customUserDataPath + '_ (which contains all user-configured services).')
}
})
// Call from renderer: Open folder with user settings
ipcMain.on('openUserSettingsConfigFolder', (event) => {
const customUserDataPath = path.join(defaultUserDataPath, 'ttthUserSettings')
if (shell.openPath(customUserDataPath) === true) {
writeLog('info', 'ipcMain.openUserSettingsConfigFolder ::: UserSettings: Opened the folder _' + customUserDataPath + '_ which contains all user-configured services')
} else {
writeLog('warn', 'ipcMain.openUserSettingsConfigFolder ::: UserSettings: Failed to open the folder _' + customUserDataPath + '_ (which contains all user-configured services).')
}
})
// Call from renderer ::: deleteAllGlobalServicesShortcut
ipcMain.on('deleteAllGlobalServicesShortcut', function (arg1, numberOfEnabledServices) {
globalShortcut.unregisterAll() // doesnt work - whyever
writeLog('info', 'ipcMain.deleteAllGlobalServicesShortcut ::: Shortcuts: Deleting all global service shortcut at once.')
// delete all global shortcuts manually
/*
var i;
for (i = 1; i <= numberOfEnabledServices; i++)
{
globalShortcut.unregister("CmdOrCtrl+" + i);
writeLog("info", "createWindow ::: Shortcuts: Deleting the global service shortcut: CmdOrCtrl+" + i);
}
*/
writeLog('info', 'ipcMain.deleteAllGlobalServicesShortcut ::: Shortcuts: Finished deleting all global service shortcuts')
})
// Call from renderer ::: createNewGlobalShortcut
ipcMain.on('createNewGlobalShortcut', function (arg1, shortcut, targetTab) {
writeLog('info', 'ipcMain.createNewGlobalShortcut ::: Shortcuts: Creating a new shortcut: _' + shortcut + '_ for the service/tab: _' + targetTab + '_.')
// const ret = globalShortcut.register(shortcut, () => {
globalShortcut.register(shortcut, () => {
writeLog('error', 'Shortcut: _' + shortcut + '_ was pressed.')
mainWindow.webContents.send('switchToTab', targetTab) // activate the related tab
})
})
// Call from renderer: should ttth update the app badge count
// is supported for macOS & Linux (running Unity)
ipcMain.on('updateBadgeCount', (event, arg) => {
let environmentSupported = false
// Possible values are 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', and 'win32'.
switch (userOSPlatform) {
case 'darwin':
environmentSupported = true
break
case 'linux':
const checkForUnity = app.isUnityRunning()
if (checkForUnity === true) {
environmentSupported = true
}
break
default:
// do nothing
}
// if the environment supports BadgeCount - update it
if (environmentSupported === true) {
// temporary hack - cause of #182
if (Number.isNaN(arg)) {
writeLog('warn', 'ipcMain.updateBadgeCount ::: Returned value is not a number (NaN). Falling back to value 0') // updating badge count worked
arg = 0 // set a fallback value - see #182
}
const currentBadgeCount = app.getBadgeCount() // get the current badge count
// FIXME: deprecated - Please use 'badgeCount property' instead.
// if badge count has to be updated - try to update it
if (currentBadgeCount !== arg) {
const didUpdateBadgeCount = app.setBadgeCount(arg) // FIXME: deprecated.- Please use 'badgeCount property' instead.
if (didUpdateBadgeCount === true) {
writeLog('info', 'ipcMain.updateBadgeCount ::: Updating application badge count to _' + arg + '_.') // updating badge count worked
} else {
writeLog('warn', 'ipcMain.updateBadgeCount ::: Updating application badge count to _' + arg + '_ failed.') // updating badge count failed
}
}
}
})
// *****************************************************************
// modal window: to allow creating and configuring a single service
// *****************************************************************
//
configWindow = new BrowserWindow({
parent: mainWindow,
modal: true, // Whether this is a modal window. This only works when the window is a child window
// title: '${productName}',
frame: false, // false results in a borderless window
show: false, // hide as default
titleBarStyle: 'hidden',
resizable: false,
width: 600,
height: 650,
minWidth: 600,
minHeight: 650,
backgroundColor: '#ffffff',
icon: path.join(__dirname, 'app/img/icon/icon.png'),
webPreferences: {
enableRemoteModule: true,
nodeIntegration: true,
webviewTag: true // see #37
}
})
writeLog('info', 'createWindow ::: Finished creating configWindow')
// load html form to the window
configWindow.loadFile('app/configWindow.html')
writeLog('info', 'createWindow ::: Loaded configWindow.html to configWindow')
// hide menubar
configWindow.setMenuBarVisibility(false)
writeLog('info', 'createWindow ::: Hiding menubar of configWindow')
// Emitted when the window gets a close event.(close VS closed)
configWindow.on('close', function (event) {
writeLog('info', 'configWindow will close, but we hide it (event: close)')
configWindow.hide() // just hide it - so it can re-opened
})
// Emitted when the window is ready to be shown
configWindow.on('ready-to-show', function (event) {
writeLog('info', 'configWindow is now ready to show (event: ready-to-show)')
// do some checks & routines once at start of the application
mainWindow.webContents.send('startSearchUpdatesSilent') // search silently for ttth updates
})
// Emitted when the window is shown
configWindow.on('show', function (event) {
writeLog('info', 'configWindow is now shown (event: show)')
})
// Call from renderer: show configure-single-service window for a new service
ipcMain.on('showConfigureSingleServiceWindowNew', (event, arg) => {
writeLog('info', 'ipcMain.showConfigureSingleServiceWindowNew ::: configWindow preparing for new service creation')
configWindow.show() // show window
configWindow.webContents.send('serviceToCreate', arg)
})
// Call from renderer: show configure-single-service window
ipcMain.on('showConfigureSingleServiceWindow', (event, arg) => {
writeLog('info', 'ipcMain.showConfigureSingleServiceWindow ::: configWindow preparing for service editing')
configWindow.show() // show window
configWindow.webContents.send('serviceToConfigure', arg)
})
// Call from renderer: hide configure-single-service window
ipcMain.on('closeConfigureSingleServiceWindow', (event) => {
configWindow.hide() // hide window
writeLog('info', 'ipcMain.closeConfigureSingleServiceWindow ::: configWindow is now hidden')
})
// Call from renderer: Tray: RecreateTray
ipcMain.on('recreateTray', function () {
writeLog('info', 'ipcMain.recreateTray ::: Recreating tray')
createTray()
})
writeLog('info', 'createWindow ::: Finished creating mainWindow and configWindow')
}
/**
* @function forceSingleAppInstance
* @summary Takes care that there is only 1 instance of this app running
* @description Takes care that there is only 1 instance of this app running
* @memberof main
*/
function forceSingleAppInstance () {
writeLog('info', 'forceSingleAppInstance ::: Checking if there is only 1 instance of ttth')
if (!gotTheLock) {
writeLog('error', 'forceSingleAppInstance ::: There is already another instance of ttth')
app.quit() // quit the second instance
} else {
app.on('second-instance', (event, commandLine, workingDirectory) => {
// Someone tried to run a second instance, we should focus our first instance window.
if (mainWindow) {
// #134
/*
if (mainWindow === null) {
// do nothing - there is no mainwindow - most likely we are on macOS
} else {
// mainWindow exists
if (mainWindow.isMinimized()) {
mainWindow.restore()
}
mainWindow.focus()
}
*/
// simplify:
// mainWindow exists
if (mainWindow.isMinimized()) {
mainWindow.restore()
}
mainWindow.focus()
}
})
}
}
/**
* @function createMenu
* @summary Creates the application menu
* @description Creates the application menu
* @memberof main
*/
function createMenu () {
// Create a custom menu
const menu = Menu.buildFromTemplate([
// Menu: File
{
label: 'File',
submenu: [
// Settings
{
label: 'Settings',
click (item, mainWindow) {
mainWindow.webContents.send('showSettings')
},
accelerator: 'CmdOrCtrl+,'
},
// Separator
{
type: 'separator'
},
// Exit
{
role: 'quit',
label: 'Exit',
click () {
app.quit()
},
accelerator: 'CmdOrCtrl+Q'
}
]
},
// Menu: Edit
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:'
}
]
},
// Menu: View
{
label: 'View',
submenu: [
{
label: 'Next Service',
click (item, mainWindow) {
mainWindow.webContents.send('nextTab')
},
accelerator: 'CmdOrCtrl+right'
},
{
label: 'Previous Service',
click (item, mainWindow) {
mainWindow.webContents.send('previousTab')
},
accelerator: 'CmdOrCtrl+left'
},
{
type: 'separator'
},
{
role: 'reload',
label: 'Reload',
click (item, mainWindow) {
mainWindow.reload()
},
accelerator: 'CmdOrCtrl+R'
},
{
label: 'Reload current service',
click (item, mainWindow) {
mainWindow.webContents.send('reloadCurrentService')
},
accelerator: 'CmdOrCtrl+S',
enabled: true
}
]
},
// Menu: Window
{
label: 'Window',
submenu: [
{
role: 'togglefullscreen',
label: 'Toggle Fullscreen',
click (item, mainWindow) {
if (mainWindow.isFullScreen()) {
mainWindow.setFullScreen(false)
} else {
mainWindow.setFullScreen(true)
}
},
accelerator: 'F11' // is most likely predefined on osx - results in: doesnt work on osx
},
{
role: 'hide',
label: 'Hide',
click (item, mainWindow) {
mainWindow.hide()
// mainWindow.reload();
},
accelerator: 'CmdOrCtrl+H',
enabled: true
},
{
role: 'minimize',
label: 'Minimize',
click (item, mainWindow) {
if (mainWindow.isMinimized()) {
// mainWindow.restore();
} else {
mainWindow.minimize()
}
},
accelerator: 'CmdOrCtrl+M'
},
{
label: 'Maximize',
click (item, mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
} else {
mainWindow.maximize()
}
},
accelerator: 'CmdOrCtrl+K'
}
]
},
// Menu: Help
{
role: 'help',
label: 'Help',
submenu: [
// About
{
// role: 'about', - see: https://github.com/rhysd/electron-about-window/issues/59
label: 'About',
click () {
openAboutWindow({
icon_path: path.join(__dirname, 'app/img/about/icon_about.png'),
open_devtools: false,
use_version_info: true,
win_options: // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#new-browserwindowoptions
{
autoHideMenuBar: true,
titleBarStyle: 'hidden',
minimizable: false, // not implemented on linux
maximizable: false, // not implemented on linux
movable: false, // not implemented on linux
resizable: false,
alwaysOnTop: true,
fullscreenable: false,
skipTaskbar: false
}
})
}
},
// open homepage
{
label: 'Homepage',
click () {
shell.openExternal(urls.urlGitHubGeneral)
},
accelerator: 'F1'
},
// report issue
{
label: 'Report issue',
click () {
shell.openExternal(urls.urlGitHubIssues)
},
accelerator: 'F2'
},
// open changelog
{
label: 'Changelog',
click () {
shell.openExternal(urls.urlGitHubChangelog)
},
accelerator: 'F3'
},
// open FAQ
{
label: 'FAQ',
click () {
shell.openExternal(urls.urlGitHubFAQ)
},
accelerator: 'F4'
},
// open Releases
{
label: 'Releases',
click () {
shell.openExternal(urls.urlGitHubReleases)
},
accelerator: 'F5'
},
{
type: 'separator'
},
// Update
{
label: 'Search updates',
click (item, mainWindow) {
mainWindow.webContents.send('startSearchUpdates')
},
enabled: true,
accelerator: 'F9'
},
{
type: 'separator'
},
// SubMenu Console
{
label: 'Console',
submenu: [
// console for current service
{
id: 'HelpConsoleCurrentService',
label: 'Console for current service',
click (item, mainWindow) {
mainWindow.webContents.send('openDevToolForCurrentService')
},
enabled: true,
accelerator: 'F10'
},
// Console
{
id: 'HelpConsole',
label: 'Console',
click (item, mainWindow) {
mainWindow.webContents.toggleDevTools()
},
enabled: true,
accelerator: 'F12'
}
]
},
{
type: 'separator'
},
// SubMenu of help
{
label: 'Maintenance',
submenu: [
// Clear cache in userData
{
id: 'ClearCache',
label: 'Clear cache',
click (item, mainWindow) {
const chromeCacheDir = path.join(app.getPath('userData'), 'Cache')
if (fs.existsSync(chromeCacheDir)) {
const files = fs.readdirSync(chromeCacheDir)
for (let i = 0; i < files.length; i++) {
const filename = path.join(chromeCacheDir, files[i])
if (fs.existsSync(filename)) {
try {
fs.unlinkSync(filename)
} catch (error) {
console.log(error)
}
}
}
}
mainWindow.reload()
},
enabled: true
}
]
}
]
}
])
// use the menu
Menu.setApplicationMenu(menu)
// OPTIONAL & currently not in use:
//
// Disable some menu-elements - depending on the platform
//
/*
var os = require("os");
Menu.getApplicationMenu().items; // all the items
// macos specific
if(os.platform() === "darwin")
{
// see #21 - disable the menuitem Toggle-menubar
//var item = Menu.getApplicationMenu().getMenuItemById("ViewToggleMenubar");
//item.enabled = false;
}
// linux specific
if(os.platform() === "linux")
{
// nothing to do so far
}
// windows specific
if(os.platform() === "windows")
{
// nothing to do so far
}
*/
}
// -----------------------------------------------------------------------------
// LETS GO
// -----------------------------------------------------------------------------
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
//
app.on('ready', function () {
writeLog('info', 'app got ready signal (event: ready)')
forceSingleAppInstance() // check for single instance
createWindow() // create the application UI
createMenu() // create the application menu
createTray() // create the tray
})
// Emitted while app tries to do a basic auth (https://electronjs.org/docs/api/app#event-login)
app.on('login', function () {
writeLog('info', 'app tries to do basic auth (event: login)')
})
// Emitted before the application starts closing its windows.
app.on('before-quit', function () {
writeLog('info', 'app is preparing to quit (event: before-quit)')
})
// Emitted when all windows have been closed and the application will quit.
app.on('will-quit', function () {
writeLog('info', 'app will quit (event: will-quit)')
})
// Emitted when the application is quitting.
app.on('quit', function () {
writeLog('info', 'app got quit event (event: quit)')
})
// Emitted when a browserWindow gets blurred. (loosing focus)
app.on('browser-window-blur', function () {
// writeLog("info", "app lost focus (event: browser-window-blur)");
})
// Emitted when a browserWindow gets focused.
app.on('browser-window-focus', function () {
// disabled to reduce clutter
// writeLog("info", "app got focus (event: browser-window-focus)");
})
// Emitted when failed to verify the certificate for url, to trust the certificate you should prevent the default behavior with event.preventDefault() and call callback(true).
app.on('certificate-error', function () {
writeLog('info', 'app failed to verify a cert (event: certificate-error)')
})
// Emitted when remote.require() is called in the renderer process of webContents.
app.on('remote-require', function () {
writeLog('info', 'app called .require() in the renderer process (event: remote-require)')
})
// Emitted when remote.getGlobal() is called in the renderer process of webContents.
app.on('remote-get-global', function () {
// writeLog("info", "app called .getGlobal() in the renderer process (event: remote-get-global)");
})
// Emitted when remote.getBuiltin() is called in the renderer process of webContents.
app.on('remote-get-builtin', function () {
// disabled to reduce clutter
// writeLog("info", "app called .getBuiltin() in the renderer process (event: remote-get-builtin)");
})
// Emitted when remote.getCurrentWindow() is called in the renderer process of webContents.
app.on('remote-get-current-window', function () {
// writeLog("info", "app called .getCurrentWindow() in the renderer process(event: remote-get-current-window)");
})
// Emitted when remote.getCurrentWebContents() is called in the renderer process of webContents
app.on('remote-get-current-web-contents', function () {
writeLog('info', 'app called .getCurrentWebContents() in the renderer process (event: remote-get-current-web-contents)')
})
// Quit when all windows are closed.
app.on('window-all-closed', function () {
writeLog('info', 'app closed all application windows (event: window-all-closed)')
// On macOS it is common for applications and their menu bar to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
writeLog('info', 'Bye')
app.quit()
}
// we handle all systemes the same - this means: close the mainWindow = the app closes as well - why: see #134
// writeLog("info", "Bye");
// app.quit();
})
// activate = macOS only:
// Emitted when the application is activated. Various actions can trigger this event, such as launching the application for the first time,
// attempting to re-launch the application when it's already running, or clicking on the application's dock or taskbar icon.
app.on('activate', function () {
writeLog('info', 'app got activate event (event: activate)')
// On macOS it's common to re-create a window in the app when the dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
writeLog('warn', 'Trying to re-create the mainWindow, as it doesnt exist anymore (event: activate)')
createWindow()
}
})
// Emitted when a new webContents is created.
// Try to set some values while creating new webviews. See: https://electronjs.org/docs/tutorial/security
app.on('web-contents-created', (event, contents) => {
contents.on('will-attach-webview', (event, webPreferences, params) => {
writeLog('info', 'app will attach new webview with target url set to: _' + params.src + '_.')
// Strip away preload scripts if unused or verify their location is legitimate
//
// delete webPreferences.preload
// delete webPreferences.preloadURL
// Disable Node.js integration
webPreferences.nodeIntegration = false
// Verify URL being loaded
//
// if (!params.src.startsWith('https://example.com/'))
// {
// event.preventDefault()
// }
})
})
// Emitted when a new browserWindow is created.
app.on('browser-window-created', function () {
writeLog('info', 'app created a browser window (event: browser-window-created)')
})
// Emitted when the application has finished basic startup.
app.on('will-finish-launching', function () {
writeLog('info', 'app will finish launching (event: will-finish-launching)')
})
// Emitted when the renderer process of webContents crashes or is killed.
app.on('renderer-process-crashed', function (event, webContents, killed) {
writeLog('error', 'app is realizing a crashed renderer process (event: renderer-process-crashed)')
writeLog('error', 'Event: ', event)
writeLog('error', 'webContents: ', webContents)
writeLog('error', 'killed: ', killed)
})
// Emitted when the GPU process crashes or is killed.
app.on('gpu-process-crashed', function () {
writeLog('error', 'app is realizing a crashed gpu process (event: gpu-process-crashed)')
})
// Emitted whenever there is a GPU info update.
app.on('gpu-info-update', function () {
writeLog('info', 'app.on-gpu-info-update ::: Realizing a GPU info update')
const gpuInfo = app.getGPUFeatureStatus() // https://www.electronjs.org/docs/api/app#appgetgpufeaturestatus
writeLog('info', 'app.on-gpu-info-update ::: Status: ', gpuInfo)
// console.error(gpuInfo)
})
// Emitted when failed to verify the certificate for url, to trust the certificate you should prevent the default behavior
// with event.preventDefault() and call callback(true).
app.on('certificate-error', function () {
writeLog('warn', 'app failed to verify the cert (event: certificate-error)')
})
process.on('uncaughtException', (err, origin) => {
fs.writeSync(
process.stderr.fd,
`Caught exception: ${err}\n` +
`Exception origin: ${origin}`
)
writeLog('error', 'UncaughtException - got error: _' + err + '_ with origin: _' + origin + '_.')
})