From e94af2a7aa38fdaac4f80a76c5438d9e41896bbb Mon Sep 17 00:00:00 2001 From: bbedward Date: Wed, 8 Jul 2026 10:01:56 -0400 Subject: [PATCH] dankinstall: add danksearch and dankcalendar as optional deps --- core/cmd/dankinstall/main.go | 8 +++ core/cmd/dms/commands_doctor.go | 1 + core/internal/distros/arch.go | 36 ++++++++++++ core/internal/distros/base.go | 8 +++ core/internal/distros/debian.go | 4 ++ core/internal/distros/dsearch_service.go | 34 +++++++++++ core/internal/distros/fedora.go | 4 ++ core/internal/distros/gentoo.go | 2 + core/internal/distros/opensuse.go | 4 ++ core/internal/distros/ubuntu.go | 4 ++ core/internal/distros/void.go | 4 ++ core/internal/headless/runner.go | 48 ++++++++++++---- core/internal/headless/runner_test.go | 68 ++++++++++++++++------ core/internal/tui/views_config.go | 12 ++++ core/internal/tui/views_dependencies.go | 73 +++++++++++++++++++----- 15 files changed, 268 insertions(+), 42 deletions(-) create mode 100644 core/internal/distros/dsearch_service.go diff --git a/core/cmd/dankinstall/main.go b/core/cmd/dankinstall/main.go index 3560a5e55..389992ab1 100644 --- a/core/cmd/dankinstall/main.go +++ b/core/cmd/dankinstall/main.go @@ -23,6 +23,8 @@ var ( replaceConfigs []string replaceConfigsAll bool yes bool + danksearch bool + dankcalendar bool ) var rootCmd = &cobra.Command{ @@ -49,6 +51,8 @@ func init() { rootCmd.Flags().StringSliceVar(&replaceConfigs, "replace-configs", []string{}, "Deploy only named configs (e.g. niri,ghostty)") rootCmd.Flags().BoolVar(&replaceConfigsAll, "replace-configs-all", false, "Deploy and replace all configurations") rootCmd.Flags().BoolVarP(&yes, "yes", "y", false, "Auto-confirm all prompts") + rootCmd.Flags().BoolVar(&danksearch, "danksearch", false, "Install danksearch and enable its user indexing service") + rootCmd.Flags().BoolVar(&dankcalendar, "dankcalendar", false, "Install dankcalendar") } func main() { @@ -74,6 +78,8 @@ func runDankinstall(cmd *cobra.Command, args []string) error { "replace-configs", "replace-configs-all", "yes", + "danksearch", + "dankcalendar", } var set []string for _, name := range headlessOnly { @@ -109,6 +115,8 @@ func runHeadless() error { ReplaceConfigs: replaceConfigs, ReplaceConfigsAll: replaceConfigsAll, Yes: yes, + DankSearch: danksearch, + DankCalendar: dankcalendar, } runner := headless.NewRunner(cfg) diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index 7a7921368..543dd82eb 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -886,6 +886,7 @@ func checkOptionalDependencies() []checkResult { {"cava", "cava", "Audio visualizer", true}, {"khal", "khal", "Calendar events", false}, {"danksearch", "dsearch", "File search", false}, + {"dankcalendar", "dcal", "Calendar app", false}, {"fprintd", "fprintd-list", "Fingerprint auth", false}, } diff --git a/core/internal/distros/arch.go b/core/internal/distros/arch.go index 5ce2d2619..f40333b70 100644 --- a/core/internal/distros/arch.go +++ b/core/internal/distros/arch.go @@ -119,10 +119,30 @@ func (a *ArchDistribution) DetectDependenciesWithTerminal(ctx context.Context, w dependencies = append(dependencies, a.detectMatugen()) dependencies = append(dependencies, a.detectDgop()) + dependencies = append(dependencies, a.detectDanksearch()) + dependencies = append(dependencies, a.detectDankCalendar()) return dependencies, nil } +func (a *ArchDistribution) detectDanksearch() deps.Dependency { + dep := a.BaseDistribution.detectDanksearch() + dep.CanToggle = true + if a.packageInstalled("dsearch-git") { + dep.Variant = deps.VariantGit + } + return dep +} + +func (a *ArchDistribution) detectDankCalendar() deps.Dependency { + dep := a.BaseDistribution.detectDankCalendar() + dep.CanToggle = true + if a.packageInstalled("dankcalendar-git") { + dep.Variant = deps.VariantGit + } + return dep +} + func (a *ArchDistribution) detectXDGPortal() deps.Dependency { return a.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", a.packageInstalled("xdg-desktop-portal-gtk")) } @@ -199,6 +219,8 @@ func (a *ArchDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, "alacritty": {Name: "alacritty", Repository: RepoTypeSystem}, "xdg-desktop-portal-gtk": {Name: "xdg-desktop-portal-gtk", Repository: RepoTypeSystem}, "accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem}, + "danksearch": a.getDanksearchMapping(variants["danksearch"]), + "dankcalendar": a.getDankCalendarMapping(variants["dankcalendar"]), } switch wm { @@ -253,6 +275,20 @@ func (a *ArchDistribution) getMatugenMapping(variant deps.PackageVariant) Packag return PackageMapping{Name: "matugen", Repository: RepoTypeSystem} } +func (a *ArchDistribution) getDanksearchMapping(variant deps.PackageVariant) PackageMapping { + if variant == deps.VariantGit { + return PackageMapping{Name: "dsearch-git", Repository: RepoTypeAUR} + } + return PackageMapping{Name: "dsearch-bin", Repository: RepoTypeAUR} +} + +func (a *ArchDistribution) getDankCalendarMapping(variant deps.PackageVariant) PackageMapping { + if variant == deps.VariantGit { + return PackageMapping{Name: "dankcalendar-git", Repository: RepoTypeAUR} + } + return PackageMapping{Name: "dankcalendar-bin", Repository: RepoTypeAUR} +} + func (a *ArchDistribution) getDMSMapping(variant deps.PackageVariant) PackageMapping { if forceDMSGit || variant == deps.VariantGit { return PackageMapping{Name: "dms-shell-git", Repository: RepoTypeAUR} diff --git a/core/internal/distros/base.go b/core/internal/distros/base.go index 194ad8bbc..603225a14 100644 --- a/core/internal/distros/base.go +++ b/core/internal/distros/base.go @@ -107,6 +107,14 @@ func (b *BaseDistribution) detectDgop() deps.Dependency { return b.detectCommand("dgop", "Desktop portal management tool") } +func (b *BaseDistribution) detectDanksearch() deps.Dependency { + return b.detectOptionalPackage("danksearch", "File indexing and search service", b.commandExists("dsearch") || b.commandExists("danksearch")) +} + +func (b *BaseDistribution) detectDankCalendar() deps.Dependency { + return b.detectOptionalPackage("dankcalendar", "Calendar application", b.commandExists("dcal") || b.commandExists("dankcalendar")) +} + func (b *BaseDistribution) detectDMS() deps.Dependency { dmsPath := filepath.Join(os.Getenv("HOME"), ".config/quickshell/dms") diff --git a/core/internal/distros/debian.go b/core/internal/distros/debian.go index d5c00aa8e..d18f252e8 100644 --- a/core/internal/distros/debian.go +++ b/core/internal/distros/debian.go @@ -71,6 +71,8 @@ func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, d.detectMatugen()) dependencies = append(dependencies, d.detectDgop()) + dependencies = append(dependencies, d.detectDanksearch()) + dependencies = append(dependencies, d.detectDankCalendar()) return dependencies, nil } @@ -135,6 +137,8 @@ func (d *DebianDistribution) GetPackageMappingWithVariants(wm deps.WindowManager "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, + "danksearch": {Name: "danksearch", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, + "dankcalendar": {Name: "dankcalendar-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, } if wm == deps.WindowManagerNiri { diff --git a/core/internal/distros/dsearch_service.go b/core/internal/distros/dsearch_service.go new file mode 100644 index 000000000..44702393c --- /dev/null +++ b/core/internal/distros/dsearch_service.go @@ -0,0 +1,34 @@ +package distros + +import ( + "context" + "fmt" + "os/exec" +) + +// SetupDsearchService enables the dsearch.service user unit. Enablement failures +// are returned for the caller to surface as a non-fatal warning. +func SetupDsearchService(ctx context.Context, logf func(string)) error { + if logf == nil { + logf = func(string) {} + } + + if err := runSystemctlUser(ctx, "daemon-reload"); err != nil { + return err + } + + if err := runSystemctlUser(ctx, "enable", "--now", "dsearch.service"); err != nil { + return err + } + logf("Enabled dsearch.service") + + return nil +} + +func runSystemctlUser(ctx context.Context, args ...string) error { + cmd := exec.CommandContext(ctx, "systemctl", append([]string{"--user"}, args...)...) + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("systemctl --user %v failed: %w: %s", args, err, string(output)) + } + return nil +} diff --git a/core/internal/distros/fedora.go b/core/internal/distros/fedora.go index dc6f57317..2899c7833 100644 --- a/core/internal/distros/fedora.go +++ b/core/internal/distros/fedora.go @@ -104,6 +104,8 @@ func (f *FedoraDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, f.detectMatugen()) dependencies = append(dependencies, f.detectDgop()) + dependencies = append(dependencies, f.detectDanksearch()) + dependencies = append(dependencies, f.detectDankCalendar()) return dependencies, nil } @@ -138,6 +140,8 @@ func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager "matugen": {Name: "matugen", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, "dms (DankMaterialShell)": f.getDmsMapping(variants["dms (DankMaterialShell)"]), "dgop": {Name: "dgop", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, + "danksearch": {Name: "danksearch", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, + "dankcalendar": {Name: "dankcalendar-git", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, } switch wm { diff --git a/core/internal/distros/gentoo.go b/core/internal/distros/gentoo.go index c5500dd15..c28fb19bb 100644 --- a/core/internal/distros/gentoo.go +++ b/core/internal/distros/gentoo.go @@ -113,6 +113,7 @@ func (g *GentooDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, g.detectMatugen()) dependencies = append(dependencies, g.detectDgop()) + dependencies = append(dependencies, g.detectDanksearch()) return dependencies, nil } @@ -171,6 +172,7 @@ func (g *GentooDistribution) GetPackageMappingWithVariants(wm deps.WindowManager "matugen": {Name: "x11-misc/matugen", Repository: RepoTypeGURU, AcceptKeywords: archKeyword}, "dms (DankMaterialShell)": g.getDmsMapping(), "dgop": {Name: "gui-apps/dgop", Repository: RepoTypeGURU, AcceptKeywords: archKeyword}, + "danksearch": {Name: "gui-apps/danksearch", Repository: RepoTypeGURU, AcceptKeywords: archKeyword}, } switch wm { diff --git a/core/internal/distros/opensuse.go b/core/internal/distros/opensuse.go index a9b290681..aa5733299 100644 --- a/core/internal/distros/opensuse.go +++ b/core/internal/distros/opensuse.go @@ -91,6 +91,8 @@ func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Contex dependencies = append(dependencies, o.detectMatugen()) dependencies = append(dependencies, o.detectDgop()) + dependencies = append(dependencies, o.detectDanksearch()) + dependencies = append(dependencies, o.detectDankCalendar()) return dependencies, nil } @@ -129,6 +131,8 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag "ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, + "danksearch": {Name: "danksearch", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, + "dankcalendar": {Name: "dankcalendar-git", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, } switch wm { diff --git a/core/internal/distros/ubuntu.go b/core/internal/distros/ubuntu.go index d99ec6b1f..f1fe0ed1e 100644 --- a/core/internal/distros/ubuntu.go +++ b/core/internal/distros/ubuntu.go @@ -80,6 +80,8 @@ func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, u.detectMatugen()) dependencies = append(dependencies, u.detectDgop()) + dependencies = append(dependencies, u.detectDanksearch()) + dependencies = append(dependencies, u.detectDankCalendar()) return dependencies, nil } @@ -124,6 +126,8 @@ func (u *UbuntuDistribution) GetPackageMappingWithVariants(wm deps.WindowManager "matugen": {Name: "matugen", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, "ghostty": {Name: "ghostty", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, + "danksearch": {Name: "danksearch", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, + "dankcalendar": {Name: "dankcalendar-git", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, } switch wm { diff --git a/core/internal/distros/void.go b/core/internal/distros/void.go index 41ddd0759..581062470 100644 --- a/core/internal/distros/void.go +++ b/core/internal/distros/void.go @@ -83,6 +83,8 @@ func (v *VoidDistribution) DetectDependenciesWithTerminal(ctx context.Context, w dependencies = append(dependencies, v.detectMatugen()) dependencies = append(dependencies, v.detectDgop()) + dependencies = append(dependencies, v.detectDanksearch()) + dependencies = append(dependencies, v.detectDankCalendar()) return dependencies, nil } @@ -176,6 +178,8 @@ func (v *VoidDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, "dms (DankMaterialShell)": v.getDmsMapping(variants["dms (DankMaterialShell)"]), "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeXBPS, RepoURL: VoidDMSRepo}, "dgop": {Name: "dgop", Repository: RepoTypeXBPS, RepoURL: VoidDankLinuxRepo}, + "danksearch": {Name: "danksearch", Repository: RepoTypeXBPS, RepoURL: VoidDankLinuxRepo}, + "dankcalendar": {Name: "dankcalendar", Repository: RepoTypeXBPS, RepoURL: VoidDankLinuxRepo}, } switch wm { diff --git a/core/internal/headless/runner.go b/core/internal/headless/runner.go index beaf71c42..f7bc8903d 100644 --- a/core/internal/headless/runner.go +++ b/core/internal/headless/runner.go @@ -41,6 +41,8 @@ type Config struct { ReplaceConfigs []string // specific configs to deploy (e.g. "niri", "ghostty") ReplaceConfigsAll bool // deploy/replace all configurations Yes bool + DankSearch bool // install danksearch and enable its user service + DankCalendar bool // install dankcalendar } // Runner orchestrates unattended (headless) installation. @@ -214,6 +216,11 @@ func (r *Runner) Run() error { return fmt.Errorf("package installation failed: %w", err) } + useSystemd := true + if distroConfig, exists := distros.Registry[osInfo.Distribution.ID]; exists && distroConfig.Family == distros.FamilyVoid { + useSystemd = false + } + // 9. Greeter setup (if dms-greeter was included) if !disabledItems["dms-greeter"] && r.depExists(dependencies, "dms-greeter") { compositorName := "niri" @@ -231,15 +238,24 @@ func (r *Runner) Run() error { } } + // 9b. danksearch service setup (if danksearch was included) + if useSystemd && !disabledItems["danksearch"] && r.depExists(dependencies, "danksearch") { + fmt.Fprintln(os.Stdout, "Enabling danksearch service...") + logFunc := func(line string) { + r.log(line) + fmt.Fprintf(os.Stdout, " danksearch: %s\n", line) + } + if err := distros.SetupDsearchService(context.Background(), logFunc); err != nil { + // Non-fatal, matching greeter behavior + fmt.Fprintf(os.Stderr, "Warning: danksearch service setup issue (non-fatal): %v\n", err) + } + } + // 10. Deploy configurations fmt.Fprintln(os.Stdout, "Deploying configurations...") r.log("Starting configuration deployment") deployer := config.NewConfigDeployer(r.logChan) - useSystemd := true - if distroConfig, exists := distros.Registry[osInfo.Distribution.ID]; exists && distroConfig.Family == distros.FamilyVoid { - useSystemd = false - } results, err := deployer.DeployConfigurationsSelectiveWithReinstallsAndSystemd( context.Background(), wm, @@ -272,19 +288,31 @@ func (r *Runner) Run() error { } // buildDisabledItems computes the set of dependencies that should be skipped -// during installation, applying the --include-deps and --exclude-deps filters. -// dms-greeter is disabled by default (opt-in), matching TUI behavior. +// during installation. Optional components are opt-in (disabled by default), +// then re-enabled by the dedicated flags and --include-deps. func (r *Runner) buildDisabledItems(dependencies []deps.Dependency) (map[string]bool, error) { disabledItems := make(map[string]bool) - // dms-greeter is opt-in (disabled by default), matching TUI behavior for i := range dependencies { - if dependencies[i].Name == "dms-greeter" { - disabledItems["dms-greeter"] = true - break + if !dependencies[i].Required { + disabledItems[dependencies[i].Name] = true } } + // Dedicated flags resolve before include/exclude + if r.cfg.DankSearch { + if !r.depExists(dependencies, "danksearch") { + return nil, fmt.Errorf("--danksearch: not available on this distribution") + } + delete(disabledItems, "danksearch") + } + if r.cfg.DankCalendar { + if !r.depExists(dependencies, "dankcalendar") { + return nil, fmt.Errorf("--dankcalendar: not available on this distribution") + } + delete(disabledItems, "dankcalendar") + } + // Process --include-deps (enable items that are disabled by default) for _, name := range r.cfg.IncludeDeps { name = strings.TrimSpace(name) diff --git a/core/internal/headless/runner_test.go b/core/internal/headless/runner_test.go index 79260ae3d..d667644e5 100644 --- a/core/internal/headless/runner_test.go +++ b/core/internal/headless/runner_test.go @@ -342,17 +342,21 @@ func TestConfigReplaceConfigsStoredCorrectly(t *testing.T) { func TestBuildDisabledItems(t *testing.T) { dependencies := []deps.Dependency{ - {Name: "niri", Status: deps.StatusInstalled}, - {Name: "ghostty", Status: deps.StatusMissing}, - {Name: "dms (DankMaterialShell)", Status: deps.StatusInstalled}, + {Name: "niri", Status: deps.StatusInstalled, Required: true}, + {Name: "ghostty", Status: deps.StatusMissing, Required: true}, + {Name: "dms (DankMaterialShell)", Status: deps.StatusInstalled, Required: true}, {Name: "dms-greeter", Status: deps.StatusMissing}, - {Name: "waybar", Status: deps.StatusMissing}, + {Name: "danksearch", Status: deps.StatusMissing}, + {Name: "dankcalendar", Status: deps.StatusMissing}, + {Name: "waybar", Status: deps.StatusMissing, Required: true}, } tests := []struct { name string includeDeps []string excludeDeps []string + dankSearch bool + dankCalendar bool deps []deps.Dependency // nil means use the shared fixture wantErr bool errContains string // substring expected in error message @@ -360,19 +364,20 @@ func TestBuildDisabledItems(t *testing.T) { wantEnabled []string // dep names that should NOT be in disabledItems (extra check) }{ { - name: "no flags set, dms-greeter disabled by default", - wantDisabled: []string{"dms-greeter"}, + name: "no flags set, optional deps disabled by default", + wantDisabled: []string{"dms-greeter", "danksearch", "dankcalendar"}, wantEnabled: []string{"niri", "ghostty", "waybar"}, }, { - name: "include dms-greeter enables it", - includeDeps: []string{"dms-greeter"}, - wantEnabled: []string{"dms-greeter"}, + name: "include dms-greeter enables it", + includeDeps: []string{"dms-greeter"}, + wantEnabled: []string{"dms-greeter"}, + wantDisabled: []string{"danksearch", "dankcalendar"}, }, { name: "exclude a regular dep", excludeDeps: []string{"waybar"}, - wantDisabled: []string{"dms-greeter", "waybar"}, + wantDisabled: []string{"dms-greeter", "danksearch", "dankcalendar", "waybar"}, }, { name: "include unknown dep returns error", @@ -399,24 +404,53 @@ func TestBuildDisabledItems(t *testing.T) { wantDisabled: []string{"dms-greeter"}, }, { - name: "whitespace entries are skipped", - includeDeps: []string{" ", "dms-greeter"}, - wantEnabled: []string{"dms-greeter"}, + name: "whitespace entries are skipped", + includeDeps: []string{" ", "dms-greeter"}, + wantEnabled: []string{"dms-greeter"}, + wantDisabled: []string{"danksearch", "dankcalendar"}, }, { - name: "no dms-greeter in deps, nothing disabled by default", + name: "no optional deps present, nothing disabled by default", deps: []deps.Dependency{ - {Name: "niri", Status: deps.StatusInstalled}, + {Name: "niri", Status: deps.StatusInstalled, Required: true}, }, wantEnabled: []string{"niri"}, }, + { + name: "danksearch flag enables it", + dankSearch: true, + wantEnabled: []string{"danksearch"}, + wantDisabled: []string{"dms-greeter", "dankcalendar"}, + }, + { + name: "dankcalendar flag enables it", + dankCalendar: true, + wantEnabled: []string{"dankcalendar"}, + wantDisabled: []string{"dms-greeter", "danksearch"}, + }, + { + name: "danksearch flag when unavailable errors", + dankSearch: true, + deps: []deps.Dependency{{Name: "niri", Status: deps.StatusInstalled, Required: true}}, + wantErr: true, + errContains: "--danksearch", + }, + { + name: "dankcalendar flag when unavailable errors", + dankCalendar: true, + deps: []deps.Dependency{{Name: "niri", Status: deps.StatusInstalled, Required: true}}, + wantErr: true, + errContains: "--dankcalendar", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := NewRunner(Config{ - IncludeDeps: tt.includeDeps, - ExcludeDeps: tt.excludeDeps, + IncludeDeps: tt.includeDeps, + ExcludeDeps: tt.excludeDeps, + DankSearch: tt.dankSearch, + DankCalendar: tt.dankCalendar, }) d := tt.deps if d == nil { diff --git a/core/internal/tui/views_config.go b/core/internal/tui/views_config.go index e5cd60901..dd527499e 100644 --- a/core/internal/tui/views_config.go +++ b/core/internal/tui/views_config.go @@ -139,6 +139,18 @@ func (m Model) deployConfigurations() tea.Cmd { } } +func (m Model) optionalDepSelected(name string) bool { + if m.disabledItems[name] { + return false + } + for _, dep := range m.dependencies { + if dep.Name == name { + return true + } + } + return false +} + func (m Model) useSystemdConfig() bool { if m.osInfo == nil { return true diff --git a/core/internal/tui/views_dependencies.go b/core/internal/tui/views_dependencies.go index e9eb44f3f..e28945eb3 100644 --- a/core/internal/tui/views_dependencies.go +++ b/core/internal/tui/views_dependencies.go @@ -28,6 +28,21 @@ func (m Model) viewDetectingDeps() string { return b.String() } +func partitionOptionalLast(dependencies []deps.Dependency) []deps.Dependency { + ordered := make([]deps.Dependency, 0, len(dependencies)) + for _, dep := range dependencies { + if dep.Required { + ordered = append(ordered, dep) + } + } + for _, dep := range dependencies { + if !dep.Required { + ordered = append(ordered, dep) + } + } + return ordered +} + func (m Model) viewDependencyReview() string { var b strings.Builder @@ -39,7 +54,15 @@ func (m Model) viewDependencyReview() string { b.WriteString("\n\n") if len(m.dependencies) > 0 { + optionalHeaderShown := false for i, dep := range m.dependencies { + if !dep.Required && !optionalHeaderShown { + b.WriteString("\n") + b.WriteString(m.styles.Subtle.Render("Optional (space to enable)")) + b.WriteString("\n") + optionalHeaderShown = true + } + var status string var reinstallMarker string var variantMarker string @@ -82,8 +105,13 @@ func (m Model) viewDependencyReview() string { } note := "" - if dep.Name == "dms-greeter" { + switch dep.Name { + case "dms-greeter": note = m.styles.Subtle.Render(" (selection replaces your current display manager)") + case "danksearch": + note = m.styles.Subtle.Render(" (file search; enables dsearch.service)") + case "dankcalendar": + note = m.styles.Subtle.Render(" (autostart managed in dankcalendar settings)") } var line string @@ -120,13 +148,13 @@ func (m Model) updateDetectingDepsState(msg tea.Msg) (tea.Model, tea.Cmd) { m.err = depsMsg.err m.state = StateError } else { - m.dependencies = depsMsg.deps - // dms-greeter is opt-in skipped by default - for _, dep := range depsMsg.deps { - if dep.Name == "dms-greeter" { - m.disabledItems["dms-greeter"] = true - break + m.dependencies = partitionOptionalLast(depsMsg.deps) + // Optional components are opt-in, skipped by default + for _, dep := range m.dependencies { + if dep.Required { + continue } + m.disabledItems[dep.Name] = true } m.state = StateDependencyReview } @@ -231,14 +259,7 @@ func (m Model) installPackages() tea.Cmd { for msg := range installerProgressChan { // Run optional greeter setup if msg.Phase == distros.PhaseComplete && msg.IsComplete && msg.Error == nil { - greeterSelected := false - for _, dep := range m.dependencies { - if dep.Name == "dms-greeter" && !m.disabledItems["dms-greeter"] { - greeterSelected = true - break - } - } - if greeterSelected { + if m.optionalDepSelected("dms-greeter") { compositorName := "niri" switch m.selectedWindowManager() { case deps.WindowManagerHyprland: @@ -265,6 +286,28 @@ func (m Model) installPackages() tea.Cmd { logOutput: fmt.Sprintf("⚠ Greeter auto-setup warning (non-fatal): %v", err), } } + + if m.useSystemdConfig() && m.optionalDepSelected("danksearch") { + m.packageProgressChan <- packageInstallProgressMsg{ + progress: 0.97, + step: "Enabling danksearch service...", + logOutput: "Setting up dsearch.service...", + } + dsearchLogFunc := func(line string) { + m.packageProgressChan <- packageInstallProgressMsg{ + progress: 0.97, + step: "Enabling danksearch service...", + logOutput: line, + } + } + if err := distros.SetupDsearchService(context.Background(), dsearchLogFunc); err != nil { + m.packageProgressChan <- packageInstallProgressMsg{ + progress: 0.98, + step: "danksearch service warning", + logOutput: fmt.Sprintf("danksearch service setup warning (non-fatal): %v", err), + } + } + } } } tuiMsg := packageInstallProgressMsg{