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.

Move default browser logic to host package and unify test commands

Aly Raffauf (Jul 5, 2026, 1:54 PM EDT) 9d2a6561 4aa4ca7f

+71 -43
+2 -6
.github/workflows/ci.yml
··· 29 29 run: | 30 30 scripts/generate-flatpak-go-modules.py 31 31 git diff --exit-code -- flatpak/go-modules.json 32 - - name: Test routing package with coverage 33 - run: just test-routing-coverage 34 - - name: Test config package 35 - run: just test-config 36 - - name: Test browser package 37 - run: just test-browser 32 + - name: Test internal packages with coverage 33 + run: just test-coverage 38 34 - name: Display coverage summary 39 35 run: | 40 36 echo "## Test Coverage Summary" >> $GITHUB_STEP_SUMMARY
-21
gtk/config.go
··· 5 5 import ( 6 6 "fmt" 7 7 "os" 8 - "strings" 9 8 10 9 appconfig "github.com/alyraffauf/switchyard/internal/config" 11 - "github.com/alyraffauf/switchyard/internal/host" 12 10 "github.com/alyraffauf/switchyard/internal/routing" 13 11 ) 14 12 ··· 32 30 33 31 func saveConfig(config *Config) error { 34 32 return appconfig.Save(configPath(), config) 35 - } 36 - 37 - func isDefaultBrowser() bool { 38 - cmd := host.HostCommand("xdg-settings", "get", "default-web-browser") 39 - 40 - output, err := cmd.Output() 41 - if err != nil { 42 - return false 43 - } 44 - 45 - defaultBrowser := strings.TrimSpace(string(output)) 46 - desktopFile := getAppID() + ".desktop" 47 - return defaultBrowser == desktopFile 48 - } 49 - 50 - func setAsDefaultBrowser() error { 51 - desktopFile := getAppID() + ".desktop" 52 - cmd := host.HostCommand("xdg-settings", "set", "default-web-browser", desktopFile) 53 - return cmd.Run() 54 33 } 55 34 56 35 func exportConfig(config *Config, path string) error {
+2 -1
gtk/dialog_default_browser.go
··· 3 3 package gtk 4 4 5 5 import ( 6 + "github.com/alyraffauf/switchyard/internal/host" 6 7 "github.com/diamondburned/gotk4-adwaita/pkg/adw" 7 8 "github.com/diamondburned/gotk4/pkg/gtk/v4" 8 9 ) ··· 25 26 26 27 dialog.ConnectResponse(func(response string) { 27 28 if response == "yes" { 28 - setAsDefaultBrowser() 29 + host.SetDefaultBrowser(getAppID()) 29 30 cfg.CheckDefaultBrowser = false 30 31 saveConfig(cfg) 31 32 updateUI()
+2 -1
gtk/window_settings.go
··· 5 5 import ( 6 6 "context" 7 7 8 + "github.com/alyraffauf/switchyard/internal/host" 8 9 "github.com/diamondburned/gotk4-adwaita/pkg/adw" 9 10 "github.com/diamondburned/gotk4/pkg/gio/v2" 10 11 "github.com/diamondburned/gotk4/pkg/glib/v2" ··· 34 35 35 36 win.SetContent(splitView) 36 37 37 - if cfg.CheckDefaultBrowser && !isDefaultBrowser() { 38 + if cfg.CheckDefaultBrowser && !host.IsDefaultBrowser(getAppID()) { 38 39 showDefaultBrowserPrompt(win, cfg, func() {}) 39 40 } 40 41
+19
internal/host/host.go
··· 8 8 import ( 9 9 "os" 10 10 "os/exec" 11 + "strings" 11 12 ) 13 + 14 + const desktopSuffix = ".desktop" 12 15 13 16 // InFlatpak reports whether the process is running inside a Flatpak sandbox. 14 17 func InFlatpak() bool { ··· 24 27 hostArgs := append([]string{"--host", name}, args...) 25 28 return exec.Command("flatpak-spawn", hostArgs...) 26 29 } 30 + 31 + // IsDefaultBrowser reports whether appID's desktop entry is the system default 32 + // web browser, per xdg-settings. 33 + func IsDefaultBrowser(appID string) bool { 34 + output, err := HostCommand("xdg-settings", "get", "default-web-browser").Output() 35 + if err != nil { 36 + return false 37 + } 38 + return strings.TrimSpace(string(output)) == appID+desktopSuffix 39 + } 40 + 41 + // SetDefaultBrowser registers appID's desktop entry as the system default web 42 + // browser, per xdg-settings. 43 + func SetDefaultBrowser(appID string) error { 44 + return HostCommand("xdg-settings", "set", "default-web-browser", appID+desktopSuffix).Run() 45 + }
+42
internal/host/host_test.go
··· 1 + // SPDX-License-Identifier: GPL-3.0-or-later 2 + 3 + package host 4 + 5 + import ( 6 + "slices" 7 + "testing" 8 + ) 9 + 10 + func TestInFlatpak(t *testing.T) { 11 + t.Setenv("FLATPAK_ID", "") 12 + if InFlatpak() { 13 + t.Fatal("want false when FLATPAK_ID is empty") 14 + } 15 + 16 + t.Setenv("FLATPAK_ID", "io.example.App") 17 + if !InFlatpak() { 18 + t.Fatal("want true when FLATPAK_ID is set") 19 + } 20 + } 21 + 22 + func TestHostCommandRunsDirectlyWhenNotSandboxed(t *testing.T) { 23 + t.Setenv("FLATPAK_ID", "") 24 + 25 + cmd := HostCommand("xdg-open", "https://example.com") 26 + 27 + want := []string{"xdg-open", "https://example.com"} 28 + if !slices.Equal(cmd.Args, want) { 29 + t.Fatalf("args: got %v, want %v", cmd.Args, want) 30 + } 31 + } 32 + 33 + func TestHostCommandWrapsWithSpawnWhenSandboxed(t *testing.T) { 34 + t.Setenv("FLATPAK_ID", "io.example.App") 35 + 36 + cmd := HostCommand("xdg-open", "https://example.com") 37 + 38 + want := []string{"flatpak-spawn", "--host", "xdg-open", "https://example.com"} 39 + if !slices.Equal(cmd.Args, want) { 40 + t.Fatalf("args: got %v, want %v", cmd.Args, want) 41 + } 42 + }
+4 -14
justfile
··· 55 55 scripts/generate-flatpak-go-modules.py 56 56 57 57 # Run unit tests 58 - test-routing: 59 - go test -v ./internal/routing 60 - 61 - test-config: 62 - go test -v ./internal/config 63 - 64 - test-browser: 65 - go test -v ./internal/browser 66 - 67 - test: test-routing test-config test-browser 58 + test: 59 + go test ./internal/... 68 60 69 61 # Run tests with coverage report 70 - test-routing-coverage: 62 + test-coverage: 71 63 @echo "Running tests with coverage..." 72 - go test -v -coverprofile=coverage.out ./internal/routing 64 + go test -coverprofile=coverage.out ./internal/... 73 65 go tool cover -func=coverage.out 74 66 @echo "" 75 67 @echo "To view HTML coverage report, run: go tool cover -html=coverage.out" 76 - 77 - test-coverage: test-config test-browser test-routing-coverage 78 68 79 69 # Build and install Flatpak (development version) 80 70 flatpak: