1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-30 09:32:05 -04:00

system update: general fixes to flatpak parsing

This commit is contained in:
bbedward
2026-04-29 16:14:19 -04:00
parent f76724f7cd
commit 86096db26b
11 changed files with 159 additions and 25 deletions

View File

@@ -45,7 +45,11 @@ func (aptBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(
OnLine: onLine, OnLine: onLine,
}) })
} }
argv := []string{"pkexec", "env", "DEBIAN_FRONTEND=noninteractive", "LC_ALL=C", bin, "upgrade", "-y"} names := pickTargetNames(opts.Targets, "apt", true)
if len(names) == 0 {
return nil
}
argv := append([]string{"pkexec", "env", "DEBIAN_FRONTEND=noninteractive", "LC_ALL=C", bin, "install", "-y", "--only-upgrade"}, names...)
return Run(ctx, argv, RunOptions{OnLine: onLine}) return Run(ctx, argv, RunOptions{OnLine: onLine})
} }

View File

@@ -45,7 +45,12 @@ func (b dnfBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine fun
if opts.DryRun { if opts.DryRun {
return Run(ctx, []string{b.bin, "upgrade", "--assumeno"}, RunOptions{OnLine: onLine}) return Run(ctx, []string{b.bin, "upgrade", "--assumeno"}, RunOptions{OnLine: onLine})
} }
return Run(ctx, []string{"pkexec", b.bin, "upgrade", "-y"}, RunOptions{OnLine: onLine}) names := pickTargetNames(opts.Targets, b.bin, true)
if len(names) == 0 {
return nil
}
argv := append([]string{"pkexec", b.bin, "upgrade", "-y"}, names...)
return Run(ctx, argv, RunOptions{OnLine: onLine})
} }
func dnfListUpgrades(ctx context.Context, bin string) (string, error) { func dnfListUpgrades(ctx context.Context, bin string) (string, error) {
@@ -88,14 +93,14 @@ func parseDnfList(text, backendID string, installed map[string]string) []Package
} }
nameArch := fields[0] nameArch := fields[0]
version := fields[1] version := fields[1]
switch nameArch { dot := strings.LastIndex(nameArch, ".")
case "Available", "Upgrades": if dot <= 0 {
continue continue
} }
name := nameArch if !looksLikeRpmVersion(version) {
if dot := strings.LastIndex(nameArch, "."); dot > 0 { continue
name = nameArch[:dot]
} }
name := nameArch[:dot]
pkgs = append(pkgs, Package{ pkgs = append(pkgs, Package{
Name: nameArch, Name: nameArch,
Repo: RepoSystem, Repo: RepoSystem,
@@ -106,3 +111,15 @@ func parseDnfList(text, backendID string, installed map[string]string) []Package
} }
return pkgs return pkgs
} }
func looksLikeRpmVersion(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r >= '0' && r <= '9' {
return true
}
}
return false
}

View File

