import { app , BrowserWindow , ipcMain , nativeImage , WebContents , } from 'electron' ; import * as fs from 'fs' ; import * as os from 'os' ; import * as path from 'path' ; import { ChildProcess , ExecException , execFile } from 'child_process' ; import * as util from 'util' ; import { IScreenSnippet } from '../common/api-interface' ; import { isDevEnv , isElectronQA , isLinux , isMac , isWindowsOS , } from '../common/env' ; import { i18n } from '../common/i18n' ; import { logger } from '../common/logger' ; import { analytics , AnalyticsElements , ScreenSnippetActionTypes , } from './analytics-handler' ; import { updateAlwaysOnTop } from './window-actions' ; import { windowHandler } from './window-handler' ; import { windowExists } from './window-utils' ; const readFile = util . promisify ( fs . readFile ) ; class ScreenSnippet { private readonly tempDir : string ; private outputFilePath : string | undefined ; private captureUtil : string ; private captureUtilArgs : ReadonlyArray < string > ; private child : ChildProcess | undefined ; private focusedWindow : BrowserWindow | null = null ; private shouldUpdateAlwaysOnTop : boolean = false ; constructor ( ) { if ( isElectronQA ) { this . tempDir = os . tmpdir ( ) ; } else { this . tempDir = path . join ( app . getPath ( 'userData' ) , 'temp' ) ; if ( ! fs . existsSync ( this . tempDir ) ) { fs . mkdirSync ( this . tempDir ) ; } } this . captureUtil = '' ; this . captureUtilArgs = [ ] ; ipcMain . on ( 'snippet-analytics-data' , async ( _event , eventData ) => { analytics . track ( { element : eventData . element , action_type : eventData . type , } ) ; } ) ; } param webContents { WeContents } public async capture ( webContents ) { const mainWindow = windowHandler . getMainWindow ( ) ; if ( mainWindow && windowExists ( mainWindow ) && isWindowsOS ) { this . shouldUpdateAlwaysOnTop = mainWindow . isAlwaysOnTop ( ) ; if ( this . shouldUpdateAlwaysOnTop ) { await updateAlwaysOnTop ( false , false , false ) ; } } logger . info ( `screen-snippet-handler: Starting screen capture!` ) ; this . outputFilePath = path . join ( this . tempDir , 'symphonyImage-' + Date . now ( ) + '.png' ) ; if ( isMac ) { logger . info ( 'screen-snippet-handler: Mac' ) ; this . captureUtil = '/usr/sbin/screencapture' ; this . captureUtilArgs = [ '-i' , '-s' , '-t' , 'png' , this . outputFilePath ] ; } else if ( isWindowsOS ) { logger . info ( 'screen-snippet-handler: Windows' ) ; if ( windowHandler . isMana ) { logger . info ( 'screen-snippet-handler: Mana, no native annotate' ) ; this . captureUtil = isDevEnv ? path . join ( __dirname , '../../../node_modules/screen-snippet/ScreenSnippet.exe' ) : path . join ( path . dirname ( app . getPath ( 'exe' ) ) , 'ScreenSnippet.exe' ) ; this . captureUtilArgs = [ '--no-annotate' , this . outputFilePath , i18n . getLocale ( ) , ] ; } else { this . captureUtil = isDevEnv ? path . join ( __dirname , '../../../node_modules/screen-snippet/ScreenSnippet.exe' ) : path . join ( path . dirname ( app . getPath ( 'exe' ) ) , 'ScreenSnippet.exe' ) ; this . captureUtilArgs = [ this . outputFilePath , i18n . getLocale ( ) ] ; } } else if ( isLinux ) { this . captureUtil = '/usr/bin/gnome-screenshot' ; this . captureUtilArgs = [ '-a' , '-f' , this . outputFilePath ] ; } this . focusedWindow = BrowserWindow . getFocusedWindow ( ) ; logger . info ( `screen-snippet-handler: Capturing snippet with file ${ this . outputFilePath } and args ${ this . captureUtilArgs }!` ) ; if ( this . child ) { logger . info ( `screen-snippet-handler: Child screen capture exists, killing it and keeping only 1 instance!` ) ; this . killChildProcess ( ) ; } try { await this . execCmd ( this . captureUtil , this . captureUtilArgs ) ; if ( windowHandler . isMana ) { logger . info ( 'screen-snippet-handler: Attempting to extract image dimensions from: ' + this . outputFilePath ) ; const dimensions = this . getImageDimensions ( this . outputFilePath ) ; logger . info ( 'screen-snippet-handler: Extracted dimensions from image: ' + JSON . stringify ( dimensions ) ) ; if ( ! dimensions ) { logger . error ( 'screen-snippet-handler: Could not get image size' ) ; return ; } if ( dimensions . width === 0 && dimensions . height === 0 ) { logger . info ( 'screen-snippet-handler: no screen capture picture' ) ; return ; } windowHandler . closeSnippingToolWindow ( ) ; windowHandler . createSnippingToolWindow ( this . outputFilePath , dimensions ) ; this . uploadSnippet ( webContents ) ; return ; } const { message , data , type , } = await this . convertFileToData ( ) ; logger . info ( `screen-snippet-handler: Snippet captured! Sending data straight to SFE without opening annotate tool` ) ; webContents . send ( 'screen-snippet-data' , { message , data , type } ) ; await this . verifyAndUpdateAlwaysOnTop ( ) ; } catch ( error ) { await this . verifyAndUpdateAlwaysOnTop ( ) ; logger . error ( `screen-snippet-handler: screen capture failed, user probably escaped the capture. Error: ${ error }!` ) ; } } /**
     * Cancels a screen capture and closes the snippet window
     */ public async cancelCapture ( ) { if ( ! isWindowsOS || windowHandler . isMana || this . captureUtil === '' ) { return ; } logger . info ( `screen-snippet-handler: Cancel screen capture!` ) ; this . focusedWindow = BrowserWindow . getFocusedWindow ( ) ; try { await this . execCmd ( this . captureUtil , [ ] ) ; await this . verifyAndUpdateAlwaysOnTop ( ) ; } catch ( error ) { await this . verifyAndUpdateAlwaysOnTop ( ) ; logger . error ( `screen-snippet-handler: screen capture cancel failed with error: ${ error }!` ) ; } } /**
     * Kills the child process when the application is reloaded
     */ public killChildProcess ( ) { if ( this . child && typeof this . child . kill === 'function' ) { this . child . kill ( ) ; } } param captureUtil { string } param captureUtilArgs { captureUtilArgs } example private execCmd ( captureUtil , captureUtilArgs ) { logger . info ( `screen-snippet-handlers: execCmd ${ captureUtil } ${ captureUtilArgs }` ) ; return new Promise < void > ( ( resolve , reject ) => { return ( this . child = execFile ( captureUtil , captureUtilArgs , ( error ) => { if ( error && error . killed ) { return reject ( error ) ; } resolve ( ) ; } ) ) ; } ) ; } return private async convertFileToData ( ) { try { if ( ! this . outputFilePath ) { logger . info ( `screen-snippet-handler: screen capture failed! output file doesn't exist!` ) ; return { message : 'output file name is required' , type : 'ERROR' } ; } const data = await readFile ( this . outputFilePath ) ; if ( ! data ) { logger . info ( `screen-snippet-handler: screen capture failed! data doesn't exist!` ) ; return { message : `no file data provided` , type : 'ERROR' } ; } const output = Buffer . from ( data ) . toString ( 'base64' ) ; return { message : 'success' , data : output , type : 'image/png;base64' } ; } catch ( error ) { return error && error . code === 'ENOENT' ? { message : `file does not exist` , type : 'ERROR' } : { message : `${ error }` , type : 'ERROR' } ; } finally { if ( this . focusedWindow && windowExists ( this . focusedWindow ) ) { this . focusedWindow . moveTop ( ) ; } } } /**
     * Verify and updates always on top
     */ private async verifyAndUpdateAlwaysOnTop ( ) { if ( this . shouldUpdateAlwaysOnTop ) { await updateAlwaysOnTop ( true , false , false ) ; this . shouldUpdateAlwaysOnTop = false ; } } param filePath private getImageDimensions ( filePath ) { const img = nativeImage . createFromPath ( filePath ) ; const size = img . getSize ( ) ; return size ; } param webContents private uploadSnippet ( webContents ) { ipcMain . once ( 'upload-snippet' , async ( _event , snippetData ) => { try { windowHandler . closeSnippingToolWindow ( ) ; const [ type , data ] = snippetData . mergedImageData . split ( ',' ) ; const payload = { message : 'SUCCESS' , data , type , } ; logger . info ( 'screen-snippet-handler: Snippet uploaded correctly, sending payload to SFE' ) ; webContents . send ( 'screen-snippet-data' , payload ) ; await this . verifyAndUpdateAlwaysOnTop ( ) ; } catch ( error ) { await this . verifyAndUpdateAlwaysOnTop ( ) ; logger . error ( `screen-snippet-handler: upload of screen capture failed with error: ${ error }!` ) ; } } ) ; } } const screenSnippet = new ScreenSnippet ( ) ; export { screenSnippet } ;