fix(setup): support AUR and Flatpak desktop file detection
- commit
- 507145ed4c6434c21ba302a1a8ea326fdaf4f989
- author
- bnema · · 2026-01-06 18:16 UTC
- committer
- bnema · · 2026-01-06 19:04 UTC
- parent
- 1cac971
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +61 -14
M internal/cli/cmd/setup.go +18 -0
| @@ -9,6 +9,7 @@ import ( | |||
| 9 | 9 | "github.com/bnema/dumber/assets" | |
| 10 | 10 | "github.com/bnema/dumber/internal/application/usecase" | |
| 11 | 11 | "github.com/bnema/dumber/internal/infrastructure/desktop" | |
| 12 | + | "github.com/bnema/dumber/internal/infrastructure/env" | |
| 12 | 13 | ) | |
| 13 | 14 | ||
| 14 | 15 | var setupCmd = &cobra.Command{ | |
| @@ -62,6 +63,23 @@ func runSetupInstall(_ *cobra.Command, _ []string) error { | |||
| 62 | 63 | } | |
| 63 | 64 | ||
| 64 | 65 | theme := app.Theme | |
| 66 | + | ||
| 67 | + | // Check if installed via package manager | |
| 68 | + | switch { | |
| 69 | + | case env.IsFlatpak(): | |
| 70 | + | fmt.Printf("%s Desktop integration already handled by Flatpak\n", | |
| 71 | + | theme.SuccessStyle.Render("\u2713")) | |
| 72 | + | fmt.Println() | |
| 73 | + | fmt.Println(theme.Subtle.Render("Run 'dumber setup default' to set as default browser")) | |
| 74 | + | return nil | |
| 75 | + | case env.IsPacman(): | |
| 76 | + | fmt.Printf("%s Desktop integration already handled by AUR package\n", | |
| 77 | + | theme.SuccessStyle.Render("\u2713")) | |
| 78 | + | fmt.Println() | |
| 79 | + | fmt.Println(theme.Subtle.Render("Run 'dumber setup default' to set as default browser")) | |
| 80 | + | return nil | |
| 81 | + | } | |
| 82 | + | ||
| 65 | 83 | adapter := desktop.New() | |
| 66 | 84 | uc := usecase.NewInstallDesktopUseCase(adapter) | |
| 67 | 85 | ||
| @@ -9,6 +9,7 @@ import ( | |||||
| 9 | "github.com/bnema/dumber/assets" | 9 | "github.com/bnema/dumber/assets" | ||
| 10 | "github.com/bnema/dumber/internal/application/usecase" | 10 | "github.com/bnema/dumber/internal/application/usecase" | ||
| 11 | "github.com/bnema/dumber/internal/infrastructure/desktop" | 11 | "github.com/bnema/dumber/internal/infrastructure/desktop" | ||
| 12 | + | "github.com/bnema/dumber/internal/infrastructure/env" | |||
| 12 | ) | 13 | ) | ||
| 13 | 14 | ||||
| 14 | var setupCmd = &cobra.Command{ | 15 | var setupCmd = &cobra.Command{ | ||
| @@ -62,6 +63,23 @@ func runSetupInstall(_ *cobra.Command, _ []string) error { | |||||
| 62 | } | 63 | } | ||
| 63 | 64 | ||||
| 64 | theme := app.Theme | 65 | theme := app.Theme | ||
| 66 | + | ||||
| 67 | + | // Check if installed via package manager | |||
| 68 | + | switch { | |||
| 69 | + | case env.IsFlatpak(): | |||
| 70 | + | fmt.Printf("%s Desktop integration already handled by Flatpak\n", | |||
| 71 | + | theme.SuccessStyle.Render("\u2713")) | |||
| 72 | + | fmt.Println() | |||
| 73 | + | fmt.Println(theme.Subtle.Render("Run 'dumber setup default' to set as default browser")) | |||
| 74 | + | return nil | |||
| 75 | + | case env.IsPacman(): | |||
| 76 | + | fmt.Printf("%s Desktop integration already handled by AUR package\n", | |||
| 77 | + | theme.SuccessStyle.Render("\u2713")) | |||
| 78 | + | fmt.Println() | |||
| 79 | + | fmt.Println(theme.Subtle.Render("Run 'dumber setup default' to set as default browser")) | |||
| 80 | + | return nil | |||
| 81 | + | } | |||
| 82 | + | ||||
| 65 | adapter := desktop.New() | 83 | adapter := desktop.New() | ||
| 66 | uc := usecase.NewInstallDesktopUseCase(adapter) | 84 | uc := usecase.NewInstallDesktopUseCase(adapter) | ||
| 67 | 85 | ||||
M internal/infrastructure/desktop/adapter.go +43 -14
| @@ -15,11 +15,12 @@ import ( | |||
| 15 | 15 | ) | |
| 16 | 16 | ||
| 17 | 17 | const ( | |
| 18 | - | appName = "dumber" | |
| 19 | - | desktopFileName = "dumber.desktop" | |
| 20 | - | iconFileName = "dumber.svg" | |
| 21 | - | filePerm = 0644 | |
| 22 | - | dirPerm = 0755 | |
| 18 | + | appName = "dumber" | |
| 19 | + | desktopFileName = "dumber.desktop" | |
| 20 | + | packageDesktopFileName = "dev.bnema.Dumber.desktop" // Used by AUR/Flatpak | |
| 21 | + | iconFileName = "dumber.svg" | |
| 22 | + | filePerm = 0644 | |
| 23 | + | dirPerm = 0755 | |
| 23 | 24 | ) | |
| 24 | 25 | ||
| 25 | 26 | // desktopFileTemplate is the freedesktop.org desktop entry format. | |
| @@ -157,7 +158,7 @@ func (a *Adapter) GetStatus(ctx context.Context) (*port.DesktopIntegrationStatus | |||
| 157 | 158 | out, err := exec.CommandContext(ctx, a.xdgSettingsPath, "get", "default-web-browser").Output() | |
| 158 | 159 | if err == nil { | |
| 159 | 160 | currentDefault := strings.TrimSpace(string(out)) | |
| 160 | - | status.IsDefaultBrowser = currentDefault == desktopFileName | |
| 161 | + | status.IsDefaultBrowser = currentDefault == desktopFileName || currentDefault == packageDesktopFileName | |
| 161 | 162 | } | |
| 162 | 163 | } | |
| 163 | 164 | ||
| @@ -306,25 +307,53 @@ func (a *Adapter) SetAsDefaultBrowser(ctx context.Context) error { | |||
| 306 | 307 | return fmt.Errorf("xdg-settings not found (install xdg-utils)") | |
| 307 | 308 | } | |
| 308 | 309 | ||
| 309 | - | // Check if desktop file is installed | |
| 310 | - | desktopPath, err := getDesktopFilePath() | |
| 311 | - | if err != nil { | |
| 312 | - | return err | |
| 313 | - | } | |
| 314 | - | if _, err := os.Stat(desktopPath); os.IsNotExist(err) { | |
| 310 | + | // Determine which desktop file to use | |
| 311 | + | // Priority: package manager desktop file (AUR/Flatpak) > user-installed | |
| 312 | + | desktopFile := a.findDesktopFile(ctx) | |
| 313 | + | if desktopFile == "" { | |
| 315 | 314 | return fmt.Errorf("desktop file not installed - run 'dumber setup install' first") | |
| 316 | 315 | } | |
| 317 | 316 | ||
| 318 | 317 | // Set default browser | |
| 319 | - | cmd := exec.CommandContext(ctx, a.xdgSettingsPath, "set", "default-web-browser", desktopFileName) | |
| 318 | + | cmd := exec.CommandContext(ctx, a.xdgSettingsPath, "set", "default-web-browser", desktopFile) | |
| 320 | 319 | if out, err := cmd.CombinedOutput(); err != nil { | |
| 321 | 320 | return fmt.Errorf("xdg-settings failed: %s", strings.TrimSpace(string(out))) | |
| 322 | 321 | } | |
| 323 | 322 | ||
| 324 | - | log.Info().Msg("dumber set as default browser") | |
| 323 | + | log.Info().Str("desktop_file", desktopFile).Msg("dumber set as default browser") | |
| 325 | 324 | return nil | |
| 326 | 325 | } | |
| 327 | 326 | ||
| 327 | + | // findDesktopFile returns the desktop file name to use for default browser setting. | |
| 328 | + | // Checks system paths first (AUR/Flatpak), then user path (manual install). | |
| 329 | + | func (a *Adapter) findDesktopFile(ctx context.Context) string { | |
| 330 | + | log := logging.FromContext(ctx) | |
| 331 | + | ||
| 332 | + | // Check system-wide paths for package manager installs | |
| 333 | + | systemPaths := []string{ | |
| 334 | + | "/usr/share/applications/" + packageDesktopFileName, | |
| 335 | + | "/app/share/applications/" + packageDesktopFileName, // Flatpak | |
| 336 | + | } | |
| 337 | + | ||
| 338 | + | for _, path := range systemPaths { | |
| 339 | + | if _, err := os.Stat(path); err == nil { | |
| 340 | + | log.Debug().Str("path", path).Msg("found package desktop file") | |
| 341 | + | return packageDesktopFileName | |
| 342 | + | } | |
| 343 | + | } | |
| 344 | + | ||
| 345 | + | // Check user path for manual installs | |
| 346 | + | userPath, err := getDesktopFilePath() | |
| 347 | + | if err == nil { | |
| 348 | + | if _, err := os.Stat(userPath); err == nil { | |
| 349 | + | log.Debug().Str("path", userPath).Msg("found user desktop file") | |
| 350 | + | return desktopFileName | |
| 351 | + | } | |
| 352 | + | } | |
| 353 | + | ||
| 354 | + | return "" | |
| 355 | + | } | |
| 356 | + | ||
| 328 | 357 | // UnsetAsDefaultBrowser resets default browser if dumber is currently default. | |
| 329 | 358 | func (a *Adapter) UnsetAsDefaultBrowser(ctx context.Context) error { | |
| 330 | 359 | log := logging.FromContext(ctx) | |
| @@ -15,11 +15,12 @@ import ( | |||||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| 17 | const ( | 17 | const ( | ||
| 18 | - | appName = "dumber" | 18 | + | appName = "dumber" |
| 19 | - | desktopFileName = "dumber.desktop" | 19 | + | desktopFileName = "dumber.desktop" |
| 20 | - | iconFileName = "dumber.svg" | 20 | + | packageDesktopFileName = "dev.bnema.Dumber.desktop" // Used by AUR/Flatpak |
| 21 | - | filePerm = 0644 | 21 | + | iconFileName = "dumber.svg" |
| 22 | - | dirPerm = 0755 | 22 | + | filePerm = 0644 |
| 23 | + | dirPerm = 0755 | |||
| 23 | ) | 24 | ) | ||
| 24 | 25 | ||||
| 25 | // desktopFileTemplate is the freedesktop.org desktop entry format. | 26 | // desktopFileTemplate is the freedesktop.org desktop entry format. | ||
| @@ -157,7 +158,7 @@ func (a *Adapter) GetStatus(ctx context.Context) (*port.DesktopIntegrationStatus | |||||
| 157 | out, err := exec.CommandContext(ctx, a.xdgSettingsPath, "get", "default-web-browser").Output() | 158 | out, err := exec.CommandContext(ctx, a.xdgSettingsPath, "get", "default-web-browser").Output() | ||
| 158 | if err == nil { | 159 | if err == nil { | ||
| 159 | currentDefault := strings.TrimSpace(string(out)) | 160 | currentDefault := strings.TrimSpace(string(out)) | ||
| 160 | - | status.IsDefaultBrowser = currentDefault == desktopFileName | 161 | + | status.IsDefaultBrowser = currentDefault == desktopFileName || currentDefault == packageDesktopFileName |
| 161 | } | 162 | } | ||
| 162 | } | 163 | } | ||
| 163 | 164 | ||||
| @@ -306,25 +307,53 @@ func (a *Adapter) SetAsDefaultBrowser(ctx context.Context) error { | |||||
| 306 | return fmt.Errorf("xdg-settings not found (install xdg-utils)") | 307 | return fmt.Errorf("xdg-settings not found (install xdg-utils)") | ||
| 307 | } | 308 | } | ||
| 308 | 309 | ||||
| 309 | - | // Check if desktop file is installed | 310 | + | // Determine which desktop file to use |
| 310 | - | desktopPath, err := getDesktopFilePath() | 311 | + | // Priority: package manager desktop file (AUR/Flatpak) > user-installed |
| 311 | - | if err != nil { | 312 | + | desktopFile := a.findDesktopFile(ctx) |
| 312 | - | return err | 313 | + | if desktopFile == "" { |
| 313 | - | } | |||
| 314 | - | if _, err := os.Stat(desktopPath); os.IsNotExist(err) { | |||
| 315 | return fmt.Errorf("desktop file not installed - run 'dumber setup install' first") | 314 | return fmt.Errorf("desktop file not installed - run 'dumber setup install' first") | ||
| 316 | } | 315 | } | ||
| 317 | 316 | ||||
| 318 | // Set default browser | 317 | // Set default browser | ||
| 319 | - | cmd := exec.CommandContext(ctx, a.xdgSettingsPath, "set", "default-web-browser", desktopFileName) | 318 | + | cmd := exec.CommandContext(ctx, a.xdgSettingsPath, "set", "default-web-browser", desktopFile) |
| 320 | if out, err := cmd.CombinedOutput(); err != nil { | 319 | if out, err := cmd.CombinedOutput(); err != nil { | ||
| 321 | return fmt.Errorf("xdg-settings failed: %s", strings.TrimSpace(string(out))) | 320 | return fmt.Errorf("xdg-settings failed: %s", strings.TrimSpace(string(out))) | ||
| 322 | } | 321 | } | ||
| 323 | 322 | ||||
| 324 | - | log.Info().Msg("dumber set as default browser") | 323 | + | log.Info().Str("desktop_file", desktopFile).Msg("dumber set as default browser") |
| 325 | return nil | 324 | return nil | ||
| 326 | } | 325 | } | ||
| 327 | 326 | ||||
| 327 | + | // findDesktopFile returns the desktop file name to use for default browser setting. | |||
| 328 | + | // Checks system paths first (AUR/Flatpak), then user path (manual install). | |||
| 329 | + | func (a *Adapter) findDesktopFile(ctx context.Context) string { | |||
| 330 | + | log := logging.FromContext(ctx) | |||
| 331 | + | ||||
| 332 | + | // Check system-wide paths for package manager installs | |||
| 333 | + | systemPaths := []string{ | |||
| 334 | + | "/usr/share/applications/" + packageDesktopFileName, | |||
| 335 | + | "/app/share/applications/" + packageDesktopFileName, // Flatpak | |||
| 336 | + | } | |||
| 337 | + | ||||
| 338 | + | for _, path := range systemPaths { | |||
| 339 | + | if _, err := os.Stat(path); err == nil { | |||
| 340 | + | log.Debug().Str("path", path).Msg("found package desktop file") | |||
| 341 | + | return packageDesktopFileName | |||
| 342 | + | } | |||
| 343 | + | } | |||
| 344 | + | ||||
| 345 | + | // Check user path for manual installs | |||
| 346 | + | userPath, err := getDesktopFilePath() | |||
| 347 | + | if err == nil { | |||
| 348 | + | if _, err := os.Stat(userPath); err == nil { | |||
| 349 | + | log.Debug().Str("path", userPath).Msg("found user desktop file") | |||
| 350 | + | return desktopFileName | |||
| 351 | + | } | |||
| 352 | + | } | |||
| 353 | + | ||||
| 354 | + | return "" | |||
| 355 | + | } | |||
| 356 | + | ||||
| 328 | // UnsetAsDefaultBrowser resets default browser if dumber is currently default. | 357 | // UnsetAsDefaultBrowser resets default browser if dumber is currently default. | ||
| 329 | func (a *Adapter) UnsetAsDefaultBrowser(ctx context.Context) error { | 358 | func (a *Adapter) UnsetAsDefaultBrowser(ctx context.Context) error { | ||
| 330 | log := logging.FromContext(ctx) | 359 | log := logging.FromContext(ctx) | ||