@@ -56,12 +56,15 @@ bash.x86_64 5.2.40-1.fc41 updates`,
want: nil, want: nil,
}, },
{ {
name: "package without arch suffix", name: "skips dnf5 banner / column header lines",
input: "noarchpkg 1.2.3 updates", input: `Updates available
Last metadata expiration check: 0:01:23 ago on Tue Apr 29 14:00:00 2026.
Package Version Repository Size
bash.x86_64 5.2.40-1.fc41 updates`,
backendID: "dnf", backendID: "dnf",
installed: map[string]string{"noarchpkg": "1.2.0"}, installed: nil,
want: []Package{ want: []Package{
{Name: "noarchpkg", Repo: RepoSystem, Backend: "dnf", FromVersion: "1.2.0", ToVersion: "1.2.3"}, {Name: "bash.x86_64", Repo: RepoSystem, Backend: "dnf", FromVersion: "", ToVersion: "5.2.40-1.fc41"},
}, },
}, },
} }

View File

@@ -70,13 +70,32 @@ type flatpakInstalledEntry struct {
} }
func (flatpakBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error { func (flatpakBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
argv := []string{"flatpak", "update", "-y", "--noninteractive"}
if opts.DryRun { if opts.DryRun {
argv = []string{"flatpak", "update", "--no-deploy", "-y"} return Run(ctx, []string{"flatpak", "update", "--no-deploy", "-y"}, RunOptions{OnLine: onLine})
} }
refs := flatpakTargetRefs(opts.Targets)
if len(refs) == 0 {
return nil
}
argv := append([]string{"flatpak", "update", "-y", "--noninteractive"}, refs...)
return Run(ctx, argv, RunOptions{OnLine: onLine}) return Run(ctx, argv, RunOptions{OnLine: onLine})
} }
func flatpakTargetRefs(targets []Package) []string {
out := make([]string, 0, len(targets))
for _, p := range targets {
if p.Backend != "flatpak" {
continue
}
ref := p.Ref
if ref == "" {
ref = p.Name
}
out = append(out, ref)
}
return out
}
func parseFlatpakUpdates(text string, installed map[string]flatpakInstalledEntry) []Package { func parseFlatpakUpdates(text string, installed map[string]flatpakInstalledEntry) []Package {
if text == "" { if text == "" {
return nil return nil
@@ -111,14 +130,25 @@ func parseFlatpakUpdates(text string, installed map[string]flatpakInstalledEntry
key = appID + "//" + branch key = appID + "//" + branch
} }
inst := installed[key] inst := installed[key]
if inst.commit != "" && commit != "" && strings.HasPrefix(commit, inst.commit) {
continue
}
from, to := flatpakVersionPair(inst.version, inst.commit, version, commit) from, to := flatpakVersionPair(inst.version, inst.commit, version, commit)
ref := appID
if branch != "" {
ref = appID + "//" + branch
}
pkgs = append(pkgs, Package{ pkgs = append(pkgs, Package{
Name: display, Name: display,
Repo: RepoFlatpak, Repo: RepoFlatpak,
Backend: "flatpak", Backend: "flatpak",
FromVersion: from, FromVersion: from,
ToVersion: to, ToVersion: to,
Ref: ref,
}) })
} }
return pkgs return pkgs

View File

@@ -31,6 +31,7 @@ func TestParseFlatpakUpdates(t *testing.T) {
Backend: "flatpak", Backend: "flatpak",
FromVersion: "8b16fa1a", FromVersion: "8b16fa1a",
ToVersion: "43a1e5d2", ToVersion: "43a1e5d2",
Ref: "com.discordapp.Discord//stable",
}, },
}, },
}, },
@@ -47,6 +48,7 @@ func TestParseFlatpakUpdates(t *testing.T) {
Backend: "flatpak", Backend: "flatpak",
FromVersion: "1.4.2", FromVersion: "1.4.2",
ToVersion: "1.5.0", ToVersion: "1.5.0",
Ref: "com.example.App//stable",
}, },
}, },
}, },
@@ -61,6 +63,7 @@ func TestParseFlatpakUpdates(t *testing.T) {
Backend: "flatpak", Backend: "flatpak",
FromVersion: "", FromVersion: "",
ToVersion: "badcd4af", ToVersion: "badcd4af",
Ref: "org.gnome.Platform//49",
}, },
}, },
}, },
@@ -74,6 +77,7 @@ func TestParseFlatpakUpdates(t *testing.T) {
Backend: "flatpak", Backend: "flatpak",
FromVersion: "", FromVersion: "",
ToVersion: "2.0", ToVersion: "2.0",
Ref: "com.example.NoName//stable",
}, },
}, },
}, },
@@ -87,9 +91,18 @@ func TestParseFlatpakUpdates(t *testing.T) {
Backend: "flatpak", Backend: "flatpak",
FromVersion: "", FromVersion: "",
ToVersion: "1.0", ToVersion: "1.0",
Ref: "org.real.App//stable",
}, },
}, },
}, },
{
name: "skips phantom updates where remote commit matches installed",
input: "com.phantom.App\t\tstable\tabc12345deadbeef\tPhantom",
installed: map[string]flatpakInstalledEntry{
"com.phantom.App//stable": {commit: "abc12345"},
},
want: nil,
},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -43,17 +43,24 @@ func (b pacmanBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine
if opts.DryRun { if opts.DryRun {
return Run(ctx, []string{"pacman", "-Sup"}, RunOptions{OnLine: onLine}) return Run(ctx, []string{"pacman", "-Sup"}, RunOptions{OnLine: onLine})
} }
return Run(ctx, []string{"pkexec", "pacman", "-Syu", "--noconfirm"}, RunOptions{OnLine: onLine}) names := pickTargetNames(opts.Targets, b.ID(), opts.IncludeAUR)
if len(names) == 0 {
return nil
}
argv := append([]string{"pkexec", "pacman", "-Sy", "--noconfirm", "--needed"}, names...)
return Run(ctx, argv, RunOptions{OnLine: onLine})
} }
type archHelperBackend struct { type archHelperBackend struct {
id string id string
} }
func (b archHelperBackend) ID() string { return b.id } func (b archHelperBackend) ID() string { return b.id }
func (b archHelperBackend) Repo() RepoKind { return RepoSystem } func (b archHelperBackend) Repo() RepoKind { return RepoSystem }
func (b archHelperBackend) NeedsAuth() bool { return true } func (b archHelperBackend) NeedsAuth() bool { return true }
func (b archHelperBackend) RunsInTerminal() bool { return true } func (b archHelperBackend) RunsInTerminal() bool {
return os.Getenv("DMS_FORCE_PKEXEC") != "1"
}
func (b archHelperBackend) IsAvailable(_ context.Context) bool { return commandExists(b.id) } func (b archHelperBackend) IsAvailable(_ context.Context) bool { return commandExists(b.id) }
func (b archHelperBackend) DisplayName() string { func (b archHelperBackend) DisplayName() string {
@@ -86,18 +93,37 @@ func (b archHelperBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onL
if opts.DryRun { if opts.DryRun {
return Run(ctx, []string{b.id, "-Sup"}, RunOptions{OnLine: onLine}) return Run(ctx, []string{b.id, "-Sup"}, RunOptions{OnLine: onLine})
} }
names := pickTargetNames(opts.Targets, b.id, opts.IncludeAUR)
if len(names) == 0 {
return nil
}
if os.Getenv("DMS_FORCE_PKEXEC") == "1" {
argv := append([]string{"pkexec", b.id, "-Sy", "--noconfirm", "--needed"}, names...)
return Run(ctx, argv, RunOptions{OnLine: onLine})
}
term := findTerminal(opts.Terminal) term := findTerminal(opts.Terminal)
if term == "" { if term == "" {
return fmt.Errorf("no terminal found (pick one in DMS settings, set $TERMINAL, or install kitty/ghostty/foot/alacritty)") return fmt.Errorf("no terminal found (pick one in DMS settings, set $TERMINAL, or install kitty/ghostty/foot/alacritty)")
} }
cmd := fmt.Sprintf("%s -Syu", b.id) cmd := fmt.Sprintf("%s -Sy --noconfirm --needed %s", b.id, strings.Join(names, " "))
if !opts.IncludeAUR {
cmd += " --repo"
}
title := fmt.Sprintf("DMS — System Update (%s)", b.id) title := fmt.Sprintf("DMS — System Update (%s)", b.id)
return Run(ctx, wrapInTerminal(term, title, cmd), RunOptions{OnLine: onLine}) return Run(ctx, wrapInTerminal(term, title, cmd), RunOptions{OnLine: onLine})
} }
func pickTargetNames(targets []Package, backendID string, includeAUR bool) []string {
out := make([]string, 0, len(targets))
for _, p := range targets {
if p.Backend != backendID {
continue
}
if !includeAUR && p.Repo == RepoAUR {
continue
}
out = append(out, p.Name)
}
return out
}
func pacmanRepoUpdates(ctx context.Context) (string, error) { func pacmanRepoUpdates(ctx context.Context) (string, error) {
if commandExists("checkupdates") { if commandExists("checkupdates") {
return capturePermissive(ctx, "checkupdates") return capturePermissive(ctx, "checkupdates")

View File

@@ -74,5 +74,10 @@ func (zypperBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine fu
if opts.DryRun { if opts.DryRun {
return Run(ctx, []string{"zypper", "--non-interactive", "--dry-run", "update"}, RunOptions{OnLine: onLine}) return Run(ctx, []string{"zypper", "--non-interactive", "--dry-run", "update"}, RunOptions{OnLine: onLine})
} }
return Run(ctx, []string{"pkexec", "zypper", "--non-interactive", "update"}, RunOptions{OnLine: onLine}) names := pickTargetNames(opts.Targets, "zypper", true)
if len(names) == 0 {
return nil
}
argv := append([]string{"pkexec", "zypper", "--non-interactive", "update"}, names...)
return Run(ctx, argv, RunOptions{OnLine: onLine})
} }

View File

@@ -8,6 +8,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"sync" "sync"
"syscall"
) )
type RunOptions struct { type RunOptions struct {
@@ -24,6 +25,13 @@ func Run(ctx context.Context, argv []string, opts RunOptions) error {
if len(opts.Env) > 0 { if len(opts.Env) > 0 {
cmd.Env = append(cmd.Environ(), opts.Env...) cmd.Env = append(cmd.Environ(), opts.Env...)
} }
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Cancel = func() error {
if cmd.Process == nil {
return nil
}
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()
if err != nil { if err != nil {

View File

@@ -309,6 +309,12 @@ func (m *Manager) runUpgrade(ctx context.Context, opts UpgradeOptions) {
return return
} }
if len(opts.Targets) == 0 {
m.mu.RLock()
opts.Targets = append([]Package(nil), m.state.Packages...)
m.mu.RUnlock()
}
opID := fmt.Sprintf("op-%d", time.Now().UnixNano()) opID := fmt.Sprintf("op-%d", time.Now().UnixNano())
m.mu.Lock() m.mu.Lock()
m.state.Phase = PhaseUpgrading m.state.Phase = PhaseUpgrading

View File

@@ -38,6 +38,7 @@ type Package struct {
ToVersion string `json:"toVersion,omitempty"` ToVersion string `json:"toVersion,omitempty"`
SizeBytes int64 `json:"sizeBytes,omitempty"` SizeBytes int64 `json:"sizeBytes,omitempty"`
ChangelogURL string `json:"changelogUrl,omitempty"` ChangelogURL string `json:"changelogUrl,omitempty"`
Ref string `json:"-"`
} }
type BackendInfo struct { type BackendInfo struct {
@@ -77,6 +78,7 @@ type UpgradeOptions struct {
DryRun bool DryRun bool
CustomCommand string CustomCommand string
Terminal string Terminal string
Targets []Package
} }
type RefreshOptions struct { type RefreshOptions struct {

View File

@@ -1,4 +1,5 @@
import QtQuick import QtQuick
import Quickshell.Wayland
import qs.Common import qs.Common
import qs.Services import qs.Services
import qs.Widgets import qs.Widgets
@@ -17,6 +18,21 @@ DankPopout {
property bool _reopenAfterUpgrade: false property bool _reopenAfterUpgrade: false
readonly property bool polkitModalOpen: PopoutService.polkitAuthModal?.visible ?? false
readonly property bool anyModalOpen: polkitModalOpen
backgroundInteractive: !anyModalOpen
customKeyboardFocus: {
if (!shouldBeVisible)
return WlrKeyboardFocus.None;
if (anyModalOpen)
return WlrKeyboardFocus.None;
if (CompositorService.useHyprlandFocusGrab)
return WlrKeyboardFocus.OnDemand;
return WlrKeyboardFocus.Exclusive;
}
Connections { Connections {
target: SystemUpdateService target: SystemUpdateService
function onIsUpgradingChanged() { function onIsUpgradingChanged() {
@@ -38,7 +54,11 @@ DankPopout {
screen: triggerScreen screen: triggerScreen
shouldBeVisible: false shouldBeVisible: false
onBackgroundClicked: close() onBackgroundClicked: {
if (anyModalOpen)
return;
close();
}
onShouldBeVisibleChanged: { onShouldBeVisibleChanged: {
if (!shouldBeVisible) { if (!shouldBeVisible) {
@@ -290,7 +310,7 @@ DankPopout {
} }
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: SystemUpdateService.hasError ? Theme.errorText : Theme.surfaceText color: SystemUpdateService.hasError ? Theme.error : Theme.surfaceText
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
} }