|
|
|
@@ -1,7 +1,7 @@
|
|
|
|
|
import { SELECTION_FINETUNED_LIST, SELECTION_PREDEFINED_BLACKLIST } from '@main/configs/SelectionConfig'
|
|
|
|
|
import { isDev, isMac, isWin } from '@main/constant'
|
|
|
|
|
import { IpcChannel } from '@shared/IpcChannel'
|
|
|
|
|
import { BrowserWindow, ipcMain, screen, systemPreferences } from 'electron'
|
|
|
|
|
import { app, BrowserWindow, ipcMain, screen, systemPreferences } from 'electron'
|
|
|
|
|
import Logger from 'electron-log'
|
|
|
|
|
import { join } from 'path'
|
|
|
|
|
import type {
|
|
|
|
@@ -509,54 +509,55 @@ export class SelectionService {
|
|
|
|
|
//should set every time the window is shown
|
|
|
|
|
this.toolbarWindow!.setAlwaysOnTop(true, 'screen-saver')
|
|
|
|
|
|
|
|
|
|
// [macOS] a series of hacky ways only for macOS
|
|
|
|
|
if (isMac) {
|
|
|
|
|
// [macOS] a hacky way
|
|
|
|
|
// when set `skipTransformProcessType: true`, if the selection is in self app, it will make the selection canceled after toolbar showing
|
|
|
|
|
// so we just don't set `skipTransformProcessType: true` when in self app
|
|
|
|
|
const isSelf = ['com.github.Electron', 'com.kangfenmao.CherryStudio'].includes(programName)
|
|
|
|
|
|
|
|
|
|
if (!isSelf) {
|
|
|
|
|
// [macOS] an ugly hacky way
|
|
|
|
|
// `focusable: true` will make mainWindow disappeared when `setVisibleOnAllWorkspaces`
|
|
|
|
|
// so we set `focusable: true` before showing, and then set false after showing
|
|
|
|
|
this.toolbarWindow!.setFocusable(false)
|
|
|
|
|
|
|
|
|
|
// [macOS]
|
|
|
|
|
// force `setVisibleOnAllWorkspaces: true` to let toolbar show in all workspaces. And we MUST not set it to false again
|
|
|
|
|
// set `skipTransformProcessType: true` to avoid dock icon spinning when `setVisibleOnAllWorkspaces`
|
|
|
|
|
this.toolbarWindow!.setVisibleOnAllWorkspaces(true, {
|
|
|
|
|
visibleOnFullScreen: true,
|
|
|
|
|
skipTransformProcessType: true
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [macOS] MUST use `showInactive()` to prevent other windows bring to front together
|
|
|
|
|
// [Windows] is OK for both `show()` and `showInactive()` because of `focusable: false`
|
|
|
|
|
this.toolbarWindow!.showInactive()
|
|
|
|
|
|
|
|
|
|
// [macOS] restore the focusable status
|
|
|
|
|
this.toolbarWindow!.setFocusable(true)
|
|
|
|
|
|
|
|
|
|
if (!isMac) {
|
|
|
|
|
this.toolbarWindow!.show()
|
|
|
|
|
/**
|
|
|
|
|
* [Windows]
|
|
|
|
|
* In Windows 10, setOpacity(1) will make the window completely transparent
|
|
|
|
|
* It's a strange behavior, so we don't use it for compatibility
|
|
|
|
|
*/
|
|
|
|
|
// this.toolbarWindow!.setOpacity(1)
|
|
|
|
|
this.startHideByMouseKeyListener()
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The following is for Windows
|
|
|
|
|
*/
|
|
|
|
|
/************************************************
|
|
|
|
|
* [macOS] the following code is only for macOS
|
|
|
|
|
*
|
|
|
|
|
* WARNING:
|
|
|
|
|
* DO NOT MODIFY THESE CODES, UNLESS YOU REALLY KNOW WHAT YOU ARE DOING!!!!
|
|
|
|
|
*************************************************/
|
|
|
|
|
|
|
|
|
|
this.toolbarWindow!.show()
|
|
|
|
|
// [macOS] a hacky way
|
|
|
|
|
// when set `skipTransformProcessType: true`, if the selection is in self app, it will make the selection canceled after toolbar showing
|
|
|
|
|
// so we just don't set `skipTransformProcessType: true` when in self app
|
|
|
|
|
const isSelf = ['com.github.Electron', 'com.kangfenmao.CherryStudio'].includes(programName)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* [Windows]
|
|
|
|
|
* In Windows 10, setOpacity(1) will make the window completely transparent
|
|
|
|
|
* It's a strange behavior, so we don't use it for compatibility
|
|
|
|
|
*/
|
|
|
|
|
// this.toolbarWindow!.setOpacity(1)
|
|
|
|
|
if (!isSelf) {
|
|
|
|
|
// [macOS] an ugly hacky way
|
|
|
|
|
// `focusable: true` will make mainWindow disappeared when `setVisibleOnAllWorkspaces`
|
|
|
|
|
// so we set `focusable: true` before showing, and then set false after showing
|
|
|
|
|
this.toolbarWindow!.setFocusable(false)
|
|
|
|
|
|
|
|
|
|
// [macOS]
|
|
|
|
|
// force `setVisibleOnAllWorkspaces: true` to let toolbar show in all workspaces. And we MUST not set it to false again
|
|
|
|
|
// set `skipTransformProcessType: true` to avoid dock icon spinning when `setVisibleOnAllWorkspaces`
|
|
|
|
|
this.toolbarWindow!.setVisibleOnAllWorkspaces(true, {
|
|
|
|
|
visibleOnFullScreen: true,
|
|
|
|
|
skipTransformProcessType: true
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [macOS] MUST use `showInactive()` to prevent other windows bring to front together
|
|
|
|
|
// [Windows] is OK for both `show()` and `showInactive()` because of `focusable: false`
|
|
|
|
|
this.toolbarWindow!.showInactive()
|
|
|
|
|
|
|
|
|
|
// [macOS] restore the focusable status
|
|
|
|
|
this.toolbarWindow!.setFocusable(true)
|
|
|
|
|
|
|
|
|
|
this.startHideByMouseKeyListener()
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -911,6 +912,7 @@ export class SelectionService {
|
|
|
|
|
refPoint = { x: Math.round(refPoint.x), y: Math.round(refPoint.y) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [macOS] isFullscreen is only available on macOS
|
|
|
|
|
this.showToolbarAtPosition(refPoint, refOrientation, selectionData.programName)
|
|
|
|
|
this.toolbarWindow!.webContents.send(IpcChannel.Selection_TextSelected, selectionData)
|
|
|
|
|
}
|
|
|
|
@@ -1218,20 +1220,26 @@ export class SelectionService {
|
|
|
|
|
return actionWindow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public processAction(actionItem: ActionItem): void {
|
|
|
|
|
/**
|
|
|
|
|
* Process action item
|
|
|
|
|
* @param actionItem Action item to process
|
|
|
|
|
* @param isFullScreen [macOS] only macOS has the available isFullscreen mode
|
|
|
|
|
*/
|
|
|
|
|
public processAction(actionItem: ActionItem, isFullScreen: boolean = false): void {
|
|
|
|
|
const actionWindow = this.popActionWindow()
|
|
|
|
|
|
|
|
|
|
actionWindow.webContents.send(IpcChannel.Selection_UpdateActionData, actionItem)
|
|
|
|
|
|
|
|
|
|
this.showActionWindow(actionWindow)
|
|
|
|
|
this.showActionWindow(actionWindow, isFullScreen)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Show action window with proper positioning relative to toolbar
|
|
|
|
|
* Ensures window stays within screen boundaries
|
|
|
|
|
* @param actionWindow Window to position and show
|
|
|
|
|
* @param isFullScreen [macOS] only macOS has the available isFullscreen mode
|
|
|
|
|
*/
|
|
|
|
|
private showActionWindow(actionWindow: BrowserWindow): void {
|
|
|
|
|
private showActionWindow(actionWindow: BrowserWindow, isFullScreen: boolean = false): void {
|
|
|
|
|
let actionWindowWidth = this.ACTION_WINDOW_WIDTH
|
|
|
|
|
let actionWindowHeight = this.ACTION_WINDOW_HEIGHT
|
|
|
|
|
|
|
|
|
@@ -1241,11 +1249,14 @@ export class SelectionService {
|
|
|
|
|
actionWindowHeight = this.lastActionWindowSize.height
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//center way
|
|
|
|
|
if (!this.isFollowToolbar || !this.toolbarWindow) {
|
|
|
|
|
const display = screen.getDisplayNearestPoint(screen.getCursorScreenPoint())
|
|
|
|
|
const workArea = display.workArea
|
|
|
|
|
/********************************************
|
|
|
|
|
* Setting the position of the action window
|
|
|
|
|
********************************************/
|
|
|
|
|
const display = screen.getDisplayNearestPoint(screen.getCursorScreenPoint())
|
|
|
|
|
const workArea = display.workArea
|
|
|
|
|
|
|
|
|
|
// Center of the screen
|
|
|
|
|
if (!this.isFollowToolbar || !this.toolbarWindow) {
|
|
|
|
|
const centerX = workArea.x + (workArea.width - actionWindowWidth) / 2
|
|
|
|
|
const centerY = workArea.y + (workArea.height - actionWindowHeight) / 2
|
|
|
|
|
|
|
|
|
@@ -1255,54 +1266,107 @@ export class SelectionService {
|
|
|
|
|
x: Math.round(centerX),
|
|
|
|
|
y: Math.round(centerY)
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
// Follow toolbar position
|
|
|
|
|
const toolbarBounds = this.toolbarWindow!.getBounds()
|
|
|
|
|
const GAP = 6 // 6px gap from screen edges
|
|
|
|
|
|
|
|
|
|
//make sure action window is inside screen
|
|
|
|
|
if (actionWindowWidth > workArea.width - 2 * GAP) {
|
|
|
|
|
actionWindowWidth = workArea.width - 2 * GAP
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actionWindowHeight > workArea.height - 2 * GAP) {
|
|
|
|
|
actionWindowHeight = workArea.height - 2 * GAP
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate initial position to center action window horizontally below toolbar
|
|
|
|
|
let posX = Math.round(toolbarBounds.x + (toolbarBounds.width - actionWindowWidth) / 2)
|
|
|
|
|
let posY = Math.round(toolbarBounds.y)
|
|
|
|
|
|
|
|
|
|
// Ensure action window stays within screen boundaries with a small gap
|
|
|
|
|
if (posX + actionWindowWidth > workArea.x + workArea.width) {
|
|
|
|
|
posX = workArea.x + workArea.width - actionWindowWidth - GAP
|
|
|
|
|
} else if (posX < workArea.x) {
|
|
|
|
|
posX = workArea.x + GAP
|
|
|
|
|
}
|
|
|
|
|
if (posY + actionWindowHeight > workArea.y + workArea.height) {
|
|
|
|
|
// If window would go below screen, try to position it above toolbar
|
|
|
|
|
posY = workArea.y + workArea.height - actionWindowHeight - GAP
|
|
|
|
|
} else if (posY < workArea.y) {
|
|
|
|
|
posY = workArea.y + GAP
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
actionWindow.setPosition(posX, posY, false)
|
|
|
|
|
//KEY to make window not resize
|
|
|
|
|
actionWindow.setBounds({
|
|
|
|
|
width: actionWindowWidth,
|
|
|
|
|
height: actionWindowHeight,
|
|
|
|
|
x: posX,
|
|
|
|
|
y: posY
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isMac) {
|
|
|
|
|
actionWindow.show()
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//follow toolbar
|
|
|
|
|
const toolbarBounds = this.toolbarWindow!.getBounds()
|
|
|
|
|
const display = screen.getDisplayNearestPoint(screen.getCursorScreenPoint())
|
|
|
|
|
const workArea = display.workArea
|
|
|
|
|
const GAP = 6 // 6px gap from screen edges
|
|
|
|
|
/************************************************
|
|
|
|
|
* [macOS] the following code is only for macOS
|
|
|
|
|
*
|
|
|
|
|
* WARNING:
|
|
|
|
|
* DO NOT MODIFY THESE CODES, UNLESS YOU REALLY KNOW WHAT YOU ARE DOING!!!!
|
|
|
|
|
*************************************************/
|
|
|
|
|
|
|
|
|
|
//make sure action window is inside screen
|
|
|
|
|
if (actionWindowWidth > workArea.width - 2 * GAP) {
|
|
|
|
|
actionWindowWidth = workArea.width - 2 * GAP
|
|
|
|
|
// act normally when the app is not in fullscreen mode
|
|
|
|
|
if (!isFullScreen) {
|
|
|
|
|
actionWindow.show()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (actionWindowHeight > workArea.height - 2 * GAP) {
|
|
|
|
|
actionWindowHeight = workArea.height - 2 * GAP
|
|
|
|
|
}
|
|
|
|
|
// [macOS] an UGLY HACKY way for fullscreen override settings
|
|
|
|
|
|
|
|
|
|
// Calculate initial position to center action window horizontally below toolbar
|
|
|
|
|
let posX = Math.round(toolbarBounds.x + (toolbarBounds.width - actionWindowWidth) / 2)
|
|
|
|
|
let posY = Math.round(toolbarBounds.y)
|
|
|
|
|
// FIXME sometimes the dock will be shown when the action window is shown
|
|
|
|
|
// FIXME if actionWindow show on the fullscreen app, switch to other space will cause the mainWindow to be shown
|
|
|
|
|
// FIXME When setVisibleOnAllWorkspaces is true, docker icon disappeared when the first action window is shown on the fullscreen app
|
|
|
|
|
// use app.dock.show() to show the dock again will cause the action window to be closed when auto hide on blur is enabled
|
|
|
|
|
|
|
|
|
|
// Ensure action window stays within screen boundaries with a small gap
|
|
|
|
|
if (posX + actionWindowWidth > workArea.x + workArea.width) {
|
|
|
|
|
posX = workArea.x + workArea.width - actionWindowWidth - GAP
|
|
|
|
|
} else if (posX < workArea.x) {
|
|
|
|
|
posX = workArea.x + GAP
|
|
|
|
|
}
|
|
|
|
|
if (posY + actionWindowHeight > workArea.y + workArea.height) {
|
|
|
|
|
// If window would go below screen, try to position it above toolbar
|
|
|
|
|
posY = workArea.y + workArea.height - actionWindowHeight - GAP
|
|
|
|
|
} else if (posY < workArea.y) {
|
|
|
|
|
posY = workArea.y + GAP
|
|
|
|
|
}
|
|
|
|
|
// setFocusable(false) to prevent the action window hide when blur (if auto hide on blur is enabled)
|
|
|
|
|
actionWindow.setFocusable(false)
|
|
|
|
|
actionWindow.setAlwaysOnTop(true, 'floating')
|
|
|
|
|
|
|
|
|
|
actionWindow.setPosition(posX, posY, false)
|
|
|
|
|
//KEY to make window not resize
|
|
|
|
|
actionWindow.setBounds({
|
|
|
|
|
width: actionWindowWidth,
|
|
|
|
|
height: actionWindowHeight,
|
|
|
|
|
x: posX,
|
|
|
|
|
y: posY
|
|
|
|
|
// `setVisibleOnAllWorkspaces(true)` will cause the dock icon disappeared
|
|
|
|
|
// just store the dock icon status, and show it again
|
|
|
|
|
const isDockShown = app.dock?.isVisible()
|
|
|
|
|
|
|
|
|
|
// DO NOT set `skipTransformProcessType: true`,
|
|
|
|
|
// it will cause the action window to be shown on other space
|
|
|
|
|
actionWindow.setVisibleOnAllWorkspaces(true, {
|
|
|
|
|
visibleOnFullScreen: true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
actionWindow.show()
|
|
|
|
|
actionWindow.showInactive()
|
|
|
|
|
|
|
|
|
|
// show the dock again if last time it was shown
|
|
|
|
|
// do not put it after `actionWindow.focus()`, will cause the action window to be closed when auto hide on blur is enabled
|
|
|
|
|
if (!app.dock?.isVisible() && isDockShown) {
|
|
|
|
|
app.dock?.show()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// unset everything
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
actionWindow.setVisibleOnAllWorkspaces(false, {
|
|
|
|
|
visibleOnFullScreen: true,
|
|
|
|
|
skipTransformProcessType: true
|
|
|
|
|
})
|
|
|
|
|
actionWindow.setAlwaysOnTop(false)
|
|
|
|
|
|
|
|
|
|
actionWindow.setFocusable(true)
|
|
|
|
|
|
|
|
|
|
// regain the focus when all the works done
|
|
|
|
|
actionWindow.focus()
|
|
|
|
|
}, 50)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public closeActionWindow(actionWindow: BrowserWindow): void {
|
|
|
|
@@ -1408,8 +1472,9 @@ export class SelectionService {
|
|
|
|
|
configManager.setSelectionAssistantFilterList(filterList)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
ipcMain.handle(IpcChannel.Selection_ProcessAction, (_, actionItem: ActionItem) => {
|
|
|
|
|
selectionService?.processAction(actionItem)
|
|
|
|
|
// [macOS] only macOS has the available isFullscreen mode
|
|
|
|
|
ipcMain.handle(IpcChannel.Selection_ProcessAction, (_, actionItem: ActionItem, isFullScreen: boolean = false) => {
|
|
|
|
|
selectionService?.processAction(actionItem, isFullScreen)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
ipcMain.handle(IpcChannel.Selection_ActionWindowClose, (event) => {
|
|
|
|
|