Bring the Wails desktop shell to feature parity with the CLI for provider and alias management while keeping the browser fallback working. Wire tray, notifications, autostart, and GUI warnings so desktop flows behave predictably.
152 lines
3.9 KiB
Go
152 lines
3.9 KiB
Go
package desktop
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/Apale7/opencode-provider-switch/internal/app"
|
|
"github.com/Apale7/opencode-provider-switch/internal/config"
|
|
)
|
|
|
|
// AutoStart manages real launch-at-login integration where the platform permits
|
|
// it. Linux uses XDG autostart; Windows uses Startup folder scripts.
|
|
type AutoStart struct {
|
|
service *app.Service
|
|
ctx context.Context
|
|
}
|
|
|
|
func NewAutoStart(service *app.Service) *AutoStart {
|
|
return &AutoStart{service: service}
|
|
}
|
|
|
|
func (a *AutoStart) Attach(ctx context.Context) {
|
|
a.ctx = ctx
|
|
}
|
|
|
|
func (a *AutoStart) Detach() {
|
|
a.ctx = nil
|
|
}
|
|
|
|
func (a *AutoStart) Sync(ctx context.Context, prefs app.DesktopPrefsView) error {
|
|
_ = ctx
|
|
if runtime.GOOS != "linux" && runtime.GOOS != "windows" {
|
|
return nil
|
|
}
|
|
entryPath, err := a.entryPathForOS(runtime.GOOS)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if prefs.LaunchAtLogin {
|
|
return a.writeEntry(entryPath, runtime.GOOS)
|
|
}
|
|
if err := os.Remove(entryPath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("remove autostart entry: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *AutoStart) entryPath() (string, error) {
|
|
return a.entryPathForOS(runtime.GOOS)
|
|
}
|
|
|
|
func (a *AutoStart) entryPathForOS(goos string) (string, error) {
|
|
switch goos {
|
|
case "linux":
|
|
return a.linuxEntryPath()
|
|
case "windows":
|
|
return a.windowsEntryPath()
|
|
default:
|
|
return "", fmt.Errorf("autostart unsupported on %s", goos)
|
|
}
|
|
}
|
|
|
|
func (a *AutoStart) linuxEntryPath() (string, error) {
|
|
base := strings.TrimSpace(os.Getenv("XDG_CONFIG_HOME"))
|
|
if base == "" {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve home for autostart: %w", err)
|
|
}
|
|
base = filepath.Join(home, ".config")
|
|
}
|
|
return filepath.Join(base, "autostart", "ocswitch-desktop.desktop"), nil
|
|
}
|
|
|
|
func (a *AutoStart) windowsEntryPath() (string, error) {
|
|
base := strings.TrimSpace(os.Getenv("APPDATA"))
|
|
if base == "" {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve home for autostart: %w", err)
|
|
}
|
|
base = filepath.Join(home, "AppData", "Roaming")
|
|
}
|
|
return filepath.Join(base, "Microsoft", "Windows", "Start Menu", "Programs", "Startup", "ocswitch-desktop.cmd"), nil
|
|
}
|
|
|
|
func (a *AutoStart) writeEntry(path string, goos string) error {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return fmt.Errorf("mkdir autostart dir: %w", err)
|
|
}
|
|
execPath, err := os.Executable()
|
|
if err != nil {
|
|
return fmt.Errorf("resolve desktop executable: %w", err)
|
|
}
|
|
configPath := config.DefaultPath()
|
|
if a.service != nil {
|
|
configPath = a.service.ConfigPath()
|
|
}
|
|
var content string
|
|
switch goos {
|
|
case "linux":
|
|
content = linuxAutostartEntry(execPath, configPath)
|
|
case "windows":
|
|
content = windowsAutostartScript(execPath, configPath)
|
|
default:
|
|
return fmt.Errorf("autostart unsupported on %s", goos)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
return fmt.Errorf("write autostart entry: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func linuxAutostartEntry(execPath string, configPath string) string {
|
|
content := strings.Join([]string{
|
|
"[Desktop Entry]",
|
|
"Type=Application",
|
|
"Version=1.0",
|
|
"Name=ocswitch desktop",
|
|
"Comment=OpenCode provider switch desktop shell",
|
|
fmt.Sprintf("Exec=%s --config %s", shellQuote(execPath), shellQuote(configPath)),
|
|
"Terminal=false",
|
|
"X-GNOME-Autostart-enabled=true",
|
|
"Categories=Network;Development;",
|
|
"StartupNotify=false",
|
|
"",
|
|
}, "\n")
|
|
return content
|
|
}
|
|
|
|
func shellQuote(value string) string {
|
|
replacer := strings.NewReplacer("\\", "\\\\", `"`, `\\"`)
|
|
return `"` + replacer.Replace(value) + `"`
|
|
}
|
|
|
|
func windowsAutostartScript(execPath string, configPath string) string {
|
|
return strings.Join([]string{
|
|
"@echo off",
|
|
fmt.Sprintf("start \"\" %s --config %s", cmdQuote(execPath), cmdQuote(configPath)),
|
|
"",
|
|
}, "\r\n")
|
|
}
|
|
|
|
func cmdQuote(value string) string {
|
|
replacer := strings.NewReplacer(`"`, `""`)
|
|
return `"` + replacer.Replace(value) + `"`
|
|
}
|