Something went wrong. Try again.
Rules-based browser launcher for TUI + GNOME. switchyard.aly.codes
tui gnome bowser go
Something went wrong. Try again.
7.6 kB · 257 lines
Python
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258#!/usr/bin/env python3# SPDX-License-Identifier: GPL-3.0-or-later# Switchyard desktop integration installer — https://github.com/alyraffauf/Switchyard
import argparseimport jsonimport shutilimport subprocessimport sysfrom dataclasses import asdict, dataclassfrom pathlib import Path
FLATPAK_CHROMIUM: dict[str, str] = { "com.brave.Browser": "BraveSoftware/Brave-Browser", "com.google.Chrome": "google-chrome", "com.microsoft.Edge": "microsoft-edge", "com.vivaldi.Vivaldi": "vivaldi", "io.github.ungoogled_software.ungoogled_chromium": "chromium", "net.imput.helium": "net.imput.helium", "org.chromium.Chromium": "chromium",}
NATIVE_CHROMIUM = [ "net.imput.helium", "google-chrome", "chromium", "BraveSoftware/Brave-Browser", "microsoft-edge", "vivaldi",]
FLATPAK_FIREFOX: dict[str, str] = { "org.mozilla.firefox": ".mozilla", "io.gitlab.librewolf-community": ".librewolf",}
NATIVE_FIREFOX = [".mozilla", ".librewolf"]
@dataclassclass NativeMessagingManifest: path: str name: str = "io.github.alyraffauf.switchyard" description: str = "Switchyard browser selector" type: str = "stdio" allowed_origins: list[str] | None = None allowed_extensions: list[str] | None = None
def run(cmd: list[str]) -> bool: return subprocess.run(cmd, capture_output=True).returncode == 0
def flatpak_installed(flatpak: str | None, id: str) -> bool: if flatpak: return run([flatpak, "info", id]) return False
def find_switchyard(flatpak: str | None) -> str | None: switchyard = shutil.which("switchyard")
if flatpak and flatpak_installed(flatpak, "io.github.alyraffauf.Switchyard"): return f"{flatpak} run io.github.alyraffauf.Switchyard"
return switchyard
def write_wrapper(switchyard: str, path: Path): wrapper = f"""#!/bin/shif [ "${{container-}}" = flatpak ]; then exec /usr/bin/flatpak-spawn --host {switchyard} --native-host "$@"else exec {switchyard} --native-host "$@"fi"""
path.parent.mkdir(parents=True, exist_ok=True) path.write_text(wrapper) path.chmod(0o755)
def install_manifests(switchyard: str, configs: list[Path], hosts_dir: str, **kwargs): for config_dir in configs: manifest_file = config_dir / hosts_dir / "io.github.alyraffauf.switchyard.json" manifest_file.parent.mkdir(parents=True, exist_ok=True) manifest = NativeMessagingManifest( path=str(manifest_file.parent / "switchyard-native-host-wrapper.sh"), **kwargs, ) manifest_file.write_text( json.dumps( {key: val for key, val in asdict(manifest).items() if val is not None} ) ) print(f" Installed {manifest_file}") write_wrapper( switchyard, manifest_file.parent / "switchyard-native-host-wrapper.sh" )
def install(yes: bool = False): print("Installing Switchyard native messaging host...")
flatpak = shutil.which("flatpak") switchyard = find_switchyard(flatpak)
if switchyard is None: print("Switchyard is not installed.") sys.exit(1)
installed_chromium = [ app_id for app_id in FLATPAK_CHROMIUM if flatpak_installed(flatpak, app_id) ] installed_firefox = [ app_id for app_id in FLATPAK_FIREFOX if flatpak_installed(flatpak, app_id) ] to_override = installed_chromium + installed_firefox
chromium = [ Path.home() / f".var/app/{app_id}/config/{FLATPAK_CHROMIUM[app_id]}" for app_id in installed_chromium ] + [Path.home() / f".config/{config_dir}" for config_dir in NATIVE_CHROMIUM]
firefox = [ Path.home() / f".var/app/{app_id}/{FLATPAK_FIREFOX[app_id]}" for app_id in installed_firefox ] + [Path.home() / profile_dir for profile_dir in NATIVE_FIREFOX]
install_manifests( switchyard, chromium, "NativeMessagingHosts", allowed_origins=[ "chrome-extension://ncehhpikkabfdcceimdhjjjodogflokc/", "chrome-extension://gmdmmjfmpfbmddgphjbkbbmdolkifloi/", ], )
install_manifests( switchyard, firefox, "native-messaging-hosts", allowed_extensions=["switchyard@alyraffauf.github.io"], )
if to_override: print( "\nThe following browsers are installed via Flatpak and run in a " "sandbox. To let them talk to Switchyard, we need to grant each one " "permission to reach outside the sandbox. This does weaken their " "isolation slightly, so it's worth knowing before we proceed.\n" ) for app_id in to_override: print(f" {app_id}") print()
if yes: answer = "y" else: answer = input("Grant permission to these browsers? [y/N] ").strip().lower()
if answer != "y": print("Skipping permission grants.") to_override = []
assert flatpak for app_id in to_override: granted = run( [ flatpak, "override", "--user", "--talk-name=org.freedesktop.Flatpak", app_id, ] ) if granted: print(f" Granted: {app_id}") else: print(f" Failed to grant permission for {app_id}")
print("\nManifests installed. Please restart your browser.")
def uninstall(): chromium = [ Path.home() / f".var/app/{app_id}/config/{FLATPAK_CHROMIUM[app_id]}" for app_id in FLATPAK_CHROMIUM ] + [Path.home() / f".config/{config_dir}" for config_dir in NATIVE_CHROMIUM]
firefox = [ Path.home() / f".var/app/{app_id}/{FLATPAK_FIREFOX[app_id]}" for app_id in FLATPAK_FIREFOX ] + [Path.home() / profile_dir for profile_dir in NATIVE_FIREFOX]
count = 0 for config_dir in chromium: hosts = config_dir / "NativeMessagingHosts" for file in ( hosts / "io.github.alyraffauf.switchyard.json", hosts / "switchyard-native-host-wrapper.sh", ): if file.exists(): file.unlink() print(f" Removed {file}") count += 1 for profile_dir in firefox: hosts = profile_dir / "native-messaging-hosts" for file in ( hosts / "io.github.alyraffauf.switchyard.json", hosts / "switchyard-native-host-wrapper.sh", ): if file.exists(): file.unlink() print(f" Removed {file}") count += 1 print(f"\nRemoved {count} file(s).")
def main(): parser = argparse.ArgumentParser( prog="switchyard-browser-setup", description="Set up desktop integration between the Switchyard app and browser extension.", )
group = parser.add_mutually_exclusive_group() group.add_argument( "--install", action="store_true", help="install the native messaging manifest." )
parser.add_argument( "--yes", "-y", action="store_true", help="skip confirmation prompts." )
group.add_argument( "--uninstall", action="store_true", help="remove the native messaging host manifest.", )
args = parser.parse_args()
if not args.install and not args.uninstall: parser.print_help() sys.exit(0)
if args.install: install(yes=args.yes)
if args.uninstall: uninstall()
if __name__ == "__main__": main()