From 417701a5f3af40f69efa1a7d62e85c4801dac8a9 Mon Sep 17 00:00:00 2001 From: radj307 Date: Fri, 3 Nov 2023 21:49:24 -0400 Subject: [PATCH] update & clean up --- .../Input/Actions/HotkeyActionDefinition.cs | 10 +- .../Input/Actions/HotkeyActionInstance.cs | 4 +- .../AudioDeviceSelector.cs | 2 +- .../AudioSessionSelector.cs | 2 +- VolumeControl.HotkeyActions/SystemActions.cs | 1 - .../VolumeControl.HotkeyActions.csproj | 1 - .../Helpers/ExceptionMessageHelper.cs | 1 - .../Delegates/HotkeyActionDelegate.cs | 26 +-- .../EnumExtensions.cs | 2 +- .../StringExtensions.cs | 11 +- VolumeControl/ActionSettingsWindow.xaml.cs | 9 +- VolumeControl/App.xaml.cs | 96 +++------ .../Controls/VolumeControlNotifyIcon.cs | 5 +- VolumeControl/Helpers/AddonLoader.cs | 198 ++++++++++++------ VolumeControl/Helpers/PathFinder.cs | 5 +- VolumeControl/Helpers/ShellHelper.cs | 136 ++++++++++++ VolumeControl/Helpers/VCSettings.cs | 1 - VolumeControl/Mixer.xaml.cs | 7 +- VolumeControl/Program.cs | 4 +- VolumeControl/ViewModels/HotkeyManagerVM.cs | 1 - VolumeControl/ViewModels/LogFilterFlagsVM.cs | 2 +- VolumeControl/ViewModels/VolumeControlVM.cs | 35 ---- 22 files changed, 344 insertions(+), 215 deletions(-) create mode 100644 VolumeControl/Helpers/ShellHelper.cs diff --git a/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs b/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs index f7add49a6..3f4331a89 100644 --- a/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs +++ b/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs @@ -77,6 +77,10 @@ public HotkeyActionDefinition(Type objectType, MethodInfo methodInfo, string nam /// /// Gets the method info for the action method that this action represents. /// + /// + /// Do not use this to invoke the method!
+ /// Use instead! + ///
public MethodInfo ActionMethodInfo { get; } #endregion Reflection @@ -166,14 +170,14 @@ public HotkeyActionInstance CreateInstance(IActionSettingInstance[] actionSettin /// /// Parameters to invoke the method with. These must match the parameters accepted by the actual method. /// The return value from . - public object? Invoke_Unsafe(params object?[] parameters) + private object? Invoke_Unsafe(object?[] parameters) => ActionMethodInfo.Invoke(ActionGroupInstance, parameters); /// /// Directly invokes the method specified by with the parameters expected by . /// /// - public object? Invoke_Unsafe(object? sender, IActionSettingInstance[] actionSettings) - => Invoke_Unsafe(sender, new HotkeyPressedEventArgs(actionSettings)); + internal object? Invoke_Unsafe(object sender, HotkeyPressedEventArgs e) + => Invoke_Unsafe(new[] { sender, e }); #endregion Invoke_Unsafe #region GetActionSettingDefinition diff --git a/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs b/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs index fba2e401f..4ee7b338e 100644 --- a/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs +++ b/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs @@ -46,7 +46,7 @@ internal HotkeyActionInstance(HotkeyActionDefinition definition, IActionSettingI /// /// The object instance to use as the sender of the event. /// The event arguments to use for the event. - public void Invoke(object? sender, HotkeyPressedEventArgs e) + public void Invoke(object sender, HotkeyPressedEventArgs e) { try { @@ -64,7 +64,7 @@ public void Invoke(object? sender, HotkeyPressedEventArgs e) /// Invokes the method specified by the HotkeyActionDefinition with a new instance. /// /// The object instance to use as the sender of the event. - public void Invoke(object? sender) => Invoke(sender, new HotkeyPressedEventArgs(ActionSettings)); + public void Invoke(object sender) => Invoke(sender, new HotkeyPressedEventArgs(ActionSettings)); #endregion Methods } } diff --git a/VolumeControl.CoreAudio/AudioDeviceSelector.cs b/VolumeControl.CoreAudio/AudioDeviceSelector.cs index 2b854d3e0..446382fd1 100644 --- a/VolumeControl.CoreAudio/AudioDeviceSelector.cs +++ b/VolumeControl.CoreAudio/AudioDeviceSelector.cs @@ -34,7 +34,7 @@ public AudioDeviceSelector(AudioDeviceManager audioDeviceManager) #endregion Fields #region Properties - private static Config Settings => (Config.Default as Config)!; + private static Config Settings => Config.Default!; AudioDeviceManager AudioDeviceManager { get; } /// /// Gets or the sets the selected item. diff --git a/VolumeControl.CoreAudio/AudioSessionSelector.cs b/VolumeControl.CoreAudio/AudioSessionSelector.cs index 562cd61fd..f65006271 100644 --- a/VolumeControl.CoreAudio/AudioSessionSelector.cs +++ b/VolumeControl.CoreAudio/AudioSessionSelector.cs @@ -34,7 +34,7 @@ public AudioSessionSelector(AudioSessionManager audioSessionManager) #endregion Fields #region Properties - private static Config Settings => (Config.Default as Config)!; + private static Config Settings => Config.Default!; AudioSessionManager AudioSessionManager { get; } /// /// Gets or the sets the selected item. diff --git a/VolumeControl.HotkeyActions/SystemActions.cs b/VolumeControl.HotkeyActions/SystemActions.cs index b91681f5e..551aea1a8 100644 --- a/VolumeControl.HotkeyActions/SystemActions.cs +++ b/VolumeControl.HotkeyActions/SystemActions.cs @@ -2,7 +2,6 @@ using System.Text; using System.Windows; using VolumeControl.Core.Attributes; -using VolumeControl.Core.Helpers; using VolumeControl.Core.Input; using VolumeControl.Log; diff --git a/VolumeControl.HotkeyActions/VolumeControl.HotkeyActions.csproj b/VolumeControl.HotkeyActions/VolumeControl.HotkeyActions.csproj index 9526b7e8e..464928d36 100644 --- a/VolumeControl.HotkeyActions/VolumeControl.HotkeyActions.csproj +++ b/VolumeControl.HotkeyActions/VolumeControl.HotkeyActions.csproj @@ -25,6 +25,5 @@ - \ No newline at end of file diff --git a/VolumeControl.Log/Helpers/ExceptionMessageHelper.cs b/VolumeControl.Log/Helpers/ExceptionMessageHelper.cs index 1e7bc91d6..fd7c5634e 100644 --- a/VolumeControl.Log/Helpers/ExceptionMessageHelper.cs +++ b/VolumeControl.Log/Helpers/ExceptionMessageHelper.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Reflection; -using System.Runtime.InteropServices; using System.Text; namespace VolumeControl.Log.Helpers diff --git a/VolumeControl.SDK/Delegates/HotkeyActionDelegate.cs b/VolumeControl.SDK/Delegates/HotkeyActionDelegate.cs index ebb947494..685a16387 100644 --- a/VolumeControl.SDK/Delegates/HotkeyActionDelegate.cs +++ b/VolumeControl.SDK/Delegates/HotkeyActionDelegate.cs @@ -1,29 +1,11 @@ -using System.ComponentModel; +using VolumeControl.Core.Input; namespace VolumeControl.SDK.Delegates { /// /// Represents a method that can be used by Volume Control to generate a hotkey action. /// - /// - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3, T4 opt4); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3, T4 opt4, T5 opt5); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3, T4 opt4, T5 opt5, T6 opt6); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3, T4 opt4, T5 opt5, T6 opt6, T7 opt7); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3, T4 opt4, T5 opt5, T6 opt6, T7 opt7, T8 opt8); - /// - public delegate void HotkeyActionDelegate(object? sender, HandledEventArgs e, T1 opt1, T2 opt2, T3 opt3, T4 opt4, T5 opt5, T6 opt6, T7 opt7, T8 opt8, T9 opt9); + /// The hotkey that was pressed. + /// The event arguments for this action, including the current values of any action settings. + public delegate void HotkeyActionDelegate(object sender, HotkeyPressedEventArgs e); } diff --git a/VolumeControl.TypeExtensions/EnumExtensions.cs b/VolumeControl.TypeExtensions/EnumExtensions.cs index 517a07a71..c9774570c 100644 --- a/VolumeControl.TypeExtensions/EnumExtensions.cs +++ b/VolumeControl.TypeExtensions/EnumExtensions.cs @@ -135,7 +135,7 @@ public static T GetSingleValue(this IEnumerable enumerable) where T : stru public static bool IsSingleValue(this T e) where T : struct, Enum { var e_v = Convert.ToInt64(e); - + return e_v == 0 || (e_v & (e_v - 1)) == 0; } } diff --git a/VolumeControl.TypeExtensions/StringExtensions.cs b/VolumeControl.TypeExtensions/StringExtensions.cs index 75cd93af6..0610374e8 100644 --- a/VolumeControl.TypeExtensions/StringExtensions.cs +++ b/VolumeControl.TypeExtensions/StringExtensions.cs @@ -10,7 +10,16 @@ public static class StringExtensions /// /// Parses a string containing a version number in semantic versioning 2 format. /// - public static SemVersion? GetSemVer(this string? s) => s is null ? null : SemVersion.TryParse(s.Trim(), SemVersionStyles.OptionalPatch, out SemVersion result) ? result : null; + public static SemVersion? GetSemVer(this string? s) + { + if (string.IsNullOrWhiteSpace(s)) return null; + + if (SemVersion.TryParse(s.Trim(), SemVersionStyles.OptionalPatch, out SemVersion result)) + { + return result; + } + return null; + } /// /// Removes all chars that returns true for. /// diff --git a/VolumeControl/ActionSettingsWindow.xaml.cs b/VolumeControl/ActionSettingsWindow.xaml.cs index 6d3362257..99822d29a 100644 --- a/VolumeControl/ActionSettingsWindow.xaml.cs +++ b/VolumeControl/ActionSettingsWindow.xaml.cs @@ -30,7 +30,14 @@ public ActionSettingsWindow(Window owner, HotkeyVM hotkey) // init window properties Owner = owner; - Title = hotkey.Hotkey.Name; + var hotkeyName = hotkey.Hotkey.Name; + var hasName = !string.IsNullOrWhiteSpace(hotkeyName); + var actionIdentifier = hotkey.ActionDefinition!.Identifier; + if (hasName) + { + Title = hotkeyName + $" ({actionIdentifier})"; + } + else Title = actionIdentifier; } #endregion Initializers diff --git a/VolumeControl/App.xaml.cs b/VolumeControl/App.xaml.cs index f318ade8e..068bde455 100644 --- a/VolumeControl/App.xaml.cs +++ b/VolumeControl/App.xaml.cs @@ -1,8 +1,6 @@ using System; -using System.Diagnostics; using System.IO; using System.Reflection; -using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -25,38 +23,43 @@ public App() string version = $"v{assembly?.GetCustomAttribute()?.InformationalVersion}"; // Add a log handler to the dispatcher's unhandled exception event - DispatcherUnhandledException += (s, e) => - { - FLog.Error($"An unhandled exception occurred!", $"Sender: '{s}' ({s.GetType()})", e.Exception); - e.Handled = true; - }; + DispatcherUnhandledException += this.App_DispatcherUnhandledException; // setup the tray icon TrayIcon = new(() => this.MainWindow.Visibility == Visibility.Visible) { Tooltip = $"Volume Control {version}" }; - TrayIcon.DoubleClick += this.HandleTrayIconClick; - TrayIcon.ShowClicked += this.HandleTrayIconClick; + TrayIcon.DoubleClick += (s, e) => + { + if (this.MainWindow.IsVisible) + this.HideMainWindow(); + else + { + this.ShowMainWindow(); + MainWindow.Activate(); + } + }; + TrayIcon.ShowClicked += (s, e) => this.ShowMainWindow(); TrayIcon.HideClicked += (s, e) => this.HideMainWindow(); TrayIcon.BringToFrontClicked += (s, e) => this.ActivateMainWindow(); TrayIcon.OpenConfigClicked += (s, e) => { - Process.Start(new ProcessStartInfo(Config.Default.Location) { UseShellExecute = true }); + ShellHelper.OpenWithDefault(Config.Default.Location); }; TrayIcon.OpenLogClicked += (s, e) => { - Process.Start(new ProcessStartInfo(Config.Default.LogPath) { UseShellExecute = true }); + ShellHelper.OpenWithDefault(Config.Default.LogPath); }; TrayIcon.OpenLocationClicked += (s, e) => { - OpenFolderAndSelectItem(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), Path.ChangeExtension(AppDomain.CurrentDomain.FriendlyName, ".exe")))); + ShellHelper.OpenFolderAndSelectItem(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), Path.ChangeExtension(AppDomain.CurrentDomain.FriendlyName, ".exe")))); }; TrayIcon.OpenAppDataClicked += (s, e) => { - Process.Start("explorer", PathFinder.ApplicationAppDataPath); + ShellHelper.OpenWith("explorer", PathFinder.ApplicationAppDataPath); }; TrayIcon.CloseClicked += (s, e) => this.Shutdown(); TrayIcon.Visible = true; @@ -79,61 +82,6 @@ private void ActivateMainWindow() this.MainWindow.Show(); _ = this.MainWindow.Activate(); } - private void HandleTrayIconClick(object? sender, EventArgs e) - { - if (this.MainWindow.IsVisible) - this.HideMainWindow(); - else - { - this.ShowMainWindow(); - MainWindow.Activate(); - } - } - #region OpenFolderAndSelectItem - [DllImport("shell32.dll", SetLastError = true)] - private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, uint dwFlags); - [DllImport("shell32.dll", SetLastError = true)] - private static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr bindingContext, [Out] out IntPtr pidl, uint sfgaoIn, [Out] out uint psfgaoOut); - private static void OpenFolderAndSelectItem(string filePath) - { - if (Path.GetDirectoryName(filePath) is not string directoryPath) - { - FLog.Error($"Cannot get directory from path '{filePath}'!"); - return; - } - - SHParseDisplayName(directoryPath, IntPtr.Zero, out IntPtr nativeFolder, 0, out uint psfgaoOut); - - if (nativeFolder == IntPtr.Zero) - { - FLog.Error($"Cannot locate directory '{directoryPath}'!"); - return; - } - - SHParseDisplayName(filePath, IntPtr.Zero, out IntPtr nativeFile, 0, out psfgaoOut); - - IntPtr[] fileArray; - if (nativeFile == IntPtr.Zero) - { - // Open the folder without the file selected if we can't find the file - fileArray = Array.Empty(); - } - else - { - fileArray = new IntPtr[] { nativeFile }; - } - - FLog.Debug($"Opening and selecting '{filePath}' in the file explorer."); - - _ = SHOpenFolderAndSelectItems(nativeFolder, (uint)fileArray.Length, fileArray, 0); - - Marshal.FreeCoTaskMem(nativeFolder); - if (nativeFile != IntPtr.Zero) - { - Marshal.FreeCoTaskMem(nativeFile); - } - } - #endregion OpenFolderAndSelectItem #endregion Methods #region EventHandlers @@ -147,6 +95,18 @@ private void Application_Exit(object sender, ExitEventArgs e) } #endregion Application + #region App + private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) + { + FLog.Critical( + $"An unhandled exception occurred in the application dispatcher!", + $"Sender: \"{sender}\" ({sender.GetType()})", + $"Thread: \"{e.Dispatcher.Thread.Name}\"", + e.Exception); + e.Handled = true; + } + #endregion App + #region PART_TextBox /// /// Prevents non-numeric or control keys from being received as input by the textbox.
diff --git a/VolumeControl/Controls/VolumeControlNotifyIcon.cs b/VolumeControl/Controls/VolumeControlNotifyIcon.cs index 92b9fed4f..d68debfdb 100644 --- a/VolumeControl/Controls/VolumeControlNotifyIcon.cs +++ b/VolumeControl/Controls/VolumeControlNotifyIcon.cs @@ -1,6 +1,5 @@ using CodingSeb.Localization; using System; -using System.Collections.Generic; namespace VolumeControl.Controls { @@ -12,7 +11,7 @@ public VolumeControlNotifyIcon(Query queryMainWindowVisible) _notifyIcon.Icon = Properties.Resources.iconSilvered; - this.Items.AddRange(new List() + this.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { // TOOLSTRIP: new System.Windows.Forms.ToolStripButton(GetShowText(), Properties.Resources.foreground, this.HandleShowHideClick), new System.Windows.Forms.ToolStripButton(GetBringToFrontText(), Properties.Resources.bringtofront, this.HandleBringToFrontClick), @@ -25,7 +24,7 @@ public VolumeControlNotifyIcon(Query queryMainWindowVisible) new System.Windows.Forms.ToolStripButton(GetOpenAppDataText(), Properties.Resources.file_inverted, this.HandleOpenAppDataClick), new System.Windows.Forms.ToolStripButton(GetOpenLocationText(), Properties.Resources.file, this.HandleOpenLocationClick), new System.Windows.Forms.ToolStripButton(GetCloseText(), Properties.Resources.X, this.HandleCloseClicked), - }.ToArray()); + }); Loc.Instance.CurrentLanguageChanged += this.Handle_CurrentLanguageChanged; _contextMenuStrip.VisibleChanged += this.Handle_ContextMenuVisibilityChanged; diff --git a/VolumeControl/Helpers/AddonLoader.cs b/VolumeControl/Helpers/AddonLoader.cs index 492dc33a3..5e6906c1e 100644 --- a/VolumeControl/Helpers/AddonLoader.cs +++ b/VolumeControl/Helpers/AddonLoader.cs @@ -1,15 +1,11 @@ -using CodingSeb.Localization.Loaders; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using VolumeControl.Core; using VolumeControl.Core.Input; using VolumeControl.Log; -using VolumeControl.TypeExtensions; using VolumeControl.ViewModels; namespace VolumeControl.Helpers @@ -21,7 +17,7 @@ public class AddonLoader #endregion Fields #region Properties - private static LocalizationLoader LocalizationLoader => LocalizationLoader.Instance; + private static Config Settings => Config.Default; #endregion Properties #region Methods @@ -29,35 +25,135 @@ public class AddonLoader #region LoadTranslations private static void LoadTranslations(Assembly assembly) { - foreach (var resourceName in assembly.GetManifestResourceNames()) + // get the names of all embedded resources in the assembly + var manifestResourceNames = assembly.GetManifestResourceNames(); + bool writeDebugLogMessages = FLog.FilterEventType(EventType.DEBUG); + if (writeDebugLogMessages) { - // filter out invalid names - var match = TranslationConfigAddonRegex.Match(resourceName); - if (!match.Success) + if (manifestResourceNames.Length == 0) { - FLog.Debug($"[AddonLoader] Embedded resource \"{resourceName}\" does not have a valid name for a translation config, so it was not loaded."); - continue; + FLog.Debug($"[{nameof(AddonLoader)}] Assembly \"{assembly.FullName}\" does not contain any embedded resources."); + return; } else { - FLog.Debug($"[AddonLoader] Loading translations from embedded resource \"{resourceName}\"."); + FLog.Debug($"[{nameof(AddonLoader)}] Assembly \"{assembly.FullName}\" contains {manifestResourceNames.Length} embedded resources. Preparing to search them for translation configs."); + } + } - // load translations + int totalCount = 0; + int loadedCount = 0; + + // enumerate embedded resources + foreach (var resourceName in manifestResourceNames) + { + // filter out invalid names + if (!TranslationConfigAddonRegex.IsMatch(resourceName)) continue; + else ++totalCount; // this is a translation config. + + if (writeDebugLogMessages) + FLog.Debug($"[{nameof(AddonLoader)}] Loading translation config from embedded resource \"{resourceName}\"."); + try + { + // load translation config resource LocalizationHelper.LoadFromManifestResource(assembly, resourceName); + ++loadedCount; + } + catch (Exception ex) + { + FLog.Error($"[{nameof(AddonLoader)}] An exception occurred while loading embedded resource \"{resourceName}\":", ex); } } + + + // log the outcome + if (!writeDebugLogMessages) return; + else if (manifestResourceNames.Length == 0 && totalCount == 0) + FLog.Debug($"[{nameof(AddonLoader)}] Assembly \"{assembly.FullName}\" does not contain any translation configs."); + else + FLog.Debug($"[{nameof(AddonLoader)}] Loaded {loadedCount}/{totalCount} translation config{(totalCount == 1 ? "" : "s")} from assembly \"{assembly.FullName}\""); } #endregion LoadTranslations + #region (Private) LoadAddonsFromDirectory + private static void LoadAddonsFromDirectory(string directoryPath, bool recursive, VolumeControlVM inst, TemplateProviderManager providerManager) + { + FLog.Trace($"[{nameof(AddonLoader)}] Searching for DLLs in directory \"{directoryPath}\"."); + foreach (string dllPath in Directory.EnumerateFiles(directoryPath, "*.dll", new EnumerationOptions() { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = recursive, })) + { + // get file version info + var dllName = Path.GetFileName(dllPath); + var versionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(dllPath); + + // report that we found an addon DLL + var versionInfoNumber = versionInfo.ProductVersion ?? versionInfo.FileVersion; + if (versionInfoNumber != null) + { + FLog.Info($"[{nameof(AddonLoader)}] Found addon \"{versionInfo.ProductName ?? dllName}\" version {versionInfoNumber}"); + } + else + { + FLog.Warning($"[{nameof(AddonLoader)}] Found addon \"{dllName}\", but it doesn't have any version information!"); + } + + // ensure the DLL was not compiled in DEBUG configuration + if (versionInfo.IsDebug) + { +#if DEBUG // we're in DEBUG configuration; warn & continue + FLog.Warning($"[{nameof(AddonLoader)}] Addon DLL \"{dllName}\" was built in DEBUG configuration! (Volume Control will not be able to load this in RELEASE configuration!)"); +#else // we're in RELEASE configuration; error & skip + FLog.Error($"[{nameof(AddonLoader)}] Addon DLL \"{dllName}\" was built in DEBUG configuration and cannot be loaded! Report this issue to the addon author."); + continue; +#endif + } + + // try loading the assembly + try + { + var asm = Assembly.LoadFrom(dllPath); + var assemblyName = asm.FullName ?? dllPath; + var exportedTypes = asm.GetExportedTypes(); + + FLog.Debug($"[{nameof(AddonLoader)}] Found {exportedTypes.Length} public type{(exportedTypes.Length == 1 ? "" : "s")} in assembly \"{assemblyName}\"."); + + // load translations from addon assembly + LoadTranslations(asm); + + // load providers from addon assembly + HotkeyActionAddonLoader.LoadProviders(ref providerManager, exportedTypes); + + // load actions from addon assembly + inst.HotkeyAPI.HotkeyManager.HotkeyActionManager.AddActionDefinitions(HotkeyActionAddonLoader.LoadActions(providerManager, exportedTypes)); + } + catch (Exception ex) + { + FLog.Critical($"[{nameof(AddonLoader)}] Failed to load addon DLL \"{dllPath}\" due to an exception!", ex); + } + } + } + #endregion (Private) LoadAddonsFromDirectory + #region LoadAddons + /// + /// Loads addons from the default and custom addon directories. + /// + /// + /// Addon components are loaded in the following order: + /// + /// Translation Configs + /// DataTemplate Providers + /// Actions + /// + /// The built-in translations/providers/actions are loaded first, then addons in the default location, then addons in custom directories. + /// + /// The main window's view model instance. public void LoadAddons(VolumeControlVM inst) { // create a template provider manager var templateProviderManager = new TemplateProviderManager(); - var hotkeyActionsAssembly = Assembly.Load($"{nameof(VolumeControl)}.{nameof(HotkeyActions)}"); - // load default addin translations + var hotkeyActionsAssembly = Assembly.Load($"{nameof(VolumeControl)}.{nameof(HotkeyActions)}"); LoadTranslations(hotkeyActionsAssembly); // load default template providers @@ -67,59 +163,35 @@ public void LoadAddons(VolumeControlVM inst) // load default actions inst.HotkeyAPI.HotkeyManager.HotkeyActionManager.AddActionDefinitions(HotkeyActionAddonLoader.LoadActions(templateProviderManager, GetActionGroupTypes(hotkeyActionsAssembly))); + hotkeyActionsAssembly = null; + + // create the default addons directory if it doesn't exist + string defaultAddonDirectory = Path.Combine(PathFinder.ApplicationAppDataPath, "Addons"); + if (!Directory.Exists(defaultAddonDirectory)) + { + try + { + Directory.CreateDirectory(defaultAddonDirectory); + } + catch { } + } + + // load addons from default directory + if (Directory.Exists(defaultAddonDirectory)) + LoadAddonsFromDirectory(defaultAddonDirectory, true, inst, templateProviderManager); - // load custom addons - inst.AddonDirectories.ForEach(dir => + // load addons from custom directories + foreach (var directoryPath in Settings.CustomAddonDirectories) { - if (Directory.Exists(dir)) + if (Directory.Exists(directoryPath)) { - FLog.Trace($"[AddonLoader] Searching for DLLs in directory \"{dir}\"."); - foreach (string dllPath in Directory.EnumerateFiles(dir, "*.dll", new EnumerationOptions() { MatchCasing = MatchCasing.CaseInsensitive, RecurseSubdirectories = false, })) - { - // print version info - var fileName = Path.GetFileName(dllPath); - var versionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(dllPath); - - if (versionInfo != null) - { - if (FLog.FilterEventType(EventType.DEBUG)) - FLog.Debug($"[AddonLoader] Found addon DLL \"{versionInfo.FileName}\":", versionInfo.ToString()); - - if (versionInfo.IsDebug) - FLog.Warning( - $"[AddonLoader] Addon DLL was built in DEBUG configuration \"{dllPath}\"!", - " Contact the addon author"); - } - else FLog.Debug($"[AddonLoader] Addon DLL \"{fileName}\" does not have any version information."); - - // try loading the assembly - try - { - var asm = Assembly.LoadFrom(dllPath); - var assemblyName = asm.FullName ?? dllPath; - var exportedTypes = asm.GetExportedTypes(); - - FLog.Debug($"[AddonLoader] \"{assemblyName}\" exports {exportedTypes.Length} types."); - - LoadTranslations(asm); - - // load providers from addon assembly - HotkeyActionAddonLoader.LoadProviders(ref templateProviderManager, exportedTypes); - - // load actions from addon assembly - inst.HotkeyAPI.HotkeyManager.HotkeyActionManager.AddActionDefinitions(HotkeyActionAddonLoader.LoadActions(templateProviderManager, exportedTypes)); - } - catch (Exception ex) - { - FLog.Critical($"[AddonLoader] Failed to load addon DLL \"{dllPath}\" due to an exception!", ex); - } - } + LoadAddonsFromDirectory(directoryPath, false, inst, templateProviderManager); } else { - FLog.Trace($"[AddonLoader] Addon directory \"{dir}\" doesn't exist."); + FLog.Trace($"[{nameof(AddonLoader)}] Addon directory \"{directoryPath}\" doesn't exist."); } - }); + } } #endregion LoadAddons diff --git a/VolumeControl/Helpers/PathFinder.cs b/VolumeControl/Helpers/PathFinder.cs index bad1fb847..c47c70a8e 100644 --- a/VolumeControl/Helpers/PathFinder.cs +++ b/VolumeControl/Helpers/PathFinder.cs @@ -15,10 +15,13 @@ internal static class PathFinder /// /// The absolute filepath of Volume Control's local appdata subdirectory. /// + /// + /// ~/AppData/Local/radj307/VolumeControl + /// public static string ApplicationAppDataPath => _localAppData ??= FindLocalAppDataConfigDir(); private static string? _localAppData = null; public static string ExecutableDirectory => _executableDirectory ??= FindExecutableDirectory(); - private static string? _executableDirectory = null; + private static string? _executableDirectory = null; #endregion Properties #region Functions diff --git a/VolumeControl/Helpers/ShellHelper.cs b/VolumeControl/Helpers/ShellHelper.cs new file mode 100644 index 000000000..d3f3c770c --- /dev/null +++ b/VolumeControl/Helpers/ShellHelper.cs @@ -0,0 +1,136 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using VolumeControl.Log; + +namespace VolumeControl.Helpers +{ + public static class ShellHelper + { + #region OpenIn + /// + /// Starts an application with in the specified as a new process. + /// + /// The path of the application to start. + /// The working directory to open the specified application in. + /// Commandline arguments for the application. + public static void OpenIn(string appPath, string workingDirectory, string arguments) + { + Process.Start(new ProcessStartInfo(appPath) + { + WorkingDirectory = workingDirectory, + Arguments = arguments, + })?.Dispose(); + } + /// + /// Starts an application in the specified as a new process. + /// + /// + public static void OpenIn(string appPath, string workingDirectory) + => OpenIn(appPath, workingDirectory, string.Empty); + #endregion OpenIn + + #region Open + /// + /// Starts an application with in the current directory. + /// + /// The path of the application to start. + /// Commandline arguments for the application. + public static void Open(string appPath, string arguments) + => OpenIn(appPath, Environment.CurrentDirectory, arguments); + /// + /// Starts an application in the current directory. + /// + /// The path of the application to start. + public static void Open(string appPath) + => OpenIn(appPath, Environment.CurrentDirectory, string.Empty); + #endregion Open + + #region OpenWith + /// + /// Opens the specified with an application in the specified directory. + /// + /// The path of the file to open. + /// The directory where the application to use to open the file is located. + /// Commandline arguments for the application. + public static void OpenWith(string filePath, string appBasePath, string args) + { + Process.Start(new ProcessStartInfo(filePath) + { + UseShellExecute = true, + WorkingDirectory = appBasePath, + Arguments = args, + })?.Dispose(); + } + /// + public static void OpenWith(string filePath, string appBasePath) + => OpenWith(filePath, appBasePath, string.Empty); + #endregion OpenWith + + #region OpenWithDefault + /// + /// Opens the specified with the default application for that filetype. + /// + /// The path of the file to open. + public static void OpenWithDefault(string filePath) + { + Process.Start(new ProcessStartInfo(filePath) { UseShellExecute = true })?.Dispose(); + } + #endregion OpenWithDefault + + #region OpenFolderAndSelectItem + + #region P/Invoke + [DllImport("shell32.dll", SetLastError = true)] + private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, [In, MarshalAs(UnmanagedType.LPArray)] IntPtr[] apidl, uint dwFlags); + [DllImport("shell32.dll", SetLastError = true)] + private static extern void SHParseDisplayName([MarshalAs(UnmanagedType.LPWStr)] string name, IntPtr bindingContext, [Out] out IntPtr pidl, uint sfgaoIn, [Out] out uint psfgaoOut); + #endregion P/Invoke + + /// + /// Opens the file explorer with the specified selected. + /// + /// A file to select in the file explorer. + public static void OpenFolderAndSelectItem(string filePath) + { + if (Path.GetDirectoryName(filePath) is not string directoryPath) + { + FLog.Error($"Cannot get directory from path '{filePath}'!"); + return; + } + + SHParseDisplayName(directoryPath, IntPtr.Zero, out IntPtr nativeFolder, 0, out uint psfgaoOut); + + if (nativeFolder == IntPtr.Zero) + { + FLog.Error($"Cannot locate directory '{directoryPath}'!"); + return; + } + + SHParseDisplayName(filePath, IntPtr.Zero, out IntPtr nativeFile, 0, out psfgaoOut); + + IntPtr[] fileArray; + if (nativeFile == IntPtr.Zero) + { + // Open the folder without the file selected if we can't find the file + fileArray = Array.Empty(); + } + else + { + fileArray = new IntPtr[] { nativeFile }; + } + + FLog.Debug($"Opening and selecting '{filePath}' in the file explorer."); + + _ = SHOpenFolderAndSelectItems(nativeFolder, (uint)fileArray.Length, fileArray, 0); + + Marshal.FreeCoTaskMem(nativeFolder); + if (nativeFile != IntPtr.Zero) + { + Marshal.FreeCoTaskMem(nativeFile); + } + } + #endregion OpenFolderAndSelectItem + } +} diff --git a/VolumeControl/Helpers/VCSettings.cs b/VolumeControl/Helpers/VCSettings.cs index 623ac8082..3dcd86a9e 100644 --- a/VolumeControl/Helpers/VCSettings.cs +++ b/VolumeControl/Helpers/VCSettings.cs @@ -1,7 +1,6 @@ using Semver; using System; using System.ComponentModel; -using System.Diagnostics; using System.Management; using VolumeControl.Core; using VolumeControl.Helpers.Win32; diff --git a/VolumeControl/Mixer.xaml.cs b/VolumeControl/Mixer.xaml.cs index fa868d244..e0d70b3ee 100644 --- a/VolumeControl/Mixer.xaml.cs +++ b/VolumeControl/Mixer.xaml.cs @@ -7,12 +7,10 @@ using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using VolumeControl.Core; using VolumeControl.Core.Enum; -using VolumeControl.Core.Extensions; using VolumeControl.Core.Input; using VolumeControl.Core.Input.Enums; using VolumeControl.Helpers; @@ -194,10 +192,7 @@ private void Handle_BrowseForLogFilePathClick(object sender, RoutedEventArgs e) } private void Handle_OpenLogClick(object sender, RoutedEventArgs e) { - Process.Start(new ProcessStartInfo(VCSettings.LogFilePath) - { - UseShellExecute = true - })?.Dispose(); + ShellHelper.OpenWithDefault(VCSettings.LogFilePath); } private void Handle_TargetNameBoxDoubleClick(object sender, MouseButtonEventArgs e) { diff --git a/VolumeControl/Program.cs b/VolumeControl/Program.cs index b993e68d4..ad68b1be1 100644 --- a/VolumeControl/Program.cs +++ b/VolumeControl/Program.cs @@ -344,6 +344,8 @@ private static int Main_Impl(string[] args) #if DEBUG // show all log message types in debug mode FLog.Log.EventTypeFilter = EventType.DEBUG | EventType.INFO | EventType.WARN | EventType.ERROR | EventType.FATAL | EventType.TRACE; + // open the log file in debug mode + ShellHelper.OpenWithDefault(Settings.LogPath); #endif // write commandline arguments to the log if they were specified @@ -352,7 +354,7 @@ private static int Main_Impl(string[] args) var msg = new LogMessage(EventType.INFO, $"Commandline arguments were included:"); for (int i = 0, i_max = args.Length; i < i_max; ++i) { - msg.AppendLine($" [{i}] \"{args[i]}\""); + msg.Add($" [{i}] \"{args[i]}\""); } FLog.LogMessage(msg); } diff --git a/VolumeControl/ViewModels/HotkeyManagerVM.cs b/VolumeControl/ViewModels/HotkeyManagerVM.cs index 1ef49fe67..0be01b7d4 100644 --- a/VolumeControl/ViewModels/HotkeyManagerVM.cs +++ b/VolumeControl/ViewModels/HotkeyManagerVM.cs @@ -1,6 +1,5 @@ using CodingSeb.Localization; using System; -using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; diff --git a/VolumeControl/ViewModels/LogFilterFlagsVM.cs b/VolumeControl/ViewModels/LogFilterFlagsVM.cs index f217dbf61..0453ff327 100644 --- a/VolumeControl/ViewModels/LogFilterFlagsVM.cs +++ b/VolumeControl/ViewModels/LogFilterFlagsVM.cs @@ -20,7 +20,7 @@ public LogFilterFlagsVM() : base(Settings.LogFilter, EventType.NONE | EventType. #endregion Fields #region Properties - private static Config Settings => (Config.Default as Config)!; + private static Config Settings => Config.Default!; #endregion Properties #region EventHandlers diff --git a/VolumeControl/ViewModels/VolumeControlVM.cs b/VolumeControl/ViewModels/VolumeControlVM.cs index efa5f8abd..d84d9d8c1 100644 --- a/VolumeControl/ViewModels/VolumeControlVM.cs +++ b/VolumeControl/ViewModels/VolumeControlVM.cs @@ -1,18 +1,15 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Windows; using VolumeControl.Core; using VolumeControl.Core.Input; -using VolumeControl.Core.Input.Actions; using VolumeControl.CoreAudio; using VolumeControl.Helpers; using VolumeControl.Helpers.Update; using VolumeControl.Log; using VolumeControl.SDK; using VolumeControl.SDK.Internal; -using VolumeControl.TypeExtensions; namespace VolumeControl.ViewModels { @@ -87,7 +84,6 @@ public VolumeControlVM() : base() ///
public IEnumerable AudioSessionProcessIdentifierAutocompleteSource { get; private set; } = null!; public IEnumerable AudioSessionProcessNameAutocompleteSource { get; private set; } = null!; - public IEnumerable AddonDirectories { get; set; } = GetAddonDirectories(); public IReadOnlyList Actions => _actions; private readonly List _actions = new(); #endregion Other @@ -130,37 +126,6 @@ public void ResetHotkeySettings() FLog.Info("Hotkey definitions were reset to default."); } } - private static List GetAddonDirectories() - { - List l = new(); - // check default path: - string defaultPath = Path.Combine(PathFinder.ApplicationAppDataPath, "Addons"); - if (Directory.Exists(defaultPath)) - _ = l.AddIfUnique(defaultPath); - // check custom directories: - if (Settings.CustomAddonDirectories is not null) - { - foreach (string? path in Settings.CustomAddonDirectories) - { - if (path is null) continue; - if (Directory.Exists(path)) - { - _ = l.AddIfUnique(path); - FLog.Debug($"Successfully added custom addon search directory '{path}'"); - } - else - { - FLog.Debug($"'{nameof(Settings.CustomAddonDirectories)}' contains an item that wasn't found: '{path}'!"); - } - } - } - else - { - FLog.Debug($"{nameof(Settings.CustomAddonDirectories)} is null."); - } - - return l; - } /// /// Refreshes the list of auto completion options for the target box. ///