@@ -7,6 +7,7 @@ const { detect } = require('detect-port');
77const defaultPort = 3024 ;
88const viteDevPort = defaultPort ; // Using same port for consistency
99const { shell } = require ( 'electron' ) ;
10+ const { exec } = require ( 'child_process' ) ;
1011const config = 'easyedit.json' ;
1112const configPath = path . join ( app . getPath ( 'userData' ) , config ) ;
1213
@@ -210,6 +211,28 @@ async function handleFileOpen(filePath) {
210211 resolve ( ) ;
211212 } ) ;
212213 } else {
214+ // Detect WSL (Windows Subsystem for Linux). In WSL, Linux openers like xdg-open
215+ // are often unavailable; prefer calling the Windows host to open the URL.
216+ let isWSL = false ;
217+ try {
218+ if ( fs . existsSync ( '/proc/version' ) ) {
219+ const ver = fs . readFileSync ( '/proc/version' , 'utf8' ) . toLowerCase ( ) ;
220+ if ( ver . indexOf ( 'microsoft' ) !== - 1 || ver . indexOf ( 'wsl' ) !== - 1 ) isWSL = true ;
221+ }
222+ } catch ( wslErr ) {
223+ // ignore
224+ }
225+
226+ if ( isWSL ) {
227+ try {
228+ // Prefer cmd.exe start which opens the default Windows browser from WSL.
229+ exec ( `cmd.exe /C start "" "${ url . replace ( / " / g, '\\"' ) } "` , ( e ) => { if ( e ) console . error ( 'Fallback cmd.exe start failed:' , e ) ; } ) ;
230+ return { success : true , fallback : true , wsl : true } ;
231+ } catch ( we ) {
232+ console . error ( 'WSL fallback failed:' , we ) ;
233+ // continue to general Linux attempts below
234+ }
235+ }
213236 mainWindow . webContents . send ( 'file-opened' , content ) ;
214237 resolve ( ) ;
215238 }
@@ -250,6 +273,106 @@ function setupIPCHandlers() {
250273 } ) ;
251274}
252275
276+ // Support opening external links from renderer safely when running inside Electron
277+ ipcMain . handle ( 'open-external' , async ( _event , url ) => {
278+ // Detect WSL up-front so we can prefer Windows host openers instead of shell.openExternal
279+ let isWSL = false ;
280+ try {
281+ if ( fs . existsSync ( '/proc/version' ) ) {
282+ const ver = fs . readFileSync ( '/proc/version' , 'utf8' ) . toLowerCase ( ) ;
283+ if ( ver . indexOf ( 'microsoft' ) !== - 1 || ver . indexOf ( 'wsl' ) !== - 1 ) isWSL = true ;
284+ }
285+ } catch ( wslErr ) {
286+ // ignore
287+ }
288+
289+ // If running inside WSL, prefer invoking the Windows cmd.exe to open the URL
290+ if ( isWSL ) {
291+ try {
292+ // Try plain cmd.exe first (should be available in WSL distros), else try absolute path
293+ const tryCmd = ( cmd ) => new Promise ( ( resolve ) => {
294+ exec ( `${ cmd } /C start "" "${ url . replace ( / " / g, '\\"' ) } "` , ( e ) => {
295+ if ( e ) {
296+ console . error ( 'WSL cmd fallback failed for' , cmd , e ) ;
297+ resolve ( { success : false , error : String ( e ) } ) ;
298+ } else {
299+ resolve ( { success : true , used : cmd } ) ;
300+ }
301+ } ) ;
302+ } ) ;
303+
304+ let res = await tryCmd ( 'cmd.exe' ) ;
305+ if ( ! res . success ) {
306+ // common absolute path in WSL mounts
307+ const alt = '/mnt/c/Windows/System32/cmd.exe' ;
308+ if ( fs . existsSync ( alt ) ) {
309+ res = await tryCmd ( alt ) ;
310+ }
311+ }
312+ return res ;
313+ } catch ( we ) {
314+ console . error ( 'WSL open-external fallback failed:' , we ) ;
315+ return { success : false , error : String ( we ) } ;
316+ }
317+ }
318+
319+ // Try shell.openExternal first, then fall back to platform commands if necessary.
320+ try {
321+ await shell . openExternal ( url ) ;
322+ return { success : true } ;
323+ } catch ( err ) {
324+ console . warn ( 'shell.openExternal failed, attempting platform fallback:' , err ) ;
325+ // Platform-specific fallbacks
326+ try {
327+ if ( process . platform === 'darwin' ) {
328+ exec ( `open "${ url } "` , ( e ) => { if ( e ) console . error ( 'Fallback open failed:' , e ) ; } ) ;
329+ return { success : true , fallback : true } ;
330+ } else if ( process . platform === 'win32' ) {
331+ // start is a shell builtin on Windows; use cmd to run it
332+ exec ( `cmd /c start "" "${ url } "` , ( e ) => { if ( e ) console . error ( 'Fallback start failed:' , e ) ; } ) ;
333+ return { success : true , fallback : true } ;
334+ } else {
335+ // Linux and other Unix-like: try common openers by absolute path first
336+ const candidates = [
337+ '/usr/bin/xdg-open' ,
338+ '/bin/xdg-open' ,
339+ '/usr/local/bin/xdg-open' ,
340+ '/usr/bin/gio' ,
341+ '/usr/bin/gnome-open' ,
342+ '/usr/bin/kde-open5'
343+ ] ;
344+ let used = false ;
345+ for ( const c of candidates ) {
346+ try {
347+ if ( fs . existsSync ( c ) ) {
348+ // gio needs different args: 'gio open <url>' works
349+ if ( c . endsWith ( '/gio' ) ) {
350+ exec ( `${ c } open "${ url } "` , ( e ) => { if ( e ) console . error ( `Fallback ${ c } failed:` , e ) ; } ) ;
351+ } else {
352+ exec ( `${ c } "${ url } "` , ( e ) => { if ( e ) console . error ( `Fallback ${ c } failed:` , e ) ; } ) ;
353+ }
354+ used = true ;
355+ break ;
356+ }
357+ } catch ( fsErr ) {
358+ // ignore and try next
359+ }
360+ }
361+
362+ if ( ! used ) {
363+ // Last resort: try xdg-open on PATH (may still fail if missing)
364+ exec ( `xdg-open "${ url } "` , ( e ) => { if ( e ) console . error ( 'Fallback xdg-open failed:' , e ) ; } ) ;
365+ }
366+
367+ return { success : true , fallback : true , usedPath : used } ;
368+ }
369+ } catch ( err2 ) {
370+ console . error ( 'All methods to open external URL failed:' , err2 ) ;
371+ return { success : false , error : String ( err2 ) } ;
372+ }
373+ }
374+ } ) ;
375+
253376// function createMenuTemplate() {
254377// const menuTemplate = [
255378// {
0 commit comments