mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2025-12-06 05:25:41 -05:00
393 lines
9.6 KiB
Go
393 lines
9.6 KiB
Go
//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
|
|
}
|