···115115116116 // If browser preferences specified, try each in order
117117 if len(browserPrefs) > 0 {
118118- for _, pref := range browserPrefs {
119119- // Try with and without .desktop suffix
120120- id := pref
121121- if !strings.HasSuffix(id, ".desktop") {
122122- id = id + ".desktop"
118118+ for _, browserPreference := range browserPrefs {
119119+ if launchBrowserByID(browserPreference, sanitized) {
120120+ return
121121+ }
122122+123123+ if strings.HasSuffix(browserPreference, ".desktop") {
124124+ continue
123125 }
124124- if launchBrowserByID(id, sanitized) {
126126+127127+ if launchBrowserByID(browserPreference+".desktop", sanitized) {
125128 return
126129 }
127130 }
+6-4
internal/browser/actions.go
···2233package browser
4455-import "github.com/alyraffauf/goxdgdesktop/desktopfile"
66-77-// Action is a desktop-entry action, e.g. "new-private-window".
88-type Action = desktopfile.Action
55+// Action is an app-specific launch action, e.g. "new-private-window".
66+type Action struct {
77+ ID string
88+ Name string
99+ Exec string
1010+}
+7-18
internal/browser/launch.go
···11// SPDX-License-Identifier: GPL-3.0-or-later
2233+//go:build !darwin
44+35package browser
4657import (
···12141315const activationTokenEnv = "XDG_ACTIVATION_TOKEN"
14161515-// Launch runs cmdline for url in the background. activationToken is the
1616-// Wayland/X11 startup token for raising the window and may be empty; when
1717-// inFlatpak is set the command runs on the host via flatpak-spawn. An empty
1818-// command line is a no-op.
1919-func Launch(cmdline, url, activationToken string, inFlatpak bool) error {
2020- cmd := buildCommand(cmdline, url, activationToken, inFlatpak)
2121- if cmd == nil {
2222- return nil
2323- }
2424- if err := cmd.Start(); err != nil {
2525- return err
2626- }
2727- go cmd.Wait()
2828- return nil
2929-}
3030-3131-// buildCommand assembles the OS command that launches cmdline for url, or nil
3232-// when the resolved command line is empty.
1717+// buildCommand assembles the OS command that launches cmdline for url via a
1818+// desktop Exec line, honoring activationToken (the Wayland/X11 startup token
1919+// for raising the window, may be empty) and running on the host via
2020+// flatpak-spawn when inFlatpak is set. Returns nil when the resolved command
2121+// line is empty.
3322func buildCommand(cmdline, url, activationToken string, inFlatpak bool) *exec.Cmd {
3423 if url != "" {
3524 cmdline = desktopexec.SubstituteOrAppendURL(cmdline, url)
+17
internal/browser/launch_common.go
···11+// SPDX-License-Identifier: GPL-3.0-or-later
22+33+package browser
44+55+// Launch starts the platform command for cmdline/url in the background.
66+// Empty command lines are no-ops.
77+func Launch(cmdline, url, activationToken string, inFlatpak bool) error {
88+ cmd := buildCommand(cmdline, url, activationToken, inFlatpak)
99+ if cmd == nil {
1010+ return nil
1111+ }
1212+ if err := cmd.Start(); err != nil {
1313+ return err
1414+ }
1515+ go cmd.Wait()
1616+ return nil
1717+}
+21
internal/browser/launch_darwin.go
···11+// SPDX-License-Identifier: GPL-3.0-or-later
22+33+//go:build darwin
44+55+package browser
66+77+import "os/exec"
88+99+// buildCommand assembles the macOS open invocation for bundlePath and url.
1010+// Linux-only launch options are ignored. Returns nil when bundlePath is empty.
1111+func buildCommand(bundlePath, url, _ string, _ bool) *exec.Cmd {
1212+ if bundlePath == "" {
1313+ return nil
1414+ }
1515+1616+ args := []string{"-a", bundlePath}
1717+ if url != "" {
1818+ args = append(args, url)
1919+ }
2020+ return exec.Command("open", args...)
2121+}
···11+// SPDX-License-Identifier: GPL-3.0-or-later
22+33+package browserscan
44+55+import (
66+ "cmp"
77+ "slices"
88+ "strings"
99+1010+ "github.com/alyraffauf/switchyard/internal/browser"
1111+)
1212+1313+// A browser switcher must never list itself, across all packaged variants.
1414+const selfIDPrefix = "io.github.alyraffauf.Switchyard"
1515+1616+// isSelf reports whether id belongs to Switchyard itself.
1717+// The id is a desktop file ID on Linux and a bundle identifier on macOS.
1818+func isSelf(id string) bool {
1919+ return id == "" || strings.HasPrefix(id, selfIDPrefix)
2020+}
2121+2222+// sortByName sorts browsers in place.
2323+func sortByName(browsers []browser.Browser) {
2424+ slices.SortFunc(browsers, func(first, second browser.Browser) int {
2525+ return cmp.Compare(first.Name, second.Name)
2626+ })
2727+}
+10-5
internal/browserscan/locale.go
···11// SPDX-License-Identifier: GPL-3.0-or-later
2233+//go:build !darwin
44+35package browserscan
4657import (
···2022}
21232224func localizedActions(file *desktopfile.File) []browser.Action {
2323- actions := file.Actions()
2424- for i := range actions {
2525- section := desktopfile.ActionSectionStart + actions[i].ID
2626- if name := localizedString(file, section, "Name"); name != "" {
2727- actions[i].Name = name
2525+ desktopActions := file.Actions()
2626+ actions := make([]browser.Action, len(desktopActions))
2727+ for i, action := range desktopActions {
2828+ name := action.Name
2929+ section := desktopfile.ActionSectionStart + action.ID
3030+ if localized := localizedString(file, section, "Name"); localized != "" {
3131+ name = localized
2832 }
3333+ actions[i] = browser.Action{ID: action.ID, Name: name, Exec: action.Exec}
2934 }
3035 return actions
3136}