diff --git a/src/DesktopMagic/Helpers/FileUtilities.cs b/src/DesktopMagic/Helpers/FileUtilities.cs
new file mode 100644
index 0000000..0428fbc
--- /dev/null
+++ b/src/DesktopMagic/Helpers/FileUtilities.cs
@@ -0,0 +1,93 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace DesktopMagic.Helpers;
+
+internal static class FileUtilities
+{
+ public static bool ExistsOnPath(string fileName)
+ {
+ return GetFullPath(fileName) is not null;
+ }
+
+ public static string? GetFullPath(string fileName)
+ {
+ if (File.Exists(fileName))
+ {
+ return Path.GetFullPath(fileName);
+ }
+
+ string? values = Environment.GetEnvironmentVariable("PATH");
+
+ if (string.IsNullOrEmpty(values))
+ {
+ return null;
+ }
+
+ foreach (string path in values.Split(Path.PathSeparator))
+ {
+ string fullPath = Path.Combine(path, fileName);
+ if (File.Exists(fullPath))
+ {
+ return fullPath;
+ }
+ }
+ return null;
+ }
+
+ public static bool HasAssociatedProgram(string extension)
+ {
+ return GetAssociatedProgram(extension) is not null;
+ }
+
+ public static string? GetAssociatedProgram(string extension)
+ {
+ uint pcchOut = 0;
+ _ = AssocQueryString(AssocF.Verify, AssocStr.Executable, extension, null, null, ref pcchOut);
+
+ StringBuilder pszOut = new StringBuilder((int)pcchOut);
+ _ = AssocQueryString(AssocF.Verify, AssocStr.Executable, extension, null, pszOut, ref pcchOut);
+
+ if (File.Exists(pszOut.ToString()))
+ {
+ return pszOut.ToString();
+ }
+
+ return null;
+ }
+
+ [DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ private static extern uint AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string? pszExtra, [Out] StringBuilder? pszOut, [In][Out] ref uint pcchOut);
+
+ [Flags]
+ public enum AssocF
+ {
+ Init_NoRemapCLSID = 0x1,
+ Init_ByExeName = 0x2,
+ Open_ByExeName = 0x2,
+ Init_DefaultToStar = 0x4,
+ Init_DefaultToFolder = 0x8,
+ NoUserSettings = 0x10,
+ NoTruncate = 0x20,
+ Verify = 0x40,
+ RemapRunDll = 0x80,
+ NoFixUps = 0x100,
+ IgnoreBaseClass = 0x200
+ }
+
+ public enum AssocStr
+ {
+ Command = 1,
+ Executable,
+ FriendlyDocName,
+ FriendlyAppName,
+ NoOpen,
+ ShellNewValue,
+ DDECommand,
+ DDEIfExec,
+ DDEApplication,
+ DDETopic
+ }
+}
\ No newline at end of file
diff --git a/src/DesktopMagic/Plugins/PluginManager.xaml b/src/DesktopMagic/Plugins/PluginManager.xaml
index d2834b5..baba24a 100644
--- a/src/DesktopMagic/Plugins/PluginManager.xaml
+++ b/src/DesktopMagic/Plugins/PluginManager.xaml
@@ -96,7 +96,15 @@
-
+
+
+
+
diff --git a/src/DesktopMagic/Plugins/PluginManager.xaml.cs b/src/DesktopMagic/Plugins/PluginManager.xaml.cs
index 78d3cc2..a6d61ff 100644
--- a/src/DesktopMagic/Plugins/PluginManager.xaml.cs
+++ b/src/DesktopMagic/Plugins/PluginManager.xaml.cs
@@ -1,4 +1,5 @@
using DesktopMagic.DataContexts;
+using DesktopMagic.Dialogs;
using DesktopMagic.Helpers;
using Modio;
@@ -9,11 +10,13 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
+using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
+using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
@@ -34,6 +37,7 @@ public partial class PluginManager : Window
private readonly PluginManagerDataContext pluginManagerDataContext = new();
private readonly string pluginsPath = Path.Combine(App.ApplicationDataPath, "Plugins");
+ private readonly string pluginDevelopmentPath = Path.Combine(App.ApplicationDataPath, "PluginDevelopment");
private readonly DispatcherTimer searchTimer = new()
{
@@ -125,6 +129,9 @@ public partial class PluginManager : Window
e.Cancel = pluginManagerDataContext.IsLoading;
}
+ [GeneratedRegex(@"[^a-zA-Z0-9]")]
+ private static partial Regex IdentifierNameRegex();
+
private async Task Install(Mod mod)
{
pluginManagerDataContext.IsLoading = true;
@@ -235,4 +242,123 @@ public partial class PluginManager : Window
pluginManagerDataContext.IsSearching = false;
}
+
+ private void CreatePluginButton_Click(object sender, RoutedEventArgs e)
+ {
+ try
+ {
+ CreateNewPlugin();
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError(ex.Message, source: "PluginManager");
+ _ = MessageBox.Show(ex.Message, "Plugin Manager", MessageBoxButton.OK, MessageBoxImage.Error);
+ }
+ }
+
+ private void CreateNewPlugin()
+ {
+ if (!FileUtilities.ExistsOnPath("dotnet.exe"))
+ {
+ _ = MessageBox.Show("The .NET SDK is required to create a plugin. Please install it and try again.", "Plugin Manager", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+
+ string pluginGuid = Guid.NewGuid().ToString();
+ string pluginPath = Path.Combine(pluginsPath, pluginGuid);
+ uint pluginId = (uint)Random.Shared.Next(1000, 9999);
+
+ InputDialog inputDialog = new((string)FindResource("enterPluginName"), "Plugin Manager")
+ {
+ Owner = this,
+ };
+
+ if (inputDialog.ShowDialog() is not true)
+ {
+ return;
+ }
+
+ string pluginName = inputDialog.ResponseText;
+ string pluginSafeName = pluginName.ToLower().Replace("_", " ");
+
+ TextInfo info = CultureInfo.CurrentCulture.TextInfo;
+ pluginSafeName = info.ToTitleCase(pluginSafeName);
+ pluginSafeName = IdentifierNameRegex().Replace(pluginSafeName, "");
+
+ if (!Directory.Exists(pluginDevelopmentPath))
+ {
+ _ = Directory.CreateDirectory(pluginDevelopmentPath);
+ }
+
+ string pluginProjectPath = Path.Combine(pluginDevelopmentPath, pluginSafeName);
+
+ if (Directory.Exists(pluginProjectPath))
+ {
+ _ = MessageBox.Show("A plugin with the same name already exists. Please choose a different name.", "Plugin Manager", MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+
+ PluginMetadata pluginMetadata = new(pluginName, pluginId);
+
+ _ = Directory.CreateDirectory(pluginPath);
+ _ = Directory.CreateDirectory(pluginProjectPath);
+
+ string pluginMetadataPath = Path.Combine(pluginPath, "metadata.json");
+ File.WriteAllText(pluginMetadataPath, JsonSerializer.Serialize(pluginMetadata));
+
+ string cmd = $"new classlib -n {pluginSafeName} -o {pluginProjectPath} -f net8.0 --target-framework-override net8.0-windows7";
+ Process process = Process.Start("dotnet", cmd);
+ process.WaitForExit();
+
+ // Install the required NuGet packages
+ process = Process.Start("dotnet", $"add {pluginProjectPath} package DesktopMagic.Api");
+ process.WaitForExit();
+
+ File.Move(Path.Combine(pluginProjectPath, "Class1.cs"), Path.Combine(pluginProjectPath, $"{pluginSafeName}.cs"));
+
+ string code = $@"using DesktopMagic.Api;
+using System.Drawing;
+
+namespace {pluginSafeName};
+
+public class {pluginSafeName}Plugin : Plugin
+{{
+ public override Bitmap Main()
+ {{
+ Bitmap bmp = new Bitmap(1000, 1000);
+
+ using (Graphics g = Graphics.FromImage(bmp))
+ {{
+ g.Clear(Application.Theme.PrimaryColor); // Set the background color to the color specified in the DesktopMagic application.
+
+ g.DrawString(""Hello World"", new Font(Application.Theme.Font, 100), Brushes.Black, new PointF(0, 0)); // Draw ""Hello World"" to the image.
+ }}
+
+ bmp.SetResolution(300, 300); // Set DPI to avoid scaling issues.
+
+ return bmp; // Return the image.
+ }}
+}}
+";
+
+ File.WriteAllText(Path.Combine(pluginProjectPath, $"{pluginSafeName}.cs"), code);
+
+ // Open the project in the default IDE
+
+ string? associatedProgram = FileUtilities.GetAssociatedProgram(".csproj");
+
+ if (associatedProgram is null)
+ {
+ _ = Process.Start("explorer.exe", pluginProjectPath);
+ return;
+ }
+
+ ProcessStartInfo psi = new ProcessStartInfo
+ {
+ FileName = associatedProgram,
+ Arguments = Path.Combine(pluginProjectPath, $"{pluginSafeName}.csproj"),
+ };
+
+ _ = Process.Start(psi);
+ }
}
\ No newline at end of file
diff --git a/src/DesktopMagic/Resources/Strings/StringResources.de.xaml b/src/DesktopMagic/Resources/Strings/StringResources.de.xaml
index 786fdde..a3ff1e3 100644
--- a/src/DesktopMagic/Resources/Strings/StringResources.de.xaml
+++ b/src/DesktopMagic/Resources/Strings/StringResources.de.xaml
@@ -15,6 +15,8 @@
Neu Laden
Plugins Ordner
Plugins Manager
+ Neues Plugin erstellen
+ Pluginnamen eingeben
Farbe:
Standard
Wollen sie das Programm wirklich schließen?
diff --git a/src/DesktopMagic/Resources/Strings/StringResources.en.xaml b/src/DesktopMagic/Resources/Strings/StringResources.en.xaml
index fa6d822..fec2c8b 100644
--- a/src/DesktopMagic/Resources/Strings/StringResources.en.xaml
+++ b/src/DesktopMagic/Resources/Strings/StringResources.en.xaml
@@ -15,6 +15,8 @@
Reload
Plugins Folder
Plugins Manager
+ Create New Plugin
+ Enter plugin name
Color:
Default
Do you really want to close the program?