74 Commits
Author SHA1 Message Date
M_Viper 0f48097707 Upload via GUI (186 Dateien) 2026-10-04 14:48:59 +00:00
M_Viper 0d7ff9e9c3 Upload via GUI (186 Dateien) 2026-10-04 14:48:57 +00:00
M_Viper 70c3b8288b Upload via GUI (186 Dateien) 2026-10-04 14:48:55 +00:00
M_Viper 8efd5bc497 Upload via GUI (186 Dateien) 2026-10-04 14:48:53 +00:00
M_Viper c0b552ef39 Delete Setup/obj/project.nuget.cache via Git Manager GUI 2026-10-04 14:48:30 +00:00
M_Viper 58461c1211 Delete Setup/obj/Release/net9.0-windows/win-x64/refint/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:29 +00:00
M_Viper 5c4fe0a029 Delete Setup/obj/Release/net9.0-windows/win-x64/apphost.exe via Git Manager GUI 2026-10-04 14:48:29 +00:00
M_Viper 51bfaa73b0 Delete Setup/obj/Release/net9.0-windows/win-x64/MainWindow.g.cs via Git Manager GUI 2026-10-04 14:48:28 +00:00
M_Viper fcc0bedfab Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.genruntimeconfig.cache via Git Manager GUI 2026-10-04 14:48:28 +00:00
M_Viper c78a8cc6f1 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.genbundle.cache via Git Manager GUI 2026-10-04 14:48:27 +00:00
M_Viper 624d9c7a79 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.csproj.CoreCompileInputs.cache via Git Manager GUI 2026-10-04 14:48:27 +00:00
M_Viper 18128817d4 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig via Git Manager GUI 2026-10-04 14:48:26 +00:00
M_Viper 5b6f6881ee Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.AssemblyInfoInputs.cache via Git Manager GUI 2026-10-04 14:48:26 +00:00
M_Viper 5058ceb6e5 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup_MarkupCompile.cache via Git Manager GUI 2026-10-04 14:48:26 +00:00
M_Viper c22bea3b39 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:25 +00:00
M_Viper 56352ede19 Delete Setup/obj/Release/net9.0-windows/win-x64/Controls.baml via Git Manager GUI 2026-10-04 14:48:25 +00:00
M_Viper e62822a544 Delete Setup/obj/Release/net9.0-windows/win-x64/Colors.Dark.baml via Git Manager GUI 2026-10-04 14:48:24 +00:00
M_Viper 100caee6ed Delete Setup/obj/Release/net9.0-windows/win-x64/App.baml via Git Manager GUI 2026-10-04 14:48:24 +00:00
M_Viper 3ed91b8406 Delete Setup/obj/Release/net48/MainWindow.g.cs via Git Manager GUI 2026-10-04 14:48:23 +00:00
M_Viper d8c18425fe Delete Setup/obj/Release/net48/DeskCoreSetup.csproj.FileListAbsolute.txt via Git Manager GUI 2026-10-04 14:48:23 +00:00
M_Viper b7c38cd380 Delete Setup/obj/Release/net48/DeskCoreSetup.assets.cache via Git Manager GUI 2026-10-04 14:48:23 +00:00
M_Viper eb8c33e990 Delete Setup/obj/Release/net48/DeskCoreSetup.GlobalUsings.g.cs via Git Manager GUI 2026-10-04 14:48:22 +00:00
M_Viper 8638b115e8 Delete Setup/obj/Release/net48/DeskCoreSetup.AssemblyInfoInputs.cache via Git Manager GUI 2026-10-04 14:48:22 +00:00
M_Viper 33cfe843f1 Delete Setup/obj/Release/net48/DeskCore-Setup_MarkupCompile.cache via Git Manager GUI 2026-10-04 14:48:21 +00:00
M_Viper f4ff96a0e4 Delete Setup/obj/Release/net48/DeskCore-Setup.exe.withSupportedRuntime.config via Git Manager GUI 2026-10-04 14:48:21 +00:00
M_Viper 483af97e6b Delete Setup/obj/Release/net48/Controls.baml via Git Manager GUI 2026-10-04 14:48:20 +00:00
M_Viper 6d610ca447 Delete Setup/obj/Release/net48/Colors.Dark.baml via Git Manager GUI 2026-10-04 14:48:20 +00:00
M_Viper d87c360d2d Delete Setup/obj/Release/net48/App.baml via Git Manager GUI 2026-10-04 14:48:19 +00:00
M_Viper 33eaacc293 Delete Setup/obj/DeskCoreSetup.csproj.nuget.g.targets via Git Manager GUI 2026-10-04 14:48:19 +00:00
M_Viper 28b031ba58 Delete Setup/obj/DeskCoreSetup.csproj.nuget.dgspec.json via Git Manager GUI 2026-10-04 14:48:18 +00:00
M_Viper 99a1a45c7a Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.exe via Git Manager GUI 2026-10-04 14:48:18 +00:00
M_Viper 6fcacbb577 Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.deps.json via Git Manager GUI 2026-10-04 14:48:17 +00:00
M_Viper eec5f2a4ca Delete Setup/MainWindow.xaml.cs via Git Manager GUI 2026-10-04 14:48:17 +00:00
M_Viper 20b0812c82 Delete Setup/Installer.cs via Git Manager GUI 2026-10-04 14:48:17 +00:00
M_Viper 0462d3fae2 Delete Setup/App.xaml.cs via Git Manager GUI 2026-10-04 14:48:16 +00:00
M_Viper 020c83180c Delete Setup/obj/project.assets.json via Git Manager GUI 2026-10-04 14:48:14 +00:00
M_Viper 6791a114b2 Delete Setup/obj/Release/net9.0-windows/win-x64/ref/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:14 +00:00
M_Viper 7183e39d21 Delete Setup/obj/Release/net9.0-windows/win-x64/PublishOutputs.02abb2eb9c.txt via Git Manager GUI 2026-10-04 14:48:13 +00:00
M_Viper d8f6a70c2c Delete Setup/obj/Release/net9.0-windows/win-x64/MainWindow.baml via Git Manager GUI 2026-10-04 14:48:13 +00:00
M_Viper ba0d990d69 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.genpublishdeps.cache via Git Manager GUI 2026-10-04 14:48:12 +00:00
M_Viper 9a5e954b73 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.csproj.FileListAbsolute.txt via Git Manager GUI 2026-10-04 14:48:12 +00:00
M_Viper 5cb7224a2d Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.assets.cache via Git Manager GUI 2026-10-04 14:48:12 +00:00
M_Viper 0318a3dc20 Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.GlobalUsings.g.cs via Git Manager GUI 2026-10-04 14:48:11 +00:00
M_Viper 6b713b08ad Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCoreSetup.AssemblyInfo.cs via Git Manager GUI 2026-10-04 14:48:11 +00:00
M_Viper 271701bfda Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup.g.resources via Git Manager GUI 2026-10-04 14:48:10 +00:00
M_Viper 85f3f597bb Delete Setup/obj/Release/net9.0-windows/win-x64/DeskCore-Setup.deps.json via Git Manager GUI 2026-10-04 14:48:09 +00:00
M_Viper 17dcfb16ea Delete Setup/obj/Release/net9.0-windows/win-x64/Colors.Light.baml via Git Manager GUI 2026-10-04 14:48:09 +00:00
M_Viper 29fe9816a3 Delete Setup/obj/Release/net9.0-windows/win-x64/App.g.cs via Git Manager GUI 2026-10-04 14:48:09 +00:00
M_Viper e170dcd90c Delete Setup/obj/Release/net9.0-windows/win-x64/.NETCoreApp,Version=v9.0.AssemblyAttributes.cs via Git Manager GUI 2026-10-04 14:48:08 +00:00
M_Viper 077eba609e Delete Setup/obj/Release/net48/MainWindow.baml via Git Manager GUI 2026-10-04 14:48:08 +00:00
M_Viper ff8b5b9484 Delete Setup/obj/Release/net48/DeskCoreSetup.csproj.CoreCompileInputs.cache via Git Manager GUI 2026-10-04 14:48:07 +00:00
M_Viper 1e8b538a71 Delete Setup/obj/Release/net48/DeskCoreSetup.csproj.AssemblyReference.cache via Git Manager GUI 2026-10-04 14:48:07 +00:00
M_Viper a1c2bc38ba Delete Setup/obj/Release/net48/DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig via Git Manager GUI 2026-10-04 14:48:06 +00:00
M_Viper 4c44b7769d Delete Setup/obj/Release/net48/DeskCoreSetup.AssemblyInfo.cs via Git Manager GUI 2026-10-04 14:48:06 +00:00
M_Viper 54aee17a69 Delete Setup/obj/Release/net48/DeskCore-Setup.g.resources via Git Manager GUI 2026-10-04 14:48:05 +00:00
M_Viper 55baffb4d8 Delete Setup/obj/Release/net48/DeskCore-Setup.exe via Git Manager GUI 2026-10-04 14:48:05 +00:00
M_Viper 2df0e95fd6 Delete Setup/obj/Release/net48/Colors.Light.baml via Git Manager GUI 2026-10-04 14:48:04 +00:00
M_Viper 84d835828c Delete Setup/obj/Release/net48/App.g.cs via Git Manager GUI 2026-10-04 14:48:04 +00:00
M_Viper a434d48cb0 Delete Setup/obj/Release/net48/.NETFramework,Version=v4.8.AssemblyAttributes.cs via Git Manager GUI 2026-10-04 14:48:03 +00:00
M_Viper 97e5b87ef3 Delete Setup/obj/DeskCoreSetup.csproj.nuget.g.props via Git Manager GUI 2026-10-04 14:48:03 +00:00
M_Viper fffc70d1fb Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.runtimeconfig.json via Git Manager GUI 2026-10-04 14:48:02 +00:00
M_Viper 1b0c3ee187 Delete Setup/bin/Release/net9.0-windows/win-x64/DeskCore-Setup.dll via Git Manager GUI 2026-10-04 14:48:02 +00:00
M_Viper 48ed7eef5c Delete Setup/Runtime.cs via Git Manager GUI 2026-10-04 14:48:01 +00:00
M_Viper ee92f6cb9f Delete Setup/MainWindow.xaml via Git Manager GUI 2026-10-04 14:48:01 +00:00
M_Viper efd6100ab9 Delete Setup/DeskCoreSetup.csproj via Git Manager GUI 2026-10-04 14:48:00 +00:00
M_Viper beee89114f Delete Setup/App.xaml via Git Manager GUI 2026-10-04 14:48:00 +00:00
M_Viper 3e2b10ce6c Upload via GUI (180 Dateien) 2026-10-03 19:53:51 +00:00
M_Viper 65c293a0e7 Upload via GUI (180 Dateien) 2026-10-03 19:53:49 +00:00
M_Viper 67373a378f Upload via GUI (180 Dateien) 2026-10-03 19:53:48 +00:00
M_Viper b21a03afad Upload via GUI (180 Dateien) 2026-10-03 19:53:47 +00:00
M_Viper 3739eb2c46 Upload via GUI (180 Dateien) 2026-10-03 19:52:00 +00:00
M_Viper b1ef0c4190 Upload via GUI (180 Dateien) 2026-10-03 19:51:59 +00:00
M_Viper 2cf6ebe0a5 Upload via GUI (180 Dateien) 2026-10-03 19:51:57 +00:00
M_Viper ddc30b0fe9 Upload via GUI (180 Dateien) 2026-10-03 19:51:56 +00:00
172 changed files with 17560 additions and 5734 deletions
+209 -128
View File
@@ -1,128 +1,209 @@
using System.IO;
using System.Windows;
using System.Windows.Interop;
namespace DeskCore;
public partial class App : Application
{
private const string MutexName = "DeskCore.Instanz";
private const string ShowEventName = "DeskCore.Anzeigen";
public static MainWindow? Win { get; private set; }
public static Tray? TrayIcon { get; private set; }
public static bool Quitting { get; private set; }
private static Hotkey? _hotkey;
private static Watcher? _watcher;
private Mutex? _mutex;
private EventWaitHandle? _showEvent;
protected override void OnStartup(StartupEventArgs e)
{
// Nur eine Instanz: eine zweite holt einfach das vorhandene Fenster nach vorne
_mutex = new Mutex(true, MutexName, out bool first);
if (!first)
{
try { EventWaitHandle.OpenExisting(ShowEventName).Set(); } catch { }
Shutdown();
return;
}
Migration.Run(); // alte "Desktop Sortierer"-Ordner übernehmen – vor allem anderen
_showEvent = new EventWaitHandle(false, EventResetMode.AutoReset, ShowEventName);
new Thread(() =>
{
while (_showEvent.WaitOne())
Dispatcher.BeginInvoke(() => Win?.ShowFromBackground());
}) { IsBackground = true, Name = "ShowListener" }.Start();
DispatcherUnhandledException += (_, args) =>
{
var log = Path.Combine(Storage.Root, "fehler.log");
try { File.AppendAllText(log, $"[{DateTime.Now}] {args.Exception}\n\n"); } catch { }
MessageBox.Show($"Unerwarteter Fehler:\n\n{args.Exception.Message}\n\nDetails: {log}", "DeskCore",
MessageBoxButton.OK, MessageBoxImage.Error);
args.Handled = true;
};
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnExplicitShutdown;
Storage.FlushHolding(); // Reste eines Absturzes in den Papierkorb
Theme.Init();
Theme.Changed += FenceManager.RefreshAll;
Win = new MainWindow();
MainWindow = Win;
var hwnd = new WindowInteropHelper(Win).EnsureHandle();
_hotkey = new Hotkey(hwnd);
_hotkey.Pressed += () => Win.ToggleFromHotkey();
ApplyHotkey();
TrayIcon = new Tray();
TrayIcon.Clicked += () => Win.ShowFromBackground();
TrayIcon.BalloonClicked += () => Win.ShowFromBackground();
TrayIcon.MenuRequested += () => Win.ShowTrayMenu();
ApplyTray();
_watcher = new Watcher(Dispatcher);
_watcher.Changed += () =>
{
Win.OnStorageChanged();
FenceManager.RefreshAll();
};
_watcher.NewOnDesktop += Win.AutoSortNew;
Autostart.RefreshPath();
bool minimized = e.Args.Contains("--minimized", StringComparer.OrdinalIgnoreCase);
if (!minimized || !AppSettings.Current.TrayEnabled) Win.Show();
FenceManager.Sync();
// Bildschirm abgesteckt/Auflösung geändert → Fenster und Boxen wieder in den sichtbaren Bereich holen
Microsoft.Win32.SystemEvents.DisplaySettingsChanged += (_, _) => Dispatcher.BeginInvoke(() =>
{
Win.EnsureOnScreen();
FenceManager.EnsureOnScreen();
});
Win.CheckForUpdatesOnStartup();
}
/// <summary>Tastenkürzel neu registrieren. false = von anderem Programm belegt.</summary>
public static bool ApplyHotkey()
{
if (_hotkey is null) return false;
var s = AppSettings.Current;
if (!s.HotkeyEnabled) { _hotkey.Unregister(); return true; }
return _hotkey.Register(s.HotkeyModifiers, s.HotkeyKey);
}
public static void ApplyTray()
{
if (TrayIcon is not null) TrayIcon.Visible = AppSettings.Current.TrayEnabled;
}
public static void Quit()
{
if (Quitting) return;
Quitting = true;
Win?.CancelBusyAndWait(); // z. B. große Kopie abbrechen, halbe Dateien aufräumen
Win?.SavePlacement();
Win?.Undo.FlushAll();
FenceManager.CloseAll();
_watcher?.Dispose();
_hotkey?.Dispose();
TrayIcon?.Dispose();
Win?.Close();
Current.Shutdown();
}
protected override void OnExit(ExitEventArgs e)
{
TrayIcon?.Dispose();
_mutex?.Dispose();
base.OnExit(e);
}
}
using System.IO;
using System.Windows;
using System.Windows.Interop;
namespace DeskCore;
public partial class App : Application
{
private const string MutexName = "DeskCore.Instanz";
public static MainWindow? Win { get; private set; }
public static Tray? TrayIcon { get; private set; }
public static bool Quitting { get; private set; }
private static Hotkey? _hotkey;
private static Hotkey? _peekHotkey;
private static Hotkey? _pagePrev, _pageNext, _quickSearch;
private static System.Windows.Threading.DispatcherTimer? _desktopTimer;
private static Watcher? _watcher;
private Mutex? _mutex;
protected override void OnStartup(StartupEventArgs e)
{
// Nur eine Instanz: weitere Aufrufe reichen ihren Befehl (oder "Fenster zeigen") an die laufende weiter
_mutex = new Mutex(true, MutexName, out bool first);
if (!first)
{
Commands.Send(e.Args);
Shutdown();
return;
}
Migration.Run(); // alte "Desktop Sortierer"-Ordner übernehmen – vor allem anderen
Commands.Listen(args => Dispatcher.BeginInvoke(() => HandleCommand(args)));
DispatcherUnhandledException += (_, args) =>
{
var log = Path.Combine(Storage.Root, "fehler.log");
try { File.AppendAllText(log, $"[{DateTime.Now}] {args.Exception}\n\n"); } catch { }
MessageBox.Show($"Unerwarteter Fehler:\n\n{args.Exception.Message}\n\nDetails: {log}", "DeskCore",
MessageBoxButton.OK, MessageBoxImage.Error);
args.Handled = true;
};
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnExplicitShutdown;
Storage.FlushHolding(); // Reste eines Absturzes in den Papierkorb
Theme.Init();
Theme.Changed += FenceManager.RefreshAll;
Win = new MainWindow();
MainWindow = Win;
var hwnd = new WindowInteropHelper(Win).EnsureHandle();
_hotkey = new Hotkey(hwnd);
_hotkey.Pressed += () => Win.ToggleFromHotkey();
ApplyHotkey();
_peekHotkey = new Hotkey(hwnd, 0xB002);
_peekHotkey.Pressed += FenceManager.TogglePeek;
ApplyPeekHotkey();
// Desktop-Seiten: Win + Alt + Pfeil links/rechts
_pagePrev = new Hotkey(hwnd, 0xB003);
_pagePrev.Pressed += () => FenceManager.NextPage(-1);
_pagePrev.Register(Hotkey.ModWin | Hotkey.ModAlt, 0x25);
_pageNext = new Hotkey(hwnd, 0xB004);
_pageNext.Pressed += () => FenceManager.NextPage(1);
_pageNext.Register(Hotkey.ModWin | Hotkey.ModAlt, 0x27);
_quickSearch = new Hotkey(hwnd, 0xB005);
_quickSearch.Pressed += QuickSearch.Toggle;
ApplyQuickSearchHotkey();
TrayIcon = new Tray();
TrayIcon.Clicked += () => Win.ShowFromBackground();
TrayIcon.BalloonClicked += () => Win.ShowFromBackground();
TrayIcon.MenuRequested += () => Win.ShowTrayMenu();
ApplyTray();
_watcher = new Watcher(Dispatcher);
_watcher.Changed += () =>
{
Win.OnStorageChanged();
FenceManager.RefreshAll();
Win.ScheduleDeadShortcutScan(); // z. B. Deinstallation hat die Desktop-Verknüpfung entfernt
};
_watcher.NewOnDesktop += Win.AutoSortNew;
Autostart.RefreshPath();
// Mit einem Befehl gestartet (z. B. Start-Gruppe per Desktop-Verknüpfung)? Dann ohne Fenster.
if (Commands.WantsWindow(e.Args) || !AppSettings.Current.TrayEnabled) Win.Show();
FenceManager.Sync();
// Bildschirm abgesteckt/Auflösung geändert → Fenster und Boxen wieder in den sichtbaren Bereich holen
Microsoft.Win32.SystemEvents.DisplaySettingsChanged += (_, _) => Dispatcher.BeginInvoke(() =>
{
Win.EnsureOnScreen();
FenceManager.OnDisplayChanged();
});
Win.CheckForUpdatesOnStartup();
if (!Commands.WantsWindow(e.Args)) Dispatcher.BeginInvoke(() => HandleCommand(e.Args));
Win.StartDeadShortcutWatch();
DesktopToggle.Apply();
// Positionen der Desktop-Icons regelmäßig lesen (für Aufziehen, Doppelklick und die Icon-Tönung)
_desktopTimer = new System.Windows.Threading.DispatcherTimer { Interval = TimeSpan.FromSeconds(2) };
_desktopTimer.Tick += (_, _) => RefreshDesktopIcons();
_desktopTimer.Start();
RefreshDesktopIcons();
}
private static void RefreshDesktopIcons()
{
var s = AppSettings.Current;
if (Quitting) return;
PageScheduler.Tick();
// Nur lesen, wenn eine Funktion die Positionen braucht
if (s.DesktopTint is null && !s.DrawFences && !s.QuickHide) return;
try { DesktopIcons.RefreshCache(); } catch { /* Explorer gerade neu gestartet o. Ä. */ }
DesktopTint.Refresh();
}
/// <summary>Befehl von der Befehlszeile oder einem weiteren Aufruf ausführen.</summary>
private static void HandleCommand(string[] args)
{
if (Win is null || Quitting) return;
if (Commands.Value(args, "--launch") is { } group) Win.LaunchGroupByName(group);
else if (Commands.Value(args, "--snapshot") is { } snap) Win.RestoreSnapshotByName(snap);
else if (Commands.Has(args, "--toggle-desktop")) DesktopToggle.Toggle();
else if (Commands.Has(args, "--peek")) FenceManager.TogglePeek();
else if (Commands.Has(args, "--search")) QuickSearch.Toggle();
else if (Commands.Value(args, "--page") is { } page) SwitchPageByName(page);
else if (Commands.WantsWindow(args)) Win.ShowFromBackground();
}
/// <summary>DeskCore.exe --page "Name" oder --page 2.</summary>
private static void SwitchPageByName(string page)
{
var pages = AppSettings.Current.Pages;
int i = pages.FindIndex(p => string.Equals(p, page, StringComparison.OrdinalIgnoreCase));
if (i < 0 && int.TryParse(page, out var n)) i = n - 1;
if (i >= 0 && i < pages.Count) FenceManager.SwitchPage(i);
else TrayIcon?.Notify("Seite nicht gefunden", $"Es gibt keine Desktop-Seite „{page}“.");
}
public static bool QuickSearchRegistered { get; private set; }
/// <summary>Tastenkürzel der Schnellstart-Suche neu registrieren. false = von anderem Programm belegt.</summary>
public static bool ApplyQuickSearchHotkey()
{
if (_quickSearch is null) return false;
var s = AppSettings.Current;
if (!s.QuickSearchEnabled) { _quickSearch.Unregister(); return QuickSearchRegistered = true; }
return QuickSearchRegistered = _quickSearch.Register(s.QuickSearchModifiers, s.QuickSearchKey);
}
/// <summary>Tastenkürzel neu registrieren. false = von anderem Programm belegt.</summary>
public static bool ApplyHotkey()
{
if (_hotkey is null) return false;
var s = AppSettings.Current;
if (!s.HotkeyEnabled) { _hotkey.Unregister(); return true; }
return _hotkey.Register(s.HotkeyModifiers, s.HotkeyKey);
}
/// <summary>Peek-Kürzel neu registrieren. false = von anderem Programm belegt.</summary>
public static bool ApplyPeekHotkey()
{
if (_peekHotkey is null) return false;
var s = AppSettings.Current;
if (!s.PeekEnabled) { _peekHotkey.Unregister(); return true; }
return _peekHotkey.Register(s.PeekModifiers, s.PeekKey);
}
public static void ApplyTray()
{
if (TrayIcon is not null) TrayIcon.Visible = AppSettings.Current.TrayEnabled;
}
public static void Quit()
{
if (Quitting) return;
Quitting = true;
Win?.CancelBusyAndWait(); // z. B. große Kopie abbrechen, halbe Dateien aufräumen
Win?.SavePlacement();
_desktopTimer?.Stop();
DesktopToggle.Stop();
DesktopToggle.RestoreOnExit();
_peekHotkey?.Dispose();
_pagePrev?.Dispose();
_quickSearch?.Dispose();
Usage.Flush();
_pageNext?.Dispose();
Win?.Undo.FlushAll();
FenceManager.CloseAll();
_watcher?.Dispose();
_hotkey?.Dispose();
TrayIcon?.Dispose();
Win?.Close();
Current.Shutdown();
}
protected override void OnExit(ExitEventArgs e)
{
TrayIcon?.Dispose();
_mutex?.Dispose();
base.OnExit(e);
}
}
+6
View File
@@ -170,6 +170,12 @@ public static class Backup
if (!s.Rules.Any(x => string.Equals(x.Contains, r.Contains, StringComparison.OrdinalIgnoreCase) &&
string.Equals(x.Category, r.Category, StringComparison.OrdinalIgnoreCase)))
s.Rules.Add(r);
foreach (var g in from.LaunchGroups ?? [])
if (!s.LaunchGroups.Any(x => string.Equals(x.Name, g.Name, StringComparison.OrdinalIgnoreCase)))
s.LaunchGroups.Add(g);
foreach (var snap in from.Snapshots ?? [])
if (!s.Snapshots.Any(x => string.Equals(x.Name, snap.Name, StringComparison.OrdinalIgnoreCase)))
s.Snapshots.Add(snap);
s.Save();
}
}
+190
View File
@@ -0,0 +1,190 @@
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace DeskCore;
/// <summary>
/// Auswahl nach dem Aufziehen einer Box: Welche Kategorie (oder welcher Ordner) soll hinein?
/// Ein eigenes Fenster statt eines Kontextmenüs – ein Menü ohne aktives Fenster schließt sich sonst gelegentlich von selbst.
/// </summary>
public sealed class BoxPicker : Window
{
private static BoxPicker? _open;
private readonly long _shownAt = Environment.TickCount64;
private bool _done;
private static AppSettings S => AppSettings.Current;
/// <param name="area">Fläche der neuen Box in WPF-Einheiten.</param>
/// <param name="create">Legt die Box mit dem gewählten Tab an.</param>
public static void Show(Rect area, Action<FenceTab> create)
{
_open?.Finish(null);
_open = new BoxPicker(area, create);
_open.Show();
}
private readonly Action<FenceTab> _create;
private BoxPicker(Rect area, Action<FenceTab> create)
{
_create = create;
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent;
ResizeMode = ResizeMode.NoResize;
ShowInTaskbar = false;
Topmost = true;
SizeToContent = SizeToContent.WidthAndHeight;
Title = "Neue Box";
var res = Application.Current.Resources;
var list = new StackPanel();
list.Children.Add(new TextBlock
{
Text = "Neue Box hier", FontSize = 14, FontWeight = FontWeights.SemiBold, Margin = new Thickness(12, 8, 12, 0),
Foreground = (Brush)res["TextBrush"],
});
list.Children.Add(new TextBlock
{
Text = "Was soll hinein?", FontSize = 12, Margin = new Thickness(12, 2, 12, 6),
Foreground = (Brush)res["TextSecondaryBrush"],
});
var cats = Storage.Categories();
var catPanel = new StackPanel();
foreach (var c in cats)
{
var meta = S.Categories.GetValueOrDefault(c);
// Pro Seite steht eine Kategorie nur in einer Box – andere Seiten behalten ihre
string? hint = S.HasFence(c) ? "wird aus ihrer Box auf dieser Seite hierher geholt" : null;
catPanel.Children.Add(Row(meta?.Glyph ?? "", CategoryColors.Brush(meta?.Color), c, hint,
() => Pick(new FenceTab { Value = c })));
}
if (cats.Count > 0)
{
list.Children.Add(new ScrollViewer
{
Content = catPanel, MaxHeight = 360, VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
});
list.Children.Add(Divider());
}
list.Children.Add(Row("", null, "Zuletzt benutzt", "zeigt, was du zuletzt gestartet hast",
() => Pick(new FenceTab { Kind = FenceTab.RecentKind })));
list.Children.Add(Row("", null, "Neue Kategorie …", null, () => PickLater(() =>
App.Win?.CreateCategoryInteractive() is { } name ? new FenceTab { Value = name } : null)));
list.Children.Add(Row("", null, "Ordner-Portal …", null, () => PickLater(() =>
FenceManager.PickFolder() is { } folder ? new FenceTab { Kind = FenceTab.FolderKind, Value = folder } : null)));
list.Children.Add(Divider());
list.Children.Add(Row("", null, "Abbrechen", null, () => Finish(null)));
Content = new Border
{
Margin = new Thickness(14), Padding = new Thickness(4), MinWidth = 240, CornerRadius = new CornerRadius(8),
Background = (Brush)res["FlyoutBrush"], BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(1),
Effect = (System.Windows.Media.Effects.Effect)res["FlyoutShadow"],
Child = list,
};
// Neben dem Mauszeiger öffnen, aber immer ganz auf dem Bildschirm
Loaded += (_, _) =>
{
var mouse = GetCursorPos(out var p) ? new Point(p.X / Screens.Scale, p.Y / Screens.Scale) : area.BottomRight;
var wa = Screens.WorkAreaAt(mouse);
Left = Math.Clamp(mouse.X - 14, wa.Left, Math.Max(wa.Left, wa.Right - ActualWidth));
Top = Math.Clamp(mouse.Y - 14, wa.Top, Math.Max(wa.Top, wa.Bottom - ActualHeight));
BringToFront();
};
KeyDown += (_, e) => { if (e.Key == Key.Escape) Finish(null); };
// Klick daneben schließt – aber nicht durch ein kurzes Flackern direkt nach dem Öffnen
Deactivated += (_, _) =>
{
if (_done) return;
if (Environment.TickCount64 - _shownAt < 400) BringToFront();
else Finish(null);
};
Closed += (_, _) => { if (_open == this) _open = null; DrawBoxOverlay.Hide(); };
}
private Button Row(string glyph, Brush? glyphBrush, string text, string? hint, Action click)
{
var res = Application.Current.Resources;
var content = new DockPanel();
var icon = new TextBlock
{
Text = glyph, FontFamily = (FontFamily)res["IconFont"], FontSize = 15, Width = 20, Margin = new Thickness(0, 0, 12, 0),
VerticalAlignment = VerticalAlignment.Center, Foreground = glyphBrush ?? (Brush)res["TextBrush"],
};
DockPanel.SetDock(icon, Dock.Left);
content.Children.Add(icon);
var texts = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
texts.Children.Add(new TextBlock { Text = text, Foreground = (Brush)res["TextBrush"] });
if (hint is not null)
texts.Children.Add(new TextBlock { Text = hint, FontSize = 11, Foreground = (Brush)res["TextTertiaryBrush"] });
content.Children.Add(texts);
var b = new Button
{
Style = (Style)res["SubtleButton"], Content = content, HorizontalContentAlignment = HorizontalAlignment.Stretch,
Padding = new Thickness(10, 7, 10, 7), Focusable = true,
};
b.Click += (_, _) => click();
return b;
}
private static Border Divider() => new()
{
Height = 1, Margin = new Thickness(4, 4, 4, 4), Background = (Brush)Application.Current.Resources["ControlStrokeBrush"],
};
private void Pick(FenceTab tab) => Finish(tab);
/// <summary>Für Auswahlen mit eigenem Dialog: erst die Auswahl schließen, Rahmen aber stehen lassen.</summary>
private void PickLater(Func<FenceTab?> ask)
{
_done = true;
Hide();
var tab = ask();
if (tab is not null) _create(tab);
Close();
}
private void Finish(FenceTab? tab)
{
if (_done) return;
_done = true;
Close();
if (tab is not null) _create(tab);
}
/// <summary>
/// DeskCore läuft im Hintergrund – Windows lässt es nicht ohne Weiteres nach vorne. Ein kurzer Alt-Tastendruck
/// gibt die Erlaubnis (bewährter Weg, der auch von Windows selbst so akzeptiert wird).
/// </summary>
private void BringToFront()
{
var hwnd = new WindowInteropHelper(this).Handle;
if (GetForegroundWindow() != hwnd)
{
// Loslassen erst nach dem Wechsel – so landet es bei uns und öffnet nirgends ein Menü
keybd_event(VK_MENU, 0, 0, UIntPtr.Zero);
SetForegroundWindow(hwnd);
keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, UIntPtr.Zero);
}
Activate();
Focus();
}
private const byte VK_MENU = 0x12;
private const uint KEYEVENTF_KEYUP = 0x2;
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT p);
[DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll")] private static extern void keybd_event(byte vk, byte scan, uint flags, UIntPtr extra);
}
+70
View File
@@ -0,0 +1,70 @@
using System.IO;
using System.IO.Pipes;
namespace DeskCore;
/// <summary>
/// Befehlszeile von DeskCore, z. B. für Desktop-Verknüpfungen und Skripte:
/// DeskCore.exe --launch "Arbeit" Start-Gruppe starten
/// DeskCore.exe --snapshot "Name" Box-Anordnung wiederherstellen
/// DeskCore.exe --toggle-desktop Desktop-Icons und Boxen ein-/ausblenden
/// DeskCore.exe --peek Boxen vor alle Fenster holen
/// DeskCore.exe --minimized im Infobereich starten
/// Läuft DeskCore schon, wird der Befehl an die laufende Instanz weitergereicht.
/// </summary>
public static class Commands
{
private const string PipeName = "DeskCore.Befehle";
private const char Separator = '\u001F';
public static string? Value(string[] args, string name)
{
int i = Array.FindIndex(args, a => a.Equals(name, StringComparison.OrdinalIgnoreCase));
return i >= 0 && i + 1 < args.Length ? args[i + 1] : null;
}
public static bool Has(string[] args, string name) => args.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase));
/// <summary>Soll für diesen Aufruf das Hauptfenster erscheinen?</summary>
public static bool WantsWindow(string[] args) =>
!Has(args, "--minimized") && Value(args, "--launch") is null && Value(args, "--snapshot") is null &&
!Has(args, "--toggle-desktop") && !Has(args, "--peek");
/// <summary>An die laufende Instanz senden.</summary>
public static bool Send(string[] args)
{
try
{
using var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out);
client.Connect(3000);
using var w = new StreamWriter(client);
w.Write(string.Join(Separator, args));
w.Flush();
return true;
}
catch { return false; }
}
/// <summary>
/// Empfängt Befehle weiterer Aufrufe (im Hintergrund). Die Pipe ist standardmäßig nur für den
/// eigenen Benutzer beschreibbar – andere Benutzer können DeskCore keine Befehle schicken.
/// </summary>
public static void Listen(Action<string[]> onCommand)
{
new Thread(() =>
{
while (true)
{
try
{
using var server = new NamedPipeServerStream(PipeName, PipeDirection.In, 1);
server.WaitForConnection();
using var r = new StreamReader(server);
var text = r.ReadToEnd();
onCommand(text.Length == 0 ? [] : text.Split(Separator));
}
catch { Thread.Sleep(500); }
}
}) { IsBackground = true, Name = "Befehle" }.Start();
}
}
+234
View File
@@ -0,0 +1,234 @@
using System.Runtime.InteropServices;
using System.Text;
namespace DeskCore;
/// <summary>Ein Icon auf dem echten Windows-Desktop (Bildschirm-Pixel).</summary>
public record DesktopIcon(string Path, int X, int Y, int IconSize, int CellWidth);
/// <summary>
/// Liest Positionen und Größe der echten Desktop-Icons über die offizielle Shell-Schnittstelle (IFolderView)
/// aus – ohne etwas in den Explorer einzuschleusen. Nur auf dem UI-Thread (STA) aufrufen.
/// </summary>
public static class DesktopIcons
{
/// <summary>Alle Desktop-Icons mit Position – oder leer, wenn der Desktop gerade nicht erreichbar ist.</summary>
public static List<DesktopIcon> Read()
{
var result = new List<DesktopIcon>();
try
{
var view = DesktopFolderView(out var listView);
if (view is null || listView == IntPtr.Zero) return result;
var view2 = view as IFolderView2;
int iconSize = 48;
if (view2 is not null && view2.GetViewModeAndIconSize(out _, out var size) == 0) iconSize = size;
view.GetSpacing(out var spacing);
var origin = new POINT();
ClientToScreen(listView, ref origin);
view.ItemCount(SVGIO_ALLVIEW, out int count);
for (int i = 0; i < count; i++)
{
if (view.Item(i, out var pidl) != 0 || pidl == IntPtr.Zero) continue;
try
{
if (view.GetItemPosition(pidl, out var pos) != 0) continue;
string path = "";
if (view2 is not null && view2.GetItem(i, typeof(IShellItem).GUID, out var obj) == 0 && obj is IShellItem item)
{
if (item.GetDisplayName(SIGDN_FILESYSPATH, out var p) == 0)
{
path = Marshal.PtrToStringUni(p) ?? "";
Marshal.FreeCoTaskMem(p);
}
Marshal.ReleaseComObject(item);
}
result.Add(new DesktopIcon(path, origin.X + pos.X, origin.Y + pos.Y, iconSize, spacing.X));
}
finally { Marshal.FreeCoTaskMem(pidl); }
}
}
catch { }
return result;
}
/// <summary>
/// Rechteck des Icon-Bildes in Bildschirm-Pixeln. Die Position aus IFolderView ist bereits die linke obere
/// Ecke des Bildes (gemessen: Eintrag beginnt (Abstand − Bildgröße) / 2 weiter links, 3 px weiter oben).
/// </summary>
public static (int L, int T, int Size) IconRect(DesktopIcon i) => (i.X, i.Y + IconTopOffset, i.IconSize);
/// <summary>Feinkorrektur nach unten (Windows zeichnet das Bild mit kleinem Rand).</summary>
public static int IconTopOffset { get; set; } = 3;
/// <summary>Gesamte Fläche eines Eintrags (Bild + Beschriftung), z. B. für Klick-Tests.</summary>
public static (int L, int T, int W, int H) CellRect(DesktopIcon i)
{
int pad = Math.Max(0, (i.CellWidth - i.IconSize) / 2);
return (i.X - pad, i.Y, i.CellWidth, i.IconSize + 44);
}
/// <summary>Zuletzt gelesene Icons (für schnelle Prüfungen aus dem Maus-Hook, ohne COM-Aufruf).</summary>
public static volatile IReadOnlyList<DesktopIcon> Cached = [];
/// <summary>Zwischenspeicher auffrischen (UI-Thread). Gibt true zurück, wenn sich etwas geändert hat.</summary>
public static bool RefreshCache()
{
var fresh = Read();
bool changed = fresh.Count != Cached.Count || fresh.Where((x, n) => x != Cached[n]).Any();
Cached = fresh;
return changed;
}
/// <param name="margin">Zusätzlicher Rand in Pixeln, damit Klicks knapp neben Bild oder Beschriftung auch zählen.</param>
public static bool HitsIcon(int x, int y, int margin = 0) => Cached.Any(i =>
{
var (l, t, w, h) = CellRect(i);
return x >= l - margin && x < l + w + margin && y >= t - margin && y < t + h + margin;
});
// ---------- Desktop-Ansicht finden ----------
private static IFolderView? DesktopFolderView(out IntPtr listView)
{
listView = IntPtr.Zero;
var shellWindows = (IShellWindows)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_ShellWindows)!)!;
try
{
object loc = CSIDL_DESKTOP, empty = new object();
var disp = shellWindows.FindWindowSW(ref loc, ref empty, SWC_DESKTOP, out _, SWFO_NEEDDISPATCH);
if (disp is not IServiceProvider sp) return null;
var sidBrowser = SID_STopLevelBrowser;
var iidBrowser = typeof(IShellBrowser).GUID;
if (sp.QueryService(ref sidBrowser, ref iidBrowser, out var browserObj) != 0) return null;
var browser = (IShellBrowser)browserObj;
if (browser.QueryActiveShellView(out var shellView) != 0 || shellView is null) return null;
shellView.GetWindow(out var defView);
listView = FindWindowEx(defView, IntPtr.Zero, "SysListView32", null);
return shellView as IFolderView;
}
finally { Marshal.ReleaseComObject(shellWindows); }
}
// ---------- COM-Schnittstellen (nur die benötigten Methoden in vtable-Reihenfolge) ----------
private const int CSIDL_DESKTOP = 0, SWC_DESKTOP = 8, SWFO_NEEDDISPATCH = 1, SVGIO_ALLVIEW = 2;
private const uint SIGDN_FILESYSPATH = 0x80058000;
private static readonly Guid CLSID_ShellWindows = new("9BA05972-F6A8-11CF-A442-00A0C90A8F39");
private static readonly Guid SID_STopLevelBrowser = new("4C96BE40-915C-11CF-99D3-00AA004AE837");
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
[ComImport, Guid("85CB6900-4D95-11CF-960C-0080C7F4EE85"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
private interface IShellWindows
{
int Count { get; }
[return: MarshalAs(UnmanagedType.IDispatch)] object Item(object index);
[return: MarshalAs(UnmanagedType.IUnknown)] object _NewEnum();
void Register([MarshalAs(UnmanagedType.IDispatch)] object pid, int hwnd, int swClass, out int plCookie);
void RegisterPending(int lThreadId, ref object pvarloc, ref object pvarlocRoot, int swClass, out int plCookie);
void Revoke(int lCookie);
void OnNavigate(int lCookie, ref object pvarLoc);
void OnActivated(int lCookie, short fActive);
[return: MarshalAs(UnmanagedType.IDispatch)]
object FindWindowSW(ref object pvarLoc, ref object pvarLocRoot, int swClass, out int pHWND, int swfwOptions);
}
[ComImport, Guid("6D5140C1-7436-11CE-8034-00AA006009FA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IServiceProvider
{
[PreserveSig] int QueryService(ref Guid guidService, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppvObject);
}
[ComImport, Guid("000214E2-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellBrowser
{
void GetWindow(out IntPtr phwnd);
void ContextSensitiveHelp(bool fEnterMode);
void InsertMenusSB(IntPtr hmenuShared, IntPtr lpMenuWidths);
void SetMenuSB(IntPtr hmenuShared, IntPtr holemenuRes, IntPtr hwndActiveObject);
void RemoveMenusSB(IntPtr hmenuShared);
void SetStatusTextSB(IntPtr pszStatusText);
void EnableModelessSB(bool fEnable);
void TranslateAcceleratorSB(IntPtr pmsg, ushort wID);
void BrowseObject(IntPtr pidl, uint wFlags);
void GetViewStateStream(uint grfMode, out IntPtr ppStrm);
void GetControlWindow(uint id, out IntPtr phwnd);
void SendControlMsg(uint id, uint uMsg, IntPtr wParam, IntPtr lParam, out IntPtr pret);
[PreserveSig] int QueryActiveShellView(out IShellView ppshv);
}
[ComImport, Guid("000214E3-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellView
{
void GetWindow(out IntPtr phwnd);
}
[ComImport, Guid("CDE725B0-CCC9-4519-917E-325D72FAB4CE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IFolderView
{
[PreserveSig] int GetCurrentViewMode(out uint pViewMode);
[PreserveSig] int SetCurrentViewMode(uint ViewMode);
[PreserveSig] int GetFolder(ref Guid riid, out IntPtr ppv);
[PreserveSig] int Item(int iItemIndex, out IntPtr ppidl);
[PreserveSig] int ItemCount(uint uFlags, out int pcItems);
[PreserveSig] int Items(uint uFlags, ref Guid riid, out IntPtr ppv);
[PreserveSig] int GetSelectionMarkedItem(out int piItem);
[PreserveSig] int GetFocusedItem(out int piItem);
[PreserveSig] int GetItemPosition(IntPtr pidl, out POINT ppt);
[PreserveSig] int GetSpacing(out POINT ppt);
}
[ComImport, Guid("1AF3A467-214F-4298-908E-06B03E0B39F9"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IFolderView2
{
// IFolderView
[PreserveSig] int GetCurrentViewMode(out uint pViewMode);
[PreserveSig] int SetCurrentViewMode(uint ViewMode);
[PreserveSig] int GetFolder(ref Guid riid, out IntPtr ppv);
[PreserveSig] int Item(int iItemIndex, out IntPtr ppidl);
[PreserveSig] int ItemCount(uint uFlags, out int pcItems);
[PreserveSig] int Items(uint uFlags, ref Guid riid, out IntPtr ppv);
[PreserveSig] int GetSelectionMarkedItem(out int piItem);
[PreserveSig] int GetFocusedItem(out int piItem);
[PreserveSig] int GetItemPosition(IntPtr pidl, out POINT ppt);
[PreserveSig] int GetSpacing(out POINT ppt);
[PreserveSig] int GetDefaultSpacing(out POINT ppt);
[PreserveSig] int GetAutoArrange();
[PreserveSig] int SelectItem(int iItem, uint dwFlags);
[PreserveSig] int SelectAndPositionItems(uint cidl, IntPtr apidl, IntPtr apt, uint dwFlags);
// IFolderView2
[PreserveSig] int SetGroupBy(IntPtr key, bool fAscending);
[PreserveSig] int GetGroupBy(IntPtr pkey, out bool pfAscending);
[PreserveSig] int SetViewProperty(IntPtr pidl, IntPtr propkey, IntPtr propvar);
[PreserveSig] int GetViewProperty(IntPtr pidl, IntPtr propkey, IntPtr ppropvar);
[PreserveSig] int SetTileViewProperties(IntPtr pidl, IntPtr pszPropList);
[PreserveSig] int SetExtendedTileViewProperties(IntPtr pidl, IntPtr pszPropList);
[PreserveSig] int SetText(int iType, IntPtr pwszText);
[PreserveSig] int SetCurrentFolderFlags(uint dwMask, uint dwFlags);
[PreserveSig] int GetCurrentFolderFlags(out uint pdwFlags);
[PreserveSig] int GetSortColumnCount(out int pcColumns);
[PreserveSig] int SetSortColumns(IntPtr rgSortColumns, int cColumns);
[PreserveSig] int GetSortColumns(IntPtr rgSortColumns, int cColumns);
[PreserveSig] int GetItem(int iItem, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.Interface)] out object ppv);
[PreserveSig] int GetVisibleItem(int iStart, bool fPrevious, out int piItem);
[PreserveSig] int GetSelectedItem(int iStart, out int piItem);
[PreserveSig] int GetSelection(bool fNoneImpliesFolder, out IntPtr ppsia);
[PreserveSig] int GetSelectionState(IntPtr pidl, out uint pdwFlags);
[PreserveSig] int InvokeVerbOnSelection(IntPtr pszVerb);
[PreserveSig] int SetViewModeAndIconSize(int uViewMode, int iImageSize);
[PreserveSig] int GetViewModeAndIconSize(out int puViewMode, out int piImageSize);
}
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellItem
{
[PreserveSig] int BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
[PreserveSig] int GetParent(out IShellItem ppsi);
[PreserveSig] int GetDisplayName(uint sigdnName, out IntPtr ppszName);
}
[DllImport("user32.dll")] private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string? cls, string? title);
}
+263
View File
@@ -0,0 +1,263 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>Gemeinsame Hilfen für klickdurchlässige Overlay-Fenster.</summary>
internal static class Overlay
{
public static Window Create(bool topmost)
{
var w = new Window
{
WindowStyle = WindowStyle.None, AllowsTransparency = true, Background = Brushes.Transparent,
ShowInTaskbar = false, ShowActivated = false, Topmost = topmost, ResizeMode = ResizeMode.NoResize,
Focusable = false, IsHitTestVisible = false,
};
w.SourceInitialized += (_, _) =>
{
var hwnd = new WindowInteropHelper(w).Handle;
long ex = GetWindowLongPtr(hwnd, GWL_EXSTYLE).ToInt64();
SetWindowLongPtr(hwnd, GWL_EXSTYLE, new IntPtr(ex | WS_EX_TRANSPARENT | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE));
};
return w;
}
/// <summary>Wie die Boxen: Besitzer = Desktop, immer ganz unten (über den Desktop-Icons, unter allen Fenstern).</summary>
public static void PinToDesktop(Window w)
{
var hwnd = new WindowInteropHelper(w).Handle;
var progman = FindWindow("Progman", null);
if (progman != IntPtr.Zero) SetWindowLongPtr(hwnd, GWLP_HWNDPARENT, progman);
HwndSource.FromHwnd(hwnd).AddHook((IntPtr h, int msg, IntPtr wp, IntPtr lp, ref bool handled) =>
{
if (msg == WM_WINDOWPOSCHANGING)
{
var pos = Marshal.PtrToStructure<WINDOWPOS>(lp);
pos.hwndInsertAfter = new IntPtr(1); // HWND_BOTTOM
pos.flags &= ~SWP_NOZORDER;
Marshal.StructureToPtr(pos, lp, false);
}
return IntPtr.Zero;
});
}
private const int GWL_EXSTYLE = -20, GWLP_HWNDPARENT = -8, WM_WINDOWPOSCHANGING = 0x46;
private const long WS_EX_TRANSPARENT = 0x20, WS_EX_LAYERED = 0x80000, WS_EX_TOOLWINDOW = 0x80, WS_EX_NOACTIVATE = 0x8000000;
private const uint SWP_NOZORDER = 0x4;
[StructLayout(LayoutKind.Sequential)]
private struct WINDOWPOS { public IntPtr hwnd, hwndInsertAfter; public int x, y, cx, cy; public uint flags; }
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindow(string? cls, string? title);
[DllImport("user32.dll")] private static extern IntPtr GetWindowLongPtr(IntPtr h, int i);
[DllImport("user32.dll")] private static extern IntPtr SetWindowLongPtr(IntPtr h, int i, IntPtr v);
}
// =========================================================================
// Seitenanzeige beim Wechseln der Desktop-Seite
// =========================================================================
public static class PageIndicator
{
private static Window? _w;
private static TextBlock? _name;
private static StackPanel? _dots;
private static DispatcherTimer? _hide;
public static void Show(string name, int index, int count)
{
if (_w is null)
{
_w = Overlay.Create(topmost: true);
_w.SizeToContent = SizeToContent.WidthAndHeight;
_name = new TextBlock { FontSize = 15, FontWeight = FontWeights.SemiBold, HorizontalAlignment = HorizontalAlignment.Center };
_dots = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center, Margin = new Thickness(0, 8, 0, 0) };
_w.Content = new Border
{
Padding = new Thickness(22, 12, 22, 12), CornerRadius = new CornerRadius(10), Margin = new Thickness(12),
Child = new StackPanel { Children = { _name, _dots } },
};
_hide = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1300) };
_hide.Tick += (_, _) =>
{
_hide.Stop();
var fade = new DoubleAnimation(0, TimeSpan.FromMilliseconds(350));
fade.Completed += (_, _) => { if (_w.Opacity == 0) _w.Hide(); };
_w.BeginAnimation(UIElement.OpacityProperty, fade);
};
}
var res = Application.Current.Resources;
var border = (Border)_w.Content;
border.Background = (Brush)res["FlyoutBrush"];
border.BorderBrush = (Brush)res["FlyoutStrokeBrush"];
border.BorderThickness = new Thickness(1);
border.Effect = (System.Windows.Media.Effects.Effect)res["FlyoutShadow"];
_name!.Text = name;
_name.Foreground = (Brush)res["TextBrush"];
_dots!.Children.Clear();
for (int i = 0; i < count; i++)
_dots.Children.Add(new Ellipse
{
Width = 7, Height = 7, Margin = new Thickness(4, 0, 4, 0),
Fill = (Brush)res[i == index ? "AccentBrush" : "ControlStrongStrokeBrush"],
});
_w.BeginAnimation(UIElement.OpacityProperty, null);
_w.Opacity = 1;
_w.Show();
_w.UpdateLayout();
var wa = Screens.PrimaryWorkArea;
_w.Left = wa.Left + (wa.Width - _w.ActualWidth) / 2;
_w.Top = wa.Bottom - _w.ActualHeight - 40;
_hide!.Stop();
_hide.Start();
}
}
// =========================================================================
// Rahmen beim Aufziehen einer neuen Box (rechte Maustaste auf dem Desktop)
// =========================================================================
public static class DrawBoxOverlay
{
private static Window? _w;
public static void Begin()
{
if (_w is null)
{
_w = Overlay.Create(topmost: true);
var res = Application.Current.Resources;
var accent = ((SolidColorBrush)res["AccentBrush"]).Color;
_w.Content = new Grid
{
Children =
{
new Rectangle
{
RadiusX = 8, RadiusY = 8, StrokeThickness = 2, StrokeDashArray = [4, 3],
Stroke = new SolidColorBrush(accent), Fill = new SolidColorBrush(Color.FromArgb(0x38, accent.R, accent.G, accent.B)),
},
new TextBlock
{
Text = "Neue Box", Foreground = Brushes.White, FontSize = 14, FontWeight = FontWeights.SemiBold,
HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center,
Effect = new System.Windows.Media.Effects.DropShadowEffect { BlurRadius = 6, ShadowDepth = 0, Opacity = 0.8 },
},
},
};
}
_w.Width = _w.Height = 1;
_w.Show();
}
/// <summary>Rechteck in Bildschirm-Pixeln.</summary>
public static void Update(Rect px)
{
if (_w is null) return;
double s = Screens.Scale;
_w.Left = px.X / s;
_w.Top = px.Y / s;
_w.Width = Math.Max(1, px.Width / s);
_w.Height = Math.Max(1, px.Height / s);
}
public static void Finish(Rect px)
{
double s = Screens.Scale;
var dip = new Rect(px.X / s, px.Y / s, px.Width / s, px.Height / s);
if (dip.Width < 120 || dip.Height < 60) { Hide(); return; } // zu klein – war wohl nur ein Versehen
Update(px); // Rahmen bleibt stehen, bis etwas ausgewählt ist
FenceManager.ShowCreateMenu(dip);
}
public static void Hide() => _w?.Hide();
}
// =========================================================================
// Tönung der echten Desktop-Icons
// =========================================================================
/// <summary>
/// Legt eine klickdurchlässige Farbschicht exakt über die Bilder der Desktop-Icons. Die Positionen stammen aus der
/// offiziellen Shell-Schnittstelle; am Explorer selbst wird nichts verändert. Icons unter Boxen bleiben ausgespart.
/// </summary>
public static class DesktopTint
{
private static Window? _w;
private static Canvas? _canvas;
private static string _signature = "";
private static AppSettings S => AppSettings.Current;
/// <summary>Schicht neu aufbauen, wenn sich Icons, Boxen oder Einstellungen geändert haben.</summary>
public static void Refresh(bool force = false)
{
bool on = S.DesktopTint is not null && !FenceManager.Hidden && DesktopToggle.IconsVisible();
if (!on)
{
_w?.Hide();
_signature = "";
return;
}
var icons = DesktopIcons.Cached.Where(i => i.Path.Length > 0).ToList();
var fences = FenceManager.OpenRects();
var sig = $"{S.DesktopTint}|{S.DesktopTintStrength}|{DesktopIcons.IconTopOffset}|" +
string.Join(";", icons.Select(i => $"{i.X},{i.Y},{i.IconSize}")) + "|" + string.Join(";", fences.Select(r => r.ToString()));
if (!force && sig == _signature && _w?.IsVisible == true) return;
_signature = sig;
if (_w is null)
{
_w = Overlay.Create(topmost: false);
_canvas = new Canvas();
_w.Content = _canvas;
_w.SourceInitialized += (_, _) => Overlay.PinToDesktop(_w);
}
// Fenster über die gesamte Desktop-Fläche (alle Bildschirme)
double s = Screens.Scale;
var vs = new Rect(SystemParameters.VirtualScreenLeft, SystemParameters.VirtualScreenTop,
SystemParameters.VirtualScreenWidth, SystemParameters.VirtualScreenHeight);
_w.Left = vs.Left;
_w.Top = vs.Top;
_w.Width = vs.Width;
_w.Height = vs.Height;
var tint = CategoryColors.Brush(S.DesktopTint) ?? Brushes.Transparent;
_canvas!.Children.Clear();
foreach (var icon in icons)
{
var (l, t, size) = DesktopIcons.IconRect(icon);
var r = new Rect(l / s, t / s, size / s, size / s);
if (fences.Any(f => f.IntersectsWith(r))) continue; // liegt unter einer Box
var item = new IconItem { Path = icon.Path };
var rect = new Rectangle
{
Width = r.Width, Height = r.Height, Fill = tint, Opacity = Math.Clamp(S.DesktopTintStrength, 0.1, 0.95),
Visibility = Visibility.Hidden, // erst zeigen, wenn die Form (das Icon) geladen ist
};
void Apply()
{
if (item.Icon is null) return;
rect.OpacityMask = new ImageBrush(item.Icon) { Stretch = Stretch.Fill };
rect.Visibility = Visibility.Visible;
}
item.PropertyChanged += (_, _) => Apply();
Canvas.SetLeft(rect, r.X - vs.Left);
Canvas.SetTop(rect, r.Y - vs.Top);
_canvas.Children.Add(rect);
IconLoader.Load(item, size);
Apply();
}
if (!_w.IsVisible) _w.Show();
}
}
+281
View File
@@ -0,0 +1,281 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
using System.Windows.Automation;
namespace DeskCore;
/// <summary>
/// Maus-Funktionen direkt auf dem Windows-Desktop – ohne etwas in den Explorer einzuschleusen:
/// • Doppelklick auf freie Stelle: Desktop-Icons und Boxen aus-/einblenden ("Schnell verstecken")
/// • Rechte Maustaste auf freier Stelle ziehen: neue Box aufziehen (normaler Rechtsklick bleibt unverändert)
/// • Mausrad über dem Desktop: Desktop-Seite wechseln
/// </summary>
public static class DesktopToggle
{
private static IntPtr _hook;
private static LowLevelMouseProc? _proc; // Referenz halten, sonst räumt der GC den Callback weg
private static long _lastDownTick;
private static POINT _lastDownPt;
// Box aufziehen
private static bool _rPending, _rDrawing, _updatePosted;
private static POINT _rStart, _rCurrent;
private static long _lastWheelTick;
private static AppSettings S => AppSettings.Current;
public static void Apply()
{
bool needed = S.QuickHide || S.DrawFences || (S.PageWheel && S.Pages.Count > 1);
if (needed) Start();
else Stop();
}
private static void Start()
{
if (_hook != IntPtr.Zero) return;
_proc = Proc;
using var module = Process.GetCurrentProcess().MainModule!;
_hook = SetWindowsHookEx(WH_MOUSE_LL, _proc, GetModuleHandle(module.ModuleName), 0);
}
public static void Stop()
{
if (_hook == IntPtr.Zero) return;
UnhookWindowsHookEx(_hook);
_hook = IntPtr.Zero;
}
/// <summary>Muss sehr schnell sein: nur Fensterklasse und zwischengespeicherte Icon-Positionen prüfen.</summary>
private static IntPtr Proc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0) return CallNextHookEx(_hook, nCode, wParam, lParam);
var info = Marshal.PtrToStructure<MSLLHOOKSTRUCT>(lParam);
bool injected = (info.flags & LLMHF_INJECTED) != 0;
int msg = wParam.ToInt32();
var dispatcher = Application.Current?.Dispatcher;
switch (msg)
{
case WM_LBUTTONDOWN when S.QuickHide:
{
long now = Environment.TickCount64;
bool isDouble = now - _lastDownTick <= GetDoubleClickTime() &&
Math.Abs(info.pt.x - _lastDownPt.x) <= 4 && Math.Abs(info.pt.y - _lastDownPt.y) <= 4;
if (isDouble)
{
_lastDownTick = 0;
var pt = info.pt;
var first = _lastDownPt;
// Schon im Hook (ohne COM) aussortieren: Doppelklick auf ein Icon = Programmstart
if (!DesktopIcons.HitsIcon(pt.x, pt.y, margin: 8) && !DesktopIcons.HitsIcon(first.x, first.y, margin: 8))
dispatcher?.BeginInvoke(() => OnDoubleClick(pt, first));
}
else
{
_lastDownTick = now;
_lastDownPt = info.pt;
}
break;
}
case WM_RBUTTONDOWN when !injected && S.DrawFences && IsDesktopAt(info.pt) && !DesktopIcons.HitsIcon(info.pt.x, info.pt.y):
// Rechtsklick auf freie Desktop-Fläche abfangen – bleibt es ein Klick, wird er unten nachgereicht
_rPending = true;
_rDrawing = false;
_rStart = _rCurrent = info.pt;
return new IntPtr(1);
case WM_MOUSEMOVE when _rPending:
_rCurrent = info.pt;
if (!_rDrawing && (Math.Abs(info.pt.x - _rStart.x) > 8 || Math.Abs(info.pt.y - _rStart.y) > 8))
{
_rDrawing = true;
dispatcher?.BeginInvoke(() => DrawBoxOverlay.Begin());
}
if (_rDrawing && !_updatePosted)
{
_updatePosted = true;
dispatcher?.BeginInvoke(() => { _updatePosted = false; DrawBoxOverlay.Update(PixelRect(_rStart, _rCurrent)); });
}
break;
case WM_RBUTTONUP when _rPending && !injected:
{
_rPending = false;
var rect = PixelRect(_rStart, info.pt);
if (_rDrawing) dispatcher?.BeginInvoke(() => DrawBoxOverlay.Finish(rect));
else dispatcher?.BeginInvoke(ReplayRightClick); // normales Desktop-Kontextmenü
return new IntPtr(1);
}
case WM_MOUSEWHEEL when S.PageWheel && S.Pages.Count > 1 && !injected:
{
if ((GetKeyState(VK_CONTROL) & 0x8000) != 0) break; // Strg+Rad = Icon-Größe (Windows)
long now = Environment.TickCount64;
if (now - _lastWheelTick < 350 || !IsDesktopAt(info.pt)) break;
_lastWheelTick = now;
int delta = (short)((info.mouseData >> 16) & 0xFFFF);
dispatcher?.BeginInvoke(() => FenceManager.NextPage(delta < 0 ? 1 : -1));
break;
}
}
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
private static Rect PixelRect(POINT a, POINT b) =>
new(Math.Min(a.x, b.x), Math.Min(a.y, b.y), Math.Abs(a.x - b.x), Math.Abs(a.y - b.y));
/// <summary>Liegt unter dem Punkt direkt der Desktop (kein Fenster darüber)?</summary>
private static bool IsDesktopAt(POINT pt)
{
var cls = ClassName(WindowFromPoint(pt));
return cls is "WorkerW" or "Progman" or "SHELLDLL_DefView" ||
(cls == "SysListView32" && ClassName(GetParent(WindowFromPoint(pt))) == "SHELLDLL_DefView");
}
/// <summary>Abgefangenen Rechtsklick an Windows weitergeben (wird als "eingespeist" markiert und nicht erneut abgefangen).</summary>
private static void ReplayRightClick()
{
var inputs = new[]
{
new INPUT { type = INPUT_MOUSE, mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_RIGHTDOWN } },
new INPUT { type = INPUT_MOUSE, mi = new MOUSEINPUT { dwFlags = MOUSEEVENTF_RIGHTUP } },
};
SendInput((uint)inputs.Length, inputs, Marshal.SizeOf<INPUT>());
}
private static void OnDoubleClick(POINT pt, POINT first)
{
if (!IsEmptyDesktopSpot(pt) || !IsEmptyDesktopSpot(first)) return;
// Positionen frisch lesen – der Zwischenspeicher kann ein paar Sekunden alt sein (Icon gerade verschoben)
if (IconsVisible())
{
try { DesktopIcons.RefreshCache(); } catch { return; }
if (DesktopIcons.HitsIcon(pt.x, pt.y, margin: 8) || DesktopIcons.HitsIcon(first.x, first.y, margin: 8)) return;
}
Toggle();
}
/// <summary>Liegt der Punkt auf dem Desktop – und zwar nicht auf einem Icon?</summary>
private static bool IsEmptyDesktopSpot(POINT pt)
{
var hwnd = WindowFromPoint(pt);
var cls = ClassName(hwnd);
if (cls is "WorkerW" or "Progman" or "SHELLDLL_DefView") return true; // Icons gerade ausgeblendet
if (cls != "SysListView32" || ClassName(GetParent(hwnd)) != "SHELLDLL_DefView") return false;
// Auf ein Icon geklickt? Dann ist es ein normaler Programmstart, nichts tun.
try
{
var el = AutomationElement.FromPoint(new Point(pt.x, pt.y));
for (var e = el; e is not null; e = TreeWalker.RawViewWalker.GetParent(e))
{
var type = e.Current.ControlType;
if (type == ControlType.ListItem) return false;
if (type == ControlType.List) break;
}
return true;
}
catch { return false; }
}
/// <summary>Desktop-Icons und Boxen umschalten (auch per Befehl --toggle-desktop).</summary>
public static void Toggle()
{
var view = DesktopView();
var list = view == IntPtr.Zero ? IntPtr.Zero : FindWindowEx(view, IntPtr.Zero, "SysListView32", null);
bool iconsVisible = list != IntPtr.Zero && IsWindowVisible(list);
if (view != IntPtr.Zero) SendMessage(view, WM_COMMAND, new IntPtr(0x7402), IntPtr.Zero); // "Desktopsymbole anzeigen" umschalten
FenceManager.SetHidden(iconsVisible);
DesktopTint.Refresh();
}
/// <summary>Beim Beenden: ausgeblendete Desktop-Icons wieder zeigen, damit niemand vor einem leeren Desktop sitzt.</summary>
public static void RestoreOnExit()
{
if (!FenceManager.Hidden) return;
var view = DesktopView();
var list = view == IntPtr.Zero ? IntPtr.Zero : FindWindowEx(view, IntPtr.Zero, "SysListView32", null);
if (view != IntPtr.Zero && list != IntPtr.Zero && !IsWindowVisible(list))
SendMessage(view, WM_COMMAND, new IntPtr(0x7402), IntPtr.Zero);
}
/// <summary>Das Fenster mit den Desktop-Icons (liegt je nach Zustand unter Progman oder einem WorkerW).</summary>
public static IntPtr DesktopView()
{
var progman = FindWindow("Progman", null);
var view = FindWindowEx(progman, IntPtr.Zero, "SHELLDLL_DefView", null);
if (view != IntPtr.Zero) return view;
IntPtr worker = IntPtr.Zero;
while ((worker = FindWindowEx(IntPtr.Zero, worker, "WorkerW", null)) != IntPtr.Zero)
{
view = FindWindowEx(worker, IntPtr.Zero, "SHELLDLL_DefView", null);
if (view != IntPtr.Zero) return view;
}
return IntPtr.Zero;
}
/// <summary>Sind die Desktop-Icons gerade sichtbar?</summary>
public static bool IconsVisible()
{
var view = DesktopView();
var list = view == IntPtr.Zero ? IntPtr.Zero : FindWindowEx(view, IntPtr.Zero, "SysListView32", null);
return list != IntPtr.Zero && IsWindowVisible(list);
}
private static string ClassName(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero) return "";
var sb = new StringBuilder(64);
GetClassName(hwnd, sb, sb.Capacity);
return sb.ToString();
}
private const int WH_MOUSE_LL = 14, WM_COMMAND = 0x0111, VK_CONTROL = 0x11;
private const int WM_MOUSEMOVE = 0x0200, WM_LBUTTONDOWN = 0x0201, WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205, WM_MOUSEWHEEL = 0x020A;
private const uint LLMHF_INJECTED = 0x1, INPUT_MOUSE = 0, MOUSEEVENTF_RIGHTDOWN = 0x0008, MOUSEEVENTF_RIGHTUP = 0x0010;
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int x, y; }
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData, flags, time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct MOUSEINPUT
{
public int dx, dy;
public uint mouseData, dwFlags, time;
public IntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Explicit)]
private struct INPUT
{
[FieldOffset(0)] public uint type;
[FieldOffset(8)] public MOUSEINPUT mi; // 64-Bit: Union beginnt bei Offset 8
}
[DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll")] private static extern uint GetDoubleClickTime();
[DllImport("user32.dll")] private static extern short GetKeyState(int vk);
[DllImport("user32.dll")] private static extern uint SendInput(uint n, INPUT[] inputs, int size);
[DllImport("user32.dll")] private static extern IntPtr WindowFromPoint(POINT p);
[DllImport("user32.dll")] private static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern int GetClassName(IntPtr hWnd, StringBuilder name, int max);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindow(string? cls, string? title);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr after, string? cls, string? title);
[DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
}
+2 -2
View File
@@ -1,7 +1,7 @@
<Project>
<!-- Eine Versionsnummer für App und Installer – nur hier ändern -->
<!-- Eine Versionsnummer für App und Installer – nur hier ändern -->
<PropertyGroup>
<Version>1.0.1</Version>
<Version>1.0.4</Version>
<!-- keine Git-Kennung an die Produktversion anhängen (sonst "1.0.1+a1b2c3…") -->
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
</PropertyGroup>
+403
View File
@@ -0,0 +1,403 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>Verwaltet alle Desktop-Boxen: Anzeigen, Peek, Ausblenden, Snapshots und Anordnung je Bildschirm-Setup.</summary>
public static class FenceManager
{
private static readonly List<FenceWindow> _open = [];
private static AppSettings S => AppSettings.Current;
private static DispatcherTimer? _peekWatch;
private static string? _screenSignature;
/// <summary>Boxen gerade per "Schnell verstecken" ausgeblendet?</summary>
public static bool Hidden { get; private set; }
/// <summary>Boxen gerade per Peek vor allen Fenstern?</summary>
public static bool Peeking { get; private set; }
public static void Sync()
{
_screenSignature ??= ScreenSignature();
// Boxen auf nicht (mehr) vorhandenen Seiten landen auf der letzten Seite
foreach (var f in S.Fences) f.Page = Math.Clamp(f.Page, 0, S.Pages.Count - 1);
var wanted = S.FencesEnabled && !Hidden
? S.Fences.Where(f => f.Tabs.Count > 0 && f.Page == S.CurrentPage).ToList()
: [];
foreach (var w in _open.Where(w => !wanted.Contains(w.Def)).ToList())
{
w.Close();
_open.Remove(w);
}
var wa = Screens.PrimaryWorkArea;
foreach (var def in wanted)
{
var w = _open.FirstOrDefault(x => x.Def == def);
if (w is not null) { w.Refresh(); continue; }
if (def.X is null)
{
// neue Box: oben rechts beginnen – links liegen meist die Desktop-Icons
int perRow = Math.Max(1, (int)((wa.Width - 48) / (def.W + 16)));
int i = _open.Count;
def.X = wa.Right - 24 - def.W - (i % perRow) * (def.W + 16);
def.Y = wa.Top + 24 + (i / perRow) * (def.H + 16);
}
w = new FenceWindow(def);
_open.Add(w);
w.Show();
}
}
public static void RefreshAll()
{
foreach (var w in _open) w.Refresh();
}
public static void CloseAll()
{
foreach (var w in _open) w.Close();
_open.Clear();
}
public static void Remove(FenceDef def)
{
S.Fences.Remove(def);
S.Save();
Sync();
App.Win?.RefreshFromFences();
}
/// <summary>
/// Kategorie aus den anderen Boxen derselben Seite entfernen – pro Seite steht eine Kategorie nur in einer Box.
/// Auf anderen Seiten darf sie zusätzlich liegen (z. B. „Internet“ auf „Arbeit“ und auf „Freizeit“).
/// </summary>
public static void RemoveCategoryTab(string category, FenceDef? except, int page)
{
foreach (var f in S.Fences.Where(f => f != except && f.Page == page))
f.Tabs.RemoveAll(t => t.IsCategory && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
S.Fences.RemoveAll(f => f != except && f.Tabs.Count == 0);
}
/// <summary>Nach Verschieben/Zusammenlegen: doppelte Kategorien auf einer Seite auflösen – „keep“ behält seine.</summary>
private static void ResolveDuplicates(int page, FenceDef? keep)
{
if (keep is not null)
foreach (var t in keep.Tabs.Where(t => t.IsCategory).ToList())
RemoveCategoryTab(t.Value, keep, page);
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var f in S.Fences.Where(f => f.Page == page))
f.Tabs.RemoveAll(t => t.IsCategory && !seen.Add(t.Value));
S.Fences.RemoveAll(f => f.Tabs.Count == 0);
foreach (var f in S.Fences) f.ActiveTab = Math.Clamp(f.ActiveTab, 0, Math.Max(0, f.Tabs.Count - 1));
}
/// <summary>Tab als eigene Box daneben lösen.</summary>
public static void DetachTab(FenceDef def, int index)
{
if (index < 0 || index >= def.Tabs.Count || def.Tabs.Count < 2) return;
var tab = def.Tabs[index];
def.Tabs.RemoveAt(index);
def.ActiveTab = Math.Clamp(def.ActiveTab, 0, def.Tabs.Count - 1);
S.Fences.Add(new FenceDef
{
Page = def.Page, Tabs = [tab], X = (def.X ?? 0) + 40, Y = (def.Y ?? 0) + 40, W = def.W, H = def.H,
Background = def.Background, Opacity = def.Opacity, Blur = def.Blur, IconSize = def.IconSize, Sort = def.Sort, Tint = def.Tint,
});
S.Save();
Sync();
}
public static string? PickFolder()
{
var dlg = new Microsoft.Win32.OpenFolderDialog { Title = "Ordner für das Portal wählen" };
return dlg.ShowDialog() == true ? dlg.FolderName : null;
}
/// <summary>Neue Box, die live einen beliebigen Ordner zeigt (z. B. Downloads).</summary>
public static void AddPortal()
{
if (PickFolder() is not { } folder) return;
S.Fences.Add(new FenceDef { Page = S.CurrentPage, Tabs = [new FenceTab { Kind = FenceTab.FolderKind, Value = folder }] });
S.FencesEnabled = true;
S.Save();
Sync();
}
/// <summary>Flächen der sichtbaren Boxen (WPF-Einheiten), z. B. damit die Icon-Tönung sie ausspart.</summary>
public static List<Rect> OpenRects() =>
_open.Where(w => w.IsVisible).Select(w => new Rect(w.Left, w.Top, w.ActualWidth, w.ActualHeight)).ToList();
/// <summary>Nach dem Aufziehen: Auswahl "Neue Box hier" an der Mausposition.</summary>
public static void ShowCreateMenu(Rect area)
{
BoxPicker.Show(area, tab =>
{
if (tab.IsCategory) RemoveCategoryTab(tab.Value, except: null, S.CurrentPage);
S.Fences.Add(new FenceDef
{
Page = S.CurrentPage, Tabs = [tab],
X = area.X, Y = area.Y, W = Math.Max(180, area.Width), H = Math.Max(120, area.Height),
});
S.FencesEnabled = true;
S.Save();
Sync();
App.Win?.RefreshFromFences();
});
}
// =====================================================================
// Desktop-Seiten
// =====================================================================
/// <summary>Zur nächsten/vorigen Seite wechseln (rundum).</summary>
public static void NextPage(int delta)
{
if (S.Pages.Count < 2) return;
SwitchPage((S.CurrentPage + delta + S.Pages.Count) % S.Pages.Count);
}
public static void SwitchPage(int index)
{
index = Math.Clamp(index, 0, S.Pages.Count - 1);
if (Peeking) EndPeek();
bool changed = index != S.CurrentPage;
S.CurrentPage = index;
S.Save();
if (changed) Sync();
PageScheduler.ApplyWallpaper(index);
PageIndicator.Show(S.Pages[index], index, S.Pages.Count);
App.Win?.RefreshFromFences();
}
public static void MoveToPage(FenceDef def, int page)
{
def.Page = Math.Clamp(page, 0, S.Pages.Count - 1);
ResolveDuplicates(def.Page, keep: def);
S.Save();
Sync();
App.Win?.RefreshFromFences();
}
/// <summary>Box mit gleichem Inhalt und Aussehen zusätzlich auf eine andere Seite legen.</summary>
public static void CopyToPage(FenceDef def, int page)
{
var copy = AppSettings.CloneFences([def])[0];
copy.Id = Guid.NewGuid().ToString("N");
copy.Page = Math.Clamp(page, 0, S.Pages.Count - 1);
S.Fences.Add(copy);
ResolveDuplicates(copy.Page, keep: copy);
S.Save();
Sync();
App.Win?.RefreshFromFences();
App.TrayIcon?.Notify("Box kopiert", $"Die Box liegt jetzt auch auf „{S.Pages[copy.Page]}“ – an derselben Stelle.");
}
/// <summary>Seite löschen: ihre Boxen wandern auf die Seite davor.</summary>
public static void DeletePage(int index)
{
if (S.Pages.Count < 2 || index < 0 || index >= S.Pages.Count) return;
S.Pages.RemoveAt(index);
foreach (var f in S.Fences)
if (f.Page == index) f.Page = Math.Max(0, index - 1);
else if (f.Page > index) f.Page--;
// Kategorien, die es auf beiden Seiten gab, nicht doppelt zeigen
ResolveDuplicates(Math.Max(0, index - 1), keep: null);
if (S.CurrentPage >= index && S.CurrentPage > 0) S.CurrentPage--;
S.Save();
CloseAll();
Sync();
}
// =====================================================================
// Aussehen
// =====================================================================
public static void EditLook(FenceWindow w)
{
var def = w.Def;
var res = Application.Current.Resources;
var before = AppSettings.CloneFences([def])[0];
var panel = new StackPanel();
TextBlock Label(string t, double top = 16) => new() { Text = t, Margin = new Thickness(0, top, 0, 8), FontWeight = FontWeights.SemiBold, FontSize = 13 };
// Hintergrund
panel.Children.Add(Label("Hintergrund", 0));
var bgPanel = new WrapPanel();
void AddSwatch(WrapPanel p, string group, string? hex, Brush brush, string tip, Func<string?> get, Action<string?> set)
{
var rb = new RadioButton { Style = (Style)res["SwatchRadio"], GroupName = group, Background = brush, ToolTip = tip, IsChecked = get() == hex };
rb.Checked += (_, _) => { set(hex); w.ApplyLook(); };
p.Children.Add(rb);
}
AddSwatch(bgPanel, "bg", null, (Brush)res["FenceBrush"], "Passend zum Design", () => def.Background, v => def.Background = v);
foreach (var (name, hex) in CategoryColors.All.Concat([("Schwarz", "#101010"), ("Weiß", "#F5F5F5")]))
AddSwatch(bgPanel, "bg", hex, CategoryColors.Brush(hex)!, name, () => def.Background, v => def.Background = v);
panel.Children.Add(bgPanel);
// Deckkraft
panel.Children.Add(Label("Deckkraft"));
var opacity = new Slider { Minimum = 20, Maximum = 100, Value = Math.Round(def.Opacity * 100), TickFrequency = 5, IsSnapToTickEnabled = true };
opacity.ValueChanged += (_, _) => { def.Opacity = opacity.Value / 100; w.ApplyLook(); };
panel.Children.Add(opacity);
var blur = new CheckBox { Content = "Milchglas (Hintergrund verschwommen)", IsChecked = def.Blur, Margin = new Thickness(0, 12, 0, 0) };
blur.Click += (_, _) => { def.Blur = blur.IsChecked == true; w.ApplyLook(); };
panel.Children.Add(blur);
// Icon-Größe und Sortierung als Segmente
StackPanel Segments(string group, (string Label, string Value)[] options, string current, Action<string> set)
{
var sp = new StackPanel { Orientation = Orientation.Horizontal };
foreach (var (label, value) in options)
{
var rb = new RadioButton { Style = (Style)res["SegmentRadio"], GroupName = group, Content = label, IsChecked = value == current };
rb.Checked += (_, _) => { set(value); w.Refresh(); };
sp.Children.Add(rb);
}
return sp;
}
panel.Children.Add(Label("Icon-Größe"));
panel.Children.Add(new Border
{
Style = (Style)res["SegmentGroup"], HorizontalAlignment = HorizontalAlignment.Left,
Child = Segments("size", [("Klein", "Small"), ("Mittel", "Medium"), ("Groß", "Large")], def.IconSize.ToString(),
v => def.IconSize = Enum.Parse<ViewMode>(v)),
});
panel.Children.Add(Label("Sortieren nach"));
panel.Children.Add(new TextBlock
{
Text = "„Eigene“: Icons in der Box einfach an die gewünschte Stelle ziehen.", FontSize = 12, Margin = new Thickness(0, -4, 0, 8),
TextWrapping = TextWrapping.Wrap, Foreground = (Brush)res["TextSecondaryBrush"],
});
panel.Children.Add(new Border
{
Style = (Style)res["SegmentGroup"], HorizontalAlignment = HorizontalAlignment.Left,
Child = Segments("sort", [("Name", "Name"), ("Typ", "Typ"), ("Datum", "Datum"), ("Meistgenutzt", "Häufig"), ("Eigene", "Eigene")],
def.Sort, v => def.Sort = v),
});
// Icon-Tönung
panel.Children.Add(Label("Icon-Tönung"));
var tintPanel = new WrapPanel();
AddSwatch(tintPanel, "tint", null, Brushes.Transparent, "Keine Tönung", () => def.Tint, v => def.Tint = v);
foreach (var (name, hex) in CategoryColors.All)
AddSwatch(tintPanel, "tint", hex, CategoryColors.Brush(hex)!, name, () => def.Tint, v => def.Tint = v);
panel.Children.Add(tintPanel);
var owner = App.Win!;
if (Dialog.Show(owner, "Aussehen der Box", panel, "Speichern", "Abbrechen", null, width: 470))
S.Save();
else
{
// Vorschau zurücknehmen
(def.Background, def.Opacity, def.Blur, def.IconSize, def.Sort, def.Tint) =
(before.Background, before.Opacity, before.Blur, before.IconSize, before.Sort, before.Tint);
w.Refresh();
}
}
// =====================================================================
// Peek & Schnell verstecken
// =====================================================================
public static void TogglePeek()
{
if (Peeking) EndPeek();
else StartPeek();
}
private static void StartPeek()
{
if (_open.Count == 0) return;
if (Hidden) SetHidden(false);
Peeking = true;
foreach (var w in _open) w.SetPeek(true);
_open[0].Activate();
// Peek endet, sobald ein anderes Fenster aktiv wird
_peekWatch ??= new DispatcherTimer(TimeSpan.FromMilliseconds(300), DispatcherPriority.Background, (_, _) =>
{
if (!_open.Any(w => w.IsActive) && !_open.Any(w => w.IsMouseOver)) EndPeek();
}, Application.Current.Dispatcher);
_peekWatch.Start();
}
public static void EndPeek()
{
_peekWatch?.Stop();
if (!Peeking) return;
Peeking = false;
foreach (var w in _open) w.SetPeek(false);
}
/// <summary>Alle Boxen aus- bzw. wieder einblenden (Schnell verstecken).</summary>
public static void SetHidden(bool hidden)
{
if (Hidden == hidden) return;
Hidden = hidden;
if (hidden) EndPeek();
Sync();
}
// =====================================================================
// Bildschirme & Snapshots
// =====================================================================
/// <summary>Kennung der aktuellen Bildschirm-Anordnung (Anzahl, Größen, Positionen).</summary>
private static string ScreenSignature() =>
string.Join("|", System.Windows.Forms.Screen.AllScreens
.OrderBy(s => s.Bounds.X).ThenBy(s => s.Bounds.Y)
.Select(s => $"{s.Bounds.X},{s.Bounds.Y},{s.Bounds.Width}x{s.Bounds.Height}"));
private static Dictionary<string, double[]> CurrentPositions() =>
S.Fences.Where(f => f.X is not null).ToDictionary(f => f.Id, f => new[] { f.X!.Value, f.Y!.Value, f.W, f.H });
/// <summary>
/// Bildschirm angesteckt/abgesteckt oder Auflösung geändert: Anordnung der alten Konstellation merken
/// und – falls bekannt – die zur neuen Konstellation passende wiederherstellen.
/// </summary>
public static void OnDisplayChanged()
{
var newSig = ScreenSignature();
if (_screenSignature is { } oldSig && oldSig != newSig)
{
S.ScreenLayouts[oldSig] = CurrentPositions();
if (S.ScreenLayouts.TryGetValue(newSig, out var layout))
{
foreach (var f in S.Fences)
if (layout.TryGetValue(f.Id, out var r) && r.Length == 4)
(f.X, f.Y, f.W, f.H) = (r[0], r[1], r[2], r[3]);
foreach (var w in _open) w.Reapply();
}
S.Save();
}
_screenSignature = newSig;
foreach (var w in _open) w.EnsureOnScreen();
}
public static void SaveSnapshot(string name)
{
S.Snapshots.RemoveAll(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase));
S.Snapshots.Add(new FenceSnapshot { Name = name, Fences = AppSettings.CloneFences(S.Fences) });
S.Save();
}
/// <summary>Anordnung wiederherstellen. Gibt die Anordnung davor zurück (für Rückgängig).</summary>
public static List<FenceDef> RestoreSnapshot(FenceSnapshot snap) => ApplyFences(AppSettings.CloneFences(snap.Fences));
public static List<FenceDef> ApplyFences(List<FenceDef> fences)
{
var before = AppSettings.CloneFences(S.Fences);
CloseAll();
S.Fences = fences;
S.FencesEnabled = true;
S.Save();
Sync();
return before;
}
}
+43 -14
View File
@@ -1,29 +1,37 @@
<Window x:Class="DeskCore.FenceWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
WindowStyle="None" AllowsTransparency="True" Background="Transparent" ResizeMode="CanResize"
ShowInTaskbar="False" ShowActivated="False" MinWidth="180" MinHeight="40"
FontFamily="{DynamicResource UiFont}" FontSize="12" Foreground="{DynamicResource TextBrush}"
TextOptions.TextFormattingMode="Display" UseLayoutRounding="True">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="36" ResizeBorderThickness="6" GlassFrameThickness="0" CornerRadius="0"/>
<WindowChrome x:Name="Chrome" CaptionHeight="36" ResizeBorderThickness="6" GlassFrameThickness="0" CornerRadius="0"/>
</WindowChrome.WindowChrome>
<Border CornerRadius="8" Background="{DynamicResource FenceBrush}" BorderBrush="{DynamicResource FlyoutStrokeBrush}"
BorderThickness="1" ClipToBounds="True">
<DockPanel>
<!-- Titel (zum Verschieben ziehen, Doppelklick = ein-/ausklappen) -->
<Grid DockPanel.Dock="Top" Height="36" Background="Transparent" x:Name="TitleBar">
<Window.Resources>
<!-- Kachelgrößen je Box (im Code je nach Einstellung gesetzt) -->
<sys:Double x:Key="FTileW">78</sys:Double>
<sys:Double x:Key="FTileH">80</sys:Double>
<sys:Double x:Key="FIcon">32</sys:Double>
</Window.Resources>
<Border x:Name="Root" CornerRadius="8" BorderBrush="{DynamicResource FlyoutStrokeBrush}" BorderThickness="1" ClipToBounds="True">
<DockPanel x:Name="Content_">
<!-- Titel / Tabs (zum Verschieben ziehen, Doppelklick = ein-/ausklappen) -->
<Grid DockPanel.Dock="Top" Height="36" Background="Transparent" x:Name="TitleBar" MouseLeftButtonDown="TitleBar_MouseLeftButtonDown">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="GlyphText" FontFamily="{DynamicResource IconFont}" FontSize="14" VerticalAlignment="Center" Margin="12,0,10,0"/>
<TextBlock x:Name="TitleText" Grid.Column="1" FontSize="13" FontWeight="SemiBold" VerticalAlignment="Center"
TextTrimming="CharacterEllipsis"/>
<StackPanel x:Name="TabStrip" Orientation="Horizontal" Margin="6,0,0,0" VerticalAlignment="Center" ClipToBounds="True"/>
<TextBlock x:Name="LockGlyph" Grid.Column="1" Text="&#xE72E;" FontFamily="{DynamicResource IconFont}" FontSize="11"
Foreground="{DynamicResource TextTertiaryBrush}" VerticalAlignment="Center" Margin="6,0,0,0"
Visibility="Collapsed" ToolTip="Box ist gesperrt"/>
<TextBlock x:Name="CountText" Grid.Column="2" Foreground="{DynamicResource TextTertiaryBrush}" VerticalAlignment="Center" Margin="8,0"/>
<Button x:Name="MenuButton" Grid.Column="3" Style="{StaticResource SubtleButton}" Padding="7,5" MinHeight="0" Margin="0,0,4,0"
WindowChrome.IsHitTestVisibleInChrome="True" Click="MenuButton_Click" ToolTip="Optionen">
@@ -34,9 +42,18 @@
</Button>
</Grid>
<!-- Ordner-Portal: Unterordner-Navigation -->
<DockPanel x:Name="PortalBar" DockPanel.Dock="Top" Margin="6,0,8,4" Visibility="Collapsed">
<Button DockPanel.Dock="Left" Style="{StaticResource SubtleButton}" Padding="6,3" MinHeight="0" Click="PortalUp_Click" ToolTip="Zurück">
<TextBlock Text="&#xE72B;" FontFamily="{DynamicResource IconFont}" FontSize="11"/>
</Button>
<TextBlock x:Name="PortalPath" VerticalAlignment="Center" Margin="6,0,0,0" TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource TextSecondaryBrush}"/>
</DockPanel>
<Grid x:Name="Body">
<ListBox x:Name="Items" Padding="6,0,6,8" SelectionMode="Extended" ItemContainerStyle="{StaticResource TileItem}"
AllowDrop="True" DragOver="Items_DragOver" Drop="Items_Drop"
AllowDrop="True" DragOver="Items_DragOver" DragLeave="Items_DragLeave" Drop="Items_Drop"
MouseDoubleClick="Items_MouseDoubleClick" KeyDown="Items_KeyDown"
PreviewMouseLeftButtonDown="Items_PreviewMouseLeftButtonDown" PreviewMouseMove="Items_PreviewMouseMove"
ContextMenuOpening="Items_ContextMenuOpening">
@@ -50,8 +67,16 @@
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="78" Height="80" ToolTip="{Binding Name}">
<Image Source="{Binding Icon}" Width="32" Height="32" Margin="0,8,0,0" RenderOptions.BitmapScalingMode="HighQuality"/>
<StackPanel Width="{DynamicResource FTileW}" Height="{DynamicResource FTileH}" ToolTip="{Binding Name}">
<Grid Width="{DynamicResource FIcon}" Height="{DynamicResource FIcon}" Margin="0,8,0,0">
<Image Source="{Binding Icon}" RenderOptions.BitmapScalingMode="HighQuality"/>
<!-- Icon-Tönung: Farbfläche in Form des Icons -->
<Rectangle Fill="{DynamicResource FTint}" Opacity="0.6" IsHitTestVisible="False">
<Rectangle.OpacityMask>
<ImageBrush ImageSource="{Binding Icon}"/>
</Rectangle.OpacityMask>
</Rectangle>
</Grid>
<TextBlock Text="{Binding Name}" TextAlignment="Center" TextWrapping="Wrap" TextTrimming="CharacterEllipsis"
MaxHeight="30" LineHeight="15" Margin="4,6,4,0"/>
</StackPanel>
@@ -59,7 +84,11 @@
</ListBox.ItemTemplate>
</ListBox>
<TextBlock x:Name="EmptyText" Text="Leer – Icons hierher ziehen" HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource TextTertiaryBrush}" IsHitTestVisible="False" Margin="0,0,0,10"/>
Foreground="{DynamicResource TextTertiaryBrush}" IsHitTestVisible="False" Margin="16,0,16,10"
TextWrapping="Wrap" TextAlignment="Center"/>
<!-- Einfügestelle beim Umsortieren per Ziehen -->
<Rectangle x:Name="DropMarker" Width="3" RadiusX="1.5" RadiusY="1.5" Fill="{DynamicResource AccentBrush}"
HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False" Visibility="Collapsed"/>
</Grid>
</DockPanel>
</Border>
+732 -346
View File
File diff suppressed because it is too large Load Diff
+101
View File
@@ -0,0 +1,101 @@
using System.IO;
using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.Win32;
namespace DeskCore;
/// <summary>
/// Erkennt, ob ein Spiel aus einem Steam- oder Epic-Link (.url) noch installiert ist.
/// Ergebnis null = "weiß ich nicht" → DeskCore lässt den Link dann sicherheitshalber stehen.
/// </summary>
public static class GameLibraries
{
private static readonly Regex SteamRun = new(@"^steam://(?:rungameid|run|launch)/(\d+)", RegexOptions.IgnoreCase);
private const string EpicPrefix = "com.epicgames.launcher://apps/";
/// <summary>Ist das Spiel hinter diesem Launcher-Link noch installiert? (null = nicht feststellbar)</summary>
public static bool? IsInstalled(string url)
{
url = url.Trim();
var m = SteamRun.Match(url);
if (m.Success) return SteamAppInstalled(m.Groups[1].Value);
if (url.StartsWith(EpicPrefix, StringComparison.OrdinalIgnoreCase)) return EpicAppInstalled(url);
return null;
}
// ---------- Steam ----------
private static (DateTime When, List<string>? Libraries) _steamCache;
/// <summary>Alle Steam-Bibliotheksordner – oder null, wenn Steam fehlt bzw. eine Bibliothek gerade nicht erreichbar ist.</summary>
private static List<string>? SteamLibraries()
{
if (DateTime.UtcNow - _steamCache.When < TimeSpan.FromSeconds(30)) return _steamCache.Libraries;
List<string>? result = null;
try
{
using var k = Registry.CurrentUser.OpenSubKey(@"Software\Valve\Steam");
if (k?.GetValue("SteamPath") is string sp && sp.Length > 0)
{
var steamPath = Path.GetFullPath(sp.Replace('/', '\\'));
var libs = new List<string> { steamPath };
var vdf = Path.Combine(steamPath, "steamapps", "libraryfolders.vdf");
if (File.Exists(vdf))
{
foreach (Match lm in Regex.Matches(File.ReadAllText(vdf), "\"path\"\\s+\"([^\"]+)\""))
libs.Add(Path.GetFullPath(lm.Groups[1].Value.Replace(@"\\", @"\")));
}
libs = libs.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
// Liegt eine Bibliothek auf einem abgesteckten Laufwerk, könnte das Spiel dort sein → nichts entscheiden
result = libs.All(l => Directory.Exists(Path.Combine(l, "steamapps"))) ? libs : null;
}
}
catch { result = null; }
_steamCache = (DateTime.UtcNow, result);
return result;
}
private static bool? SteamAppInstalled(string appId)
{
var libs = SteamLibraries();
if (libs is null) return null;
return libs.Any(l => File.Exists(Path.Combine(l, "steamapps", $"appmanifest_{appId}.acf")));
}
// ---------- Epic Games ----------
private static bool? EpicAppInstalled(string url)
{
// Format: com.epicgames.launcher://apps/<namespace>%3A<katalog>%3A<AppName>?action=launch
// oder com.epicgames.launcher://apps/<AppName>?action=launch
var rest = url[EpicPrefix.Length..];
var q = rest.IndexOf('?');
if (q >= 0) rest = rest[..q];
var appName = Uri.UnescapeDataString(rest).Split(':').Last().Trim();
if (appName.Length == 0) return null;
var manifests = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"Epic", "EpicGamesLauncher", "Data", "Manifests");
if (!Directory.Exists(manifests)) return null; // Epic-Launcher nicht (mehr) vorhanden → nicht entscheiden
try
{
foreach (var f in Directory.EnumerateFiles(manifests, "*.item"))
{
using var doc = JsonDocument.Parse(File.ReadAllText(f));
var root = doc.RootElement;
if (!root.TryGetProperty("AppName", out var an) ||
!string.Equals(an.GetString(), appName, StringComparison.OrdinalIgnoreCase)) continue;
// Installationsort auf abgestecktem Laufwerk? → nicht entscheiden
if (root.TryGetProperty("InstallLocation", out var loc) && loc.GetString() is { Length: > 0 } dir)
{
var drive = new DriveInfo(Path.GetPathRoot(dir)!);
if (!drive.IsReady) return null;
}
return true;
}
return false;
}
catch { return null; }
}
}
+140 -138
View File
@@ -1,138 +1,140 @@
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Interop;
using Microsoft.Win32;
namespace DeskCore;
/// <summary>Systemweites Tastenkürzel (RegisterHotKey).</summary>
public sealed class Hotkey : IDisposable
{
public const int ModAlt = 0x1, ModCtrl = 0x2, ModShift = 0x4, ModWin = 0x8;
private const int ModNoRepeat = 0x4000, WmHotkey = 0x0312, Id = 0xB001;
private readonly IntPtr _hwnd;
private readonly HwndSource _source;
private bool _registered;
public event Action? Pressed;
public Hotkey(IntPtr hwnd)
{
_hwnd = hwnd;
_source = HwndSource.FromHwnd(hwnd);
_source.AddHook(Hook);
}
/// <summary>Registriert das Kürzel. false = schon von einem anderen Programm belegt.</summary>
public bool Register(int modifiers, int vk)
{
Unregister();
if (vk == 0) return false;
_registered = RegisterHotKey(_hwnd, Id, modifiers | ModNoRepeat, vk);
return _registered;
}
public void Unregister()
{
if (_registered) UnregisterHotKey(_hwnd, Id);
_registered = false;
}
private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WmHotkey && wParam.ToInt32() == Id)
{
Pressed?.Invoke();
handled = true;
}
return IntPtr.Zero;
}
public static string Describe(int modifiers, int vk)
{
if (vk == 0) return "–";
var parts = new List<string>();
if ((modifiers & ModWin) != 0) parts.Add("Win");
if ((modifiers & ModCtrl) != 0) parts.Add("Strg");
if ((modifiers & ModAlt) != 0) parts.Add("Alt");
if ((modifiers & ModShift) != 0) parts.Add("Umschalt");
var key = KeyInterop.KeyFromVirtualKey(vk);
parts.Add(key switch
{
>= Key.D0 and <= Key.D9 => ((char)('0' + (key - Key.D0))).ToString(),
Key.Space => "Leertaste",
_ => key.ToString(),
});
return string.Join(" + ", parts);
}
public void Dispose()
{
Unregister();
_source.RemoveHook(Hook);
}
[DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
/// <summary>Start mit Windows über HKCU\...\Run.</summary>
public static class Autostart
{
private const string Key = @"Software\Microsoft\Windows\CurrentVersion\Run";
private const string Name = "DeskCore";
// Frühere Namen dieser App. "DeskHub" heißt auch ein anderes Programm → nur übernehmen, wenn der Eintrag eindeutig von uns ist.
private static readonly string[] OldNames = ["DesktopSortierer", "DeskHub"];
public static bool Enabled
{
get
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(Key);
return k?.GetValue(Name) is string;
}
catch { return false; }
}
set
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(Key, writable: true) ?? Registry.CurrentUser.CreateSubKey(Key);
if (value) k.SetValue(Name, $"\"{Environment.ProcessPath}\" --minimized");
else k.DeleteValue(Name, throwOnMissingValue: false);
}
catch { }
}
}
/// <summary>Alten Autostart-Eintrag ("DesktopSortierer") auf den neuen Namen umstellen.</summary>
public static void Migrate()
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(Key, writable: true);
if (k is null) return;
bool wasEnabled = false;
foreach (var old in OldNames)
{
if (k.GetValue(old) is not string cmd) continue;
bool ours = old == "DesktopSortierer" ||
(cmd.Contains($"{old}.exe", StringComparison.OrdinalIgnoreCase) && cmd.Contains("--minimized"));
if (!ours) continue;
k.DeleteValue(old, throwOnMissingValue: false);
wasEnabled = true;
}
if (wasEnabled) Enabled = true;
}
catch { }
}
/// <summary>Falls die exe verschoben wurde: Pfad im Autostart aktualisieren.</summary>
public static void RefreshPath()
{
if (Enabled) Enabled = true;
}
}
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Windows.Interop;
using Microsoft.Win32;
namespace DeskCore;
/// <summary>Systemweites Tastenkürzel (RegisterHotKey).</summary>
public sealed class Hotkey : IDisposable
{
public const int ModAlt = 0x1, ModCtrl = 0x2, ModShift = 0x4, ModWin = 0x8;
private const int ModNoRepeat = 0x4000, WmHotkey = 0x0312;
private readonly int Id;
private readonly IntPtr _hwnd;
private readonly HwndSource _source;
private bool _registered;
public event Action? Pressed;
public Hotkey(IntPtr hwnd, int id = 0xB001)
{
Id = id;
_hwnd = hwnd;
_source = HwndSource.FromHwnd(hwnd);
_source.AddHook(Hook);
}
/// <summary>Registriert das Kürzel. false = schon von einem anderen Programm belegt.</summary>
public bool Register(int modifiers, int vk)
{
Unregister();
if (vk == 0) return false;
_registered = RegisterHotKey(_hwnd, Id, modifiers | ModNoRepeat, vk);
return _registered;
}
public void Unregister()
{
if (_registered) UnregisterHotKey(_hwnd, Id);
_registered = false;
}
private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WmHotkey && wParam.ToInt32() == Id)
{
Pressed?.Invoke();
handled = true;
}
return IntPtr.Zero;
}
public static string Describe(int modifiers, int vk)
{
if (vk == 0) return "–";
var parts = new List<string>();
if ((modifiers & ModWin) != 0) parts.Add("Win");
if ((modifiers & ModCtrl) != 0) parts.Add("Strg");
if ((modifiers & ModAlt) != 0) parts.Add("Alt");
if ((modifiers & ModShift) != 0) parts.Add("Umschalt");
var key = KeyInterop.KeyFromVirtualKey(vk);
parts.Add(key switch
{
>= Key.D0 and <= Key.D9 => ((char)('0' + (key - Key.D0))).ToString(),
Key.Space => "Leertaste",
_ => key.ToString(),
});
return string.Join(" + ", parts);
}
public void Dispose()
{
Unregister();
_source.RemoveHook(Hook);
}
[DllImport("user32.dll")] private static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vk);
[DllImport("user32.dll")] private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
}
/// <summary>Start mit Windows über HKCU\...\Run.</summary>
public static class Autostart
{
private const string Key = @"Software\Microsoft\Windows\CurrentVersion\Run";
private const string Name = "DeskCore";
// Frühere Namen dieser App. "DeskHub" heißt auch ein anderes Programm → nur übernehmen, wenn der Eintrag eindeutig von uns ist.
private static readonly string[] OldNames = ["DesktopSortierer", "DeskHub"];
public static bool Enabled
{
get
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(Key);
return k?.GetValue(Name) is string;
}
catch { return false; }
}
set
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(Key, writable: true) ?? Registry.CurrentUser.CreateSubKey(Key);
if (value) k.SetValue(Name, $"\"{Environment.ProcessPath}\" --minimized");
else k.DeleteValue(Name, throwOnMissingValue: false);
}
catch { }
}
}
/// <summary>Alten Autostart-Eintrag ("DesktopSortierer") auf den neuen Namen umstellen.</summary>
public static void Migrate()
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(Key, writable: true);
if (k is null) return;
bool wasEnabled = false;
foreach (var old in OldNames)
{
if (k.GetValue(old) is not string cmd) continue;
bool ours = old == "DesktopSortierer" ||
(cmd.Contains($"{old}.exe", StringComparison.OrdinalIgnoreCase) && cmd.Contains("--minimized"));
if (!ours) continue;
k.DeleteValue(old, throwOnMissingValue: false);
wasEnabled = true;
}
if (wasEnabled) Enabled = true;
}
catch { }
}
/// <summary>Falls die exe verschoben wurde: Pfad im Autostart aktualisieren.</summary>
public static void RefreshPath()
{
if (Enabled) Enabled = true;
}
}
+114
View File
@@ -0,0 +1,114 @@
using System.IO;
using System.Windows;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>Verknüpfungen zu deinstallierten Programmen automatisch aus den Kategorien entfernen.</summary>
public partial class MainWindow
{
private DispatcherTimer? _deadScanTimer;
private DispatcherTimer? _deadScanPeriodic;
private bool _deadScanRunning;
private DispatcherTimer? _deadConfirmTimer;
private readonly Dictionary<string, DateTime> _deadSince = new(StringComparer.OrdinalIgnoreCase);
private static readonly TimeSpan ConfirmAfter = TimeSpan.FromSeconds(60);
/// <summary>Zweite Prüfung kurz nach Ablauf der Wartezeit.</summary>
private void ScheduleConfirmScan()
{
if (_deadConfirmTimer is null)
{
_deadConfirmTimer = new DispatcherTimer { Interval = ConfirmAfter + TimeSpan.FromSeconds(5) };
_deadConfirmTimer.Tick += (_, _) => { _deadConfirmTimer.Stop(); CleanupDeadShortcuts(); };
}
if (!_deadConfirmTimer.IsEnabled) _deadConfirmTimer.Start();
}
/// <summary>Einmal beim Start einrichten: Prüfung kurz nach dem Start und danach alle 10 Minuten.</summary>
public void StartDeadShortcutWatch()
{
_deadScanTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(4) };
_deadScanTimer.Tick += (_, _) => { _deadScanTimer.Stop(); CleanupDeadShortcuts(); };
// Deinstallationen über die Windows-Einstellungen ändern unsere Ordner nicht → zusätzlich regelmäßig prüfen
_deadScanPeriodic = new DispatcherTimer { Interval = TimeSpan.FromMinutes(10) };
_deadScanPeriodic.Tick += (_, _) => CleanupDeadShortcuts();
_deadScanPeriodic.Start();
ScheduleDeadShortcutScan();
}
/// <summary>Nach Änderungen (z. B. Deinstallation entfernt die Desktop-Verknüpfung) gebündelt prüfen.</summary>
public void ScheduleDeadShortcutScan()
{
if (_deadScanTimer is null) return;
_deadScanTimer.Stop();
_deadScanTimer.Start();
}
private async void CleanupDeadShortcuts()
{
if (!S.RemoveDeadShortcuts || _deadScanRunning || IsBusy) return;
_deadScanRunning = true;
try
{
var keep = S.KeptShortcuts.ToList();
var found = await Task.Run(() => Storage.FindDeadShortcuts(keep));
// Updates ersetzen ihre Programmdatei manchmal kurz → erst entfernen, wenn sie mindestens
// eine Minute lang fehlt (zweite Prüfung), sonst würde ein laufendes Update fälschlich aufgeräumt
var now = DateTime.UtcNow;
foreach (var p in _deadSince.Keys.Where(k => !found.Contains(k, StringComparer.OrdinalIgnoreCase)).ToList())
_deadSince.Remove(p);
foreach (var p in found) _deadSince.TryAdd(p, now);
var dead = found.Where(p => now - _deadSince[p] >= ConfirmAfter).ToList();
if (dead.Count < found.Count) ScheduleConfirmScan();
if (dead.Count == 0) return;
foreach (var p in dead) _deadSince.Remove(p);
var entry = new UndoEntry(dead.Count == 1
? $"„{Storage.DisplayName(dead[0])}“ entfernen (Programm deinstalliert)"
: $"{dead.Count} Verknüpfungen deinstallierter Programme entfernen");
var removed = new List<string>();
foreach (var p in dead)
{
try
{
// Reihenfolge beim Rückgängigmachen: erst zurückholen, dann merken, dass sie bleiben soll
var path = p;
entry.Add(() =>
{
if (!S.KeptShortcuts.Contains(path, StringComparer.OrdinalIgnoreCase)) S.KeptShortcuts.Add(path);
S.Save();
return null;
});
entry.AddDelete(p, Storage.Delete(p));
removed.Add(p);
}
catch { }
}
if (removed.Count == 0) return;
Undo.Push(entry);
Refresh();
FenceManager.RefreshAll();
var text = removed.Count == 1
? $"„{Storage.DisplayName(removed[0])}“ entfernt – das Programm ist nicht mehr installiert"
: $"{removed.Count} Verknüpfungen entfernt – die Programme sind nicht mehr installiert";
if (IsVisible && IsActive) ShowToast(text, "", undoable: true);
else App.TrayIcon?.Notify("Aufgeräumt", text);
}
finally
{
_deadScanRunning = false;
}
}
private void DeadShortcutsToggle_Click(object sender, RoutedEventArgs e)
{
S.RemoveDeadShortcuts = DeadShortcutsToggle.IsChecked == true;
// beim Einschalten: früher behaltene Verknüpfungen wieder prüfen lassen
if (S.RemoveDeadShortcuts) S.KeptShortcuts.RemoveAll(p => !File.Exists(p));
S.Save();
if (S.RemoveDeadShortcuts) ScheduleDeadShortcutScan();
}
}
+249 -244
View File
@@ -1,244 +1,249 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>Fortschrittsanzeige für lange Vorgänge, Fensterposition merken und Update-Hinweis.</summary>
public partial class MainWindow
{
// =====================================================================
// Fortschritt mit Abbrechen
// =====================================================================
/// <summary>Meldet Fortschritt aus dem Hintergrund (threadsicher).</summary>
public sealed class ProgressSink
{
private long _done, _total;
public long Done => Interlocked.Read(ref _done);
public long Total => Interlocked.Read(ref _total);
public void Add(long bytes) => Interlocked.Add(ref _done, bytes);
public void SetTotal(long total) => Interlocked.Exchange(ref _total, total);
public void SetDone(long done) => Interlocked.Exchange(ref _done, done);
public volatile string? Detail;
}
private CancellationTokenSource? _busyCts;
private Task? _busyTask;
public bool IsBusy => _busyCts is not null;
/// <summary>
/// Führt einen langen Vorgang im Hintergrund aus. Das Fenster bleibt bedienbar; nach kurzer Zeit erscheint
/// eine Fortschrittsanzeige mit Abbrechen-Knopf (bei schnellen Vorgängen gar nicht erst).
/// </summary>
private async Task<T> RunWithProgress<T>(string title, string detail, Func<ProgressSink, CancellationToken, Task<T>> work)
{
var cts = new CancellationTokenSource();
_busyCts = cts;
var sink = new ProgressSink { Detail = detail };
var sw = Stopwatch.StartNew();
ProgressTitle.Text = title;
ProgressDetail.Text = detail;
ProgressStats.Text = "";
ProgressFill.Width = 0;
ProgressCancel.IsEnabled = true;
ProgressCancel.Content = "Abbrechen";
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
timer.Tick += (_, _) =>
{
if (ProgressOverlay.Visibility != Visibility.Visible && sw.ElapsedMilliseconds > 500)
ProgressOverlay.Visibility = Visibility.Visible;
if (sink.Detail is { } d) ProgressDetail.Text = d;
long done = sink.Done, total = sink.Total;
if (total > 0)
{
double frac = Math.Min(1, done / (double)total);
ProgressFill.Width = ProgressTrack.ActualWidth * frac;
ProgressStats.Text = $"{Backup.FormatSize(done)} von {Backup.FormatSize(total)}{Remaining(done, total, sw.Elapsed)}";
}
else if (done > 0) ProgressStats.Text = Backup.FormatSize(done);
};
timer.Start();
var task = Task.Run(() => work(sink, cts.Token));
_busyTask = task;
try
{
return await task;
}
finally
{
timer.Stop();
ProgressOverlay.Visibility = Visibility.Collapsed;
_busyCts = null;
_busyTask = null;
cts.Dispose();
}
}
private static string Remaining(long done, long total, TimeSpan elapsed)
{
if (done <= 0 || elapsed.TotalSeconds < 2) return "";
var rest = TimeSpan.FromSeconds((total - done) / (done / elapsed.TotalSeconds));
return rest.TotalSeconds < 5 ? " · gleich fertig"
: rest.TotalMinutes < 1 ? $" · noch ca. {Math.Ceiling(rest.TotalSeconds / 5) * 5:0} Sek."
: rest.TotalHours < 1 ? $" · noch ca. {Math.Ceiling(rest.TotalMinutes):0} Min."
: $" · noch ca. {rest.TotalHours:0.#} Std.";
}
private void ProgressCancel_Click(object sender, RoutedEventArgs e)
{
_busyCts?.Cancel();
ProgressCancel.IsEnabled = false;
ProgressCancel.Content = "Wird abgebrochen …";
}
/// <summary>Beim Beenden: laufenden Vorgang abbrechen und kurz aufs Aufräumen warten.</summary>
public void CancelBusyAndWait()
{
if (_busyCts is null) return;
_busyCts.Cancel();
try { _busyTask?.Wait(TimeSpan.FromSeconds(5)); } catch { }
}
// =====================================================================
// Fensterposition merken
// =====================================================================
private void RestorePlacement()
{
if (S.WindowLeft is double l && S.WindowTop is double t && S.WindowWidth is double w && S.WindowHeight is double h)
{
var r = new Rect(l, t, Math.Max(MinWidth, w), Math.Max(MinHeight, h));
// nur übernehmen, wenn das Fenster dort noch erreichbar ist (Monitor evtl. abgesteckt)
if (Screens.IsReachable(r))
{
WindowStartupLocation = WindowStartupLocation.Manual;
Left = r.Left;
Top = r.Top;
Width = r.Width;
Height = r.Height;
}
}
if (S.WindowMaximized) WindowState = WindowState.Maximized;
}
public void SavePlacement()
{
if (!IsLoaded) return; // nie angezeigt (Start im Infobereich) → nichts zu merken
var r = WindowState == WindowState.Normal ? new Rect(Left, Top, ActualWidth, ActualHeight) : RestoreBounds;
if (r.IsEmpty || r.Width < 100) return;
S.WindowLeft = r.Left;
S.WindowTop = r.Top;
S.WindowWidth = r.Width;
S.WindowHeight = r.Height;
S.WindowMaximized = WindowState == WindowState.Maximized;
S.Save();
}
/// <summary>Bildschirm abgesteckt/umgestellt: Fenster zurückholen, falls es nicht mehr erreichbar ist.</summary>
public void EnsureOnScreen()
{
if (WindowState != WindowState.Normal) return;
if (Screens.IsReachable(new Rect(Left, Top, ActualWidth, ActualHeight))) return;
var wa = Screens.PrimaryWorkArea;
Width = Math.Min(Width, wa.Width);
Height = Math.Min(Height, wa.Height);
Left = wa.Left + (wa.Width - Width) / 2;
Top = wa.Top + (wa.Height - Height) / 2;
}
// =====================================================================
// Updates
// =====================================================================
private UpdateInfo? _update;
/// <summary>Beim Start: höchstens einmal am Tag nachsehen, leise im Hintergrund.</summary>
public async void CheckForUpdatesOnStartup()
{
if (!S.AutoUpdateCheck) return;
if (S.LastUpdateCheck is { } last && DateTime.Now - last < TimeSpan.FromHours(20)) return;
await Task.Delay(TimeSpan.FromSeconds(8)); // Start nicht ausbremsen
await CheckForUpdates(manual: false);
}
private async Task CheckForUpdates(bool manual)
{
if (manual)
{
CheckUpdateButton.IsEnabled = false;
UpdateStatusText.Text = "Suche nach Updates …";
}
try
{
var info = await UpdateChecker.CheckAsync();
S.LastUpdateCheck = DateTime.Now;
S.Save();
if (info is null)
{
if (manual) UpdateStatusText.Text = $"Du hast die neueste Version ({UpdateChecker.Current}).";
return;
}
if (!manual && S.SkippedVersion == info.Version.ToString()) return;
_update = info;
UpdateText.Text = $"DeskCore {info.Version} ist verfügbar – du hast Version {UpdateChecker.Current}.";
UpdateNowButton.Content = info.SetupUrl is not null ? "Jetzt aktualisieren" : "Zur Download-Seite";
UpdateBar.Visibility = Visibility.Visible;
if (manual) UpdateStatusText.Text = $"Version {info.Version} ist verfügbar – oben auf der Startseite aktualisieren.";
else if (!IsVisible) App.TrayIcon?.Notify("Update verfügbar", $"DeskCore {info.Version} ist verfügbar. Zum Aktualisieren DeskCore öffnen.");
}
catch (Exception ex)
{
if (manual) UpdateStatusText.Text = $"Konnte nicht prüfen: {ex.Message}";
}
finally
{
if (manual) CheckUpdateButton.IsEnabled = true;
}
}
private async void CheckUpdate_Click(object sender, RoutedEventArgs e) => await CheckForUpdates(manual: true);
private void AutoUpdateToggle_Click(object sender, RoutedEventArgs e)
{
S.AutoUpdateCheck = AutoUpdateToggle.IsChecked == true;
S.Save();
}
private void UpdateLater_Click(object sender, RoutedEventArgs e) => UpdateBar.Visibility = Visibility.Collapsed;
private void UpdateSkip_Click(object sender, RoutedEventArgs e)
{
if (_update is not null)
{
S.SkippedVersion = _update.Version.ToString();
S.Save();
}
UpdateBar.Visibility = Visibility.Collapsed;
}
private async void UpdateNow_Click(object sender, RoutedEventArgs e)
{
if (_update is not { } info || IsBusy) return;
if (info.SetupUrl is null)
{
Process.Start(new ProcessStartInfo(info.PageUrl) { UseShellExecute = true });
return;
}
try
{
var setup = await RunWithProgress($"Update auf {info.Version}", "Neue Version wird heruntergeladen …",
(sink, ct) => UpdateChecker.DownloadSetupAsync(info, (done, total) => { sink.SetTotal(total); sink.SetDone(done); }, ct));
// Installer still ausführen und DeskCore danach automatisch neu starten
Process.Start(new ProcessStartInfo(setup, "/S /launch") { UseShellExecute = true });
App.Quit();
}
catch (OperationCanceledException) { ShowToast("Update abgebrochen", "", info: true); }
catch (Exception ex) { Error("Update fehlgeschlagen", $"{ex.Message}\n\nDu kannst die neue Version auch selbst herunterladen:\n{info.PageUrl}"); }
}
}
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>Fortschrittsanzeige für lange Vorgänge, Fensterposition merken und Update-Hinweis.</summary>
public partial class MainWindow
{
// =====================================================================
// Fortschritt mit Abbrechen
// =====================================================================
/// <summary>Meldet Fortschritt aus dem Hintergrund (threadsicher).</summary>
public sealed class ProgressSink
{
private long _done, _total;
public long Done => Interlocked.Read(ref _done);
public long Total => Interlocked.Read(ref _total);
public void Add(long bytes) => Interlocked.Add(ref _done, bytes);
public void SetTotal(long total) => Interlocked.Exchange(ref _total, total);
public void SetDone(long done) => Interlocked.Exchange(ref _done, done);
public volatile string? Detail;
}
private CancellationTokenSource? _busyCts;
private Task? _busyTask;
public bool IsBusy => _busyCts is not null;
/// <summary>
/// Führt einen langen Vorgang im Hintergrund aus. Das Fenster bleibt bedienbar; nach kurzer Zeit erscheint
/// eine Fortschrittsanzeige mit Abbrechen-Knopf (bei schnellen Vorgängen gar nicht erst).
/// </summary>
private async Task<T> RunWithProgress<T>(string title, string detail, Func<ProgressSink, CancellationToken, Task<T>> work)
{
var cts = new CancellationTokenSource();
_busyCts = cts;
var sink = new ProgressSink { Detail = detail };
var sw = Stopwatch.StartNew();
ProgressTitle.Text = title;
ProgressDetail.Text = detail;
ProgressStats.Text = "";
ProgressFill.Width = 0;
ProgressCancel.IsEnabled = true;
ProgressCancel.Content = "Abbrechen";
var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(200) };
timer.Tick += (_, _) =>
{
if (ProgressOverlay.Visibility != Visibility.Visible && sw.ElapsedMilliseconds > 500)
ProgressOverlay.Visibility = Visibility.Visible;
if (sink.Detail is { } d) ProgressDetail.Text = d;
long done = sink.Done, total = sink.Total;
if (total > 0)
{
double frac = Math.Min(1, done / (double)total);
ProgressFill.Width = ProgressTrack.ActualWidth * frac;
ProgressStats.Text = $"{Backup.FormatSize(done)} von {Backup.FormatSize(total)}{Remaining(done, total, sw.Elapsed)}";
}
else if (done > 0) ProgressStats.Text = Backup.FormatSize(done);
};
timer.Start();
var task = Task.Run(() => work(sink, cts.Token));
_busyTask = task;
try
{
return await task;
}
finally
{
timer.Stop();
ProgressOverlay.Visibility = Visibility.Collapsed;
_busyCts = null;
_busyTask = null;
cts.Dispose();
}
}
private static string Remaining(long done, long total, TimeSpan elapsed)
{
if (done <= 0 || elapsed.TotalSeconds < 2) return "";
var rest = TimeSpan.FromSeconds((total - done) / (done / elapsed.TotalSeconds));
return rest.TotalSeconds < 5 ? " · gleich fertig"
: rest.TotalMinutes < 1 ? $" · noch ca. {Math.Ceiling(rest.TotalSeconds / 5) * 5:0} Sek."
: rest.TotalHours < 1 ? $" · noch ca. {Math.Ceiling(rest.TotalMinutes):0} Min."
: $" · noch ca. {rest.TotalHours:0.#} Std.";
}
private void ProgressCancel_Click(object sender, RoutedEventArgs e)
{
_busyCts?.Cancel();
ProgressCancel.IsEnabled = false;
ProgressCancel.Content = "Wird abgebrochen …";
}
/// <summary>Beim Beenden: laufenden Vorgang abbrechen und kurz aufs Aufräumen warten.</summary>
public void CancelBusyAndWait()
{
if (_busyCts is null) return;
_busyCts.Cancel();
try { _busyTask?.Wait(TimeSpan.FromSeconds(5)); } catch { }
}
// =====================================================================
// Fensterposition merken
// =====================================================================
private void RestorePlacement()
{
if (S.WindowLeft is double l && S.WindowTop is double t && S.WindowWidth is double w && S.WindowHeight is double h)
{
var r = new Rect(l, t, Math.Max(MinWidth, w), Math.Max(MinHeight, h));
// nur übernehmen, wenn das Fenster dort noch erreichbar ist (Monitor evtl. abgesteckt)
if (Screens.IsReachable(r))
{
WindowStartupLocation = WindowStartupLocation.Manual;
Left = r.Left;
Top = r.Top;
Width = r.Width;
Height = r.Height;
}
}
if (S.WindowMaximized) WindowState = WindowState.Maximized;
}
public void SavePlacement()
{
if (!IsLoaded) return; // nie angezeigt (Start im Infobereich) → nichts zu merken
var r = WindowState == WindowState.Normal ? new Rect(Left, Top, ActualWidth, ActualHeight) : RestoreBounds;
if (r.IsEmpty || r.Width < 100) return;
S.WindowLeft = r.Left;
S.WindowTop = r.Top;
S.WindowWidth = r.Width;
S.WindowHeight = r.Height;
S.WindowMaximized = WindowState == WindowState.Maximized;
S.Save();
}
/// <summary>Bildschirm abgesteckt/umgestellt: Fenster zurückholen, falls es nicht mehr erreichbar ist.</summary>
public void EnsureOnScreen()
{
if (WindowState != WindowState.Normal) return;
if (Screens.IsReachable(new Rect(Left, Top, ActualWidth, ActualHeight))) return;
var wa = Screens.PrimaryWorkArea;
Width = Math.Min(Width, wa.Width);
Height = Math.Min(Height, wa.Height);
Left = wa.Left + (wa.Width - Width) / 2;
Top = wa.Top + (wa.Height - Height) / 2;
}
// =====================================================================
// Updates
// =====================================================================
private UpdateInfo? _update;
/// <summary>Beim Start: höchstens einmal am Tag nachsehen, leise im Hintergrund.</summary>
public async void CheckForUpdatesOnStartup()
{
if (!S.AutoUpdateCheck) return;
if (S.LastUpdateCheck is { } last && DateTime.Now - last < TimeSpan.FromHours(20)) return;
await Task.Delay(TimeSpan.FromSeconds(8)); // Start nicht ausbremsen
await CheckForUpdates(manual: false);
}
private async Task CheckForUpdates(bool manual)
{
if (manual)
{
CheckUpdateButton.IsEnabled = false;
UpdateStatusText.Text = "Suche nach Updates …";
}
try
{
var info = await UpdateChecker.CheckAsync();
S.LastUpdateCheck = DateTime.Now;
S.Save();
if (info is null)
{
if (manual) UpdateStatusText.Text = $"Du hast die neueste Version ({UpdateChecker.Current}).";
return;
}
if (!manual && S.SkippedVersion == info.Version.ToString()) return;
_update = info;
UpdateText.Text = $"DeskCore {info.Version} ist verfügbar – du hast Version {UpdateChecker.Current}.";
UpdateNowButton.Content = info.SetupUrl is not null ? "Jetzt aktualisieren" : "Zur Download-Seite";
UpdateBar.Visibility = Visibility.Visible;
if (manual) UpdateStatusText.Text = $"Version {info.Version} ist verfügbar – oben auf der Startseite aktualisieren.";
else if (!IsVisible) App.TrayIcon?.Notify("Update verfügbar", $"DeskCore {info.Version} ist verfügbar. Zum Aktualisieren DeskCore öffnen.");
}
catch (Exception ex)
{
if (manual) UpdateStatusText.Text = $"Konnte nicht prüfen: {ex.Message}";
}
finally
{
if (manual) CheckUpdateButton.IsEnabled = true;
}
}
private async void CheckUpdate_Click(object sender, RoutedEventArgs e) => await CheckForUpdates(manual: true);
private void AutoUpdateToggle_Click(object sender, RoutedEventArgs e)
{
S.AutoUpdateCheck = AutoUpdateToggle.IsChecked == true;
S.Save();
}
private void UpdateLater_Click(object sender, RoutedEventArgs e) => UpdateBar.Visibility = Visibility.Collapsed;
private void UpdateSkip_Click(object sender, RoutedEventArgs e)
{
if (_update is not null)
{
S.SkippedVersion = _update.Version.ToString();
S.Save();
}
UpdateBar.Visibility = Visibility.Collapsed;
}
private async void UpdateNow_Click(object sender, RoutedEventArgs e)
{
if (_update is not { } info || IsBusy) return;
if (info.SetupUrl is null)
{
Process.Start(new ProcessStartInfo(info.PageUrl) { UseShellExecute = true });
return;
}
try
{
var setup = await RunWithProgress($"Update auf {info.Version}", "Neue Version wird heruntergeladen …",
(sink, ct) => UpdateChecker.DownloadSetupAsync(info, (done, total) => { sink.SetTotal(total); sink.SetDone(done); }, ct));
// Installer still ausführen und DeskCore danach automatisch neu starten.
// Windows fragt dabei nach Administratorrechten (der Installer kümmert sich auch um die .NET-Laufzeit).
Process.Start(new ProcessStartInfo(setup, "/S /launch") { UseShellExecute = true });
App.Quit();
}
catch (OperationCanceledException) { ShowToast("Update abgebrochen", "", info: true); }
catch (System.ComponentModel.Win32Exception ex) when (ex.NativeErrorCode == 1223)
{
ShowToast("Update abgebrochen – bitte die Windows-Abfrage mit „Ja“ bestätigen", "", info: true);
}
catch (Exception ex) { Error("Update fehlgeschlagen", $"{ex.Message}\n\nDu kannst die neue Version auch selbst herunterladen:\n{info.PageUrl}"); }
}
}
+119
View File
@@ -0,0 +1,119 @@
using System.Windows;
namespace DeskCore;
/// <summary>Einstellungen der Desktop-Boxen: Portale, Schnell verstecken, Peek, Chamäleon und Snapshots.</summary>
public partial class MainWindow
{
/// <summary>Box geschlossen o. Ä. → Kennzeichnung "hat Box" in der Seitenleiste aktualisieren.</summary>
public void RefreshFromFences()
{
Refresh();
if (InSettings) { LoadFenceSettings(); LoadDesktopSettings(); }
}
private void LoadFenceSettings()
{
QuickHideToggle.IsChecked = S.QuickHide;
PeekToggle.IsChecked = S.PeekEnabled;
PeekHint.Text = $"{Hotkey.Describe(S.PeekModifiers, S.PeekKey)} holt alle Boxen vor die offenen Fenster. " +
"Nochmal drücken, Esc oder woanders hinklicken schickt sie zurück.";
ChameleonToggle.IsChecked = S.FenceChameleon;
SnapshotList.ItemsSource = null;
SnapshotList.ItemsSource = S.Snapshots.OrderByDescending(x => x.Created).ToList();
}
private void AddPortal_Click(object sender, RoutedEventArgs e)
{
int before = S.Fences.Count;
FenceManager.AddPortal();
if (S.Fences.Count > before) ShowToast("Ordner-Portal liegt jetzt auf dem Desktop", "");
LoadSettingsView();
}
private void QuickHideToggle_Click(object sender, RoutedEventArgs e)
{
S.QuickHide = QuickHideToggle.IsChecked == true;
S.Save();
DesktopToggle.Apply();
}
private void PeekToggle_Click(object sender, RoutedEventArgs e)
{
S.PeekEnabled = PeekToggle.IsChecked == true;
S.Save();
if (!App.ApplyPeekHotkey())
PeekHint.Text = "⚠ Diese Tastenkombination ist schon von einem anderen Programm belegt.";
}
private void ChameleonToggle_Click(object sender, RoutedEventArgs e)
{
S.FenceChameleon = ChameleonToggle.IsChecked == true;
S.Save();
FenceManager.RefreshAll();
}
// ---------- Snapshots ----------
private void SaveSnapshot_Click(object sender, RoutedEventArgs e)
{
if (S.Fences.Count == 0)
{
ShowToast("Es gibt noch keine Boxen zum Speichern", "", info: true);
return;
}
var name = Dialog.Prompt(this, "Anordnung speichern", "Name für diese Anordnung (z. B. „Arbeit“ oder „Zocken“):",
$"Anordnung {S.Snapshots.Count + 1}", "Speichern",
t => t.Trim().Length == 0 ? "Bitte einen Namen eingeben." : t.Contains('"') ? "Bitte ohne Anführungszeichen." : null);
if (name is null) return;
bool replace = S.Snapshots.Any(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
FenceManager.SaveSnapshot(name);
LoadFenceSettings();
ShowToast(replace ? $"Anordnung „{name}“ aktualisiert" : $"Anordnung „{name}“ gespeichert", "");
}
private void RestoreSnapshot(FenceSnapshot snap)
{
var before = FenceManager.RestoreSnapshot(snap);
var entry = new UndoEntry($"Anordnung „{snap.Name}“ laden");
entry.Add(() => { FenceManager.ApplyFences(before); return null; });
Undo.Push(entry);
Refresh();
ShowToast($"Anordnung „{snap.Name}“ wiederhergestellt", "", undoable: true);
}
private void RestoreSnapshot_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is FenceSnapshot snap) RestoreSnapshot(snap);
}
private void DeleteSnapshot_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not FenceSnapshot snap) return;
S.Snapshots.Remove(snap);
S.Save();
var entry = new UndoEntry($"Anordnung „{snap.Name}“ löschen");
entry.Add(() => { S.Snapshots.Add(snap); S.Save(); return null; });
Undo.Push(entry);
LoadFenceSettings();
ShowToast($"Anordnung „{snap.Name}“ gelöscht", "", undoable: true);
}
/// <summary>Für die Befehlszeile: DeskCore.exe --snapshot "Name".</summary>
public void RestoreSnapshotByName(string name)
{
var snap = S.Snapshots.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
if (snap is null) App.TrayIcon?.Notify("Anordnung nicht gefunden", $"Es gibt keine gespeicherte Anordnung „{name}“.");
else RestoreSnapshot(snap);
}
// ---------- Regeln: Name oder Dateityp ----------
private void RuleKind_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not SortRule r) return;
r.Kind = r.Kind == "Typ" ? "Name" : "Typ";
S.Save();
LoadSettingsView();
}
}
+415
View File
@@ -0,0 +1,415 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace DeskCore;
/// <summary>Start-Gruppen: ein Klick startet mehrere Programme nacheinander.</summary>
public partial class MainWindow
{
private LaunchGroupItem? CurrentGroup => LaunchList.SelectedItem as LaunchGroupItem;
/// <summary>Kategorie-Dialog von außen (z. B. "Neue Box hier" auf dem Desktop).</summary>
public string? CreateCategoryInteractive() => NewCategory();
private static string DelayText(LaunchGroup g) =>
g.DelaySeconds <= 0 ? "gleichzeitig" : $"{g.DelaySeconds:0.#} s Abstand";
/// <summary>Wo liegt ein Element? (für Tooltip/Listenansicht)</summary>
private static string LocationOf(string path)
{
var dir = Path.GetDirectoryName(path) ?? "";
if (Storage.IsDesktopDir(dir)) return DesktopName;
return Storage.IsInStorage(path) ? Path.GetFileName(dir) : dir;
}
private void LoadGroupIcons(LaunchGroup g)
{
var items = g.Items.Select(p => new IconItem { Path = p, Category = LocationOf(p) }).ToList();
HeaderText.Text = g.Name;
int missing = items.Count(i => !File.Exists(i.Path) && !Directory.Exists(i.Path));
SubText.Text = $"{Plural(items.Count, "Programm", "Programme")} · {DelayText(g)}" +
(missing > 0 ? $" · ⚠ {missing} nicht mehr vorhanden" : "");
SetEmpty("", "Noch leer",
"Ziehe Programme hierher oder auf die Gruppe links – oder Rechtsklick auf ein Icon → „Zur Start-Gruppe hinzufügen“.");
// Reihenfolge = Startreihenfolge, deshalb hier nicht sortieren
var px = IconPixels;
foreach (var item in items) IconLoader.Load(item, px);
IconList.ItemsSource = items;
EmptyState.Visibility = items.Count == 0 ? Visibility.Visible : Visibility.Collapsed;
CategoryCommands.Visibility = Visibility.Collapsed;
InfoBar.Visibility = Visibility.Collapsed;
GroupCommands.Visibility = Visibility.Visible;
}
private void SelectGroup(LaunchGroup g)
{
var item = LaunchList.Items.OfType<LaunchGroupItem>().FirstOrDefault(x => x.Group == g);
if (item is not null) LaunchList.SelectedItem = item;
}
// ---------- Starten ----------
/// <summary>Startet alle Programme der Gruppe nacheinander (mit eingestelltem Abstand).</summary>
public async void LaunchGroupNow(LaunchGroup g)
{
if (g.Items.Count == 0)
{
ShowToast($"„{g.Name}“ ist noch leer", "", info: true);
return;
}
Usage.Record(Usage.GroupPrefix + g.Name); // damit die Schnellstart-Suche häufige Gruppen weiter oben zeigt
int started = 0;
var problems = new List<string>();
var items = g.Items.ToList();
for (int i = 0; i < items.Count; i++)
{
if (i > 0 && g.DelaySeconds > 0) await Task.Delay(TimeSpan.FromSeconds(g.DelaySeconds));
var p = items[i];
if (!File.Exists(p) && !Directory.Exists(p)) { problems.Add($"„{Storage.DisplayName(p)}“ gibt es nicht mehr"); continue; }
try
{
Usage.Start(p);
started++;
}
catch (Exception ex) { problems.Add($"„{Storage.DisplayName(p)}“: {ex.Message}"); }
}
var text = $"„{g.Name}“: {Plural(started, "Programm", "Programme")} gestartet" +
(problems.Count > 0 ? $" · {problems.Count} nicht" : "");
if (IsVisible && IsActive) ShowToast(text, problems.Count > 0 ? "" : "", info: problems.Count > 0);
else App.TrayIcon?.Notify(g.Name, problems.Count > 0 ? text + "\n" + string.Join("\n", problems.Take(3)) : text);
}
/// <summary>Für die Befehlszeile und Desktop-Verknüpfungen: DeskCore.exe --launch "Name".</summary>
public bool LaunchGroupByName(string name)
{
var g = S.LaunchGroups.FirstOrDefault(x => string.Equals(x.Name, name, StringComparison.OrdinalIgnoreCase));
if (g is null)
{
App.TrayIcon?.Notify("Start-Gruppe nicht gefunden", $"Es gibt keine Start-Gruppe „{name}“.");
return false;
}
LaunchGroupNow(g);
return true;
}
private void LaunchCurrentGroup_Click(object sender, RoutedEventArgs e)
{
if (CurrentGroup is { } g) LaunchGroupNow(g.Group);
}
private void LaunchGroupPlay_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is LaunchGroupItem g) LaunchGroupNow(g.Group);
e.Handled = true;
}
// ---------- Anlegen / Bearbeiten ----------
/// <summary>Dialog für Name, Startabstand und Farbe. Gibt null bei Abbruch zurück.</summary>
private (string Name, double Delay, string? Color)? LaunchGroupDialog(string title, string primary, LaunchGroup? existing)
{
var res = Application.Current.Resources;
var panel = new StackPanel();
TextBlock Label(string t, double top = 16) => new()
{
Text = t, Margin = new Thickness(0, top, 0, 8), FontWeight = FontWeights.SemiBold, FontSize = 13,
};
panel.Children.Add(Label("Name", 0));
var nameBox = new TextBox { Text = existing?.Name ?? "" };
panel.Children.Add(nameBox);
panel.Children.Add(Label("Abstand zwischen den Programmen"));
var delayText = new TextBlock { VerticalAlignment = VerticalAlignment.Center, Width = 110, Foreground = (Brush)res["TextSecondaryBrush"] };
var slider = new Slider { Minimum = 0, Maximum = 10, TickFrequency = 0.5, IsSnapToTickEnabled = true, Value = existing?.DelaySeconds ?? 1 };
void UpdateDelay() => delayText.Text = slider.Value <= 0 ? "gleichzeitig" : $"{slider.Value:0.#} Sekunden";
slider.ValueChanged += (_, _) => UpdateDelay();
UpdateDelay();
var row = new DockPanel();
DockPanel.SetDock(delayText, Dock.Right);
row.Children.Add(delayText);
row.Children.Add(slider);
panel.Children.Add(row);
panel.Children.Add(new TextBlock
{
Text = "Etwas Abstand hilft, wenn Programme beim Start viel Leistung brauchen.", FontSize = 12, Margin = new Thickness(0, 4, 0, 0),
TextWrapping = TextWrapping.Wrap, Foreground = (Brush)res["TextTertiaryBrush"],
});
panel.Children.Add(Label("Farbe"));
string? color = existing?.Color;
var swatches = new WrapPanel();
var none = new RadioButton
{
Style = (Style)res["SwatchRadio"], GroupName = "lgcolor", ToolTip = "Akzentfarbe", Background = (Brush)res["AccentBrush"],
IsChecked = color is null,
};
none.Checked += (_, _) => color = null;
swatches.Children.Add(none);
foreach (var (cname, hex) in CategoryColors.All)
{
var rb = new RadioButton
{
Style = (Style)res["SwatchRadio"], GroupName = "lgcolor", ToolTip = cname, Background = CategoryColors.Brush(hex),
IsChecked = string.Equals(color, hex, StringComparison.OrdinalIgnoreCase),
};
rb.Checked += (_, _) => color = hex;
swatches.Children.Add(rb);
}
panel.Children.Add(swatches);
string? Validate()
{
var n = nameBox.Text.Trim();
if (n.Length == 0) return "Bitte einen Namen eingeben.";
if (n.Contains('"')) return "Der Name darf keine Anführungszeichen enthalten.";
bool taken = S.LaunchGroups.Any(x => x != existing && string.Equals(x.Name, n, StringComparison.OrdinalIgnoreCase));
return taken ? "Eine Start-Gruppe mit diesem Namen gibt es schon." : null;
}
if (!Dialog.Show(this, title, panel, primary, "Abbrechen", Validate, nameBox, width: 470)) return null;
return (nameBox.Text.Trim(), slider.Value, color);
}
private LaunchGroup? NewLaunchGroup(IEnumerable<string>? initialItems = null)
{
var form = LaunchGroupDialog("Neue Start-Gruppe", "Erstellen", null);
if (form is null) return null;
var g = new LaunchGroup { Name = form.Value.Name, DelaySeconds = form.Value.Delay, Color = form.Value.Color };
S.LaunchGroups.Add(g);
if (initialItems is not null) AddPathsToGroup(g, initialItems, toast: false);
S.Save();
var entry = new UndoEntry($"Start-Gruppe „{g.Name}“ erstellen");
entry.Add(() => { S.LaunchGroups.Remove(g); S.Save(); return null; });
Undo.Push(entry);
Refresh();
return g;
}
private void NewLaunchGroup_Click(object sender, RoutedEventArgs e)
{
if (NewLaunchGroup() is not { } g) return;
ShowBrowse();
SelectGroup(g);
ShowToast($"Start-Gruppe „{g.Name}“ erstellt – jetzt Programme hineinziehen", "", undoable: true);
}
private void EditLaunchGroup(LaunchGroup g)
{
var form = LaunchGroupDialog("Start-Gruppe bearbeiten", "Speichern", g);
if (form is null) return;
var before = (g.Name, g.DelaySeconds, g.Color);
(g.Name, g.DelaySeconds, g.Color) = form.Value;
Usage.Rename(Usage.GroupPrefix + before.Name, Usage.GroupPrefix + g.Name);
S.Save();
var entry = new UndoEntry($"Start-Gruppe „{before.Name}“ bearbeiten");
entry.Add(() => { (g.Name, g.DelaySeconds, g.Color) = before; S.Save(); return null; });
Undo.Push(entry);
Refresh();
if (before.Name != g.Name)
ShowToast("Gespeichert – eine vorhandene Desktop-Verknüpfung bitte neu anlegen (sie nutzt noch den alten Namen)", "", info: true);
else ShowToast("Start-Gruppe gespeichert", undoable: true);
}
private void EditLaunchGroup_Click(object sender, RoutedEventArgs e)
{
if (CurrentGroup is { } g) EditLaunchGroup(g.Group);
}
private void DeleteLaunchGroup(LaunchGroup g)
{
if (!Dialog.Confirm(this, "Start-Gruppe löschen?",
$"„{g.Name}“ wird entfernt. Die Programme selbst bleiben unverändert.", "Löschen"))
return;
int index = S.LaunchGroups.IndexOf(g);
S.LaunchGroups.Remove(g);
S.Save();
var entry = new UndoEntry($"Start-Gruppe „{g.Name}“ löschen");
entry.Add(() => { S.LaunchGroups.Insert(Math.Min(index, S.LaunchGroups.Count), g); S.Save(); return null; });
Undo.Push(entry);
Refresh();
ShowToast($"Start-Gruppe „{g.Name}“ gelöscht", "", undoable: true);
}
private void DeleteLaunchGroup_Click(object sender, RoutedEventArgs e)
{
if (CurrentGroup is { } g) DeleteLaunchGroup(g.Group);
}
private void MoveLaunchGroup(LaunchGroup g, int delta)
{
int i = S.LaunchGroups.IndexOf(g), j = i + delta;
if (i < 0 || j < 0 || j >= S.LaunchGroups.Count) return;
(S.LaunchGroups[i], S.LaunchGroups[j]) = (S.LaunchGroups[j], S.LaunchGroups[i]);
S.Save();
Refresh();
}
// ---------- Inhalt ----------
/// <summary>
/// Desktop-Icons, die schon in einer Kategorie gesichert sind, werden durch die gesicherte Kopie ersetzt –
/// so funktioniert die Gruppe auch noch, wenn das Icon später vom Desktop gelöscht wird.
/// </summary>
private static string ResolveForGroup(string path)
{
if (Storage.IsInStorage(path)) return path;
foreach (var c in Storage.Categories())
if (Storage.Entries(Storage.CategoryPath(c)).FirstOrDefault(e => Storage.SameItem(e, path)) is { } saved)
return saved;
return path;
}
private void AddPathsToGroup(LaunchGroup g, IEnumerable<string> paths, bool toast = true)
{
int added = 0, dup = 0;
foreach (var raw in paths)
{
var p = ResolveForGroup(raw);
if (g.Items.Contains(p, StringComparer.OrdinalIgnoreCase)) { dup++; continue; }
g.Items.Add(p);
added++;
}
S.Save();
if (!toast) return;
Refresh();
if (added == 0) ShowToast(dup == 1 ? "Das ist schon in der Gruppe" : "Alles schon in der Gruppe", "", info: true);
else ShowToast($"{Plural(added, "Programm", "Programme")} zu „{g.Name}“ hinzugefügt", "");
}
private void RemoveFromGroup(LaunchGroup g, List<IconItem> sel)
{
var before = g.Items.ToList();
foreach (var i in sel) g.Items.RemoveAll(p => string.Equals(p, i.Path, StringComparison.OrdinalIgnoreCase));
S.Save();
var entry = new UndoEntry($"Aus „{g.Name}“ entfernen");
entry.Add(() => { g.Items = before; S.Save(); return null; });
Undo.Push(entry);
Refresh();
ShowToast($"{Plural(sel.Count, "Programm", "Programme")} aus „{g.Name}“ entfernt – die Dateien bleiben erhalten", "", undoable: true);
}
private void MoveInGroup(LaunchGroup g, IconItem item, int delta)
{
int i = g.Items.FindIndex(p => string.Equals(p, item.Path, StringComparison.OrdinalIgnoreCase)), j = i + delta;
if (i < 0 || j < 0 || j >= g.Items.Count) return;
(g.Items[i], g.Items[j]) = (g.Items[j], g.Items[i]);
S.Save();
Refresh();
}
/// <summary>Kontextmenü der Icons, wenn eine Start-Gruppe angezeigt wird.</summary>
private void FillGroupItemMenu(LaunchGroup g, List<IconItem> sel)
{
IconMenu.Items.Add(MenuItem("Öffnen", "", () => sel.ForEach(i => Open(i.Path)), bold: true, gesture: "Enter"));
if (sel.Count == 1)
{
IconMenu.Items.Add(MenuItem("Dateispeicherort öffnen", "", () =>
Process.Start("explorer.exe", $"/select,\"{sel[0].Path}\"")));
IconMenu.Items.Add(new Separator());
int idx = g.Items.FindIndex(p => string.Equals(p, sel[0].Path, StringComparison.OrdinalIgnoreCase));
var up = MenuItem("Früher starten", "", () => MoveInGroup(g, sel[0], -1));
up.IsEnabled = idx > 0;
var down = MenuItem("Später starten", "", () => MoveInGroup(g, sel[0], +1));
down.IsEnabled = idx >= 0 && idx < g.Items.Count - 1;
IconMenu.Items.Add(up);
IconMenu.Items.Add(down);
}
IconMenu.Items.Add(new Separator());
IconMenu.Items.Add(MenuItem("Aus Start-Gruppe entfernen", "", () => RemoveFromGroup(g, sel), gesture: "Entf"));
}
/// <summary>Untermenü "Zur Start-Gruppe hinzufügen" (Haupt-Kontextmenü und Desktop-Boxen).</summary>
public MenuItem LaunchGroupSubmenu(List<string> paths)
{
var menu = MenuItem("Zur Start-Gruppe hinzufügen", "", null);
foreach (var g in S.LaunchGroups)
menu.Items.Add(MenuItem(g.Name, "", () => AddPathsToGroup(g, paths)));
if (menu.Items.Count > 0) menu.Items.Add(new Separator());
menu.Items.Add(MenuItem("Neue Start-Gruppe…", "", () =>
{
if (NewLaunchGroup(paths) is { } g)
ShowToast($"Start-Gruppe „{g.Name}“ mit {Plural(g.Items.Count, "Programm", "Programmen")} erstellt", "", undoable: true);
}));
return menu;
}
// ---------- Seitenleiste ----------
private void LaunchList_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
if (FindParent<ListBoxItem>(e.OriginalSource as DependencyObject)?.DataContext is not LaunchGroupItem item)
{
e.Handled = true;
return;
}
var g = item.Group;
int idx = S.LaunchGroups.IndexOf(g);
LaunchMenu.Items.Clear();
LaunchMenu.Items.Add(MenuItem("Alle starten", "", () => LaunchGroupNow(g), bold: true));
LaunchMenu.Items.Add(MenuItem("Bearbeiten…", "", () => EditLaunchGroup(g)));
LaunchMenu.Items.Add(MenuItem("Verknüpfung auf den Desktop", "", () => CreateGroupShortcut(g)));
LaunchMenu.Items.Add(new Separator());
var up = MenuItem("Nach oben", "", () => MoveLaunchGroup(g, -1));
up.IsEnabled = idx > 0;
var down = MenuItem("Nach unten", "", () => MoveLaunchGroup(g, +1));
down.IsEnabled = idx < S.LaunchGroups.Count - 1;
LaunchMenu.Items.Add(up);
LaunchMenu.Items.Add(down);
LaunchMenu.Items.Add(new Separator());
LaunchMenu.Items.Add(MenuItem("Löschen", "", () => DeleteLaunchGroup(g)));
}
private void LaunchList_DragOver(object sender, DragEventArgs e)
{
var lbi = FindParent<ListBoxItem>(e.OriginalSource as DependencyObject);
bool ok = lbi is not null && e.Data.GetDataPresent(DataFormats.FileDrop);
SetDropHighlight(ok ? lbi : null);
e.Effects = ok ? DragDropEffects.Link | (e.AllowedEffects & DragDropEffects.Copy) : DragDropEffects.None;
e.Handled = true;
}
private void LaunchList_Drop(object sender, DragEventArgs e)
{
SetDropHighlight(null);
if (FindParent<ListBoxItem>(e.OriginalSource as DependencyObject)?.DataContext is LaunchGroupItem item &&
e.Data.GetData(DataFormats.FileDrop) is string[] files)
AddPathsToGroup(item.Group, files);
}
// ---------- Desktop-Verknüpfung ----------
private void LaunchGroupShortcut_Click(object sender, RoutedEventArgs e)
{
if (CurrentGroup is { } g) CreateGroupShortcut(g.Group);
}
/// <summary>Legt "&lt;Name&gt; starten.lnk" auf den Desktop, die DeskCore.exe --launch "&lt;Name&gt;" aufruft.</summary>
private void CreateGroupShortcut(LaunchGroup g)
{
try
{
var exe = Environment.ProcessPath!;
var safe = string.Concat(g.Name.Split(Path.GetInvalidFileNameChars()));
var link = Storage.UniquePath(Storage.UserDesktop, $"{safe} starten.lnk");
Storage.MarkCreated(link); // nicht von der Auto-Sortierung einsammeln lassen
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
dynamic l = shell.CreateShortcut(link);
l.TargetPath = exe;
l.Arguments = $"--launch \"{g.Name}\"";
l.WorkingDirectory = Path.GetDirectoryName(exe);
l.IconLocation = $"{exe},0";
l.Description = $"Startet die DeskCore-Start-Gruppe „{g.Name}“";
l.Save();
ShowToast($"„{Path.GetFileNameWithoutExtension(link)}“ liegt jetzt auf dem Desktop", "");
}
catch (Exception ex) { Error("Verknüpfung fehlgeschlagen", ex.Message); }
}
}
+264
View File
@@ -0,0 +1,264 @@
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace DeskCore;
/// <summary>Einstellungen für Desktop-Seiten, das Aufziehen neuer Boxen und die Tönung der Desktop-Icons.</summary>
public partial class MainWindow
{
public record PageRow(int Index, string Name, string Info, Brush Dot);
private bool _loadingDesktopSettings;
private void LoadDesktopSettings()
{
_loadingDesktopSettings = true;
DrawFencesToggle.IsChecked = S.DrawFences;
PageWheelBox.IsChecked = S.PageWheel;
PageWheelBox.IsEnabled = S.Pages.Count > 1;
var res = Application.Current.Resources;
PageList.ItemsSource = S.Pages.Select((name, i) =>
{
int boxes = S.Fences.Count(f => f.Page == i);
var info = (boxes == 1 ? "1 Box" : $"{boxes} Boxen") + (i == S.CurrentPage ? " · wird gerade angezeigt" : "");
var o = S.PageOptions.GetValueOrDefault(name);
if (o?.Scheduled == true) info += " · " + PageScheduler.Describe(o);
if (string.Equals(S.PageFallback, name, StringComparison.OrdinalIgnoreCase)) info += " · sonst immer";
if (o?.Wallpaper is not null) info += " · eigenes Hintergrundbild";
return new PageRow(i, name, info, (Brush)res[i == S.CurrentPage ? "AccentBrush" : "ControlStrongStrokeBrush"]);
}).ToList();
DesktopTintSwatches.Children.Clear();
void AddSwatch(string? hex, Brush brush, string tip)
{
var rb = new RadioButton
{
Style = (Style)res["SwatchRadio"], GroupName = "desktoptint", Background = brush, ToolTip = tip,
IsChecked = S.DesktopTint == hex,
};
rb.Checked += (_, _) =>
{
if (_loadingDesktopSettings) return;
S.DesktopTint = hex;
S.Save();
DesktopTintStrength.IsEnabled = hex is not null;
DesktopIcons.RefreshCache();
DesktopTint.Refresh(force: true);
};
DesktopTintSwatches.Children.Add(rb);
}
AddSwatch(null, Brushes.Transparent, "Keine Tönung");
foreach (var (name, hex) in CategoryColors.All.Concat([("Weiß", "#F5F5F5")]))
AddSwatch(hex, CategoryColors.Brush(hex)!, name);
DesktopTintStrength.Value = Math.Round(S.DesktopTintStrength * 100);
DesktopTintStrength.IsEnabled = S.DesktopTint is not null;
_loadingDesktopSettings = false;
}
private void DrawFencesToggle_Click(object sender, RoutedEventArgs e)
{
S.DrawFences = DrawFencesToggle.IsChecked == true;
S.Save();
DesktopToggle.Apply();
}
private void PageWheel_Click(object sender, RoutedEventArgs e)
{
S.PageWheel = PageWheelBox.IsChecked == true;
S.Save();
DesktopToggle.Apply();
}
private void DesktopTintStrength_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (_loadingDesktopSettings || !IsLoaded) return;
S.DesktopTintStrength = DesktopTintStrength.Value / 100;
S.Save();
DesktopTint.Refresh(force: true);
}
// ---------- Seiten ----------
private static string? ValidatePageName(string t, int except) =>
t.Trim().Length == 0 ? "Bitte einen Namen eingeben."
: S.Pages.Where((_, i) => i != except).Any(p => string.Equals(p, t.Trim(), StringComparison.OrdinalIgnoreCase))
? "Diesen Namen gibt es schon." : null;
private void AddPage_Click(object sender, RoutedEventArgs e)
{
var name = Dialog.Prompt(this, "Neue Desktop-Seite", "Name der Seite (z. B. „Arbeit“ oder „Freizeit“):",
$"Seite {S.Pages.Count + 1}", "Hinzufügen", t => ValidatePageName(t, -1));
if (name is null) return;
S.Pages.Add(name.Trim());
S.Save();
DesktopToggle.Apply();
LoadDesktopSettings();
ShowToast($"Seite „{name.Trim()}“ hinzugefügt",
"Mit „Anzeigen“ wechselst du hin – neue Boxen landen immer auf der gerade angezeigten Seite.");
}
private void ShowPage_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is int i) FenceManager.SwitchPage(i);
LoadDesktopSettings();
}
private void RenamePage_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not int i || i >= S.Pages.Count) return;
var name = Dialog.Prompt(this, "Seite umbenennen", "Neuer Name:", S.Pages[i], "Umbenennen", t => ValidatePageName(t, i));
if (name is null) return;
S.RenamePage(i, name.Trim());
S.Save();
LoadDesktopSettings();
}
// ---------- Zeitplan & Hintergrundbild ----------
private void PageSchedule_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not int i || i >= S.Pages.Count) return;
var page = S.Pages[i];
var o = S.PageOption(page);
var res = Application.Current.Resources;
var panel = new StackPanel();
TextBlock Label(string t, double top = 18) => new()
{
Text = t, Margin = new Thickness(0, top, 0, 8), FontWeight = FontWeights.SemiBold, FontSize = 13,
};
TextBlock Hint(string t) => new()
{
Text = t, FontSize = 12, TextWrapping = TextWrapping.Wrap, Foreground = (Brush)res["TextSecondaryBrush"],
Margin = new Thickness(0, 4, 0, 0),
};
// Zeitplan
var scheduled = new CheckBox { Content = "Diese Seite automatisch anzeigen", IsChecked = o.Scheduled };
panel.Children.Add(scheduled);
var details = new StackPanel { Margin = new Thickness(28, 0, 0, 0) };
details.Children.Add(Label("An diesen Tagen", 12));
var days = new WrapPanel();
string[] dayNames = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
var dayBoxes = new List<CheckBox>();
for (int d = 0; d < 7; d++)
{
var cb = new CheckBox { Content = dayNames[d], IsChecked = (o.Days & (1 << d)) != 0, Margin = new Thickness(0, 0, 14, 4) };
dayBoxes.Add(cb);
days.Children.Add(cb);
}
details.Children.Add(days);
details.Children.Add(Label("Von – bis"));
var from = new TextBox { Text = o.From, Width = 76, HorizontalContentAlignment = HorizontalAlignment.Center };
var to = new TextBox { Text = o.To, Width = 76, HorizontalContentAlignment = HorizontalAlignment.Center };
details.Children.Add(new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
from,
new TextBlock { Text = "bis", Margin = new Thickness(12, 0, 12, 0), VerticalAlignment = VerticalAlignment.Center },
to,
new TextBlock { Text = "Uhr", Margin = new Thickness(12, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center },
},
});
details.Children.Add(Hint("Über Mitternacht geht auch, z. B. 22:00 bis 06:00."));
panel.Children.Add(details);
void UpdateEnabled() => details.IsEnabled = scheduled.IsChecked == true;
scheduled.Click += (_, _) => UpdateEnabled();
UpdateEnabled();
var fallback = new CheckBox
{
Content = "Zu allen anderen Zeiten diese Seite anzeigen", Margin = new Thickness(0, 18, 0, 0),
IsChecked = string.Equals(S.PageFallback, page, StringComparison.OrdinalIgnoreCase),
};
panel.Children.Add(fallback);
panel.Children.Add(Hint("Beispiel: „Arbeit“ Mo–Fr 08:00–17:00 und bei „Freizeit“ diesen Haken setzen."));
// Hintergrundbild
panel.Children.Add(Label("Hintergrundbild", 22));
string? wallpaper = o.Wallpaper;
var wpText = new TextBlock { TextTrimming = TextTrimming.CharacterEllipsis, VerticalAlignment = VerticalAlignment.Center };
var wpRemove = new Button { Content = "Entfernen", Margin = new Thickness(8, 0, 0, 0) };
void UpdateWallpaper()
{
wpText.Text = wallpaper is null ? "Keins – der Hintergrund bleibt, wie er ist" : Path.GetFileName(wallpaper);
wpText.ToolTip = wallpaper;
wpRemove.IsEnabled = wallpaper is not null;
}
var wpPick = new Button { Content = "Bild wählen …" };
wpPick.Click += (_, _) =>
{
var dlg = new Microsoft.Win32.OpenFileDialog
{
Title = $"Hintergrundbild für „{page}“", Filter = "Bilder|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.webp|Alle Dateien|*.*",
};
if (wallpaper is not null) dlg.InitialDirectory = Path.GetDirectoryName(wallpaper);
if (dlg.ShowDialog() == true) { wallpaper = dlg.FileName; UpdateWallpaper(); }
};
wpRemove.Click += (_, _) => { wallpaper = null; UpdateWallpaper(); };
UpdateWallpaper();
var wpRow = new DockPanel();
var wpButtons = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(12, 0, 0, 0), Children = { wpPick, wpRemove } };
DockPanel.SetDock(wpButtons, Dock.Right);
wpRow.Children.Add(wpButtons);
wpRow.Children.Add(wpText);
panel.Children.Add(wpRow);
panel.Children.Add(Hint("Wechselst du auf eine Seite ohne eigenes Bild, kommt dein ursprünglicher Hintergrund zurück."));
string? Validate()
{
if (scheduled.IsChecked != true) return null;
if (!TimeSpan.TryParse(from.Text.Trim(), out var f) || f.TotalHours >= 24 ||
!TimeSpan.TryParse(to.Text.Trim(), out var t) || t.TotalHours >= 24)
return "Bitte die Uhrzeiten so eingeben: 08:00";
if (f == t) return "Start und Ende dürfen nicht gleich sein.";
if (!dayBoxes.Any(b => b.IsChecked == true)) return "Bitte mindestens einen Tag auswählen.";
return null;
}
if (!Dialog.Show(this, $"Seite „{page}“", panel, "Speichern", "Abbrechen", Validate)) return;
o.Scheduled = scheduled.IsChecked == true;
o.Days = dayBoxes.Select((b, d) => b.IsChecked == true ? 1 << d : 0).Sum();
if (TimeSpan.TryParse(from.Text.Trim(), out var ff)) o.From = ff.ToString(@"hh\:mm");
if (TimeSpan.TryParse(to.Text.Trim(), out var tt)) o.To = tt.ToString(@"hh\:mm");
if (fallback.IsChecked == true) S.PageFallback = page;
else if (string.Equals(S.PageFallback, page, StringComparison.OrdinalIgnoreCase)) S.PageFallback = null;
bool wallpaperChanged = !string.Equals(o.Wallpaper, wallpaper, StringComparison.OrdinalIgnoreCase);
o.Wallpaper = wallpaper;
S.Save();
if (wallpaperChanged && i == S.CurrentPage) PageScheduler.ApplyWallpaper(i);
PageScheduler.Reset();
PageScheduler.Tick();
LoadDesktopSettings();
ShowToast($"Seite „{page}“ gespeichert", "");
}
private void DeletePage_Click(object sender, RoutedEventArgs e)
{
if ((sender as FrameworkElement)?.Tag is not int i || i >= S.Pages.Count) return;
if (S.Pages.Count < 2)
{
ShowToast("Die letzte Seite kann nicht gelöscht werden", "", info: true);
return;
}
var name = S.Pages[i];
int boxes = S.Fences.Count(f => f.Page == i);
if (boxes > 0 && !Dialog.Confirm(this, "Seite löschen",
$"Auf „{name}“ liegen {boxes} Box(en). Sie wandern auf die Seite „{S.Pages[i == 0 ? 1 : i - 1]}“.",
"Löschen"))
return;
FenceManager.DeletePage(i);
S.ForgetPage(name);
S.Save();
DesktopToggle.Apply();
Refresh();
LoadDesktopSettings();
ShowToast($"Seite „{name}“ gelöscht", "");
}
}
+58
View File
@@ -0,0 +1,58 @@
using System.Windows;
using System.Windows.Input;
namespace DeskCore;
/// <summary>Einstellungen der Schnellstart-Suche.</summary>
public partial class MainWindow
{
private void LoadQuickSearchSettings()
{
QuickSearchToggle.IsChecked = S.QuickSearchEnabled;
QuickSearchHotkeyBox.Text = Hotkey.Describe(S.QuickSearchModifiers, S.QuickSearchKey);
QuickSearchHotkeyBox.IsEnabled = S.QuickSearchEnabled;
QuickSearchStartMenuBox.IsChecked = S.QuickSearchStartMenu;
UpdateQuickSearchHint(App.QuickSearchRegistered);
}
private void QuickSearchToggle_Click(object sender, RoutedEventArgs e)
{
S.QuickSearchEnabled = QuickSearchToggle.IsChecked == true;
S.Save();
App.ApplyQuickSearchHotkey();
LoadQuickSearchSettings();
}
private void QuickSearchStartMenu_Click(object sender, RoutedEventArgs e)
{
S.QuickSearchStartMenu = QuickSearchStartMenuBox.IsChecked == true;
S.Save();
}
private void QuickSearchHotkeyBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
var key = e.Key == Key.System ? e.SystemKey : e.Key;
if (key is Key.Tab) return;
e.Handled = true;
if (key is Key.LeftCtrl or Key.RightCtrl or Key.LeftAlt or Key.RightAlt or Key.LeftShift or Key.RightShift
or Key.LWin or Key.RWin or Key.Escape) return;
int mods = (int)Keyboard.Modifiers; // Alt=1, Strg=2, Umschalt=4, Win=8 – gleiche Bits wie RegisterHotKey
if ((mods & (Hotkey.ModCtrl | Hotkey.ModAlt | Hotkey.ModWin)) == 0)
{
QuickSearchHint.Text = "Bitte mit Strg, Alt oder Win kombinieren.";
return;
}
S.QuickSearchModifiers = mods;
S.QuickSearchKey = KeyInterop.VirtualKeyFromKey(key);
S.Save();
QuickSearchHotkeyBox.Text = Hotkey.Describe(S.QuickSearchModifiers, S.QuickSearchKey);
UpdateQuickSearchHint(App.ApplyQuickSearchHotkey());
}
private void UpdateQuickSearchHint(bool ok) =>
QuickSearchHint.Text = !S.QuickSearchEnabled ? "Ausgeschaltet"
: ok ? $"{Hotkey.Describe(S.QuickSearchModifiers, S.QuickSearchKey)} öffnet von überall eine Suchleiste: ein paar Buchstaben tippen, Enter – fertig. " +
"Was du oft startest, steht weiter oben."
: "⚠ Diese Kombination ist schon von einem anderen Programm belegt – bitte ins Feld klicken und eine andere drücken.";
}
+955 -676
View File
File diff suppressed because it is too large Load Diff
+1580 -1503
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -22,6 +22,7 @@ public static class Migration
public static void Run()
{
if (Environment.GetEnvironmentVariable("DESKCORE_DATA") is { Length: > 0 }) return; // Testmodus: nichts umziehen
if (FindLegacy() is { } legacy)
{
foreach (var folder in Folders)
@@ -67,6 +68,10 @@ public static class Migration
/// <summary>Neuer Ordner – außer der Umzug hat nicht geklappt und nur der alte existiert.</summary>
public static string Pick(Environment.SpecialFolder folder, params string[] sub)
{
// Testmodus: alle Daten in einem eigenen Ordner, damit Tests nie echte Einstellungen/Kategorien berühren
if (Environment.GetEnvironmentVariable("DESKCORE_DATA") is { Length: > 0 } testRoot)
return Path.Combine([testRoot, folder.ToString(), .. sub]);
var baseDir = Environment.GetFolderPath(folder);
if (!Directory.Exists(Path.Combine(baseDir, NewName)) && FindLegacy() is { } legacy
&& Directory.Exists(Path.Combine(baseDir, legacy)))
+109 -97
View File
@@ -1,97 +1,109 @@
using System.ComponentModel;
using System.IO;
using System.Windows.Media;
namespace DeskCore;
public class CategoryItem
{
public required string Name { get; init; }
public bool IsDesktop { get; init; }
public int Count { get; set; }
public string Folder => IsDesktop ? Storage.UserDesktop : Storage.CategoryPath(Name);
public CategoryMeta? Meta => IsDesktop ? null : AppSettings.Current.Categories.GetValueOrDefault(Name);
public string Glyph => IsDesktop ? "" : Meta?.Glyph ?? "";
public Brush? GlyphBrush => CategoryColors.Brush(Meta?.Color);
public bool HasFence => Meta?.Fence == true;
}
public static class CategoryColors
{
public static readonly (string Name, string Hex)[] All =
[
("Blau", "#3B82F6"), ("Türkis", "#12A594"), ("Grün", "#22A06B"), ("Gelb", "#E2A600"),
("Orange", "#F07A1A"), ("Rot", "#E5484D"), ("Pink", "#D6409F"), ("Lila", "#8E4EC6"), ("Grau", "#8B8D98"),
];
public static Brush? Brush(string? hex)
{
if (string.IsNullOrEmpty(hex)) return null;
try
{
var b = new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex));
b.Freeze();
return b;
}
catch { return null; }
}
}
public static class CategoryGlyphs
{
public static readonly (string Glyph, string Name)[] All =
[
("", "Ordner"), ("", "Spiele"), ("", "Arbeit"), ("", "Code"),
("", "Musik"), ("", "Video"), ("", "Bilder"), ("", "Internet"),
("", "Mail"), ("", "Shopping"), ("", "System"), ("", "Werkzeuge"),
("", "Dokumente"), ("", "Favoriten"), ("", "Herz"), ("", "Cloud"),
("", "Downloads"), ("", "Personen"), ("", "Bibliothek"), ("", "Rechner"),
("", "Ideen"), ("", "Zuhause"), ("", "Lernen"), ("", "Erweiterungen"),
("", "Computer"), ("", "Sicherheit"), ("", "Ebenen"),
];
}
public class IconItem : INotifyPropertyChanged
{
public required string Path { get; init; }
public string? Category { get; init; }
private ImageSource? _icon;
public ImageSource? Icon
{
get => _icon;
set { _icon = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Icon))); }
}
/// <summary>Nur für Desktop-Icons: Kategorie, in der bereits eine Kopie liegt.</summary>
public string? SortedInto { get; init; }
public bool IsSorted => SortedInto is not null;
public string Name => Storage.DisplayName(Path);
public string TypeText
{
get
{
if (Directory.Exists(Path)) return "Ordner";
var ext = System.IO.Path.GetExtension(Path).ToLowerInvariant();
return ext switch
{
".lnk" => "Verknüpfung",
".url" => "Internetverknüpfung",
".exe" => "Programm",
"" => "Datei",
_ => ext.TrimStart('.').ToUpperInvariant() + "-Datei",
};
}
}
public string LocationText => SortedInto is not null ? $"✓ gesichert in {SortedInto}" : Category ?? "";
public string ToolTip =>
SortedInto is not null ? $"{Name}\n✓ Bereits gesichert in „{SortedInto}“ – kann vom Desktop gelöscht werden." :
Category is not null ? $"{Name}\nOrt: {Category}" : $"{Name}\n{TypeText}";
public event PropertyChangedEventHandler? PropertyChanged;
}
using System.ComponentModel;
using System.IO;
using System.Windows.Media;
namespace DeskCore;
public class CategoryItem
{
public required string Name { get; init; }
public bool IsDesktop { get; init; }
public int Count { get; set; }
public string Folder => IsDesktop ? Storage.UserDesktop : Storage.CategoryPath(Name);
public CategoryMeta? Meta => IsDesktop ? null : AppSettings.Current.Categories.GetValueOrDefault(Name);
public string Glyph => IsDesktop ? "" : Meta?.Glyph ?? "";
public Brush? GlyphBrush => CategoryColors.Brush(Meta?.Color);
public bool HasFence => !IsDesktop && AppSettings.Current.HasFence(Name);
}
/// <summary>Eintrag einer Start-Gruppe in der Seitenleiste.</summary>
public class LaunchGroupItem
{
public required LaunchGroup Group { get; init; }
public string Name => Group.Name;
public int Count => Group.Items.Count;
public string Glyph => ""; // Abspielen
public Brush? GlyphBrush => CategoryColors.Brush(Group.Color);
public bool HasFence => false;
public bool IsDesktop => true; // blendet den Umsortier-Griff der Kategorien aus
}
public static class CategoryColors
{
public static readonly (string Name, string Hex)[] All =
[
("Blau", "#3B82F6"), ("Türkis", "#12A594"), ("Grün", "#22A06B"), ("Gelb", "#E2A600"),
("Orange", "#F07A1A"), ("Rot", "#E5484D"), ("Pink", "#D6409F"), ("Lila", "#8E4EC6"), ("Grau", "#8B8D98"),
];
public static Brush? Brush(string? hex)
{
if (string.IsNullOrEmpty(hex)) return null;
try
{
var b = new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex));
b.Freeze();
return b;
}
catch { return null; }
}
}
public static class CategoryGlyphs
{
public static readonly (string Glyph, string Name)[] All =
[
("", "Ordner"), ("", "Spiele"), ("", "Arbeit"), ("", "Code"),
("", "Musik"), ("", "Video"), ("", "Bilder"), ("", "Internet"),
("", "Mail"), ("", "Shopping"), ("", "System"), ("", "Werkzeuge"),
("", "Dokumente"), ("", "Favoriten"), ("", "Herz"), ("", "Cloud"),
("", "Downloads"), ("", "Personen"), ("", "Bibliothek"), ("", "Rechner"),
("", "Ideen"), ("", "Zuhause"), ("", "Lernen"), ("", "Erweiterungen"),
("", "Computer"), ("", "Sicherheit"), ("", "Ebenen"),
];
}
public class IconItem : INotifyPropertyChanged
{
public required string Path { get; init; }
public string? Category { get; init; }
private ImageSource? _icon;
public ImageSource? Icon
{
get => _icon;
set { _icon = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Icon))); }
}
/// <summary>Nur für Desktop-Icons: Kategorie, in der bereits eine Kopie liegt.</summary>
public string? SortedInto { get; init; }
public bool IsSorted => SortedInto is not null;
public string Name => Storage.DisplayName(Path);
public string TypeText
{
get
{
if (Directory.Exists(Path)) return "Ordner";
var ext = System.IO.Path.GetExtension(Path).ToLowerInvariant();
return ext switch
{
".lnk" => "Verknüpfung",
".url" => "Internetverknüpfung",
".exe" => "Programm",
"" => "Datei",
_ => ext.TrimStart('.').ToUpperInvariant() + "-Datei",
};
}
}
public string LocationText => SortedInto is not null ? $"✓ gesichert in {SortedInto}" : Category ?? "";
public string ToolTip =>
SortedInto is not null ? $"{Name}\n✓ Bereits gesichert in „{SortedInto}“ – kann vom Desktop gelöscht werden." :
Category is not null ? $"{Name}\nOrt: {Category}" : $"{Name}\n{TypeText}";
public event PropertyChangedEventHandler? PropertyChanged;
}
+97
View File
@@ -0,0 +1,97 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace DeskCore;
/// <summary>
/// Wechselt die Desktop-Seite nach Zeitplan (z. B. Mo–Fr 8–17 Uhr „Arbeit“, sonst „Freizeit“)
/// und setzt das Hintergrundbild einer Seite.
/// </summary>
public static class PageScheduler
{
/// <summary>Seite, die der Zeitplan zuletzt wollte. Gewechselt wird nur, wenn sich das ändert –
/// von Hand gewählte Seiten bleiben also bis zum nächsten Zeitplan-Wechsel stehen.</summary>
private static string? _lastWanted = "\0";
private static AppSettings S => AppSettings.Current;
/// <summary>Gibt es überhaupt einen Zeitplan?</summary>
public static bool Active => S.Pages.Count > 1 && S.Pages.Any(p => S.PageOptions.GetValueOrDefault(p)?.Scheduled == true);
/// <summary>Welche Seite laut Zeitplan jetzt dran ist (null = keine Vorgabe).</summary>
public static string? Wanted(DateTime now)
{
if (!Active) return null;
foreach (var p in S.Pages)
if (S.PageOptions.GetValueOrDefault(p)?.Matches(now) == true) return p;
return S.PageFallback is { } f && S.Pages.Contains(f, StringComparer.OrdinalIgnoreCase) ? f : null;
}
/// <summary>Regelmäßig aufrufen (alle paar Sekunden).</summary>
public static void Tick()
{
var wanted = Wanted(DateTime.Now);
if (string.Equals(wanted, _lastWanted, StringComparison.OrdinalIgnoreCase)) return;
_lastWanted = wanted;
if (wanted is null) return;
int index = S.Pages.FindIndex(p => string.Equals(p, wanted, StringComparison.OrdinalIgnoreCase));
if (index >= 0 && index != S.CurrentPage) FenceManager.SwitchPage(index);
}
/// <summary>Zeitplan geändert → beim nächsten Tick sofort anwenden.</summary>
public static void Reset() => _lastWanted = "\0";
/// <summary>Text für die Einstellungen, z. B. „Mo–Fr · 08:00–17:00“.</summary>
public static string Describe(PageOption o)
{
string[] names = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
var days = Enumerable.Range(0, 7).Where(i => (o.Days & (1 << i)) != 0).ToList();
string d = days.Count == 7 ? "Täglich"
: days.SequenceEqual([0, 1, 2, 3, 4]) ? "Mo–Fr"
: days.SequenceEqual([5, 6]) ? "Wochenende"
: days.Count > 2 && days.Last() - days.First() == days.Count - 1 ? $"{names[days.First()]}–{names[days.Last()]}"
: string.Join(", ", days.Select(i => names[i]));
return $"{d} · {o.From}–{o.To} Uhr";
}
// =====================================================================
// Hintergrundbild
// =====================================================================
/// <summary>Beim Seitenwechsel: Bild der Seite setzen – oder das ursprüngliche zurückholen.</summary>
public static void ApplyWallpaper(int pageIndex)
{
if (pageIndex < 0 || pageIndex >= S.Pages.Count) return;
var wanted = S.PageOptions.GetValueOrDefault(S.Pages[pageIndex])?.Wallpaper;
if (wanted is not null && !File.Exists(wanted)) wanted = null;
string? target;
if (wanted is not null)
{
// Vor dem ersten Wechsel das bisherige Bild merken, um es später zurückzuholen
S.SavedWallpaper ??= CurrentWallpaper() ?? "";
target = wanted;
}
else if (S.SavedWallpaper is { } saved)
{
target = saved;
S.SavedWallpaper = null;
}
else return; // Seiten ohne eigenes Bild lassen den Hintergrund in Ruhe
S.Save();
if (string.Equals(CurrentWallpaper(), target, StringComparison.OrdinalIgnoreCase)) return;
Task.Run(() => SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, target, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE));
}
public static string? CurrentWallpaper()
{
var sb = new StringBuilder(520);
return SystemParametersInfo(SPI_GETDESKWALLPAPER, (uint)sb.Capacity, sb, 0) ? sb.ToString() : null;
}
private const uint SPI_GETDESKWALLPAPER = 0x73, SPI_SETDESKWALLPAPER = 0x14, SPIF_UPDATEINIFILE = 0x1, SPIF_SENDCHANGE = 0x2;
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool SystemParametersInfo(uint action, uint param, string value, uint flags);
[DllImport("user32.dll", CharSet = CharSet.Unicode)] private static extern bool SystemParametersInfo(uint action, uint param, StringBuilder value, uint flags);
}
+397
View File
@@ -0,0 +1,397 @@
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
namespace DeskCore;
/// <summary>
/// Schnellstart-Suche: Tastenkürzel → Suchleiste mitten auf dem Bildschirm, tippen, Enter, läuft.
/// Durchsucht Kategorien, Desktop, Startmenü, Start-Gruppen, Kategorien und Desktop-Seiten.
/// Was du oft startest, steht weiter oben.
/// </summary>
public sealed class QuickSearch : Window
{
private const int MaxResults = 8;
private sealed record Entry(string Name, string Sub, string? Path, string Glyph, string Key, Action Run, Action? Locate = null)
{
/// <summary>Für die Ähnlichkeit: Name ohne Endungen wie „- Verknüpfung“.</summary>
public string SearchName { get; } = Name.Replace(" - Verknüpfung", "", StringComparison.OrdinalIgnoreCase);
}
private static QuickSearch? _open;
private static List<Entry>? _startMenu;
private static DateTime _startMenuRead;
private readonly TextBox _box;
private readonly ListBox _list;
private readonly TextBlock _hint;
private List<Entry> _all = [];
private List<Entry> _shown = [];
private bool _closing;
private static AppSettings S => AppSettings.Current;
/// <summary>Per Tastenkürzel: öffnen bzw. wieder schließen.</summary>
public static void Toggle()
{
if (_open is not null) { _open.CloseNow(); return; }
_open = new QuickSearch();
_open.Show();
}
private QuickSearch()
{
var res = Application.Current.Resources;
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent;
ResizeMode = ResizeMode.NoResize;
ShowInTaskbar = false;
Topmost = true;
Width = 680;
SizeToContent = SizeToContent.Height;
Title = "DeskCore Suche";
FontFamily = (FontFamily)res["UiFont"];
Foreground = (Brush)res["TextBrush"];
// Suchzeile
_box = new TextBox
{
FontSize = 20, BorderThickness = new Thickness(0), Background = Brushes.Transparent, Padding = new Thickness(0),
VerticalContentAlignment = VerticalAlignment.Center, Foreground = (Brush)res["TextBrush"],
CaretBrush = (Brush)res["TextBrush"],
};
var placeholder = new TextBlock
{
Text = "Programm, Datei, Start-Gruppe oder Kategorie suchen …", FontSize = 20, IsHitTestVisible = false,
Foreground = (Brush)res["TextTertiaryBrush"], VerticalAlignment = VerticalAlignment.Center,
};
_box.TextChanged += (_, _) =>
{
placeholder.Visibility = _box.Text.Length == 0 ? Visibility.Visible : Visibility.Collapsed;
Filter();
};
var searchRow = new DockPanel { Margin = new Thickness(18, 14, 18, 14) };
var glass = new TextBlock
{
Text = "", FontFamily = (FontFamily)res["IconFont"], FontSize = 20, Margin = new Thickness(0, 0, 14, 0),
VerticalAlignment = VerticalAlignment.Center, Foreground = (Brush)res["AccentBrush"],
};
DockPanel.SetDock(glass, Dock.Left);
searchRow.Children.Add(glass);
searchRow.Children.Add(new Grid { Children = { placeholder, _box } });
_list = new ListBox
{
BorderThickness = new Thickness(0), Background = Brushes.Transparent, Margin = new Thickness(6, 0, 6, 6),
Focusable = false, Visibility = Visibility.Collapsed,
};
ScrollViewer.SetHorizontalScrollBarVisibility(_list, ScrollBarVisibility.Disabled);
_list.MouseDoubleClick += (_, _) => RunSelected(locate: false);
_list.PreviewMouseLeftButtonUp += (_, e) =>
{
// Ein Klick genügt – wie im Startmenü
if (MainWindow.FindParent<ListBoxItem>(e.OriginalSource as DependencyObject) is not null) RunSelected(locate: false);
};
_hint = new TextBlock
{
FontSize = 12, Margin = new Thickness(18, 8, 18, 10), Foreground = (Brush)res["TextTertiaryBrush"],
Text = "Enter Starten · Strg + Enter Speicherort öffnen · Esc Schließen",
};
var divider = new Border { Height = 1, Background = (Brush)res["ControlStrokeBrush"] };
var footer = new Border { Child = _hint, BorderBrush = (Brush)res["ControlStrokeBrush"], BorderThickness = new Thickness(0, 1, 0, 0) };
var stack = new StackPanel { Children = { searchRow, divider, _list, footer } };
Content = new Border
{
Margin = new Thickness(24), CornerRadius = new CornerRadius(10),
Background = (Brush)res["FlyoutBrush"], BorderBrush = (Brush)res["FlyoutStrokeBrush"], BorderThickness = new Thickness(1),
Effect = (System.Windows.Media.Effects.Effect)res["DialogShadow"], Child = stack,
};
PreviewKeyDown += OnKey;
Deactivated += (_, _) => CloseNow();
Closed += (_, _) => { if (_open == this) _open = null; };
Loaded += (_, _) =>
{
Position();
BringToFront();
_box.Focus();
BuildIndex();
};
}
// =====================================================================
// Position & Fokus
// =====================================================================
private void Position()
{
var mouse = GetCursorPos(out var p) ? new Point(p.X / Screens.Scale, p.Y / Screens.Scale) : new Point(0, 0);
var wa = Screens.WorkAreaAt(mouse);
Left = wa.Left + (wa.Width - Width) / 2;
Top = wa.Top + wa.Height * 0.22;
}
private void BringToFront()
{
// Das Tastenkürzel erlaubt DeskCore, nach vorne zu kommen
var hwnd = new WindowInteropHelper(this).Handle;
SetForegroundWindow(hwnd);
Activate();
}
private void CloseNow()
{
if (_closing) return;
_closing = true;
Close();
}
// =====================================================================
// Was durchsucht wird
// =====================================================================
private async void BuildIndex()
{
var list = new List<Entry>();
// Start-Gruppen
foreach (var g in S.LaunchGroups)
{
var group = g;
list.Add(new Entry(g.Name, $"Start-Gruppe · {g.Items.Count} {(g.Items.Count == 1 ? "Programm" : "Programme")}", null, "",
Usage.GroupPrefix + g.Name, () => App.Win?.LaunchGroupNow(group)));
}
// Kategorien selbst (öffnet DeskCore dort) und Desktop-Seiten
foreach (var c in Storage.Categories())
{
var cat = c;
list.Add(new Entry(c, "Kategorie in DeskCore öffnen", null, S.Categories.GetValueOrDefault(c)?.Glyph ?? "",
"kategorie:" + c, () => { App.Win?.ShowFromBackground(); App.Win?.ShowCategory(cat); }));
}
if (S.Pages.Count > 1)
for (int i = 0; i < S.Pages.Count; i++)
{
int page = i;
list.Add(new Entry(S.Pages[i], "Desktop-Seite anzeigen", null, "", "seite:" + S.Pages[i], () => FenceManager.SwitchPage(page)));
}
_all = list;
Filter();
// Dateien aus Kategorien, Desktop und Startmenü im Hintergrund sammeln (Startmenü 10 Minuten zwischengespeichert)
bool withStartMenu = S.QuickSearchStartMenu;
var categories = Storage.Categories();
var files = await Task.Run(() =>
{
var l = new List<Entry>();
foreach (var c in categories)
foreach (var p in Storage.Entries(Storage.CategoryPath(c)))
l.Add(FileEntry(p, c));
foreach (var p in Storage.DesktopEntries())
l.Add(FileEntry(p, "Desktop"));
if (withStartMenu)
{
if (_startMenu is null || DateTime.Now - _startMenuRead > TimeSpan.FromMinutes(10))
{
_startMenu = ReadStartMenu();
_startMenuRead = DateTime.Now;
}
l.AddRange(_startMenu);
}
return Dedupe(l);
});
if (_closing) return;
_all = list.Concat(files).ToList();
Filter();
}
private static Entry FileEntry(string path, string where) =>
new(Storage.DisplayName(path), where, path, "", path, () => Usage.Start(path),
() => Process.Start("explorer.exe", $"/select,\"{path}\""));
private static List<Entry> ReadStartMenu()
{
var result = new List<Entry>();
foreach (var root in new[]
{
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
})
{
if (!Directory.Exists(root)) continue;
IEnumerable<string> files;
try { files = Directory.EnumerateFiles(root, "*.*", SearchOption.AllDirectories).ToList(); }
catch { continue; }
foreach (var f in files)
{
var ext = Path.GetExtension(f).ToLowerInvariant();
if (ext is not (".lnk" or ".url" or ".appref-ms")) continue;
var name = Path.GetFileNameWithoutExtension(f);
// Deinstallations- und Hilfe-Verknüpfungen will niemand per Suche starten
if (name.Contains("uninstall", StringComparison.OrdinalIgnoreCase) || name.Contains("deinstall", StringComparison.OrdinalIgnoreCase))
continue;
result.Add(new Entry(name, "Startmenü", f, "", f, () => Usage.Start(f),
() => Process.Start("explorer.exe", $"/select,\"{f}\"")));
}
}
return result;
}
/// <summary>
/// Gleiches Programm aus Kategorie, Desktop und Startmenü nur einmal zeigen – die Reihenfolge der Quellen
/// entscheidet (Kategorie vor Desktop vor Startmenü). Gleicher Name gilt als gleiches Programm.
/// </summary>
private static List<Entry> Dedupe(IEnumerable<Entry> entries)
{
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
return entries.Where(e => e.Path is null || seen.Add(e.SearchName.Trim())).ToList();
}
// =====================================================================
// Suchen & Anzeigen
// =====================================================================
/// <summary>Wie gut passt der Name zur Eingabe? -1 = gar nicht.</summary>
private static int Match(string name, string q)
{
if (q.Length == 0) return 0;
var n = name.ToLowerInvariant();
if (n == q) return 1000;
if (n.StartsWith(q)) return 800;
var words = n.Split([' ', '-', '_', '.', '(', ')', '&'], StringSplitOptions.RemoveEmptyEntries);
if (words.Any(w => w.StartsWith(q))) return 650;
if (q.Length >= 2 && string.Concat(words.Select(w => w[0])).StartsWith(q)) return 600; // "vsc" → Visual Studio Code
if (n.Contains(q)) return 450;
// Buchstaben in der richtigen Reihenfolge ("fzla" → FileZilla) – aber nur ab einem Wortanfang,
// sonst passt fast alles irgendwie ("ets" → Malwarebytes)
if (q.Length < 3) return -1;
for (int start = 0; start < n.Length; start++)
{
if (n[start] != q[0] || (start > 0 && char.IsLetterOrDigit(n[start - 1]))) continue;
int k = 0;
for (int j = start; j < n.Length && k < q.Length; j++) if (n[j] == q[k]) k++;
if (k == q.Length) return 200;
}
return -1;
}
private void Filter()
{
var q = _box.Text.Trim().ToLowerInvariant();
if (q.Length == 0)
{
// Ohne Eingabe: zuletzt/oft benutzt
_shown = _all.Where(e => Usage.Score(e.Key) > 0)
.OrderByDescending(e => Usage.Score(e.Key)).Take(MaxResults).ToList();
}
else
{
_shown = _all.Select(e => (e, m: Match(e.SearchName, q)))
.Where(x => x.m >= 0)
.OrderByDescending(x => x.m + Math.Min(300, Usage.Score(x.e.Key) * 40))
.ThenBy(x => x.e.Name.Length)
.Take(MaxResults).Select(x => x.e).ToList();
}
ShowResults(q.Length > 0);
}
private void ShowResults(bool searching)
{
var res = Application.Current.Resources;
_list.Items.Clear();
int px = (int)Math.Round(32 * VisualTreeHelper.GetDpi(this).DpiScaleX);
foreach (var e in _shown)
{
FrameworkElement icon;
if (e.Path is not null)
{
var img = new Image { Width = 32, Height = 32 };
var item = new IconItem { Path = e.Path };
item.PropertyChanged += (_, _) => img.Source = item.Icon;
IconLoader.Load(item, px);
img.Source = item.Icon;
icon = img;
}
else
{
var meta = e.Key.StartsWith("kategorie:") ? S.Categories.GetValueOrDefault(e.Name) : null;
icon = new TextBlock
{
Text = e.Glyph, FontFamily = (FontFamily)res["IconFont"], FontSize = 22, Width = 32, TextAlignment = TextAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Foreground = CategoryColors.Brush(meta?.Color) ?? (Brush)res["AccentBrush"],
};
}
icon.Margin = new Thickness(0, 0, 14, 0);
DockPanel.SetDock(icon, Dock.Left);
var texts = new StackPanel { VerticalAlignment = VerticalAlignment.Center };
texts.Children.Add(new TextBlock { Text = e.Name, FontSize = 15, TextTrimming = TextTrimming.CharacterEllipsis });
texts.Children.Add(new TextBlock { Text = e.Sub, FontSize = 12, Foreground = (Brush)res["TextSecondaryBrush"] });
_list.Items.Add(new DockPanel { Margin = new Thickness(8, 6, 8, 6), Children = { icon, texts } });
}
if (_shown.Count == 0 && searching)
_list.Items.Add(new TextBlock
{
Text = "Nichts gefunden", Margin = new Thickness(12, 10, 12, 10), Foreground = (Brush)res["TextSecondaryBrush"],
IsHitTestVisible = false,
});
_list.Visibility = _list.Items.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
if (_shown.Count > 0) _list.SelectedIndex = 0;
}
private void OnKey(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Escape:
CloseNow();
e.Handled = true;
break;
case Key.Down or Key.Up when _shown.Count > 0:
int i = _list.SelectedIndex + (e.Key == Key.Down ? 1 : -1);
_list.SelectedIndex = (i + _shown.Count) % _shown.Count;
_list.ScrollIntoView(_list.SelectedItem);
e.Handled = true;
break;
case Key.Enter:
RunSelected(locate: Keyboard.Modifiers.HasFlag(ModifierKeys.Control));
e.Handled = true;
break;
}
}
private void RunSelected(bool locate)
{
if (_list.SelectedIndex < 0 || _list.SelectedIndex >= _shown.Count) return;
var entry = _shown[_list.SelectedIndex];
CloseNow(); // zuerst schließen, damit das gestartete Programm nach vorne kommt
try
{
if (locate && entry.Locate is not null) entry.Locate();
else
{
entry.Run();
// Kategorien/Seiten mitzählen (Dateien zählt Usage.Start, Gruppen LaunchGroupNow selbst)
if (entry.Path is null && !entry.Key.StartsWith(Usage.GroupPrefix)) Usage.Record(entry.Key);
}
}
catch (Exception ex)
{
App.TrayIcon?.Notify("Starten fehlgeschlagen", $"„{entry.Name}“ konnte nicht geöffnet werden.\n{ex.Message}");
}
}
[StructLayout(LayoutKind.Sequential)] private struct POINT { public int X, Y; }
[DllImport("user32.dll")] private static extern bool GetCursorPos(out POINT p);
[DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hwnd);
}
+487 -194
View File
@@ -1,194 +1,487 @@
<p align="center">
<img src="app.png" width="110" alt="DeskCore-Logo">
</p>
<h1 align="center">DeskCore</h1>
<p align="center">
<b>Endlich ein aufgeräumter Desktop.</b><br>
Sortiere deine Desktop-Icons in eigene Kategorien – übersichtlich, schnell und im Windows-11-Look.
</p>
<p align="center">
<a href="https://git.viper.ipv64.net/M_Viper/DeskCore/releases/latest"><b>⬇️ DeskCore herunterladen</b></a>
&nbsp;·&nbsp; Windows 10 &amp; 11 &nbsp;·&nbsp; kostenlos für private Nutzung
</p>
<p align="center">
<img src="docs/desktop.png" width="820" alt="DeskCore – Übersicht deines Desktops">
<br><sub>Alle Icons deines Desktops auf einen Blick. Der grüne Haken ✓ zeigt, was schon in einer Kategorie gesichert ist.</sub>
</p>
Mit DeskCore sortierst du deine Desktop-Icons in eigene Gruppen, zum Beispiel „Spiele“, „Arbeit“ oder „Tools“.
Danach kannst du die Icons vom Desktop löschen. In DeskCore bleiben sie erhalten und lassen sich mit einem Doppelklick starten.
---
## Auf einen Blick
| | |
|---|---|
| 🗂️ **Eigene Kategorien** | mit Symbol und Farbe, Reihenfolge frei wählbar |
| ✅ **Sicher aufräumen** | Icons werden gesichert, bevor du sie vom Desktop löschst |
| 🧱 **Desktop-Boxen** | Kategorien als verschiebbare Kästen direkt auf dem Desktop |
| ⚡ **Automatisch sortieren** | neue Icons landen per Regel von selbst in der richtigen Kategorie |
| 🔍 **Suche** | jedes Programm in Sekunden finden |
| ↩️ **Rückgängig** | Strg + Z macht jeden Schritt rückgängig |
| 🌙 **Hell &amp; Dunkel** | passt sich Windows an oder nach deinem Geschmack |
| ⌨️ **Immer griffbereit** | Win + Umschalt + D oder Klick auf das Symbol neben der Uhr |
| 💾 **Sicherung** | alles als ZIP sichern, z. B. für einen neuen PC |
| 🔄 **Updates** | DeskCore sagt Bescheid, wenn es eine neue Version gibt |
---
## Was du brauchst
- Windows 10 oder Windows 11 (64 Bit)
- Das war's. Falls DeskCore noch etwas von Microsoft braucht („.NET“), erledigt das der Installer automatisch.
---
## Installieren
1. Doppelklick auf **`DeskCore-Setup.exe`**
2. **Weiter** klicken
3. Die Lizenz lesen, den Haken bei **„Ich akzeptiere …“** setzen, dann **Weiter**
4. Bei Bedarf noch auswählen:
- Verknüpfung auf dem Desktop (empfohlen)
- Mit Windows starten
5. **Installieren** klicken, und fertig.
Du brauchst dafür **keine Administratorrechte**.
Nur wenn „.NET“ fehlt und installiert wird, fragt Windows einmal nach. Einfach mit **Ja** bestätigen.
> **Windows warnt beim Start des Installers?**
> Bei neuen Programmen zeigt Windows manchmal „Der Computer wurde durch Windows geschützt“.
> Klick dann auf **„Weitere Informationen“** und danach auf **„Trotzdem ausführen“**.
---
## So räumst du deinen Desktop auf
### 1. Gruppe anlegen
Unten links auf **„+ Neue Kategorie“** klicken und einen Namen eingeben, z. B. **Spiele**.
Auf Wunsch suchst du dir noch ein Symbol und eine Farbe aus.
### 2. Icons hineinziehen
Zieh ein Icon **vom Desktop** mit der Maus auf deine Kategorie links.
Es wird jetzt in DeskCore **gesichert**. Auf dem Desktop bleibt es vorerst liegen.
> Tipp: Du kannst auch mehrere Icons auf einmal markieren und ziehen.
<p align="center">
<img src="docs/kategorie.png" width="820" alt="Eine Kategorie in DeskCore">
<br><sub>Eine fertige Kategorie: Alle Programme ordentlich beisammen, starten per Doppelklick.</sub>
</p>
### 3. Icons vom Desktop löschen
Klick links auf **„Desktop“**. Icons mit einem **grünen Haken ✓** sind schon gesichert.
Die kannst du ohne Sorge vom Desktop löschen:
**Rechtsklick → „Vom Desktop löschen“** oder ganz normal auf dem Desktop löschen.
**Fertig!** Dein Desktop ist leer, und deine Programme findest du jetzt in DeskCore.
---
## Programme starten
- Kategorie links anklicken
- **Doppelklick** auf das Icon
Oder schneller: **Suchfeld** oben benutzen, den Namen eintippen und Doppelklick.
---
## Nützliche Extras
### DeskCore schnell öffnen
- **Win + Umschalt + D** drücken, von überall aus
- oder unten rechts neben der Uhr auf das **DeskCore-Symbol** klicken
Wenn du das Fenster mit dem **X** schließt, läuft DeskCore unten rechts weiter.
Richtig beenden: **Rechtsklick auf das Symbol neben der Uhr → Beenden**
### Hell oder Dunkel
Oben rechts auf die **Sonne bzw. den Mond** klicken und auswählen.
### Boxen auf dem Desktop
Eine Kategorie kann als kleine **Box direkt auf dem Desktop** erscheinen:
Kategorie anklicken und oben auf **„Als Box auf den Desktop“** klicken.
Die Box kannst du verschieben und in der Größe ändern. Ein Doppelklick auf ihren Titel klappt sie ein und aus.
### Automatisch sortieren
DeskCore kann neue Icons selbst einsortieren, z. B. alles mit „Steam“ im Namen → **Spiele**.
Einstellen kannst du das unter **Einstellungen → Automatisch sortieren → Regel hinzufügen**.
### Reihenfolge der Kategorien
Kategorie links mit der Maus **nach oben oder unten ziehen**.
Oder per Rechtsklick → **Nach oben / Nach unten**.
### Ansicht
Oben rechts gibt es den **Ansicht-Knopf**: kleine, mittlere oder große Symbole oder eine Liste.
### Icons in den Desktop-Boxen
Auch in einer Box auf dem Desktop kannst du per **Rechtsklick** umbenennen, löschen, in eine andere Kategorie verschieben
oder zurück auf den Desktop legen. Icons lassen sich auch einfach aus der Box herausziehen.
### Updates
DeskCore schaut einmal am Tag nach, ob es eine neue Version gibt.
Wenn ja, erscheint oben ein Hinweis. Ein Klick auf **„Jetzt aktualisieren“** genügt, und DeskCore startet danach von selbst neu.
---
## Hoppla, falsch geklickt?
**Strg + Z** macht den letzten Schritt rückgängig.
Unten im Fenster erscheint nach vielen Aktionen auch kurz ein Knopf **„Rückgängig“**.
---
## Häufige Fragen
**Wo liegen meine Icons eigentlich?**
Im Ordner `C:\Benutzer\DEIN-NAME\DeskCore`. Jede Kategorie ist dort ein ganz normaler Ordner.
Auch wenn DeskCore mal nicht läuft, ist also nichts weg.
**Ich habe etwas gelöscht. Ist es für immer weg?**
Nein. Erst geht **Strg + Z**, und danach liegt es noch im **Papierkorb** von Windows.
**Ein Icon lässt sich nicht verschieben („Keine Berechtigung“).**
Manche Icons gehören allen Benutzern des PCs. Dafür DeskCore einmal mit
**Rechtsklick → „Als Administrator ausführen“** starten.
**Ich habe einen großen Ordner hineingezogen. Ist der jetzt doppelt da?**
DeskCore fragt bei großen Ordnern nach. Wählst du **„Verschieben“**, liegt er nur einmal da und belegt keinen doppelten Platz.
Beim Kopieren siehst du einen Fortschrittsbalken und kannst jederzeit **abbrechen**. Dann bleibt alles wie vorher.
**Ich habe einen Bildschirm abgesteckt und das Fenster ist weg.**
Keine Sorge: DeskCore und die Desktop-Boxen holen sich automatisch auf den verbliebenen Bildschirm zurück.
**Ich bekomme einen neuen PC. Was jetzt?**
**Einstellungen → Sicherung → „Sichern …“** speichert alles in eine ZIP-Datei.
Auf dem neuen PC installierst du DeskCore und wählst **„Wiederherstellen …“**.
**Das Tastenkürzel funktioniert nicht.**
Ein anderes Programm benutzt es vielleicht schon. Unter **Einstellungen → Tastenkürzel** ins Feld klicken und eine andere Kombination drücken.
---
## Deinstallieren
**Windows-Einstellungen → Apps → DeskCore → Deinstallieren**
Deine Kategorien bleiben dabei **standardmäßig erhalten**.
Willst du sie auch entfernen, setz beim Deinstallieren den Haken. Dann landen sie im Papierkorb.
---
## Lizenz
DeskCore ist nur für den **privaten, nicht-kommerziellen** Gebrauch.
Weitergeben, Verändern und Verkaufen ist nicht erlaubt.
Den vollständigen Text findest du in der Datei `LICENSE.txt` oder in DeskCore unter **Einstellungen → Info → Lizenz**.
© M_Viper · [m-viper.de](https://m-viper.de)
<p align="center">
<img src="app.png" width="110" alt="DeskCore-Logo">
</p>
<h1 align="center">DeskCore</h1>
<p align="center">
<b>Endlich ein aufgeräumter Desktop.</b><br>
Sortiere deine Desktop-Icons in eigene Kategorien – übersichtlich, schnell und im Windows-11-Look.
</p>
<p align="center">
<a href="https://git.viper.ipv64.net/M_Viper/DeskCore/releases/latest"><b>⬇️ DeskCore herunterladen</b></a>
&nbsp;·&nbsp; Windows 10 &amp; 11 &nbsp;·&nbsp; kostenlos für private Nutzung
</p>
<p align="center">
<img src="docs/desktop.png" width="820" alt="DeskCore – Übersicht deines Desktops">
<br><sub>Alle Icons deines Desktops auf einen Blick. Der grüne Haken ✓ zeigt, was schon in einer Kategorie gesichert ist.</sub>
</p>
Mit DeskCore sortierst du deine Desktop-Icons in eigene Gruppen, zum Beispiel „Spiele“, „Arbeit“ oder „Tools“.
Danach kannst du die Icons vom Desktop löschen. In DeskCore bleiben sie erhalten und lassen sich mit einem Doppelklick starten.
---
## Inhalt
- [Auf einen Blick](#auf-einen-blick)
- [Was du brauchst](#was-du-brauchst)
- [Installieren](#installieren)
- [So räumst du deinen Desktop auf](#so-räumst-du-deinen-desktop-auf)
- [Programme starten](#programme-starten)
- [Schnellstart-Suche](#schnellstart-suche)
- [Start-Gruppen – mehrere Programme mit einem Klick](#start-gruppen--mehrere-programme-mit-einem-klick)
- [Boxen auf dem Desktop](#boxen-auf-dem-desktop)
- [Desktop-Seiten](#desktop-seiten)
- [Den Desktop schick machen](#den-desktop-schick-machen)
- [Automatisch sortieren](#automatisch-sortieren)
- [Weitere Extras](#weitere-extras)
- [Alle Tastenkürzel](#alle-tastenkürzel)
- [Hoppla, falsch geklickt?](#hoppla-falsch-geklickt)
- [Häufige Fragen](#häufige-fragen)
- [Deinstallieren](#deinstallieren)
- [Lizenz](#lizenz)
---
## Auf einen Blick
| | |
|---|---|
| 🗂️ **Eigene Kategorien** | mit Symbol und Farbe, Reihenfolge frei wählbar |
| ✅ **Sicher aufräumen** | Icons werden gesichert, bevor du sie vom Desktop löschst |
| 🚀 **Schnellstart-Suche** | Alt + Umschalt + Leertaste, ein paar Buchstaben, Enter – läuft |
| ▶️ **Start-Gruppen** | ein Klick startet mehrere Programme auf einmal |
| 🧱 **Desktop-Boxen** | Kategorien oder Ordner als Kästen direkt auf dem Desktop, mit Tabs und eigenem Aussehen |
| 📑 **Desktop-Seiten** | mehrere Desktops zum Umschalten, z. B. „Arbeit“ und „Freizeit“ – auch automatisch nach Uhrzeit |
| ⭐ **Meistgenutzt zuerst** | DeskCore merkt sich, was du oft startest, und sortiert danach |
| 🎨 **Icons einfärben** | alle Desktop-Icons in einem einheitlichen Farbton |
| 👁️ **Schnell verstecken** | Doppelklick auf den Desktop – alles weg, noch ein Doppelklick – alles wieder da |
| ⚡ **Automatisch sortieren** | neue Icons landen per Regel von selbst in der richtigen Kategorie |
| 🧹 **Räumt selbst auf** | deinstallierte Programme verschwinden auch aus DeskCore |
| 🔍 **Suche** | jedes Programm in Sekunden finden |
| ↩️ **Rückgängig** | Strg + Z macht jeden Schritt rückgängig |
| 🌙 **Hell &amp; Dunkel** | passt sich Windows an oder nach deinem Geschmack |
| 💾 **Sicherung** | alles als ZIP sichern, z. B. für einen neuen PC |
| 🔄 **Updates** | DeskCore sagt Bescheid, wenn es eine neue Version gibt |
---
## Was du brauchst
- Windows 10 oder Windows 11 (64 Bit)
- Das war's. Falls DeskCore noch etwas von Microsoft braucht („.NET“), erledigt das der Installer automatisch.
---
## Installieren
1. Doppelklick auf **`DeskCore-Setup.exe`**
2. Windows fragt: **„Möchten Sie zulassen, dass durch diese App Änderungen an Ihrem Gerät vorgenommen werden?“** → **Ja**
3. **Weiter** klicken
4. Die Lizenz lesen, den Haken bei **„Ich akzeptiere …“** setzen, dann **Weiter**
5. Bei Bedarf noch auswählen:
- Verknüpfung auf dem Desktop (empfohlen)
- Mit Windows starten
6. **Installieren** klicken, und fertig.
Der Installer braucht **Administratorrechte**, weil er bei Bedarf die von DeskCore benötigte Microsoft-Laufzeit („.NET“) gleich mitinstalliert.
DeskCore selbst läuft danach ganz normal **ohne** Administratorrechte.
> **Windows warnt beim Start des Installers?**
> Bei neuen Programmen zeigt Windows manchmal „Der Computer wurde durch Windows geschützt“.
> Klick dann auf **„Weitere Informationen“** und danach auf **„Trotzdem ausführen“**.
**Update auf eine neue Version:** Einfach den neuen Installer starten – oder in DeskCore auf **„Jetzt aktualisieren“** klicken und die Windows-Abfrage mit **Ja** bestätigen. Deine Kategorien und Einstellungen bleiben erhalten.
---
## So räumst du deinen Desktop auf
### 1. Kategorie anlegen
Unten links auf **„+ Neue Kategorie“** klicken und einen Namen eingeben, z. B. **Spiele**.
Auf Wunsch suchst du dir noch ein Symbol und eine Farbe aus.
### 2. Icons hineinziehen
Zieh ein Icon **vom Desktop** mit der Maus auf deine Kategorie links.
Es wird jetzt in DeskCore **gesichert**. Auf dem Desktop bleibt es vorerst liegen.
> Tipp: Du kannst auch mehrere Icons auf einmal markieren und ziehen.
<p align="center">
<img src="docs/kategorie.png" width="820" alt="Eine Kategorie in DeskCore">
<br><sub>Eine fertige Kategorie: Alle Programme ordentlich beisammen, starten per Doppelklick.</sub>
</p>
### 3. Icons vom Desktop löschen
Klick links auf **„Desktop“**. Icons mit einem **grünen Haken ✓** sind schon gesichert.
Die kannst du ohne Sorge vom Desktop löschen:
**Rechtsklick → „Vom Desktop löschen“** oder ganz normal auf dem Desktop löschen.
**Fertig!** Dein Desktop ist leer, und deine Programme findest du jetzt in DeskCore.
### Kategorien verwalten
- **Umbenennen, Symbol oder Farbe ändern:** Kategorie anklicken → oben **„Bearbeiten“**
- **Reihenfolge ändern:** Kategorie links mit der Maus **nach oben oder unten ziehen** (oder Rechtsklick → **Nach oben / Nach unten**)
- **Ordner im Explorer ansehen:** oben **„Im Explorer öffnen“**
- **Löschen:** oben **„Kategorie löschen“** – die Icons landen im Papierkorb, Strg + Z holt alles zurück
---
## Programme starten
- Kategorie links anklicken
- **Doppelklick** auf das Icon
Oder schneller: **Suchfeld** oben benutzen (Strg + F), den Namen eintippen und Doppelklick.
Die Suche findet Programme in allen Kategorien und auf dem Desktop.
Auch über das **DeskCore-Symbol neben der Uhr** kommst du per Rechtsklick direkt an deine Kategorien.
**Meistgenutzt zuerst:** DeskCore merkt sich, was du wie oft startest. Über den **Ansicht-Knopf** oben rechts →
**„Meistgenutzt zuerst“** stehen deine Lieblingsprogramme in jeder Kategorie ganz vorne.
Was du lange nicht mehr benutzt hast, rutscht mit der Zeit von selbst wieder nach hinten.
---
## Schnellstart-Suche
Programme starten, ohne DeskCore zu öffnen – ähnlich wie die Spotlight-Suche am Mac:
1. **Alt + Umschalt + Leertaste** drücken – egal, was gerade offen ist
2. Ein paar Buchstaben tippen
3. **Enter** – das Programm startet
Gefunden wird alles aus deinen **Kategorien**, vom **Desktop** und aus dem **Windows-Startmenü**,
dazu deine **Start-Gruppen**, **Kategorien** (öffnet DeskCore dort) und **Desktop-Seiten**.
Was du oft startest, steht weiter oben. Ohne Eingabe siehst du direkt deine meistgenutzten Programme.
Die Suche ist großzügig: **„vsc“** findet **V**isual **S**tudio **C**ode, **„fzla“** findet **F**ile**Z**i**l**l**a**.
| Taste | Was passiert |
|---|---|
| **↑ / ↓** | Treffer auswählen |
| **Enter** | Starten |
| **Strg + Enter** | Speicherort im Explorer öffnen |
| **Esc** | Schließen (oder einfach woanders hinklicken) |
Das Tastenkürzel änderst du unter **Einstellungen → Schnellstart-Suche** (ins Feld klicken, neue Kombination drücken).
Dort kannst du auch einstellen, ob das Windows-Startmenü mit durchsucht wird.
---
## Start-Gruppen – mehrere Programme mit einem Klick
Du startest jeden Tag dieselben Programme, z. B. Browser, Mail und Teams? Pack sie in eine **Start-Gruppe**.
Ein Klick, und alle gehen nacheinander auf.
**Gruppe anlegen**
1. Links unten bei **„Start-Gruppen“** auf das **+** klicken
2. Namen eingeben, z. B. **Feierabend** oder **Arbeit**
3. Einstellen, wie viele Sekunden zwischen den Programmen liegen sollen (oder „gleichzeitig“)
**Programme hinzufügen**
- Ein Icon mit der Maus **auf die Start-Gruppe ziehen**, oder
- Rechtsklick auf ein Icon → **„Zur Start-Gruppe hinzufügen“**
**Starten**
- Auf das kleine **▶** neben der Gruppe klicken, oder
- Gruppe anklicken → **„Alle starten“**, oder
- Rechtsklick auf das DeskCore-Symbol neben der Uhr → **„… starten“**
**Noch bequemer:** Gruppe anklicken → **„Verknüpfung auf den Desktop“**.
Dann liegt ein Symbol auf dem Desktop, das die ganze Gruppe startet.
Die Reihenfolge änderst du per Rechtsklick auf ein Programm → **„Früher starten“ / „Später starten“**.
Ein Programm aus der Gruppe nehmen: Rechtsklick → **„Aus Start-Gruppe entfernen“**. Das Programm selbst bleibt dabei unangetastet.
---
## Boxen auf dem Desktop
Eine **Box** ist ein Kasten direkt auf deinem Desktop, in dem die Icons einer Kategorie liegen.
So hast du deine Programme auf dem Desktop – aber ordentlich.
### Box anlegen
Es gibt drei Wege:
- Kategorie in DeskCore anklicken → oben **„Als Box auf den Desktop“**
- Mit **gedrückter rechter Maustaste** auf einer freien Stelle des Desktops **einen Rahmen ziehen**. Danach wählst du, welche Kategorie hinein soll – oder legst gleich eine neue an.
(Ein ganz normaler Rechtsklick öffnet weiterhin das gewohnte Windows-Menü.)
- **Einstellungen → Desktop-Boxen → Boxen auf dem Desktop anzeigen** schaltet alle Boxen auf einmal ein oder aus
### Mit Boxen arbeiten
- **Verschieben:** an der Titelleiste ziehen
- **Größe ändern:** am Rand ziehen
- **Einklappen:** Doppelklick auf den Titel – noch ein Doppelklick klappt sie wieder auf. Fährst du mit der Maus über eine eingeklappte Box, geht sie kurz auf.
- **Programm starten:** Doppelklick auf das Icon
- **Rechtsklick auf ein Icon:** Öffnen, Umbenennen, Löschen, in eine andere Kategorie verschieben oder **„Zurück auf den Desktop legen“**
- Icons lassen sich auch einfach in eine Box hinein- oder aus ihr herausziehen
- **Eigene Reihenfolge:** Zieh ein Icon innerhalb der Box an die gewünschte Stelle – ein blauer Strich zeigt, wo es landet.
Die Box merkt sich die Reihenfolge (Sortierung „Eigene“).
Das **Menü einer Box** (die drei Punkte ⋯ oben rechts) bietet:
| Menüpunkt | Was passiert |
|---|---|
| **Tab hinzufügen** | Eine weitere Kategorie, ein Ordner-Portal oder **„Zuletzt benutzt“** in dieselbe Box packen. Oben erscheinen dann Reiter zum Umschalten. |
| **Ordner-Portal …** | Einen Ordner als Reiter hinzufügen (siehe unten) |
| **Aussehen …** | Farbe, Deckkraft, Milchglas, Icon-Größe, Icon-Tönung und Sortierung der Box: nach Name, Typ, Datum, **Meistgenutzt** oder **Eigene** Reihenfolge |
| **Sperren** | Die Box lässt sich nicht mehr versehentlich verschieben oder in der Größe ändern |
| **Auf Seite verschieben** | Die Box auf eine andere [Desktop-Seite](#desktop-seiten) schicken |
| **Auf Seite kopieren** | Die gleiche Box zusätzlich auf eine andere Seite legen – z. B. „Internet“ auf „Arbeit“ **und** „Freizeit“ |
| **Box schließen** | Die Box verschwindet vom Desktop. Die Kategorie in DeskCore bleibt natürlich erhalten. |
Rechtsklick auf einen **Reiter**: **„Als eigene Box lösen“** macht daraus wieder eine eigene Box, **„Tab entfernen“** nimmt ihn heraus.
### Box „Zuletzt benutzt“
Eine Box, die sich von selbst füllt: Sie zeigt, was du zuletzt über DeskCore gestartet hast – neueste zuerst.
Anlegen: beim **Aufziehen einer Box** „Zuletzt benutzt“ wählen, oder im Box-Menü ⋯ → **Tab hinzufügen → Zuletzt benutzt**.
Mitgezählt wird alles, was du über DeskCore startest: aus dem Fenster, aus Boxen, über die Schnellstart-Suche und über Start-Gruppen.
### Ordner-Portal
Ein Portal ist eine Box, die **live einen beliebigen Ordner** zeigt, z. B. **Downloads** oder einen Projektordner.
Neue Dateien erscheinen sofort darin, und Unterordner öffnest du direkt in der Box.
Anlegen: **Einstellungen → Desktop-Boxen → Ordner-Portal → „Ordner wählen …“** oder beim Aufziehen einer Box **„Ordner-Portal …“** wählen.
### Peek – Boxen nach vorne holen
Alle Fenster sind offen und verdecken den Desktop? Drück **Win + Umschalt + B**.
Die Boxen kommen nach vorne, über alle Fenster. Nochmal drücken, **Esc** oder woanders hinklicken schickt sie zurück.
Ein- und ausschalten unter **Einstellungen → Peek**.
### Anordnungen speichern (Snapshots)
Du hast deine Boxen perfekt angeordnet? Speicher dir das:
**Einstellungen → Snapshots → „Aktuelle Anordnung speichern“**.
Mit **„Wiederherstellen“** ist später alles wieder genau so – auch über das DeskCore-Symbol neben der Uhr.
Arbeitest du mal am Laptop allein und mal mit Monitor? DeskCore merkt sich die Positionen der Boxen **für jede Bildschirm-Anordnung automatisch**.
---
## Desktop-Seiten
Du kannst **mehrere Desktop-Seiten** anlegen, z. B. **Arbeit** und **Freizeit** – jede Seite hat ihre eigenen Boxen.
So siehst du nach Feierabend nur deine Spiele und tagsüber nur deine Arbeitsprogramme.
**Seiten anlegen:** **Einstellungen → Desktop-Seiten → „Seite hinzufügen“**.
Dort kannst du Seiten auch umbenennen (Stift) oder löschen (Mülleimer). Die Boxen einer gelöschten Seite wandern auf die Seite davor, es geht also nichts verloren.
**Zwischen Seiten wechseln:**
- **Win + Alt + ←** oder **→**
- **Mausrad** drehen, während die Maus über einer freien Stelle des Desktops ist
- Rechtsklick auf das DeskCore-Symbol neben der Uhr → **„Desktop-Seite“**
- in den Einstellungen bei der Seite auf **„Anzeigen“**
Beim Wechseln zeigt DeskCore kurz unten am Bildschirm, auf welcher Seite du bist.
Neue Boxen landen immer auf der Seite, die gerade angezeigt wird. Eine Box auf eine andere Seite schicken: Box-Menü ⋯ → **„Auf Seite verschieben“**.
**Eine Kategorie auf mehreren Seiten:** Jede Seite darf ihre eigene Box mit derselben Kategorie haben – z. B. „Internet“ auf „Arbeit“ und auf „Freizeit“.
Am schnellsten geht das über das Box-Menü ⋯ → **„Auf Seite kopieren“**. Es sind dieselben Icons: Was du in der einen Box änderst, siehst du auch in der anderen.
Nur **auf derselben Seite** steht eine Kategorie in höchstens einer Box.
> Die Icons, die direkt auf dem Desktop liegen (nicht in einer Box), siehst du auf jeder Seite.
### Seiten automatisch wechseln (Zeitplan)
Klick in den Einstellungen bei einer Seite auf die **Uhr** 🕒:
- **„Diese Seite automatisch anzeigen“** – Tage und Uhrzeit wählen, z. B. **Mo–Fr, 08:00 bis 17:00**
- **„Zu allen anderen Zeiten diese Seite anzeigen“** – für die Seite, die sonst gelten soll
Beispiel: „Arbeit“ bekommt Mo–Fr 08:00–17:00, bei „Freizeit“ setzt du den Haken für „alle anderen Zeiten“.
Morgens um acht wechselt DeskCore dann von selbst auf „Arbeit“, um fünf zurück auf „Freizeit“.
Wechselst du zwischendurch von Hand, bleibt deine Wahl bis zum nächsten geplanten Wechsel stehen.
Zeiträume über Mitternacht (z. B. 22:00 bis 06:00) gehen auch.
### Eigenes Hintergrundbild je Seite
Im selben Fenster (**Uhr** 🕒) unter **„Hintergrundbild“** → **„Bild wählen …“**.
Beim Wechsel auf die Seite stellt DeskCore das Bild ein. Wechselst du auf eine Seite ohne eigenes Bild,
kommt dein ursprünglicher Hintergrund zurück.
---
## Den Desktop schick machen
### Desktop-Icons einfärben
**Einstellungen → Desktop-Icons tönen:** eine Farbe anklicken, und alle Icons auf dem Desktop bekommen denselben Farbton.
Mit dem Regler **„Stärke“** stellst du ein, wie kräftig. Der erste, leere Kreis schaltet die Tönung wieder aus.
Die Icons in den Boxen färbst du getrennt über das Box-Menü ⋯ → **„Aussehen …“**.
### Schnell verstecken
**Doppelklick auf eine freie Stelle des Desktops** blendet alle Icons und Boxen aus – perfekt für ein sauberes Hintergrundbild oder eine Bildschirmfreigabe.
Noch ein Doppelklick holt alles zurück. Einschalten unter **Einstellungen → Schnell verstecken**.
### Chamäleon
Die Boxen werden fast unsichtbar und erscheinen erst, wenn du mit der Maus darüberfährst.
Einschalten unter **Einstellungen → Chamäleon**.
### Hell oder Dunkel
Oben rechts auf die **Sonne bzw. den Mond** klicken und auswählen – oder **„Wie Windows“**, dann passt sich DeskCore automatisch an.
### Ansicht
Oben rechts gibt es den **Ansicht-Knopf**: kleine, mittlere oder große Symbole oder eine Liste.
---
## Automatisch sortieren
DeskCore kann neue Icons selbst einsortieren. Sobald z. B. ein Installer eine Verknüpfung auf den Desktop legt, wandert sie in die richtige Kategorie.
So geht's: **Einstellungen → Automatisch sortieren → „Regel hinzufügen“**. Es gibt zwei Arten von Regeln
(zum Umschalten auf die Art der Regel klicken):
| Regel | Beispiel |
|---|---|
| **Name enthält** | alles mit „Steam“ im Namen → **Spiele** |
| **Dateityp ist** | Bilder, Videos, Musik, Dokumente, Archive, Programme, Ordner – oder Endungen wie „pdf, docx“ → **Dokumente** |
Die erste passende Regel gewinnt. Groß- und Kleinschreibung ist egal.
Mit **„Danach vom Desktop entfernen“** verschwindet das Original gleich vom Desktop.
**„Jetzt auf den Desktop anwenden“** sortiert alles ein, was schon auf dem Desktop liegt.
---
## Weitere Extras
### DeskCore schnell öffnen
- **Win + Umschalt + D** drücken, von überall aus (änderbar unter **Einstellungen → Tastenkürzel**)
- oder unten rechts neben der Uhr auf das **DeskCore-Symbol** klicken
Wenn du das Fenster mit dem **X** schließt, läuft DeskCore unten rechts weiter – deine Boxen bleiben also da.
Richtig beenden: **Rechtsklick auf das Symbol neben der Uhr → Beenden**
### Das Symbol neben der Uhr
Ein Rechtsklick darauf bietet dir alles Wichtige, ohne das Fenster zu öffnen:
deine Kategorien, Start-Gruppen, gespeicherte Anordnungen, Desktop-Seiten und **„Desktop ausblenden“**.
### Mit Windows starten
**Einstellungen → Mit Windows starten.** DeskCore startet dann unauffällig neben der Uhr, und Boxen sowie Auto-Sortierung sind sofort da.
### Deinstallierte Programme verschwinden von selbst
Deinstallierst du ein Programm, entfernt DeskCore dessen Icon nach etwa einer Minute auch aus deinen Kategorien – auch bei **Steam- und Epic-Spielen**.
Strg + Z holt es zurück. Ausschalten unter **Einstellungen → Deinstallierte Programme entfernen**.
### Sicherung
**Einstellungen → Sicherung → „Sichern …“** speichert alle Kategorien, Boxen, Start-Gruppen und Einstellungen in eine ZIP-Datei.
Mit **„Wiederherstellen …“** holst du alles zurück, z. B. auf einem neuen PC.
### Updates
DeskCore schaut einmal am Tag nach, ob es eine neue Version gibt.
Wenn ja, erscheint oben ein Hinweis. Ein Klick auf **„Jetzt aktualisieren“** genügt, und DeskCore startet danach von selbst neu.
Prüfen von Hand: **Einstellungen → Updates → „Jetzt prüfen“**.
---
## Alle Tastenkürzel
**Überall in Windows**
| Taste | Was passiert |
|---|---|
| **Alt + Umschalt + Leertaste** | Schnellstart-Suche |
| **Win + Umschalt + D** | DeskCore öffnen / verstecken |
| **Win + Umschalt + B** | Peek: Boxen nach vorne holen |
| **Win + Alt + ← / →** | Desktop-Seite wechseln |
**Im DeskCore-Fenster**
| Taste | Was passiert |
|---|---|
| **Strg + F** | Suchen |
| **Esc** | Suche leeren |
| **Strg + A** | Alles markieren |
| **Enter** | Öffnen |
| **F2** | Umbenennen |
| **Entf** | Löschen |
| **Strg + Z** | Rückgängig |
| **Strg + N** | Neue Kategorie |
| **Alt + ↑ / ↓** | Kategorie nach oben / unten verschieben |
| **Strg + 1 … 9** | Zur Kategorie 1 … 9 springen |
| **F5** | Aktualisieren |
---
## Hoppla, falsch geklickt?
**Strg + Z** macht den letzten Schritt rückgängig – auch mehrmals hintereinander.
Unten im Fenster erscheint nach vielen Aktionen auch kurz ein Knopf **„Rückgängig“**.
---
## Häufige Fragen
**Wo liegen meine Icons eigentlich?**
Im Ordner `C:\Benutzer\DEIN-NAME\DeskCore`. Jede Kategorie ist dort ein ganz normaler Ordner.
Auch wenn DeskCore mal nicht läuft, ist also nichts weg.
**Ich habe etwas gelöscht. Ist es für immer weg?**
Nein. Erst geht **Strg + Z**, und danach liegt es noch im **Papierkorb** von Windows.
**Ich habe ein Programm deinstalliert – verschwindet es auch aus DeskCore?**
Ja, automatisch nach etwa einer Minute – auch bei **Steam- und Epic-Spielen**. Hast du es dir anders überlegt: **Strg + Z** holt es zurück.
Programme auf einem gerade abgesteckten USB- oder Netzlaufwerk bleiben natürlich erhalten.
**Mein Rechtsklick auf den Desktop fühlt sich anders an.**
Damit du Boxen aufziehen kannst, schaut DeskCore kurz, ob du ziehst oder nur klickst. Das Windows-Menü erscheint deshalb erst beim **Loslassen** der Taste.
Stört dich das: **Einstellungen → Box auf dem Desktop aufziehen** ausschalten.
**Das Mausrad wechselt ungewollt die Desktop-Seite.**
Unter **Einstellungen → Desktop-Seiten** den Haken bei **„Mit dem Mausrad … wechseln“** entfernen.
Mit gedrückter **Strg**-Taste ändert das Mausrad wie gewohnt die Icon-Größe des Desktops.
**Die Farbe der Icon-Tönung sitzt nicht genau auf den Icons.**
Kurz warten – DeskCore liest die Positionen alle zwei Sekunden neu. Hast du gerade die Bildschirmskalierung geändert, hilft ein Neustart von DeskCore.
**Ein Icon lässt sich nicht verschieben („Keine Berechtigung“).**
Manche Icons gehören allen Benutzern des PCs. Dafür DeskCore einmal mit
**Rechtsklick → „Als Administrator ausführen“** starten.
**Ich habe einen großen Ordner hineingezogen. Ist der jetzt doppelt da?**
DeskCore fragt bei großen Ordnern nach. Wählst du **„Verschieben“**, liegt er nur einmal da und belegt keinen doppelten Platz.
Beim Kopieren siehst du einen Fortschrittsbalken und kannst jederzeit **abbrechen**. Dann bleibt alles wie vorher.
**Ich habe einen Bildschirm abgesteckt und das Fenster ist weg.**
Keine Sorge: DeskCore und die Desktop-Boxen holen sich automatisch auf den verbliebenen Bildschirm zurück.
Steckst du den Bildschirm wieder an, kommen die Boxen an ihre alten Plätze.
**Ich bekomme einen neuen PC. Was jetzt?**
**Einstellungen → Sicherung → „Sichern …“** speichert alles in eine ZIP-Datei.
Auf dem neuen PC installierst du DeskCore und wählst **„Wiederherstellen …“**.
**Merkt sich DeskCore, welche Programme ich starte? Geht das irgendwohin?**
Nur auf deinem PC: in der Datei `usage.json` im DeskCore-Einstellungsordner. Gezählt wird nur, was du **über DeskCore** startest.
Nichts davon verlässt deinen Rechner.
**Ein Tastenkürzel funktioniert nicht.**
Ein anderes Programm benutzt es vielleicht schon. Unter **Einstellungen → Tastenkürzel** ins Feld klicken und eine andere Kombination drücken.
**Kann ich DeskCore auch per Verknüpfung oder Skript steuern?**
Ja, für Fortgeschrittene:
| Befehl | Was passiert |
|---|---|
| `DeskCore.exe --launch "Feierabend"` | Start-Gruppe „Feierabend“ starten |
| `DeskCore.exe --snapshot "Arbeit"` | Gespeicherte Anordnung „Arbeit“ wiederherstellen |
| `DeskCore.exe --page "Freizeit"` | Zur Desktop-Seite „Freizeit“ wechseln (oder `--page 2`) |
| `DeskCore.exe --toggle-desktop` | Desktop aus- / einblenden |
| `DeskCore.exe --peek` | Boxen nach vorne holen |
| `DeskCore.exe --search` | Schnellstart-Suche öffnen |
---
## Deinstallieren
**Windows-Einstellungen → Apps → DeskCore → Deinstallieren**
Deine Kategorien bleiben dabei **standardmäßig erhalten**.
Willst du sie auch entfernen, setz beim Deinstallieren den Haken. Dann landen sie im Papierkorb.
---
## Lizenz
DeskCore ist nur für den **privaten, nicht-kommerziellen** Gebrauch.
Weitergeben, Verändern und Verkaufen ist nicht erlaubt.
Den vollständigen Text findest du in der Datei `LICENSE.txt` oder in DeskCore unter **Einstellungen → Info → Lizenz**.
© M_Viper · [m-viper.de](https://m-viper.de)
+48 -39
View File
@@ -1,39 +1,48 @@
using System.Windows;
namespace DeskCore;
/// <summary>Bildschirm-Hilfen, damit Fenster nie außerhalb des sichtbaren Bereichs landen (z. B. nach Abstecken eines Monitors).</summary>
public static class Screens
{
/// <summary>Umrechnung Pixel → WPF-Einheiten (System-DPI).</summary>
private static double Scale
{
get
{
using var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero);
return g.DpiX / 96.0;
}
}
/// <summary>Arbeitsbereiche (ohne Taskleiste) aller angeschlossenen Bildschirme in WPF-Einheiten.</summary>
public static List<Rect> WorkAreas()
{
var s = Scale;
return System.Windows.Forms.Screen.AllScreens
.Select(m => new Rect(m.WorkingArea.X / s, m.WorkingArea.Y / s, m.WorkingArea.Width / s, m.WorkingArea.Height / s))
.ToList();
}
public static Rect PrimaryWorkArea => SystemParameters.WorkArea;
/// <summary>Ist genug vom Fenster (Titelleiste) auf irgendeinem Bildschirm sichtbar, um es greifen zu können?</summary>
public static bool IsReachable(Rect window)
{
var title = new Rect(window.X, window.Y, window.Width, Math.Min(40, window.Height));
return WorkAreas().Any(a =>
{
var i = Rect.Intersect(a, title);
return !i.IsEmpty && i.Width >= Math.Min(120, window.Width / 2) && i.Height >= 20;
});
}
}
using System.Windows;
namespace DeskCore;
/// <summary>Bildschirm-Hilfen, damit Fenster nie außerhalb des sichtbaren Bereichs landen (z. B. nach Abstecken eines Monitors).</summary>
public static class Screens
{
/// <summary>Umrechnung Pixel → WPF-Einheiten (System-DPI).</summary>
public static double Scale
{
get
{
using var g = System.Drawing.Graphics.FromHwnd(IntPtr.Zero);
return g.DpiX / 96.0;
}
}
/// <summary>Arbeitsbereiche (ohne Taskleiste) aller angeschlossenen Bildschirme in WPF-Einheiten.</summary>
public static List<Rect> WorkAreas()
{
var s = Scale;
return System.Windows.Forms.Screen.AllScreens
.Select(m => new Rect(m.WorkingArea.X / s, m.WorkingArea.Y / s, m.WorkingArea.Width / s, m.WorkingArea.Height / s))
.ToList();
}
public static Rect PrimaryWorkArea => SystemParameters.WorkArea;
/// <summary>Arbeitsbereich des Bildschirms, auf dem der Punkt liegt (sonst der nächstgelegene).</summary>
public static Rect WorkAreaAt(Point p)
{
var areas = WorkAreas();
return areas.FirstOrDefault(a => a.Contains(p), areas.OrderBy(a =>
Math.Pow(Math.Clamp(p.X, a.Left, a.Right) - p.X, 2) + Math.Pow(Math.Clamp(p.Y, a.Top, a.Bottom) - p.Y, 2))
.FirstOrDefault(PrimaryWorkArea));
}
/// <summary>Ist genug vom Fenster (Titelleiste) auf irgendeinem Bildschirm sichtbar, um es greifen zu können?</summary>
public static bool IsReachable(Rect window)
{
var title = new Rect(window.X, window.Y, window.Width, Math.Min(40, window.Height));
return WorkAreas().Any(a =>
{
var i = Rect.Intersect(a, title);
return !i.IsEmpty && i.Width >= Math.Min(120, window.Width / 2) && i.Height >= 20;
});
}
}
+384 -141
View File
@@ -1,141 +1,384 @@
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DeskCore;
public enum DesignMode { System, Light, Dark }
public enum ViewMode { Small, Medium, Large, List }
public class CategoryMeta
{
public string? Glyph { get; set; }
public string? Color { get; set; }
public bool Fence { get; set; }
public double? FenceX { get; set; }
public double? FenceY { get; set; }
public double FenceW { get; set; } = 340;
public double FenceH { get; set; } = 260;
public bool FenceCollapsed { get; set; }
}
public class SortRule
{
public string Contains { get; set; } = "";
public string Category { get; set; } = "";
}
/// <summary>Alle Einstellungen, gespeichert in %APPDATA%\DeskCore\settings.json.</summary>
public class AppSettings
{
public DesignMode Design { get; set; } = DesignMode.System;
public ViewMode View { get; set; } = ViewMode.Medium;
public bool TrayEnabled { get; set; } = true;
public bool CloseToTray { get; set; } = true;
public bool TrayHintShown { get; set; }
public bool HotkeyEnabled { get; set; } = true;
public int HotkeyModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift;
public int HotkeyKey { get; set; } = 0x44; // D
public bool AutoSort { get; set; }
public bool AutoSortRemoveFromDesktop { get; set; }
public List<SortRule> Rules { get; set; } = [];
public bool FencesEnabled { get; set; } = true;
// Fenster merken (WPF-Einheiten)
public double? WindowLeft { get; set; }
public double? WindowTop { get; set; }
public double? WindowWidth { get; set; }
public double? WindowHeight { get; set; }
public bool WindowMaximized { get; set; }
// Updates
public bool AutoUpdateCheck { get; set; } = true;
public DateTime? LastUpdateCheck { get; set; }
public string? SkippedVersion { get; set; }
public List<string> CategoryOrder { get; set; } = [];
public Dictionary<string, CategoryMeta> Categories { get; set; } = new(StringComparer.OrdinalIgnoreCase);
// ---------- Laden/Speichern ----------
public static readonly string Folder = Migration.Pick(Environment.SpecialFolder.ApplicationData);
private static readonly string FilePath = Path.Combine(Folder, "settings.json");
public static readonly JsonSerializerOptions Json = new()
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() },
};
public static AppSettings Current { get; private set; } = Load();
public static event Action? Saved;
private static AppSettings Load()
{
AppSettings s;
try
{
s = File.Exists(FilePath)
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(FilePath), Json) ?? new()
: new();
}
catch { s = new(); }
s.Categories = new(s.Categories ?? [], StringComparer.OrdinalIgnoreCase);
s.Rules ??= [];
s.CategoryOrder ??= [];
// Übernahme der alten Einstellung aus Version 2
var old = Path.Combine(Folder, "design.txt");
if (File.Exists(old))
{
try
{
var v = File.ReadAllText(old).Trim();
s.Design = v == "Light" ? DesignMode.Light : v == "Dark" ? DesignMode.Dark : DesignMode.System;
File.Delete(old);
}
catch { }
}
return s;
}
public void Save()
{
try
{
Directory.CreateDirectory(Folder);
File.WriteAllText(FilePath, JsonSerializer.Serialize(this, Json));
}
catch { }
Saved?.Invoke();
}
public CategoryMeta Meta(string category)
{
if (!Categories.TryGetValue(category, out var m))
Categories[category] = m = new CategoryMeta();
return m;
}
public void RenameCategory(string oldName, string newName)
{
if (Categories.Remove(oldName, out var m)) Categories[newName] = m;
for (int i = 0; i < CategoryOrder.Count; i++)
if (string.Equals(CategoryOrder[i], oldName, StringComparison.OrdinalIgnoreCase)) CategoryOrder[i] = newName;
foreach (var r in Rules)
if (string.Equals(r.Category, oldName, StringComparison.OrdinalIgnoreCase)) r.Category = newName;
}
public void RemoveCategory(string name)
{
Categories.Remove(name);
CategoryOrder.RemoveAll(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase));
Rules.RemoveAll(r => string.Equals(r.Category, name, StringComparison.OrdinalIgnoreCase));
}
}
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DeskCore;
public enum DesignMode { System, Light, Dark }
public enum ViewMode { Small, Medium, Large, List }
public class CategoryMeta
{
public string? Glyph { get; set; }
public string? Color { get; set; }
// Die folgenden Box-Felder stammen aus Version 1.0.x und werden nur noch beim Umstieg auf FenceDef gelesen
public bool Fence { get; set; }
public double? FenceX { get; set; }
public double? FenceY { get; set; }
public double FenceW { get; set; } = 340;
public double FenceH { get; set; } = 260;
public bool FenceCollapsed { get; set; }
}
/// <summary>Eine Start-Gruppe: ein Klick startet mehrere Programme nacheinander.</summary>
public class LaunchGroup
{
public string Name { get; set; } = "";
/// <summary>Pfade in Startreihenfolge (meist Verknüpfungen aus den Kategorien).</summary>
public List<string> Items { get; set; } = [];
/// <summary>Wartezeit zwischen zwei Programmen in Sekunden.</summary>
public double DelaySeconds { get; set; } = 1;
public string? Color { get; set; }
}
public class SortRule
{
/// <summary>"Name" = Name enthält …, "Typ" = Dateityp ist … (z. B. "Bilder" oder "pdf, docx")</summary>
public string Kind { get; set; } = "Name";
public string Contains { get; set; } = "";
public string Category { get; set; } = "";
[JsonIgnore] public string KindText => Kind == "Typ" ? "Dateityp ist" : "Name enthält";
}
/// <summary>Ein Tab einer Desktop-Box: eine Kategorie, ein beliebiger Ordner ("Ordner-Portal") oder "Zuletzt benutzt".</summary>
public class FenceTab
{
public const string CategoryKind = "Kategorie", FolderKind = "Ordner", RecentKind = "Zuletzt";
public string Kind { get; set; } = CategoryKind;
/// <summary>Kategoriename bzw. Ordnerpfad (bei "Zuletzt benutzt" leer).</summary>
public string Value { get; set; } = "";
/// <summary>Eigene Reihenfolge (Dateinamen), wenn die Box nach "Eigene" sortiert ist.</summary>
public List<string> Order { get; set; } = [];
[JsonIgnore] public bool IsFolder => Kind == FolderKind;
[JsonIgnore] public bool IsRecent => Kind == RecentKind;
[JsonIgnore] public bool IsCategory => Kind == CategoryKind;
[JsonIgnore] public string Title => IsRecent ? "Zuletzt benutzt"
: IsFolder ? Path.GetFileName(Value.TrimEnd('\\')) is { Length: > 0 } n ? n : Value : Value;
}
/// <summary>Zusatzeinstellungen einer Desktop-Seite: Zeitplan und Hintergrundbild.</summary>
public class PageOption
{
/// <summary>Seite automatisch zu bestimmten Zeiten anzeigen.</summary>
public bool Scheduled { get; set; }
/// <summary>Wochentage als Bits: Montag = 1, Dienstag = 2 … Sonntag = 64.</summary>
public int Days { get; set; } = 0b0011111; // Mo–Fr
public string From { get; set; } = "08:00";
public string To { get; set; } = "17:00";
/// <summary>Eigenes Hintergrundbild für diese Seite (null = nicht ändern).</summary>
public string? Wallpaper { get; set; }
public static int DayBit(DayOfWeek d) => 1 << (((int)d + 6) % 7); // Montag = Bit 0
/// <summary>Gilt der Zeitplan zu diesem Zeitpunkt? Zeiträume über Mitternacht (z. B. 22:00–06:00) gehen auch.</summary>
public bool Matches(DateTime now)
{
if (!Scheduled || !TimeSpan.TryParse(From, out var from) || !TimeSpan.TryParse(To, out var to)) return false;
var t = now.TimeOfDay;
if (from <= to) return (Days & DayBit(now.DayOfWeek)) != 0 && t >= from && t < to;
// über Mitternacht: der Tag zählt, an dem der Zeitraum beginnt
return t >= from ? (Days & DayBit(now.DayOfWeek)) != 0 : t < to && (Days & DayBit(now.AddDays(-1).DayOfWeek)) != 0;
}
}
/// <summary>Eine Desktop-Box mit Position, Tabs und Aussehen.</summary>
public class FenceDef
{
public string Id { get; set; } = Guid.NewGuid().ToString("N");
public List<FenceTab> Tabs { get; set; } = [];
public int ActiveTab { get; set; }
public double? X { get; set; }
public double? Y { get; set; }
public double W { get; set; } = 340;
public double H { get; set; } = 260;
public bool Collapsed { get; set; }
/// <summary>Gesperrt: nicht verschieb- oder vergrößerbar.</summary>
public bool Locked { get; set; }
/// <summary>Hintergrundfarbe (#RRGGBB) oder null = passend zum Design.</summary>
public string? Background { get; set; }
/// <summary>Deckkraft des Hintergrunds 0.2–1.</summary>
public double Opacity { get; set; } = 0.92;
/// <summary>Milchglas-Effekt (Hintergrund verschwommen).</summary>
public bool Blur { get; set; }
public ViewMode IconSize { get; set; } = ViewMode.Medium;
/// <summary>"Name", "Typ", "Datum", "Häufig" (meistgenutzt zuerst) oder "Eigene" (per Ziehen angeordnet)</summary>
public string Sort { get; set; } = "Name";
/// <summary>Icon-Tönung (#RRGGBB) oder null.</summary>
public string? Tint { get; set; }
/// <summary>Desktop-Seite, auf der die Box liegt (0 = erste Seite).</summary>
public int Page { get; set; }
[JsonIgnore] public FenceTab? Active => Tabs.Count == 0 ? null : Tabs[Math.Clamp(ActiveTab, 0, Tabs.Count - 1)];
}
/// <summary>Gespeicherte Anordnung aller Desktop-Boxen.</summary>
public class FenceSnapshot
{
public string Name { get; set; } = "";
public DateTime Created { get; set; } = DateTime.Now;
public List<FenceDef> Fences { get; set; } = [];
[JsonIgnore] public string Info => $"{Fences.Count} {(Fences.Count == 1 ? "Box" : "Boxen")} · gespeichert am {Created:dd.MM.yyyy HH:mm}";
}
/// <summary>Alle Einstellungen, gespeichert in %APPDATA%\DeskCore\settings.json.</summary>
public class AppSettings
{
public DesignMode Design { get; set; } = DesignMode.System;
public ViewMode View { get; set; } = ViewMode.Medium;
public bool TrayEnabled { get; set; } = true;
public bool CloseToTray { get; set; } = true;
public bool TrayHintShown { get; set; }
public bool HotkeyEnabled { get; set; } = true;
public int HotkeyModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift;
public int HotkeyKey { get; set; } = 0x44; // D
public bool AutoSort { get; set; }
public bool AutoSortRemoveFromDesktop { get; set; }
public List<SortRule> Rules { get; set; } = [];
public bool FencesEnabled { get; set; } = true;
public List<FenceDef> Fences { get; set; } = [];
public List<FenceSnapshot> Snapshots { get; set; } = [];
/// <summary>Box-Positionen je Bildschirm-Anordnung (Signatur → Box-Id → x, y, w, h).</summary>
public Dictionary<string, Dictionary<string, double[]>> ScreenLayouts { get; set; } = [];
/// <summary>Chamäleon: Boxen blenden sich aus, bis die Maus darüberfährt.</summary>
public bool FenceChameleon { get; set; }
/// <summary>Doppelklick auf eine freie Desktop-Stelle blendet Icons und Boxen aus/ein.</summary>
public bool QuickHide { get; set; } = true;
/// <summary>Peek: Tastenkürzel holt alle Boxen vor die Fenster.</summary>
public bool PeekEnabled { get; set; } = true;
public int PeekModifiers { get; set; } = Hotkey.ModWin | Hotkey.ModShift;
public int PeekKey { get; set; } = 0x42; // B wie "Boxen"
/// <summary>Desktop-Seiten: jede Seite hat ihre eigenen Boxen.</summary>
public List<string> Pages { get; set; } = ["Seite 1"];
public int CurrentPage { get; set; }
/// <summary>Mausrad über einer freien Desktop-Stelle wechselt die Seite.</summary>
public bool PageWheel { get; set; } = true;
/// <summary>Zeitplan und Hintergrundbild je Seite (Schlüssel = Seitenname).</summary>
public Dictionary<string, PageOption> PageOptions { get; set; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Seite, die außerhalb aller Zeitpläne angezeigt wird (null = keine).</summary>
public string? PageFallback { get; set; }
/// <summary>Hintergrundbild, das vor dem ersten Seiten-Hintergrund eingestellt war (zum Zurückholen).</summary>
public string? SavedWallpaper { get; set; }
/// <summary>Schnellstart-Suche per Tastenkürzel.</summary>
public bool QuickSearchEnabled { get; set; } = true;
public int QuickSearchModifiers { get; set; } = Hotkey.ModAlt | Hotkey.ModShift;
public int QuickSearchKey { get; set; } = 0x20; // Leertaste
/// <summary>Programme aus dem Windows-Startmenü mit durchsuchen.</summary>
public bool QuickSearchStartMenu { get; set; } = true;
/// <summary>Sortierung im Hauptfenster: "Name" oder "Häufig".</summary>
public string MainSort { get; set; } = "Name";
/// <summary>Rechte Maustaste auf freier Desktop-Fläche ziehen → neue Box.</summary>
public bool DrawFences { get; set; } = true;
/// <summary>Tönung der echten Desktop-Icons (#RRGGBB) oder null = aus.</summary>
public string? DesktopTint { get; set; }
public double DesktopTintStrength { get; set; } = 0.55;
/// <summary>Verknüpfungen zu deinstallierten Programmen automatisch entfernen.</summary>
public bool RemoveDeadShortcuts { get; set; } = true;
/// <summary>Tote Verknüpfungen, die der Nutzer per Rückgängig behalten wollte.</summary>
public List<string> KeptShortcuts { get; set; } = [];
// Fenster merken (WPF-Einheiten)
public double? WindowLeft { get; set; }
public double? WindowTop { get; set; }
public double? WindowWidth { get; set; }
public double? WindowHeight { get; set; }
public bool WindowMaximized { get; set; }
// Updates
public bool AutoUpdateCheck { get; set; } = true;
public DateTime? LastUpdateCheck { get; set; }
public string? SkippedVersion { get; set; }
public List<string> CategoryOrder { get; set; } = [];
public List<LaunchGroup> LaunchGroups { get; set; } = [];
public Dictionary<string, CategoryMeta> Categories { get; set; } = new(StringComparer.OrdinalIgnoreCase);
// ---------- Laden/Speichern ----------
public static readonly string Folder = Migration.Pick(Environment.SpecialFolder.ApplicationData);
private static readonly string FilePath = Path.Combine(Folder, "settings.json");
public static readonly JsonSerializerOptions Json = new()
{
WriteIndented = true,
Converters = { new JsonStringEnumConverter() },
};
public static AppSettings Current { get; private set; } = Load();
public static event Action? Saved;
private static AppSettings Load()
{
AppSettings s;
try
{
s = File.Exists(FilePath)
? JsonSerializer.Deserialize<AppSettings>(File.ReadAllText(FilePath), Json) ?? new()
: new();
}
catch { s = new(); }
s.Categories = new(s.Categories ?? [], StringComparer.OrdinalIgnoreCase);
s.Rules ??= [];
s.CategoryOrder ??= [];
s.KeptShortcuts ??= [];
s.LaunchGroups ??= [];
s.Fences ??= [];
s.Snapshots ??= [];
s.ScreenLayouts ??= [];
s.PageOptions = new(s.PageOptions ?? [], StringComparer.OrdinalIgnoreCase);
foreach (var t in s.Fences.SelectMany(f => f.Tabs)) t.Order ??= [];
if (s.Pages is null || s.Pages.Count == 0) s.Pages = ["Seite 1"];
s.CurrentPage = Math.Clamp(s.CurrentPage, 0, s.Pages.Count - 1);
// Umstieg von 1.0.x: Box gehörte fest zu einer Kategorie → eigenständige Box mit einem Tab
foreach (var (name, meta) in s.Categories)
{
if (!meta.Fence) continue;
s.Fences.Add(new FenceDef
{
Tabs = [new FenceTab { Value = name }],
X = meta.FenceX, Y = meta.FenceY, W = meta.FenceW, H = meta.FenceH, Collapsed = meta.FenceCollapsed,
});
meta.Fence = false;
}
// Übernahme der alten Einstellung aus Version 2
var old = Path.Combine(Folder, "design.txt");
if (File.Exists(old))
{
try
{
var v = File.ReadAllText(old).Trim();
s.Design = v == "Light" ? DesignMode.Light : v == "Dark" ? DesignMode.Dark : DesignMode.System;
File.Delete(old);
}
catch { }
}
return s;
}
public void Save()
{
try
{
Directory.CreateDirectory(Folder);
File.WriteAllText(FilePath, JsonSerializer.Serialize(this, Json));
}
catch { }
Saved?.Invoke();
}
public CategoryMeta Meta(string category)
{
if (!Categories.TryGetValue(category, out var m))
Categories[category] = m = new CategoryMeta();
return m;
}
public void RenameCategory(string oldName, string newName)
{
if (Categories.Remove(oldName, out var m)) Categories[newName] = m;
for (int i = 0; i < CategoryOrder.Count; i++)
if (string.Equals(CategoryOrder[i], oldName, StringComparison.OrdinalIgnoreCase)) CategoryOrder[i] = newName;
foreach (var r in Rules)
if (string.Equals(r.Category, oldName, StringComparison.OrdinalIgnoreCase)) r.Category = newName;
foreach (var t in Fences.SelectMany(f => f.Tabs))
if (!t.IsFolder && string.Equals(t.Value, oldName, StringComparison.OrdinalIgnoreCase)) t.Value = newName;
// Start-Gruppen zeigen auf Dateien in der Kategorie → Pfade mit umbenennen
var oldDir = Storage.CategoryPath(oldName).TrimEnd('\\') + "\\";
var newDir = Storage.CategoryPath(newName).TrimEnd('\\') + "\\";
foreach (var g in LaunchGroups)
for (int i = 0; i < g.Items.Count; i++)
if (g.Items[i].StartsWith(oldDir, StringComparison.OrdinalIgnoreCase))
g.Items[i] = newDir + g.Items[i][oldDir.Length..];
}
/// <summary>Liegt diese Kategorie auf der gerade angezeigten Desktop-Seite in einer Box?</summary>
public bool HasFence(string category) =>
Fences.Any(f => f.Page == CurrentPage &&
f.Tabs.Any(t => t.IsCategory && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase)));
/// <summary>Kategorie auf der aktuellen Seite als eigene Box anzeigen bzw. von dieser Seite entfernen (andere Seiten bleiben).</summary>
public void SetFence(string category, bool on)
{
if (on)
{
if (!HasFence(category)) Fences.Add(new FenceDef { Page = CurrentPage, Tabs = [new FenceTab { Value = category }] });
FencesEnabled = true;
}
else
{
foreach (var f in Fences.Where(f => f.Page == CurrentPage))
f.Tabs.RemoveAll(t => t.IsCategory && string.Equals(t.Value, category, StringComparison.OrdinalIgnoreCase));
Fences.RemoveAll(f => f.Tabs.Count == 0);
}
}
/// <summary>Tiefe Kopie (für Snapshots).</summary>
public static List<FenceDef> CloneFences(List<FenceDef> fences) =>
JsonSerializer.Deserialize<List<FenceDef>>(JsonSerializer.Serialize(fences, Json), Json) ?? [];
/// <summary>Datei wurde umbenannt/verschoben → Start-Gruppen zeigen auf den neuen Pfad.</summary>
public void ReplaceLaunchPath(string oldPath, string newPath)
{
bool changed = false;
foreach (var g in LaunchGroups)
for (int i = 0; i < g.Items.Count; i++)
if (string.Equals(g.Items[i], oldPath, StringComparison.OrdinalIgnoreCase)) { g.Items[i] = newPath; changed = true; }
// Umbenannt im selben Ordner → Platz in der eigenen Box-Reihenfolge behalten
if (Storage.SamePath(Path.GetDirectoryName(oldPath), Path.GetDirectoryName(newPath)))
{
string oldName = Path.GetFileName(oldPath), newName = Path.GetFileName(newPath);
foreach (var t in Fences.SelectMany(f => f.Tabs))
for (int i = 0; i < t.Order.Count; i++)
if (string.Equals(t.Order[i], oldName, StringComparison.OrdinalIgnoreCase)) { t.Order[i] = newName; changed = true; }
}
Usage.Rename(oldPath, newPath);
if (changed) Save();
}
// ---------- Desktop-Seiten ----------
public PageOption PageOption(string page)
{
if (!PageOptions.TryGetValue(page, out var o)) PageOptions[page] = o = new PageOption();
return o;
}
public void RenamePage(int index, string newName)
{
var old = Pages[index];
Pages[index] = newName;
if (PageOptions.Remove(old, out var o)) PageOptions[newName] = o;
if (string.Equals(PageFallback, old, StringComparison.OrdinalIgnoreCase)) PageFallback = newName;
}
/// <summary>Aufräumen nach dem Löschen einer Seite (Boxen verschiebt der FenceManager).</summary>
public void ForgetPage(string name)
{
PageOptions.Remove(name);
if (string.Equals(PageFallback, name, StringComparison.OrdinalIgnoreCase)) PageFallback = null;
}
public void RemoveCategory(string name)
{
foreach (var f in Fences)
f.Tabs.RemoveAll(t => !t.IsFolder && string.Equals(t.Value, name, StringComparison.OrdinalIgnoreCase));
Fences.RemoveAll(f => f.Tabs.Count == 0);
Categories.Remove(name);
CategoryOrder.RemoveAll(n => string.Equals(n, name, StringComparison.OrdinalIgnoreCase));
Rules.RemoveAll(r => string.Equals(r.Category, name, StringComparison.OrdinalIgnoreCase));
}
}
+77 -77
View File
@@ -1,77 +1,77 @@
using System.Windows;
using System.Windows.Media;
using Microsoft.Win32;
namespace DeskCoreSetup;
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
bool uninstall = e.Args.Any(a => a.Equals("--uninstall", StringComparison.OrdinalIgnoreCase));
bool silent = e.Args.Any(a => a.Equals("/S", StringComparison.OrdinalIgnoreCase));
bool launch = e.Args.Any(a => a.Equals("/launch", StringComparison.OrdinalIgnoreCase));
var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?.Substring(3);
// Stille Installation/Deinstallation für Skripte: DeskCore-Setup.exe /S [/D=Ordner] bzw. --uninstall /S
if (silent)
{
var progress = new Progress<(double, string)>(_ => { });
try
{
if (uninstall) Installer.Uninstall(removeData: false, progress);
else
{
var dir = dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir;
Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists,
autostart: Installer.AutostartEnabled, progress, installRuntime: true);
// nach einem Update aus DeskCore heraus: gleich wieder starten
if (launch) System.Diagnostics.Process.Start(System.IO.Path.Combine(dir, Installer.ExeName));
}
Shutdown(0);
}
catch (Exception ex)
{
// ohne Oberfläche: Fehler für Skripte nachvollziehbar ablegen
try { System.IO.File.WriteAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "DeskCore-Setup.log"), $"[{DateTime.Now}] {ex}"); } catch { }
Shutdown(1);
}
return;
}
ApplyTheme();
new MainWindow(uninstall).Show();
}
/// <summary>Hell/Dunkel und Akzentfarbe wie in Windows eingestellt.</summary>
private void ApplyTheme()
{
bool light = ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0;
Resources.MergedDictionaries[0] = new ResourceDictionary
{
Source = new Uri($"pack://application:,,,/Themes/Colors.{(light ? "Light" : "Dark")}.xaml"),
};
int abgr = ReadDword(@"Software\Microsoft\Windows\DWM", "AccentColor") is var v and not -1 ? v : unchecked((int)0xFFD77800);
var accent = Color.FromRgb((byte)abgr, (byte)(abgr >> 8), (byte)(abgr >> 16));
var a = light ? Mix(accent, Colors.Black, 0.12) : Mix(accent, Colors.White, 0.45);
Resources["AccentBrush"] = new SolidColorBrush(a);
Resources["AccentHoverBrush"] = new SolidColorBrush(light ? Mix(a, Colors.White, 0.1) : Mix(a, Colors.Black, 0.1));
Resources["OnAccentBrush"] = new SolidColorBrush(light ? Colors.White : Colors.Black);
Resources["AccentSubtleBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x22 : (byte)0x30, a.R, a.G, a.B));
Resources["AccentSubtleStrokeBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x55 : (byte)0x60, a.R, a.G, a.B));
}
private static int ReadDword(string key, string name)
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(key);
return k?.GetValue(name) is int i ? i : -1;
}
catch { return -1; }
}
private static Color Mix(Color c, Color with, double t) => Color.FromRgb(
(byte)(c.R + (with.R - c.R) * t), (byte)(c.G + (with.G - c.G) * t), (byte)(c.B + (with.B - c.B) * t));
}
using System.Windows;
using System.Windows.Media;
using Microsoft.Win32;
namespace DeskCoreSetup;
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
bool uninstall = e.Args.Any(a => a.Equals("--uninstall", StringComparison.OrdinalIgnoreCase));
bool silent = e.Args.Any(a => a.Equals("/S", StringComparison.OrdinalIgnoreCase));
bool launch = e.Args.Any(a => a.Equals("/launch", StringComparison.OrdinalIgnoreCase));
var dirArg = e.Args.FirstOrDefault(a => a.StartsWith("/D=", StringComparison.OrdinalIgnoreCase))?.Substring(3);
// Stille Installation/Deinstallation für Skripte: DeskCore-Setup.exe /S [/D=Ordner] bzw. --uninstall /S
if (silent)
{
var progress = new Progress<(double, string)>(_ => { });
try
{
if (uninstall) Installer.Uninstall(removeData: false, progress);
else
{
var dir = dirArg ?? Installer.InstalledDir ?? Installer.DefaultDir;
Installer.Install(dir, desktopLink: Installer.InstalledDir is null || Installer.DesktopLinkExists,
autostart: Installer.AutostartEnabled, progress, installRuntime: true);
// nach einem Update aus DeskCore heraus: gleich wieder starten
if (launch) Installer.StartUnelevated(System.IO.Path.Combine(dir, Installer.ExeName));
}
Shutdown(0);
}
catch (Exception ex)
{
// ohne Oberfläche: Fehler für Skripte nachvollziehbar ablegen
try { System.IO.File.WriteAllText(System.IO.Path.Combine(System.IO.Path.GetTempPath(), "DeskCore-Setup.log"), $"[{DateTime.Now}] {ex}"); } catch { }
Shutdown(1);
}
return;
}
ApplyTheme();
new MainWindow(uninstall).Show();
}
/// <summary>Hell/Dunkel und Akzentfarbe wie in Windows eingestellt.</summary>
private void ApplyTheme()
{
bool light = ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0;
Resources.MergedDictionaries[0] = new ResourceDictionary
{
Source = new Uri($"pack://application:,,,/Themes/Colors.{(light ? "Light" : "Dark")}.xaml"),
};
int abgr = ReadDword(@"Software\Microsoft\Windows\DWM", "AccentColor") is var v and not -1 ? v : unchecked((int)0xFFD77800);
var accent = Color.FromRgb((byte)abgr, (byte)(abgr >> 8), (byte)(abgr >> 16));
var a = light ? Mix(accent, Colors.Black, 0.12) : Mix(accent, Colors.White, 0.45);
Resources["AccentBrush"] = new SolidColorBrush(a);
Resources["AccentHoverBrush"] = new SolidColorBrush(light ? Mix(a, Colors.White, 0.1) : Mix(a, Colors.Black, 0.1));
Resources["OnAccentBrush"] = new SolidColorBrush(light ? Colors.White : Colors.Black);
Resources["AccentSubtleBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x22 : (byte)0x30, a.R, a.G, a.B));
Resources["AccentSubtleStrokeBrush"] = new SolidColorBrush(Color.FromArgb(light ? (byte)0x55 : (byte)0x60, a.R, a.G, a.B));
}
private static int ReadDword(string key, string name)
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(key);
return k?.GetValue(name) is int i ? i : -1;
}
catch { return -1; }
}
private static Color Mix(Color c, Color with, double t) => Color.FromRgb(
(byte)(c.R + (with.R - c.R) * t), (byte)(c.G + (with.G - c.G) * t), (byte)(c.B + (with.B - c.B) * t));
}
+2
View File
@@ -15,6 +15,8 @@
<AssemblyName>DeskCore-Setup</AssemblyName>
<RootNamespace>DeskCoreSetup</RootNamespace>
<ApplicationIcon>..\app.ico</ApplicationIcon>
<!-- Installer fragt beim Start einmal nach Administratorrechten (für die .NET-Laufzeit von Microsoft) -->
<ApplicationManifest>app.manifest</ApplicationManifest>
<Product>DeskCore Setup</Product>
<AssemblyTitle>DeskCore Setup</AssemblyTitle>
<Description>Installiert DeskCore</Description>
+401 -388
View File
@@ -1,388 +1,401 @@
using System.Diagnostics;
using System.IO;
using Microsoft.Win32;
namespace DeskCoreSetup;
/// <summary>Installation/Deinstallation – nur für den aktuellen Benutzer, ohne Administratorrechte.</summary>
public static class Installer
{
public const string AppName = "DeskCore";
public const string Publisher = "M_Viper";
public const string ExeName = "DeskCore.exe";
public const string UninstallerName = "DeskCore-Uninstall.exe";
private const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskCore";
private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run";
public static string Version
{
get
{
var v = typeof(Installer).Assembly.GetName().Version!;
return $"{v.Major}.{v.Minor}.{v.Build}";
}
}
/// <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);
private static string StartMenuLink =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), AppName + ".lnk");
private static string DesktopLink =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), AppName + ".lnk");
/// <summary>Ordner einer vorhandenen Installation (oder null).</summary>
public static string? InstalledDir
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(UninstallKey);
return k?.GetValue("InstallLocation") as string is { } d && File.Exists(Path.Combine(d, ExeName)) ? d : null;
}
}
public static string? InstalledVersion
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(UninstallKey);
return k?.GetValue("DisplayVersion") as string;
}
}
public static bool AutostartEnabled
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(RunKey);
return k?.GetValue(AppName) is string;
}
}
public static bool DesktopLinkExists => File.Exists(DesktopLink);
public static string LicenseText => ReadResourceText("payload.LICENSE.txt");
// ---------- Installieren ----------
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();
progress.Report((0.2, "Dateien werden kopiert …"));
Directory.CreateDirectory(dir);
var exe = Path.Combine(dir, ExeName);
WriteResource("payload.DeskCore.exe", exe);
WriteResource("payload.LICENSE.txt", Path.Combine(dir, "LICENSE.txt"));
// Setup selbst als Deinstallationsprogramm ablegen
var self = SelfPath;
var uninstaller = Path.Combine(dir, UninstallerName);
if (!string.Equals(Path.GetFullPath(self), Path.GetFullPath(uninstaller), StringComparison.OrdinalIgnoreCase))
{
MoveAside(uninstaller);
File.Copy(self, uninstaller, overwrite: true);
}
progress.Report((0.55, "Verknüpfungen werden angelegt …"));
CreateShortcut(StartMenuLink, exe);
if (desktopLink) CreateShortcut(DesktopLink, exe);
progress.Report((0.75, "Windows wird eingerichtet …"));
using (var run = Registry.CurrentUser.CreateSubKey(RunKey))
{
if (autostart) run.SetValue(AppName, $"\"{exe}\" --minimized");
else run.DeleteValue(AppName, throwOnMissingValue: false);
}
using (var k = Registry.CurrentUser.CreateSubKey(UninstallKey))
{
long size = new[] { exe, uninstaller, Path.Combine(dir, "LICENSE.txt") }
.Where(File.Exists).Sum(f => new FileInfo(f).Length) / 1024;
k.SetValue("DisplayName", AppName);
k.SetValue("DisplayVersion", Version);
k.SetValue("Publisher", Publisher);
k.SetValue("DisplayIcon", $"{exe},0");
k.SetValue("InstallLocation", dir);
k.SetValue("UninstallString", $"\"{uninstaller}\" --uninstall");
k.SetValue("QuietUninstallString", $"\"{uninstaller}\" --uninstall /S");
k.SetValue("URLInfoAbout", "https://m-viper.de");
k.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
k.SetValue("EstimatedSize", (int)size, RegistryValueKind.DWord);
k.SetValue("NoModify", 1, RegistryValueKind.DWord);
k.SetValue("NoRepair", 1, RegistryValueKind.DWord);
}
progress.Report((1.0, "Fertig"));
}
// ---------- Frühere Version "DeskHub" entfernen ----------
private const string LegacyName = "DeskHub";
private const string LegacyUninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskHub";
/// <summary>Ordner einer alten Installation unter dem früheren Namen "DeskHub" – nur unsere eigene.</summary>
public static string? LegacyInstallDir
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(LegacyUninstallKey);
// "DeskHub" heißt auch ein anderes Programm → nur mit unserem Herausgeber anfassen
if (k?.GetValue("Publisher") as string != Publisher) return null;
return k.GetValue("InstallLocation") as string;
}
}
private static void RemoveLegacyInstall()
{
var dir = LegacyInstallDir;
if (dir is null) return;
var exe = Path.Combine(dir, LegacyName + ".exe");
// nur Prozesse beenden, die wirklich aus dem alten Ordner laufen
foreach (var p in Process.GetProcessesByName(LegacyName))
{
try
{
if (p.MainModule?.FileName is { } f && Storage_IsUnder(f, dir))
{
p.Kill();
p.WaitForExit(10000);
}
}
catch { }
}
DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), LegacyName + ".lnk"), exe);
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.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0)
run.DeleteValue(LegacyName, throwOnMissingValue: false);
}
foreach (var f in new[] { exe, Path.Combine(dir, "LICENSE.txt"), Path.Combine(dir, LegacyName + "-Uninstall.exe") })
MoveAside(f);
CleanupOldFiles(dir);
try { Directory.Delete(dir); } catch { } // nur wenn leer
Registry.CurrentUser.DeleteSubKeyTree(LegacyUninstallKey, throwOnMissingSubKey: false);
}
// ---------- Deinstallieren ----------
public static readonly string[] DataDirs =
[
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), AppName), // Kategorien
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppName), // Einstellungen
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppName), // Rückgängig-Zwischenablage
];
public static void Uninstall(bool removeData, IProgress<(double, string)> progress)
{
// Nur im eingetragenen Installationsordner löschen – nie dort, wo das Setup zufällig liegt (z. B. Downloads)
var dir = InstalledDir;
var exe = dir is null ? "" : Path.Combine(dir, ExeName);
progress.Report((0.1, "DeskCore wird beendet …"));
CloseRunningApp();
progress.Report((0.3, "Verknüpfungen werden entfernt …"));
DeleteIfPointsTo(StartMenuLink, exe);
DeleteIfPointsTo(DesktopLink, exe);
progress.Report((0.45, "Windows-Einträge werden entfernt …"));
using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true))
run?.DeleteValue(AppName, throwOnMissingValue: false);
Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false);
if (removeData)
{
progress.Report((0.6, "Kategorien und Einstellungen werden in den Papierkorb gelegt …"));
foreach (var d in DataDirs.Where(Directory.Exists))
{
try
{
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(d,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
}
catch { }
}
}
progress.Report((0.85, "Dateien werden entfernt …"));
if (dir is null) { progress.Report((1.0, "Fertig")); return; }
TryDelete(exe);
TryDelete(Path.Combine(dir, "LICENSE.txt"));
CleanupOldFiles(dir);
// 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 = SelfPath;
if (Storage_IsUnder(self, dir))
{
Process.Start(new ProcessStartInfo("cmd.exe",
$"/c ping 127.0.0.1 -n 3 > nul & del /f /q \"{self}\" & del /f /q \"{Path.Combine(dir, "*.old")}\" & rmdir \"{dir}\"")
{
CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden,
});
}
else
{
TryDelete(Path.Combine(dir, UninstallerName));
try { Directory.Delete(dir); } catch { } // nur wenn leer
}
progress.Report((1.0, "Fertig"));
}
// ---------- 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()
{
foreach (var p in Process.GetProcessesByName("DeskCore"))
{
try
{
p.Kill();
p.WaitForExit(10000);
}
catch { }
}
}
private static void WriteResource(string name, string path)
{
CleanupOldFiles(Path.GetDirectoryName(path)!);
MoveAside(path);
for (int attempt = 1; ; attempt++)
{
try
{
using var s = typeof(Installer).Assembly.GetManifestResourceStream(name)
?? throw new InvalidOperationException($"Installationsdatei {name} fehlt.");
using var f = File.Create(path);
s.CopyTo(f);
return;
}
catch (IOException) when (attempt < 10)
{
Thread.Sleep(500);
}
catch (IOException) when (attempt >= 10)
{
throw new IOException($"„{Path.GetFileName(path)}“ wird noch verwendet. Bitte DeskCore beenden (Rechtsklick auf das Symbol im Infobereich → Beenden) und erneut versuchen.");
}
}
}
/// <summary>
/// Vorhandene Datei beiseite umbenennen. Das klappt unter Windows auch dann, wenn sie noch geöffnet ist
/// (z. B. vom Explorer für das Icon oder vom Virenscanner) – überschreiben würde dagegen fehlschlagen.
/// </summary>
private static void MoveAside(string path)
{
if (!File.Exists(path)) return;
try { File.Delete(path); return; } catch { }
try { File.Move(path, $"{path}.{Guid.NewGuid():N}.old"); } catch { }
}
/// <summary>Beiseitegelegte Dateien früherer Installationen wegräumen (sofern nicht mehr in Benutzung).</summary>
private static void CleanupOldFiles(string dir)
{
if (!Directory.Exists(dir)) return;
foreach (var f in Directory.GetFiles(dir, "*.old"))
try { File.Delete(f); } catch { }
}
private static string ReadResourceText(string name)
{
using var s = typeof(Installer).Assembly.GetManifestResourceStream(name);
if (s is null) return "";
using var r = new StreamReader(s);
return r.ReadToEnd().Trim();
}
private static void CreateShortcut(string link, string target)
{
Directory.CreateDirectory(Path.GetDirectoryName(link)!);
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
dynamic lnk = shell.CreateShortcut(link);
lnk.TargetPath = target;
lnk.WorkingDirectory = Path.GetDirectoryName(target);
lnk.IconLocation = $"{target},0";
lnk.Description = "Desktop-Icons in Kategorien sortieren";
lnk.Save();
}
private static void DeleteIfPointsTo(string link, string target)
{
if (!File.Exists(link)) return;
try
{
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
string t = shell.CreateShortcut(link).TargetPath;
if (!string.Equals(t, target, StringComparison.OrdinalIgnoreCase)) return;
}
catch { return; }
// Explorer/Indexdienst halten Verknüpfungen manchmal kurz fest → mehrmals versuchen
for (int i = 0; i < 10 && File.Exists(link); i++)
{
try { File.Delete(link); }
catch { Thread.Sleep(300); }
}
}
private static void TryDelete(string path)
{
try { if (File.Exists(path)) File.Delete(path); } catch { }
}
private static bool Storage_IsUnder(string path, string dir) =>
(Path.GetFullPath(path)).StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
public static long DataSize()
{
long sum = 0;
foreach (var d in DataDirs.Where(Directory.Exists))
{
try
{
foreach (var f in Directory.EnumerateFiles(d, "*", SearchOption.AllDirectories))
try { sum += new FileInfo(f).Length; } catch { }
}
catch { }
}
return sum;
}
public static string FormatSize(long bytes) => bytes switch
{
< 1024 * 1024 => $"{Math.Max(1, bytes / 1024)} KB",
< 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:0.#} MB",
_ => $"{bytes / 1024.0 / 1024 / 1024:0.##} GB",
};
}
using System.Diagnostics;
using System.IO;
using Microsoft.Win32;
namespace DeskCoreSetup;
/// <summary>
/// Installation/Deinstallation für den aktuellen Benutzer. Der Installer selbst läuft mit Administratorrechten
/// (wegen der .NET-Laufzeit von Microsoft), DeskCore wird aber immer ohne gestartet.
/// </summary>
public static class Installer
{
/// <summary>
/// DeskCore ohne Administratorrechte starten – über den Explorer, der als normaler Benutzer läuft.
/// Wichtig: Ein Programm mit Adminrechten darf von Windows aus keine Icons per Ziehen vom Desktop annehmen.
/// </summary>
public static void StartUnelevated(string exe)
{
try { Process.Start(new ProcessStartInfo("explorer.exe", $"\"{exe}\"") { UseShellExecute = false }); }
catch { }
}
public const string AppName = "DeskCore";
public const string Publisher = "M_Viper";
public const string ExeName = "DeskCore.exe";
public const string UninstallerName = "DeskCore-Uninstall.exe";
private const string UninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskCore";
private const string RunKey = @"Software\Microsoft\Windows\CurrentVersion\Run";
public static string Version
{
get
{
var v = typeof(Installer).Assembly.GetName().Version!;
return $"{v.Major}.{v.Minor}.{v.Build}";
}
}
/// <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);
private static string StartMenuLink =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), AppName + ".lnk");
private static string DesktopLink =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), AppName + ".lnk");
/// <summary>Ordner einer vorhandenen Installation (oder null).</summary>
public static string? InstalledDir
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(UninstallKey);
return k?.GetValue("InstallLocation") as string is { } d && File.Exists(Path.Combine(d, ExeName)) ? d : null;
}
}
public static string? InstalledVersion
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(UninstallKey);
return k?.GetValue("DisplayVersion") as string;
}
}
public static bool AutostartEnabled
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(RunKey);
return k?.GetValue(AppName) is string;
}
}
public static bool DesktopLinkExists => File.Exists(DesktopLink);
public static string LicenseText => ReadResourceText("payload.LICENSE.txt");
// ---------- Installieren ----------
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();
progress.Report((0.2, "Dateien werden kopiert …"));
Directory.CreateDirectory(dir);
var exe = Path.Combine(dir, ExeName);
WriteResource("payload.DeskCore.exe", exe);
WriteResource("payload.LICENSE.txt", Path.Combine(dir, "LICENSE.txt"));
// Setup selbst als Deinstallationsprogramm ablegen
var self = SelfPath;
var uninstaller = Path.Combine(dir, UninstallerName);
if (!string.Equals(Path.GetFullPath(self), Path.GetFullPath(uninstaller), StringComparison.OrdinalIgnoreCase))
{
MoveAside(uninstaller);
File.Copy(self, uninstaller, overwrite: true);
}
progress.Report((0.55, "Verknüpfungen werden angelegt …"));
CreateShortcut(StartMenuLink, exe);
if (desktopLink) CreateShortcut(DesktopLink, exe);
progress.Report((0.75, "Windows wird eingerichtet …"));
using (var run = Registry.CurrentUser.CreateSubKey(RunKey))
{
if (autostart) run.SetValue(AppName, $"\"{exe}\" --minimized");
else run.DeleteValue(AppName, throwOnMissingValue: false);
}
using (var k = Registry.CurrentUser.CreateSubKey(UninstallKey))
{
long size = new[] { exe, uninstaller, Path.Combine(dir, "LICENSE.txt") }
.Where(File.Exists).Sum(f => new FileInfo(f).Length) / 1024;
k.SetValue("DisplayName", AppName);
k.SetValue("DisplayVersion", Version);
k.SetValue("Publisher", Publisher);
k.SetValue("DisplayIcon", $"{exe},0");
k.SetValue("InstallLocation", dir);
k.SetValue("UninstallString", $"\"{uninstaller}\" --uninstall");
k.SetValue("QuietUninstallString", $"\"{uninstaller}\" --uninstall /S");
k.SetValue("URLInfoAbout", "https://m-viper.de");
k.SetValue("InstallDate", DateTime.Now.ToString("yyyyMMdd"));
k.SetValue("EstimatedSize", (int)size, RegistryValueKind.DWord);
k.SetValue("NoModify", 1, RegistryValueKind.DWord);
k.SetValue("NoRepair", 1, RegistryValueKind.DWord);
}
progress.Report((1.0, "Fertig"));
}
// ---------- Frühere Version "DeskHub" entfernen ----------
private const string LegacyName = "DeskHub";
private const string LegacyUninstallKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall\DeskHub";
/// <summary>Ordner einer alten Installation unter dem früheren Namen "DeskHub" – nur unsere eigene.</summary>
public static string? LegacyInstallDir
{
get
{
using var k = Registry.CurrentUser.OpenSubKey(LegacyUninstallKey);
// "DeskHub" heißt auch ein anderes Programm → nur mit unserem Herausgeber anfassen
if (k?.GetValue("Publisher") as string != Publisher) return null;
return k.GetValue("InstallLocation") as string;
}
}
private static void RemoveLegacyInstall()
{
var dir = LegacyInstallDir;
if (dir is null) return;
var exe = Path.Combine(dir, LegacyName + ".exe");
// nur Prozesse beenden, die wirklich aus dem alten Ordner laufen
foreach (var p in Process.GetProcessesByName(LegacyName))
{
try
{
if (p.MainModule?.FileName is { } f && Storage_IsUnder(f, dir))
{
p.Kill();
p.WaitForExit(10000);
}
}
catch { }
}
DeleteIfPointsTo(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Programs), LegacyName + ".lnk"), exe);
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.IndexOf(exe, StringComparison.OrdinalIgnoreCase) >= 0)
run.DeleteValue(LegacyName, throwOnMissingValue: false);
}
foreach (var f in new[] { exe, Path.Combine(dir, "LICENSE.txt"), Path.Combine(dir, LegacyName + "-Uninstall.exe") })
MoveAside(f);
CleanupOldFiles(dir);
try { Directory.Delete(dir); } catch { } // nur wenn leer
Registry.CurrentUser.DeleteSubKeyTree(LegacyUninstallKey, throwOnMissingSubKey: false);
}
// ---------- Deinstallieren ----------
public static readonly string[] DataDirs =
[
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), AppName), // Kategorien
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppName), // Einstellungen
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), AppName), // Rückgängig-Zwischenablage
];
public static void Uninstall(bool removeData, IProgress<(double, string)> progress)
{
// Nur im eingetragenen Installationsordner löschen – nie dort, wo das Setup zufällig liegt (z. B. Downloads)
var dir = InstalledDir;
var exe = dir is null ? "" : Path.Combine(dir, ExeName);
progress.Report((0.1, "DeskCore wird beendet …"));
CloseRunningApp();
progress.Report((0.3, "Verknüpfungen werden entfernt …"));
DeleteIfPointsTo(StartMenuLink, exe);
DeleteIfPointsTo(DesktopLink, exe);
progress.Report((0.45, "Windows-Einträge werden entfernt …"));
using (var run = Registry.CurrentUser.OpenSubKey(RunKey, writable: true))
run?.DeleteValue(AppName, throwOnMissingValue: false);
Registry.CurrentUser.DeleteSubKeyTree(UninstallKey, throwOnMissingSubKey: false);
if (removeData)
{
progress.Report((0.6, "Kategorien und Einstellungen werden in den Papierkorb gelegt …"));
foreach (var d in DataDirs.Where(Directory.Exists))
{
try
{
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(d,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs,
Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
}
catch { }
}
}
progress.Report((0.85, "Dateien werden entfernt …"));
if (dir is null) { progress.Report((1.0, "Fertig")); return; }
TryDelete(exe);
TryDelete(Path.Combine(dir, "LICENSE.txt"));
CleanupOldFiles(dir);
// 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 = SelfPath;
if (Storage_IsUnder(self, dir))
{
Process.Start(new ProcessStartInfo("cmd.exe",
$"/c ping 127.0.0.1 -n 3 > nul & del /f /q \"{self}\" & del /f /q \"{Path.Combine(dir, "*.old")}\" & rmdir \"{dir}\"")
{
CreateNoWindow = true, UseShellExecute = false, WindowStyle = ProcessWindowStyle.Hidden,
});
}
else
{
TryDelete(Path.Combine(dir, UninstallerName));
try { Directory.Delete(dir); } catch { } // nur wenn leer
}
progress.Report((1.0, "Fertig"));
}
// ---------- 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()
{
foreach (var p in Process.GetProcessesByName("DeskCore"))
{
try
{
p.Kill();
p.WaitForExit(10000);
}
catch { }
}
}
private static void WriteResource(string name, string path)
{
CleanupOldFiles(Path.GetDirectoryName(path)!);
MoveAside(path);
for (int attempt = 1; ; attempt++)
{
try
{
using var s = typeof(Installer).Assembly.GetManifestResourceStream(name)
?? throw new InvalidOperationException($"Installationsdatei {name} fehlt.");
using var f = File.Create(path);
s.CopyTo(f);
return;
}
catch (IOException) when (attempt < 10)
{
Thread.Sleep(500);
}
catch (IOException) when (attempt >= 10)
{
throw new IOException($"„{Path.GetFileName(path)}“ wird noch verwendet. Bitte DeskCore beenden (Rechtsklick auf das Symbol im Infobereich → Beenden) und erneut versuchen.");
}
}
}
/// <summary>
/// Vorhandene Datei beiseite umbenennen. Das klappt unter Windows auch dann, wenn sie noch geöffnet ist
/// (z. B. vom Explorer für das Icon oder vom Virenscanner) – überschreiben würde dagegen fehlschlagen.
/// </summary>
private static void MoveAside(string path)
{
if (!File.Exists(path)) return;
try { File.Delete(path); return; } catch { }
try { File.Move(path, $"{path}.{Guid.NewGuid():N}.old"); } catch { }
}
/// <summary>Beiseitegelegte Dateien früherer Installationen wegräumen (sofern nicht mehr in Benutzung).</summary>
private static void CleanupOldFiles(string dir)
{
if (!Directory.Exists(dir)) return;
foreach (var f in Directory.GetFiles(dir, "*.old"))
try { File.Delete(f); } catch { }
}
private static string ReadResourceText(string name)
{
using var s = typeof(Installer).Assembly.GetManifestResourceStream(name);
if (s is null) return "";
using var r = new StreamReader(s);
return r.ReadToEnd().Trim();
}
private static void CreateShortcut(string link, string target)
{
Directory.CreateDirectory(Path.GetDirectoryName(link)!);
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
dynamic lnk = shell.CreateShortcut(link);
lnk.TargetPath = target;
lnk.WorkingDirectory = Path.GetDirectoryName(target);
lnk.IconLocation = $"{target},0";
lnk.Description = "Desktop-Icons in Kategorien sortieren";
lnk.Save();
}
private static void DeleteIfPointsTo(string link, string target)
{
if (!File.Exists(link)) return;
try
{
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
string t = shell.CreateShortcut(link).TargetPath;
if (!string.Equals(t, target, StringComparison.OrdinalIgnoreCase)) return;
}
catch { return; }
// Explorer/Indexdienst halten Verknüpfungen manchmal kurz fest → mehrmals versuchen
for (int i = 0; i < 10 && File.Exists(link); i++)
{
try { File.Delete(link); }
catch { Thread.Sleep(300); }
}
}
private static void TryDelete(string path)
{
try { if (File.Exists(path)) File.Delete(path); } catch { }
}
private static bool Storage_IsUnder(string path, string dir) =>
(Path.GetFullPath(path)).StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
public static long DataSize()
{
long sum = 0;
foreach (var d in DataDirs.Where(Directory.Exists))
{
try
{
foreach (var f in Directory.EnumerateFiles(d, "*", SearchOption.AllDirectories))
try { sum += new FileInfo(f).Length; } catch { }
}
catch { }
}
return sum;
}
public static string FormatSize(long bytes) => bytes switch
{
< 1024 * 1024 => $"{Math.Max(1, bytes / 1024)} KB",
< 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024:0.#} MB",
_ => $"{bytes / 1024.0 / 1024 / 1024:0.##} GB",
};
}
+183 -183
View File
@@ -1,183 +1,183 @@
<Window x:Class="DeskCoreSetup.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DeskCore Setup" Icon="app.ico" Width="720" Height="500" ResizeMode="CanMinimize"
Background="{DynamicResource BaseBrush}" Foreground="{DynamicResource TextBrush}"
FontFamily="{DynamicResource UiFont}" FontSize="14"
TextOptions.TextFormattingMode="Display" UseLayoutRounding="True"
WindowStartupLocation="CenterScreen">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="36" ResizeBorderThickness="0" GlassFrameThickness="0,0,0,1"
CornerRadius="0" UseAeroCaptionButtons="False"/>
</WindowChrome.WindowChrome>
<Window.Resources>
<Style x:Key="StepText" TargetType="TextBlock">
<Setter Property="Margin" Value="0,0,0,14"/>
<Setter Property="Foreground" Value="{DynamicResource TextTertiaryBrush}"/>
</Style>
<Style x:Key="PageTitle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{DynamicResource DisplayFont}"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Margin" Value="0,0,0,12"/>
</Style>
<Style x:Key="Body" TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="LineHeight" Value="21"/>
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Titelleiste -->
<Grid Grid.ColumnSpan="2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="14,0,0,0">
<Image Source="app.ico" Width="16" Height="16" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock x:Name="WindowTitle" Text="DeskCore Setup" FontSize="12" Margin="10,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button Style="{StaticResource CaptionButton}" Content="&#xE921;" Click="Minimize_Click"
WindowChrome.IsHitTestVisibleInChrome="True"/>
<Button x:Name="CloseButton" Style="{StaticResource CloseCaptionButton}" Content="&#xE8BB;" Click="Cancel_Click"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</StackPanel>
</Grid>
<!-- Seitenleiste mit Schritten -->
<StackPanel Grid.Row="1" Grid.RowSpan="2" Margin="28,18,20,20">
<Image Source="app.png" Width="72" Height="72" HorizontalAlignment="Left" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="DeskCore" FontFamily="{DynamicResource DisplayFont}" FontSize="22" FontWeight="SemiBold" Margin="0,14,0,0"/>
<TextBlock x:Name="VersionText" Foreground="{DynamicResource TextSecondaryBrush}" FontSize="12" Margin="0,2,0,28"/>
<StackPanel x:Name="Steps">
<TextBlock x:Name="Step0" Text="Willkommen" Style="{StaticResource StepText}"/>
<TextBlock x:Name="Step1" Text="Lizenz" Style="{StaticResource StepText}"/>
<TextBlock x:Name="Step2" Text="Optionen" Style="{StaticResource StepText}"/>
<TextBlock x:Name="Step3" Text="Installation" Style="{StaticResource StepText}"/>
</StackPanel>
</StackPanel>
<!-- Inhalt -->
<Border Grid.Row="1" Grid.Column="1" Background="{DynamicResource LayerBrush}" BorderBrush="{DynamicResource LayerStrokeBrush}"
BorderThickness="1,1,0,1" CornerRadius="8,0,0,8" Padding="32,28,32,20">
<Grid>
<!-- 0: Willkommen -->
<StackPanel x:Name="PageWelcome">
<TextBlock Text="Willkommen bei DeskCore" Style="{StaticResource PageTitle}"/>
<TextBlock Style="{StaticResource Body}"
Text="DeskCore sortiert deine Desktop-Icons in eigene Kategorien – mit Suche, Desktop-Boxen, automatischem Einsortieren und Rückgängig-Funktion."/>
<TextBlock Style="{StaticResource Body}" Margin="0,14,0,0"
Text="Der Assistent installiert DeskCore nur für dein Benutzerkonto. Administratorrechte sind nicht nötig."/>
<Border x:Name="RuntimeHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="&#xE7BA;" FontFamily="{DynamicResource IconFont}" FontSize="16"
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
<TextBlock x:Name="RuntimeHintText" TextWrapping="Wrap" LineHeight="20"/>
</DockPanel>
</Border>
<Border x:Name="UpdateHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="&#xE946;" FontFamily="{DynamicResource IconFont}" FontSize="16"
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
<TextBlock x:Name="UpdateHintText" TextWrapping="Wrap" LineHeight="20"/>
</DockPanel>
</Border>
</StackPanel>
<!-- 1: Lizenz -->
<DockPanel x:Name="PageLicense" Visibility="Collapsed">
<TextBlock DockPanel.Dock="Top" Text="Lizenz- und Nutzungsbedingungen" Style="{StaticResource PageTitle}"/>
<CheckBox x:Name="AcceptLicense" DockPanel.Dock="Bottom" Margin="0,14,0,0" Click="AcceptLicense_Click"
Content="Ich akzeptiere die Lizenz- und Nutzungsbedingungen"/>
<Border Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}"
BorderThickness="1" CornerRadius="6">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="LicenseText" TextWrapping="Wrap" FontSize="12.5" LineHeight="18" Margin="14,10"/>
</ScrollViewer>
</Border>
</DockPanel>
<!-- 2: Optionen -->
<StackPanel x:Name="PageOptions" Visibility="Collapsed">
<TextBlock Text="Optionen" Style="{StaticResource PageTitle}"/>
<TextBlock Text="Installationsordner" FontWeight="SemiBold" FontSize="13" Margin="0,4,0,8"/>
<DockPanel>
<Button DockPanel.Dock="Right" Content="Ändern …" Margin="8,0,0,0" Click="ChangeDir_Click"/>
<TextBox x:Name="DirBox"/>
</DockPanel>
<CheckBox x:Name="DesktopLinkBox" Content="Verknüpfung auf dem Desktop" Margin="0,24,0,0" IsChecked="True"/>
<CheckBox x:Name="StartMenuBox" Content="Eintrag im Startmenü" Margin="0,12,0,0" IsChecked="True" IsEnabled="False"/>
<CheckBox x:Name="AutostartBox" Content="Mit Windows starten (unauffällig im Infobereich)" Margin="0,12,0,0"/>
<CheckBox x:Name="RuntimeBox" Margin="0,12,0,0" IsChecked="True" Visibility="Collapsed"
Content="Microsoft .NET 9 Desktop Runtime automatisch installieren (benötigt, ca. 55 MB)"/>
</StackPanel>
<!-- 3: Fortschritt / Fertig -->
<StackPanel x:Name="PageProgress" Visibility="Collapsed">
<TextBlock x:Name="ProgressTitle" Text="DeskCore wird installiert …" Style="{StaticResource PageTitle}"/>
<TextBlock x:Name="ProgressText" Style="{StaticResource Body}" Text="Einen Moment bitte."/>
<Border x:Name="ProgressTrack" Height="4" CornerRadius="2" Margin="0,22,0,0" Background="{DynamicResource SubtleSelectedBrush}">
<Border x:Name="ProgressFill" HorizontalAlignment="Left" Width="0" CornerRadius="2" Background="{DynamicResource AccentBrush}"/>
</Border>
<StackPanel x:Name="DonePanel" Visibility="Collapsed" Margin="0,26,0,0">
<StackPanel Orientation="Horizontal">
<Border Width="28" Height="28" CornerRadius="14" Background="{DynamicResource SuccessBrush}">
<TextBlock Text="&#xE73E;" FontFamily="{DynamicResource IconFont}" FontSize="13" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock x:Name="DoneText" VerticalAlignment="Center" Margin="12,0,0,0" FontWeight="SemiBold"/>
</StackPanel>
<Border x:Name="RuntimeMissing" Margin="0,18,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
<StackPanel>
<TextBlock TextWrapping="Wrap" LineHeight="20"
Text="Damit DeskCore startet, wird noch die kostenlose „Microsoft .NET 9 Desktop Runtime“ benötigt."/>
<Button Content="Download-Seite von Microsoft öffnen" HorizontalAlignment="Left" Margin="0,10,0,0" Click="OpenRuntimePage_Click"/>
</StackPanel>
</Border>
<CheckBox x:Name="LaunchBox" Content="DeskCore jetzt starten" IsChecked="True" Margin="0,22,0,0"/>
</StackPanel>
</StackPanel>
<!-- Deinstallation -->
<StackPanel x:Name="PageUninstall" Visibility="Collapsed">
<TextBlock Text="DeskCore deinstallieren" Style="{StaticResource PageTitle}"/>
<TextBlock Style="{StaticResource Body}"
Text="DeskCore wird von deinem PC entfernt: Programm, Verknüpfungen, Autostart und der Eintrag in den Windows-Einstellungen."/>
<Border Margin="0,22,0,0" Padding="16,14" CornerRadius="6" Background="{DynamicResource ControlFillBrush}"
BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
<StackPanel>
<TextBlock Text="Deine Kategorien bleiben standardmäßig erhalten" FontWeight="SemiBold"/>
<TextBlock x:Name="DataInfo" Style="{StaticResource Body}" FontSize="12.5" Margin="0,4,0,12"/>
<CheckBox x:Name="RemoveDataBox" Content="Auch Kategorien und Einstellungen entfernen (Papierkorb)"
Click="RemoveDataBox_Click"/>
<TextBlock x:Name="RemoveDataWarning" Visibility="Collapsed" Margin="30,8,0,0" TextWrapping="Wrap" FontSize="12.5"
Foreground="{DynamicResource DangerBrush}"
Text="Achtung: Dateien und Ordner, die nur noch in deinen Kategorien liegen, wandern damit in den Papierkorb."/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Border>
<!-- Fußleiste -->
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,20,18">
<Button x:Name="BackButton" Content="Zurück" Width="110" Margin="0,0,8,0" Click="Back_Click"/>
<Button x:Name="NextButton" Content="Weiter" Width="130" Margin="0,0,8,0" Style="{StaticResource AccentButton}" IsDefault="True" Click="Next_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Width="110" Click="Cancel_Click"/>
</StackPanel>
</Grid>
</Window>
<Window x:Class="DeskCoreSetup.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DeskCore Setup" Icon="app.ico" Width="720" Height="500" ResizeMode="CanMinimize"
Background="{DynamicResource BaseBrush}" Foreground="{DynamicResource TextBrush}"
FontFamily="{DynamicResource UiFont}" FontSize="14"
TextOptions.TextFormattingMode="Display" UseLayoutRounding="True"
WindowStartupLocation="CenterScreen">
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="36" ResizeBorderThickness="0" GlassFrameThickness="0,0,0,1"
CornerRadius="0" UseAeroCaptionButtons="False"/>
</WindowChrome.WindowChrome>
<Window.Resources>
<Style x:Key="StepText" TargetType="TextBlock">
<Setter Property="Margin" Value="0,0,0,14"/>
<Setter Property="Foreground" Value="{DynamicResource TextTertiaryBrush}"/>
</Style>
<Style x:Key="PageTitle" TargetType="TextBlock">
<Setter Property="FontFamily" Value="{DynamicResource DisplayFont}"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Margin" Value="0,0,0,12"/>
</Style>
<Style x:Key="Body" TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="LineHeight" Value="21"/>
<Setter Property="Foreground" Value="{DynamicResource TextSecondaryBrush}"/>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="36"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- Titelleiste -->
<Grid Grid.ColumnSpan="2">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="14,0,0,0">
<Image Source="app.ico" Width="16" Height="16" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock x:Name="WindowTitle" Text="DeskCore Setup" FontSize="12" Margin="10,0,0,0" VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
<Button Style="{StaticResource CaptionButton}" Content="&#xE921;" Click="Minimize_Click"
WindowChrome.IsHitTestVisibleInChrome="True"/>
<Button x:Name="CloseButton" Style="{StaticResource CloseCaptionButton}" Content="&#xE8BB;" Click="Cancel_Click"
WindowChrome.IsHitTestVisibleInChrome="True"/>
</StackPanel>
</Grid>
<!-- Seitenleiste mit Schritten -->
<StackPanel Grid.Row="1" Grid.RowSpan="2" Margin="28,18,20,20">
<Image Source="app.png" Width="72" Height="72" HorizontalAlignment="Left" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="DeskCore" FontFamily="{DynamicResource DisplayFont}" FontSize="22" FontWeight="SemiBold" Margin="0,14,0,0"/>
<TextBlock x:Name="VersionText" Foreground="{DynamicResource TextSecondaryBrush}" FontSize="12" Margin="0,2,0,28"/>
<StackPanel x:Name="Steps">
<TextBlock x:Name="Step0" Text="Willkommen" Style="{StaticResource StepText}"/>
<TextBlock x:Name="Step1" Text="Lizenz" Style="{StaticResource StepText}"/>
<TextBlock x:Name="Step2" Text="Optionen" Style="{StaticResource StepText}"/>
<TextBlock x:Name="Step3" Text="Installation" Style="{StaticResource StepText}"/>
</StackPanel>
</StackPanel>
<!-- Inhalt -->
<Border Grid.Row="1" Grid.Column="1" Background="{DynamicResource LayerBrush}" BorderBrush="{DynamicResource LayerStrokeBrush}"
BorderThickness="1,1,0,1" CornerRadius="8,0,0,8" Padding="32,28,32,20">
<Grid>
<!-- 0: Willkommen -->
<StackPanel x:Name="PageWelcome">
<TextBlock Text="Willkommen bei DeskCore" Style="{StaticResource PageTitle}"/>
<TextBlock Style="{StaticResource Body}"
Text="DeskCore sortiert deine Desktop-Icons in eigene Kategorien – mit Suche, Desktop-Boxen, automatischem Einsortieren und Rückgängig-Funktion."/>
<TextBlock Style="{StaticResource Body}" Margin="0,14,0,0"
Text="Der Assistent installiert DeskCore für dein Benutzerkonto und – falls nötig – die benötigte Laufzeit von Microsoft. DeskCore selbst läuft danach ganz normal ohne Administratorrechte."/>
<Border x:Name="RuntimeHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="&#xE7BA;" FontFamily="{DynamicResource IconFont}" FontSize="16"
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
<TextBlock x:Name="RuntimeHintText" TextWrapping="Wrap" LineHeight="20"/>
</DockPanel>
</Border>
<Border x:Name="UpdateHint" Margin="0,22,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="&#xE946;" FontFamily="{DynamicResource IconFont}" FontSize="16"
Foreground="{DynamicResource AccentBrush}" Margin="0,2,12,0" VerticalAlignment="Top"/>
<TextBlock x:Name="UpdateHintText" TextWrapping="Wrap" LineHeight="20"/>
</DockPanel>
</Border>
</StackPanel>
<!-- 1: Lizenz -->
<DockPanel x:Name="PageLicense" Visibility="Collapsed">
<TextBlock DockPanel.Dock="Top" Text="Lizenz- und Nutzungsbedingungen" Style="{StaticResource PageTitle}"/>
<CheckBox x:Name="AcceptLicense" DockPanel.Dock="Bottom" Margin="0,14,0,0" Click="AcceptLicense_Click"
Content="Ich akzeptiere die Lizenz- und Nutzungsbedingungen"/>
<Border Background="{DynamicResource ControlFillBrush}" BorderBrush="{DynamicResource ControlStrokeBrush}"
BorderThickness="1" CornerRadius="6">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock x:Name="LicenseText" TextWrapping="Wrap" FontSize="12.5" LineHeight="18" Margin="14,10"/>
</ScrollViewer>
</Border>
</DockPanel>
<!-- 2: Optionen -->
<StackPanel x:Name="PageOptions" Visibility="Collapsed">
<TextBlock Text="Optionen" Style="{StaticResource PageTitle}"/>
<TextBlock Text="Installationsordner" FontWeight="SemiBold" FontSize="13" Margin="0,4,0,8"/>
<DockPanel>
<Button DockPanel.Dock="Right" Content="Ändern …" Margin="8,0,0,0" Click="ChangeDir_Click"/>
<TextBox x:Name="DirBox"/>
</DockPanel>
<CheckBox x:Name="DesktopLinkBox" Content="Verknüpfung auf dem Desktop" Margin="0,24,0,0" IsChecked="True"/>
<CheckBox x:Name="StartMenuBox" Content="Eintrag im Startmenü" Margin="0,12,0,0" IsChecked="True" IsEnabled="False"/>
<CheckBox x:Name="AutostartBox" Content="Mit Windows starten (unauffällig im Infobereich)" Margin="0,12,0,0"/>
<CheckBox x:Name="RuntimeBox" Margin="0,12,0,0" IsChecked="True" Visibility="Collapsed"
Content="Microsoft .NET 9 Desktop Runtime automatisch installieren (benötigt, ca. 55 MB)"/>
</StackPanel>
<!-- 3: Fortschritt / Fertig -->
<StackPanel x:Name="PageProgress" Visibility="Collapsed">
<TextBlock x:Name="ProgressTitle" Text="DeskCore wird installiert …" Style="{StaticResource PageTitle}"/>
<TextBlock x:Name="ProgressText" Style="{StaticResource Body}" Text="Einen Moment bitte."/>
<Border x:Name="ProgressTrack" Height="4" CornerRadius="2" Margin="0,22,0,0" Background="{DynamicResource SubtleSelectedBrush}">
<Border x:Name="ProgressFill" HorizontalAlignment="Left" Width="0" CornerRadius="2" Background="{DynamicResource AccentBrush}"/>
</Border>
<StackPanel x:Name="DonePanel" Visibility="Collapsed" Margin="0,26,0,0">
<StackPanel Orientation="Horizontal">
<Border Width="28" Height="28" CornerRadius="14" Background="{DynamicResource SuccessBrush}">
<TextBlock Text="&#xE73E;" FontFamily="{DynamicResource IconFont}" FontSize="13" Foreground="White"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock x:Name="DoneText" VerticalAlignment="Center" Margin="12,0,0,0" FontWeight="SemiBold"/>
</StackPanel>
<Border x:Name="RuntimeMissing" Margin="0,18,0,0" Padding="14,10" CornerRadius="6" Visibility="Collapsed"
Background="{DynamicResource AccentSubtleBrush}" BorderBrush="{DynamicResource AccentSubtleStrokeBrush}" BorderThickness="1">
<StackPanel>
<TextBlock TextWrapping="Wrap" LineHeight="20"
Text="Damit DeskCore startet, wird noch die kostenlose „Microsoft .NET 9 Desktop Runtime“ benötigt."/>
<Button Content="Download-Seite von Microsoft öffnen" HorizontalAlignment="Left" Margin="0,10,0,0" Click="OpenRuntimePage_Click"/>
</StackPanel>
</Border>
<CheckBox x:Name="LaunchBox" Content="DeskCore jetzt starten" IsChecked="True" Margin="0,22,0,0"/>
</StackPanel>
</StackPanel>
<!-- Deinstallation -->
<StackPanel x:Name="PageUninstall" Visibility="Collapsed">
<TextBlock Text="DeskCore deinstallieren" Style="{StaticResource PageTitle}"/>
<TextBlock Style="{StaticResource Body}"
Text="DeskCore wird von deinem PC entfernt: Programm, Verknüpfungen, Autostart und der Eintrag in den Windows-Einstellungen."/>
<Border Margin="0,22,0,0" Padding="16,14" CornerRadius="6" Background="{DynamicResource ControlFillBrush}"
BorderBrush="{DynamicResource ControlStrokeBrush}" BorderThickness="1">
<StackPanel>
<TextBlock Text="Deine Kategorien bleiben standardmäßig erhalten" FontWeight="SemiBold"/>
<TextBlock x:Name="DataInfo" Style="{StaticResource Body}" FontSize="12.5" Margin="0,4,0,12"/>
<CheckBox x:Name="RemoveDataBox" Content="Auch Kategorien und Einstellungen entfernen (Papierkorb)"
Click="RemoveDataBox_Click"/>
<TextBlock x:Name="RemoveDataWarning" Visibility="Collapsed" Margin="30,8,0,0" TextWrapping="Wrap" FontSize="12.5"
Foreground="{DynamicResource DangerBrush}"
Text="Achtung: Dateien und Ordner, die nur noch in deinen Kategorien liegen, wandern damit in den Papierkorb."/>
</StackPanel>
</Border>
</StackPanel>
</Grid>
</Border>
<!-- Fußleiste -->
<StackPanel Grid.Row="2" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,14,20,18">
<Button x:Name="BackButton" Content="Zurück" Width="110" Margin="0,0,8,0" Click="Back_Click"/>
<Button x:Name="NextButton" Content="Weiter" Width="130" Margin="0,0,8,0" Style="{StaticResource AccentButton}" IsDefault="True" Click="Next_Click"/>
<Button x:Name="CancelButton" Content="Abbrechen" Width="110" Click="Cancel_Click"/>
</StackPanel>
</Grid>
</Window>
+279 -279
View File
@@ -1,279 +1,279 @@
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace DeskCoreSetup;
public partial class MainWindow : Window
{
private readonly bool _uninstall;
private int _page;
private bool _busy;
private bool _done;
private string? _installedExe;
public MainWindow(bool uninstall)
{
InitializeComponent();
_uninstall = uninstall;
VersionText.Text = $"Version {Installer.Version}";
LicenseText.Text = Installer.LicenseText;
DirBox.Text = Installer.InstalledDir ?? Installer.DefaultDir;
DesktopLinkBox.IsChecked = Installer.InstalledDir is null || Installer.DesktopLinkExists;
AutostartBox.IsChecked = Installer.AutostartEnabled;
if (!Runtime.IsInstalled)
{
RuntimeHint.Visibility = Visibility.Visible;
RuntimeBox.Visibility = Visibility.Visible;
RuntimeHintText.Text = Runtime.Is64BitWindows
? "DeskCore benötigt die kostenlose „Microsoft .NET 9 Desktop Runtime“. Sie fehlt auf diesem PC und wird bei der " +
"Installation automatisch von Microsoft geladen (ca. 55 MB). Windows fragt dabei einmal nach Administratorrechten."
: "DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11) und kann auf diesem PC leider nicht installiert werden.";
}
if (Installer.InstalledDir is null && Installer.LegacyInstallDir is not null)
{
UpdateHint.Visibility = Visibility.Visible;
UpdateHintText.Text = "Die bisherige Version „DeskHub“ wird durch DeskCore ersetzt. Deine Kategorien und Einstellungen werden übernommen.";
}
else if (Installer.InstalledDir is not null)
{
UpdateHint.Visibility = Visibility.Visible;
UpdateHintText.Text = Installer.InstalledVersion == Installer.Version
? $"DeskCore {Installer.Version} ist bereits installiert und wird neu installiert. Deine Kategorien und Einstellungen bleiben erhalten."
: $"Die vorhandene Installation (Version {Installer.InstalledVersion}) wird durch Version {Installer.Version} ersetzt. Deine Kategorien und Einstellungen bleiben erhalten.";
}
if (_uninstall)
{
Title = WindowTitle.Text = "DeskCore deinstallieren";
Steps.Visibility = Visibility.Collapsed;
ShowOnly(PageUninstall);
NextButton.Content = "Deinstallieren";
BackButton.Visibility = Visibility.Collapsed;
LoadDataInfo();
}
else ShowPage(0);
}
// ---------- Seiten ----------
private void ShowOnly(UIElement page)
{
foreach (var p in new UIElement[] { PageWelcome, PageLicense, PageOptions, PageProgress, PageUninstall })
p.Visibility = p == page ? Visibility.Visible : Visibility.Collapsed;
}
private void ShowPage(int page)
{
_page = page;
ShowOnly(page switch { 0 => PageWelcome, 1 => PageLicense, 2 => PageOptions, _ => PageProgress });
var steps = new[] { Step0, Step1, Step2, Step3 };
for (int i = 0; i < steps.Length; i++)
{
var label = ((string)steps[i].Tag ?? (string)(steps[i].Tag = steps[i].Text));
steps[i].Text = i < page || (_done && i == page) ? $"✓ {label}" : label;
steps[i].FontWeight = i == page ? FontWeights.SemiBold : FontWeights.Normal;
steps[i].Foreground = (Brush)FindResource(i == page ? "TextBrush" : i < page ? "TextSecondaryBrush" : "TextTertiaryBrush");
}
BackButton.Visibility = page is 1 or 2 ? Visibility.Visible : Visibility.Hidden;
NextButton.Content = page switch { 2 => "Installieren", 3 => "Fertigstellen", _ => "Weiter" };
NextButton.IsEnabled = page != 1 || AcceptLicense.IsChecked == true;
}
private void AcceptLicense_Click(object sender, RoutedEventArgs e) => NextButton.IsEnabled = AcceptLicense.IsChecked == true;
private void Back_Click(object sender, RoutedEventArgs e)
{
if (!_busy && _page > 0) ShowPage(_page - 1);
}
private async void Next_Click(object sender, RoutedEventArgs e)
{
if (_busy) return;
if (_uninstall)
{
if (_done) { Close(); return; }
await RunUninstall();
return;
}
switch (_page)
{
case 0: ShowPage(1); break;
case 1 when AcceptLicense.IsChecked == true: ShowPage(2); break;
case 2:
var dir = DirBox.Text.Trim();
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
{
MessageBox.Show(this, "Bitte einen gültigen Ordner angeben, z. B. C:\\Programme\\DeskCore.", "DeskCore Setup");
return;
}
await RunInstall(dir);
break;
case 3:
if (LaunchBox.IsChecked == true && _installedExe is not null)
Process.Start(new ProcessStartInfo(_installedExe) { UseShellExecute = true });
Close();
break;
}
}
private void ChangeDir_Click(object sender, RoutedEventArgs e)
{
using var dlg = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Installationsordner wählen",
SelectedPath = Directory.Exists(DirBox.Text) ? DirBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
ShowNewFolderButton = true,
};
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
var chosen = dlg.SelectedPath;
// wie bei üblichen Installern: Unterordner "DeskCore" anhängen
DirBox.Text = Path.GetFileName(chosen.TrimEnd('\\')).Equals("DeskCore", StringComparison.OrdinalIgnoreCase)
? chosen : Path.Combine(chosen, "DeskCore");
}
// ---------- Installation ----------
private async Task RunInstall(string dir)
{
_busy = true;
ShowPage(3);
SetButtons(false);
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
bool desktop = DesktopLinkBox.IsChecked == true, autostart = AutostartBox.IsChecked == true;
bool runtime = RuntimeBox.Visibility == Visibility.Visible && RuntimeBox.IsChecked == true;
try
{
await Task.Run(() => Installer.Install(dir, desktop, autostart, progress, runtime));
_installedExe = Path.Combine(dir, Installer.ExeName);
_done = true;
ProgressTitle.Text = "Installation abgeschlossen";
ProgressText.Text = $"DeskCore wurde in {dir} installiert. Du findest es im Startmenü" + (desktop ? " und auf dem Desktop." : ".");
DoneText.Text = "Alles bereit!";
DonePanel.Visibility = Visibility.Visible;
if (!Runtime.IsInstalled)
{
// Laufzeit fehlt weiterhin (abgewählt) → Hinweis statt Startversuch
DoneText.Text = "DeskCore ist installiert";
RuntimeMissing.Visibility = Visibility.Visible;
LaunchBox.IsChecked = false;
LaunchBox.IsEnabled = false;
}
ShowPage(3);
}
catch (Exception ex)
{
ProgressTitle.Text = "Installation fehlgeschlagen";
ProgressText.Text = ex.Message;
ProgressText.Foreground = (Brush)FindResource("DangerBrush");
NextButton.Content = "Schließen";
_installedExe = null;
}
_busy = false;
NextButton.IsEnabled = true;
CloseButton.IsEnabled = true;
BackButton.Visibility = Visibility.Hidden;
CancelButton.Visibility = Visibility.Collapsed;
}
// ---------- Deinstallation ----------
private async void LoadDataInfo()
{
var root = Installer.DataDirs[0];
if (!Directory.Exists(root))
{
DataInfo.Text = "Es wurden keine Kategorien gefunden.";
return;
}
DataInfo.Text = $"Sie liegen in {root}. Größe wird berechnet …";
var size = await Task.Run(Installer.DataSize);
DataInfo.Text = $"Sie liegen in {root} ({Installer.FormatSize(size)}) und bleiben dort, falls du DeskCore später wieder installierst.";
}
private void RemoveDataBox_Click(object sender, RoutedEventArgs e) =>
RemoveDataWarning.Visibility = RemoveDataBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
private async Task RunUninstall()
{
_busy = true;
bool removeData = RemoveDataBox.IsChecked == true;
ShowOnly(PageProgress);
ProgressTitle.Text = "DeskCore wird entfernt …";
LaunchBox.Visibility = Visibility.Collapsed;
SetButtons(false);
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
try
{
await Task.Run(() => Installer.Uninstall(removeData, progress));
ProgressTitle.Text = "Deinstallation abgeschlossen";
ProgressText.Text = removeData
? "DeskCore, deine Kategorien und Einstellungen wurden entfernt (die Daten liegen im Papierkorb)."
: "DeskCore wurde entfernt. Deine Kategorien liegen weiterhin in " + Installer.DataDirs[0] + ".";
DoneText.Text = "Fertig";
DonePanel.Visibility = Visibility.Visible;
}
catch (Exception ex)
{
ProgressTitle.Text = "Deinstallation fehlgeschlagen";
ProgressText.Text = ex.Message;
}
_done = true;
_busy = false;
CloseButton.IsEnabled = true;
NextButton.Content = "Schließen";
NextButton.IsEnabled = true;
CancelButton.Visibility = Visibility.Collapsed;
}
// ---------- Helfer ----------
private void SetButtons(bool enabled)
{
NextButton.IsEnabled = enabled;
BackButton.IsEnabled = enabled;
CancelButton.IsEnabled = enabled;
CloseButton.IsEnabled = enabled;
}
private void SetProgress(double value, string text)
{
if (!_done) ProgressText.Text = text;
var target = Math.Max(0, ProgressTrack.ActualWidth * value);
ProgressFill.BeginAnimation(WidthProperty,
new DoubleAnimation(target, TimeSpan.FromMilliseconds(250)) { EasingFunction = new CubicEase() });
}
private static bool IsFullPath(string p) =>
p.Length >= 3 && char.IsLetter(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
private void OpenRuntimePage_Click(object sender, RoutedEventArgs e) =>
Process.Start(new ProcessStartInfo(Runtime.DownloadPage) { UseShellExecute = true });
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if (_busy) return;
if (!_done && !_uninstall && _page > 0 &&
MessageBox.Show(this, "Installation wirklich abbrechen?", "DeskCore Setup", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
Close();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
if (_busy) e.Cancel = true; // während der Installation nicht schließen
base.OnClosing(e);
}
}
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace DeskCoreSetup;
public partial class MainWindow : Window
{
private readonly bool _uninstall;
private int _page;
private bool _busy;
private bool _done;
private string? _installedExe;
public MainWindow(bool uninstall)
{
InitializeComponent();
_uninstall = uninstall;
VersionText.Text = $"Version {Installer.Version}";
LicenseText.Text = Installer.LicenseText;
DirBox.Text = Installer.InstalledDir ?? Installer.DefaultDir;
DesktopLinkBox.IsChecked = Installer.InstalledDir is null || Installer.DesktopLinkExists;
AutostartBox.IsChecked = Installer.AutostartEnabled;
if (!Runtime.IsInstalled)
{
RuntimeHint.Visibility = Visibility.Visible;
RuntimeBox.Visibility = Visibility.Visible;
RuntimeHintText.Text = Runtime.Is64BitWindows
? "DeskCore benötigt die kostenlose „Microsoft .NET 9 Desktop Runtime“. Sie fehlt auf diesem PC und wird bei der " +
"Installation automatisch von Microsoft geladen (ca. 55 MB) und mitinstalliert."
: "DeskCore benötigt ein 64-Bit-Windows (Windows 10 oder 11) und kann auf diesem PC leider nicht installiert werden.";
}
if (Installer.InstalledDir is null && Installer.LegacyInstallDir is not null)
{
UpdateHint.Visibility = Visibility.Visible;
UpdateHintText.Text = "Die bisherige Version „DeskHub“ wird durch DeskCore ersetzt. Deine Kategorien und Einstellungen werden übernommen.";
}
else if (Installer.InstalledDir is not null)
{
UpdateHint.Visibility = Visibility.Visible;
UpdateHintText.Text = Installer.InstalledVersion == Installer.Version
? $"DeskCore {Installer.Version} ist bereits installiert und wird neu installiert. Deine Kategorien und Einstellungen bleiben erhalten."
: $"Die vorhandene Installation (Version {Installer.InstalledVersion}) wird durch Version {Installer.Version} ersetzt. Deine Kategorien und Einstellungen bleiben erhalten.";
}
if (_uninstall)
{
Title = WindowTitle.Text = "DeskCore deinstallieren";
Steps.Visibility = Visibility.Collapsed;
ShowOnly(PageUninstall);
NextButton.Content = "Deinstallieren";
BackButton.Visibility = Visibility.Collapsed;
LoadDataInfo();
}
else ShowPage(0);
}
// ---------- Seiten ----------
private void ShowOnly(UIElement page)
{
foreach (var p in new UIElement[] { PageWelcome, PageLicense, PageOptions, PageProgress, PageUninstall })
p.Visibility = p == page ? Visibility.Visible : Visibility.Collapsed;
}
private void ShowPage(int page)
{
_page = page;
ShowOnly(page switch { 0 => PageWelcome, 1 => PageLicense, 2 => PageOptions, _ => PageProgress });
var steps = new[] { Step0, Step1, Step2, Step3 };
for (int i = 0; i < steps.Length; i++)
{
var label = ((string)steps[i].Tag ?? (string)(steps[i].Tag = steps[i].Text));
steps[i].Text = i < page || (_done && i == page) ? $"✓ {label}" : label;
steps[i].FontWeight = i == page ? FontWeights.SemiBold : FontWeights.Normal;
steps[i].Foreground = (Brush)FindResource(i == page ? "TextBrush" : i < page ? "TextSecondaryBrush" : "TextTertiaryBrush");
}
BackButton.Visibility = page is 1 or 2 ? Visibility.Visible : Visibility.Hidden;
NextButton.Content = page switch { 2 => "Installieren", 3 => "Fertigstellen", _ => "Weiter" };
NextButton.IsEnabled = page != 1 || AcceptLicense.IsChecked == true;
}
private void AcceptLicense_Click(object sender, RoutedEventArgs e) => NextButton.IsEnabled = AcceptLicense.IsChecked == true;
private void Back_Click(object sender, RoutedEventArgs e)
{
if (!_busy && _page > 0) ShowPage(_page - 1);
}
private async void Next_Click(object sender, RoutedEventArgs e)
{
if (_busy) return;
if (_uninstall)
{
if (_done) { Close(); return; }
await RunUninstall();
return;
}
switch (_page)
{
case 0: ShowPage(1); break;
case 1 when AcceptLicense.IsChecked == true: ShowPage(2); break;
case 2:
var dir = DirBox.Text.Trim();
if (!IsFullPath(dir) || dir.IndexOfAny(Path.GetInvalidPathChars()) >= 0)
{
MessageBox.Show(this, "Bitte einen gültigen Ordner angeben, z. B. C:\\Programme\\DeskCore.", "DeskCore Setup");
return;
}
await RunInstall(dir);
break;
case 3:
if (LaunchBox.IsChecked == true && _installedExe is not null)
Installer.StartUnelevated(_installedExe); // ohne Adminrechte, sonst klappt Ziehen vom Desktop nicht
Close();
break;
}
}
private void ChangeDir_Click(object sender, RoutedEventArgs e)
{
using var dlg = new System.Windows.Forms.FolderBrowserDialog
{
Description = "Installationsordner wählen",
SelectedPath = Directory.Exists(DirBox.Text) ? DirBox.Text : Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
ShowNewFolderButton = true,
};
if (dlg.ShowDialog() != System.Windows.Forms.DialogResult.OK) return;
var chosen = dlg.SelectedPath;
// wie bei üblichen Installern: Unterordner "DeskCore" anhängen
DirBox.Text = Path.GetFileName(chosen.TrimEnd('\\')).Equals("DeskCore", StringComparison.OrdinalIgnoreCase)
? chosen : Path.Combine(chosen, "DeskCore");
}
// ---------- Installation ----------
private async Task RunInstall(string dir)
{
_busy = true;
ShowPage(3);
SetButtons(false);
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
bool desktop = DesktopLinkBox.IsChecked == true, autostart = AutostartBox.IsChecked == true;
bool runtime = RuntimeBox.Visibility == Visibility.Visible && RuntimeBox.IsChecked == true;
try
{
await Task.Run(() => Installer.Install(dir, desktop, autostart, progress, runtime));
_installedExe = Path.Combine(dir, Installer.ExeName);
_done = true;
ProgressTitle.Text = "Installation abgeschlossen";
ProgressText.Text = $"DeskCore wurde in {dir} installiert. Du findest es im Startmenü" + (desktop ? " und auf dem Desktop." : ".");
DoneText.Text = "Alles bereit!";
DonePanel.Visibility = Visibility.Visible;
if (!Runtime.IsInstalled)
{
// Laufzeit fehlt weiterhin (abgewählt) → Hinweis statt Startversuch
DoneText.Text = "DeskCore ist installiert";
RuntimeMissing.Visibility = Visibility.Visible;
LaunchBox.IsChecked = false;
LaunchBox.IsEnabled = false;
}
ShowPage(3);
}
catch (Exception ex)
{
ProgressTitle.Text = "Installation fehlgeschlagen";
ProgressText.Text = ex.Message;
ProgressText.Foreground = (Brush)FindResource("DangerBrush");
NextButton.Content = "Schließen";
_installedExe = null;
}
_busy = false;
NextButton.IsEnabled = true;
CloseButton.IsEnabled = true;
BackButton.Visibility = Visibility.Hidden;
CancelButton.Visibility = Visibility.Collapsed;
}
// ---------- Deinstallation ----------
private async void LoadDataInfo()
{
var root = Installer.DataDirs[0];
if (!Directory.Exists(root))
{
DataInfo.Text = "Es wurden keine Kategorien gefunden.";
return;
}
DataInfo.Text = $"Sie liegen in {root}. Größe wird berechnet …";
var size = await Task.Run(Installer.DataSize);
DataInfo.Text = $"Sie liegen in {root} ({Installer.FormatSize(size)}) und bleiben dort, falls du DeskCore später wieder installierst.";
}
private void RemoveDataBox_Click(object sender, RoutedEventArgs e) =>
RemoveDataWarning.Visibility = RemoveDataBox.IsChecked == true ? Visibility.Visible : Visibility.Collapsed;
private async Task RunUninstall()
{
_busy = true;
bool removeData = RemoveDataBox.IsChecked == true;
ShowOnly(PageProgress);
ProgressTitle.Text = "DeskCore wird entfernt …";
LaunchBox.Visibility = Visibility.Collapsed;
SetButtons(false);
var progress = new Progress<(double Value, string Text)>(p => SetProgress(p.Value, p.Text));
try
{
await Task.Run(() => Installer.Uninstall(removeData, progress));
ProgressTitle.Text = "Deinstallation abgeschlossen";
ProgressText.Text = removeData
? "DeskCore, deine Kategorien und Einstellungen wurden entfernt (die Daten liegen im Papierkorb)."
: "DeskCore wurde entfernt. Deine Kategorien liegen weiterhin in " + Installer.DataDirs[0] + ".";
DoneText.Text = "Fertig";
DonePanel.Visibility = Visibility.Visible;
}
catch (Exception ex)
{
ProgressTitle.Text = "Deinstallation fehlgeschlagen";
ProgressText.Text = ex.Message;
}
_done = true;
_busy = false;
CloseButton.IsEnabled = true;
NextButton.Content = "Schließen";
NextButton.IsEnabled = true;
CancelButton.Visibility = Visibility.Collapsed;
}
// ---------- Helfer ----------
private void SetButtons(bool enabled)
{
NextButton.IsEnabled = enabled;
BackButton.IsEnabled = enabled;
CancelButton.IsEnabled = enabled;
CloseButton.IsEnabled = enabled;
}
private void SetProgress(double value, string text)
{
if (!_done) ProgressText.Text = text;
var target = Math.Max(0, ProgressTrack.ActualWidth * value);
ProgressFill.BeginAnimation(WidthProperty,
new DoubleAnimation(target, TimeSpan.FromMilliseconds(250)) { EasingFunction = new CubicEase() });
}
private static bool IsFullPath(string p) =>
p.Length >= 3 && char.IsLetter(p[0]) && p[1] == ':' && (p[2] == '\\' || p[2] == '/');
private void OpenRuntimePage_Click(object sender, RoutedEventArgs e) =>
Process.Start(new ProcessStartInfo(Runtime.DownloadPage) { UseShellExecute = true });
private void Minimize_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
private void Cancel_Click(object sender, RoutedEventArgs e)
{
if (_busy) return;
if (!_done && !_uninstall && _page > 0 &&
MessageBox.Show(this, "Installation wirklich abbrechen?", "DeskCore Setup", MessageBoxButton.YesNo, MessageBoxImage.Question) != MessageBoxResult.Yes)
return;
Close();
}
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
if (_busy) e.Cancel = true; // während der Installation nicht schließen
base.OnClosing(e);
}
}
+20 -3
View File
@@ -20,6 +20,19 @@ public static class Runtime
public static bool Is64BitWindows => Environment.Is64BitOperatingSystem;
public static bool IsElevated
{
get
{
try
{
using var id = System.Security.Principal.WindowsIdentity.GetCurrent();
return new System.Security.Principal.WindowsPrincipal(id).IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator);
}
catch { return false; }
}
}
public static bool IsInstalled
{
get
@@ -43,7 +56,7 @@ public static class Runtime
}
}
/// <summary>Lädt die Laufzeit von Microsoft und installiert sie (Windows fragt einmal nach Administratorrechten).</summary>
/// <summary>Lädt die Laufzeit von Microsoft und installiert sie (der Installer läuft bereits mit Administratorrechten).</summary>
public static void Install(Action<double, string> progress)
{
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
@@ -77,11 +90,15 @@ public static class Runtime
throw new InvalidOperationException("Die heruntergeladene Datei ist nicht gültig von Microsoft signiert und wurde nicht ausgeführt.");
}
progress(1, $"{DisplayName} wird installiert – bitte die Windows-Abfrage bestätigen …");
progress(1, $"{DisplayName} wird installiert …");
Process p;
try
{
p = Process.Start(new ProcessStartInfo(file, "/install /quiet /norestart") { UseShellExecute = true, Verb = "runas" })!;
// Der Installer hat schon Adminrechte – "runas" nur als Absicherung, falls er doch ohne gestartet wurde
p = Process.Start(new ProcessStartInfo(file, "/install /quiet /norestart")
{
UseShellExecute = true, Verb = IsElevated ? "" : "runas", WindowStyle = ProcessWindowStyle.Hidden,
})!;
}
catch (Win32Exception ex) when (ex.NativeErrorCode == 1223)
{
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="DeskCore.Setup"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- Der Installer läuft mit Administratorrechten: so kann er die von Microsoft benötigte
.NET-Laufzeit ohne zweite Nachfrage mitinstallieren. DeskCore selbst startet danach ohne Adminrechte. -->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 und 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
@@ -0,0 +1,24 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v9.0/win-x64",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v9.0": {},
".NETCoreApp,Version=v9.0/win-x64": {
"DeskCore-Setup/1.0.0": {
"runtime": {
"DeskCore-Setup.dll": {}
}
}
}
},
"libraries": {
"DeskCore-Setup/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
@@ -0,0 +1,20 @@
{
"runtimeOptions": {
"tfm": "net9.0",
"frameworks": [
{
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
{
"name": "Microsoft.WindowsDesktop.App",
"version": "9.0.0"
}
],
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
}
}
}
@@ -0,0 +1,59 @@
{
"format": 1,
"restore": {
"C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj": {}
},
"projects": {
"C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj": {
"version": "1.0.4",
"restore": {
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"projectName": "DeskCore-Setup",
"projectPath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"packagesPath": "C:\\Users\\Viper\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\Viper\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net48"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net48": {
"targetAlias": "net48",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net48": {
"targetAlias": "net48",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"suppressParent": "All",
"target": "Package",
"version": "[1.0.3, )",
"autoReferenced": true
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.112\\RuntimeIdentifierGraph.json"
}
}
}
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\Viper\.nuget\packages\</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">7.0.0</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\Viper\.nuget\packages\" />
</ItemGroup>
</Project>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.netframework.referenceassemblies.net48\1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net48.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.netframework.referenceassemblies.net48\1.0.3\build\Microsoft.NETFramework.ReferenceAssemblies.net48.targets')" />
</ImportGroup>
</Project>
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
Binary file not shown.
+77
View File
@@ -0,0 +1,77 @@
#pragma checksum "..\..\..\App.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "2F55484CCDC4F93150C83267DEA1BE82F9151116"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Controls.Ribbon;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms.Integration;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace DeskCoreSetup {
/// <summary>
/// App
/// </summary>
public partial class App : System.Windows.Application {
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/DeskCore-Setup;component/app.xaml", System.UriKind.Relative);
#line 1 "..\..\..\App.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
public static void Main() {
DeskCoreSetup.App app = new DeskCoreSetup.App();
app.InitializeComponent();
app.Run();
}
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
</configuration>
Binary file not shown.
@@ -0,0 +1,20 @@
DeskCore-Setup
winexe
C#
.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\
DeskCoreSetup
none
false
TRACE;RELEASE;NETFRAMEWORK;NET48;NET30_OR_GREATER;NET35_OR_GREATER;NET40_OR_GREATER;NET45_OR_GREATER;NET451_OR_GREATER;NET452_OR_GREATER;NET46_OR_GREATER;NET461_OR_GREATER;NET462_OR_GREATER;NET47_OR_GREATER;NET471_OR_GREATER;NET472_OR_GREATER;NET48_OR_GREATER
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\App.xaml
4-1182040411
5-706554335
241796289289
MainWindow.xaml;..\Themes\Colors.Light.xaml;..\Themes\Colors.Dark.xaml;..\Themes\Controls.xaml;
False
@@ -0,0 +1,25 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("M_Viper")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert DeskCore")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.4.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.4")]
[assembly: System.Reflection.AssemblyProductAttribute("DeskCore Setup")]
[assembly: System.Reflection.AssemblyTitleAttribute("DeskCore Setup")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.4.0")]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
// Von der MSBuild WriteCodeFragment-Klasse generiert.
@@ -0,0 +1 @@
d2e033b6ce1399c33b5b36da4d426df28d32a528502994c6fd13241554523b24
@@ -0,0 +1,14 @@
is_global = true
build_property.ApplicationManifest = app.manifest
build_property.StartupObject =
build_property.ApplicationDefaultFont =
build_property.ApplicationHighDpiMode =
build_property.ApplicationUseCompatibleTextRendering =
build_property.ApplicationVisualStyles =
build_property.RootNamespace = DeskCoreSetup
build_property.ProjectDir = C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.CsWinRTUseWindowsUIXamlProjections = false
build_property.EffectiveAnalysisLevelStyle =
build_property.EnableCodeStyleSeverity =
@@ -0,0 +1,6 @@
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
Binary file not shown.
@@ -0,0 +1 @@
054b30b1baba2269cbcd4852a291a982fdd2874935a858bccfe0f353f3966d8b
@@ -0,0 +1,17 @@
C:\Users\Viper\Desktop\Programme\DeskCore-Source\artifacts\setup\DeskCore-Setup.exe.config
C:\Users\Viper\Desktop\Programme\DeskCore-Source\artifacts\setup\DeskCore-Setup.exe
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCoreSetup.csproj.AssemblyReference.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\MainWindow.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\Colors.Light.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\Colors.Dark.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\Controls.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\App.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\MainWindow.g.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\App.g.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCore-Setup_MarkupCompile.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCore-Setup.g.resources
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCoreSetup.AssemblyInfoInputs.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCoreSetup.AssemblyInfo.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCoreSetup.csproj.CoreCompileInputs.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net48\DeskCore-Setup.exe
Binary file not shown.
+556
View File
@@ -0,0 +1,556 @@
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "1A3CEC98E280296CC3F91E26F8B8AC24476C46EF"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Controls.Ribbon;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms.Integration;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace DeskCoreSetup {
/// <summary>
/// MainWindow
/// </summary>
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 48 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock WindowTitle;
#line default
#line hidden
#line 53 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button CloseButton;
#line default
#line hidden
#line 62 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock VersionText;
#line default
#line hidden
#line 63 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel Steps;
#line default
#line hidden
#line 64 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step0;
#line default
#line hidden
#line 65 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step1;
#line default
#line hidden
#line 66 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step2;
#line default
#line hidden
#line 67 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step3;
#line default
#line hidden
#line 76 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageWelcome;
#line default
#line hidden
#line 82 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border RuntimeHint;
#line default
#line hidden
#line 87 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock RuntimeHintText;
#line default
#line hidden
#line 90 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border UpdateHint;
#line default
#line hidden
#line 95 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock UpdateHintText;
#line default
#line hidden
#line 101 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.DockPanel PageLicense;
#line default
#line hidden
#line 103 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox AcceptLicense;
#line default
#line hidden
#line 108 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock LicenseText;
#line default
#line hidden
#line 114 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageOptions;
#line default
#line hidden
#line 119 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox DirBox;
#line default
#line hidden
#line 121 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox DesktopLinkBox;
#line default
#line hidden
#line 122 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox StartMenuBox;
#line default
#line hidden
#line 123 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox AutostartBox;
#line default
#line hidden
#line 124 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox RuntimeBox;
#line default
#line hidden
#line 129 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageProgress;
#line default
#line hidden
#line 130 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressTitle;
#line default
#line hidden
#line 131 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressText;
#line default
#line hidden
#line 132 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ProgressTrack;
#line default
#line hidden
#line 133 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ProgressFill;
#line default
#line hidden
#line 135 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel DonePanel;
#line default
#line hidden
#line 141 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock DoneText;
#line default
#line hidden
#line 143 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border RuntimeMissing;
#line default
#line hidden
#line 151 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox LaunchBox;
#line default
#line hidden
#line 156 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageUninstall;
#line default
#line hidden
#line 164 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock DataInfo;
#line default
#line hidden
#line 165 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox RemoveDataBox;
#line default
#line hidden
#line 167 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock RemoveDataWarning;
#line default
#line hidden
#line 178 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button BackButton;
#line default
#line hidden
#line 179 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button NextButton;
#line default
#line hidden
#line 180 "..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button CancelButton;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/DeskCore-Setup;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.WindowTitle = ((System.Windows.Controls.TextBlock)(target));
return;
case 2:
#line 51 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Minimize_Click);
#line default
#line hidden
return;
case 3:
this.CloseButton = ((System.Windows.Controls.Button)(target));
#line 53 "..\..\..\MainWindow.xaml"
this.CloseButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
#line default
#line hidden
return;
case 4:
this.VersionText = ((System.Windows.Controls.TextBlock)(target));
return;
case 5:
this.Steps = ((System.Windows.Controls.StackPanel)(target));
return;
case 6:
this.Step0 = ((System.Windows.Controls.TextBlock)(target));
return;
case 7:
this.Step1 = ((System.Windows.Controls.TextBlock)(target));
return;
case 8:
this.Step2 = ((System.Windows.Controls.TextBlock)(target));
return;
case 9:
this.Step3 = ((System.Windows.Controls.TextBlock)(target));
return;
case 10:
this.PageWelcome = ((System.Windows.Controls.StackPanel)(target));
return;
case 11:
this.RuntimeHint = ((System.Windows.Controls.Border)(target));
return;
case 12:
this.RuntimeHintText = ((System.Windows.Controls.TextBlock)(target));
return;
case 13:
this.UpdateHint = ((System.Windows.Controls.Border)(target));
return;
case 14:
this.UpdateHintText = ((System.Windows.Controls.TextBlock)(target));
return;
case 15:
this.PageLicense = ((System.Windows.Controls.DockPanel)(target));
return;
case 16:
this.AcceptLicense = ((System.Windows.Controls.CheckBox)(target));
#line 103 "..\..\..\MainWindow.xaml"
this.AcceptLicense.Click += new System.Windows.RoutedEventHandler(this.AcceptLicense_Click);
#line default
#line hidden
return;
case 17:
this.LicenseText = ((System.Windows.Controls.TextBlock)(target));
return;
case 18:
this.PageOptions = ((System.Windows.Controls.StackPanel)(target));
return;
case 19:
#line 118 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.ChangeDir_Click);
#line default
#line hidden
return;
case 20:
this.DirBox = ((System.Windows.Controls.TextBox)(target));
return;
case 21:
this.DesktopLinkBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 22:
this.StartMenuBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 23:
this.AutostartBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 24:
this.RuntimeBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 25:
this.PageProgress = ((System.Windows.Controls.StackPanel)(target));
return;
case 26:
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
return;
case 27:
this.ProgressText = ((System.Windows.Controls.TextBlock)(target));
return;
case 28:
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
return;
case 29:
this.ProgressFill = ((System.Windows.Controls.Border)(target));
return;
case 30:
this.DonePanel = ((System.Windows.Controls.StackPanel)(target));
return;
case 31:
this.DoneText = ((System.Windows.Controls.TextBlock)(target));
return;
case 32:
this.RuntimeMissing = ((System.Windows.Controls.Border)(target));
return;
case 33:
#line 148 "..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.OpenRuntimePage_Click);
#line default
#line hidden
return;
case 34:
this.LaunchBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 35:
this.PageUninstall = ((System.Windows.Controls.StackPanel)(target));
return;
case 36:
this.DataInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 37:
this.RemoveDataBox = ((System.Windows.Controls.CheckBox)(target));
#line 166 "..\..\..\MainWindow.xaml"
this.RemoveDataBox.Click += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
#line default
#line hidden
return;
case 38:
this.RemoveDataWarning = ((System.Windows.Controls.TextBlock)(target));
return;
case 39:
this.BackButton = ((System.Windows.Controls.Button)(target));
#line 178 "..\..\..\MainWindow.xaml"
this.BackButton.Click += new System.Windows.RoutedEventHandler(this.Back_Click);
#line default
#line hidden
return;
case 40:
this.NextButton = ((System.Windows.Controls.Button)(target));
#line 179 "..\..\..\MainWindow.xaml"
this.NextButton.Click += new System.Windows.RoutedEventHandler(this.Next_Click);
#line default
#line hidden
return;
case 41:
this.CancelButton = ((System.Windows.Controls.Button)(target));
#line 180 "..\..\..\MainWindow.xaml"
this.CancelButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}
@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]
Binary file not shown.
@@ -0,0 +1,76 @@
#pragma checksum "..\..\..\..\App.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "2F55484CCDC4F93150C83267DEA1BE82F9151116"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Controls.Ribbon;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace DeskCoreSetup {
/// <summary>
/// App
/// </summary>
public partial class App : System.Windows.Application {
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/DeskCore-Setup;component/app.xaml", System.UriKind.Relative);
#line 1 "..\..\..\..\App.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
/// <summary>
/// Application Entry Point.
/// </summary>
[System.STAThreadAttribute()]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
public static void Main() {
DeskCoreSetup.App app = new DeskCoreSetup.App();
app.InitializeComponent();
app.Run();
}
}
}
@@ -0,0 +1,24 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v9.0/win-x64",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v9.0": {},
".NETCoreApp,Version=v9.0/win-x64": {
"DeskCore-Setup/1.0.0": {
"runtime": {
"DeskCore-Setup.dll": {}
}
}
}
},
"libraries": {
"DeskCore-Setup/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
@@ -0,0 +1,20 @@
DeskCore-Setup
winexe
C#
.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\
DeskCoreSetup
none
false
TRACE;RELEASE;NET;NET9_0;NETCOREAPP;WINDOWS;WINDOWS7_0;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER;WINDOWS7_0_OR_GREATER
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\App.xaml
4-1182040411
4647195960
200-1670248436
MainWindow.xaml;..\Themes\Colors.Light.xaml;..\Themes\Colors.Dark.xaml;..\Themes\Controls.xaml;
False
@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("M_Viper")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Release")]
[assembly: System.Reflection.AssemblyCopyrightAttribute("Copyright (c) M_Viper. Alle Rechte vorbehalten.")]
[assembly: System.Reflection.AssemblyDescriptionAttribute("Installiert DeskCore")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("DeskCore Setup")]
[assembly: System.Reflection.AssemblyTitleAttribute("DeskCore Setup")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
[assembly: System.Resources.NeutralResourcesLanguageAttribute("de-DE")]
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]
// Von der MSBuild WriteCodeFragment-Klasse generiert.
@@ -0,0 +1 @@
aa9cb945fc0e24f2fe5d76e4d4c638a76152e98407d3ea2108dbe178b58b4abf
@@ -0,0 +1,22 @@
is_global = true
build_property.EnableAotAnalyzer =
build_property.EnableSingleFileAnalyzer = true
build_property.EnableTrimAnalyzer =
build_property.IncludeAllContentForSelfExtract =
build_property.TargetFramework = net9.0-windows
build_property.TargetFrameworkIdentifier = .NETCoreApp
build_property.TargetFrameworkVersion = v9.0
build_property.TargetPlatformMinVersion = 7.0
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = DeskCoreSetup
build_property.ProjectDir = C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.CsWinRTUseWindowsUIXamlProjections = false
build_property.EffectiveAnalysisLevelStyle = 9.0
build_property.EnableCodeStyleSeverity =
@@ -0,0 +1,6 @@
// <auto-generated/>
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Threading.Tasks;
@@ -0,0 +1 @@
2b200cd0f14b4e4eb4b4885f0e8b3c56c346fd55b4f74f792b3cdc502d3e8cbb
@@ -0,0 +1,21 @@
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\bin\Release\net9.0-windows\win-x64\DeskCore-Setup.exe
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\bin\Release\net9.0-windows\win-x64\DeskCore-Setup.deps.json
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\bin\Release\net9.0-windows\win-x64\DeskCore-Setup.runtimeconfig.json
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\bin\Release\net9.0-windows\win-x64\DeskCore-Setup.dll
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\MainWindow.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\Colors.Light.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\Colors.Dark.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\Controls.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\App.baml
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\MainWindow.g.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\App.g.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCore-Setup_MarkupCompile.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCore-Setup.g.resources
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCoreSetup.GeneratedMSBuildEditorConfig.editorconfig
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCoreSetup.AssemblyInfoInputs.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCoreSetup.AssemblyInfo.cs
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCoreSetup.csproj.CoreCompileInputs.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCore-Setup.dll
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\refint\DeskCore-Setup.dll
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\DeskCoreSetup.genruntimeconfig.cache
C:\Users\Viper\Desktop\Programme\DeskCore-Source\Setup\obj\Release\net9.0-windows\win-x64\ref\DeskCore-Setup.dll
@@ -0,0 +1 @@
914b6a259056b8c0f6c3227ea7cdf35b8381c900fbf78428cfcb5446fdb60624
@@ -0,0 +1 @@
c41825f0c5957fff8272ec718c138cd0a31afc83a6d3eab90859164c0e911ccf
@@ -0,0 +1 @@
94e9cde7f02f6a1624bb951dc6d8d1cb1351d8ca9035e4dbef6cd3f5efea400c
@@ -0,0 +1,503 @@
#pragma checksum "..\..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "6F090F12111B2DA7E86EB9B4D3E7CE46DAE44931"
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Automation;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Controls.Ribbon;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Media.TextFormatting;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
namespace DeskCoreSetup {
/// <summary>
/// MainWindow
/// </summary>
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
#line 48 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock WindowTitle;
#line default
#line hidden
#line 53 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button CloseButton;
#line default
#line hidden
#line 62 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock VersionText;
#line default
#line hidden
#line 63 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel Steps;
#line default
#line hidden
#line 64 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step0;
#line default
#line hidden
#line 65 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step1;
#line default
#line hidden
#line 66 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step2;
#line default
#line hidden
#line 67 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock Step3;
#line default
#line hidden
#line 76 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageWelcome;
#line default
#line hidden
#line 82 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border UpdateHint;
#line default
#line hidden
#line 87 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock UpdateHintText;
#line default
#line hidden
#line 93 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.DockPanel PageLicense;
#line default
#line hidden
#line 95 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox AcceptLicense;
#line default
#line hidden
#line 100 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock LicenseText;
#line default
#line hidden
#line 106 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageOptions;
#line default
#line hidden
#line 111 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBox DirBox;
#line default
#line hidden
#line 113 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox DesktopLinkBox;
#line default
#line hidden
#line 114 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox StartMenuBox;
#line default
#line hidden
#line 115 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox AutostartBox;
#line default
#line hidden
#line 119 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageProgress;
#line default
#line hidden
#line 120 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressTitle;
#line default
#line hidden
#line 121 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock ProgressText;
#line default
#line hidden
#line 122 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ProgressTrack;
#line default
#line hidden
#line 123 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Border ProgressFill;
#line default
#line hidden
#line 125 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel DonePanel;
#line default
#line hidden
#line 131 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock DoneText;
#line default
#line hidden
#line 133 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox LaunchBox;
#line default
#line hidden
#line 138 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.StackPanel PageUninstall;
#line default
#line hidden
#line 146 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock DataInfo;
#line default
#line hidden
#line 147 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.CheckBox RemoveDataBox;
#line default
#line hidden
#line 149 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.TextBlock RemoveDataWarning;
#line default
#line hidden
#line 160 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button BackButton;
#line default
#line hidden
#line 161 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button NextButton;
#line default
#line hidden
#line 162 "..\..\..\..\MainWindow.xaml"
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
internal System.Windows.Controls.Button CancelButton;
#line default
#line hidden
private bool _contentLoaded;
/// <summary>
/// InitializeComponent
/// </summary>
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
public void InitializeComponent() {
if (_contentLoaded) {
return;
}
_contentLoaded = true;
System.Uri resourceLocater = new System.Uri("/DeskCore-Setup;component/mainwindow.xaml", System.UriKind.Relative);
#line 1 "..\..\..\..\MainWindow.xaml"
System.Windows.Application.LoadComponent(this, resourceLocater);
#line default
#line hidden
}
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "10.0.12.0")]
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
switch (connectionId)
{
case 1:
this.WindowTitle = ((System.Windows.Controls.TextBlock)(target));
return;
case 2:
#line 51 "..\..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.Minimize_Click);
#line default
#line hidden
return;
case 3:
this.CloseButton = ((System.Windows.Controls.Button)(target));
#line 53 "..\..\..\..\MainWindow.xaml"
this.CloseButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
#line default
#line hidden
return;
case 4:
this.VersionText = ((System.Windows.Controls.TextBlock)(target));
return;
case 5:
this.Steps = ((System.Windows.Controls.StackPanel)(target));
return;
case 6:
this.Step0 = ((System.Windows.Controls.TextBlock)(target));
return;
case 7:
this.Step1 = ((System.Windows.Controls.TextBlock)(target));
return;
case 8:
this.Step2 = ((System.Windows.Controls.TextBlock)(target));
return;
case 9:
this.Step3 = ((System.Windows.Controls.TextBlock)(target));
return;
case 10:
this.PageWelcome = ((System.Windows.Controls.StackPanel)(target));
return;
case 11:
this.UpdateHint = ((System.Windows.Controls.Border)(target));
return;
case 12:
this.UpdateHintText = ((System.Windows.Controls.TextBlock)(target));
return;
case 13:
this.PageLicense = ((System.Windows.Controls.DockPanel)(target));
return;
case 14:
this.AcceptLicense = ((System.Windows.Controls.CheckBox)(target));
#line 95 "..\..\..\..\MainWindow.xaml"
this.AcceptLicense.Click += new System.Windows.RoutedEventHandler(this.AcceptLicense_Click);
#line default
#line hidden
return;
case 15:
this.LicenseText = ((System.Windows.Controls.TextBlock)(target));
return;
case 16:
this.PageOptions = ((System.Windows.Controls.StackPanel)(target));
return;
case 17:
#line 110 "..\..\..\..\MainWindow.xaml"
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.ChangeDir_Click);
#line default
#line hidden
return;
case 18:
this.DirBox = ((System.Windows.Controls.TextBox)(target));
return;
case 19:
this.DesktopLinkBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 20:
this.StartMenuBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 21:
this.AutostartBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 22:
this.PageProgress = ((System.Windows.Controls.StackPanel)(target));
return;
case 23:
this.ProgressTitle = ((System.Windows.Controls.TextBlock)(target));
return;
case 24:
this.ProgressText = ((System.Windows.Controls.TextBlock)(target));
return;
case 25:
this.ProgressTrack = ((System.Windows.Controls.Border)(target));
return;
case 26:
this.ProgressFill = ((System.Windows.Controls.Border)(target));
return;
case 27:
this.DonePanel = ((System.Windows.Controls.StackPanel)(target));
return;
case 28:
this.DoneText = ((System.Windows.Controls.TextBlock)(target));
return;
case 29:
this.LaunchBox = ((System.Windows.Controls.CheckBox)(target));
return;
case 30:
this.PageUninstall = ((System.Windows.Controls.StackPanel)(target));
return;
case 31:
this.DataInfo = ((System.Windows.Controls.TextBlock)(target));
return;
case 32:
this.RemoveDataBox = ((System.Windows.Controls.CheckBox)(target));
#line 148 "..\..\..\..\MainWindow.xaml"
this.RemoveDataBox.Click += new System.Windows.RoutedEventHandler(this.RemoveDataBox_Click);
#line default
#line hidden
return;
case 33:
this.RemoveDataWarning = ((System.Windows.Controls.TextBlock)(target));
return;
case 34:
this.BackButton = ((System.Windows.Controls.Button)(target));
#line 160 "..\..\..\..\MainWindow.xaml"
this.BackButton.Click += new System.Windows.RoutedEventHandler(this.Back_Click);
#line default
#line hidden
return;
case 35:
this.NextButton = ((System.Windows.Controls.Button)(target));
#line 161 "..\..\..\..\MainWindow.xaml"
this.NextButton.Click += new System.Windows.RoutedEventHandler(this.Next_Click);
#line default
#line hidden
return;
case 36:
this.CancelButton = ((System.Windows.Controls.Button)(target));
#line 162 "..\..\..\..\MainWindow.xaml"
this.CancelButton.Click += new System.Windows.RoutedEventHandler(this.Cancel_Click);
#line default
#line hidden
return;
}
this._contentLoaded = true;
}
}
}
@@ -0,0 +1 @@
C:\Users\Viper\Desktop\Programme\DeskCore-Installer\DeskCore-Setup.exe
Binary file not shown.
+471
View File
@@ -0,0 +1,471 @@
{
"version": 3,
"targets": {
".NETFramework,Version=v4.8": {
"Microsoft.NETFramework.ReferenceAssemblies/1.0.3": {
"type": "package",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies.net48": "1.0.3"
}
},
"Microsoft.NETFramework.ReferenceAssemblies.net48/1.0.3": {
"type": "package",
"build": {
"build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets": {}
}
}
}
},
"libraries": {
"Microsoft.NETFramework.ReferenceAssemblies/1.0.3": {
"sha512": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==",
"type": "package",
"path": "microsoft.netframework.referenceassemblies/1.0.3",
"files": [
".nupkg.metadata",
".signature.p7s",
"microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512",
"microsoft.netframework.referenceassemblies.nuspec"
]
},
"Microsoft.NETFramework.ReferenceAssemblies.net48/1.0.3": {
"sha512": "zMk4D+9zyiEWByyQ7oPImPN/Jhpj166Ky0Nlla4eXlNL8hI/BtSJsgR8Inldd4NNpIAH3oh8yym0W2DrhXdSLQ==",
"type": "package",
"path": "microsoft.netframework.referenceassemblies.net48/1.0.3",
"files": [
".nupkg.metadata",
".signature.p7s",
"build/.NETFramework/v4.8/Accessibility.dll",
"build/.NETFramework/v4.8/Accessibility.xml",
"build/.NETFramework/v4.8/CustomMarshalers.dll",
"build/.NETFramework/v4.8/CustomMarshalers.xml",
"build/.NETFramework/v4.8/Facades/Microsoft.Win32.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.AppContext.dll",
"build/.NETFramework/v4.8/Facades/System.Collections.Concurrent.dll",
"build/.NETFramework/v4.8/Facades/System.Collections.NonGeneric.dll",
"build/.NETFramework/v4.8/Facades/System.Collections.Specialized.dll",
"build/.NETFramework/v4.8/Facades/System.Collections.dll",
"build/.NETFramework/v4.8/Facades/System.ComponentModel.Annotations.dll",
"build/.NETFramework/v4.8/Facades/System.ComponentModel.EventBasedAsync.dll",
"build/.NETFramework/v4.8/Facades/System.ComponentModel.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.ComponentModel.TypeConverter.dll",
"build/.NETFramework/v4.8/Facades/System.ComponentModel.dll",
"build/.NETFramework/v4.8/Facades/System.Console.dll",
"build/.NETFramework/v4.8/Facades/System.Data.Common.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Contracts.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Debug.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.FileVersionInfo.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Process.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.StackTrace.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.TextWriterTraceListener.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.Tools.dll",
"build/.NETFramework/v4.8/Facades/System.Diagnostics.TraceSource.dll",
"build/.NETFramework/v4.8/Facades/System.Drawing.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.Dynamic.Runtime.dll",
"build/.NETFramework/v4.8/Facades/System.Globalization.Calendars.dll",
"build/.NETFramework/v4.8/Facades/System.Globalization.Extensions.dll",
"build/.NETFramework/v4.8/Facades/System.Globalization.dll",
"build/.NETFramework/v4.8/Facades/System.IO.Compression.ZipFile.dll",
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.DriveInfo.dll",
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.Watcher.dll",
"build/.NETFramework/v4.8/Facades/System.IO.FileSystem.dll",
"build/.NETFramework/v4.8/Facades/System.IO.IsolatedStorage.dll",
"build/.NETFramework/v4.8/Facades/System.IO.MemoryMappedFiles.dll",
"build/.NETFramework/v4.8/Facades/System.IO.Pipes.dll",
"build/.NETFramework/v4.8/Facades/System.IO.UnmanagedMemoryStream.dll",
"build/.NETFramework/v4.8/Facades/System.IO.dll",
"build/.NETFramework/v4.8/Facades/System.Linq.Expressions.dll",
"build/.NETFramework/v4.8/Facades/System.Linq.Parallel.dll",
"build/.NETFramework/v4.8/Facades/System.Linq.Queryable.dll",
"build/.NETFramework/v4.8/Facades/System.Linq.dll",
"build/.NETFramework/v4.8/Facades/System.Net.Http.Rtc.dll",
"build/.NETFramework/v4.8/Facades/System.Net.NameResolution.dll",
"build/.NETFramework/v4.8/Facades/System.Net.NetworkInformation.dll",
"build/.NETFramework/v4.8/Facades/System.Net.Ping.dll",
"build/.NETFramework/v4.8/Facades/System.Net.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.Net.Requests.dll",
"build/.NETFramework/v4.8/Facades/System.Net.Security.dll",
"build/.NETFramework/v4.8/Facades/System.Net.Sockets.dll",
"build/.NETFramework/v4.8/Facades/System.Net.WebHeaderCollection.dll",
"build/.NETFramework/v4.8/Facades/System.Net.WebSockets.Client.dll",
"build/.NETFramework/v4.8/Facades/System.Net.WebSockets.dll",
"build/.NETFramework/v4.8/Facades/System.ObjectModel.dll",
"build/.NETFramework/v4.8/Facades/System.Reflection.Emit.ILGeneration.dll",
"build/.NETFramework/v4.8/Facades/System.Reflection.Emit.Lightweight.dll",
"build/.NETFramework/v4.8/Facades/System.Reflection.Emit.dll",
"build/.NETFramework/v4.8/Facades/System.Reflection.Extensions.dll",
"build/.NETFramework/v4.8/Facades/System.Reflection.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.Reflection.dll",
"build/.NETFramework/v4.8/Facades/System.Resources.Reader.dll",
"build/.NETFramework/v4.8/Facades/System.Resources.ResourceManager.dll",
"build/.NETFramework/v4.8/Facades/System.Resources.Writer.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.CompilerServices.VisualC.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Extensions.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Handles.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.RuntimeInformation.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.WindowsRuntime.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.InteropServices.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Numerics.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Formatters.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Json.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.Serialization.Xml.dll",
"build/.NETFramework/v4.8/Facades/System.Runtime.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Claims.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Algorithms.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Csp.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Encoding.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Cryptography.X509Certificates.dll",
"build/.NETFramework/v4.8/Facades/System.Security.Principal.dll",
"build/.NETFramework/v4.8/Facades/System.Security.SecureString.dll",
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Duplex.dll",
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Http.dll",
"build/.NETFramework/v4.8/Facades/System.ServiceModel.NetTcp.dll",
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Primitives.dll",
"build/.NETFramework/v4.8/Facades/System.ServiceModel.Security.dll",
"build/.NETFramework/v4.8/Facades/System.Text.Encoding.Extensions.dll",
"build/.NETFramework/v4.8/Facades/System.Text.Encoding.dll",
"build/.NETFramework/v4.8/Facades/System.Text.RegularExpressions.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.Overlapped.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.Tasks.Parallel.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.Tasks.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.Thread.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.ThreadPool.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.Timer.dll",
"build/.NETFramework/v4.8/Facades/System.Threading.dll",
"build/.NETFramework/v4.8/Facades/System.ValueTuple.dll",
"build/.NETFramework/v4.8/Facades/System.Xml.ReaderWriter.dll",
"build/.NETFramework/v4.8/Facades/System.Xml.XDocument.dll",
"build/.NETFramework/v4.8/Facades/System.Xml.XPath.XDocument.dll",
"build/.NETFramework/v4.8/Facades/System.Xml.XPath.dll",
"build/.NETFramework/v4.8/Facades/System.Xml.XmlDocument.dll",
"build/.NETFramework/v4.8/Facades/System.Xml.XmlSerializer.dll",
"build/.NETFramework/v4.8/Facades/netstandard.dll",
"build/.NETFramework/v4.8/ISymWrapper.dll",
"build/.NETFramework/v4.8/ISymWrapper.xml",
"build/.NETFramework/v4.8/Microsoft.Activities.Build.dll",
"build/.NETFramework/v4.8/Microsoft.Activities.Build.xml",
"build/.NETFramework/v4.8/Microsoft.Build.Conversion.v4.0.dll",
"build/.NETFramework/v4.8/Microsoft.Build.Conversion.v4.0.xml",
"build/.NETFramework/v4.8/Microsoft.Build.Engine.dll",
"build/.NETFramework/v4.8/Microsoft.Build.Engine.xml",
"build/.NETFramework/v4.8/Microsoft.Build.Framework.dll",
"build/.NETFramework/v4.8/Microsoft.Build.Framework.xml",
"build/.NETFramework/v4.8/Microsoft.Build.Tasks.v4.0.dll",
"build/.NETFramework/v4.8/Microsoft.Build.Tasks.v4.0.xml",
"build/.NETFramework/v4.8/Microsoft.Build.Utilities.v4.0.dll",
"build/.NETFramework/v4.8/Microsoft.Build.Utilities.v4.0.xml",
"build/.NETFramework/v4.8/Microsoft.Build.dll",
"build/.NETFramework/v4.8/Microsoft.Build.xml",
"build/.NETFramework/v4.8/Microsoft.CSharp.dll",
"build/.NETFramework/v4.8/Microsoft.CSharp.xml",
"build/.NETFramework/v4.8/Microsoft.JScript.dll",
"build/.NETFramework/v4.8/Microsoft.JScript.xml",
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.Data.dll",
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.Data.xml",
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.dll",
"build/.NETFramework/v4.8/Microsoft.VisualBasic.Compatibility.xml",
"build/.NETFramework/v4.8/Microsoft.VisualBasic.dll",
"build/.NETFramework/v4.8/Microsoft.VisualBasic.xml",
"build/.NETFramework/v4.8/Microsoft.VisualC.STLCLR.dll",
"build/.NETFramework/v4.8/Microsoft.VisualC.STLCLR.xml",
"build/.NETFramework/v4.8/Microsoft.VisualC.dll",
"build/.NETFramework/v4.8/Microsoft.VisualC.xml",
"build/.NETFramework/v4.8/PermissionSets/FullTrust.xml",
"build/.NETFramework/v4.8/PermissionSets/Internet.xml",
"build/.NETFramework/v4.8/PermissionSets/LocalIntranet.xml",
"build/.NETFramework/v4.8/PresentationBuildTasks.dll",
"build/.NETFramework/v4.8/PresentationBuildTasks.xml",
"build/.NETFramework/v4.8/PresentationCore.dll",
"build/.NETFramework/v4.8/PresentationCore.xml",
"build/.NETFramework/v4.8/PresentationFramework.Aero.dll",
"build/.NETFramework/v4.8/PresentationFramework.Aero.xml",
"build/.NETFramework/v4.8/PresentationFramework.Aero2.dll",
"build/.NETFramework/v4.8/PresentationFramework.Aero2.xml",
"build/.NETFramework/v4.8/PresentationFramework.AeroLite.dll",
"build/.NETFramework/v4.8/PresentationFramework.AeroLite.xml",
"build/.NETFramework/v4.8/PresentationFramework.Classic.dll",
"build/.NETFramework/v4.8/PresentationFramework.Classic.xml",
"build/.NETFramework/v4.8/PresentationFramework.Luna.dll",
"build/.NETFramework/v4.8/PresentationFramework.Luna.xml",
"build/.NETFramework/v4.8/PresentationFramework.Royale.dll",
"build/.NETFramework/v4.8/PresentationFramework.Royale.xml",
"build/.NETFramework/v4.8/PresentationFramework.dll",
"build/.NETFramework/v4.8/PresentationFramework.xml",
"build/.NETFramework/v4.8/ReachFramework.dll",
"build/.NETFramework/v4.8/ReachFramework.xml",
"build/.NETFramework/v4.8/RedistList/FrameworkList.xml",
"build/.NETFramework/v4.8/System.Activities.Core.Presentation.dll",
"build/.NETFramework/v4.8/System.Activities.Core.Presentation.xml",
"build/.NETFramework/v4.8/System.Activities.DurableInstancing.dll",
"build/.NETFramework/v4.8/System.Activities.DurableInstancing.xml",
"build/.NETFramework/v4.8/System.Activities.Presentation.dll",
"build/.NETFramework/v4.8/System.Activities.Presentation.xml",
"build/.NETFramework/v4.8/System.Activities.dll",
"build/.NETFramework/v4.8/System.Activities.xml",
"build/.NETFramework/v4.8/System.AddIn.Contract.dll",
"build/.NETFramework/v4.8/System.AddIn.Contract.xml",
"build/.NETFramework/v4.8/System.AddIn.dll",
"build/.NETFramework/v4.8/System.AddIn.xml",
"build/.NETFramework/v4.8/System.ComponentModel.Composition.Registration.dll",
"build/.NETFramework/v4.8/System.ComponentModel.Composition.Registration.xml",
"build/.NETFramework/v4.8/System.ComponentModel.Composition.dll",
"build/.NETFramework/v4.8/System.ComponentModel.Composition.xml",
"build/.NETFramework/v4.8/System.ComponentModel.DataAnnotations.dll",
"build/.NETFramework/v4.8/System.ComponentModel.DataAnnotations.xml",
"build/.NETFramework/v4.8/System.Configuration.Install.dll",
"build/.NETFramework/v4.8/System.Configuration.Install.xml",
"build/.NETFramework/v4.8/System.Configuration.dll",
"build/.NETFramework/v4.8/System.Configuration.xml",
"build/.NETFramework/v4.8/System.Core.dll",
"build/.NETFramework/v4.8/System.Core.xml",
"build/.NETFramework/v4.8/System.Data.DataSetExtensions.dll",
"build/.NETFramework/v4.8/System.Data.DataSetExtensions.xml",
"build/.NETFramework/v4.8/System.Data.Entity.Design.dll",
"build/.NETFramework/v4.8/System.Data.Entity.Design.xml",
"build/.NETFramework/v4.8/System.Data.Entity.dll",
"build/.NETFramework/v4.8/System.Data.Entity.xml",
"build/.NETFramework/v4.8/System.Data.Linq.dll",
"build/.NETFramework/v4.8/System.Data.Linq.xml",
"build/.NETFramework/v4.8/System.Data.OracleClient.dll",
"build/.NETFramework/v4.8/System.Data.OracleClient.xml",
"build/.NETFramework/v4.8/System.Data.Services.Client.dll",
"build/.NETFramework/v4.8/System.Data.Services.Client.xml",
"build/.NETFramework/v4.8/System.Data.Services.Design.dll",
"build/.NETFramework/v4.8/System.Data.Services.Design.xml",
"build/.NETFramework/v4.8/System.Data.Services.dll",
"build/.NETFramework/v4.8/System.Data.Services.xml",
"build/.NETFramework/v4.8/System.Data.SqlXml.dll",
"build/.NETFramework/v4.8/System.Data.SqlXml.xml",
"build/.NETFramework/v4.8/System.Data.dll",
"build/.NETFramework/v4.8/System.Data.xml",
"build/.NETFramework/v4.8/System.Deployment.dll",
"build/.NETFramework/v4.8/System.Deployment.xml",
"build/.NETFramework/v4.8/System.Design.dll",
"build/.NETFramework/v4.8/System.Design.xml",
"build/.NETFramework/v4.8/System.Device.dll",
"build/.NETFramework/v4.8/System.Device.xml",
"build/.NETFramework/v4.8/System.Diagnostics.Tracing.dll",
"build/.NETFramework/v4.8/System.Diagnostics.Tracing.xml",
"build/.NETFramework/v4.8/System.DirectoryServices.AccountManagement.dll",
"build/.NETFramework/v4.8/System.DirectoryServices.AccountManagement.xml",
"build/.NETFramework/v4.8/System.DirectoryServices.Protocols.dll",
"build/.NETFramework/v4.8/System.DirectoryServices.Protocols.xml",
"build/.NETFramework/v4.8/System.DirectoryServices.dll",
"build/.NETFramework/v4.8/System.DirectoryServices.xml",
"build/.NETFramework/v4.8/System.Drawing.Design.dll",
"build/.NETFramework/v4.8/System.Drawing.Design.xml",
"build/.NETFramework/v4.8/System.Drawing.dll",
"build/.NETFramework/v4.8/System.Drawing.xml",
"build/.NETFramework/v4.8/System.Dynamic.dll",
"build/.NETFramework/v4.8/System.EnterpriseServices.Thunk.dll",
"build/.NETFramework/v4.8/System.EnterpriseServices.Wrapper.dll",
"build/.NETFramework/v4.8/System.EnterpriseServices.dll",
"build/.NETFramework/v4.8/System.EnterpriseServices.xml",
"build/.NETFramework/v4.8/System.IO.Compression.FileSystem.dll",
"build/.NETFramework/v4.8/System.IO.Compression.FileSystem.xml",
"build/.NETFramework/v4.8/System.IO.Compression.dll",
"build/.NETFramework/v4.8/System.IO.Compression.xml",
"build/.NETFramework/v4.8/System.IO.Log.dll",
"build/.NETFramework/v4.8/System.IO.Log.xml",
"build/.NETFramework/v4.8/System.IdentityModel.Selectors.dll",
"build/.NETFramework/v4.8/System.IdentityModel.Selectors.xml",
"build/.NETFramework/v4.8/System.IdentityModel.Services.dll",
"build/.NETFramework/v4.8/System.IdentityModel.Services.xml",
"build/.NETFramework/v4.8/System.IdentityModel.dll",
"build/.NETFramework/v4.8/System.IdentityModel.xml",
"build/.NETFramework/v4.8/System.Linq.xml",
"build/.NETFramework/v4.8/System.Management.Instrumentation.dll",
"build/.NETFramework/v4.8/System.Management.Instrumentation.xml",
"build/.NETFramework/v4.8/System.Management.dll",
"build/.NETFramework/v4.8/System.Management.xml",
"build/.NETFramework/v4.8/System.Messaging.dll",
"build/.NETFramework/v4.8/System.Messaging.xml",
"build/.NETFramework/v4.8/System.Net.Http.WebRequest.dll",
"build/.NETFramework/v4.8/System.Net.Http.WebRequest.xml",
"build/.NETFramework/v4.8/System.Net.Http.dll",
"build/.NETFramework/v4.8/System.Net.Http.xml",
"build/.NETFramework/v4.8/System.Net.dll",
"build/.NETFramework/v4.8/System.Net.xml",
"build/.NETFramework/v4.8/System.Numerics.dll",
"build/.NETFramework/v4.8/System.Numerics.xml",
"build/.NETFramework/v4.8/System.Printing.dll",
"build/.NETFramework/v4.8/System.Printing.xml",
"build/.NETFramework/v4.8/System.Reflection.Context.dll",
"build/.NETFramework/v4.8/System.Reflection.Context.xml",
"build/.NETFramework/v4.8/System.Runtime.Caching.dll",
"build/.NETFramework/v4.8/System.Runtime.Caching.xml",
"build/.NETFramework/v4.8/System.Runtime.DurableInstancing.dll",
"build/.NETFramework/v4.8/System.Runtime.DurableInstancing.xml",
"build/.NETFramework/v4.8/System.Runtime.Remoting.dll",
"build/.NETFramework/v4.8/System.Runtime.Remoting.xml",
"build/.NETFramework/v4.8/System.Runtime.Serialization.Formatters.Soap.dll",
"build/.NETFramework/v4.8/System.Runtime.Serialization.Formatters.Soap.xml",
"build/.NETFramework/v4.8/System.Runtime.Serialization.dll",
"build/.NETFramework/v4.8/System.Runtime.Serialization.xml",
"build/.NETFramework/v4.8/System.Security.dll",
"build/.NETFramework/v4.8/System.Security.xml",
"build/.NETFramework/v4.8/System.ServiceModel.Activation.dll",
"build/.NETFramework/v4.8/System.ServiceModel.Activation.xml",
"build/.NETFramework/v4.8/System.ServiceModel.Activities.dll",
"build/.NETFramework/v4.8/System.ServiceModel.Activities.xml",
"build/.NETFramework/v4.8/System.ServiceModel.Channels.dll",
"build/.NETFramework/v4.8/System.ServiceModel.Channels.xml",
"build/.NETFramework/v4.8/System.ServiceModel.Discovery.dll",
"build/.NETFramework/v4.8/System.ServiceModel.Discovery.xml",
"build/.NETFramework/v4.8/System.ServiceModel.Routing.dll",
"build/.NETFramework/v4.8/System.ServiceModel.Routing.xml",
"build/.NETFramework/v4.8/System.ServiceModel.Web.dll",
"build/.NETFramework/v4.8/System.ServiceModel.Web.xml",
"build/.NETFramework/v4.8/System.ServiceModel.dll",
"build/.NETFramework/v4.8/System.ServiceModel.xml",
"build/.NETFramework/v4.8/System.ServiceProcess.dll",
"build/.NETFramework/v4.8/System.ServiceProcess.xml",
"build/.NETFramework/v4.8/System.Speech.dll",
"build/.NETFramework/v4.8/System.Speech.xml",
"build/.NETFramework/v4.8/System.Threading.Tasks.Dataflow.xml",
"build/.NETFramework/v4.8/System.Transactions.dll",
"build/.NETFramework/v4.8/System.Transactions.xml",
"build/.NETFramework/v4.8/System.Web.Abstractions.dll",
"build/.NETFramework/v4.8/System.Web.ApplicationServices.dll",
"build/.NETFramework/v4.8/System.Web.ApplicationServices.xml",
"build/.NETFramework/v4.8/System.Web.DataVisualization.Design.dll",
"build/.NETFramework/v4.8/System.Web.DataVisualization.dll",
"build/.NETFramework/v4.8/System.Web.DataVisualization.xml",
"build/.NETFramework/v4.8/System.Web.DynamicData.Design.dll",
"build/.NETFramework/v4.8/System.Web.DynamicData.Design.xml",
"build/.NETFramework/v4.8/System.Web.DynamicData.dll",
"build/.NETFramework/v4.8/System.Web.DynamicData.xml",
"build/.NETFramework/v4.8/System.Web.Entity.Design.dll",
"build/.NETFramework/v4.8/System.Web.Entity.Design.xml",
"build/.NETFramework/v4.8/System.Web.Entity.dll",
"build/.NETFramework/v4.8/System.Web.Entity.xml",
"build/.NETFramework/v4.8/System.Web.Extensions.Design.dll",
"build/.NETFramework/v4.8/System.Web.Extensions.Design.xml",
"build/.NETFramework/v4.8/System.Web.Extensions.dll",
"build/.NETFramework/v4.8/System.Web.Extensions.xml",
"build/.NETFramework/v4.8/System.Web.Mobile.dll",
"build/.NETFramework/v4.8/System.Web.Mobile.xml",
"build/.NETFramework/v4.8/System.Web.RegularExpressions.dll",
"build/.NETFramework/v4.8/System.Web.RegularExpressions.xml",
"build/.NETFramework/v4.8/System.Web.Routing.dll",
"build/.NETFramework/v4.8/System.Web.Services.dll",
"build/.NETFramework/v4.8/System.Web.Services.xml",
"build/.NETFramework/v4.8/System.Web.dll",
"build/.NETFramework/v4.8/System.Web.xml",
"build/.NETFramework/v4.8/System.Windows.Controls.Ribbon.dll",
"build/.NETFramework/v4.8/System.Windows.Controls.Ribbon.xml",
"build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.Design.dll",
"build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.dll",
"build/.NETFramework/v4.8/System.Windows.Forms.DataVisualization.xml",
"build/.NETFramework/v4.8/System.Windows.Forms.dll",
"build/.NETFramework/v4.8/System.Windows.Forms.xml",
"build/.NETFramework/v4.8/System.Windows.Input.Manipulations.dll",
"build/.NETFramework/v4.8/System.Windows.Input.Manipulations.xml",
"build/.NETFramework/v4.8/System.Windows.Presentation.dll",
"build/.NETFramework/v4.8/System.Windows.Presentation.xml",
"build/.NETFramework/v4.8/System.Windows.dll",
"build/.NETFramework/v4.8/System.Workflow.Activities.dll",
"build/.NETFramework/v4.8/System.Workflow.Activities.xml",
"build/.NETFramework/v4.8/System.Workflow.ComponentModel.dll",
"build/.NETFramework/v4.8/System.Workflow.ComponentModel.xml",
"build/.NETFramework/v4.8/System.Workflow.Runtime.dll",
"build/.NETFramework/v4.8/System.Workflow.Runtime.xml",
"build/.NETFramework/v4.8/System.WorkflowServices.dll",
"build/.NETFramework/v4.8/System.WorkflowServices.xml",
"build/.NETFramework/v4.8/System.Xaml.dll",
"build/.NETFramework/v4.8/System.Xaml.xml",
"build/.NETFramework/v4.8/System.Xml.Linq.dll",
"build/.NETFramework/v4.8/System.Xml.Linq.xml",
"build/.NETFramework/v4.8/System.Xml.Serialization.dll",
"build/.NETFramework/v4.8/System.Xml.dll",
"build/.NETFramework/v4.8/System.Xml.xml",
"build/.NETFramework/v4.8/System.dll",
"build/.NETFramework/v4.8/System.xml",
"build/.NETFramework/v4.8/UIAutomationClient.dll",
"build/.NETFramework/v4.8/UIAutomationClient.xml",
"build/.NETFramework/v4.8/UIAutomationClientsideProviders.dll",
"build/.NETFramework/v4.8/UIAutomationClientsideProviders.xml",
"build/.NETFramework/v4.8/UIAutomationProvider.dll",
"build/.NETFramework/v4.8/UIAutomationProvider.xml",
"build/.NETFramework/v4.8/UIAutomationTypes.dll",
"build/.NETFramework/v4.8/UIAutomationTypes.xml",
"build/.NETFramework/v4.8/WindowsBase.dll",
"build/.NETFramework/v4.8/WindowsBase.xml",
"build/.NETFramework/v4.8/WindowsFormsIntegration.dll",
"build/.NETFramework/v4.8/WindowsFormsIntegration.xml",
"build/.NETFramework/v4.8/XamlBuildTask.dll",
"build/.NETFramework/v4.8/XamlBuildTask.xml",
"build/.NETFramework/v4.8/mscorlib.dll",
"build/.NETFramework/v4.8/mscorlib.xml",
"build/.NETFramework/v4.8/namespaces.xml",
"build/.NETFramework/v4.8/sysglobl.dll",
"build/.NETFramework/v4.8/sysglobl.xml",
"build/Microsoft.NETFramework.ReferenceAssemblies.net48.targets",
"microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512",
"microsoft.netframework.referenceassemblies.net48.nuspec"
]
}
},
"projectFileDependencyGroups": {
".NETFramework,Version=v4.8": [
"Microsoft.NETFramework.ReferenceAssemblies >= 1.0.3"
]
},
"packageFolders": {
"C:\\Users\\Viper\\.nuget\\packages\\": {}
},
"project": {
"version": "1.0.4",
"restore": {
"projectUniqueName": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"projectName": "DeskCore-Setup",
"projectPath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"packagesPath": "C:\\Users\\Viper\\.nuget\\packages\\",
"outputPath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\Viper\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net48"
],
"sources": {
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net48": {
"targetAlias": "net48",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "10.0.100"
},
"frameworks": {
"net48": {
"targetAlias": "net48",
"dependencies": {
"Microsoft.NETFramework.ReferenceAssemblies": {
"suppressParent": "All",
"target": "Package",
"version": "[1.0.3, )",
"autoReferenced": true
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\10.0.112\\RuntimeIdentifierGraph.json"
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"version": 2,
"dgSpecHash": "1d5+c4eGnFw=",
"success": true,
"projectFilePath": "C:\\Users\\Viper\\Desktop\\Programme\\DeskCore-Source\\Setup\\DeskCoreSetup.csproj",
"expectedPackageFiles": [
"C:\\Users\\Viper\\.nuget\\packages\\microsoft.netframework.referenceassemblies\\1.0.3\\microsoft.netframework.referenceassemblies.1.0.3.nupkg.sha512",
"C:\\Users\\Viper\\.nuget\\packages\\microsoft.netframework.referenceassemblies.net48\\1.0.3\\microsoft.netframework.referenceassemblies.net48.1.0.3.nupkg.sha512"
],
"logs": []
}
+476 -392
View File
@@ -1,392 +1,476 @@
using System.Collections.Concurrent;
using System.IO;
namespace DeskCore;
public enum TransferStatus { Ok, Skipped, Error, Cancelled }
/// <param name="Copied">true = kopiert (Original bleibt), false = verschoben</param>
public record TransferResult(TransferStatus Status, string Source, string? Dest = null, bool Copied = false, string? Message = null);
/// <summary>
/// Kategorien sind einfache Unterordner von %USERPROFILE%\DeskCore.
/// Icons vom Desktop werden dorthin kopiert – es bleiben normale Dateien.
/// </summary>
public static class Storage
{
public static readonly string Root = Migration.Pick(Environment.SpecialFolder.UserProfile);
public static readonly string UserDesktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
public static readonly string PublicDesktop = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
/// <summary>Hier liegen gelöschte Elemente, solange man sie noch rückgängig machen kann.</summary>
public static readonly string Holding = Migration.Pick(Environment.SpecialFolder.LocalApplicationData, "Geloescht");
// ---------- Kategorien ----------
public static List<string> Categories()
{
Directory.CreateDirectory(Root);
var order = AppSettings.Current.CategoryOrder;
return Directory.GetDirectories(Root)
.Select(Path.GetFileName)
.OfType<string>()
.OrderBy(n => order.FindIndex(o => string.Equals(o, n, StringComparison.OrdinalIgnoreCase)) is var i and >= 0 ? i : int.MaxValue)
.ThenBy(n => n, StringComparer.CurrentCultureIgnoreCase)
.ToList();
}
public static string CategoryPath(string name) => Path.Combine(Root, name);
public static IEnumerable<string> Entries(string dir)
{
if (!Directory.Exists(dir)) return [];
try
{
return Directory.EnumerateFileSystemEntries(dir)
.Where(p =>
{
var n = Path.GetFileName(p);
if (n.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase)) return false;
try
{
var attr = File.GetAttributes(p);
return !attr.HasFlag(FileAttributes.Hidden) && !attr.HasFlag(FileAttributes.System);
}
catch { return false; }
})
.ToList();
}
catch { return []; }
}
public static IEnumerable<string> DesktopEntries() =>
Entries(UserDesktop).Concat(Entries(PublicDesktop));
public static bool IsDesktopDir(string dir) =>
SamePath(dir, UserDesktop) || SamePath(dir, PublicDesktop);
public static bool IsInStorage(string path) =>
path.StartsWith(Root.TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
public static string? ValidateName(string name)
{
name = name.Trim();
if (name.Length == 0) return "Bitte einen Namen eingeben.";
if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) return "Der Name enthält ungültige Zeichen ( \\ / : * ? \" < > | ).";
if (name is "." or ".." || name.EndsWith('.')) return "Ungültiger Name.";
return null;
}
// ---------- Duplikate ----------
private static readonly ConcurrentDictionary<string, (DateTime Stamp, string? Target)> _lnkCache = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Ziel einer Verknüpfung (.lnk/.url), sonst null.</summary>
public static string? ShortcutTarget(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
if (ext is not (".lnk" or ".url")) return null;
DateTime stamp;
try { stamp = File.GetLastWriteTimeUtc(path); } catch { return null; }
if (_lnkCache.TryGetValue(path, out var c) && c.Stamp == stamp) return c.Target;
string? target = null;
try
{
if (ext == ".url")
{
target = File.ReadLines(path).FirstOrDefault(l => l.StartsWith("URL=", StringComparison.OrdinalIgnoreCase))?[4..];
}
else
{
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
dynamic lnk = shell.CreateShortcut(path);
string t = lnk.TargetPath;
string a = lnk.Arguments;
target = string.IsNullOrEmpty(t) ? null : (t + " " + a).Trim();
}
}
catch { }
if (string.IsNullOrWhiteSpace(target)) target = null;
_lnkCache[path] = (stamp, target);
return target;
}
/// <summary>Gleicher Dateiname oder Verknüpfung mit gleichem Ziel?</summary>
public static bool SameItem(string a, string b)
{
if (string.Equals(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase)) return true;
var ta = ShortcutTarget(a);
return ta is not null && string.Equals(ta, ShortcutTarget(b), StringComparison.OrdinalIgnoreCase);
}
/// <summary>Liegt das Element (oder ein gleichwertiges) schon in diesem Ordner?</summary>
public static string? FindEquivalent(string dir, string item) =>
Entries(dir).FirstOrDefault(e => !SamePath(e, item) && SameItem(e, item));
/// <summary>In welcher Kategorie liegt das Element bereits? (für das ✓ auf dem Desktop)</summary>
public static string? FindCategoryOf(string item, IReadOnlyDictionary<string, List<string>> categoryEntries)
{
foreach (var (cat, entries) in categoryEntries)
if (entries.Any(e => SameItem(e, item))) return cat;
return null;
}
// ---------- Übertragen ----------
/// <summary>
/// Übernimmt Datei/Ordner nach targetDir. Kommt die Quelle von außerhalb der App (z. B. vom Desktop),
/// wird kopiert – das Original kann danach gelöscht werden. Innerhalb der App und zurück auf den
/// Desktop wird verschoben. Gleiche Elemente werden nicht doppelt angelegt.
/// </summary>
public static TransferResult Transfer(string source, string targetDir, bool forceMove = false,
Action<long>? onBytes = null, CancellationToken ct = default)
{
if (ct.IsCancellationRequested) return new(TransferStatus.Cancelled, source);
var name = Path.GetFileName(source.TrimEnd('\\'));
try
{
if (SamePath(Path.GetDirectoryName(source), targetDir))
return new(TransferStatus.Skipped, source, Message: $"„{name}“ ist schon dort.");
if (Directory.Exists(source) &&
(targetDir.TrimEnd('\\') + "\\").StartsWith(source.TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase))
return new(TransferStatus.Error, source, Message: $"„{name}“ kann nicht in sich selbst verschoben werden.");
bool copy = !forceMove && !IsInStorage(source) && IsInStorage(targetDir);
if (FindEquivalent(targetDir, source) is { } existing)
{
var where = IsDesktopDir(targetDir) ? "auf dem Desktop" : $"in „{Path.GetFileName(targetDir)}“";
return new(TransferStatus.Skipped, source, existing, Message: $"„{DisplayName(source)}“ ist schon {where}.");
}
Directory.CreateDirectory(targetDir);
var dest = UniquePath(targetDir, name);
try
{
if (copy) CopyAny(source, dest, onBytes, ct);
else MoveAny(source, dest, onBytes, ct);
}
catch (OperationCanceledException)
{
// halb kopiertes Ziel wieder entfernen – die Quelle ist unverändert
// (beim Verschieben zwischen Laufwerken wird die Quelle erst nach vollständiger Kopie gelöscht)
DeletePartial(dest);
return new(TransferStatus.Cancelled, source);
}
return new(TransferStatus.Ok, source, dest, copy);
}
catch (UnauthorizedAccessException)
{
return new(TransferStatus.Error, source, Message: $"„{name}“: Keine Berechtigung. " +
"(Icons auf dem öffentlichen Desktop brauchen Administratorrechte – App ggf. als Administrator starten.)");
}
catch (Exception ex)
{
return new(TransferStatus.Error, source, Message: $"„{name}“: {ex.Message}");
}
}
/// <summary>Verschiebt Datei oder Ordner – auch zwischen Laufwerken.</summary>
public static void MoveAny(string source, string dest, Action<long>? onBytes = null, CancellationToken ct = default)
{
NoteCreated(dest);
if (File.Exists(source))
{
File.Move(source, dest);
return;
}
if (string.Equals(Path.GetPathRoot(source), Path.GetPathRoot(dest), StringComparison.OrdinalIgnoreCase))
{
Directory.Move(source, dest);
return;
}
CopyDirectory(source, dest, onBytes, ct);
Directory.Delete(source, recursive: true);
}
public static void CopyAny(string source, string dest, Action<long>? onBytes = null, CancellationToken ct = default)
{
NoteCreated(dest);
if (Directory.Exists(source)) CopyDirectory(source, dest, onBytes, ct);
else CopyFile(source, dest, onBytes, ct);
}
/// <summary>Benennt ein Element um. Gibt den neuen Pfad zurück.</summary>
public static string Rename(string path, string newName)
{
var dir = Path.GetDirectoryName(path)!;
var dest = Path.Combine(dir, newName);
if (string.Equals(path, dest, StringComparison.Ordinal)) return path;
if (string.Equals(path, dest, StringComparison.OrdinalIgnoreCase))
{ // nur Groß-/Kleinschreibung geändert
var tmp = path + ".umbenennen";
MoveAny(path, tmp);
MoveAny(tmp, dest);
return dest;
}
if (File.Exists(dest) || Directory.Exists(dest)) throw new IOException($"„{newName}“ gibt es dort schon.");
MoveAny(path, dest);
return dest;
}
/// <summary>
/// "Löscht" ein Element: es wandert erst in einen internen Ordner, damit Strg+Z funktioniert.
/// Erst wenn es nicht mehr rückgängig gemacht werden kann, geht es in den Papierkorb.
/// </summary>
public static string Delete(string path)
{
var dir = Path.Combine(Holding, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
var dest = Path.Combine(dir, Path.GetFileName(path.TrimEnd('\\')));
MoveAny(path, dest);
return dest;
}
/// <summary>Endgültig in den Windows-Papierkorb.</summary>
public static void Recycle(string path)
{
try
{
if (Directory.Exists(path))
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(path,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
else if (File.Exists(path))
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(path,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
// leeren Hilfsordner aufräumen
var parent = Path.GetDirectoryName(path);
if (parent is not null && IsUnder(parent, Holding) && Directory.Exists(parent) && !Directory.EnumerateFileSystemEntries(parent).Any())
Directory.Delete(parent);
}
catch { }
}
/// <summary>Reste von einem Absturz in den Papierkorb legen.</summary>
public static void FlushHolding()
{
if (!Directory.Exists(Holding)) return;
foreach (var d in Directory.GetDirectories(Holding))
foreach (var e in Directory.EnumerateFileSystemEntries(d).ToList())
Recycle(e);
}
// ---------- Selbst angelegte Desktop-Elemente merken (damit die Auto-Sortierung sie ignoriert) ----------
private static readonly ConcurrentDictionary<string, DateTime> _created = new(StringComparer.OrdinalIgnoreCase);
private static void NoteCreated(string path) => _created[path] = DateTime.UtcNow;
public static bool CreatedByApp(string path) =>
_created.TryGetValue(path, out var t) && DateTime.UtcNow - t < TimeSpan.FromSeconds(15);
/// <summary>Größe eines Ordners in Bytes (bricht ab, sobald limit überschritten ist).</summary>
public static long DirectorySize(string dir, long limit = long.MaxValue)
{
long sum = 0;
try
{
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
{
try { sum += new FileInfo(f).Length; } catch { }
if (sum > limit) break;
}
}
catch { }
return sum;
}
// ---------- Helfer ----------
public static string DisplayName(string path)
{
var n = Path.GetFileName(path.TrimEnd('\\'));
var ext = Path.GetExtension(n).ToLowerInvariant();
return ext is ".lnk" or ".url" or ".appref-ms" ? Path.GetFileNameWithoutExtension(n) : n;
}
/// <summary>Endung, die beim Umbenennen erhalten bleibt (bei Verknüpfungen versteckt).</summary>
public static string HiddenExtension(string path)
{
var ext = Path.GetExtension(path);
return ext.ToLowerInvariant() is ".lnk" or ".url" or ".appref-ms" ? ext : "";
}
public static bool SamePath(string? a, string? b) =>
a is not null && b is not null &&
string.Equals(Path.GetFullPath(a).TrimEnd('\\'), Path.GetFullPath(b).TrimEnd('\\'), StringComparison.OrdinalIgnoreCase);
public static bool IsUnder(string path, string dir) =>
(Path.GetFullPath(path).TrimEnd('\\') + "\\").StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
public static string UniquePath(string dir, string fileName)
{
var path = Path.Combine(dir, fileName);
if (!File.Exists(path) && !Directory.Exists(path)) return path;
var stem = Path.GetFileNameWithoutExtension(fileName);
var ext = Path.GetExtension(fileName);
for (int i = 2; ; i++)
{
path = Path.Combine(dir, $"{stem} ({i}){ext}");
if (!File.Exists(path) && !Directory.Exists(path)) return path;
}
}
private static void CopyDirectory(string source, string dest, Action<long>? onBytes, CancellationToken ct)
{
Directory.CreateDirectory(dest);
foreach (var f in Directory.GetFiles(source))
CopyFile(f, Path.Combine(dest, Path.GetFileName(f)), onBytes, ct);
foreach (var d in Directory.GetDirectories(source))
CopyDirectory(d, Path.Combine(dest, Path.GetFileName(d)), onBytes, ct);
}
/// <summary>Kopiert in 1-MB-Stücken, damit Fortschritt angezeigt und abgebrochen werden kann.</summary>
private static void CopyFile(string source, string dest, Action<long>? onBytes, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
const int Chunk = 1 << 20;
using (var input = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, Chunk, FileOptions.SequentialScan))
using (var output = new FileStream(dest, FileMode.CreateNew, FileAccess.Write, FileShare.None, Chunk))
{
var buffer = new byte[Chunk];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ct.ThrowIfCancellationRequested();
output.Write(buffer, 0, read);
onBytes?.Invoke(read);
}
}
// Zeitstempel und Attribute wie beim normalen Kopieren übernehmen
try
{
File.SetCreationTimeUtc(dest, File.GetCreationTimeUtc(source));
File.SetLastWriteTimeUtc(dest, File.GetLastWriteTimeUtc(source));
File.SetAttributes(dest, File.GetAttributes(source) & ~FileAttributes.ReadOnly);
}
catch { }
}
private static void DeletePartial(string path)
{
try
{
if (Directory.Exists(path)) Directory.Delete(path, recursive: true);
else if (File.Exists(path)) File.Delete(path);
}
catch { }
}
/// <summary>Wie viele Bytes müssen für diese Übertragung tatsächlich kopiert werden? (0 = nur umbenennen)</summary>
public static long BytesToCopy(string source, string targetDir, bool forceMove)
{
bool copy = !forceMove && !IsInStorage(source) && IsInStorage(targetDir);
bool crossDrive = !string.Equals(Path.GetPathRoot(Path.GetFullPath(source)), Path.GetPathRoot(Path.GetFullPath(targetDir)),
StringComparison.OrdinalIgnoreCase);
if (!copy && !crossDrive) return 0;
try { return Directory.Exists(source) ? DirectorySize(source) : new FileInfo(source).Length; }
catch { return 0; }
}
}
using System.Collections.Concurrent;
using System.IO;
namespace DeskCore;
public enum TransferStatus { Ok, Skipped, Error, Cancelled }
/// <param name="Copied">true = kopiert (Original bleibt), false = verschoben</param>
public record TransferResult(TransferStatus Status, string Source, string? Dest = null, bool Copied = false, string? Message = null);
/// <summary>
/// Kategorien sind einfache Unterordner von %USERPROFILE%\DeskCore.
/// Icons vom Desktop werden dorthin kopiert – es bleiben normale Dateien.
/// </summary>
public static class Storage
{
public static readonly string Root = Migration.Pick(Environment.SpecialFolder.UserProfile);
public static readonly string UserDesktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
public static readonly string PublicDesktop = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
/// <summary>Hier liegen gelöschte Elemente, solange man sie noch rückgängig machen kann.</summary>
public static readonly string Holding = Migration.Pick(Environment.SpecialFolder.LocalApplicationData, "Geloescht");
// ---------- Kategorien ----------
public static List<string> Categories()
{
Directory.CreateDirectory(Root);
var order = AppSettings.Current.CategoryOrder;
return Directory.GetDirectories(Root)
.Select(Path.GetFileName)
.OfType<string>()
.OrderBy(n => order.FindIndex(o => string.Equals(o, n, StringComparison.OrdinalIgnoreCase)) is var i and >= 0 ? i : int.MaxValue)
.ThenBy(n => n, StringComparer.CurrentCultureIgnoreCase)
.ToList();
}
public static string CategoryPath(string name) => Path.Combine(Root, name);
public static IEnumerable<string> Entries(string dir)
{
if (!Directory.Exists(dir)) return [];
try
{
return Directory.EnumerateFileSystemEntries(dir)
.Where(p =>
{
var n = Path.GetFileName(p);
if (n.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase)) return false;
try
{
var attr = File.GetAttributes(p);
return !attr.HasFlag(FileAttributes.Hidden) && !attr.HasFlag(FileAttributes.System);
}
catch { return false; }
})
.ToList();
}
catch { return []; }
}
public static IEnumerable<string> DesktopEntries() =>
Entries(UserDesktop).Concat(Entries(PublicDesktop));
public static bool IsDesktopDir(string dir) =>
SamePath(dir, UserDesktop) || SamePath(dir, PublicDesktop);
public static bool IsInStorage(string path) =>
path.StartsWith(Root.TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
public static string? ValidateName(string name)
{
name = name.Trim();
if (name.Length == 0) return "Bitte einen Namen eingeben.";
if (name.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) return "Der Name enthält ungültige Zeichen ( \\ / : * ? \" < > | ).";
if (name is "." or ".." || name.EndsWith('.')) return "Ungültiger Name.";
return null;
}
// ---------- Duplikate ----------
private static readonly ConcurrentDictionary<string, (DateTime Stamp, string? Target)> _lnkCache = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Ziel einer Verknüpfung (.lnk/.url), sonst null.</summary>
public static string? ShortcutTarget(string path)
{
var ext = Path.GetExtension(path).ToLowerInvariant();
if (ext is not (".lnk" or ".url")) return null;
DateTime stamp;
try { stamp = File.GetLastWriteTimeUtc(path); } catch { return null; }
if (_lnkCache.TryGetValue(path, out var c) && c.Stamp == stamp) return c.Target;
string? target = null;
try
{
if (ext == ".url")
{
target = File.ReadLines(path).FirstOrDefault(l => l.StartsWith("URL=", StringComparison.OrdinalIgnoreCase))?[4..];
}
else
{
dynamic shell = Activator.CreateInstance(Type.GetTypeFromProgID("WScript.Shell")!)!;
dynamic lnk = shell.CreateShortcut(path);
string t = lnk.TargetPath;
string a = lnk.Arguments;
target = string.IsNullOrEmpty(t) ? null : (t + " " + a).Trim();
}
}
catch { }
if (string.IsNullOrWhiteSpace(target)) target = null;
_lnkCache[path] = (stamp, target);
return target;
}
// ---------- Verknüpfungen zu deinstallierten Programmen ----------
private static readonly ConcurrentDictionary<string, (DateTime Stamp, string? Target)> _targetPathCache = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Reiner Zielpfad einer .lnk-Verknüpfung (ohne Argumente), sonst null.</summary>
public static string? LinkTargetPath(string path)
{
if (!string.Equals(Path.GetExtension(path), ".lnk", StringComparison.OrdinalIgnoreCase)) return null;
DateTime stamp;
try { stamp = File.GetLastWriteTimeUtc(path); } catch { return null; }
if (_targetPathCache.TryGetValue(path, out var c) && c.Stamp == stamp) return c.Target;
string? target = null;
try
{
// Direkt über IShellLink lesen (Rohpfad). WScript.Shell "repariert" Ziele, die es nicht findet,
// z. B. \\server\… zu C:\server\… – darauf darf eine Lösch-Entscheidung nicht beruhen.
var link = (IShellLinkW)new ShellLinkCoClass();
try
{
((System.Runtime.InteropServices.ComTypes.IPersistFile)link).Load(path, 0); // STGM_READ
var sb = new System.Text.StringBuilder(32768);
link.GetPath(sb, sb.Capacity, IntPtr.Zero, 0x4); // SLGP_RAWPATH
var raw = sb.ToString().Trim();
target = raw.Length == 0 ? null : Environment.ExpandEnvironmentVariables(raw);
}
finally { System.Runtime.InteropServices.Marshal.ReleaseComObject(link); }
}
catch { }
_targetPathCache[path] = (stamp, target);
return target;
}
[System.Runtime.InteropServices.ComImport, System.Runtime.InteropServices.Guid("00021401-0000-0000-C000-000000000046")]
private class ShellLinkCoClass { }
[System.Runtime.InteropServices.ComImport, System.Runtime.InteropServices.Guid("000214F9-0000-0000-C000-000000000046"),
System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIUnknown)]
private interface IShellLinkW
{
void GetPath([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] System.Text.StringBuilder pszFile,
int cch, IntPtr pfd, uint fFlags);
// weitere Methoden werden nicht gebraucht (Reihenfolge der vtable ab hier egal)
}
/// <summary>
/// Zeigt die Verknüpfung auf ein Programm, das es nicht mehr gibt (z. B. deinstalliert)?
/// Bewusst vorsichtig – im Zweifel "nein":
/// .lnk nur mit festem Ziel auf einer eingebauten, erreichbaren Festplatte; .url nur bei Steam/Epic, wenn deren
/// Installationsliste das Spiel nicht mehr kennt. Netzwerk-/USB-Laufwerke, Web-Links, Store-Apps und
/// Windows-Installer-Verknüpfungen werden nie als tot gewertet.
/// </summary>
public static bool IsDeadShortcut(string path)
{
// Steam-/Epic-Links: deren eigene Installationslisten fragen
if (string.Equals(Path.GetExtension(path), ".url", StringComparison.OrdinalIgnoreCase))
{
var url = ShortcutTarget(path);
return url is not null && GameLibraries.IsInstalled(url) == false;
}
var target = LinkTargetPath(path);
if (target is null) return false;
try
{
if (!Path.IsPathFullyQualified(target) || target.StartsWith(@"\\")) return false; // relativ oder Netzwerk
var drive = new DriveInfo(Path.GetPathRoot(target)!);
if (!drive.IsReady || drive.DriveType != DriveType.Fixed) return false; // abgesteckt/USB/Netz
var installer = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Installer");
if (IsUnder(target, installer)) return false; // MSI-"Werbe"-Verknüpfung
return !File.Exists(target) && !Directory.Exists(target);
}
catch { return false; }
}
/// <summary>Alle toten Verknüpfungen in den Kategorien (ohne die, die der Nutzer behalten will).</summary>
public static List<string> FindDeadShortcuts(ICollection<string> keep) =>
Categories()
.SelectMany(c => Entries(CategoryPath(c)))
.Where(p => !keep.Contains(p, StringComparer.OrdinalIgnoreCase) && IsDeadShortcut(p))
.ToList();
/// <summary>Gleicher Dateiname oder Verknüpfung mit gleichem Ziel?</summary>
public static bool SameItem(string a, string b)
{
if (string.Equals(Path.GetFileName(a), Path.GetFileName(b), StringComparison.OrdinalIgnoreCase)) return true;
var ta = ShortcutTarget(a);
return ta is not null && string.Equals(ta, ShortcutTarget(b), StringComparison.OrdinalIgnoreCase);
}
/// <summary>Liegt das Element (oder ein gleichwertiges) schon in diesem Ordner?</summary>
public static string? FindEquivalent(string dir, string item) =>
Entries(dir).FirstOrDefault(e => !SamePath(e, item) && SameItem(e, item));
/// <summary>In welcher Kategorie liegt das Element bereits? (für das ✓ auf dem Desktop)</summary>
public static string? FindCategoryOf(string item, IReadOnlyDictionary<string, List<string>> categoryEntries)
{
foreach (var (cat, entries) in categoryEntries)
if (entries.Any(e => SameItem(e, item))) return cat;
return null;
}
// ---------- Übertragen ----------
/// <summary>
/// Übernimmt Datei/Ordner nach targetDir. Kommt die Quelle von außerhalb der App (z. B. vom Desktop),
/// wird kopiert – das Original kann danach gelöscht werden. Innerhalb der App und zurück auf den
/// Desktop wird verschoben. Gleiche Elemente werden nicht doppelt angelegt.
/// </summary>
public static TransferResult Transfer(string source, string targetDir, bool forceMove = false,
Action<long>? onBytes = null, CancellationToken ct = default)
{
if (ct.IsCancellationRequested) return new(TransferStatus.Cancelled, source);
var name = Path.GetFileName(source.TrimEnd('\\'));
try
{
if (SamePath(Path.GetDirectoryName(source), targetDir))
return new(TransferStatus.Skipped, source, Message: $"„{name}“ ist schon dort.");
if (Directory.Exists(source) &&
(targetDir.TrimEnd('\\') + "\\").StartsWith(source.TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase))
return new(TransferStatus.Error, source, Message: $"„{name}“ kann nicht in sich selbst verschoben werden.");
bool copy = !forceMove && !IsInStorage(source) && IsInStorage(targetDir);
if (FindEquivalent(targetDir, source) is { } existing)
{
var where = IsDesktopDir(targetDir) ? "auf dem Desktop" : $"in „{Path.GetFileName(targetDir)}“";
return new(TransferStatus.Skipped, source, existing, Message: $"„{DisplayName(source)}“ ist schon {where}.");
}
Directory.CreateDirectory(targetDir);
var dest = UniquePath(targetDir, name);
try
{
if (copy) CopyAny(source, dest, onBytes, ct);
else MoveAny(source, dest, onBytes, ct);
}
catch (OperationCanceledException)
{
// halb kopiertes Ziel wieder entfernen – die Quelle ist unverändert
// (beim Verschieben zwischen Laufwerken wird die Quelle erst nach vollständiger Kopie gelöscht)
DeletePartial(dest);
return new(TransferStatus.Cancelled, source);
}
return new(TransferStatus.Ok, source, dest, copy);
}
catch (UnauthorizedAccessException)
{
return new(TransferStatus.Error, source, Message: $"„{name}“: Keine Berechtigung. " +
"(Icons auf dem öffentlichen Desktop brauchen Administratorrechte – App ggf. als Administrator starten.)");
}
catch (Exception ex)
{
return new(TransferStatus.Error, source, Message: $"„{name}“: {ex.Message}");
}
}
/// <summary>Verschiebt Datei oder Ordner – auch zwischen Laufwerken.</summary>
public static void MoveAny(string source, string dest, Action<long>? onBytes = null, CancellationToken ct = default)
{
NoteCreated(dest);
if (File.Exists(source))
{
File.Move(source, dest);
return;
}
if (string.Equals(Path.GetPathRoot(source), Path.GetPathRoot(dest), StringComparison.OrdinalIgnoreCase))
{
Directory.Move(source, dest);
return;
}
CopyDirectory(source, dest, onBytes, ct);
Directory.Delete(source, recursive: true);
}
public static void CopyAny(string source, string dest, Action<long>? onBytes = null, CancellationToken ct = default)
{
NoteCreated(dest);
if (Directory.Exists(source)) CopyDirectory(source, dest, onBytes, ct);
else CopyFile(source, dest, onBytes, ct);
}
/// <summary>Benennt ein Element um. Gibt den neuen Pfad zurück.</summary>
public static string Rename(string path, string newName)
{
var dir = Path.GetDirectoryName(path)!;
var dest = Path.Combine(dir, newName);
if (string.Equals(path, dest, StringComparison.Ordinal)) return path;
if (string.Equals(path, dest, StringComparison.OrdinalIgnoreCase))
{ // nur Groß-/Kleinschreibung geändert
var tmp = path + ".umbenennen";
MoveAny(path, tmp);
MoveAny(tmp, dest);
return dest;
}
if (File.Exists(dest) || Directory.Exists(dest)) throw new IOException($"„{newName}“ gibt es dort schon.");
MoveAny(path, dest);
return dest;
}
/// <summary>
/// "Löscht" ein Element: es wandert erst in einen internen Ordner, damit Strg+Z funktioniert.
/// Erst wenn es nicht mehr rückgängig gemacht werden kann, geht es in den Papierkorb.
/// </summary>
public static string Delete(string path)
{
var dir = Path.Combine(Holding, Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
var dest = Path.Combine(dir, Path.GetFileName(path.TrimEnd('\\')));
MoveAny(path, dest);
return dest;
}
/// <summary>Endgültig in den Windows-Papierkorb.</summary>
public static void Recycle(string path)
{
try
{
if (Directory.Exists(path))
Microsoft.VisualBasic.FileIO.FileSystem.DeleteDirectory(path,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
else if (File.Exists(path))
Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile(path,
Microsoft.VisualBasic.FileIO.UIOption.OnlyErrorDialogs, Microsoft.VisualBasic.FileIO.RecycleOption.SendToRecycleBin);
// leeren Hilfsordner aufräumen
var parent = Path.GetDirectoryName(path);
if (parent is not null && IsUnder(parent, Holding) && Directory.Exists(parent) && !Directory.EnumerateFileSystemEntries(parent).Any())
Directory.Delete(parent);
}
catch { }
}
/// <summary>Reste von einem Absturz in den Papierkorb legen.</summary>
public static void FlushHolding()
{
if (!Directory.Exists(Holding)) return;
foreach (var d in Directory.GetDirectories(Holding))
foreach (var e in Directory.EnumerateFileSystemEntries(d).ToList())
Recycle(e);
}
// ---------- Selbst angelegte Desktop-Elemente merken (damit die Auto-Sortierung sie ignoriert) ----------
private static readonly ConcurrentDictionary<string, DateTime> _created = new(StringComparer.OrdinalIgnoreCase);
private static void NoteCreated(string path) => _created[path] = DateTime.UtcNow;
/// <summary>Von der App selbst angelegte Datei merken (z. B. Desktop-Verknüpfung einer Start-Gruppe).</summary>
public static void MarkCreated(string path) => NoteCreated(path);
public static bool CreatedByApp(string path) =>
_created.TryGetValue(path, out var t) && DateTime.UtcNow - t < TimeSpan.FromSeconds(15);
/// <summary>Größe eines Ordners in Bytes (bricht ab, sobald limit überschritten ist).</summary>
public static long DirectorySize(string dir, long limit = long.MaxValue)
{
long sum = 0;
try
{
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
{
try { sum += new FileInfo(f).Length; } catch { }
if (sum > limit) break;
}
}
catch { }
return sum;
}
// ---------- Helfer ----------
public static string DisplayName(string path)
{
var n = Path.GetFileName(path.TrimEnd('\\'));
var ext = Path.GetExtension(n).ToLowerInvariant();
return ext is ".lnk" or ".url" or ".appref-ms" ? Path.GetFileNameWithoutExtension(n) : n;
}
/// <summary>Endung, die beim Umbenennen erhalten bleibt (bei Verknüpfungen versteckt).</summary>
public static string HiddenExtension(string path)
{
var ext = Path.GetExtension(path);
return ext.ToLowerInvariant() is ".lnk" or ".url" or ".appref-ms" ? ext : "";
}
public static bool SamePath(string? a, string? b) =>
a is not null && b is not null &&
string.Equals(Path.GetFullPath(a).TrimEnd('\\'), Path.GetFullPath(b).TrimEnd('\\'), StringComparison.OrdinalIgnoreCase);
public static bool IsUnder(string path, string dir) =>
(Path.GetFullPath(path).TrimEnd('\\') + "\\").StartsWith(Path.GetFullPath(dir).TrimEnd('\\') + "\\", StringComparison.OrdinalIgnoreCase);
public static string UniquePath(string dir, string fileName)
{
var path = Path.Combine(dir, fileName);
if (!File.Exists(path) && !Directory.Exists(path)) return path;
var stem = Path.GetFileNameWithoutExtension(fileName);
var ext = Path.GetExtension(fileName);
for (int i = 2; ; i++)
{
path = Path.Combine(dir, $"{stem} ({i}){ext}");
if (!File.Exists(path) && !Directory.Exists(path)) return path;
}
}
private static void CopyDirectory(string source, string dest, Action<long>? onBytes, CancellationToken ct)
{
Directory.CreateDirectory(dest);
foreach (var f in Directory.GetFiles(source))
CopyFile(f, Path.Combine(dest, Path.GetFileName(f)), onBytes, ct);
foreach (var d in Directory.GetDirectories(source))
CopyDirectory(d, Path.Combine(dest, Path.GetFileName(d)), onBytes, ct);
}
/// <summary>Kopiert in 1-MB-Stücken, damit Fortschritt angezeigt und abgebrochen werden kann.</summary>
private static void CopyFile(string source, string dest, Action<long>? onBytes, CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
const int Chunk = 1 << 20;
using (var input = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, Chunk, FileOptions.SequentialScan))
using (var output = new FileStream(dest, FileMode.CreateNew, FileAccess.Write, FileShare.None, Chunk))
{
var buffer = new byte[Chunk];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ct.ThrowIfCancellationRequested();
output.Write(buffer, 0, read);
onBytes?.Invoke(read);
}
}
// Zeitstempel und Attribute wie beim normalen Kopieren übernehmen
try
{
File.SetCreationTimeUtc(dest, File.GetCreationTimeUtc(source));
File.SetLastWriteTimeUtc(dest, File.GetLastWriteTimeUtc(source));
File.SetAttributes(dest, File.GetAttributes(source) & ~FileAttributes.ReadOnly);
}
catch { }
}
private static void DeletePartial(string path)
{
try
{
if (Directory.Exists(path)) Directory.Delete(path, recursive: true);
else if (File.Exists(path)) File.Delete(path);
}
catch { }
}
/// <summary>Wie viele Bytes müssen für diese Übertragung tatsächlich kopiert werden? (0 = nur umbenennen)</summary>
public static long BytesToCopy(string source, string targetDir, bool forceMove)
{
bool copy = !forceMove && !IsInStorage(source) && IsInStorage(targetDir);
bool crossDrive = !string.Equals(Path.GetPathRoot(Path.GetFullPath(source)), Path.GetPathRoot(Path.GetFullPath(targetDir)),
StringComparison.OrdinalIgnoreCase);
if (!copy && !crossDrive) return 0;
try { return Directory.Exists(source) ? DirectorySize(source) : new FileInfo(source).Length; }
catch { return 0; }
}
}
+74 -74
View File
@@ -1,74 +1,74 @@
using System.Windows;
using System.Windows.Media;
using Microsoft.Win32;
namespace DeskCore;
/// <summary>Übernimmt Hell/Dunkel-Modus und Akzentfarbe von Windows – auch live, wenn man sie ändert.</summary>
public static class Theme
{
public static bool IsLight { get; private set; } = true;
/// <summary>Vom Benutzer gewählter Modus; "System" folgt der Windows-Einstellung.</summary>
public static DesignMode Mode => AppSettings.Current.Design;
public static void Init()
{
Apply();
SystemEvents.UserPreferenceChanged += (_, e) =>
{
if (e.Category is UserPreferenceCategory.General or UserPreferenceCategory.Color or UserPreferenceCategory.VisualStyle)
Application.Current.Dispatcher.BeginInvoke(Apply);
};
}
public static void Apply()
{
IsLight = Mode switch
{
DesignMode.Light => true,
DesignMode.Dark => false,
_ => ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0,
};
var res = Application.Current.Resources;
res.MergedDictionaries[0] = new ResourceDictionary
{
Source = new Uri($"pack://application:,,,/Themes/Colors.{(IsLight ? "Light" : "Dark")}.xaml")
};
// DWM speichert die Akzentfarbe als ABGR
int abgr = ReadDword(@"Software\Microsoft\Windows\DWM", "AccentColor") is var v and not -1 ? v : unchecked((int)0xFFD77800);
var accent = Color.FromRgb((byte)abgr, (byte)(abgr >> 8), (byte)(abgr >> 16));
// Wie Windows 11: im Hellmodus etwas dunkler + weiße Schrift, im Dunkelmodus deutlich heller + schwarze Schrift
var a = IsLight ? Mix(accent, Colors.Black, 0.12) : Mix(accent, Colors.White, 0.45);
res["AccentBrush"] = Brush(a);
res["AccentHoverBrush"] = Brush(IsLight ? Mix(a, Colors.White, 0.1) : Mix(a, Colors.Black, 0.1));
res["OnAccentBrush"] = Brush(IsLight ? Colors.White : Colors.Black);
res["AccentSubtleBrush"] = Brush(Color.FromArgb(IsLight ? (byte)0x22 : (byte)0x30, a.R, a.G, a.B));
res["AccentSubtleStrokeBrush"] = Brush(Color.FromArgb(IsLight ? (byte)0x55 : (byte)0x60, a.R, a.G, a.B));
Changed?.Invoke();
}
public static event Action? Changed;
private static int ReadDword(string key, string name)
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(key);
return k?.GetValue(name) is int i ? i : -1;
}
catch { return -1; }
}
private static Color Mix(Color c, Color with, double t) => Color.FromRgb(
(byte)(c.R + (with.R - c.R) * t), (byte)(c.G + (with.G - c.G) * t), (byte)(c.B + (with.B - c.B) * t));
private static SolidColorBrush Brush(Color c)
{
var b = new SolidColorBrush(c);
b.Freeze();
return b;
}
}
using System.Windows;
using System.Windows.Media;
using Microsoft.Win32;
namespace DeskCore;
/// <summary>Übernimmt Hell/Dunkel-Modus und Akzentfarbe von Windows – auch live, wenn man sie ändert.</summary>
public static class Theme
{
public static bool IsLight { get; private set; } = true;
/// <summary>Vom Benutzer gewählter Modus; "System" folgt der Windows-Einstellung.</summary>
public static DesignMode Mode => AppSettings.Current.Design;
public static void Init()
{
Apply();
SystemEvents.UserPreferenceChanged += (_, e) =>
{
if (e.Category is UserPreferenceCategory.General or UserPreferenceCategory.Color or UserPreferenceCategory.VisualStyle)
Application.Current.Dispatcher.BeginInvoke(Apply);
};
}
public static void Apply()
{
IsLight = Mode switch
{
DesignMode.Light => true,
DesignMode.Dark => false,
_ => ReadDword(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme") != 0,
};
var res = Application.Current.Resources;
res.MergedDictionaries[0] = new ResourceDictionary
{
Source = new Uri($"pack://application:,,,/Themes/Colors.{(IsLight ? "Light" : "Dark")}.xaml")
};
// DWM speichert die Akzentfarbe als ABGR
int abgr = ReadDword(@"Software\Microsoft\Windows\DWM", "AccentColor") is var v and not -1 ? v : unchecked((int)0xFFD77800);
var accent = Color.FromRgb((byte)abgr, (byte)(abgr >> 8), (byte)(abgr >> 16));
// Wie Windows 11: im Hellmodus etwas dunkler + weiße Schrift, im Dunkelmodus deutlich heller + schwarze Schrift
var a = IsLight ? Mix(accent, Colors.Black, 0.12) : Mix(accent, Colors.White, 0.45);
res["AccentBrush"] = Brush(a);
res["AccentHoverBrush"] = Brush(IsLight ? Mix(a, Colors.White, 0.1) : Mix(a, Colors.Black, 0.1));
res["OnAccentBrush"] = Brush(IsLight ? Colors.White : Colors.Black);
res["AccentSubtleBrush"] = Brush(Color.FromArgb(IsLight ? (byte)0x22 : (byte)0x30, a.R, a.G, a.B));
res["AccentSubtleStrokeBrush"] = Brush(Color.FromArgb(IsLight ? (byte)0x55 : (byte)0x60, a.R, a.G, a.B));
Changed?.Invoke();
}
public static event Action? Changed;
private static int ReadDword(string key, string name)
{
try
{
using var k = Registry.CurrentUser.OpenSubKey(key);
return k?.GetValue(name) is int i ? i : -1;
}
catch { return -1; }
}
private static Color Mix(Color c, Color with, double t) => Color.FromRgb(
(byte)(c.R + (with.R - c.R) * t), (byte)(c.G + (with.G - c.G) * t), (byte)(c.B + (with.B - c.B) * t));
private static SolidColorBrush Brush(Color c)
{
var b = new SolidColorBrush(c);
b.Freeze();
return b;
}
}
+27 -27
View File
@@ -1,27 +1,27 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BaseBrush" Color="#202020"/>
<SolidColorBrush x:Key="LayerBrush" Color="#282828"/>
<SolidColorBrush x:Key="LayerStrokeBrush" Color="#1A1A1A"/>
<SolidColorBrush x:Key="SubtleHoverBrush" Color="#0FFFFFFF"/>
<SolidColorBrush x:Key="SubtlePressedBrush" Color="#0AFFFFFF"/>
<SolidColorBrush x:Key="SubtleSelectedBrush" Color="#14FFFFFF"/>
<SolidColorBrush x:Key="ControlFillBrush" Color="#2D2D2D"/>
<SolidColorBrush x:Key="ControlHoverBrush" Color="#323232"/>
<SolidColorBrush x:Key="ControlPressedBrush" Color="#272727"/>
<SolidColorBrush x:Key="ControlStrokeBrush" Color="#353535"/>
<SolidColorBrush x:Key="ControlStrongStrokeBrush" Color="#8A8A8A"/>
<SolidColorBrush x:Key="InputFocusBrush" Color="#1F1F1F"/>
<SolidColorBrush x:Key="TextBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#C8C8C8"/>
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#9A9A9A"/>
<SolidColorBrush x:Key="FlyoutBrush" Color="#2C2C2C"/>
<SolidColorBrush x:Key="FlyoutStrokeBrush" Color="#404040"/>
<SolidColorBrush x:Key="DialogFooterBrush" Color="#202020"/>
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#9F9F9F"/>
<SolidColorBrush x:Key="OverlayBrush" Color="#66000000"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#4CAF50"/>
<SolidColorBrush x:Key="DangerBrush" Color="#FF99A4"/>
<SolidColorBrush x:Key="FenceBrush" Color="#F21C1C1C"/>
<Color x:Key="ShadowColor">#000000</Color>
</ResourceDictionary>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BaseBrush" Color="#202020"/>
<SolidColorBrush x:Key="LayerBrush" Color="#282828"/>
<SolidColorBrush x:Key="LayerStrokeBrush" Color="#1A1A1A"/>
<SolidColorBrush x:Key="SubtleHoverBrush" Color="#0FFFFFFF"/>
<SolidColorBrush x:Key="SubtlePressedBrush" Color="#0AFFFFFF"/>
<SolidColorBrush x:Key="SubtleSelectedBrush" Color="#14FFFFFF"/>
<SolidColorBrush x:Key="ControlFillBrush" Color="#2D2D2D"/>
<SolidColorBrush x:Key="ControlHoverBrush" Color="#323232"/>
<SolidColorBrush x:Key="ControlPressedBrush" Color="#272727"/>
<SolidColorBrush x:Key="ControlStrokeBrush" Color="#353535"/>
<SolidColorBrush x:Key="ControlStrongStrokeBrush" Color="#8A8A8A"/>
<SolidColorBrush x:Key="InputFocusBrush" Color="#1F1F1F"/>
<SolidColorBrush x:Key="TextBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#C8C8C8"/>
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#9A9A9A"/>
<SolidColorBrush x:Key="FlyoutBrush" Color="#2C2C2C"/>
<SolidColorBrush x:Key="FlyoutStrokeBrush" Color="#404040"/>
<SolidColorBrush x:Key="DialogFooterBrush" Color="#202020"/>
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#9F9F9F"/>
<SolidColorBrush x:Key="OverlayBrush" Color="#66000000"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#4CAF50"/>
<SolidColorBrush x:Key="DangerBrush" Color="#FF99A4"/>
<SolidColorBrush x:Key="FenceBrush" Color="#F21C1C1C"/>
<Color x:Key="ShadowColor">#000000</Color>
</ResourceDictionary>
+27 -27
View File
@@ -1,27 +1,27 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BaseBrush" Color="#F3F3F3"/>
<SolidColorBrush x:Key="LayerBrush" Color="#FAFAFA"/>
<SolidColorBrush x:Key="LayerStrokeBrush" Color="#E5E5E5"/>
<SolidColorBrush x:Key="SubtleHoverBrush" Color="#0A000000"/>
<SolidColorBrush x:Key="SubtlePressedBrush" Color="#06000000"/>
<SolidColorBrush x:Key="SubtleSelectedBrush" Color="#0F000000"/>
<SolidColorBrush x:Key="ControlFillBrush" Color="#FDFDFD"/>
<SolidColorBrush x:Key="ControlHoverBrush" Color="#F6F6F6"/>
<SolidColorBrush x:Key="ControlPressedBrush" Color="#F2F2F2"/>
<SolidColorBrush x:Key="ControlStrokeBrush" Color="#E3E3E3"/>
<SolidColorBrush x:Key="ControlStrongStrokeBrush" Color="#C4C4C4"/>
<SolidColorBrush x:Key="InputFocusBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextBrush" Color="#1B1B1B"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#5D5D5D"/>
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#8B8B8B"/>
<SolidColorBrush x:Key="FlyoutBrush" Color="#F9F9F9"/>
<SolidColorBrush x:Key="FlyoutStrokeBrush" Color="#DCDCDC"/>
<SolidColorBrush x:Key="DialogFooterBrush" Color="#F3F3F3"/>
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#8A8A8A"/>
<SolidColorBrush x:Key="OverlayBrush" Color="#4D000000"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#0F7B0F"/>
<SolidColorBrush x:Key="DangerBrush" Color="#C42B1C"/>
<SolidColorBrush x:Key="FenceBrush" Color="#F5F7F7F7"/>
<Color x:Key="ShadowColor">#000000</Color>
</ResourceDictionary>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush x:Key="BaseBrush" Color="#F3F3F3"/>
<SolidColorBrush x:Key="LayerBrush" Color="#FAFAFA"/>
<SolidColorBrush x:Key="LayerStrokeBrush" Color="#E5E5E5"/>
<SolidColorBrush x:Key="SubtleHoverBrush" Color="#0A000000"/>
<SolidColorBrush x:Key="SubtlePressedBrush" Color="#06000000"/>
<SolidColorBrush x:Key="SubtleSelectedBrush" Color="#0F000000"/>
<SolidColorBrush x:Key="ControlFillBrush" Color="#FDFDFD"/>
<SolidColorBrush x:Key="ControlHoverBrush" Color="#F6F6F6"/>
<SolidColorBrush x:Key="ControlPressedBrush" Color="#F2F2F2"/>
<SolidColorBrush x:Key="ControlStrokeBrush" Color="#E3E3E3"/>
<SolidColorBrush x:Key="ControlStrongStrokeBrush" Color="#C4C4C4"/>
<SolidColorBrush x:Key="InputFocusBrush" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TextBrush" Color="#1B1B1B"/>
<SolidColorBrush x:Key="TextSecondaryBrush" Color="#5D5D5D"/>
<SolidColorBrush x:Key="TextTertiaryBrush" Color="#8B8B8B"/>
<SolidColorBrush x:Key="FlyoutBrush" Color="#F9F9F9"/>
<SolidColorBrush x:Key="FlyoutStrokeBrush" Color="#DCDCDC"/>
<SolidColorBrush x:Key="DialogFooterBrush" Color="#F3F3F3"/>
<SolidColorBrush x:Key="ScrollThumbBrush" Color="#8A8A8A"/>
<SolidColorBrush x:Key="OverlayBrush" Color="#4D000000"/>
<SolidColorBrush x:Key="SuccessBrush" Color="#0F7B0F"/>
<SolidColorBrush x:Key="DangerBrush" Color="#C42B1C"/>
<SolidColorBrush x:Key="FenceBrush" Color="#F5F7F7F7"/>
<Color x:Key="ShadowColor">#000000</Color>
</ResourceDictionary>
+651 -651
View File
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
using System.Diagnostics;
using System.IO;
using System.Text.Json;
using System.Windows.Threading;
namespace DeskCore;
public class UsageEntry
{
public int Count { get; set; }
public DateTime Last { get; set; }
}
/// <summary>
/// Merkt sich, was wie oft und wann zuletzt über DeskCore gestartet wurde (für „Meistgenutzt“, „Zuletzt benutzt“
/// und die Schnellstart-Suche). Liegt getrennt von den Einstellungen in usage.json, weil es sich ständig ändert.
/// </summary>
public static class Usage
{
/// <summary>Schlüssel-Präfix für Start-Gruppen (die keinen Dateipfad haben).</summary>
public const string GroupPrefix = "gruppe:";
private static readonly string FilePath = Path.Combine(AppSettings.Folder, "usage.json");
private static readonly Dictionary<string, UsageEntry> _data = Load();
private static DispatcherTimer? _saveTimer;
/// <summary>Etwas wurde gestartet (z. B. damit „Zuletzt benutzt“-Boxen sich aktualisieren).</summary>
public static event Action? Changed;
private static Dictionary<string, UsageEntry> Load()
{
try
{
if (File.Exists(FilePath) &&
JsonSerializer.Deserialize<Dictionary<string, UsageEntry>>(File.ReadAllText(FilePath)) is { } d)
return new(d, StringComparer.OrdinalIgnoreCase);
}
catch { }
return new(StringComparer.OrdinalIgnoreCase);
}
/// <summary>Programm/Datei starten und den Start mitzählen. Wirft bei Fehlern wie Process.Start.</summary>
public static void Start(string path)
{
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
Record(path);
}
public static void Record(string key)
{
if (!_data.TryGetValue(key, out var e)) _data[key] = e = new UsageEntry();
e.Count++;
e.Last = DateTime.Now;
ScheduleSave();
Changed?.Invoke();
}
public static int Count(string key) => _data.TryGetValue(key, out var e) ? e.Count : 0;
/// <summary>
/// Wie „beliebt“ etwas ist: Anzahl der Starts, mit der Zeit abnehmend gewichtet –
/// was du früher oft, aber seit Wochen nicht mehr benutzt hast, rutscht langsam nach hinten.
/// </summary>
public static double Score(string key)
{
if (!_data.TryGetValue(key, out var e)) return 0;
double days = Math.Max(0, (DateTime.Now - e.Last).TotalDays);
return e.Count / (1 + days / 30);
}
/// <summary>Zuletzt gestartete Dateien, die es noch gibt (neueste zuerst).</summary>
public static List<string> Recent(int max) => _data
.Where(kv => !kv.Key.StartsWith(GroupPrefix) && (File.Exists(kv.Key) || Directory.Exists(kv.Key)))
.OrderByDescending(kv => kv.Value.Last)
.Take(max)
.Select(kv => kv.Key)
.ToList();
/// <summary>Datei wurde umbenannt/verschoben → Zählung mitnehmen.</summary>
public static void Rename(string oldKey, string newKey)
{
if (!_data.Remove(oldKey, out var e)) return;
if (_data.TryGetValue(newKey, out var existing))
{
existing.Count += e.Count;
if (e.Last > existing.Last) existing.Last = e.Last;
}
else _data[newKey] = e;
ScheduleSave();
}
public static void Forget(string key)
{
if (_data.Remove(key)) { ScheduleSave(); Changed?.Invoke(); }
}
private static void ScheduleSave()
{
_saveTimer ??= new DispatcherTimer(TimeSpan.FromSeconds(2), DispatcherPriority.Background, (_, _) => Flush(),
Dispatcher.CurrentDispatcher);
_saveTimer.Stop();
_saveTimer.Start();
}
/// <summary>Sofort speichern (z. B. beim Beenden).</summary>
public static void Flush()
{
_saveTimer?.Stop();
try
{
// Nicht mehr vorhandene Einträge, die lange nicht benutzt wurden, aufräumen
foreach (var key in _data.Where(kv => kv.Value.Last < DateTime.Now.AddDays(-180) && !kv.Key.StartsWith(GroupPrefix)
&& !File.Exists(kv.Key) && !Directory.Exists(kv.Key)).Select(kv => kv.Key).ToList())
_data.Remove(key);
Directory.CreateDirectory(AppSettings.Folder);
File.WriteAllText(FilePath, JsonSerializer.Serialize(_data));
}
catch { }
}
}
+140 -111
View File
@@ -1,111 +1,140 @@
using System.IO;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>
/// Beobachtet Desktop und Kategorie-Ordner. Meldet Änderungen gebündelt (statt bei jedem einzelnen Ereignis)
/// und sammelt neu auf dem Desktop aufgetauchte Elemente für die automatische Sortierung.
/// </summary>
public sealed class Watcher : IDisposable
{
private readonly List<FileSystemWatcher> _watchers = [];
private readonly DispatcherTimer _changedTimer;
private readonly DispatcherTimer _newTimer;
private readonly HashSet<string> _newOnDesktop = new(StringComparer.OrdinalIgnoreCase);
private readonly object _lock = new();
/// <summary>Irgendetwas hat sich geändert (UI-Thread).</summary>
public event Action? Changed;
/// <summary>Neue Elemente auf dem Desktop (UI-Thread, nach kurzer Wartezeit).</summary>
public event Action<List<string>>? NewOnDesktop;
public Watcher(Dispatcher dispatcher)
{
_changedTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(350), DispatcherPriority.Background, (_, _) =>
{
_changedTimer!.Stop();
Changed?.Invoke();
}, dispatcher);
_changedTimer.Stop();
// Etwas länger warten: Installer legen Verknüpfungen oft in mehreren Schritten an
_newTimer = new DispatcherTimer(TimeSpan.FromSeconds(2), DispatcherPriority.Background, (_, _) =>
{
_newTimer!.Stop();
List<string> items;
lock (_lock) { items = _newOnDesktop.ToList(); _newOnDesktop.Clear(); }
items = items.Where(p => (File.Exists(p) || Directory.Exists(p)) && !Storage.CreatedByApp(p)).ToList();
if (items.Count > 0) NewOnDesktop?.Invoke(items);
}, dispatcher);
_newTimer.Stop();
Directory.CreateDirectory(Storage.Root);
Add(Storage.UserDesktop, false, true);
Add(Storage.PublicDesktop, false, true);
Add(Storage.Root, true, false);
}
private void Add(string dir, bool subdirs, bool isDesktop)
{
if (!Directory.Exists(dir)) return;
try
{
var w = new FileSystemWatcher(dir)
{
IncludeSubdirectories = subdirs,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite,
InternalBufferSize = 64 * 1024,
};
FileSystemEventHandler onChange = (_, e) =>
{
Poke();
if (isDesktop && e.ChangeType == WatcherChangeTypes.Created) NoteNew(e.FullPath);
};
w.Created += onChange;
w.Deleted += onChange;
w.Changed += onChange;
w.Renamed += (_, e) => { Poke(); if (isDesktop) NoteNew(e.FullPath); };
w.Error += (_, _) => Poke();
w.EnableRaisingEvents = true;
_watchers.Add(w);
}
catch { }
}
private void NoteNew(string path)
{
var name = Path.GetFileName(path);
if (name.StartsWith('~') || name.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase) ||
name.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase)) return;
lock (_lock) _newOnDesktop.Add(path);
_newTimer.Dispatcher.BeginInvoke(() => { _newTimer.Stop(); _newTimer.Start(); });
}
private void Poke() => _changedTimer.Dispatcher.BeginInvoke(() => { _changedTimer.Stop(); _changedTimer.Start(); });
public void Dispose()
{
foreach (var w in _watchers) w.Dispose();
_changedTimer.Stop();
_newTimer.Stop();
}
}
public static class AutoSorter
{
/// <summary>Erste passende Regel → Kategoriename (nur existierende Kategorien).</summary>
public static string? Match(string path)
{
var name = Storage.DisplayName(path);
var cats = Storage.Categories();
foreach (var r in AppSettings.Current.Rules)
{
if (string.IsNullOrWhiteSpace(r.Contains)) continue;
if (!name.Contains(r.Contains.Trim(), StringComparison.CurrentCultureIgnoreCase)) continue;
var cat = cats.FirstOrDefault(c => string.Equals(c, r.Category, StringComparison.OrdinalIgnoreCase));
if (cat is not null) return cat;
}
return null;
}
}
using System.IO;
using System.Windows.Threading;
namespace DeskCore;
/// <summary>
/// Beobachtet Desktop und Kategorie-Ordner. Meldet Änderungen gebündelt (statt bei jedem einzelnen Ereignis)
/// und sammelt neu auf dem Desktop aufgetauchte Elemente für die automatische Sortierung.
/// </summary>
public sealed class Watcher : IDisposable
{
private readonly List<FileSystemWatcher> _watchers = [];
private readonly DispatcherTimer _changedTimer;
private readonly DispatcherTimer _newTimer;
private readonly HashSet<string> _newOnDesktop = new(StringComparer.OrdinalIgnoreCase);
private readonly object _lock = new();
/// <summary>Irgendetwas hat sich geändert (UI-Thread).</summary>
public event Action? Changed;
/// <summary>Neue Elemente auf dem Desktop (UI-Thread, nach kurzer Wartezeit).</summary>
public event Action<List<string>>? NewOnDesktop;
public Watcher(Dispatcher dispatcher)
{
_changedTimer = new DispatcherTimer(TimeSpan.FromMilliseconds(350), DispatcherPriority.Background, (_, _) =>
{
_changedTimer!.Stop();
Changed?.Invoke();
}, dispatcher);
_changedTimer.Stop();
// Etwas länger warten: Installer legen Verknüpfungen oft in mehreren Schritten an
_newTimer = new DispatcherTimer(TimeSpan.FromSeconds(2), DispatcherPriority.Background, (_, _) =>
{
_newTimer!.Stop();
List<string> items;
lock (_lock) { items = _newOnDesktop.ToList(); _newOnDesktop.Clear(); }
items = items.Where(p => (File.Exists(p) || Directory.Exists(p)) && !Storage.CreatedByApp(p)).ToList();
if (items.Count > 0) NewOnDesktop?.Invoke(items);
}, dispatcher);
_newTimer.Stop();
Directory.CreateDirectory(Storage.Root);
Add(Storage.UserDesktop, false, true);
Add(Storage.PublicDesktop, false, true);
Add(Storage.Root, true, false);
}
private void Add(string dir, bool subdirs, bool isDesktop)
{
if (!Directory.Exists(dir)) return;
try
{
var w = new FileSystemWatcher(dir)
{
IncludeSubdirectories = subdirs,
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite,
InternalBufferSize = 64 * 1024,
};
FileSystemEventHandler onChange = (_, e) =>
{
Poke();
if (isDesktop && e.ChangeType == WatcherChangeTypes.Created) NoteNew(e.FullPath);
};
w.Created += onChange;
w.Deleted += onChange;
w.Changed += onChange;
w.Renamed += (_, e) => { Poke(); if (isDesktop) NoteNew(e.FullPath); };
w.Error += (_, _) => Poke();
w.EnableRaisingEvents = true;
_watchers.Add(w);
}
catch { }
}
private void NoteNew(string path)
{
var name = Path.GetFileName(path);
if (name.StartsWith('~') || name.EndsWith(".tmp", StringComparison.OrdinalIgnoreCase) ||
name.Equals("desktop.ini", StringComparison.OrdinalIgnoreCase)) return;
lock (_lock) _newOnDesktop.Add(path);
_newTimer.Dispatcher.BeginInvoke(() => { _newTimer.Stop(); _newTimer.Start(); });
}
private void Poke() => _changedTimer.Dispatcher.BeginInvoke(() => { _changedTimer.Stop(); _changedTimer.Start(); });
public void Dispose()
{
foreach (var w in _watchers) w.Dispose();
_changedTimer.Stop();
_newTimer.Stop();
}
}
public static class AutoSorter
{
/// <summary>Dateityp-Gruppen für Regeln vom Typ "Dateityp ist".</summary>
private static readonly Dictionary<string, string[]> Groups = new(StringComparer.CurrentCultureIgnoreCase)
{
["Bilder"] = ["jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "tif", "tiff", "svg", "ico", "raw", "psd"],
["Videos"] = ["mp4", "mkv", "avi", "mov", "wmv", "webm", "m4v", "flv", "mpg", "mpeg"],
["Musik"] = ["mp3", "wav", "flac", "aac", "ogg", "m4a", "wma", "opus"],
["Dokumente"] = ["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "odt", "ods", "odp", "txt", "rtf", "md", "csv"],
["Archive"] = ["zip", "rar", "7z", "tar", "gz", "bz2", "xz", "iso"],
["Programme"] = ["exe", "msi", "lnk", "url", "appref-ms", "bat", "cmd"],
["Verknüpfungen"] = ["lnk", "url", "appref-ms"],
};
/// <summary>Passt eine Regel auf das Element?</summary>
public static bool Matches(SortRule r, string path)
{
var value = r.Contains.Trim();
if (value.Length == 0) return false;
if (r.Kind != "Typ") return Storage.DisplayName(path).Contains(value, StringComparison.CurrentCultureIgnoreCase);
bool isFolder = Directory.Exists(path);
var ext = Path.GetExtension(path).TrimStart('.').ToLowerInvariant();
foreach (var token in value.Split([',', ';', ' '], StringSplitOptions.RemoveEmptyEntries))
{
var t = token.Trim().TrimStart('*', '.').ToLowerInvariant();
if (t is "ordner") { if (isFolder) return true; continue; }
if (Groups.TryGetValue(t, out var exts)) { if (!isFolder && exts.Contains(ext)) return true; continue; }
if (!isFolder && t == ext) return true;
}
return false;
}
/// <summary>Erste passende Regel → Kategoriename (nur existierende Kategorien).</summary>
public static string? Match(string path)
{
var cats = Storage.Categories();
foreach (var r in AppSettings.Current.Rules)
{
if (!Matches(r, path)) continue;
var cat = cats.FirstOrDefault(c => string.Equals(c, r.Category, StringComparison.OrdinalIgnoreCase));
if (cat is not null) return cat;
}
return null;
}
}
Binary file not shown.
+38
View File
@@ -0,0 +1,38 @@
DeskCore – Lizenz- und Nutzungsbedingungen
Copyright (c) M_Viper. Alle Rechte vorbehalten.
Diese Software („DeskCore") sowie sämtlicher zugehöriger Quellcode, Ressourcen und
Dokumentation sind urheberrechtlich geschützt. Mit der Nutzung dieser Software
erkennst du die folgenden Bedingungen an.
1. Erlaubte Nutzung
Die Nutzung von DeskCore ist ausschließlich für den privaten, nicht-kommerziellen
Gebrauch gestattet.
2. Untersagt ist insbesondere
- Die Weitergabe dieser Software oder von Teilen davon an Dritte
- Das Verändern, Dekompilieren, Zurückentwickeln (Reverse Engineering) oder
Bearbeiten des Codes
- Die Veröffentlichung – auch in veränderter oder abgeleiteter Form –
an anderer Stelle als den offiziellen Kanälen des Autors
- Der Verkauf sowie jede sonstige kommerzielle Nutzung
3. Keine Gewährleistung
Die Software wird ohne jegliche Gewährleistung bereitgestellt, weder
ausdrücklich noch stillschweigend, einschließlich, aber nicht beschränkt auf
die Gewährleistung der Marktgängigkeit, der Eignung für einen bestimmten
Zweck und der Nichtverletzung von Rechten Dritter. Der Autor haftet nicht für
Schäden, die aus der Nutzung oder Unmöglichkeit der Nutzung der Software
entstehen.
4. Rechtsfolgen bei Verstoß
Wer gegen diese Bedingungen verstößt, macht sich strafbar und kann rechtlich
belangt werden. Alle Rechte, die hier nicht ausdrücklich eingeräumt werden,
bleiben beim Autor vorbehalten.
5. Markenhinweis
DeskCore ist kein offizielles Produkt von Microsoft. Windows ist
eine Marke der Microsoft Corporation.
Kontakt: https://m-viper.de
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More