From b2df17cf8f67656ba9b589cebdc86fca18edf476 Mon Sep 17 00:00:00 2001 From: w3lld1 <42353747+w3lld1@users.noreply.github.com> Date: Fri, 17 Jul 2026 16:17:59 +0200 Subject: [PATCH] feat: add background startup mode --- gtk/run.go | 27 ++++++++++++++++-- internal/startup/mode.go | 33 ++++++++++++++++++++++ internal/startup/mode_test.go | 46 +++++++++++++++++++++++++++++++ website/src/content/docs/using.md | 16 ++++++++++- 4 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 internal/startup/mode.go create mode 100644 internal/startup/mode_test.go diff --git a/gtk/run.go b/gtk/run.go index 91e902b..f0a5a43 100644 --- a/gtk/run.go +++ b/gtk/run.go @@ -4,6 +4,8 @@ package gtk import ( + "context" + "fmt" "net/url" "os" "strings" @@ -11,6 +13,7 @@ import ( appconfig "github.com/alyraffauf/switchyard/internal/config" "github.com/alyraffauf/switchyard/internal/host" "github.com/alyraffauf/switchyard/internal/routing" + "github.com/alyraffauf/switchyard/internal/startup" "github.com/diamondburned/gotk4-adwaita/pkg/adw" "github.com/diamondburned/gotk4/pkg/gdk/v4" "github.com/diamondburned/gotk4/pkg/gio/v2" @@ -21,26 +24,44 @@ import ( // Run starts the Switchyard GTK application and blocks until it exits. func Run() { app := adw.NewApplication(getAppID(), gio.ApplicationHandlesOpen) + mode := startup.LaunchMode{} app.AddMainOption("native-host", 0, glib.OptionFlagNone, glib.OptionArgNone, "Run as native-messaging host (invoked by browsers)", "") + app.AddMainOption("background", 0, glib.OptionFlagNone, glib.OptionArgNone, + "Start without opening a window", "") app.ConnectHandleLocalOptions(func(options *glib.VariantDict) int { if options.Contains("native-host") { runNativeMessagingHost() return 0 } + if options.Contains("background") { + mode.Background = true + if err := app.Register(context.Background()); err != nil { + fmt.Fprintf(os.Stderr, "Error: could not start Switchyard in the background: %v\n", err) + return 1 + } + if mode.ShouldHandleLocally(app.IsRemote()) { + return 0 + } + } return -1 }) app.ConnectActivate(func() { cfg, _ := appconfig.Load(appconfig.Path()) - if cfg.StayAlive { + shouldHold := mode.ShouldHold(cfg.StayAlive) + shouldShowWindow := mode.ShouldShowWindow() + mode.CompleteActivation() + + if shouldHold { app.Hold() } setupApp(cfg) - browsers := detectBrowsers() - showSettingsWindow(app, browsers, cfg) + if shouldShowWindow { + showSettingsWindow(app, detectBrowsers(), cfg) + } }) app.ConnectOpen(func(files []gio.Filer, hint string) { diff --git a/internal/startup/mode.go b/internal/startup/mode.go new file mode 100644 index 0000000..c94171e --- /dev/null +++ b/internal/startup/mode.go @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// Package startup describes how a Switchyard invocation should start. +package startup + +// LaunchMode contains invocation-specific startup behavior. +type LaunchMode struct { + Background bool +} + +// ShouldHold reports whether the application should keep running without a +// window. +func (mode LaunchMode) ShouldHold(stayAlive bool) bool { + return mode.Background || stayAlive +} + +// ShouldShowWindow reports whether activation should present the settings +// window. +func (mode LaunchMode) ShouldShowWindow() bool { + return !mode.Background +} + +// CompleteActivation clears invocation-specific behavior so later activations +// forwarded to this process behave normally. +func (mode *LaunchMode) CompleteActivation() { + mode.Background = false +} + +// ShouldHandleLocally reports whether this invocation can exit without +// activating an existing primary instance. +func (mode LaunchMode) ShouldHandleLocally(remote bool) bool { + return mode.Background && remote +} diff --git a/internal/startup/mode_test.go b/internal/startup/mode_test.go new file mode 100644 index 0000000..decd4e9 --- /dev/null +++ b/internal/startup/mode_test.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +package startup + +import "testing" + +func TestLaunchModeBackgroundStart(t *testing.T) { + mode := LaunchMode{Background: true} + + if !mode.ShouldHold(false) { + t.Fatal("background start should hold the application even when stay_alive is disabled") + } + if mode.ShouldShowWindow() { + t.Fatal("background start should not show a window") + } + if !mode.ShouldHandleLocally(true) { + t.Fatal("background start should exit locally when another instance is already running") + } +} + +func TestLaunchModeBackgroundOnlyAppliesToFirstActivation(t *testing.T) { + mode := LaunchMode{Background: true} + + mode.CompleteActivation() + + if !mode.ShouldShowWindow() { + t.Fatal("a later activation should show a window") + } +} + +func TestLaunchModeNormalStart(t *testing.T) { + mode := LaunchMode{} + + if mode.ShouldHold(false) { + t.Fatal("normal start should respect a disabled stay_alive setting") + } + if !mode.ShouldHold(true) { + t.Fatal("normal start should respect an enabled stay_alive setting") + } + if !mode.ShouldShowWindow() { + t.Fatal("normal start should show a window") + } + if mode.ShouldHandleLocally(true) { + t.Fatal("normal start should be forwarded to the primary instance") + } +} diff --git a/website/src/content/docs/using.md b/website/src/content/docs/using.md index bad6fe2..8117ce2 100644 --- a/website/src/content/docs/using.md +++ b/website/src/content/docs/using.md @@ -6,7 +6,7 @@ order: 10 Switchyard is a powerful, rules-based browser launcher. Once it is set as your default browser, clicked links pass through Switchyard first. It can then open the link in a specific browser, ask you which browser to use, or rewrite the URL before opening it. -Use it when different parts of your life live in different browsers: work links in Chrome, personal links in Firefox, video links in Brave, or to add privacy-friendly redirects before anything opens. +Use it when different parts of your life live in different browsers: work links in Chrome, personal links in Firefox, video links in Brave, or to add privacy-friendly redirects before anything opens. The basic flow is: @@ -24,3 +24,17 @@ xdg-settings set default-web-browser io.github.alyraffauf.Switchyard.desktop ``` Or use your desktop environment's graphical settings to set Switchyard as the default browser. + +## Start in the Background + +To preload Switchyard without opening its settings window, add one of these commands to your desktop environment's startup applications: + +```bash +# Flatpak installation +flatpak run io.github.alyraffauf.Switchyard --background + +# Native installation +switchyard --background +``` + +The background option keeps Switchyard running for that session even when **Keep running in background** is disabled. If Switchyard is already running, the command exits without opening another window.