Rules-based browser launcher for TUI + GNOME. switchyard.aly.codes
tui gome bowser go
0

Configure Feed

Select the types of activity you want to include in your feed.

adjust names

authored by

Aly Raffauf and committed by
Aly Raffauf
(May 24, 2026, 6:44 PM EDT) 3003c0c8 6653fcf6

+199 -190
+4 -4
docs/WebExtension.md
··· 6 6 7 7 The intent is to add deeper integration with the desktop app. Eventually, it will be submitted to the Chrome Web Store and Mozilla. 8 8 9 - ## Native Messaging Support 9 + ## Desktop Integration 10 10 11 11 The Switchyard extension can also show your installed browsers directly in the popup, letting you send the current tab to a specific browser in one click. This requires installing a native messaging host on your system. 12 12 13 - Please read [`scripts/install-native-host.sh`](../scripts/install-native-host.sh) before running this command: 13 + Please read [`scripts/install-desktop-integration.sh`](../scripts/install-desktop-integration.sh) before running this command: 14 14 15 15 ```bash 16 - curl -fsSL https://raw.githubusercontent.com/alyraffauf/Switchyard/master/scripts/install-native-host.sh | bash 16 + curl -fsSL https://raw.githubusercontent.com/alyraffauf/Switchyard/master/scripts/install-desktop-integration.sh | bash 17 17 ``` 18 18 19 19 After installation, restart your browser(s). The extension popup will list your installed browsers. ··· 21 21 ### Uninstall 22 22 23 23 ```bash 24 - bash install-native-host.sh --uninstall 24 + bash install-desktop-integration.sh --uninstall 25 25 ``` 26 26 27 27 This removes all manifests and wrappers, but the `flatpak override` permissions granted during install are left in place. It's up to the user to decide what to do with them.
+185
scripts/install-desktop-integration.sh
··· 1 + #!/usr/bin/env bash 2 + # SPDX-License-Identifier: GPL-3.0-or-later 3 + # Switchyard desktop integration installer — https://github.com/alyraffauf/Switchyard 4 + # 5 + # Installs the desktop integration that lets the browser extension list your installed browsers. 6 + # 7 + # For Flatpak browsers this grants --talk-name=org.freedesktop.Flatpak, which 8 + # lets code inside the browser sandbox run arbitrary host commands via 9 + # flatpak-spawn --host. This weakens the Flatpak sandbox for those browsers. 10 + # Read and understand this before running it. 11 + # 12 + # Usage: bash install-native-host.sh [--uninstall] 13 + 14 + set -euo pipefail 15 + 16 + # ── Find Switchyard ──────────────────────────────────────────────────────────── 17 + 18 + CMD="" 19 + for id in io.github.alyraffauf.Switchyard io.github.alyraffauf.Switchyard.Devel; do 20 + if flatpak info "$id" &>/dev/null; then 21 + CMD="/usr/bin/flatpak run --command=switchyard $id --native-host" 22 + break 23 + fi 24 + done 25 + if [[ -z $CMD ]]; then 26 + bin=$(command -v switchyard 2>/dev/null) || { 27 + echo "error: Switchyard not found" >&2 28 + exit 1 29 + } 30 + CMD="$bin --native-host" 31 + fi 32 + 33 + # ── Config ───────────────────────────────────────────────────────────────────── 34 + 35 + NAME="io.github.alyraffauf.switchyard" 36 + WRAPPER="$HOME/.local/share/switchyard/native-host-wrapper.sh" 37 + CHROME_ORIGIN="chrome-extension://gmdmmjfmpfbmddgphjbkbbmdolkifloi/" 38 + FF_EXT="switchyard@alyraffauf.github.io" 39 + 40 + NATIVE_CHROMIUM=(net.imput.helium google-chrome chromium BraveSoftware/Brave-Browser microsoft-edge vivaldi) 41 + NATIVE_FIREFOX=(.mozilla .librewolf .zen) 42 + 43 + # (appId:configSubdir) — configSubdir maps to ~/.config/<sub>/ inside the sandbox 44 + FLATPAK_CHROMIUM=( 45 + com.google.Chrome:google-chrome 46 + com.google.ChromeDev:google-chrome-unstable 47 + org.chromium.Chromium:chromium 48 + com.brave.Browser:BraveSoftware/Brave-Browser 49 + com.microsoft.Edge:microsoft-edge 50 + com.vivaldi.Vivaldi:vivaldi 51 + io.github.ungoogled_software.ungoogled_chromium:chromium 52 + net.imput.helium:net.imput.helium 53 + ) 54 + 55 + # (appId:profileDir) — profileDir is persisted as ~/<sub>/ inside the sandbox 56 + FLATPAK_FIREFOX=( 57 + org.mozilla.firefox:.mozilla 58 + io.gitlab.librewolf-community:.librewolf 59 + io.github.zen_browser.zen:.zen 60 + ) 61 + 62 + # ── Helpers ──────────────────────────────────────────────────────────────────── 63 + 64 + N=0 65 + 66 + chromium_manifest() { 67 + printf '{"name":"%s","description":"Switchyard browser selector","path":"%s","type":"stdio","allowed_origins":["%s"]}\n' \ 68 + "$NAME" "$1" "$CHROME_ORIGIN" 69 + } 70 + 71 + firefox_manifest() { 72 + printf '{"name":"%s","description":"Switchyard browser selector","path":"%s","type":"stdio","allowed_extensions":["%s"]}\n' \ 73 + "$NAME" "$1" "$FF_EXT" 74 + } 75 + 76 + # ── Uninstall ────────────────────────────────────────────────────────────────── 77 + 78 + if [[ ${1-} == "--uninstall" ]]; then 79 + echo "Removing Switchyard native messaging host..." 80 + 81 + for sub in "${NATIVE_CHROMIUM[@]}"; do 82 + f="$HOME/.config/$sub/NativeMessagingHosts/$NAME.json" 83 + [[ -f $f ]] && rm -f "$f" && echo " removed $f" && N=$((N + 1)) || true 84 + done 85 + 86 + for sub in "${NATIVE_FIREFOX[@]}"; do 87 + f="$HOME/$sub/native-messaging-hosts/$NAME.json" 88 + [[ -f $f ]] && rm -f "$f" && echo " removed $f" && N=$((N + 1)) || true 89 + done 90 + 91 + for entry in "${FLATPAK_CHROMIUM[@]}"; do 92 + appId="${entry%%:*}" 93 + sub="${entry##*:}" 94 + dir="$HOME/.var/app/$appId/config/$sub/NativeMessagingHosts" 95 + f="$dir/$NAME.json" 96 + [[ -f $f ]] && rm -f "$f" "$dir/switchyard-native-host.sh" && echo " removed $f" && N=$((N + 1)) || true 97 + done 98 + 99 + for entry in "${FLATPAK_FIREFOX[@]}"; do 100 + appId="${entry%%:*}" 101 + sub="${entry##*:}" 102 + dir="$HOME/.var/app/$appId/$sub/native-messaging-hosts" 103 + f="$dir/$NAME.json" 104 + [[ -f $f ]] && rm -f "$f" "$dir/switchyard-native-host.sh" && echo " removed $f" && N=$((N + 1)) || true 105 + done 106 + 107 + [[ -f $WRAPPER ]] && rm -f "$WRAPPER" && echo " removed $WRAPPER" || true 108 + 109 + echo 110 + echo "Removed $N manifest(s)." 111 + exit 0 112 + fi 113 + 114 + # ── Install ──────────────────────────────────────────────────────────────────── 115 + 116 + echo "Installing Switchyard native messaging host..." 117 + 118 + mkdir -p "$(dirname "$WRAPPER")" 119 + printf '#!/bin/sh\nexec %s "$@"\n' "$CMD" >"$WRAPPER" 120 + chmod +x "$WRAPPER" 121 + echo "Wrapper: $WRAPPER" 122 + 123 + # Flatpak browsers need a different wrapper: they detect the sandbox via 124 + # $container and use flatpak-spawn --host to escape it. This requires the 125 + # org.freedesktop.Flatpak talk-name, granted per-browser below. 126 + FP_WRAPPER="#!/bin/sh 127 + if [ \"\${container-}\" = flatpak ]; then 128 + exec /usr/bin/flatpak-spawn --host $CMD 129 + else 130 + exec $CMD 131 + fi" 132 + 133 + for sub in "${NATIVE_CHROMIUM[@]}"; do 134 + [[ -d "$HOME/.config/$sub" ]] || continue 135 + target="$HOME/.config/$sub/NativeMessagingHosts/$NAME.json" 136 + mkdir -p "$(dirname "$target")" 137 + chromium_manifest "$WRAPPER" >"$target" 138 + echo " $target" 139 + N=$((N + 1)) 140 + done 141 + 142 + for sub in "${NATIVE_FIREFOX[@]}"; do 143 + [[ -d "$HOME/$sub" ]] || continue 144 + target="$HOME/$sub/native-messaging-hosts/$NAME.json" 145 + mkdir -p "$(dirname "$target")" 146 + firefox_manifest "$WRAPPER" >"$target" 147 + echo " $target" 148 + N=$((N + 1)) 149 + done 150 + 151 + for entry in "${FLATPAK_CHROMIUM[@]}"; do 152 + appId="${entry%%:*}" 153 + sub="${entry##*:}" 154 + [[ -d "$HOME/.var/app/$appId" ]] || continue 155 + dir="$HOME/.var/app/$appId/config/$sub/NativeMessagingHosts" 156 + wp="$dir/switchyard-native-host.sh" 157 + mkdir -p "$dir" 158 + printf '%s\n' "$FP_WRAPPER" >"$wp" && chmod +x "$wp" 159 + chromium_manifest "$wp" >"$dir/$NAME.json" 160 + flatpak override --user --talk-name=org.freedesktop.Flatpak "$appId" 161 + echo " $dir/$NAME.json [Flatpak]" 162 + N=$((N + 1)) 163 + done 164 + 165 + for entry in "${FLATPAK_FIREFOX[@]}"; do 166 + appId="${entry%%:*}" 167 + sub="${entry##*:}" 168 + [[ -d "$HOME/.var/app/$appId" ]] || continue 169 + dir="$HOME/.var/app/$appId/$sub/native-messaging-hosts" 170 + wp="$dir/switchyard-native-host.sh" 171 + mkdir -p "$dir" 172 + printf '%s\n' "$FP_WRAPPER" >"$wp" && chmod +x "$wp" 173 + firefox_manifest "$wp" >"$dir/$NAME.json" 174 + flatpak override --user --talk-name=org.freedesktop.Flatpak "$appId" 175 + echo " $dir/$NAME.json [Flatpak]" 176 + N=$((N + 1)) 177 + done 178 + 179 + [[ $N -gt 0 ]] || { 180 + echo 181 + echo "No supported browsers found." 182 + exit 0 183 + } 184 + echo 185 + echo "Installed $N manifest(s). Restart your browser to activate."
-185
scripts/install-native-host.sh
··· 1 - #!/usr/bin/env bash 2 - # SPDX-License-Identifier: GPL-3.0-or-later 3 - # Switchyard native messaging host installer — https://github.com/alyraffauf/Switchyard 4 - # 5 - # Installs manifests that let the browser extension list your installed browsers. 6 - # 7 - # For Flatpak browsers this grants --talk-name=org.freedesktop.Flatpak, which 8 - # lets code inside the browser sandbox run arbitrary host commands via 9 - # flatpak-spawn --host. This weakens the Flatpak sandbox for those browsers. 10 - # Read and understand this before running it. 11 - # 12 - # Usage: bash install-native-host.sh [--uninstall] 13 - 14 - set -euo pipefail 15 - 16 - # ── Find Switchyard ──────────────────────────────────────────────────────────── 17 - 18 - CMD="" 19 - for id in io.github.alyraffauf.Switchyard io.github.alyraffauf.Switchyard.Devel; do 20 - if flatpak info "$id" &>/dev/null; then 21 - CMD="/usr/bin/flatpak run --command=switchyard $id --native-host" 22 - break 23 - fi 24 - done 25 - if [[ -z $CMD ]]; then 26 - bin=$(command -v switchyard 2>/dev/null) || { 27 - echo "error: Switchyard not found" >&2 28 - exit 1 29 - } 30 - CMD="$bin --native-host" 31 - fi 32 - 33 - # ── Config ───────────────────────────────────────────────────────────────────── 34 - 35 - NAME="io.github.alyraffauf.switchyard" 36 - WRAPPER="$HOME/.local/share/switchyard/native-host-wrapper.sh" 37 - CHROME_ORIGIN="chrome-extension://gmdmmjfmpfbmddgphjbkbbmdolkifloi/" 38 - FF_EXT="switchyard@alyraffauf.github.io" 39 - 40 - NATIVE_CHROMIUM=(net.imput.helium google-chrome chromium BraveSoftware/Brave-Browser microsoft-edge vivaldi) 41 - NATIVE_FIREFOX=(.mozilla .librewolf .zen) 42 - 43 - # (appId:configSubdir) — configSubdir maps to ~/.config/<sub>/ inside the sandbox 44 - FLATPAK_CHROMIUM=( 45 - com.google.Chrome:google-chrome 46 - com.google.ChromeDev:google-chrome-unstable 47 - org.chromium.Chromium:chromium 48 - com.brave.Browser:BraveSoftware/Brave-Browser 49 - com.microsoft.Edge:microsoft-edge 50 - com.vivaldi.Vivaldi:vivaldi 51 - io.github.ungoogled_software.ungoogled_chromium:chromium 52 - net.imput.helium:net.imput.helium 53 - ) 54 - 55 - # (appId:profileDir) — profileDir is persisted as ~/<sub>/ inside the sandbox 56 - FLATPAK_FIREFOX=( 57 - org.mozilla.firefox:.mozilla 58 - io.gitlab.librewolf-community:.librewolf 59 - io.github.zen_browser.zen:.zen 60 - ) 61 - 62 - # ── Helpers ──────────────────────────────────────────────────────────────────── 63 - 64 - N=0 65 - 66 - chromium_manifest() { 67 - printf '{"name":"%s","description":"Switchyard browser selector","path":"%s","type":"stdio","allowed_origins":["%s"]}\n' \ 68 - "$NAME" "$1" "$CHROME_ORIGIN" 69 - } 70 - 71 - firefox_manifest() { 72 - printf '{"name":"%s","description":"Switchyard browser selector","path":"%s","type":"stdio","allowed_extensions":["%s"]}\n' \ 73 - "$NAME" "$1" "$FF_EXT" 74 - } 75 - 76 - # ── Uninstall ────────────────────────────────────────────────────────────────── 77 - 78 - if [[ ${1-} == "--uninstall" ]]; then 79 - echo "Removing Switchyard native messaging host..." 80 - 81 - for sub in "${NATIVE_CHROMIUM[@]}"; do 82 - f="$HOME/.config/$sub/NativeMessagingHosts/$NAME.json" 83 - [[ -f $f ]] && rm -f "$f" && echo " removed $f" && N=$((N + 1)) || true 84 - done 85 - 86 - for sub in "${NATIVE_FIREFOX[@]}"; do 87 - f="$HOME/$sub/native-messaging-hosts/$NAME.json" 88 - [[ -f $f ]] && rm -f "$f" && echo " removed $f" && N=$((N + 1)) || true 89 - done 90 - 91 - for entry in "${FLATPAK_CHROMIUM[@]}"; do 92 - appId="${entry%%:*}" 93 - sub="${entry##*:}" 94 - dir="$HOME/.var/app/$appId/config/$sub/NativeMessagingHosts" 95 - f="$dir/$NAME.json" 96 - [[ -f $f ]] && rm -f "$f" "$dir/switchyard-native-host.sh" && echo " removed $f" && N=$((N + 1)) || true 97 - done 98 - 99 - for entry in "${FLATPAK_FIREFOX[@]}"; do 100 - appId="${entry%%:*}" 101 - sub="${entry##*:}" 102 - dir="$HOME/.var/app/$appId/$sub/native-messaging-hosts" 103 - f="$dir/$NAME.json" 104 - [[ -f $f ]] && rm -f "$f" "$dir/switchyard-native-host.sh" && echo " removed $f" && N=$((N + 1)) || true 105 - done 106 - 107 - [[ -f $WRAPPER ]] && rm -f "$WRAPPER" && echo " removed $WRAPPER" || true 108 - 109 - echo 110 - echo "Removed $N manifest(s)." 111 - exit 0 112 - fi 113 - 114 - # ── Install ──────────────────────────────────────────────────────────────────── 115 - 116 - echo "Installing Switchyard native messaging host..." 117 - 118 - mkdir -p "$(dirname "$WRAPPER")" 119 - printf '#!/bin/sh\nexec %s "$@"\n' "$CMD" >"$WRAPPER" 120 - chmod +x "$WRAPPER" 121 - echo "Wrapper: $WRAPPER" 122 - 123 - # Flatpak browsers need a different wrapper: they detect the sandbox via 124 - # $container and use flatpak-spawn --host to escape it. This requires the 125 - # org.freedesktop.Flatpak talk-name, granted per-browser below. 126 - FP_WRAPPER="#!/bin/sh 127 - if [ \"\${container-}\" = flatpak ]; then 128 - exec /usr/bin/flatpak-spawn --host $CMD 129 - else 130 - exec $CMD 131 - fi" 132 - 133 - for sub in "${NATIVE_CHROMIUM[@]}"; do 134 - [[ -d "$HOME/.config/$sub" ]] || continue 135 - target="$HOME/.config/$sub/NativeMessagingHosts/$NAME.json" 136 - mkdir -p "$(dirname "$target")" 137 - chromium_manifest "$WRAPPER" >"$target" 138 - echo " $target" 139 - N=$((N + 1)) 140 - done 141 - 142 - for sub in "${NATIVE_FIREFOX[@]}"; do 143 - [[ -d "$HOME/$sub" ]] || continue 144 - target="$HOME/$sub/native-messaging-hosts/$NAME.json" 145 - mkdir -p "$(dirname "$target")" 146 - firefox_manifest "$WRAPPER" >"$target" 147 - echo " $target" 148 - N=$((N + 1)) 149 - done 150 - 151 - for entry in "${FLATPAK_CHROMIUM[@]}"; do 152 - appId="${entry%%:*}" 153 - sub="${entry##*:}" 154 - [[ -d "$HOME/.var/app/$appId" ]] || continue 155 - dir="$HOME/.var/app/$appId/config/$sub/NativeMessagingHosts" 156 - wp="$dir/switchyard-native-host.sh" 157 - mkdir -p "$dir" 158 - printf '%s\n' "$FP_WRAPPER" >"$wp" && chmod +x "$wp" 159 - chromium_manifest "$wp" >"$dir/$NAME.json" 160 - flatpak override --user --talk-name=org.freedesktop.Flatpak "$appId" 161 - echo " $dir/$NAME.json [Flatpak]" 162 - N=$((N + 1)) 163 - done 164 - 165 - for entry in "${FLATPAK_FIREFOX[@]}"; do 166 - appId="${entry%%:*}" 167 - sub="${entry##*:}" 168 - [[ -d "$HOME/.var/app/$appId" ]] || continue 169 - dir="$HOME/.var/app/$appId/$sub/native-messaging-hosts" 170 - wp="$dir/switchyard-native-host.sh" 171 - mkdir -p "$dir" 172 - printf '%s\n' "$FP_WRAPPER" >"$wp" && chmod +x "$wp" 173 - firefox_manifest "$wp" >"$dir/$NAME.json" 174 - flatpak override --user --talk-name=org.freedesktop.Flatpak "$appId" 175 - echo " $dir/$NAME.json [Flatpak]" 176 - N=$((N + 1)) 177 - done 178 - 179 - [[ $N -gt 0 ]] || { 180 - echo 181 - echo "No supported browsers found." 182 - exit 0 183 - } 184 - echo 185 - echo "Installed $N manifest(s). Restart your browser to activate."
+10 -1
webextension/src/App.tsx
··· 94 94 <div className="separator" /> 95 95 )} 96 96 {loaded && browsers.length === 0 && ( 97 - <div className="hint">Install the native host to list browsers</div> 97 + <div className="hint"> 98 + <a 99 + href="https://github.com/alyraffauf/Switchyard/blob/master/docs/WebExtension.md#desktop-integration" 100 + target="_blank" 101 + rel="noreferrer" 102 + > 103 + Set up desktop integration 104 + </a>{" "} 105 + to list browsers 106 + </div> 98 107 )} 99 108 {browsers.map((b) => ( 100 109 <button