@@ -168,7 +168,7 @@ class GenericBrowser(BaseBrowser):
168168 """Class for all browsers started with a command
169169 and without remote functionality."""
170170
171- def __init__ (self , name ):
171+ def __init__ (self , name , / , _supports_file = True ):
172172 if isinstance (name , str ):
173173 self .name = name
174174 self .args = ["%s" ]
@@ -177,9 +177,20 @@ def __init__(self, name):
177177 self .name = name [0 ]
178178 self .args = name [1 :]
179179 self .basename = os .path .basename (self .name )
180+ # whether it supports file:// URLs
181+ # set to False for generic openers like xdg-open,
182+ # which do not launch webbrowsers reliably
183+ self ._supports_file = _supports_file
180184
181185 def open (self , url , new = 0 , autoraise = True ):
182186 sys .audit ("webbrowser.open" , url )
187+
188+ if not self ._supports_file :
189+ # skip me for `file://` URLs for generic openers (e.g. xdg-open)
190+ proto , _sep , _rest = url .partition (":" )
191+ if _sep and proto .lower () == "file" :
192+ return False
193+
183194 cmdline = [self .name ] + [arg .replace ("%s" , url )
184195 for arg in self .args ]
185196 try :
@@ -197,6 +208,12 @@ class BackgroundBrowser(GenericBrowser):
197208 background."""
198209
199210 def open (self , url , new = 0 , autoraise = True ):
211+ if not self ._supports_file :
212+ # skip me for `file://` URLs for generic openers (e.g. xdg-open)
213+ proto , _sep , _rest = url .partition (":" )
214+ if _sep and proto .lower () == "file" :
215+ return False
216+
200217 cmdline = [self .name ] + [arg .replace ("%s" , url )
201218 for arg in self .args ]
202219 sys .audit ("webbrowser.open" , url )
@@ -415,34 +432,87 @@ class Edge(UnixBrowser):
415432# Platform support for Unix
416433#
417434
435+
436+ def _locate_xdg_desktop (name : str ) -> str | None :
437+ """Locate .desktop file by name
438+
439+ Returns absolute path to .desktop file found on $XDG_DATA search path
440+ or None if no matching .desktop file is found.
441+
442+ Needed for `gio launch` support.
443+ """
444+ if not name .endswith (".desktop" ):
445+ # ensure it ends in .desktop
446+ name += ".desktop"
447+ xdg_data_home = os .environ .get ("XDG_DATA_HOME" ) or os .path .expanduser (
448+ "~/.local/share"
449+ )
450+ xdg_data_dirs = os .environ .get ("XDG_DATA_DIRS" ) or "/usr/local/share/:/usr/share/"
451+ all_data_dirs = [xdg_data_home ]
452+ all_data_dirs .extend (xdg_data_dirs .split (os .pathsep ))
453+ for data_dir in all_data_dirs :
454+ desktop_path = os .path .join (data_dir , "applications" , name )
455+ if os .path .exists (desktop_path ):
456+ return desktop_path
457+ return None
458+
418459# These are the right tests because all these Unix browsers require either
419460# a console terminal or an X display to run.
420461
421462def register_X_browsers ():
422463
464+ # use gtk-launch to launch preferred browser by name, if found
465+ # this should be _before_ xdg-open, which doesn't necessarily launch a browser
466+ if _os_preferred_browser and shutil .which ("gtk-launch" ):
467+ register (
468+ "gtk-launch" ,
469+ None ,
470+ BackgroundBrowser (["gtk-launch" , _os_preferred_browser , "%s" ]),
471+ )
472+
423473 # use xdg-open if around
424474 if shutil .which ("xdg-open" ):
425- register ("xdg-open" , None , BackgroundBrowser ("xdg-open" ))
475+ # `xdg-open` does NOT guarantee a browser is launched,
476+ # so skip it for `file://`
477+ register ("xdg-open" , None , BackgroundBrowser ("xdg-open" , _supports_file = False ))
478+
426479
427- # Opens an appropriate browser for the URL scheme according to
480+ # Opens the default application for the URL scheme according to
428481 # freedesktop.org settings (GNOME, KDE, XFCE, etc.)
429482 if shutil .which ("gio" ):
430- register ("gio" , None , BackgroundBrowser (["gio" , "open" , "--" , "%s" ]))
483+ if _os_preferred_browser :
484+ absolute_browser = _locate_xdg_desktop (_os_preferred_browser )
485+ if absolute_browser :
486+ register (
487+ "gio-launch" ,
488+ None ,
489+ BackgroundBrowser (["gio" , "launch" , absolute_browser , "%s" ]),
490+ )
491+ # `gio open` does NOT guarantee a browser is launched
492+ register ("gio" , None , BackgroundBrowser (["gio" , "open" , "--" , "%s" ], _supports_file = False ))
431493
432494 xdg_desktop = os .getenv ("XDG_CURRENT_DESKTOP" , "" ).split (":" )
433495
434496 # The default GNOME3 browser
435497 if (("GNOME" in xdg_desktop or
436498 "GNOME_DESKTOP_SESSION_ID" in os .environ ) and
437499 shutil .which ("gvfs-open" )):
438- register ("gvfs-open" , None , BackgroundBrowser ("gvfs-open" ))
500+ register ("gvfs-open" , None , BackgroundBrowser ("gvfs-open" , _supports_file = False ))
439501
440502 # The default KDE browser
441503 if (("KDE" in xdg_desktop or
442504 "KDE_FULL_SESSION" in os .environ ) and
443505 shutil .which ("kfmclient" )):
444506 register ("kfmclient" , Konqueror , Konqueror ("kfmclient" ))
445507
508+ # The default XFCE browser
509+ if "XFCE" in xdg_desktop and shutil .which ("exo-open" ):
510+ register (
511+ "exo-open" ,
512+ None ,
513+ BackgroundBrowser (["exo-open" , "--launch" , "WebBrowser" , "%s" ]),
514+ )
515+
446516 # Common symbolic link for the default X11 browser
447517 if shutil .which ("x-www-browser" ):
448518 register ("x-www-browser" , None , BackgroundBrowser ("x-www-browser" ))
0 commit comments