From c90be28f38018aa4b8fd593fa413e31e85186a61 Mon Sep 17 00:00:00 2001
From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com>
Date: Wed, 24 Dec 2025 00:09:20 +0100
Subject: [PATCH] Add basic support for web based plugins
---
src/DesktopMagic/DesktopMagic.csproj | 1 +
.../Helpers/SettingElementGenerator.cs | 7 +-
src/DesktopMagic/MainWindow.xaml.cs | 53 +++--
src/DesktopMagic/Plugins/IPluginWindow.cs | 24 ++
.../Plugins/PluginManager.xaml.cs | 6 +-
src/DesktopMagic/Plugins/PluginWindow.xaml | 1 +
src/DesktopMagic/Plugins/PluginWindow.xaml.cs | 16 +-
src/DesktopMagic/Plugins/WebPluginWindow.xaml | 32 +++
.../Plugins/WebPluginWindow.xaml.cs | 205 ++++++++++++++++++
9 files changed, 318 insertions(+), 27 deletions(-)
create mode 100644 src/DesktopMagic/Plugins/IPluginWindow.cs
create mode 100644 src/DesktopMagic/Plugins/WebPluginWindow.xaml
create mode 100644 src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs
diff --git a/src/DesktopMagic/DesktopMagic.csproj b/src/DesktopMagic/DesktopMagic.csproj
index 7177fd5..7f3b383 100644
--- a/src/DesktopMagic/DesktopMagic.csproj
+++ b/src/DesktopMagic/DesktopMagic.csproj
@@ -46,6 +46,7 @@
+
diff --git a/src/DesktopMagic/Helpers/SettingElementGenerator.cs b/src/DesktopMagic/Helpers/SettingElementGenerator.cs
index 3327a7b..c4040f0 100644
--- a/src/DesktopMagic/Helpers/SettingElementGenerator.cs
+++ b/src/DesktopMagic/Helpers/SettingElementGenerator.cs
@@ -230,7 +230,10 @@ internal class SettingElementGenerator(ComboBox optionsComboBox)
_ = MessageBox.Show("File execution error:\n" + message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
int index = MainWindow.WindowNames.IndexOf(optionsComboBox.SelectedItem.ToString() ?? string.Empty);
- PluginWindow window = MainWindow.Windows[index];
- window?.Exit();
+ if (index >= 0 && index < MainWindow.Windows.Count)
+ {
+ IPluginWindow window = MainWindow.Windows[index];
+ window?.Exit();
+ }
}
}
\ No newline at end of file
diff --git a/src/DesktopMagic/MainWindow.xaml.cs b/src/DesktopMagic/MainWindow.xaml.cs
index 028f055..2be98b0 100644
--- a/src/DesktopMagic/MainWindow.xaml.cs
+++ b/src/DesktopMagic/MainWindow.xaml.cs
@@ -33,7 +33,7 @@ namespace DesktopMagic
private bool loaded = false;
private bool blockWindowsClosing = true;
- public static List Windows { get; } = [];
+ public static List Windows { get; } = [];
public static List WindowNames { get; } = [];
private DesktopMagicSettings Settings
@@ -115,17 +115,18 @@ namespace DesktopMagic
foreach (var buildInPlugin in builtInPlugins.Keys)
{
- plugins.Add(buildInPlugin.Id, new(buildInPlugin, string.Empty));
+ plugins.Add(buildInPlugin.Id, new(buildInPlugin, PluginType.DotNet, string.Empty));
}
foreach (string directory in Directory.GetDirectories(App.PluginsPath))
{
string? pluginDllPath = Directory.GetFiles(directory, "main.dll").FirstOrDefault();
+ string? pluginHtmlPath = Directory.GetFiles(directory, "main.html").FirstOrDefault();
string? pluginMetadataPath = Directory.GetFiles(directory, "metadata.json").FirstOrDefault();
- if (pluginDllPath is null)
+ if (pluginDllPath is null && pluginHtmlPath is null)
{
- App.Logger.LogError($"Plugin \"{directory}\" has no \"main.dll\"", source: "Main");
+ App.Logger.LogError($"Plugin \"{directory}\" has no \"main.dll\" or \"main.html\"", source: "Main");
continue;
}
@@ -149,7 +150,14 @@ namespace DesktopMagic
continue;
}
- plugins.Add(pluginMetadata.Id, new(pluginMetadata, directory));
+ PluginType pluginType = PluginType.DotNet;
+
+ if (pluginHtmlPath is not null)
+ {
+ pluginType = PluginType.Web;
+ }
+
+ plugins.Add(pluginMetadata.Id, new(pluginMetadata, pluginType, directory));
}
mainWindowDataContext.IsLoading = false;
}
@@ -160,7 +168,7 @@ namespace DesktopMagic
private void EditCheckBox_Click(object? sender, RoutedEventArgs? e)
{
- foreach (PluginWindow window in Windows)
+ foreach (IPluginWindow window in Windows)
{
window.SetEditMode(editCheckBox.IsChecked == true);
}
@@ -213,7 +221,7 @@ namespace DesktopMagic
return;
}
- PluginWindow window;
+ IPluginWindow window;
if (builtInPlugins.TryGetValue(internalPluginData.Metadata, out Type? pluginType))
{
@@ -222,6 +230,13 @@ namespace DesktopMagic
Title = internalPluginData.Metadata.Id.ToString()
};
}
+ else if (internalPluginData.Type == PluginType.Web)
+ {
+ window = new WebPluginWindow(internalPluginData.Metadata, pluginSettings, internalPluginData.DirectoryPath)
+ {
+ Title = internalPluginData.Metadata.Id.ToString()
+ };
+ }
else
{
window = new PluginWindow(internalPluginData.Metadata, pluginSettings, internalPluginData.DirectoryPath)
@@ -241,11 +256,12 @@ namespace DesktopMagic
}
optionsComboBox.SelectedIndex = -1;
optionsComboBox.SelectedIndex = optionsComboBox.Items.IndexOf(internalPluginData.Metadata);
+
window.PluginLoaded -= onPluginLoaded;
});
};
- window.OnExit += () =>
+ Action onExit = () =>
{
Windows.Remove(window);
WindowNames.Remove(window.Title);
@@ -254,11 +270,13 @@ namespace DesktopMagic
blockWindowsClosing = true;
pluginSettings.Enabled = false;
};
- window.PluginLoaded += onPluginLoaded;
- window.ShowInTaskbar = false;
+ window.PluginLoaded += onPluginLoaded;
+ window.OnExit += onExit;
+
window.Show();
window.SetEditMode(editCheckBox.IsChecked == true);
+
window.Closing += DisplayWindow_Closing;
Windows.Add(window);
WindowNames.Add(window.Title);
@@ -289,7 +307,7 @@ namespace DesktopMagic
{
Visibility = Visibility.Collapsed;
UpdateLayout();
- foreach (Window window in Windows)
+ foreach (IPluginWindow window in Windows)
{
window.Hide();
}
@@ -511,7 +529,7 @@ namespace DesktopMagic
Settings = JsonSerializer.Deserialize(json, jsonSettingsOptions) ?? new DesktopMagicSettings();
- if(Settings.Layouts.Count == 0)
+ if (Settings.Layouts.Count == 0)
{
Settings.Layouts.Add(new Layout((string)FindResource("default")));
}
@@ -527,7 +545,7 @@ namespace DesktopMagic
mainWindowDataContext.IsLoading = true;
blockWindowsClosing = false;
- foreach (Window window in Windows)
+ foreach (IPluginWindow window in Windows)
{
window.Close();
}
@@ -630,9 +648,16 @@ namespace DesktopMagic
}
}
- internal class InternalPluginData(PluginMetadata pluginMetadata, string directoryPath)
+ internal class InternalPluginData(PluginMetadata pluginMetadata, PluginType pluginType, string directoryPath)
{
public PluginMetadata Metadata { get; set; } = pluginMetadata;
+ public PluginType Type { get; set; } = pluginType;
public string DirectoryPath { get; set; } = directoryPath;
+
+ }
+ internal enum PluginType
+ {
+ DotNet,
+ Web
}
}
\ No newline at end of file
diff --git a/src/DesktopMagic/Plugins/IPluginWindow.cs b/src/DesktopMagic/Plugins/IPluginWindow.cs
new file mode 100644
index 0000000..d5e8ec4
--- /dev/null
+++ b/src/DesktopMagic/Plugins/IPluginWindow.cs
@@ -0,0 +1,24 @@
+using DesktopMagic.Plugins;
+
+using System;
+
+namespace DesktopMagic;
+
+public interface IPluginWindow
+{
+ event Action? PluginLoaded;
+ event Action? OnExit;
+
+ bool IsRunning { get; }
+ PluginMetadata PluginMetadata { get; }
+ string PluginFolderPath { get; }
+ string Title { get; set; }
+
+ void Exit();
+ void SetEditMode(bool enabled);
+ void Show();
+ void Hide();
+ void Close();
+
+ event System.ComponentModel.CancelEventHandler Closing;
+}
diff --git a/src/DesktopMagic/Plugins/PluginManager.xaml.cs b/src/DesktopMagic/Plugins/PluginManager.xaml.cs
index f7565e2..3f3d9b3 100644
--- a/src/DesktopMagic/Plugins/PluginManager.xaml.cs
+++ b/src/DesktopMagic/Plugins/PluginManager.xaml.cs
@@ -241,12 +241,12 @@ public partial class PluginManager : Window
File.Delete(zipFilePath);
- if (!File.Exists(Path.Combine(pluginPath, "main.dll")))
+ if (!File.Exists(Path.Combine(pluginPath, "main.dll")) && !File.Exists(Path.Combine(pluginPath, "main.html")))
{
- App.Logger.LogError($"Plugin {mod.Id} does not contain main.dll", source: "PluginManager");
+ App.Logger.LogError($"Plugin {mod.Id} does not contain main.dll or main.html", source: "PluginManager");
_ = Remove(pluginPath, mod.Id);
pluginManagerDataContext.IsLoading = false;
- _ = MessageBox.Show("The plugin you are trying to install does not contain a \"main.dll\" file. Please contact the plugin author.", "Plugin Manager", MessageBoxButton.OK, MessageBoxImage.Error);
+ _ = MessageBox.Show("The plugin you are trying to install does not contain a \"main.dll\" or \"main.html\" file. Please contact the plugin author.", "Plugin Manager", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
diff --git a/src/DesktopMagic/Plugins/PluginWindow.xaml b/src/DesktopMagic/Plugins/PluginWindow.xaml
index 79b7dda..7372412 100644
--- a/src/DesktopMagic/Plugins/PluginWindow.xaml
+++ b/src/DesktopMagic/Plugins/PluginWindow.xaml
@@ -8,6 +8,7 @@
Title="PluginWindow" Height="450" Width="800"
Background="Transparent"
WindowStyle="None"
+ ShowInTaskbar="False"
AllowsTransparency="True"
LocationChanged="Window_LocationChanged"
SizeChanged="Window_SizeChanged"
diff --git a/src/DesktopMagic/Plugins/PluginWindow.xaml.cs b/src/DesktopMagic/Plugins/PluginWindow.xaml.cs
index c370585..9909c77 100644
--- a/src/DesktopMagic/Plugins/PluginWindow.xaml.cs
+++ b/src/DesktopMagic/Plugins/PluginWindow.xaml.cs
@@ -23,7 +23,7 @@ using System.Windows.Media.Imaging;
namespace DesktopMagic;
-public partial class PluginWindow : Window
+public partial class PluginWindow : Window, IPluginWindow
{
public event Action? PluginLoaded;
@@ -177,13 +177,6 @@ public partial class PluginWindow : Window
pluginThread.Start();
}
- private void ThemeChanged()
- {
- viewBox.Margin = new Thickness(settings.Theme.Margin);
- border.Background = new SolidColorBrush(MultiColorConverter.ConvertToMediaColor(settings.Theme.BackgroundColor));
- border.CornerRadius = new CornerRadius(settings.Theme.CornerRadius);
- }
-
private void LoadPlugin()
{
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin", source: "Plugin");
@@ -211,6 +204,13 @@ public partial class PluginWindow : Window
PluginLoaded?.Invoke();
}
+ private void ThemeChanged()
+ {
+ viewBox.Margin = new Thickness(settings.Theme.Margin);
+ border.Background = new SolidColorBrush(MultiColorConverter.ConvertToMediaColor(settings.Theme.BackgroundColor));
+ border.CornerRadius = new CornerRadius(settings.Theme.CornerRadius);
+ }
+
private void ExecuteSource()
{
object? instance = pluginClassInstance;
diff --git a/src/DesktopMagic/Plugins/WebPluginWindow.xaml b/src/DesktopMagic/Plugins/WebPluginWindow.xaml
new file mode 100644
index 0000000..51f3061
--- /dev/null
+++ b/src/DesktopMagic/Plugins/WebPluginWindow.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs b/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs
new file mode 100644
index 0000000..965be24
--- /dev/null
+++ b/src/DesktopMagic/Plugins/WebPluginWindow.xaml.cs
@@ -0,0 +1,205 @@
+using DesktopMagic.Helpers;
+using DesktopMagic.Plugins;
+using DesktopMagic.Settings;
+
+using Microsoft.Web.WebView2.Core;
+
+using System;
+using System.IO;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Media;
+
+namespace DesktopMagic;
+
+public partial class WebPluginWindow : Window, IPluginWindow
+{
+ public event Action? PluginLoaded;
+
+ public event Action? OnExit;
+
+ private readonly PluginSettings settings;
+ private bool isInitialized = false;
+
+ public bool IsRunning { get; private set; } = true;
+ public PluginMetadata PluginMetadata { get; private set; }
+ public string PluginFolderPath { get; private set; }
+
+ public WebPluginWindow(PluginMetadata pluginMetadata, PluginSettings settings, string pluginFolderPath)
+ {
+ InitializeComponent();
+
+ Window w = new()
+ {
+ Top = -100,
+ Left = -100,
+ Width = 0,
+ Height = 0,
+
+ WindowStyle = WindowStyle.ToolWindow,
+ ShowInTaskbar = false
+ };
+
+ WindowInteropHelper helper = new WindowInteropHelper(w);
+ _ = helper.EnsureHandle();
+
+ Owner = w;
+
+ settings.PropertyChanged += (e, s) =>
+ {
+ if (s.PropertyName == nameof(PluginSettings.CurrentThemeName))
+ {
+ settings.Theme.PropertyChanged += (se, ev) =>
+ {
+ ThemeChanged();
+ };
+ ThemeChanged();
+ }
+ };
+
+ PluginMetadata = pluginMetadata;
+ this.settings = settings;
+
+ Left = settings.Position.X;
+ Top = settings.Position.Y;
+ Width = settings.Size.X;
+ Height = settings.Size.Y;
+
+ PluginFolderPath = pluginFolderPath;
+ }
+
+ public void Exit()
+ {
+ IsRunning = false;
+
+ Dispatcher.Invoke(() =>
+ {
+ OnExit?.Invoke();
+ });
+ }
+
+ public void SetEditMode(bool enabled)
+ {
+ if (enabled)
+ {
+ Topmost = true;
+ panel.Visibility = Visibility.Visible;
+ _ = W32.EnableWindow(webView.Handle, false);
+ WindowPos.SetIsLocked(this, false);
+ tileBar.CaptionHeight = tileBar.CaptionHeight = ActualHeight - 10 < 0 ? 0 : ActualHeight - 10;
+ ResizeMode = ResizeMode.CanResize;
+ }
+ else
+ {
+ Topmost = false;
+ panel.Visibility = Visibility.Collapsed;
+ _ = W32.EnableWindow(webView.Handle, true);
+ WindowPos.SendWpfWindowBack(this);
+ WindowPos.SendWpfWindowBack(this);
+ WindowPos.SetIsLocked(this, true);
+ tileBar.CaptionHeight = 0;
+ ResizeMode = ResizeMode.NoResize;
+ }
+ }
+
+ protected override void OnSourceInitialized(EventArgs e)
+ {
+ base.OnSourceInitialized(e);
+
+ WindowInteropHelper helper = new(this);
+ _ = WindowPos.SetWindowLong(helper.Handle, WindowPos.GWL_EXSTYLE,
+ WindowPos.GetWindowLong(helper.Handle, WindowPos.GWL_EXSTYLE) | WindowPos.WS_EX_NOACTIVATE);
+ }
+
+ private async void Window_ContentRendered(object? sender, EventArgs e)
+ {
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Starting web plugin", source: "WebPlugin");
+
+ try
+ {
+ await InitializeWebView();
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "WebPlugin");
+ _ = MessageBox.Show("WebView2 initialization error:\n" + ex, $"Error \"{PluginMetadata.Name}\"", MessageBoxButton.OK, MessageBoxImage.Error);
+ Exit();
+ }
+ }
+
+ private void ThemeChanged()
+ {
+ border.Background = new SolidColorBrush(MultiColorConverter.ConvertToMediaColor(settings.Theme.BackgroundColor));
+ border.CornerRadius = new CornerRadius(settings.Theme.CornerRadius);
+ }
+
+ private async System.Threading.Tasks.Task InitializeWebView()
+ {
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Initializing WebView2", source: "WebPlugin");
+
+ string htmlPath = Path.Combine(PluginFolderPath, "main.html");
+
+ if (!File.Exists(htmlPath))
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - File \"main.html\" does not exist", source: "WebPlugin");
+ _ = MessageBox.Show("File \"main.html\" does not exist!", $"Error \"{PluginMetadata.Name}\"", MessageBoxButton.OK, MessageBoxImage.Error);
+ Exit();
+ return;
+ }
+
+ try
+ {
+ string userDataFolder = Path.Combine(Path.GetTempPath(), "DesktopMagic", "WebView2", PluginMetadata.Id.ToString());
+ CoreWebView2Environment environment = await CoreWebView2Environment.CreateAsync(null, userDataFolder);
+ await webView.EnsureCoreWebView2Async(environment);
+
+ webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
+ webView.CoreWebView2.Settings.AreDevToolsEnabled = false;
+ webView.CoreWebView2.Settings.IsStatusBarEnabled = false;
+ webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = true;
+
+ string htmlUri = new Uri(htmlPath).AbsoluteUri;
+ webView.Source = new Uri(htmlUri);
+
+ isInitialized = true;
+
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - WebView2 initialized successfully", source: "WebPlugin");
+ PluginLoaded?.Invoke();
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - WebView2 initialization failed: {ex}", source: "WebPlugin");
+ throw;
+ }
+ }
+
+ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
+ {
+ App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Stopping web plugin", source: "WebPlugin");
+ IsRunning = false;
+
+ try
+ {
+ if (isInitialized && webView.CoreWebView2 != null)
+ {
+ webView.Dispose();
+ }
+ }
+ catch (Exception ex)
+ {
+ App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "WebPlugin");
+ }
+ }
+
+ private void Window_LocationChanged(object sender, EventArgs e)
+ {
+ settings.Position = new System.Windows.Point(Left, Top);
+ }
+
+ private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ settings.Size = new System.Windows.Point(Width, Height);
+
+ tileBar.CaptionHeight = ActualHeight - 10;
+ }
+}