@@ -94,6 +94,7 @@ if (process.platform === 'linux') {
9494
9595let mainWindow : BrowserWindow | null = null
9696let contentWindow : BrowserWindow | null = null
97+ let spotlightWindow : BrowserWindow | null = null
9798let tray : Tray | null = null
9899let isQuiting = false
99100
@@ -103,34 +104,151 @@ let SERVER_STATUS: string | null = null
103104let SERVER_REACHABLE = false
104105let SERVER_PID : number | null = null
105106
106- // ─── Global Shortcut ─ ───────────────────────────────────
107+ // ─── Global Shortcuts ───────────────────────────────────
107108
108- const registerGlobalShortcut = ( accelerator ?: string ) : void => {
109+ const registerShortcuts = ( globalAccel ?: string , spotlightAccel ?: string ) : void => {
109110 globalShortcut . unregisterAll ( )
110- if ( ! accelerator ) return
111- try {
112- globalShortcut . register ( accelerator , ( ) => {
113- if ( contentWindow && ! contentWindow . isDestroyed ( ) ) {
114- contentWindow . show ( )
115- contentWindow . focus ( )
111+
112+ // Global shortcut – bring main window to foreground
113+ if ( globalAccel ) {
114+ try {
115+ globalShortcut . register ( globalAccel , ( ) => {
116+ if ( mainWindow ) {
117+ mainWindow . show ( )
118+ mainWindow . focus ( )
119+ } else {
120+ createMainWindow ( )
121+ }
122+ } )
123+ } catch ( error ) {
124+ log . warn ( 'Failed to register global shortcut:' , globalAccel , error )
125+ }
126+ }
127+
128+ // Spotlight shortcut – toggle the spotlight input bar
129+ if ( spotlightAccel ) {
130+ try {
131+ globalShortcut . register ( spotlightAccel , ( ) => {
132+ toggleSpotlight ( )
133+ } )
134+ } catch ( error ) {
135+ log . warn ( 'Failed to register spotlight shortcut:' , spotlightAccel , error )
136+ }
137+ }
138+ }
139+
140+ // ─── Spotlight Window ───────────────────────────────────
141+
142+ // Remember where the user dragged the spotlight
143+ let spotlightPosition : { x : number ; y : number } | null = null
144+
145+ // Load persisted spotlight position from config (call after CONFIG is loaded)
146+ function loadSpotlightPosition ( ) : void {
147+ if ( CONFIG ?. spotlightPosition ) {
148+ spotlightPosition = { ...CONFIG . spotlightPosition }
149+ }
150+ }
151+
152+ function getDefaultSpotlightPosition ( ) : { x : number ; y : number } {
153+ const { screen } = require ( 'electron' )
154+ const cursorPoint = screen . getCursorScreenPoint ( )
155+ const activeDisplay = screen . getDisplayNearestPoint ( cursorPoint )
156+ const { width : screenW } = activeDisplay . workAreaSize
157+ const { x : screenX , y : screenY } = activeDisplay . workArea
158+ const winW = 748
159+ return {
160+ x : Math . round ( screenX + ( screenW - winW ) / 2 ) ,
161+ y : Math . round ( screenY + 160 )
162+ }
163+ }
164+
165+ function createSpotlightWindow ( ) : BrowserWindow {
166+ const pos = spotlightPosition || getDefaultSpotlightPosition ( )
167+
168+ const winW = 748
169+ const winH = 86
170+
171+ spotlightWindow = new BrowserWindow ( {
172+ width : winW ,
173+ height : winH ,
174+ x : pos . x ,
175+ y : pos . y ,
176+ frame : false ,
177+ transparent : true ,
178+ alwaysOnTop : true ,
179+ skipTaskbar : true ,
180+ resizable : false ,
181+ hasShadow : false ,
182+ show : false ,
183+ icon : path . join ( __dirname , 'assets/icon.png' ) ,
184+ webPreferences : {
185+ preload : join ( __dirname , '../preload/spotlight-preload.js' ) ,
186+ sandbox : false ,
187+ webviewTag : false
188+ }
189+ } )
190+
191+ if ( is . dev && process . env [ 'ELECTRON_RENDERER_URL' ] ) {
192+ spotlightWindow . loadURL ( `${ process . env [ 'ELECTRON_RENDERER_URL' ] } /spotlight.html` )
193+ } else {
194+ spotlightWindow . loadFile ( join ( __dirname , '../renderer/spotlight.html' ) )
195+ }
196+
197+ // Save position when user drags to a new spot
198+ spotlightWindow . on ( 'moved' , ( ) => {
199+ if ( spotlightWindow && ! spotlightWindow . isDestroyed ( ) ) {
200+ const [ x , y ] = spotlightWindow . getPosition ( )
201+ spotlightPosition = { x, y }
202+ // Persist to config for cross-session recall
203+ setConfig ( { spotlightPosition : { x, y } } ) . catch ( ( err ) =>
204+ log . warn ( 'Failed to persist spotlight position:' , err )
205+ )
206+ }
207+ } )
208+
209+ spotlightWindow . on ( 'blur' , ( ) => {
210+ spotlightWindow ?. hide ( )
211+ } )
212+
213+ spotlightWindow . on ( 'closed' , ( ) => {
214+ spotlightWindow = null
215+ } )
216+
217+ return spotlightWindow
218+ }
219+
220+ function toggleSpotlight ( ) : void {
221+ if ( spotlightWindow && ! spotlightWindow . isDestroyed ( ) ) {
222+ if ( spotlightWindow . isVisible ( ) ) {
223+ spotlightWindow . hide ( )
224+ } else {
225+ // Restore to saved position, or default if none saved
226+ if ( spotlightPosition ) {
227+ spotlightWindow . setPosition ( spotlightPosition . x , spotlightPosition . y )
116228 } else {
117- mainWindow ?. show ( )
118- mainWindow ?. focus ( )
229+ const pos = getDefaultSpotlightPosition ( )
230+ spotlightWindow . setPosition ( pos . x , pos . y )
119231 }
232+ spotlightWindow . show ( )
233+ spotlightWindow . focus ( )
234+ }
235+ } else {
236+ const win = createSpotlightWindow ( )
237+ win . once ( 'ready-to-show' , ( ) => {
238+ win . show ( )
239+ win . focus ( )
120240 } )
121- } catch ( error ) {
122- log . warn ( 'Failed to register global shortcut:' , accelerator , error )
123241 }
124242}
125243
126244// ─── Windows ────────────────────────────────────────────
127245
128246function createMainWindow ( show = true ) : void {
129247 mainWindow = new BrowserWindow ( {
130- width : 1100 ,
131- height : 700 ,
132- minWidth : 900 ,
133- minHeight : 560 ,
248+ width : 1280 ,
249+ height : 800 ,
250+ minWidth : 1280 ,
251+ minHeight : 800 ,
134252 icon : path . join ( __dirname , 'assets/icon.png' ) ,
135253 show : false ,
136254 titleBarStyle : process . platform === 'win32' ? 'default' : 'hidden' ,
@@ -192,10 +310,10 @@ function createContentWindow(url: string, connectionId: string): BrowserWindow {
192310 }
193311
194312 contentWindow = new BrowserWindow ( {
195- width : 1200 ,
313+ width : 1280 ,
196314 height : 800 ,
197- minWidth : 900 ,
198- minHeight : 560 ,
315+ minWidth : 1280 ,
316+ minHeight : 800 ,
199317 icon : path . join ( __dirname , 'assets/icon.png' ) ,
200318 show : false ,
201319 titleBarStyle : process . platform === 'win32' ? 'default' : 'hidden' ,
@@ -603,6 +721,7 @@ if (!gotTheLock) {
603721
604722 app . whenReady ( ) . then ( async ( ) => {
605723 CONFIG = await getConfig ( )
724+ loadSpotlightPosition ( )
606725 log . info ( 'Config:' , CONFIG )
607726
608727 app . name = 'Open WebUI'
@@ -649,7 +768,7 @@ if (!gotTheLock) {
649768 await setConfig ( config )
650769 CONFIG = await getConfig ( )
651770 updateTray ( )
652- registerGlobalShortcut ( CONFIG . globalShortcut )
771+ registerShortcuts ( CONFIG . globalShortcut , CONFIG . spotlightShortcut )
653772 } )
654773
655774 // Python/uv
@@ -803,6 +922,43 @@ if (!gotTheLock) {
803922 // Misc
804923 ipcMain . handle ( 'app:reset' , ( ) => resetAppHandler ( ) )
805924
925+ // Spotlight
926+ ipcMain . handle ( 'spotlight:submit' , async ( _event , query : string ) => {
927+ const config = await getConfig ( )
928+ if ( ! config . defaultConnectionId || config . connections . length === 0 ) {
929+ // No default connection — just show main window
930+ mainWindow ?. show ( )
931+ mainWindow ?. focus ( )
932+ return
933+ }
934+ const conn = config . connections . find ( ( c ) => c . id === config . defaultConnectionId )
935+ if ( ! conn ) {
936+ mainWindow ?. show ( )
937+ mainWindow ?. focus ( )
938+ return
939+ }
940+
941+ let url = conn . url
942+ if ( conn . type === 'local' && SERVER_URL ) {
943+ url = SERVER_URL
944+ }
945+ if ( url . startsWith ( 'http://0.0.0.0' ) ) {
946+ url = url . replace ( 'http://0.0.0.0' , 'http://localhost' )
947+ }
948+
949+ // Navigate to the connection URL with query
950+ const targetUrl = `${ url } /?q=${ encodeURIComponent ( query ) } `
951+ sendToRenderer ( 'connection:open' , { url : targetUrl , connectionId : conn . id } )
952+
953+ // Show main window and hide spotlight
954+ mainWindow ?. show ( )
955+ mainWindow ?. focus ( )
956+ spotlightWindow ?. hide ( )
957+ } )
958+ ipcMain . handle ( 'spotlight:close' , ( ) => {
959+ spotlightWindow ?. hide ( )
960+ } )
961+
806962 // Open Terminal
807963 ipcMain . handle ( 'open-terminal:start' , async ( ) => {
808964 try {
@@ -1058,7 +1214,7 @@ if (!gotTheLock) {
10581214
10591215
10601216 // Global shortcut
1061- registerGlobalShortcut ( CONFIG . globalShortcut )
1217+ registerShortcuts ( CONFIG . globalShortcut , CONFIG . spotlightShortcut )
10621218
10631219 // Enable screen capture
10641220 session . defaultSession . setDisplayMediaRequestHandler (
@@ -1145,6 +1301,10 @@ if (!gotTheLock) {
11451301 globalShortcut . unregisterAll ( )
11461302 mainWindow = null
11471303 contentWindow = null
1304+ if ( spotlightWindow && ! spotlightWindow . isDestroyed ( ) ) {
1305+ spotlightWindow . destroy ( )
1306+ }
1307+ spotlightWindow = null
11481308 tray ?. destroy ( )
11491309 tray = null
11501310 } )
0 commit comments