11import { globalShortcut , ipcMain } from 'electron'
2+ import type { BrowserWindow } from 'electron'
23import type { ModelMessage } from 'ai'
34import { takeScreenshot } from './take-screenshot'
45import { getSolutionStream , getFollowUpStream } from './ai'
@@ -9,6 +10,7 @@ type Shortcut = {
910 action : string
1011 key : string
1112 status : ShortcutStatus
13+ registeredKeys : string [ ]
1214}
1315
1416enum ShortcutStatus {
@@ -33,6 +35,44 @@ let currentStreamContext: StreamContext | null = null
3335// Conversation history tracking
3436let conversationMessages : ModelMessage [ ] = [ ]
3537
38+ const FRONT_REASSERT_DURATION = 5000
39+ const FRONT_REASSERT_INTERVAL = 150
40+ const FRONT_RELATIVE_LEVEL = 10
41+ let frontReassertTimer : NodeJS . Timeout | null = null
42+
43+ function applyTopMost ( win : BrowserWindow ) {
44+ if ( ! win || win . isDestroyed ( ) ) return
45+ win . setAlwaysOnTop ( true , 'screen-saver' , FRONT_RELATIVE_LEVEL )
46+ win . moveTop ( )
47+ }
48+
49+ function keepWindowInFront ( window : BrowserWindow ) {
50+ if ( ! window || window . isDestroyed ( ) ) return
51+ if ( frontReassertTimer ) {
52+ clearInterval ( frontReassertTimer )
53+ frontReassertTimer = null
54+ }
55+
56+ const start = Date . now ( )
57+ const reassert = ( ) => {
58+ if ( ! window . isVisible ( ) || window . isDestroyed ( ) ) return false
59+ applyTopMost ( window )
60+ return true
61+ }
62+
63+ if ( ! reassert ( ) ) return
64+
65+ frontReassertTimer = setInterval ( ( ) => {
66+ const shouldStop = Date . now ( ) - start > FRONT_REASSERT_DURATION
67+ if ( shouldStop || ! reassert ( ) ) {
68+ if ( frontReassertTimer ) {
69+ clearInterval ( frontReassertTimer )
70+ frontReassertTimer = null
71+ }
72+ }
73+ } , FRONT_REASSERT_INTERVAL )
74+ }
75+
3676function abortCurrentStream ( reason : AbortReason ) {
3777 if ( ! currentStreamContext ) return
3878 currentStreamContext . reason = reason
@@ -46,7 +86,13 @@ const callbacks: Record<string, () => void> = {
4686 if ( mainWindow . isVisible ( ) ) {
4787 mainWindow . hide ( )
4888 } else {
49- mainWindow . show ( )
89+ // 重新显示时不断重申置顶属性,抵消其他前台软件持续抢占
90+ if ( process . platform === 'darwin' || process . platform === 'win32' ) {
91+ mainWindow . showInactive ( )
92+ } else {
93+ mainWindow . show ( )
94+ }
95+ keepWindowInFront ( mainWindow )
5096 }
5197 } ,
5298
@@ -194,16 +240,60 @@ const callbacks: Record<string, () => void> = {
194240 }
195241}
196242
243+ function unregisterShortcut ( action : string ) {
244+ const shortcut = shortcuts [ action ]
245+ if ( ! shortcut ) return
246+ if ( shortcut . registeredKeys . length ) {
247+ shortcut . registeredKeys . forEach ( ( registeredKey ) => {
248+ globalShortcut . unregister ( registeredKey )
249+ } )
250+ } else {
251+ globalShortcut . unregister ( shortcut . key )
252+ }
253+ shortcut . status = ShortcutStatus . Available
254+ shortcut . registeredKeys = [ ]
255+ }
256+
257+ function getShortcutRegistrationKeys ( key : string ) {
258+ const keys = [ key ]
259+ if ( process . platform !== 'win32' ) {
260+ return keys
261+ }
262+ const parts = key . split ( '+' )
263+ const hasAlt = parts . includes ( 'Alt' )
264+ const hasCtrl = parts . includes ( 'CommandOrControl' ) || parts . includes ( 'Control' )
265+ if ( hasAlt && ! hasCtrl ) {
266+ const aliasParts = [ ...parts ]
267+ const altIndex = aliasParts . indexOf ( 'Alt' )
268+ if ( altIndex >= 0 ) {
269+ aliasParts . splice ( altIndex , 0 , 'CommandOrControl' )
270+ const aliasKey = aliasParts . join ( '+' )
271+ if ( ! keys . includes ( aliasKey ) ) {
272+ keys . push ( aliasKey )
273+ }
274+ }
275+ }
276+ return keys
277+ }
278+
197279function registerShortcut ( action : string , key : string ) {
198- if ( shortcuts [ action ] ?. status === ShortcutStatus . Registered ) {
199- globalShortcut . unregister ( shortcuts [ action ] . key )
200- shortcuts [ action ] . status = ShortcutStatus . Available
280+ if ( shortcuts [ action ] ) {
281+ unregisterShortcut ( action )
201282 }
202- const ok = globalShortcut . register ( key , callbacks [ action ] )
283+
284+ const keysToRegister = getShortcutRegistrationKeys ( key )
285+ const registeredKeys : string [ ] = [ ]
286+ keysToRegister . forEach ( ( shortcutKey ) => {
287+ if ( globalShortcut . register ( shortcutKey , callbacks [ action ] ) ) {
288+ registeredKeys . push ( shortcutKey )
289+ }
290+ } )
291+
203292 shortcuts [ action ] = {
204293 action,
205294 key,
206- status : ok ? ShortcutStatus . Registered : ShortcutStatus . Failed
295+ status : registeredKeys . length ? ShortcutStatus . Registered : ShortcutStatus . Failed ,
296+ registeredKeys
207297 }
208298}
209299
0 commit comments