import configparser import glob import itertools import os import queue import shlex import shutil import threading _browsers = {} XDG_DATA_HOME = os.environ.get( "XDG_DATA_HOME", os.path.expanduser("~/.local/share") ) XDG_DATA_DIRS = os.environ.get( "XDG_DATA_DIRS", "/usr/local/share/:/usr/share/" ) XDG_DATA_DIRS = XDG_DATA_DIRS.split(os.pathsep) def find_unix_browsers(*paths: str) -> int: files = () if not paths: paths = (XDG_DATA_HOME, *XDG_DATA_DIRS) for appdata in paths: shortcuts = os.path.join(appdata, "applications") if not os.path.isdir(shortcuts): continue shortcuts = os.path.join(shortcuts, "**", "*.desktop") shortcuts = glob.iglob(shortcuts, recursive=True) files = itertools.chain(files, shortcuts) found = 0 lock = threading.Lock() tasks = queue.SimpleQueue() def xdg_desktop_handler() -> None: nonlocal found parser = configparser.ConfigParser(interpolation=None) while True: shortcut = tasks.get() if shortcut is None: return try: parser.clear() if not parser.read(shortcut, encoding="utf-8"): continue except (UnicodeDecodeError, configparser.Error): continue # Desktop Entry, Type, Name are required by specification if not parser.has_section("Desktop Entry"): continue entry = parser["Desktop Entry"] if entry.get("Type") != "Application": continue if "WebBrowser" not in entry.get("Categories", fallback=""): continue name = entry.get("Name") if not name: continue with lock: if name in _browsers: continue command = entry.get("Exec", fallback="") command = shlex.split(command) urlcode = False for command in reversed(command): if urlcode: if command := shutil.which(command): break elif command in ("%U", "%u"): urlcode = True else: continue with lock: _browsers[name] = command found += 1 workers = [] # process_cpu_count() replaced by cpu_count() to run on previous versions for worker in range(min(7, os.cpu_count() or 1)): worker = threading.Thread(target=xdg_desktop_handler) workers.append(worker) worker.start() files = itertools.chain(files, itertools.repeat(None, len(workers))) for file in files: tasks.put(file) for worker in workers: worker.join() return found if __name__ == "__main__": print(find_unix_browsers()) print(_browsers, "\n") for name, paths in _browsers.items(): print(name) print()