1- const { ipcMain, BrowserWindow } = require ( 'electron' ) ;
1+ const { ipcMain, BrowserWindow, shell , clipboard } = require ( 'electron' ) ;
22const path = require ( 'path' ) ;
3+ const { spawn } = require ( 'child_process' ) ;
34
45const PHOENIX_WINDOW_PREFIX = 'phcode-' ;
56const PHOENIX_EXTENSION_WINDOW_PREFIX = 'extn-' ;
@@ -20,13 +21,27 @@ function getNextLabel(prefix) {
2021 throw new Error ( `No free window label available for prefix: ${ prefix } ` ) ;
2122}
2223
24+ // Track close handlers per window
25+ const windowCloseHandlers = new Map ( ) ;
26+
2327function registerWindow ( win , label ) {
2428 windowRegistry . set ( label , win ) ;
2529 webContentsToLabel . set ( win . webContents . id , label ) ;
2630
2731 win . on ( 'closed' , ( ) => {
2832 windowRegistry . delete ( label ) ;
2933 webContentsToLabel . delete ( win . webContents . id ) ;
34+ windowCloseHandlers . delete ( win . webContents . id ) ;
35+ } ) ;
36+ }
37+
38+ function setupCloseHandler ( win ) {
39+ win . on ( 'close' , ( event ) => {
40+ const hasHandler = windowCloseHandlers . get ( win . webContents . id ) ;
41+ if ( hasHandler && ! win . forceClose ) {
42+ event . preventDefault ( ) ;
43+ win . webContents . send ( 'close-requested' ) ;
44+ }
3045 } ) ;
3146}
3247
@@ -95,6 +110,138 @@ function registerWindowIpcHandlers() {
95110 win . focus ( ) ;
96111 }
97112 } ) ;
113+
114+ // Get process ID
115+ ipcMain . handle ( 'get-process-id' , ( ) => {
116+ return process . pid ;
117+ } ) ;
118+
119+ // Get platform architecture
120+ ipcMain . handle ( 'get-platform-arch' , ( ) => {
121+ return process . arch ;
122+ } ) ;
123+
124+ // Get current working directory
125+ ipcMain . handle ( 'get-cwd' , ( ) => {
126+ return process . cwd ( ) ;
127+ } ) ;
128+
129+ // Fullscreen APIs
130+ ipcMain . handle ( 'is-fullscreen' , ( event ) => {
131+ const win = BrowserWindow . fromWebContents ( event . sender ) ;
132+ return win ? win . isFullScreen ( ) : false ;
133+ } ) ;
134+
135+ ipcMain . handle ( 'set-fullscreen' , ( event , enable ) => {
136+ const win = BrowserWindow . fromWebContents ( event . sender ) ;
137+ if ( win ) {
138+ win . setFullScreen ( enable ) ;
139+ }
140+ } ) ;
141+
142+ // Window title APIs
143+ ipcMain . handle ( 'set-window-title' , ( event , title ) => {
144+ const win = BrowserWindow . fromWebContents ( event . sender ) ;
145+ if ( win ) {
146+ win . setTitle ( title ) ;
147+ }
148+ } ) ;
149+
150+ ipcMain . handle ( 'get-window-title' , ( event ) => {
151+ const win = BrowserWindow . fromWebContents ( event . sender ) ;
152+ return win ? win . getTitle ( ) : '' ;
153+ } ) ;
154+
155+ // Clipboard APIs
156+ ipcMain . handle ( 'clipboard-read-text' , ( ) => {
157+ return clipboard . readText ( ) ;
158+ } ) ;
159+
160+ ipcMain . handle ( 'clipboard-write-text' , ( event , text ) => {
161+ clipboard . writeText ( text ) ;
162+ } ) ;
163+
164+ // Read file paths from clipboard (platform-specific)
165+ ipcMain . handle ( 'clipboard-read-files' , ( ) => {
166+ const formats = clipboard . availableFormats ( ) ;
167+
168+ // Windows: FileNameW format contains file paths
169+ if ( process . platform === 'win32' && formats . includes ( 'FileNameW' ) ) {
170+ const buffer = clipboard . readBuffer ( 'FileNameW' ) ;
171+ // FileNameW is null-terminated UTF-16LE string
172+ const paths = buffer . toString ( 'utf16le' ) . split ( '\0' ) . filter ( p => p . length > 0 ) ;
173+ return paths ;
174+ }
175+
176+ // macOS: public.file-url format
177+ if ( process . platform === 'darwin' ) {
178+ // Try reading as file URLs
179+ const text = clipboard . read ( 'public.file-url' ) ;
180+ if ( text ) {
181+ // Convert file:// URLs to paths
182+ const paths = text . split ( '\n' )
183+ . filter ( url => url . startsWith ( 'file://' ) )
184+ . map ( url => decodeURIComponent ( url . replace ( 'file://' , '' ) ) ) ;
185+ if ( paths . length > 0 ) {
186+ return paths ;
187+ }
188+ }
189+ }
190+
191+ // Linux: text/uri-list format
192+ if ( process . platform === 'linux' && formats . includes ( 'text/uri-list' ) ) {
193+ const text = clipboard . read ( 'text/uri-list' ) ;
194+ if ( text ) {
195+ const paths = text . split ( '\n' )
196+ . filter ( url => url . startsWith ( 'file://' ) )
197+ . map ( url => decodeURIComponent ( url . replace ( 'file://' , '' ) ) ) ;
198+ if ( paths . length > 0 ) {
199+ return paths ;
200+ }
201+ }
202+ }
203+
204+ return null ;
205+ } ) ;
206+
207+ // Shell APIs
208+ ipcMain . handle ( 'move-to-trash' , async ( event , platformPath ) => {
209+ await shell . trashItem ( platformPath ) ;
210+ } ) ;
211+
212+ ipcMain . handle ( 'show-in-folder' , ( event , platformPath ) => {
213+ shell . showItemInFolder ( platformPath ) ;
214+ } ) ;
215+
216+ ipcMain . handle ( 'open-external' , async ( event , url ) => {
217+ await shell . openExternal ( url ) ;
218+ } ) ;
219+
220+ // Windows-only: open URL in specific browser (fire and forget)
221+ ipcMain . handle ( 'open-url-in-browser-win' , ( event , url , browser ) => {
222+ if ( process . platform !== 'win32' ) {
223+ throw new Error ( 'open-url-in-browser-win is only supported on Windows' ) ;
224+ }
225+ spawn ( 'cmd' , [ '/c' , 'start' , browser , url ] , { shell : true , detached : true , stdio : 'ignore' } ) ;
226+ } ) ;
227+
228+ // Register close handler for current window
229+ ipcMain . handle ( 'register-close-handler' , ( event ) => {
230+ const win = BrowserWindow . fromWebContents ( event . sender ) ;
231+ if ( win ) {
232+ windowCloseHandlers . set ( win . webContents . id , true ) ;
233+ setupCloseHandler ( win ) ;
234+ }
235+ } ) ;
236+
237+ // Allow close after handler approves
238+ ipcMain . handle ( 'allow-close' , ( event ) => {
239+ const win = BrowserWindow . fromWebContents ( event . sender ) ;
240+ if ( win ) {
241+ win . forceClose = true ;
242+ win . close ( ) ;
243+ }
244+ } ) ;
98245}
99246
100- module . exports = { registerWindowIpcHandlers, registerWindow, windowRegistry } ;
247+ module . exports = { registerWindowIpcHandlers, registerWindow, setupCloseHandler , windowRegistry } ;
0 commit comments