//go:build !distro_binary package dms import ( "context" "fmt" "os/exec" "strings" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/deps" "github.com/AvengeMedia/DankMaterialShell/core/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/greeter" tea "github.com/charmbracelet/bubbletea" ) func (m Model) updateUpdateView(msg tea.KeyMsg) (tea.Model, tea.Cmd) { filteredDeps := m.getFilteredDeps() maxIndex := len(filteredDeps) - 1 switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "esc": m.state = StateMainMenu case "up", "k": if m.selectedUpdateDep > 0 { m.selectedUpdateDep-- } case "down", "j": if m.selectedUpdateDep < maxIndex { m.selectedUpdateDep++ } case " ": if dep := m.getDepAtVisualIndex(m.selectedUpdateDep); dep != nil { m.updateToggles[dep.Name] = !m.updateToggles[dep.Name] } case "enter": hasSelected := false for _, toggle := range m.updateToggles { if toggle { hasSelected = true break } } if !hasSelected { m.state = StateMainMenu return m, nil } m.state = StateUpdatePassword m.passwordInput = "" m.passwordError = "" return m, nil } return m, nil } func (m Model) updatePasswordView(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "ctrl+c": return m, tea.Quit case "esc": m.state = StateUpdate m.passwordInput = "" m.passwordError = "" return m, nil case "enter": if m.passwordInput == "" { return m, nil } return m, m.validatePassword(m.passwordInput) case "backspace": if len(m.passwordInput) > 0 { m.passwordInput = m.passwordInput[:len(m.passwordInput)-1] } default: if len(msg.String()) == 1 && msg.String()[0] >= 32 && msg.String()[0] <= 126 { m.passwordInput += msg.String() } } return m, nil } func (m Model) updateProgressView(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "esc": if m.updateProgress.complete { m.state = StateMainMenu m.updateProgress = updateProgressMsg{} m.updateLogs = []string{} } } return m, nil } func (m Model) validatePassword(password string) tea.Cmd { return func() tea.Msg { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "sudo", "-S", "-v") stdin, err := cmd.StdinPipe() if err != nil { return passwordValidMsg{password: "", valid: false} } go func() { defer stdin.Close() fmt.Fprintf(stdin, "%s\n", password) }() output, err := cmd.CombinedOutput() outputStr := string(output) if err != nil { if strings.Contains(outputStr, "Sorry, try again") || strings.Contains(outputStr, "incorrect password") || strings.Contains(outputStr, "authentication failure") { return passwordValidMsg{password: "", valid: false} } return passwordValidMsg{password: "", valid: false} } return passwordValidMsg{password: password, valid: true} } } func (m Model) performUpdate() tea.Cmd { var depsToUpdate []deps.Dependency for _, depInfo := range m.updateDeps { if m.updateToggles[depInfo.Name] { depsToUpdate = append(depsToUpdate, deps.Dependency{ Name: depInfo.Name, Status: depInfo.Status, Description: depInfo.Description, Required: depInfo.Required, }) } } if len(depsToUpdate) == 0 { return func() tea.Msg { return updateCompleteMsg{err: nil} } } wm := deps.WindowManagerHyprland if m.niriInstalled { wm = deps.WindowManagerNiri } sudoPassword := m.sudoPassword reinstallFlags := make(map[string]bool) for name, toggled := range m.updateToggles { if toggled { reinstallFlags[name] = true } } distribution := m.detector.GetDistribution() progressChan := m.updateProgressChan return func() tea.Msg { installerChan := make(chan distros.InstallProgressMsg, 100) go func() { ctx := context.Background() disabledFlags := make(map[string]bool) err := distribution.InstallPackages(ctx, depsToUpdate, wm, sudoPassword, reinstallFlags, disabledFlags, false, installerChan) close(installerChan) if err != nil { progressChan <- updateProgressMsg{complete: true, err: err} } else { progressChan <- updateProgressMsg{complete: true} } }() go func() { for msg := range installerChan { progressChan <- updateProgressMsg{ progress: msg.Progress, step: msg.Step, complete: msg.IsComplete, err: msg.Error, logOutput: msg.LogOutput, } } }() return nil } } func (m Model) updateGreeterMenu(msg tea.KeyMsg) (tea.Model, tea.Cmd) { greeterMenuItems := []string{"Install Greeter"} switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "esc": m.state = StateMainMenu case "up", "k": if m.selectedGreeterItem > 0 { m.selectedGreeterItem-- } case "down", "j": if m.selectedGreeterItem < len(greeterMenuItems)-1 { m.selectedGreeterItem++ } case "enter", " ": if m.selectedGreeterItem == 0 { compositors := greeter.DetectCompositors() if len(compositors) == 0 { return m, nil } m.greeterCompositors = compositors if len(compositors) > 1 { m.state = StateGreeterCompositorSelect m.greeterSelectedComp = 0 return m, nil } else { m.greeterChosenCompositor = compositors[0] m.state = StateGreeterPassword m.greeterPasswordInput = "" m.greeterPasswordError = "" return m, nil } } } return m, nil } func (m Model) updateGreeterCompositorSelect(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "esc": m.state = StateGreeterMenu return m, nil case "up", "k": if m.greeterSelectedComp > 0 { m.greeterSelectedComp-- } case "down", "j": if m.greeterSelectedComp < len(m.greeterCompositors)-1 { m.greeterSelectedComp++ } case "enter", " ": m.greeterChosenCompositor = m.greeterCompositors[m.greeterSelectedComp] m.state = StateGreeterPassword m.greeterPasswordInput = "" m.greeterPasswordError = "" return m, nil } return m, nil } func (m Model) updateGreeterPasswordView(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "ctrl+c": return m, tea.Quit case "esc": m.state = StateGreeterMenu m.greeterPasswordInput = "" m.greeterPasswordError = "" return m, nil case "enter": if m.greeterPasswordInput == "" { return m, nil } return m, m.validateGreeterPassword(m.greeterPasswordInput) case "backspace": if len(m.greeterPasswordInput) > 0 { m.greeterPasswordInput = m.greeterPasswordInput[:len(m.greeterPasswordInput)-1] } default: if len(msg.String()) == 1 && msg.String()[0] >= 32 && msg.String()[0] <= 126 { m.greeterPasswordInput += msg.String() } } return m, nil } func (m Model) updateGreeterInstalling(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "ctrl+c", "q": return m, tea.Quit case "esc": if m.greeterProgress.complete { m.state = StateMainMenu m.greeterProgress = greeterProgressMsg{} m.greeterLogs = []string{} } } return m, nil } func (m Model) performGreeterInstall() tea.Cmd { progressChan := m.greeterInstallChan sudoPassword := m.greeterSudoPassword compositor := m.greeterChosenCompositor return func() tea.Msg { go func() { logFunc := func(msg string) { progressChan <- greeterProgressMsg{step: msg, logOutput: msg} } progressChan <- greeterProgressMsg{step: "Checking greetd installation..."} if err := performGreeterInstallSteps(progressChan, logFunc, sudoPassword, compositor); err != nil { progressChan <- greeterProgressMsg{step: "Installation failed", complete: true, err: err} return } progressChan <- greeterProgressMsg{step: "Installation complete", complete: true} }() return nil } } func (m Model) validateGreeterPassword(password string) tea.Cmd { return func() tea.Msg { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cmd := exec.CommandContext(ctx, "sudo", "-S", "-v") stdin, err := cmd.StdinPipe() if err != nil { return greeterPasswordValidMsg{password: "", valid: false} } go func() { defer stdin.Close() fmt.Fprintf(stdin, "%s\n", password) }() output, err := cmd.CombinedOutput() outputStr := string(output) if err != nil { if strings.Contains(outputStr, "Sorry, try again") || strings.Contains(outputStr, "incorrect password") || strings.Contains(outputStr, "authentication failure") { return greeterPasswordValidMsg{password: "", valid: false} } return greeterPasswordValidMsg{password: "", valid: false} } return greeterPasswordValidMsg{password: password, valid: true} } } func performGreeterInstallSteps(progressChan chan greeterProgressMsg, logFunc func(string), sudoPassword string, compositor string) error { if err := greeter.EnsureGreetdInstalled(logFunc, sudoPassword); err != nil { return err } progressChan <- greeterProgressMsg{step: "Detecting DMS installation..."} dmsPath, err := greeter.DetectDMSPath() if err != nil { return err } logFunc(fmt.Sprintf("✓ Found DMS at: %s", dmsPath)) logFunc(fmt.Sprintf("✓ Selected compositor: %s", compositor)) progressChan <- greeterProgressMsg{step: "Copying greeter files..."} if err := greeter.CopyGreeterFiles(dmsPath, compositor, logFunc, sudoPassword); err != nil { return err } progressChan <- greeterProgressMsg{step: "Configuring greetd..."} if err := greeter.ConfigureGreetd(dmsPath, compositor, logFunc, sudoPassword); err != nil { return err } progressChan <- greeterProgressMsg{step: "Synchronizing DMS configurations..."} if err := greeter.SyncDMSConfigs(dmsPath, logFunc, sudoPassword); err != nil { return err } return nil }