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; + } +}