Fortschritt, .NET-Prüfung, Updates, Fenster merken, Box-Funktionen

- Große Kopien laufen im Hintergrund mit Fortschritt und Abbrechen;
  abgebrochene Kopien werden aufgeräumt, das Original bleibt unverändert
- Sichern/Wiederherstellen mit Fortschritt, keine halben ZIP-Dateien
- Installer auf .NET Framework 4.8: erkennt fehlende .NET 9 Desktop Runtime
  und installiert sie auf Wunsch (Microsoft-Signatur wird geprüft)
- Update-Hinweis über Gitea-Releases, Update mit automatischem Neustart
- Fenstergröße und -position werden gemerkt
- Desktop-Boxen: umbenennen, löschen, verschieben, herausziehen
- Fenster und Boxen kehren auf sichtbare Bildschirme zurück
- README ergänzt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 20:46:48 +02:00
co-authored by Claude Opus 5.5
parent 77bb3e6b9d
commit 1ae6e1abc2
19 changed files with 1098 additions and 95 deletions
+26 -6
View File
@@ -23,6 +23,9 @@ public static class Installer
}
}
/// <summary>Pfad dieses Setups (auch als Deinstallationsprogramm).</summary>
public static string SelfPath => System.Reflection.Assembly.GetExecutingAssembly().Location;
public static string DefaultDir =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Programs", AppName);
@@ -66,8 +69,19 @@ public static class Installer
// ---------- Installieren ----------
public static void Install(string dir, bool desktopLink, bool autostart, IProgress<(double, string)> progress)
public static void Install(string dir, bool desktopLink, bool autostart, IProgress<(double, string)> progress,
bool installRuntime = false)
{
if (!Runtime.Is64BitWindows)
throw new InvalidOperationException("DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11).");
if (installRuntime && !Runtime.IsInstalled)
{
// 0–60 % für die Laufzeit, Rest für DeskCore
Runtime.Install((v, text) => progress.Report((v * 0.6, text)));
var inner = progress;
progress = new Scaled(inner, 0.6, 0.4);
}
progress.Report((0.05, "DeskCore wird beendet …"));
CloseRunningApp();
RemoveLegacyInstall();
@@ -79,7 +93,7 @@ public static class Installer
WriteResource("payload.LICENSE.txt", Path.Combine(dir, "LICENSE.txt"));
// Setup selbst als Deinstallationsprogramm ablegen
var self = Environment.ProcessPath!;
var self = SelfPath;
var uninstaller = Path.Combine(dir, UninstallerName);
if (!string.Equals(Path.GetFullPath(self), Path.GetFullPath(uninstaller), StringComparison.OrdinalIgnoreCase))
{
@@ -149,7 +163,7 @@ public static class Installer
{
if (p.MainModule?.FileName is { } f && Storage_IsUnder(f, dir))
{
p.Kill(entireProcessTree: true);
p.Kill();
p.WaitForExit(10000);
}
}
@@ -160,7 +174,7 @@ public static class Installer
DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), LegacyName + ".lnk"), exe);
using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true))
{
if (run?.GetValue(LegacyName) is string cmd && cmd.Contains(exe, StringComparison.OrdinalIgnoreCase))
if (run?.GetValue(LegacyName) is string cmd && cmd.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0)
run.DeleteValue(LegacyName, throwOnMissingValue: false);
}
@@ -221,7 +235,7 @@ public static class Installer
// Das laufende Deinstallationsprogramm kann sich nicht selbst löschen → kurz nach dem Beenden per cmd.
// rmdir ohne /s entfernt den Ordner nur, wenn er danach leer ist.
var self = Environment.ProcessPath!;
var self = SelfPath;
if (Storage_IsUnder(self, dir))
{
Process.Start(new ProcessStartInfo("cmd.exe",
@@ -240,6 +254,12 @@ public static class Installer
// ---------- Helfer ----------
/// <summary>Rechnet Fortschritt 0–1 in einen Teilbereich um.</summary>
private sealed class Scaled(IProgress<(double, string)> inner, double offset, double factor) : IProgress<(double, string)>
{
public void Report((double, string) value) => inner.Report((offset + value.Item1 * factor, value.Item2));
}
/// <summary>Laufendes DeskCore beenden (das X versteckt es nur im Infobereich).</summary>
private static void CloseRunningApp()
{
@@ -247,7 +267,7 @@ public static class Installer
{
try
{
p.Kill(entireProcessTree: true);
p.Kill();
p.WaitForExit(10000);
}
catch { }