diff --git a/core/.pre-commit-config.yaml b/core/.pre-commit-config.yaml index f1f274ec..2a8df7bc 100644 --- a/core/.pre-commit-config.yaml +++ b/core/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/golangci/golangci-lint - rev: v2.9.0 + rev: v2.11.3 hooks: - id: golangci-lint-fmt require_serial: true diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index 8e744fa9..7fc05470 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -1079,14 +1079,14 @@ func formatResultsPlain(results []checkResult) string { if currentCategory != -1 { sb.WriteString("\n") } - sb.WriteString(fmt.Sprintf("**%s**\n", r.category.String())) + fmt.Fprintf(&sb, "**%s**\n", r.category.String()) currentCategory = r.category } - sb.WriteString(fmt.Sprintf("- [%s] %s: %s\n", r.status, r.name, r.message)) + fmt.Fprintf(&sb, "- [%s] %s: %s\n", r.status, r.name, r.message) if doctorVerbose && r.details != "" { - sb.WriteString(fmt.Sprintf(" - %s\n", r.details)) + fmt.Fprintf(&sb, " - %s\n", r.details) } } @@ -1096,8 +1096,8 @@ func formatResultsPlain(results []checkResult) string { } sb.WriteString("\n---\n") - sb.WriteString(fmt.Sprintf("**Summary:** %d error(s), %d warning(s), %d ok\n", - ds.ErrorCount(), ds.WarningCount(), ds.OKCount())) + fmt.Fprintf(&sb, "**Summary:** %d error(s), %d warning(s), %d ok\n", + ds.ErrorCount(), ds.WarningCount(), ds.OKCount()) return sb.String() } diff --git a/core/go.mod b/core/go.mod index 0db881de..16f925e0 100644 --- a/core/go.mod +++ b/core/go.mod @@ -1,6 +1,6 @@ module github.com/AvengeMedia/DankMaterialShell/core -go 1.25.0 +go 1.26.0 require ( github.com/Wifx/gonetworkmanager/v2 v2.2.0 diff --git a/core/internal/server/brightness/udev.go b/core/internal/server/brightness/udev.go index 7e0fcd05..b7f02a83 100644 --- a/core/internal/server/brightness/udev.go +++ b/core/internal/server/brightness/udev.go @@ -6,12 +6,20 @@ import ( "strconv" "strings" "sync" + "syscall" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/pilebones/go-udev/netlink" ) +const ( + udevRecvBufSize = 8 * 1024 * 1024 + udevMaxRetries = 5 + udevBaseDelay = 2 * time.Second + udevMaxDelay = 60 * time.Second +) + type UdevMonitor struct { stop chan struct{} rescanMutex sync.Mutex @@ -29,13 +37,6 @@ func NewUdevMonitor(manager *Manager) *UdevMonitor { } func (m *UdevMonitor) run(manager *Manager) { - conn := &netlink.UEventConn{} - if err := conn.Connect(netlink.UdevEvent); err != nil { - log.Errorf("Failed to connect to udev netlink: %v", err) - return - } - defer conn.Close() - matcher := &netlink.RuleDefinitions{ Rules: []netlink.RuleDefinition{ {Env: map[string]string{"SUBSYSTEM": "backlight"}}, @@ -48,6 +49,46 @@ func (m *UdevMonitor) run(manager *Manager) { return } + failures := 0 + for { + if err := m.monitorLoop(manager, matcher); err != nil { + log.Errorf("Udev monitor error: %v", err) + } + + select { + case <-m.stop: + return + default: + } + + failures++ + if failures > udevMaxRetries { + log.Errorf("Udev monitor exceeded %d retries, giving up", udevMaxRetries) + return + } + + delay := min(udevBaseDelay*time.Duration(1<<(failures-1)), udevMaxDelay) + log.Infof("Udev monitor reconnecting in %v (attempt %d/%d)", delay, failures, udevMaxRetries) + + select { + case <-m.stop: + return + case <-time.After(delay): + } + } +} + +func (m *UdevMonitor) monitorLoop(manager *Manager, matcher *netlink.RuleDefinitions) error { + conn := &netlink.UEventConn{} + if err := conn.Connect(netlink.UdevEvent); err != nil { + return err + } + defer conn.Close() + + if err := syscall.SetsockoptInt(conn.Fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, udevRecvBufSize); err != nil { + log.Warnf("Failed to set udev socket receive buffer: %v", err) + } + events := make(chan netlink.UEvent) errs := make(chan error) conn.Monitor(events, errs, matcher) @@ -57,10 +98,9 @@ func (m *UdevMonitor) run(manager *Manager) { for { select { case <-m.stop: - return + return nil case err := <-errs: - log.Errorf("Udev monitor error: %v", err) - return + return err case event := <-events: m.handleEvent(manager, event) } diff --git a/core/internal/tui/views_config.go b/core/internal/tui/views_config.go index 3bff4448..14daff37 100644 --- a/core/internal/tui/views_config.go +++ b/core/internal/tui/views_config.go @@ -40,7 +40,7 @@ func (m Model) viewDeployingConfigs() string { spinner := m.spinner.View() status := m.styles.Normal.Render("Setting up configuration files...") - b.WriteString(fmt.Sprintf("%s %s", spinner, status)) + fmt.Fprintf(&b, "%s %s", spinner, status) b.WriteString("\n\n") // Show progress information diff --git a/core/internal/tui/views_dependencies.go b/core/internal/tui/views_dependencies.go index f55b6a10..13f03357 100644 --- a/core/internal/tui/views_dependencies.go +++ b/core/internal/tui/views_dependencies.go @@ -23,7 +23,7 @@ func (m Model) viewDetectingDeps() string { spinner := m.spinner.View() status := m.styles.Normal.Render("Scanning system for existing packages and configurations...") - b.WriteString(fmt.Sprintf("%s %s", spinner, status)) + fmt.Fprintf(&b, "%s %s", spinner, status) return b.String() } diff --git a/core/internal/tui/views_install.go b/core/internal/tui/views_install.go index c61c3373..c4181cae 100644 --- a/core/internal/tui/views_install.go +++ b/core/internal/tui/views_install.go @@ -52,7 +52,7 @@ func (m Model) viewInstallingPackages() string { if !m.packageProgress.isComplete { spinner := m.spinner.View() status := m.styles.Normal.Render(m.packageProgress.step) - b.WriteString(fmt.Sprintf("%s %s", spinner, status)) + fmt.Fprintf(&b, "%s %s", spinner, status) b.WriteString("\n\n") // Show progress bar @@ -387,7 +387,7 @@ func (m Model) viewDebugLogs() string { for i := startIdx; i < len(allLogs); i++ { if allLogs[i] != "" { - b.WriteString(fmt.Sprintf("%d: %s\n", i, allLogs[i])) + fmt.Fprintf(&b, "%d: %s\n", i, allLogs[i]) } } diff --git a/core/internal/tui/views_password.go b/core/internal/tui/views_password.go index 2f386ae8..deec0610 100644 --- a/core/internal/tui/views_password.go +++ b/core/internal/tui/views_password.go @@ -75,7 +75,7 @@ func (m Model) viewFingerprintAuth() string { spinner := m.spinner.View() status := m.styles.Normal.Render("Waiting for fingerprint...") - b.WriteString(fmt.Sprintf("%s %s", spinner, status)) + fmt.Fprintf(&b, "%s %s", spinner, status) } return b.String() diff --git a/core/internal/tui/views_welcome.go b/core/internal/tui/views_welcome.go index 4fe4b9cd..c5ae862c 100644 --- a/core/internal/tui/views_welcome.go +++ b/core/internal/tui/views_welcome.go @@ -132,9 +132,9 @@ func (m Model) viewWelcome() string { contentStyle = contentStyle.Bold(true) } - b.WriteString(fmt.Sprintf(" %s %s\n", + fmt.Fprintf(&b, " %s %s\n", prefixStyle.Render(prefix), - contentStyle.Render(content))) + contentStyle.Render(content)) } b.WriteString("\n") @@ -158,7 +158,7 @@ func (m Model) viewWelcome() string { } else if m.isLoading { spinner := m.spinner.View() loading := m.styles.Normal.Render("Detecting system...") - b.WriteString(fmt.Sprintf("%s %s\n\n", spinner, loading)) + fmt.Fprintf(&b, "%s %s\n\n", spinner, loading) } // Footer with better visual separation diff --git a/flake.nix b/flake.nix index 4fd77119..63a89bca 100644 --- a/flake.nix +++ b/flake.nix @@ -187,7 +187,7 @@ buildInputs = with pkgs; [ - go_1_25 + go_1_26 go-mockery_2 gopls delve