2022-11-23 03:50:26 +01:00
process . traceDeprecation = true ;
process . traceProcessWarnings = true ;
2022-11-23 10:00:00 +01:00
const electron = require ( 'electron' ) ;
2016-11-21 19:05:19 +01:00
const isDev = require ( 'electron-is-dev' ) ;
2020-04-17 17:44:57 +02:00
const unhandled = require ( 'electron-unhandled' ) ;
2021-03-28 19:12:58 +02:00
const i18n = require ( 'i18next' ) ;
2022-01-11 16:47:33 +01:00
const debounce = require ( 'lodash/debounce' ) ;
2022-02-19 10:23:58 +01:00
const yargsParser = require ( 'yargs-parser' ) ;
const JSON5 = require ( 'json5' ) ;
2022-11-22 16:23:41 +01:00
const remote = require ( '@electron/remote/main' ) ;
2023-01-15 10:55:14 +01:00
const { stat } = require ( 'fs/promises' ) ;
2022-02-19 10:23:58 +01:00
2022-09-04 16:44:48 +02:00
const logger = require ( './logger' ) ;
2016-10-30 11:57:12 +01:00
const menu = require ( './menu' ) ;
2020-04-17 17:44:11 +02:00
const configStore = require ( './configStore' ) ;
2023-02-17 05:56:36 +01:00
const { frontendBuildDir } = require ( './util' ) ;
2016-10-30 11:57:12 +01:00
2018-02-17 15:15:30 +01:00
const { checkNewVersion } = require ( './update-checker' ) ;
2023-03-10 06:30:41 +01:00
const i18nCommon = require ( './i18n-common' ) ;
2021-03-28 19:12:58 +02:00
require ( './i18n' ) ;
2023-03-10 05:12:48 +01:00
const { app , ipcMain , shell , BrowserWindow , nativeTheme } = electron ;
2016-10-30 11:57:12 +01:00
2022-11-22 16:23:41 +01:00
remote . initialize ( ) ;
2020-04-17 17:44:57 +02:00
unhandled ( {
showDialog : true ,
} ) ;
2020-02-11 15:16:03 +01:00
app . name = 'LosslessCut' ;
2016-11-05 21:22:31 +01:00
2022-03-01 12:26:07 +01:00
let filesToOpen = [ ] ;
2022-02-19 10:23:58 +01:00
2016-10-30 11:57:12 +01:00
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow ;
2020-02-20 05:03:25 +01:00
let askBeforeClose = false ;
2020-03-31 13:59:05 +02:00
let rendererReady = false ;
2021-03-28 19:12:58 +02:00
let newVersion ;
2023-02-16 09:37:24 +01:00
let disableNetworking ;
2020-03-31 13:59:05 +02:00
2022-02-23 12:43:19 +01:00
const openFiles = ( paths ) => mainWindow . webContents . send ( 'openFiles' , paths ) ;
2020-02-20 05:03:25 +01:00
2023-02-15 06:59:48 +01:00
const isStoreBuild = process . windowsStore || process . mas ;
2022-01-11 16:47:33 +01:00
// https://github.com/electron/electron/issues/526#issuecomment-563010533
function getSizeOptions ( ) {
const bounds = configStore . get ( 'windowBounds' ) ;
const options = { } ;
if ( bounds ) {
const area = electron . screen . getDisplayMatching ( bounds ) . workArea ;
// If the saved position still valid (the window is entirely inside the display area), use it.
if (
bounds . x >= area . x
&& bounds . y >= area . y
&& bounds . x + bounds . width <= area . x + area . width
&& bounds . y + bounds . height <= area . y + area . height
) {
options . x = bounds . x ;
options . y = bounds . y ;
}
// If the saved size is still valid, use it.
if ( bounds . width <= area . width || bounds . height <= area . height ) {
options . width = bounds . width ;
options . height = bounds . height ;
}
}
return options ;
}
2016-10-30 11:57:12 +01:00
function createWindow ( ) {
2023-03-10 05:12:48 +01:00
const darkMode = configStore . get ( 'darkMode' ) ;
// todo follow darkMode setting when user switches
2023-03-10 05:53:22 +01:00
// https://www.electronjs.org/docs/latest/tutorial/dark-mode
2023-03-10 05:12:48 +01:00
if ( darkMode ) nativeTheme . themeSource = 'dark' ;
2016-10-30 11:57:12 +01:00
mainWindow = new BrowserWindow ( {
2022-01-11 16:47:33 +01:00
... getSizeOptions ( ) ,
2016-10-30 11:57:12 +01:00
darkTheme : true ,
2019-11-04 04:32:03 +01:00
webPreferences : {
2021-04-01 16:29:15 +02:00
enableRemoteModule : true ,
contextIsolation : false ,
2019-11-04 04:32:03 +01:00
nodeIntegration : true ,
2020-03-04 11:41:40 +01:00
// https://github.com/electron/electron/issues/5107
webSecurity : ! isDev ,
2019-11-04 04:32:03 +01:00
} ,
2023-03-10 05:12:48 +01:00
backgroundColor : darkMode ? '#333' : '#fff' ,
2016-10-30 11:57:12 +01:00
} ) ;
2020-03-04 11:41:40 +01:00
2022-11-22 16:23:41 +01:00
remote . enable ( mainWindow . webContents ) ;
2020-12-13 14:36:12 +01:00
if ( isDev ) mainWindow . loadURL ( 'http://localhost:3001' ) ;
// Need to useloadFile for special characters https://github.com/mifi/lossless-cut/issues/40
2023-02-17 05:56:36 +01:00
else mainWindow . loadFile ( ` ${ frontendBuildDir } /index.html ` ) ;
2016-10-30 11:57:12 +01:00
2020-02-24 11:18:44 +01:00
// Open the DevTools.
// mainWindow.webContents.openDevTools()
2020-03-04 11:41:40 +01:00
// Emitted when the window is closed.
2016-10-30 11:57:12 +01:00
mainWindow . on ( '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.
mainWindow = null ;
} ) ;
2020-02-18 10:06:57 +01:00
// https://stackoverflow.com/questions/39574636/prompt-to-save-quit-before-closing-window/47434365
mainWindow . on ( 'close' , ( e ) => {
2020-02-20 05:03:25 +01:00
if ( ! askBeforeClose ) return ;
2020-02-18 10:06:57 +01:00
const choice = electron . dialog . showMessageBoxSync ( mainWindow , {
type : 'question' ,
buttons : [ 'Yes' , 'No' ] ,
2021-03-28 19:12:58 +02:00
title : i18n . t ( 'Confirm quit' ) ,
message : i18n . t ( 'Are you sure you want to quit?' ) ,
2020-02-18 10:06:57 +01:00
} ) ;
if ( choice === 1 ) {
e . preventDefault ( ) ;
}
} ) ;
2022-01-11 16:47:33 +01:00
const debouncedSaveWindowState = debounce ( ( ) => {
if ( ! mainWindow ) return ;
const { x , y , width , height } = mainWindow . getNormalBounds ( ) ;
configStore . set ( 'windowBounds' , { x , y , width , height } ) ;
} , 500 ) ;
mainWindow . on ( 'resize' , debouncedSaveWindowState ) ;
mainWindow . on ( 'move' , debouncedSaveWindowState ) ;
2016-10-30 11:57:12 +01:00
}
2021-03-28 19:12:58 +02:00
function updateMenu ( ) {
2023-02-15 06:59:48 +01:00
menu ( { app , mainWindow , newVersion , isStoreBuild } ) ;
2021-03-28 19:12:58 +02:00
}
2022-10-14 22:31:01 +02:00
function openFilesEventually ( paths ) {
if ( rendererReady ) openFiles ( paths ) ;
else filesToOpen = paths ;
}
2022-02-19 10:23:58 +01:00
// https://github.com/electron/electron/issues/3657
// https://github.com/mifi/lossless-cut/issues/357
// https://github.com/mifi/lossless-cut/issues/639
// https://github.com/mifi/lossless-cut/issues/591
2022-10-14 22:31:01 +02:00
function parseCliArgs ( rawArgv = process . argv ) {
2023-02-19 03:24:26 +01:00
const ignoreFirstArgs = process . defaultApp ? 2 : 1 ;
2022-02-19 10:23:58 +01:00
// production: First arg is the LosslessCut executable
// dev: First 2 args are electron and the electron.js
2022-10-14 22:31:01 +02:00
const argsWithoutAppName = rawArgv . length > ignoreFirstArgs ? rawArgv . slice ( ignoreFirstArgs ) : [ ] ;
2022-02-19 10:23:58 +01:00
2023-02-16 09:37:24 +01:00
return yargsParser ( argsWithoutAppName , { boolean : [ 'allow-multiple-instances' , 'disable-networking' ] } ) ;
2022-02-19 10:23:58 +01:00
}
2022-10-14 22:31:01 +02:00
const argv = parseCliArgs ( ) ;
2021-04-08 17:52:15 +02:00
2023-03-10 06:30:41 +01:00
if ( argv . localesPath != null ) i18nCommon . setCustomLocalesPath ( argv . localesPath ) ;
2022-12-23 11:33:57 +01:00
function safeRequestSingleInstanceLock ( additionalData ) {
2022-11-23 09:46:04 +01:00
if ( process . mas ) return true ; // todo remove when fixed https://github.com/electron/electron/issues/35540
2022-12-23 11:33:57 +01:00
// using additionalData because the built in "argv" passing is a bit broken:
// https://github.com/electron/electron/issues/20322
return app . requestSingleInstanceLock ( additionalData ) ;
2022-11-23 09:46:04 +01:00
}
2023-02-19 10:44:11 +01:00
function initApp ( ) {
2022-10-14 22:31:01 +02:00
// On macOS, the system enforces single instance automatically when users try to open a second instance of your app in Finder, and the open-file and open-url events will be emitted for that.
// However when users start your app in command line, the system's single instance mechanism will be bypassed, and you have to use this method to ensure single instance.
// This can be tested with one terminal: npx electron .
// and another terminal: npx electron . path/to/file.mp4
2022-12-23 11:33:57 +01:00
app . on ( 'second-instance' , ( event , commandLine , workingDirectory , additionalData ) => {
2022-10-14 22:31:01 +02:00
// Someone tried to run a second instance, we should focus our window.
if ( mainWindow ) {
if ( mainWindow . isMinimized ( ) ) mainWindow . restore ( ) ;
mainWindow . focus ( ) ;
}
2020-04-17 17:44:11 +02:00
2023-02-05 12:08:40 +01:00
if ( ! Array . isArray ( additionalData ? . argv ) ) return ;
2022-12-23 11:33:57 +01:00
const argv2 = parseCliArgs ( additionalData . argv ) ;
2022-10-14 22:31:01 +02:00
if ( argv2 . _ ) openFilesEventually ( argv2 . _ ) ;
} ) ;
2022-02-19 10:23:58 +01:00
2022-10-14 22:31:01 +02:00
// Quit when all windows are closed.
app . on ( 'window-all-closed' , ( ) => {
app . quit ( ) ;
} ) ;
2020-02-20 05:03:25 +01:00
2022-10-14 22:31:01 +02:00
app . on ( 'activate' , ( ) => {
// On OS X 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 ) {
createWindow ( ) ;
}
} ) ;
2020-04-17 17:44:11 +02:00
2022-10-14 22:31:01 +02:00
ipcMain . on ( 'renderer-ready' , ( ) => {
rendererReady = true ;
if ( filesToOpen . length > 0 ) openFiles ( filesToOpen ) ;
} ) ;
// Mac OS open with LosslessCut
// Emitted when the user wants to open a file with the application. The open-file event is usually emitted when the application is already open and the OS wants to reuse the application to open the file.
app . on ( 'open-file' , ( event , path ) => {
openFilesEventually ( [ path ] ) ;
event . preventDefault ( ) ; // recommended in docs https://www.electronjs.org/docs/latest/api/app#event-open-file-macos
} ) ;
ipcMain . on ( 'setAskBeforeClose' , ( e , val ) => {
askBeforeClose = val ;
} ) ;
ipcMain . on ( 'setLanguage' , ( e , language ) => {
i18n . changeLanguage ( language ) . then ( ( ) => updateMenu ( ) ) . catch ( ( err ) => logger . error ( 'Failed to set language' , err ) ) ;
} ) ;
2023-01-15 10:55:14 +01:00
ipcMain . handle ( 'tryTrashItem' , async ( e , path ) => {
try {
await stat ( path ) ;
} catch ( err ) {
if ( err . code === 'ENOENT' ) return ;
}
await shell . trashItem ( path ) ;
} ) ;
2022-10-14 22:31:01 +02:00
}
2021-03-28 19:12:58 +02:00
2023-02-19 10:44:11 +01:00
// This promise will be fulfilled when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
// Call this immediately, to make sure we don't miss it (race condition)
const readyPromise = app . whenReady ( ) ;
( async ( ) => {
try {
logger . info ( 'Initializing config store' ) ;
await configStore . init ( ) ;
// todo remove backwards compat:
if ( argv . allowMultipleInstances ) configStore . set ( 'allowMultipleInstances' , true ) ;
const allowMultipleInstances = configStore . get ( 'allowMultipleInstances' ) ;
if ( ! allowMultipleInstances && ! safeRequestSingleInstanceLock ( { argv : process . argv } ) ) {
logger . info ( 'Found running instance, quitting' ) ;
app . quit ( ) ;
return ;
}
initApp ( ) ;
logger . info ( 'Waiting for app to become ready' ) ;
await readyPromise ;
logger . info ( 'CLI arguments' , argv ) ;
// Only if no files to open already (open-file might have already added some files)
if ( filesToOpen . length === 0 ) filesToOpen = argv . _ ;
const { settingsJson } = argv ;
( { disableNetworking } = argv ) ;
if ( settingsJson != null ) {
logger . info ( 'initializing settings' , settingsJson ) ;
Object . entries ( JSON5 . parse ( settingsJson ) ) . forEach ( ( [ key , value ] ) => {
configStore . set ( key , value ) ;
} ) ;
}
if ( isDev ) {
const { default : installExtension , REACT _DEVELOPER _TOOLS } = require ( 'electron-devtools-installer' ) ; // eslint-disable-line global-require,import/no-extraneous-dependencies
installExtension ( REACT _DEVELOPER _TOOLS )
. then ( name => logger . info ( 'Added Extension' , name ) )
. catch ( err => logger . error ( 'Failed to add extension' , err ) ) ;
}
createWindow ( ) ;
updateMenu ( ) ;
const enableUpdateCheck = configStore . get ( 'enableUpdateCheck' ) ;
if ( ! disableNetworking && enableUpdateCheck && ! isStoreBuild ) {
newVersion = await checkNewVersion ( ) ;
// newVersion = '1.2.3';
if ( newVersion ) updateMenu ( ) ;
}
} catch ( err ) {
logger . error ( 'Failed to initialize' , err ) ;
}
} ) ( ) ;
2020-05-03 12:41:41 +02:00
function focusWindow ( ) {
try {
2020-05-03 15:15:38 +02:00
app . focus ( { steal : true } ) ;
2020-05-03 12:41:41 +02:00
} catch ( err ) {
2022-09-04 16:44:48 +02:00
logger . error ( 'Failed to focus window' , err ) ;
2020-05-03 12:41:41 +02:00
}
}
2023-02-16 09:37:24 +01:00
const hasDisabledNetworking = ( ) => ! ! disableNetworking ;
module . exports = { focusWindow , isDev , hasDisabledNetworking } ;