mirror of
https://github.com/Stone-Red-Code/DesktopMagic.git
synced 2026-09-04 08:56:15 +02:00
Fix memory leak caused by plugins not properly unloading
This commit is contained in:
@@ -10,6 +10,8 @@ using SkiaSharp;
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Imaging;
|
using System.Drawing.Imaging;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -51,6 +53,14 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
private WriteableBitmap? writeableBitmap;
|
private WriteableBitmap? writeableBitmap;
|
||||||
private BitmapScalingMode lastBitmapScalingMode = BitmapScalingMode.Unspecified;
|
private BitmapScalingMode lastBitmapScalingMode = BitmapScalingMode.Unspecified;
|
||||||
|
|
||||||
|
// Event handlers on long-lived settings objects, tracked so they can be unsubscribed on close.
|
||||||
|
private readonly PropertyChangedEventHandler settingsPropertyChangedHandler;
|
||||||
|
private readonly PropertyChangedEventHandler themePropertyChangedHandler;
|
||||||
|
private Theme? subscribedTheme;
|
||||||
|
private NotifyCollectionChangedEventHandler? themesCollectionChangedHandler;
|
||||||
|
private readonly List<Setting> subscribedSettings = [];
|
||||||
|
private readonly List<(Setting Setting, Action Handler)> defaultSettingsSubscriptions = [];
|
||||||
|
|
||||||
public bool IsRunning { get; private set; } = true;
|
public bool IsRunning { get; private set; } = true;
|
||||||
public PluginMetadata PluginMetadata { get; private set; }
|
public PluginMetadata PluginMetadata { get; private set; }
|
||||||
public string PluginFolderPath { get; private set; }
|
public string PluginFolderPath { get; private set; }
|
||||||
@@ -76,14 +86,11 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
|
|
||||||
Owner = w;
|
Owner = w;
|
||||||
|
|
||||||
settings.PropertyChanged += (e, s) =>
|
settingsPropertyChangedHandler = (_, s) =>
|
||||||
{
|
{
|
||||||
if (s.PropertyName == nameof(PluginSettings.CurrentThemeName))
|
if (s.PropertyName == nameof(PluginSettings.CurrentThemeName))
|
||||||
{
|
{
|
||||||
settings.Theme.PropertyChanged += (se, ev) =>
|
SubscribeToTheme(settings.Theme);
|
||||||
{
|
|
||||||
ThemeChanged();
|
|
||||||
};
|
|
||||||
ThemeChanged();
|
ThemeChanged();
|
||||||
}
|
}
|
||||||
else if (s.PropertyName == nameof(PluginSettings.Position))
|
else if (s.PropertyName == nameof(PluginSettings.Position))
|
||||||
@@ -95,11 +102,10 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
UpdateSize();
|
UpdateSize();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
settings.PropertyChanged += settingsPropertyChangedHandler;
|
||||||
|
|
||||||
settings.Theme.PropertyChanged += (se, ev) =>
|
themePropertyChangedHandler = (_, _) => ThemeChanged();
|
||||||
{
|
SubscribeToTheme(settings.Theme);
|
||||||
ThemeChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
PluginMetadata = pluginMetadata;
|
PluginMetadata = pluginMetadata;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
@@ -129,6 +135,22 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
this.pluginClassInstance = pluginClassInstance;
|
this.pluginClassInstance = pluginClassInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SubscribeToTheme(Theme theme)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(subscribedTheme, theme))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscribedTheme is not null)
|
||||||
|
{
|
||||||
|
subscribedTheme.PropertyChanged -= themePropertyChangedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribedTheme = theme;
|
||||||
|
subscribedTheme.PropertyChanged += themePropertyChangedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
private AssemblyLoadContext CreateAssemblyLoadContext()
|
private AssemblyLoadContext CreateAssemblyLoadContext()
|
||||||
{
|
{
|
||||||
AssemblyLoadContext context = new(PluginMetadata.Name, isCollectible: true);
|
AssemblyLoadContext context = new(PluginMetadata.Name, isCollectible: true);
|
||||||
@@ -580,14 +602,18 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
SetThemeOverride();
|
SetThemeOverride();
|
||||||
SetThemeOverrideItems();
|
SetThemeOverrideItems();
|
||||||
|
|
||||||
pluginClassInstance.horizontalAlignment.OnValueChanged += SetHorizontalAlignment;
|
SubscribeToDefaultSetting(pluginClassInstance.horizontalAlignment, SetHorizontalAlignment);
|
||||||
pluginClassInstance.verticalAlignment.OnValueChanged += SetVerticalAlignment;
|
SubscribeToDefaultSetting(pluginClassInstance.verticalAlignment, SetVerticalAlignment);
|
||||||
pluginClassInstance.windowLayer.OnValueChanged += SetWindowLayer;
|
SubscribeToDefaultSetting(pluginClassInstance.windowLayer, SetWindowLayer);
|
||||||
pluginClassInstance.rotation.OnValueChanged += SetRotation;
|
SubscribeToDefaultSetting(pluginClassInstance.rotation, SetRotation);
|
||||||
pluginClassInstance.themeOverride.OnValueChanged += SetThemeOverride;
|
SubscribeToDefaultSetting(pluginClassInstance.themeOverride, SetThemeOverride);
|
||||||
|
|
||||||
DesktopMagicSettings desktopMagicSettings = MainWindowDataContext.GetSettings();
|
if (themesCollectionChangedHandler is null)
|
||||||
desktopMagicSettings.Themes.CollectionChanged += (s, e) => SetThemeOverrideItems();
|
{
|
||||||
|
themesCollectionChangedHandler = (_, _) => SetThemeOverrideItems();
|
||||||
|
DesktopMagicSettings desktopMagicSettings = MainWindowDataContext.GetSettings();
|
||||||
|
desktopMagicSettings.Themes.CollectionChanged += themesCollectionChangedHandler;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
void SetVerticalAlignment()
|
void SetVerticalAlignment()
|
||||||
@@ -654,6 +680,22 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SubscribeToDefaultSetting(Setting setting, Action handler)
|
||||||
|
{
|
||||||
|
setting.OnValueChanged += handler;
|
||||||
|
defaultSettingsSubscriptions.Add((setting, handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPluginSettingValueChanged()
|
||||||
|
{
|
||||||
|
pluginClassInstance?.OnSettingsChanged();
|
||||||
|
|
||||||
|
if (pluginClassInstance?.UpdateInterval is 0 or > 500)
|
||||||
|
{
|
||||||
|
pluginClassInstance.Application.UpdateWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task LoadOptions(object instance)
|
private async Task LoadOptions(object instance)
|
||||||
{
|
{
|
||||||
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin options", source: "Plugin");
|
App.Logger.LogInfo($"\"{PluginMetadata.Name}\" - Loading plugin options", source: "Plugin");
|
||||||
@@ -679,15 +721,8 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
settingElement.JsonValue = settingsSettingElement.JsonValue;
|
settingElement.JsonValue = settingsSettingElement.JsonValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.OnValueChanged += () =>
|
element.OnValueChanged += OnPluginSettingValueChanged;
|
||||||
{
|
subscribedSettings.Add(element);
|
||||||
pluginClassInstance?.OnSettingsChanged();
|
|
||||||
|
|
||||||
if (pluginClassInstance?.UpdateInterval is 0 or > 500)
|
|
||||||
{
|
|
||||||
pluginClassInstance.Application.UpdateWindow();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
settingElements.Add(settingElement);
|
settingElements.Add(settingElement);
|
||||||
break;
|
break;
|
||||||
@@ -848,54 +883,73 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
|
|
||||||
private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs? e)
|
private async void UpdateTimer_Elapsed(object? sender, ElapsedEventArgs? e)
|
||||||
{
|
{
|
||||||
|
// Capture the fields into locals so an in-flight tick keeps the plugin assembly
|
||||||
|
// alive and stays safe when the window is being closed or the plugin reloaded.
|
||||||
|
Plugin? plugin = pluginClassInstance;
|
||||||
|
System.Timers.Timer? timer = updateTimer;
|
||||||
|
|
||||||
|
if (!IsRunning || plugin is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (IsRunning && pluginClassInstance is not null)
|
if (plugin is SkiaPlugin or SkiaAsyncPlugin)
|
||||||
{
|
{
|
||||||
if (pluginClassInstance is SkiaPlugin or SkiaAsyncPlugin)
|
await RenderSkiaFrame();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Bitmap? result;
|
||||||
|
|
||||||
|
if (plugin is AsyncPlugin asyncPlugin)
|
||||||
{
|
{
|
||||||
await RenderSkiaFrame();
|
CancellationToken token = pluginCancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
|
result = await asyncPlugin.MainAsync(token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Bitmap? result;
|
result = plugin.Main();
|
||||||
|
|
||||||
if (pluginClassInstance is AsyncPlugin asyncPlugin)
|
|
||||||
{
|
|
||||||
CancellationToken token = pluginCancellationTokenSource?.Token ?? CancellationToken.None;
|
|
||||||
result = await asyncPlugin.MainAsync(token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = pluginClassInstance.Main();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result is not null)
|
|
||||||
{
|
|
||||||
BitmapScalingMode renderOptions = pluginClassInstance.RenderQuality switch
|
|
||||||
{
|
|
||||||
RenderQuality.High => BitmapScalingMode.HighQuality,
|
|
||||||
RenderQuality.Low => BitmapScalingMode.LowQuality,
|
|
||||||
RenderQuality.Performance => BitmapScalingMode.NearestNeighbor,
|
|
||||||
_ => BitmapScalingMode.Unspecified
|
|
||||||
};
|
|
||||||
|
|
||||||
UpdateImageFromBitmap(result, renderOptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pluginClassInstance.UpdateInterval > 0)
|
if (result is not null)
|
||||||
{
|
{
|
||||||
updateTimer!.Interval = pluginClassInstance.UpdateInterval;
|
BitmapScalingMode renderOptions = plugin.RenderQuality switch
|
||||||
}
|
{
|
||||||
else
|
RenderQuality.High => BitmapScalingMode.HighQuality,
|
||||||
{
|
RenderQuality.Low => BitmapScalingMode.LowQuality,
|
||||||
updateTimer!.Stop();
|
RenderQuality.Performance => BitmapScalingMode.NearestNeighbor,
|
||||||
|
_ => BitmapScalingMode.Unspecified
|
||||||
|
};
|
||||||
|
|
||||||
|
UpdateImageFromBitmap(result, renderOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timer is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugin.UpdateInterval > 0)
|
||||||
|
{
|
||||||
|
timer.Interval = plugin.UpdateInterval;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timer.Stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
// If the window is closing, the plugin state is being torn down and the
|
||||||
|
// exception is a teardown race - ignore it instead of showing an error.
|
||||||
|
if (!IsRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "Plugin");
|
App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "Plugin");
|
||||||
_ = await Dispatcher.InvokeAsync(async () =>
|
_ = await Dispatcher.InvokeAsync(async () =>
|
||||||
@@ -909,17 +963,18 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
_ = await messageBox.ShowDialogAsync();
|
_ = await messageBox.ShowDialogAsync();
|
||||||
});
|
});
|
||||||
Exit();
|
Exit();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsRunning)
|
|
||||||
{
|
|
||||||
updateTimer!.Stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RenderSkiaFrame()
|
private async Task RenderSkiaFrame()
|
||||||
{
|
{
|
||||||
|
Plugin? plugin = pluginClassInstance;
|
||||||
|
|
||||||
|
if (plugin is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
|
|
||||||
@@ -932,12 +987,12 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Premul));
|
using SKSurface surface = SKSurface.Create(new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Premul));
|
||||||
surface.Canvas.Clear(SKColors.Transparent);
|
surface.Canvas.Clear(SKColors.Transparent);
|
||||||
|
|
||||||
if (pluginClassInstance is SkiaAsyncPlugin skiaAsyncPlugin)
|
if (plugin is SkiaAsyncPlugin skiaAsyncPlugin)
|
||||||
{
|
{
|
||||||
CancellationToken token = pluginCancellationTokenSource?.Token ?? CancellationToken.None;
|
CancellationToken token = pluginCancellationTokenSource?.Token ?? CancellationToken.None;
|
||||||
await skiaAsyncPlugin.MainAsync(surface.Canvas, token);
|
await skiaAsyncPlugin.MainAsync(surface.Canvas, token);
|
||||||
}
|
}
|
||||||
else if (pluginClassInstance is SkiaPlugin skiaPlugin)
|
else if (plugin is SkiaPlugin skiaPlugin)
|
||||||
{
|
{
|
||||||
skiaPlugin.Main(surface.Canvas);
|
skiaPlugin.Main(surface.Canvas);
|
||||||
}
|
}
|
||||||
@@ -1006,8 +1061,54 @@ public partial class PluginWindow : Window, IPluginWindow
|
|||||||
reloadDebounceTimer?.Dispose();
|
reloadDebounceTimer?.Dispose();
|
||||||
reloadDebounceTimer = null;
|
reloadDebounceTimer = null;
|
||||||
|
|
||||||
|
updateTimer?.Stop();
|
||||||
|
updateTimer?.Dispose();
|
||||||
|
updateTimer = null;
|
||||||
|
|
||||||
// Stop plugin using the shared method (no need to await in synchronous event handler)
|
// Stop plugin using the shared method (no need to await in synchronous event handler)
|
||||||
_ = StopPlugin(unloadAssembly: true);
|
_ = StopPlugin(unloadAssembly: true);
|
||||||
|
|
||||||
|
UnsubscribeEvents();
|
||||||
|
DetachSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DetachSettings()
|
||||||
|
{
|
||||||
|
foreach (SettingElement settingElement in settings.Settings)
|
||||||
|
{
|
||||||
|
string value = settingElement.JsonValue;
|
||||||
|
settingElement.Input = null;
|
||||||
|
settingElement.JsonValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeEvents()
|
||||||
|
{
|
||||||
|
settings.PropertyChanged -= settingsPropertyChangedHandler;
|
||||||
|
|
||||||
|
if (subscribedTheme is not null)
|
||||||
|
{
|
||||||
|
subscribedTheme.PropertyChanged -= themePropertyChangedHandler;
|
||||||
|
subscribedTheme = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (themesCollectionChangedHandler is not null)
|
||||||
|
{
|
||||||
|
MainWindowDataContext.GetSettings().Themes.CollectionChanged -= themesCollectionChangedHandler;
|
||||||
|
themesCollectionChangedHandler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Setting setting in subscribedSettings)
|
||||||
|
{
|
||||||
|
setting.OnValueChanged -= OnPluginSettingValueChanged;
|
||||||
|
}
|
||||||
|
subscribedSettings.Clear();
|
||||||
|
|
||||||
|
foreach ((Setting Setting, Action Handler) subscription in defaultSettingsSubscriptions)
|
||||||
|
{
|
||||||
|
subscription.Setting.OnValueChanged -= subscription.Handler;
|
||||||
|
}
|
||||||
|
defaultSettingsSubscriptions.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePosition()
|
private void UpdatePosition()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class SettingElement
|
|||||||
private string jsonValue = string.Empty;
|
private string jsonValue = string.Empty;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Setting Input { get; set; }
|
public Setting? Input { get; set; }
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public int OrderIndex { get; set; }
|
public int OrderIndex { get; set; }
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using Microsoft.Web.WebView2.Core;
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -33,6 +34,13 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
private readonly string screenDeviceName;
|
private readonly string screenDeviceName;
|
||||||
private bool isUpdatingPosition = false;
|
private bool isUpdatingPosition = false;
|
||||||
|
|
||||||
|
// Event handlers on long-lived settings objects, tracked so they can be unsubscribed on close.
|
||||||
|
private readonly PropertyChangedEventHandler settingsPropertyChangedHandler;
|
||||||
|
private readonly PropertyChangedEventHandler themePropertyChangedHandler;
|
||||||
|
private Theme? subscribedTheme;
|
||||||
|
private readonly List<(Setting Setting, Action Handler)> subscribedSettings = [];
|
||||||
|
private readonly List<(Button Button, Action Handler)> subscribedButtonClicks = [];
|
||||||
|
|
||||||
public bool IsRunning { get; private set; } = true;
|
public bool IsRunning { get; private set; } = true;
|
||||||
public PluginMetadata PluginMetadata { get; private set; }
|
public PluginMetadata PluginMetadata { get; private set; }
|
||||||
public string PluginFolderPath { get; private set; }
|
public string PluginFolderPath { get; private set; }
|
||||||
@@ -58,14 +66,11 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
|
|
||||||
Owner = w;
|
Owner = w;
|
||||||
|
|
||||||
settings.PropertyChanged += (e, s) =>
|
settingsPropertyChangedHandler = (_, s) =>
|
||||||
{
|
{
|
||||||
if (s.PropertyName == nameof(PluginSettings.CurrentThemeName))
|
if (s.PropertyName == nameof(PluginSettings.CurrentThemeName))
|
||||||
{
|
{
|
||||||
settings.Theme.PropertyChanged += (se, ev) =>
|
SubscribeToTheme(settings.Theme);
|
||||||
{
|
|
||||||
ThemeChanged();
|
|
||||||
};
|
|
||||||
ThemeChanged();
|
ThemeChanged();
|
||||||
}
|
}
|
||||||
else if (s.PropertyName == nameof(PluginSettings.Position))
|
else if (s.PropertyName == nameof(PluginSettings.Position))
|
||||||
@@ -77,11 +82,10 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
UpdateSize();
|
UpdateSize();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
settings.PropertyChanged += settingsPropertyChangedHandler;
|
||||||
|
|
||||||
settings.Theme.PropertyChanged += (se, ev) =>
|
themePropertyChangedHandler = (_, _) => ThemeChanged();
|
||||||
{
|
SubscribeToTheme(settings.Theme);
|
||||||
ThemeChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
PluginMetadata = pluginMetadata;
|
PluginMetadata = pluginMetadata;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
@@ -103,6 +107,22 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SubscribeToTheme(Theme theme)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(subscribedTheme, theme))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscribedTheme is not null)
|
||||||
|
{
|
||||||
|
subscribedTheme.PropertyChanged -= themePropertyChangedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribedTheme = theme;
|
||||||
|
subscribedTheme.PropertyChanged += themePropertyChangedHandler;
|
||||||
|
}
|
||||||
|
|
||||||
public void Exit()
|
public void Exit()
|
||||||
{
|
{
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
@@ -266,6 +286,9 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
|
|
||||||
reloadDebounceTimer?.Stop();
|
reloadDebounceTimer?.Stop();
|
||||||
reloadDebounceTimer?.Dispose();
|
reloadDebounceTimer?.Dispose();
|
||||||
|
reloadDebounceTimer = null;
|
||||||
|
|
||||||
|
UnsubscribeEvents();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -280,6 +303,29 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeEvents()
|
||||||
|
{
|
||||||
|
settings.PropertyChanged -= settingsPropertyChangedHandler;
|
||||||
|
|
||||||
|
if (subscribedTheme is not null)
|
||||||
|
{
|
||||||
|
subscribedTheme.PropertyChanged -= themePropertyChangedHandler;
|
||||||
|
subscribedTheme = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ((Setting setting, Action handler) in subscribedSettings)
|
||||||
|
{
|
||||||
|
setting.OnValueChanged -= handler;
|
||||||
|
}
|
||||||
|
subscribedSettings.Clear();
|
||||||
|
|
||||||
|
foreach ((Button button, Action handler) in subscribedButtonClicks)
|
||||||
|
{
|
||||||
|
button.OnClick -= handler;
|
||||||
|
}
|
||||||
|
subscribedButtonClicks.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdatePosition()
|
private void UpdatePosition()
|
||||||
{
|
{
|
||||||
if (isUpdatingPosition)
|
if (isUpdatingPosition)
|
||||||
@@ -415,7 +461,7 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
string capturedId = id;
|
string capturedId = id;
|
||||||
if (setting is Button button)
|
if (setting is Button button)
|
||||||
{
|
{
|
||||||
button.OnClick += () =>
|
Action clickHandler = () =>
|
||||||
{
|
{
|
||||||
_ = webView.Dispatcher.InvokeAsync(async () =>
|
_ = webView.Dispatcher.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
@@ -429,9 +475,11 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
button.OnClick += clickHandler;
|
||||||
|
subscribedButtonClicks.Add((button, clickHandler));
|
||||||
}
|
}
|
||||||
|
|
||||||
setting.OnValueChanged += () =>
|
Action valueChangedHandler = () =>
|
||||||
{
|
{
|
||||||
_ = webView.Dispatcher.InvokeAsync(async () =>
|
_ = webView.Dispatcher.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
@@ -445,6 +493,8 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
setting.OnValueChanged += valueChangedHandler;
|
||||||
|
subscribedSettings.Add((setting, valueChangedHandler));
|
||||||
|
|
||||||
settingElements.Add(settingElement);
|
settingElements.Add(settingElement);
|
||||||
orderIndex++;
|
orderIndex++;
|
||||||
@@ -614,7 +664,7 @@ public partial class WebPluginWindow : Window, IPluginWindow
|
|||||||
Dictionary<string, object?> dict = [];
|
Dictionary<string, object?> dict = [];
|
||||||
foreach (SettingElement element in settings.Settings)
|
foreach (SettingElement element in settings.Settings)
|
||||||
{
|
{
|
||||||
dict[element.Id] = GetSettingValue(element.Input);
|
dict[element.Id] = GetSettingValue(element.Input!);
|
||||||
}
|
}
|
||||||
return JsonSerializer.Serialize(dict);
|
return JsonSerializer.Serialize(dict);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user