diff --git a/package-lock.json b/package-lock.json index d8fda9c2..8cf902bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36499,4 +36499,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/app/screen-snippet-handler.ts b/src/app/screen-snippet-handler.ts index 07ff0d75..e20c9069 100644 --- a/src/app/screen-snippet-handler.ts +++ b/src/app/screen-snippet-handler.ts @@ -4,6 +4,7 @@ import { clipboard, dialog, ipcMain, + NativeImage, nativeImage, WebContents, } from 'electron'; @@ -161,6 +162,7 @@ class ScreenSnippet { await this.execCmd(this.captureUtil, this.captureUtilArgs); if (windowHandler.isMana) { + winStore.restoreWindows(hideOnCapture); logger.info( 'screen-snippet-handler: Attempting to extract image dimensions from: ' + this.outputFilePath, @@ -177,7 +179,6 @@ class ScreenSnippet { if (dimensions.width === 0 && dimensions.height === 0) { logger.info('screen-snippet-handler: no screen capture picture'); - winStore.restoreWindowsOnCapturing(hideOnCapture); return; } @@ -356,7 +357,7 @@ class ScreenSnippet { 'screen-snippet-handler: Snippet uploaded correctly, sending payload to SFE', ); webContents.send('screen-snippet-data', payload); - winStore.focusWindowsSnippingFinished(hideOnCapture); + winStore.restoreWindows(hideOnCapture); await this.verifyAndUpdateAlwaysOnTop(); } catch (error) { await this.verifyAndUpdateAlwaysOnTop(); @@ -432,6 +433,9 @@ class ScreenSnippet { clipboard: string; }, ) => { + if (isMac) { + windowHandler.closeSnippingToolWindow(); + } const filePath = path.join( app.getPath('downloads'), 'symphonyImage-' + Date.now() + '.png', @@ -439,46 +443,11 @@ class ScreenSnippet { const [, data] = saveAsData.clipboard.split(','); const buffer = Buffer.from(data, 'base64'); const img = nativeImage.createFromBuffer(buffer); - - const dialogResult = await dialog - .showSaveDialog(BrowserWindow.getFocusedWindow() as BrowserWindow, { - title: 'Select place to store your file', - defaultPath: filePath, - // defaultPath: path.join(__dirname, '../assets/'), - buttonLabel: 'Save', - // Restricting the user to only Text Files. - filters: [ - { - name: 'Image file', - extensions: ['png'], - }, - ], - properties: [], - }) - .then((file) => { - // Stating whether dialog operation was cancelled or not. - if (!file.canceled && file.filePath) { - // Creating and Writing to the sample.txt file - fs.writeFile(file.filePath.toString(), img.toPNG(), (err) => { - if (err) { - throw logger.error( - `screen-snippet-handler: cannot save file, failed with error: ${err}!`, - ); - } - - logger.info(`screen-snippet-handler: modal save opened!`); - }); - } - - return file; - }) - .catch((err) => { - logger.error( - `screen-snippet-handler: cannot save file, failed with error: ${err}!`, - ); - - return undefined; - }); + const dialogResult = await this.saveFile( + filePath, + img, + BrowserWindow.getFocusedWindow(), + ); if (dialogResult?.filePath) { windowHandler.closeSnippingToolWindow(); } @@ -496,7 +465,6 @@ class ScreenSnippet { const windowObj = winStore.getWindowStore(); const currentWindowName = (currentWindowObj as ICustomBrowserWindow) ?.winName; - if (windowObj.windows.length < 1) { const allWindows = BrowserWindow.getAllWindows(); let windowsArr: IWindowState[] = []; @@ -531,12 +499,67 @@ class ScreenSnippet { } else { windowsArr = windowsArr.concat(mainArr); } - winStore.setWindowStore({ windows: windowsArr, }); } }; + + /** + * Save image in a given location + * @param filePath where the image should be stored + * @param img the image + * @param parent parent window to attach save dialog + */ + private saveFile = ( + filePath: string, + img: NativeImage, + parentWindow: BrowserWindow | null, + ) => { + const saveOptions = { + title: 'Select place to store your file', + defaultPath: filePath, + // defaultPath: path.join(__dirname, '../assets/'), + buttonLabel: 'Save', + // Restricting the user to only Text Files. + filters: [ + { + name: 'Image file', + extensions: ['png'], + }, + ], + properties: [], + }; + const saveDialog = + !isMac && parentWindow + ? dialog.showSaveDialog(parentWindow, saveOptions) + : dialog.showSaveDialog(saveOptions); + return saveDialog + .then((file) => { + // Stating whether dialog operation was cancelled or not. + if (!file.canceled && file.filePath) { + // Creating and Writing to the sample.txt file + fs.writeFile(file.filePath.toString(), img.toPNG(), (err) => { + if (err) { + throw logger.error( + `screen-snippet-handler: cannot save file, failed with error: ${err}!`, + ); + } + + logger.info(`screen-snippet-handler: modal save opened!`); + }); + } + + return file; + }) + .catch((err) => { + logger.error( + `screen-snippet-handler: cannot save file, failed with error: ${err}!`, + ); + + return undefined; + }); + }; } const screenSnippet = new ScreenSnippet(); diff --git a/src/app/stores/window-store.ts b/src/app/stores/window-store.ts index ca93ba58..89972da0 100644 --- a/src/app/stores/window-store.ts +++ b/src/app/stores/window-store.ts @@ -1,4 +1,6 @@ import { BrowserWindow } from 'electron'; +import { isMac, isWindowsOS } from '../../common/env'; +import { ICustomBrowserWindow, windowHandler } from '../window-handler'; import { getWindowByName } from '../window-utils'; export interface IWindowObject { @@ -9,9 +11,11 @@ export interface IWindowState { id: string; minimized?: boolean; focused?: boolean; + isFullScreen?: boolean; } export class WindowStore { + public windowsRestored: boolean = true; private windowVariable: IWindowObject = { windows: [], }; @@ -35,47 +39,112 @@ export class WindowStore { public hideWindowsOnCapturing = (hideOnCapture?: boolean) => { if (hideOnCapture) { + this.windowsRestored = false; const currentWindows = BrowserWindow.getAllWindows(); currentWindows.forEach((currentWindow) => { - currentWindow?.hide(); + const isFullScreen = currentWindow.isFullScreen(); + if (isFullScreen) { + this.hideFullscreenWindow(currentWindow); + } else { + currentWindow?.hide(); + } }); } }; - public focusWindowsSnippingFinished = (hideOnCapture?: boolean) => { + public restoreWindows = (hideOnCapture?: boolean) => { if (hideOnCapture) { - const currentWindows = this.getWindowStore(); - const currentWindow = currentWindows.windows.find( + const storedWindows = this.getWindowStore(); + let currentWindow = storedWindows.windows.find( (currentWindow) => currentWindow.focused, ); - - if (currentWindow) { - if (!currentWindow.minimized) { - getWindowByName(currentWindow.id || '')?.show(); - } - - if (currentWindow.focused) { - getWindowByName(currentWindow.id || '')?.focus(); - } + if (!currentWindow) { + // In case there is no window focused, we automatically focus on the main one. + currentWindow = storedWindows.windows.find( + (currentWindow) => currentWindow.id === 'main', + ); + currentWindow!.focused = true; } - } - }; - public restoreWindowsOnCapturing = (hideOnCapture?: boolean) => { - if (hideOnCapture) { - const currentWindows = this.getWindowStore(); - currentWindows.windows.forEach((currentWindow) => { - if (!currentWindow.minimized) { - getWindowByName(currentWindow.id || '')?.show(); - } + let focusedWindowToRestore: ICustomBrowserWindow | undefined; - if (currentWindow.focused) { - getWindowByName(currentWindow.id || '')?.focus(); + const fullscreenedWindows: IWindowState[] = []; + // Restoring all windows except focused one + storedWindows.windows.forEach((currentWindow) => { + if (currentWindow) { + const window: ICustomBrowserWindow | undefined = getWindowByName( + currentWindow.id || '', + ) as ICustomBrowserWindow; + if (window) { + if (currentWindow.isFullScreen) { + fullscreenedWindows.push(currentWindow); + // Window should be shown before putting it in fullscreen on Windows + if (isWindowsOS) { + window.show(); + } + } else if (!currentWindow.minimized && !currentWindow.focused) { + window.showInactive(); + } + if (currentWindow.focused) { + focusedWindowToRestore = window; + } + } } }); + // First item in array should be the focused window + fullscreenedWindows.sort((x: IWindowState, y: IWindowState) => + x.focused === y.focused ? 0 : x.focused ? -1 : 1, + ); + this.putWindowInFullScreenAndFocus( + fullscreenedWindows, + focusedWindowToRestore, + ); + + // Store reset this.destroyWindowStore(); } }; + + private hideFullscreenWindow = (window: BrowserWindow) => { + window.once('leave-full-screen', () => { + if (isMac) { + window.hide(); + } else { + setTimeout(() => { + window.hide(); + }, 0); + } + }); + window.setFullScreen(false); + }; + + /** + * Restores windows that are in fullscreen and focus on the right window + * On macOS, windows in fullscreen need to be restore one by one + * @param windowsNames + */ + private putWindowInFullScreenAndFocus( + windows: IWindowState[], + windowToFocus?: BrowserWindow, + ) { + if (windows.length) { + const windowDetails = windows[windows.length - 1]; + const window: ICustomBrowserWindow | undefined = getWindowByName( + windowDetails.id || '', + ) as ICustomBrowserWindow; + window.once('enter-full-screen', () => { + windows.pop(); + this.putWindowInFullScreenAndFocus(windows, windowToFocus); + }); + window.setFullScreen(true); + } else { + if (windowToFocus) { + windowToFocus?.show(); + windowHandler.moveSnippingToolWindow(windowToFocus); + } + this.windowsRestored = true; + } + } } diff --git a/src/app/window-handler.ts b/src/app/window-handler.ts index 2fd9bde0..c554f381 100644 --- a/src/app/window-handler.ts +++ b/src/app/window-handler.ts @@ -1057,11 +1057,18 @@ export class WindowHandler { } /** - * Move window to the same screen as main window + * Move window to the same screen as main window or provided parent window */ - public moveWindow(windowToMove: BrowserWindow, fixedYPosition?: number) { + public moveWindow( + windowToMove: BrowserWindow, + fixedYPosition?: number, + parentWindow?: BrowserWindow, + ) { if (this.mainWindow && windowExists(this.mainWindow)) { - const display = screen.getDisplayMatching(this.mainWindow.getBounds()); + let display = screen.getDisplayMatching(this.mainWindow.getBounds()); + if (parentWindow && windowExists(parentWindow)) { + display = screen.getDisplayMatching(parentWindow.getBounds()); + } logger.info( 'window-handler: moveWindow, display: ' + @@ -1238,7 +1245,7 @@ export class WindowHandler { this.hideOnCapture = !!hideOnCapture; logger.info( - 'window-handler, createSnippingToolWindow: Receiving snippet props: ' + + 'window-handler: createSnippingToolWindow: Receiving snippet props: ' + JSON.stringify({ snipImage, snipDimensions, @@ -1247,7 +1254,7 @@ export class WindowHandler { const allDisplays = screen.getAllDisplays(); logger.info( - 'window-handler, createSnippingToolWindow: User has these displays: ' + + 'window-handler: createSnippingToolWindow: User has these displays: ' + JSON.stringify(allDisplays), ); @@ -1263,7 +1270,10 @@ export class WindowHandler { const BUTTON_BAR_BOTTOM_HEIGHT = 72; const BUTTON_BARS_HEIGHT = BUTTON_BAR_TOP_HEIGHT + BUTTON_BAR_BOTTOM_HEIGHT; - const display = screen.getDisplayMatching(this.mainWindow.getBounds()); + const focusedWindow = BrowserWindow.getFocusedWindow(); + const display = screen.getDisplayMatching( + focusedWindow ? focusedWindow.getBounds() : this.mainWindow.getBounds(), + ); const workAreaSize = display.workAreaSize; // Snipping tool height shouldn't be greater than min of screen width and height (screen orientation can be portrait or landscape) const minSize = Math.min(workAreaSize.width, workAreaSize.height); @@ -1277,7 +1287,7 @@ export class WindowHandler { width: Math.floor(snipDimensions.width / scaleFactor), }; logger.info( - 'window-handler, createSnippingToolWindow: Image will open with scaled dimensions: ' + + 'window-handler: createSnippingToolWindow: Image will open with scaled dimensions: ' + JSON.stringify(scaledImageDimensions), ); @@ -1314,12 +1324,12 @@ export class WindowHandler { } this.currentWindow = currentWindow || ''; + const parentWindow = getWindowByName(this.currentWindow); const opts: ICustomBrowserWindowConstructorOpts = this.getWindowOpts( { width: toolWidth, height: toolHeight, - parent: getWindowByName(this.currentWindow), - modal: true, + modal: isWindowsOS, alwaysOnTop: this.hideOnCapture, resizable: false, fullscreenable: false, @@ -1337,8 +1347,16 @@ export class WindowHandler { opts.alwaysOnTop = true; } + const areWindowsRestoredPostHide = + (winStore.windowsRestored && this.hideOnCapture) || !this.hideOnCapture; + + if (isWindowsOS || (isMac && areWindowsRestoredPostHide)) { + opts.parent = parentWindow; + opts.modal = true; + } + this.snippingToolWindow = createComponentWindow('snipping-tool', opts); - this.moveWindow(this.snippingToolWindow); + this.moveWindow(this.snippingToolWindow, undefined, parentWindow); this.snippingToolWindow.setVisibleOnAllWorkspaces(true); this.snippingToolWindow.webContents.once('did-finish-load', async () => { @@ -1391,12 +1409,11 @@ export class WindowHandler { 'snipping-tool-data', snippingToolInfo, ); - winStore.restoreWindowsOnCapturing(this.hideOnCapture); } }); this.snippingToolWindow.once('close', () => { logger.info( - 'window-handler, createSnippingToolWindow: Closing snipping window, attempting to delete temp snip image', + 'window-handler: createSnippingToolWindow: Closing snipping window, attempting to delete temp snip image', ); ipcMain.removeAllListeners(ScreenShotAnnotation.COPY_TO_CLIPBOARD); ipcMain.removeAllListeners(ScreenShotAnnotation.SAVE_AS); @@ -1404,7 +1421,6 @@ export class WindowHandler { this.deleteFile(snipImage); this.removeWindow(opts.winKey); this.screenPickerWindow = null; - winStore.focusWindowsSnippingFinished(this.hideOnCapture); }); } @@ -2215,6 +2231,18 @@ export class WindowHandler { exportLogs(); } + /** + * Moves snipping tool to the right place after restoring all hidden windows + * @param focusedWindow Focused window where snipping tool window should be moved + */ + public moveSnippingToolWindow(focusedWindow: BrowserWindow): void { + if (this.snippingToolWindow && !this.snippingToolWindow.isDestroyed()) { + this.snippingToolWindow.setAlwaysOnTop(true); + this.snippingToolWindow.setParentWindow(focusedWindow); + this.moveWindow(this.snippingToolWindow, undefined, focusedWindow); + } + } + /** * Listens for app load timeouts and reloads if required */