detached 507145e

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 VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +61 -14
M internal/cli/cmd/setup.go +18 -0
@@ -9,6 +9,7 @@ import (
99 "github.com/bnema/dumber/assets"
1010 "github.com/bnema/dumber/internal/application/usecase"
1111 "github.com/bnema/dumber/internal/infrastructure/desktop"
12+ "github.com/bnema/dumber/internal/infrastructure/env"
1213 )
1314
1415 var setupCmd = &cobra.Command{
@@ -62,6 +63,23 @@ func runSetupInstall(_ *cobra.Command, _ []string) error {
6263 }
6364
6465 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+
6583 adapter := desktop.New()
6684 uc := usecase.NewInstallDesktopUseCase(adapter)
6785
@@ -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.Theme65 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 (
1515 )
1616
1717 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
2324 )
2425
2526 // desktopFileTemplate is the freedesktop.org desktop entry format.
@@ -157,7 +158,7 @@ func (a *Adapter) GetStatus(ctx context.Context) (*port.DesktopIntegrationStatus
157158 out, err := exec.CommandContext(ctx, a.xdgSettingsPath, "get", "default-web-browser").Output()
158159 if err == nil {
159160 currentDefault := strings.TrimSpace(string(out))
160- status.IsDefaultBrowser = currentDefault == desktopFileName
161+ status.IsDefaultBrowser = currentDefault == desktopFileName || currentDefault == packageDesktopFileName
161162 }
162163 }
163164
@@ -306,25 +307,53 @@ func (a *Adapter) SetAsDefaultBrowser(ctx context.Context) error {
306307 return fmt.Errorf("xdg-settings not found (install xdg-utils)")
307308 }
308309
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 == "" {
315314 return fmt.Errorf("desktop file not installed - run 'dumber setup install' first")
316315 }
317316
318317 // 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)
320319 if out, err := cmd.CombinedOutput(); err != nil {
321320 return fmt.Errorf("xdg-settings failed: %s", strings.TrimSpace(string(out)))
322321 }
323322
324- log.Info().Msg("dumber set as default browser")
323+ log.Info().Str("desktop_file", desktopFile).Msg("dumber set as default browser")
325324 return nil
326325 }
327326
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+
328357 // UnsetAsDefaultBrowser resets default browser if dumber is currently default.
329358 func (a *Adapter) UnsetAsDefaultBrowser(ctx context.Context) error {
330359 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 = 064421+ iconFileName = "dumber.svg"
22- dirPerm = 075522+ 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 == desktopFileName161+ 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 installed310+ // 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 err313+ 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 browser317 // 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 nil324 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)

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close