diff --git a/VolumeControl.Core/Config.cs b/VolumeControl.Core/Config.cs
index a656510ee..882a688b2 100644
--- a/VolumeControl.Core/Config.cs
+++ b/VolumeControl.Core/Config.cs
@@ -21,8 +21,11 @@ public class Config : AppConfig.ConfigurationFile
///
/// Creates a new instance.
///
- /// The first time this is called, the property is set to that instance; all subsequent calls do not update this property.
- public Config(string filePath) : base(filePath) { }
+ /// The first time this is called, the property is set to that instance; all subsequent calls do not update this property.
+ public Config(string filePath) : base(filePath)
+ {
+ PropertyChanged += UpdateFLogState;
+ }
#endregion Constructor
#region Properties
@@ -43,7 +46,7 @@ public Config(string filePath) : base(filePath) { }
///
public void ResumeAutoSave()
{
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"Enabled {nameof(Config)} autosave.");
_autoSaveEnabled = true;
@@ -54,7 +57,7 @@ public void ResumeAutoSave()
///
public void PauseAutoSave()
{
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"Disabled {nameof(Config)} autosave.");
_autoSaveEnabled = false;
@@ -85,7 +88,7 @@ public void AttachReflectivePropertyChangedHandlers()
///
public override void Save(Formatting formatting = Formatting.Indented)
{
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"Saved {nameof(Config)}");
base.Save(formatting);
}
@@ -94,10 +97,31 @@ public override void Save(Formatting formatting = Formatting.Indented)
#region EventHandlers
private void HandlePropertyChanged(object? sender, PropertyChangedEventArgs e)
{
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"Config property '{e.PropertyName}' was modified.");
Save();
}
+ private void UpdateFLogState(object? sender, PropertyChangedEventArgs e)
+ {
+ if (!FLog.IsInitialized) return;
+
+ // update the log's properties
+ if (e.PropertyName != null)
+ {
+ if (e.PropertyName.Equals(nameof(EnableLogging), StringComparison.Ordinal))
+ {
+ FLog.Log.EndpointEnabled = EnableLogging;
+ }
+ else if (e.PropertyName.Equals(nameof(LogFilter), StringComparison.Ordinal))
+ {
+ FLog.Log.EventTypeFilter = LogFilter;
+ }
+ else if (e.PropertyName.Equals(nameof(LogPath), StringComparison.Ordinal))
+ {
+ FLog.ChangeLogPath(LogPath);
+ }
+ }
+ }
private void PropertyWithPropertyChangedEvents_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (!_autoSaveEnabled) return;
@@ -332,14 +356,6 @@ private void PropertyWithCollectionChangedEvents_CollectionChanged(object? sende
///
public bool ShowPeakMeters { get; set; } = true;
///
- /// The minimum boundary shown on peak meters.
- ///
- public const double PeakMeterMinValue = 0.0;
- ///
- /// The maximum boundary shown on peak meters.
- ///
- public const double PeakMeterMaxValue = 1.0;
- ///
/// Gets or sets whether volume & mute controls are visible in the Audio Devices list.
///
public bool EnableDeviceControl { get; set; } = false;
@@ -367,10 +383,10 @@ private void PropertyWithCollectionChangedEvents_CollectionChanged(object? sende
// ^^^^^^^
// DO NOT RENAME THIS WITHOUT ALSO RENAMING IT IN VolumeControl.Log.SettingsInterface
///
- /// Gets or sets the filter used for messages.
+ /// Gets or sets the filter used for messages.
/// See
///
- public Log.Enum.EventType LogFilter { get; set; } = Log.Enum.EventType.INFO | Log.Enum.EventType.WARN | Log.Enum.EventType.ERROR | Log.Enum.EventType.FATAL | Log.Enum.EventType.CRITICAL;
+ public EventType LogFilter { get; set; } = EventType.INFO | EventType.WARN | EventType.ERROR | EventType.FATAL | EventType.CRITICAL;
// ^^^^^^^^^
// DO NOT RENAME THIS WITHOUT ALSO RENAMING IT IN VolumeControl.Log.SettingsInterface
///
diff --git a/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs b/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs
index c65fd769a..f7add49a6 100644
--- a/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs
+++ b/VolumeControl.Core/Input/Actions/HotkeyActionDefinition.cs
@@ -135,7 +135,7 @@ private IActionSettingInstance[] CreateActionSettingInstances(IActionSettingInst
}
catch (Exception ex)
{
- if (FLog.Log.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.Log.FilterEventType(EventType.ERROR))
FLog.Log.Error($"Failed to instantiate action setting \"{Name}\" due to an exception:", ex);
}
}
diff --git a/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs b/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs
index bd8c39624..fba2e401f 100644
--- a/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs
+++ b/VolumeControl.Core/Input/Actions/HotkeyActionInstance.cs
@@ -51,12 +51,12 @@ public void Invoke(object? sender, HotkeyPressedEventArgs e)
try
{
HotkeyActionDefinition.Invoke_Unsafe(sender, e);
- if (FLog.Log.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.Log.FilterEventType(EventType.TRACE))
FLog.Log.Trace($"Successfully executed action \"{Name}\".");
}
catch (Exception ex)
{
- if (FLog.Log.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.Log.FilterEventType(EventType.ERROR))
FLog.Log.Error($"Action \"{Name}\" triggered an exception:", ex);
}
}
diff --git a/VolumeControl.Core/Input/HotkeyActionAddonLoader.cs b/VolumeControl.Core/Input/HotkeyActionAddonLoader.cs
index 787efa627..4c254abba 100644
--- a/VolumeControl.Core/Input/HotkeyActionAddonLoader.cs
+++ b/VolumeControl.Core/Input/HotkeyActionAddonLoader.cs
@@ -15,8 +15,9 @@ namespace VolumeControl.Core.Input
///
public static class HotkeyActionAddonLoader
{
+ #region LoadProviders
///
- /// Loads DataTemplate providers from the specified and registers them with the
+ /// Loads DataTemplate providers from the specified and registers them with the .
///
/// The instance to load the provider types into.
/// Any number of instances that represent classes with the .
@@ -36,11 +37,12 @@ public static void LoadProviders(ref TemplateProviderManager provider, params Ty
}
catch (Exception ex)
{
- FLog.Error($"[ActionLoader] Failed to load {nameof(DataTemplate)} provider type \"{type}\" due to an exception:", ex);
+ FLog.Error($"[ActionAddonLoader] Failed to load {nameof(DataTemplate)} provider type \"{type}\" due to an exception:", ex);
}
}
}
+ #endregion LoadProviders
#region ValidateMethodIsEligibleAsAction
private enum EMethodValidationState : byte
@@ -116,6 +118,7 @@ private static EMethodValidationState ValidateMethodIsEligibleAsAction(MethodInf
}
#endregion ValidateMethodIsEligibleAsAction
+ #region LoadActions
///
/// Loads hotkey actions from the specified .
///
@@ -145,7 +148,7 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
// if this type doesn't have any public methods, skip it
if (publicMethods.Length == 0)
{
- FLog.Error($"[ActionLoader] {type.FullName} doesn't contain any publicly-accessible methods marked with {typeof(HotkeyActionAttribute).FullName}!");
+ FLog.Error($"[ActionAddonLoader] {type.FullName} doesn't contain any publicly-accessible methods marked with {typeof(HotkeyActionAttribute).FullName}!");
continue;
}
@@ -168,15 +171,15 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
{
// this doesn't need more information because ValidateMethodIsEligibleAsAction
// logs all of the problems in detail anyway.
- FLog.Error($"[ActionLoader] {method.GetFullMethodName()} was skipped because it is invalid.");
+ FLog.Error($"[ActionAddonLoader] {method.GetFullMethodName()} was skipped because it is invalid.");
continue;
}
// get the action setting definitions for this method
List actionSettingDefs = new();
- if (FLog.FilterEventType(Log.Enum.EventType.DEBUG))
- FLog.Debug($"[ActionLoader] Loading action setting definitions for \"{method.GetFullMethodName()}\"");
+ if (FLog.FilterEventType(EventType.DEBUG))
+ FLog.Debug($"[ActionAddonLoader] Loading action setting definitions for \"{method.GetFullMethodName()}\"");
foreach (var actionSettingAttribute in method.GetCustomAttributes())
{
@@ -192,7 +195,7 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
}
catch (Exception ex)
{
- FLog.Error($"[ActionLoader] ", ex);
+ FLog.Error($"[ActionAddonLoader] ", ex);
}
if (dataTemplate == null)
@@ -212,7 +215,7 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
.GroupBy(d => d.Name)
.Where(g => g.Count() > 1)
.Select(g => $"\"{g.Key}\""));
- FLog.Error($"[ActionLoader] {method.GetFullMethodName()} was skipped because multiple settings have the same name: {duplicateNames}!");
+ FLog.Error($"[ActionAddonLoader] {method.GetFullMethodName()} was skipped because multiple settings have the same name: {duplicateNames}!");
continue;
}
@@ -227,7 +230,7 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
}
catch (Exception ex)
{
- FLog.Error($"[ActionLoader] {method.GetFullMethodName()} was skipped because constructor of type {type.Name} threw an exception:", ex);
+ FLog.Error($"[ActionAddonLoader] {method.GetFullMethodName()} was skipped because constructor of type {type.Name} threw an exception:", ex);
continue;
}
@@ -255,11 +258,11 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
l.Add(hotkeyActionDefinition);
++loadedActionsFromTypeCount;
- if (FLog.Log.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.Log.FilterEventType(EventType.TRACE))
{
List lines = new();
- var lineHeader = "[ActionLoader] ";
+ var lineHeader = "[ActionAddonLoader] ";
lines.Add($"{lineHeader}Loaded {method.GetFullMethodName()}.");
lineHeader = new string(' ', lineHeader.Length);
for (int k = 0, k_max = actionSettingDefs.Count; k < k_max; ++k)
@@ -273,14 +276,15 @@ public static HotkeyActionDefinition[] LoadActions(TemplateProviderManager provi
}
} //< enumerate public methods
- if (FLog.Log.FilterEventType(Log.Enum.EventType.DEBUG))
- FLog.Log.Debug($"[ActionLoader] Loaded {loadedActionsFromTypeCount}{(loadedActionsFromTypeCount == methodsWithActionAttrCount ? "" : $"/{methodsWithActionAttrCount}")} actions from {type.FullName}");
+ if (FLog.Log.FilterEventType(EventType.DEBUG))
+ FLog.Log.Debug($"[ActionAddonLoader] Loaded {loadedActionsFromTypeCount}{(loadedActionsFromTypeCount == methodsWithActionAttrCount ? "" : $"/{methodsWithActionAttrCount}")} actions from {type.FullName}");
} //< enumerate public types
- if (FLog.Log.FilterEventType(Log.Enum.EventType.DEBUG))
- FLog.Log.Debug($"[ActionLoader] Loaded {l.Count} total actions from {typesWithGroupAttrCount} action groups.");
+ if (FLog.Log.FilterEventType(EventType.DEBUG))
+ FLog.Log.Debug($"[ActionAddonLoader] Loaded {l.Count} total actions from {typesWithGroupAttrCount} action groups.");
return l.ToArray();
}
+ #endregion LoadActions
}
}
diff --git a/VolumeControl.Core/Input/Json/HotkeyJsonObject.cs b/VolumeControl.Core/Input/Json/HotkeyJsonObject.cs
index 9f344e9e0..9a01c2575 100644
--- a/VolumeControl.Core/Input/Json/HotkeyJsonObject.cs
+++ b/VolumeControl.Core/Input/Json/HotkeyJsonObject.cs
@@ -76,7 +76,7 @@ public THotkey CreateInstance(HotkeyActionManager actionManager, bool d
}
else
{
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"Couldn't find an action with identifier \"{ActionIdentifier}\"!");
}
}
@@ -99,7 +99,7 @@ private static IActionSettingInstance[] ActionSettingsDictionaryToArray(Dictiona
if (settingDefinition == null)
{
- if (FLog.FilterEventType(Log.Enum.EventType.WARN))
+ if (FLog.FilterEventType(EventType.WARN))
FLog.Warning($"There is no action setting definition associated with JSON key '{name}' for action \"{actionDefinition.Identifier}\".");
continue;
}
@@ -112,7 +112,7 @@ private static IActionSettingInstance[] ActionSettingsDictionaryToArray(Dictiona
}
catch (Exception ex)
{
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"An exception occurred while creating action setting \"{name}\" with value \"{value}\" for action \"{actionDefinition.Identifier}\":", ex);
#if DEBUG
throw; //< rethrow exception in DEBUG configuration
@@ -123,7 +123,7 @@ private static IActionSettingInstance[] ActionSettingsDictionaryToArray(Dictiona
if (settingInstance == null)
{
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"An unknown error occurred while creating action setting \"{name}\" with value \"{value}\" for action \"{actionDefinition.Identifier}\"!");
continue;
}
diff --git a/VolumeControl.Core/Input/TemplateProviderManager.cs b/VolumeControl.Core/Input/TemplateProviderManager.cs
index 1a8bea7ab..2d0e9cb5b 100644
--- a/VolumeControl.Core/Input/TemplateProviderManager.cs
+++ b/VolumeControl.Core/Input/TemplateProviderManager.cs
@@ -101,7 +101,7 @@ private bool TryCreateProvider(Type providerType, out ITemplateProvider provider
_failedTypes.Add(providerType);
return false;
}
- else if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ else if (FLog.FilterEventType(EventType.TRACE))
{
FLog.Trace($"[{nameof(TemplateProviderManager)}] Successfully initialized {nameof(ITemplateProvider)} type \"{providerType}\".");
}
@@ -135,7 +135,7 @@ private bool TryCreateDictionaryProvider(Type dictionaryProviderType, out ITempl
_failedTypes.Add(dictionaryProviderType);
return false;
}
- else if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ else if (FLog.FilterEventType(EventType.TRACE))
{
FLog.Trace($"[{nameof(TemplateProviderManager)}] Successfully initialized {nameof(ITemplateDictionaryProvider)} type \"{dictionaryProviderType}\".");
}
@@ -236,7 +236,7 @@ public bool TryGetDictionaryProvider(Type dictionaryProviderType, out ITemplateD
if (dictionaryProvider.ProvideDataTemplate(key) is ActionSettingDataTemplate actionSettingDataTemplate)
{
// write trace log message
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"[{nameof(TemplateProviderManager)}] Found DataTemplate with key \"{key}\" in {nameof(ITemplateDictionaryProvider)} type \"{dictionaryProvider.GetType()}\".");
return actionSettingDataTemplate;
@@ -244,7 +244,7 @@ public bool TryGetDictionaryProvider(Type dictionaryProviderType, out ITemplateD
}
// write trace log message
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"[{nameof(TemplateProviderManager)}] Couldn't find any DataTemplates with key \"{key}\".");
return null;
@@ -266,7 +266,7 @@ public bool TryGetDictionaryProvider(Type dictionaryProviderType, out ITemplateD
if (provider.ProvideDataTemplate(valueType) is ActionSettingDataTemplate actionSettingDataTemplate)
{
// write trace log message
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"[{nameof(TemplateProviderManager)}] Found DataTemplate for value type \"{valueType}\" in {nameof(ITemplateProvider)} type \"{provider.GetType()}\".");
return actionSettingDataTemplate;
@@ -281,7 +281,7 @@ public bool TryGetDictionaryProvider(Type dictionaryProviderType, out ITemplateD
if (dictionaryProvider.ProvideDataTemplate(valueType) is ActionSettingDataTemplate actionSettingDataTemplate)
{
// write trace log message
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"[{nameof(TemplateProviderManager)}] Found DataTemplate for value type \"{valueType}\" in {nameof(ITemplateDictionaryProvider)} type \"{dictionaryProvider.GetType()}\".");
return actionSettingDataTemplate;
@@ -289,7 +289,7 @@ public bool TryGetDictionaryProvider(Type dictionaryProviderType, out ITemplateD
}
// write trace log message
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"[{nameof(TemplateProviderManager)}] Couldn't find any DataTemplates for value type \"{valueType}\".");
return null;
@@ -341,14 +341,14 @@ public enum FallbackMode
/// was and the and didn't resolve to a valid template.
public DataTemplate? FindDataTemplateFor(Type? providerType, string? templateKey, Type valueType, FallbackMode fallbackMode)
{
- bool traceMessagesAreEnabled = FLog.FilterEventType(Log.Enum.EventType.TRACE);
+ bool traceMessagesAreEnabled = FLog.FilterEventType(EventType.TRACE);
// search specified provider for a data template
if (providerType != null && !FailedTypes.Contains(providerType))
{
if (providerType.IsAssignableTo(typeof(ITemplateProvider)))
{ // provider
- if (templateKey != null && FLog.FilterEventType(Log.Enum.EventType.WARN))
+ if (templateKey != null && FLog.FilterEventType(EventType.WARN))
{ // [WARN] a key name was set, but the specified provider type doesn't support keys
FLog.Warning(
$"[{nameof(TemplateProviderManager)}] A {nameof(HotkeyActionSettingAttribute.DataTemplateProviderKey)} (\"{templateKey}\") was specified, but {nameof(HotkeyActionSettingAttribute.DataTemplateProviderType)} is {nameof(ITemplateProvider)} type \"{providerType}\"!",
@@ -377,14 +377,14 @@ public enum FallbackMode
}
else
{ // [DEBUG] the specified provider does not support this value type
- if (FLog.FilterEventType(Log.Enum.EventType.DEBUG))
+ if (FLog.FilterEventType(EventType.DEBUG))
FLog.Debug($"[{nameof(TemplateProviderManager)}] {nameof(ITemplateProvider)} \"{providerType}\" does not support value type \"{valueType}\" ({StringHelper.GetFullMethodName(providerType.GetMethod(nameof(provider.CanProvideDataTemplate))!)} returned false)! Falling back to other providers.");
}
//< fallthrough
}
else if (!fallbackMode.HasFlag(FallbackMode.OnFailedProvider))
{ // [ERROR] the specified provider type failed
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"[{nameof(TemplateProviderManager)}] {nameof(ITemplateProvider)} \"{providerType}\" failed! (see the failure message above for details)");
return null;
@@ -410,7 +410,7 @@ public enum FallbackMode
}
else if (!fallbackMode.HasFlag(FallbackMode.OnFailedProvider))
{ // [ERROR] the specified dictionary provider type failed
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"[{nameof(TemplateProviderManager)}] {nameof(ITemplateDictionaryProvider)} \"{providerType}\" failed! (see the failure message above for details)");
return null;
@@ -446,14 +446,14 @@ public enum FallbackMode
/// was and the and didn't resolve to a valid template.
public DataTemplate? FindDataTemplateFor(Type? providerType, string? templateKey, Type valueType, bool allowFallbackOnMissingKey = false)
{
- bool showTraceMessages = FLog.FilterEventType(Log.Enum.EventType.TRACE);
+ bool showTraceMessages = FLog.FilterEventType(EventType.TRACE);
// search specified provider for a data template
if (providerType != null && !FailedTypes.Contains(providerType))
{
if (providerType.IsAssignableTo(typeof(ITemplateProvider)))
{ // provider
- if (templateKey != null && FLog.FilterEventType(Log.Enum.EventType.WARN))
+ if (templateKey != null && FLog.FilterEventType(EventType.WARN))
{ // [WARN] a key name was set, but the specified provider type doesn't support keys
FLog.Warning(
$"[{nameof(TemplateProviderManager)}] A {nameof(HotkeyActionSettingAttribute.DataTemplateProviderKey)} (\"{templateKey}\") was specified, but {nameof(HotkeyActionSettingAttribute.DataTemplateProviderType)} is {nameof(ITemplateProvider)} type \"{providerType}\"!",
@@ -475,13 +475,13 @@ public enum FallbackMode
}
else
{ // [DEBUG] the specified provider does not support this value type
- if (FLog.FilterEventType(Log.Enum.EventType.DEBUG))
+ if (FLog.FilterEventType(EventType.DEBUG))
FLog.Debug($"[{nameof(TemplateProviderManager)}] {nameof(ITemplateProvider)} \"{providerType}\" does not support value type \"{valueType}\" ({StringHelper.GetFullMethodName(providerType.GetMethod(nameof(provider.CanProvideDataTemplate))!)} returned false)! Falling back to other providers.");
}
}
else
{ // [ERROR] the specified provider type failed
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"[{nameof(TemplateProviderManager)}] {nameof(ITemplateProvider)} \"{providerType}\" failed! (see the failure message above for details)");
return null;
@@ -507,7 +507,7 @@ public enum FallbackMode
}
else
{ // [ERROR] the specified dictionary provider type failed
- if (FLog.FilterEventType(Log.Enum.EventType.ERROR))
+ if (FLog.FilterEventType(EventType.ERROR))
FLog.Error($"[{nameof(TemplateProviderManager)}] {nameof(ITemplateDictionaryProvider)} \"{providerType}\" failed! (see the failure message above for details)");
return null;
diff --git a/VolumeControl.CoreAudio/AudioDevice.cs b/VolumeControl.CoreAudio/AudioDevice.cs
index e7ac2e223..9b494667a 100644
--- a/VolumeControl.CoreAudio/AudioDevice.cs
+++ b/VolumeControl.CoreAudio/AudioDevice.cs
@@ -33,7 +33,7 @@ internal AudioDevice(MMDevice mmDevice)
AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification;
- if (FLog.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.FilterEventType(EventType.TRACE))
FLog.Trace($"[{nameof(AudioDevice)}] Successfully created {nameof(AudioDevice)} instance \"{FullName}\".");
}
#endregion Constructor
@@ -177,7 +177,7 @@ public float PeakMeterValue
///
public void Dispose()
{
- if (FLog.Log.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.Log.FilterEventType(EventType.TRACE))
FLog.Log.Trace($"Disposing of {nameof(AudioDevice)} instance \"{FullName}\"");
((IDisposable)this.MMDevice).Dispose();
GC.SuppressFinalize(this);
diff --git a/VolumeControl.CoreAudio/AudioDeviceSessionManager.cs b/VolumeControl.CoreAudio/AudioDeviceSessionManager.cs
index a1d6e9fba..0a6a113fa 100644
--- a/VolumeControl.CoreAudio/AudioDeviceSessionManager.cs
+++ b/VolumeControl.CoreAudio/AudioDeviceSessionManager.cs
@@ -28,7 +28,7 @@ internal AudioDeviceSessionManager(AudioDevice audioDevice)
AudioSessionManager2.OnSessionCreated += this.AudioSessionManager_OnSessionCreated;
- bool showTraceLogMessages = FLog.FilterEventType(Log.Enum.EventType.TRACE);
+ bool showTraceLogMessages = FLog.FilterEventType(EventType.TRACE);
if (AudioSessionManager2.Sessions is not null)
{ // populate the sessions list
diff --git a/VolumeControl.CoreAudio/AudioSession.cs b/VolumeControl.CoreAudio/AudioSession.cs
index f0d216769..ef9af49d0 100644
--- a/VolumeControl.CoreAudio/AudioSession.cs
+++ b/VolumeControl.CoreAudio/AudioSession.cs
@@ -18,7 +18,7 @@ public class AudioSession : IAudioControl, IReadOnlyAudioControl, IHideableAudio
#region Constructor
internal AudioSession(AudioDevice owningDevice, AudioSessionControl2 audioSessionControl2)
{
- bool showTraceLogMessages = FLog.FilterEventType(Log.Enum.EventType.TRACE);
+ bool showTraceLogMessages = FLog.FilterEventType(EventType.TRACE);
AudioDevice = owningDevice;
AudioSessionControl = audioSessionControl2;
@@ -354,7 +354,7 @@ public bool HasMatchingName(string name, StringComparison stringComparison = Str
///
public void Dispose()
{
- if (FLog.Log.FilterEventType(Log.Enum.EventType.TRACE))
+ if (FLog.Log.FilterEventType(EventType.TRACE))
FLog.Log.Trace($"Disposing of {nameof(AudioSession)} instance \"{ProcessIdentifier}\"");
this.AudioSessionControl.Dispose();
GC.SuppressFinalize(this);
diff --git a/VolumeControl.Log/AsyncLogWriter.cs b/VolumeControl.Log/AsyncLogWriter.cs
index 77528e8ee..c84f53f8b 100644
--- a/VolumeControl.Log/AsyncLogWriter.cs
+++ b/VolumeControl.Log/AsyncLogWriter.cs
@@ -1,15 +1,17 @@
using System.Collections;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
-using VolumeControl.Log.Endpoints;
-using VolumeControl.Log.Enum;
+using VolumeControl.Log.Helpers;
+using VolumeControl.Log.Interfaces;
namespace VolumeControl.Log
{
///
/// Asynchonously writes messages to the log endpoint.
///
- public sealed class AsyncLogWriter : ThreadedLogger, ILogWriter, IDisposable
+ public sealed class AsyncLogWriter : ThreadedActionQueue, ILogWriter, INotifyPropertyChanged, IDisposable
{
#region Constructor
///
@@ -17,10 +19,10 @@ public sealed class AsyncLogWriter : ThreadedLogger, ILogWriter, IDisposable
///
/// A log endpoint instance.
/// The default event type filter.
- public AsyncLogWriter(IEndpoint endpoint, EventType eventTypeFilter) : base()
+ public AsyncLogWriter(IEndpointWriter endpoint, EventType eventTypeFilter) : base()
{
Endpoint = endpoint;
- EventTypeFilter = eventTypeFilter;
+ _eventTypeFilter = eventTypeFilter;
}
static AsyncLogWriter()
{
@@ -31,7 +33,7 @@ static AsyncLogWriter()
#endregion Constructor
#region Fields
- internal readonly IEndpoint Endpoint;
+ internal readonly IEndpointWriter Endpoint;
///
/// The string that defines the format of timestamps.
///
@@ -70,21 +72,31 @@ public bool EndpointEnabled
{
lock (Endpoint)
{
- return Endpoint.Enabled;
+ return Endpoint.IsWritingEnabled;
}
}
set
{
lock (Endpoint)
{
- Endpoint.Enabled = value;
+ Endpoint.IsWritingEnabled = value;
}
+ NotifyPropertyChanged();
}
}
///
/// Gets or sets the event type filter that determines which message types are visible for this log writer instance.
///
- public EventType EventTypeFilter { get; set; }
+ public EventType EventTypeFilter
+ {
+ get => _eventTypeFilter;
+ set
+ {
+ _eventTypeFilter = value;
+ NotifyPropertyChanged();
+ }
+ }
+ private EventType _eventTypeFilter;
///
/// Gets or sets whether log messages are added to the queue to be written asynchronously, or written synchronously.
///
@@ -94,7 +106,7 @@ public bool IsAsyncEnabled
get => _isAsyncEnabled;
set
{
- if (_isAsyncEnabled == value) return;
+ if (value == _isAsyncEnabled) return;
if (value)
{ // enable async
@@ -105,14 +117,20 @@ public bool IsAsyncEnabled
_isAsyncEnabled = false;
Flush();
}
+ NotifyPropertyChanged();
}
}
private bool _isAsyncEnabled = true;
#endregion Properties
- #region WriteLogMessage Override
+ #region Events
///
- protected override void WriteLogMessage(LogMessage message)
+ public event PropertyChangedEventHandler? PropertyChanged;
+ private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new(propertyName));
+ #endregion Events
+
+ #region (Private) WriteLogMessage
+ private void WriteLogMessage(LogMessage message)
{
if (!FilterEventType(message.EventType))
{ // event type is not enabled; don't show it
@@ -121,9 +139,9 @@ protected override void WriteLogMessage(LogMessage message)
lock (Endpoint)
{
- if (!Endpoint.Enabled) return;
+ if (!Endpoint.IsWritingEnabled) return;
- using var writer = Endpoint.GetWriter();
+ using var writer = Endpoint.GetTextWriter();
if (writer == null) return;
@@ -166,7 +184,7 @@ protected override void WriteLogMessage(LogMessage message)
writer.Flush();
}
}
- #endregion WriteLogMessage Override
+ #endregion (Private) WriteLogMessage
#region Methods
@@ -239,7 +257,7 @@ public bool FilterEventType(EventType eventType)
#region ResetEndpoint
///
- /// Resets the endpoint to its default state by calling .
+ /// Resets the endpoint to its default state by calling .
///
public void ResetEndpoint()
{
@@ -249,7 +267,7 @@ public void ResetEndpoint()
}
}
///
- /// Resets the endpoint to its default state by calling , and writes the specified .
+ /// Resets the endpoint to its default state by calling , and writes the specified .
///
/// The first line to (synchronously) write to the log.
public void ResetEndpoint(string firstLine)
@@ -257,7 +275,7 @@ public void ResetEndpoint(string firstLine)
lock (Endpoint)
{
Endpoint.Reset();
- Endpoint.WriteRawLine(firstLine);
+ Endpoint.WriteLine(firstLine);
}
}
#endregion ResetEndpoint
@@ -267,14 +285,15 @@ public void ResetEndpoint(string firstLine)
/// Writes the specified to the log.
///
///
- /// When IsAsyncEnabled is , the message is added to the queue and written asynchronously; otherwise, the message is blocking and the message is written synchronously.
+ /// When IsAsyncEnabled is , the message is written asynchronously;
+ /// otherwise, the message is written synchronously and the caller will be blocked until the message has been written.
///
- ///
- public new void LogMessage(LogMessage logMessage)
+ /// The message to write to the log.
+ public void LogMessage(LogMessage logMessage)
{
if (IsAsyncEnabled)
{
- base.LogMessage(logMessage);
+ Enqueue(() => WriteLogMessage(logMessage));
}
else // async is disabled
{
@@ -333,9 +352,13 @@ public void ResetEndpoint(string firstLine)
///
/// Sets the IsAsyncEnabled property to without flushing the queue.
///
+ ///
+ /// Queued messages will still be written, but any further messages will be written synchronously.
+ ///
public void DisableAsyncNoFlush()
{
_isAsyncEnabled = false;
+ NotifyPropertyChanged(nameof(IsAsyncEnabled));
}
#endregion DisableAsyncNoFlush
diff --git a/VolumeControl.Log/Endpoints/BaseEndpointWriter.cs b/VolumeControl.Log/Endpoints/BaseEndpointWriter.cs
new file mode 100644
index 000000000..96fe8ed9c
--- /dev/null
+++ b/VolumeControl.Log/Endpoints/BaseEndpointWriter.cs
@@ -0,0 +1,86 @@
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using VolumeControl.Log.Interfaces;
+
+namespace VolumeControl.Log.Endpoints
+{
+ ///
+ /// base class for endpoint writers.
+ ///
+ public abstract class BaseEndpointWriter : IEndpointWriter, INotifyPropertyChanged
+ {
+ #region Constructor
+ ///
+ /// Creates a new instance.
+ ///
+ /// The initial state
+ protected BaseEndpointWriter(bool isWritingEnabled = true)
+ {
+ _isWritingEnabled = isWritingEnabled;
+ }
+ #endregion Constructor
+
+ #region Properties
+ ///
+ public bool IsWritingEnabled
+ {
+ get => _isWritingEnabled;
+ set
+ {
+ NotifyEnabledChanging(value);
+ _isWritingEnabled = value;
+ NotifyEnabledChanged(_isWritingEnabled);
+ }
+ }
+ private bool _isWritingEnabled;
+ #endregion Properties
+
+ #region Events
+ ///
+ public event EventHandler? EnabledChanging;
+ private void NotifyEnabledChanging(bool incomingState) => EnabledChanging?.Invoke(this, incomingState);
+ ///
+ public event EventHandler? EnabledChanged;
+ private void NotifyEnabledChanged(bool newState) => EnabledChanged?.Invoke(this, newState);
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+ ///
+ /// Triggers the PropertyChanged event for the specified .
+ ///
+ /// The name of the property that was changed.
+ protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new(propertyName));
+ #endregion Events
+
+ #region (Abstract) Methods
+ ///
+ public abstract TextWriter GetTextWriter();
+ ///
+ public abstract void Reset();
+ #endregion (Abstract) Methods
+
+ #region Methods
+ ///
+ public virtual void Write(string @string)
+ {
+ if (!IsWritingEnabled) return;
+
+ using var writer = GetTextWriter();
+ writer.Write(@string);
+ writer.Flush();
+ }
+ ///
+ public virtual void WriteLine(string @string)
+ {
+ if (!IsWritingEnabled) return;
+
+ using var writer = GetTextWriter();
+ writer.WriteLine(@string);
+ writer.Flush();
+ }
+ ///
+ /// Writes a line break to the endpoint.
+ ///
+ public virtual void WriteLine() => WriteLine(string.Empty);
+ #endregion Methods
+ }
+}
\ No newline at end of file
diff --git a/VolumeControl.Log/Endpoints/ConsoleEndpoint.cs b/VolumeControl.Log/Endpoints/ConsoleEndpoint.cs
index bec93cf83..84a2b7fff 100644
--- a/VolumeControl.Log/Endpoints/ConsoleEndpoint.cs
+++ b/VolumeControl.Log/Endpoints/ConsoleEndpoint.cs
@@ -3,52 +3,38 @@
///
/// Allows using the as a logging endpoint.
///
- public class ConsoleEndpoint : IEndpoint
+ public class ConsoleEndpoint : BaseEndpointWriter
{
#region Constructor
- ///
- public ConsoleEndpoint() => this.Enabled = true;
+ ///
+ /// Creates a new instance.
+ ///
+ public ConsoleEndpoint() : base(true) { }
#endregion Constructor
- #region Properties
- ///
- public bool Enabled
- {
- get => _enabled;
- set
- {
- NotifyEnabledChanging(value);
- _enabled = value;
- NotifyEnabledChanged(_enabled);
- }
- }
- private bool _enabled;
- #endregion Properties
-
- #region Events
- ///
- public event EventHandler? EnabledChanging;
- private void NotifyEnabledChanging(bool incomingState) => EnabledChanging?.Invoke(this, incomingState);
- ///
- public event EventHandler? EnabledChanged;
- private void NotifyEnabledChanged(bool newState) => EnabledChanged?.Invoke(this, newState);
- #endregion Events
-
#region Methods
- ///
- public TextReader? GetReader() => Console.In;
- ///
- public TextWriter? GetWriter() => Console.Out;
- ///
- public int? ReadRaw() => Console.Read();
- ///
- public string? ReadRawLine() => Console.ReadLine();
- ///
- public void Reset() => Console.Clear();
- ///
- public void WriteRaw(string? str) => Console.Write(str);
- ///
- public void WriteRawLine(string? str = null) => Console.WriteLine(str);
+ ///
+ /// Gets the instance for the standard output console stream.
+ ///
+ ///
+ /// The caller should not dispose of the returned writer object.
+ ///
+ /// instance.
+ public static TextWriter GetSTDOUT() => Console.Out;
+ ///
+ /// Gets the instance for the standard error console stream.
+ ///
+ ///
+ /// The caller should not dispose of the returned writer object.
+ ///
+ /// instance.
+ public static TextWriter GetSTDERR() => Console.Error;
+ ///
+ public override TextWriter GetTextWriter() => Console.Out;
+ ///
+ /// Clears the console.
+ ///
+ public override void Reset() => Console.Clear();
#endregion Methods
}
}
diff --git a/VolumeControl.Log/Endpoints/FileEndpoint.cs b/VolumeControl.Log/Endpoints/FileEndpoint.cs
index b8f242706..dbc553215 100644
--- a/VolumeControl.Log/Endpoints/FileEndpoint.cs
+++ b/VolumeControl.Log/Endpoints/FileEndpoint.cs
@@ -1,112 +1,61 @@
-namespace VolumeControl.Log.Endpoints
+using VolumeControl.Log.Interfaces;
+
+namespace VolumeControl.Log.Endpoints
{
///
/// Log endpoint that allows writing logs directly to a file on disk.
///
- public class FileEndpoint : IEndpoint
+ public class FileEndpoint : BaseEndpointWriter, IEndpointWriter
{
#region Constructors
///
/// The location of the log file.
/// Whether the endpoint is already enabled when constructed or not.
- public FileEndpoint(string path, bool enabled)
+ public FileEndpoint(string path, bool enabled) : base(enabled)
{
- this.Path = path;
- this.Enabled = enabled;
+ _path = path;
}
#endregion Constructors
#region Properties
- ///
- public bool Enabled
+ ///
+ /// The location of the log file.
+ ///
+ public string Path
{
- get => _enabled;
+ get => _path;
set
{
- NotifyEnabledChanging(value);
- _enabled = value;
- NotifyEnabledChanged(_enabled);
+ _path = value;
+ NotifyPropertyChanged();
}
}
- private bool _enabled;
+ private string _path;
///
- /// The location of the log file.
+ /// Gets whether the Path is a blank string or not.
///
- public string Path { get; set; }
+ /// when the Path is a blank string; otherwise .
+ public bool PathIsEmpty => Path.Length == 0;
#endregion Properties
- #region Events
- ///
- public event EventHandler? EnabledChanging;
- private void NotifyEnabledChanging(bool incomingState) => EnabledChanging?.Invoke(this, incomingState);
- ///
- public event EventHandler? EnabledChanged;
- private void NotifyEnabledChanged(bool newState) => EnabledChanged?.Invoke(this, newState);
- #endregion Events
-
#region Methods
- internal TextReader? GetReader(FileStreamOptions open) => !this.Enabled || this.Path.Length == 0 ? null : (TextReader)new StreamReader(File.Open(this.Path, open));
- internal TextWriter? GetWriter(FileStreamOptions open) => !this.Enabled || this.Path.Length == 0 ? null : (TextWriter)new StreamWriter(File.Open(this.Path, open));
- ///
- public TextReader? GetReader() => !this.Enabled || this.Path.Length == 0
- ? null
- : this.GetReader(new() { Mode = FileMode.Open, Access = FileAccess.Read, Share = FileShare.ReadWrite });
- ///
- public TextWriter? GetWriter() => !this.Enabled || this.Path.Length == 0
- ? null
- : this.GetWriter(new() { Mode = FileMode.Append, Access = FileAccess.Write, Share = FileShare.ReadWrite });
-
- ///
- public void WriteRaw(string? str, FileMode mode)
- {
- if (!this.Enabled || this.Path.Length == 0)
- return;
- using StreamWriter w = new(File.Open(this.Path, mode, FileAccess.Write, FileShare.Read)) { AutoFlush = true };
- w.Write(str);
- w.Flush();
- }
- ///
- public void WriteRaw(string? str) => this.WriteRaw(str, FileMode.Append);
- ///
- public void WriteRawLine(string? str, FileMode mode)
- {
- if (!this.Enabled || this.Path.Length == 0)
- return;
- using StreamWriter w = new(File.Open(this.Path, mode, FileAccess.Write, FileShare.Read)) { AutoFlush = true };
- w.WriteLine(str);
- w.Flush();
- }
- ///
- public void WriteRawLine(string? str) => this.WriteRawLine(str, FileMode.Append);
- ///
- public int? ReadRaw(FileMode mode)
- {
- if (!this.Enabled || this.Path.Length == 0)
- return null;
- using StreamReader r = new(File.Open(this.Path, mode, FileAccess.Read, FileShare.ReadWrite));
- return r.Read();
- }
+ internal StreamWriter? GetWriter(FileMode fileMode, FileAccess fileAccess, FileShare fileShare) => !this.IsWritingEnabled || this.Path.Length == 0 ? null : new StreamWriter(File.Open(this.Path, fileMode, fileAccess, fileShare));
+ ///
+ /// Gets a new instance for the file. The caller is responsible for disposing of it.
+ ///
+ /// A new instance when this endpoint is enabled; otherwise .
+ public StreamWriter GetStreamWriter() => !this.IsWritingEnabled || this.Path.Length == 0
+ ? null!
+ : this.GetWriter(FileMode.Append, FileAccess.Write, FileShare.Write)!;
///
- public string? ReadRawLine(FileMode mode)
- {
- if (!this.Enabled || this.Path.Length == 0)
- return null;
- using StreamReader r = new(File.Open(this.Path, mode, FileAccess.Read, FileShare.ReadWrite));
- return r.ReadLine();
- }
+ public override TextWriter GetTextWriter() => GetStreamWriter();
///
- public void Reset()
+ public override void Reset()
{
- if (!this.Enabled || !File.Exists(this.Path))
+ if (!this.IsWritingEnabled || !File.Exists(this.Path))
return;
File.Open(this.Path, FileMode.Truncate, FileAccess.Write, FileShare.ReadWrite).Dispose();
}
-
- ///
- public int? ReadRaw() => this.ReadRaw(FileMode.Open);
- ///
- public string? ReadRawLine() => this.ReadRawLine(FileMode.Open);
-
#endregion Methods
}
}
\ No newline at end of file
diff --git a/VolumeControl.Log/Endpoints/IEndpoint.cs b/VolumeControl.Log/Endpoints/IEndpoint.cs
deleted file mode 100644
index 4c2643a40..000000000
--- a/VolumeControl.Log/Endpoints/IEndpoint.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-namespace VolumeControl.Log.Endpoints
-{
- ///
- /// Represents an endpoint output target for a log writer, and exposes helper methods for interacting with it.
- ///
- public interface IEndpoint
- {
- #region Properties
- ///
- /// Return true when the endpoint is enabled.
- ///
- bool Enabled { get; set; }
- #endregion Properties
-
- #region Events
- ///
- /// Occurs when the endpoint is about to be enabled or disabled for any reason.
- ///
- ///
- /// The boolean argument is the incoming state.
- ///
- event EventHandler? EnabledChanging;
- ///
- /// Occurs when the endpoint is enabled or disabled for any reason.
- ///
- ///
- /// The boolean argument is the new state.
- ///
- event EventHandler? EnabledChanged;
- #endregion Events
-
- #region Methods
- ///
- /// Retrieve a object for reading from the endpoint.
- /// Using this is only recommended for repeated read operations, such as in a loop; There is no benefit to using this for single read operations.
- ///
- ///
- /// null The endpoint isn't enabled.
- /// A reader using this endpoint's output target.
- ///
- TextReader? GetReader();
- ///
- /// Retrieve a object for writing to the endpoint.
- /// Using this is only recommended for repeated write operations, such as in a loop; There is no benefit to using this for single write operations.
- ///
- ///
- /// null The endpoint isn't enabled.
- /// A writer using this endpoint's output target.
- ///
- TextWriter? GetWriter();
- ///
- /// Write to the filestream.
- /// It is highly recommended that you do not use this function, as it doesn't conform to formatting rules.
- ///
- /// A string to write.
- void WriteRaw(string? str);
- ///
- /// Write to the filestream, and append a newline.
- /// It is highly recommended that you do not use this function, as it doesn't conform to formatting rules.
- ///
- /// A string to write.
- void WriteRawLine(string? str = null);
- ///
- /// Read a character from the log endpoint.
- ///
- /// Integer (4-byte) representation of a single character.
- int? ReadRaw();
- ///
- /// Read a line from the log endpoint.
- ///
- /// A string containing one line from the log endpoint.
- string? ReadRawLine();
- ///
- /// Reset the contents of the log endpoint, leaving it empty.
- ///
- void Reset();
- #endregion Methods
- }
-}
\ No newline at end of file
diff --git a/VolumeControl.Log/Endpoints/MemoryEndpoint.cs b/VolumeControl.Log/Endpoints/MemoryEndpoint.cs
index 67dfb6699..273ef34c1 100644
--- a/VolumeControl.Log/Endpoints/MemoryEndpoint.cs
+++ b/VolumeControl.Log/Endpoints/MemoryEndpoint.cs
@@ -1,9 +1,11 @@
-namespace VolumeControl.Log.Endpoints
+using VolumeControl.Log.Interfaces;
+
+namespace VolumeControl.Log.Endpoints
{
///
- /// A log endpoint that implements and uses a as an endpoint.
+ /// A log endpoint that implements and uses a as an endpoint.
///
- public class MemoryEndpoint : IEndpoint, IDisposable
+ public class MemoryEndpoint : IEndpointWriter, IDisposable
{
#region Constructor
///
@@ -12,7 +14,7 @@ public class MemoryEndpoint : IEndpoint, IDisposable
public MemoryEndpoint(bool enabled = true, int kilobytes = 10)
{
_stream = new(new byte[1024 * kilobytes], true);
- this.Enabled = enabled;
+ this.IsWritingEnabled = enabled;
}
#endregion Constructor
@@ -22,7 +24,7 @@ public MemoryEndpoint(bool enabled = true, int kilobytes = 10)
#region Properties
///
- public bool Enabled
+ public bool IsWritingEnabled
{
get => _enabled;
set
@@ -45,25 +47,31 @@ public bool Enabled
#endregion Events
#region Methods
+ ///
+ /// Gets a new instance for this memory endpoint.
+ ///
+ ///
+ /// The caller is responsible for disposing of the returned reader object.
+ ///
+ /// New instance.
+ public TextReader GetTextReader() => this.IsWritingEnabled ? new StreamReader(_stream, leaveOpen: true) : null!;
///
- public TextReader? GetReader() => this.Enabled ? new StreamReader(_stream, leaveOpen: true) : null;
- ///
- public TextWriter? GetWriter() => this.Enabled ? new StreamWriter(_stream, leaveOpen: true) : null;
+ public TextWriter GetTextWriter() => this.IsWritingEnabled ? new StreamWriter(_stream, leaveOpen: true) : null!;
///
public int? ReadRaw()
{
- if (!this.Enabled)
+ if (!this.IsWritingEnabled)
return null;
- using TextReader? r = this.GetReader();
+ using TextReader? r = this.GetTextReader();
int? ch = r?.Read();
return ch;
}
///
public string? ReadRawLine()
{
- if (!this.Enabled)
+ if (!this.IsWritingEnabled)
return null;
- using TextReader? r = this.GetReader();
+ using TextReader? r = this.GetTextReader();
string? line = r?.ReadLine();
r?.Dispose();
return line;
@@ -71,18 +79,18 @@ public bool Enabled
///
public void Reset() => _stream = new();
///
- public void WriteRaw(string? str)
+ public void Write(string? str)
{
- if (!this.Enabled || str == null)
+ if (!this.IsWritingEnabled || str == null)
return;
_stream.Write(new Span(str.ToCharArray().Cast().ToArray()));
}
///
- public void WriteRawLine(string? str = null)
+ public void WriteLine(string? str = null)
{
- if (!this.Enabled)
+ if (!this.IsWritingEnabled)
return;
- this.WriteRaw(str == null ? "\n" : $"{str}\n");
+ this.Write(str == null ? "\n" : $"{str}\n");
}
///
diff --git a/VolumeControl.Log/Enum/EventType.cs b/VolumeControl.Log/Enum/EventType.cs
deleted file mode 100644
index f016f2ee7..000000000
--- a/VolumeControl.Log/Enum/EventType.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-namespace VolumeControl.Log.Enum
-{
- ///
- /// Determines the header used to print formatted log messages.
- ///
- [Flags]
- public enum EventType : byte
- {
- ///
- /// No event types. This is the 0 value for the EventType flagset.
- /// This EventType does not have an associated header, and may only be used for type filtering.
- /// If used anyway, it produces this header: "[????]"
- ///
- NONE = 0,
- ///
- /// A debugging message that should only be shown when in debug mode.
- /// Produces header: "[DEBUG]"
- ///
- DEBUG = 1,
- ///
- /// An informational message.
- /// Produces header: "[INFO]"
- ///
- INFO = 2,
- ///
- /// A warning message.
- /// Produces header: "[WARN]"
- ///
- WARN = 4,
- ///
- /// An error message.
- /// Produces header: "[ERROR]"
- ///
- ERROR = 8,
- ///
- /// A fatal error message.
- /// Produces header: "[FATAL]"
- ///
- FATAL = 16,
- ///
- /// A critical message that does not necessarily indicate failure, but cannot be prevented from appearing in the log by user settings.
- /// Produces header: "[CRITICAL]"
- ///
- CRITICAL = 32,
- ///
- /// Extremely situational debug information.
- /// Produces header: "[TRACE]"
- ///
- TRACE = 64,
- }
-}
diff --git a/VolumeControl.Log/EventType.cs b/VolumeControl.Log/EventType.cs
new file mode 100644
index 000000000..8515728c6
--- /dev/null
+++ b/VolumeControl.Log/EventType.cs
@@ -0,0 +1,58 @@
+namespace VolumeControl.Log
+{
+ ///
+ /// Determines the header used to print formatted log messages.
+ ///
+ [Flags]
+ public enum EventType : byte
+ {
+ ///
+ /// Not an event type.
+ ///
+ NONE = 0,
+ ///
+ /// Message contains debugging information.
+ ///
+ ///
+ /// For debug info that is only useful in certain situations, see .
+ ///
+ DEBUG = 1,
+ ///
+ /// Message contains information that isn't related to an error or warning.
+ ///
+ INFO = 2,
+ ///
+ /// Messages related to a warning or very minor error.
+ ///
+ WARN = 4,
+ ///
+ /// Messages related to an error.
+ ///
+ ERROR = 8,
+ ///
+ /// Messages related to a significant error of critical importance.
+ ///
+ ///
+ /// For errors that the application cannot recover from, see .
+ ///
+ CRITICAL = 16,
+ ///
+ /// Messages related to a significant error that the application cannot recover from.
+ ///
+ ///
+ /// For significant errors that did not cause the application to exit unexpectedly, see .
+ ///
+ FATAL = 32,
+ ///
+ /// Message contains debugging information that is only situationally useful.
+ ///
+ ///
+ /// For debug info that is generally useful in most or all case, see .
+ ///
+ TRACE = 64,
+ ///
+ /// All event types.
+ ///
+ ALL = DEBUG | INFO | WARN | ERROR | CRITICAL | FATAL | TRACE,
+ }
+}
diff --git a/VolumeControl.Log/NotInitializedException.cs b/VolumeControl.Log/Exceptions/NotInitializedException.cs
similarity index 95%
rename from VolumeControl.Log/NotInitializedException.cs
rename to VolumeControl.Log/Exceptions/NotInitializedException.cs
index 63bd3d034..550830108 100644
--- a/VolumeControl.Log/NotInitializedException.cs
+++ b/VolumeControl.Log/Exceptions/NotInitializedException.cs
@@ -1,4 +1,4 @@
-namespace VolumeControl.Log
+namespace VolumeControl.Log.Exceptions
{
///
/// Represents errors that occur as a result of trying to access an object before it is initialized.
diff --git a/VolumeControl.Log/FLog.cs b/VolumeControl.Log/FLog.cs
index cafd84b3f..2d484676b 100644
--- a/VolumeControl.Log/FLog.cs
+++ b/VolumeControl.Log/FLog.cs
@@ -1,13 +1,13 @@
using VolumeControl.Log.Endpoints;
-using VolumeControl.Log.Enum;
+using VolumeControl.Log.Exceptions;
namespace VolumeControl.Log
{
///
- /// Static logger class.
+ /// Static file logger class.
///
///
- /// The method must be called before accessing any properties, and after the settings have initialized.
+ /// The method must be called before accessing any properties.
///
public static class FLog
{
@@ -17,7 +17,6 @@ public static class FLog
#endregion Fields
#region Properties
- private static SettingsInterface SettingsInterface => SettingsInterface.Default;
///
/// Gets the instance.
///
@@ -49,6 +48,13 @@ public static bool IsAsyncEnabled
_logWriter.IsAsyncEnabled = value;
}
}
+ ///
+ /// Gets whether this log has been initialized yet or not.
+ ///
+ ///
+ /// This does not throw exceptions.
+ ///
+ public static bool IsInitialized => _initialized;
#endregion Properties
#region Methods
@@ -59,12 +65,12 @@ public static bool IsAsyncEnabled
///
/// Displayed in the header as "Log (verb)"
private static string MakeInitMessage(string verb)
- => $"{AsyncLogWriter.DateTimeFormatString}{AsyncLogWriter.Indent(AsyncLogWriter.TimestampLength, AsyncLogWriter.DateTimeFormatString.Length)}{new string(' ', AsyncLogWriter.EventTypeLength)}=== Log {verb} @ {DateTime.UtcNow:U} === {{ {nameof(SettingsInterface.LogFilter)}: {(int)Log.EventTypeFilter} ({Log.EventTypeFilter:G}) }}{Environment.NewLine}";
+ => $"{AsyncLogWriter.DateTimeFormatString}{AsyncLogWriter.Indent(AsyncLogWriter.TimestampLength, AsyncLogWriter.DateTimeFormatString.Length)}{new string(' ', AsyncLogWriter.EventTypeLength)}=== Log {verb} @ {DateTime.UtcNow:U} === {{ LogFilter: {(int)Log.EventTypeFilter} ({Log.EventTypeFilter:G}) }}{Environment.NewLine}";
private static void WriteRaw(string text)
{
lock (Log.Endpoint)
{
- Log.Endpoint.WriteRaw(text);
+ Log.Endpoint.Write(text);
}
}
private static NotInitializedException MakeLogNotInitializedException(Exception? innerException = null)
@@ -77,21 +83,16 @@ private static NotInitializedException MakeLogNotInitializedException(Exception?
///
/// The method has already been called before.
/// The default config object hasn't been initialized yet.
- public static void Initialize()
+ public static void Initialize(string path, bool enable, EventType logFilter, bool deleteExisting = true)
{
if (_initialized) // already initialized
throw new InvalidOperationException($"{nameof(FLog)} is already initialized! {nameof(Initialize)}() can only be called once.");
- else if (SettingsInterface == null) // settings aren't initialized yet
- throw new NotInitializedException(nameof(SettingsInterface), $"The default {nameof(AppConfig.Configuration)} instance must be initialized prior to calling {nameof(Initialize)}()!");
-
- // attach property changed handler to the settings object so we can react to changes
- SettingsInterface.PropertyChanged += SettingsInterface_PropertyChanged;
- _logWriter = new(new FileEndpoint(SettingsInterface.LogPath, SettingsInterface.EnableLogging), SettingsInterface.LogFilter);
+ _logWriter = new(new FileEndpoint(path, enable), logFilter);
_initialized = true; //< Log is valid here
- if (SettingsInterface.LogClearOnInitialize)
+ if (deleteExisting)
{
Log.ResetEndpoint(MakeInitMessage("Initialized"));
}
@@ -99,6 +100,20 @@ public static void Initialize()
}
#endregion Initialize
+ #region ChangeLogPath
+ ///
+ /// Sets the log path to a new location.
+ ///
+ /// The filepath to change the log path to.
+ public static void ChangeLogPath(string newPath)
+ {
+ if (!_initialized)
+ throw MakeLogNotInitializedException();
+
+ ((FileEndpoint)Log.Endpoint).Path = newPath;
+ }
+ #endregion ChangeLogPath
+
#region AsyncLogWriter Methods
///
public static bool FilterEventType(EventType eventType) => Log.FilterEventType(eventType);
@@ -123,34 +138,5 @@ public static void Initialize()
#endregion AsyncLogWriter Methods
#endregion Methods
-
- #region EventHandlers
-
- #region SettingsInterface
- private static void SettingsInterface_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
- {
- if (e.PropertyName == null || !_initialized) return;
-
- if (e.PropertyName.Equals(nameof(SettingsInterface.EnableLogging), StringComparison.Ordinal))
- {
- Log.EndpointEnabled = SettingsInterface.EnableLogging;
- }
- else if (e.PropertyName.Equals(nameof(SettingsInterface.LogPath), StringComparison.Ordinal))
- {
- lock (Log.Endpoint)
- {
- ((FileEndpoint)Log.Endpoint).Path = SettingsInterface.LogPath;
- }
- }
- else if (e.PropertyName.Equals(nameof(SettingsInterface.LogFilter), StringComparison.Ordinal))
- {
- Log.EventTypeFilter = SettingsInterface.LogFilter;
-
- WriteRaw(MakeInitMessage("Filter Changed"));
- }
- }
- #endregion SettingsInterface
-
- #endregion EventHandlers
}
}
diff --git a/VolumeControl.Log/FodyWeavers.xml b/VolumeControl.Log/FodyWeavers.xml
deleted file mode 100644
index 4e68ed1a8..000000000
--- a/VolumeControl.Log/FodyWeavers.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/VolumeControl.Log/FodyWeavers.xsd b/VolumeControl.Log/FodyWeavers.xsd
deleted file mode 100644
index 69dbe488c..000000000
--- a/VolumeControl.Log/FodyWeavers.xsd
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Used to control if the On_PropertyName_Changed feature is enabled.
-
-
-
-
- Used to control if the Dependent properties feature is enabled.
-
-
-
-
- Used to control if the IsChanged property feature is enabled.
-
-
-
-
- Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.
-
-
-
-
- Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.
-
-
-
-
- Used to control if equality checks should use the Equals method resolved from the base class.
-
-
-
-
- Used to control if equality checks should use the static Equals method resolved from the base class.
-
-
-
-
- Used to turn off build warnings from this weaver.
-
-
-
-
- Used to turn off build warnings about mismatched On_PropertyName_Changed methods.
-
-
-
-
-
-
-
- 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
-
-
-
-
- A comma-separated list of error codes that can be safely ignored in assembly verification.
-
-
-
-
- 'false' to turn off automatic generation of the XML Schema file.
-
-
-
-
-
\ No newline at end of file
diff --git a/VolumeControl.Log/ExceptionMessageHelper.cs b/VolumeControl.Log/Helpers/ExceptionMessageHelper.cs
similarity index 98%
rename from VolumeControl.Log/ExceptionMessageHelper.cs
rename to VolumeControl.Log/Helpers/ExceptionMessageHelper.cs
index bdc3881a6..1e7bc91d6 100644
--- a/VolumeControl.Log/ExceptionMessageHelper.cs
+++ b/VolumeControl.Log/Helpers/ExceptionMessageHelper.cs
@@ -3,7 +3,7 @@
using System.Runtime.InteropServices;
using System.Text;
-namespace VolumeControl.Log
+namespace VolumeControl.Log.Helpers
{
///
/// Helper methods for converting exceptions into nicely formatted strings.
@@ -144,7 +144,7 @@ public static string MakeExceptionMessage(Exception exception, string linePrefix
var value = propInfo.GetValue(exception);
// skip properties with null/empty values
- if (value == null || (value is string s && s.Length == 0))
+ if (value == null || value is string s && s.Length == 0)
continue;
sb.Append($"{tabPrefix}\"{propInfo.Name}\": \"{value}\"{endline}");
@@ -155,9 +155,7 @@ public static string MakeExceptionMessage(Exception exception, string linePrefix
// Source:
if (includedParts.HasFlag(MessageParts.Source) && exception.Source != null)
- {
sb.Append($"{tabPrefix}\"Source\": \"{exception.Source}\"{endline}");
- }
// TargetSite:
if (includedParts.HasFlag(MessageParts.TargetSite))
@@ -213,9 +211,7 @@ public static string MakeExceptionMessage(Exception exception, string linePrefix
// InnerException:
if (includedParts.HasFlag(MessageParts.InnerException) && exception.InnerException != null)
- {
sb.Append($"{tabPrefix}\"InnerException\": {MakeExceptionMessage(exception.InnerException, tabPrefix, endline, tabLength, includedParts)}{endline}");
- }
// closing bracket
sb.Append(linePrefix + '}');
diff --git a/VolumeControl.Log/Helpers/ThreadedActionQueue.cs b/VolumeControl.Log/Helpers/ThreadedActionQueue.cs
new file mode 100644
index 000000000..7b1b396bb
--- /dev/null
+++ b/VolumeControl.Log/Helpers/ThreadedActionQueue.cs
@@ -0,0 +1,115 @@
+namespace VolumeControl.Log.Helpers
+{
+ ///
+ /// Manages a background thread to execute queued actions.
+ ///
+ public abstract class ThreadedActionQueue : IDisposable
+ {
+ #region Constructor
+ ///
+ /// Instantiates a new instance.
+ ///
+ protected ThreadedActionQueue()
+ {
+ _thread = new Thread(new ThreadStart(ProcessQueue)) { IsBackground = true };
+ // this is performed from a bg thread, to ensure the queue is serviced from a single thread
+ _thread.Start();
+ }
+ #endregion Constructor
+
+ #region Fields
+ private readonly Queue _queue = new();
+ private readonly ManualResetEvent _itemAddedSignal = new(false);
+ private readonly ManualResetEvent _terminateSignal = new(false);
+ private readonly ManualResetEvent _isWaitingSignal = new(false);
+ private readonly Thread _thread;
+ #endregion Fields
+
+ #region Methods
+
+ #region (Private) ProcessQueue
+ private void ProcessQueue()
+ {
+ var waitHandles = new WaitHandle[] { _itemAddedSignal, _terminateSignal };
+ Queue queueCopy;
+ while (true)
+ {
+ _isWaitingSignal.Set();
+ int i = WaitHandle.WaitAny(waitHandles);
+
+ if (i == 1) return; //< terminate was signaled
+
+ _isWaitingSignal.Reset();
+ _itemAddedSignal.Reset();
+
+ lock (_queue)
+ {
+ queueCopy = new(_queue);
+ _queue.Clear();
+ }
+
+ foreach (var action in queueCopy)
+ {
+ action.Invoke();
+ }
+ }
+ }
+ #endregion (Private) ProcessQueue
+
+ #region (Protected) Enqueue
+ ///
+ /// Adds the specified to the queue.
+ ///
+ /// A delegate to add to the action queue.
+ protected void Enqueue(Action action)
+ {
+ lock (_queue)
+ {
+ _queue.Enqueue(action);
+ }
+ _itemAddedSignal.Set();
+ }
+ #endregion (Protected) Enqueue
+
+ #region Flush
+ ///
+ /// Blocks the caller until the background thread has finished processing the queue.
+ ///
+ public void Flush()
+ => _isWaitingSignal.WaitOne();
+ ///
+ /// Blocks the caller until the background thread has finished processing the queue, or until the specified timeout has elapsed.
+ ///
+ /// How many milliseconds to wait before returning early, or (-1) to wait indefinitely.
+ /// when the queue was successfully flushed before timing out; otherwise .
+ public bool Flush(int timeoutMs)
+ => _isWaitingSignal.WaitOne(timeoutMs);
+ ///
+ /// Blocks the caller until the background thread has finished processing the queue, or until the specified timeout has elapsed.
+ ///
+ /// How many milliseconds to wait before returning early, or (-1) to wait indefinitely.
+ /// to exit the synchronization domain for the context before waiting & reacquire it afterwards; otherwise, . For more information, see this StackOverflow answer .
+ /// when the queue was successfully flushed before timing out; otherwise .
+ public bool Flush(int timeoutMs, bool exitContext)
+ => _isWaitingSignal.WaitOne(timeoutMs, exitContext);
+ #endregion Flush
+
+ #endregion Methods
+
+ #region IDisposable Implementation
+ ///
+ /// Calls .
+ ///
+ ~ThreadedActionQueue() => Dispose();
+ ///
+ /// Terminates the background thread. Does not flush the queue.
+ ///
+ public void Dispose()
+ {
+ _terminateSignal.Set();
+ _thread.Join();
+ GC.SuppressFinalize(this);
+ }
+ #endregion IDisposable Implementation
+ }
+}
diff --git a/VolumeControl.Log/Interfaces/IEndpointWriter.cs b/VolumeControl.Log/Interfaces/IEndpointWriter.cs
new file mode 100644
index 000000000..4e9c6f919
--- /dev/null
+++ b/VolumeControl.Log/Interfaces/IEndpointWriter.cs
@@ -0,0 +1,57 @@
+namespace VolumeControl.Log.Interfaces
+{
+ ///
+ /// Represents an endpoint that text can be written to.
+ ///
+ public interface IEndpointWriter
+ {
+ #region Properties
+ ///
+ /// Gets or sets whether this endpoint can be written to.
+ ///
+ bool IsWritingEnabled { get; set; }
+ #endregion Properties
+
+ #region Events
+ ///
+ /// Occurs when the endpoint is about to be enabled or disabled for any reason.
+ ///
+ ///
+ /// The boolean argument is the incoming state.
+ ///
+ event EventHandler? EnabledChanging;
+ ///
+ /// Occurs when the endpoint is enabled or disabled for any reason.
+ ///
+ ///
+ /// The boolean argument is the new state.
+ ///
+ event EventHandler? EnabledChanged;
+ #endregion Events
+
+ #region Methods
+ ///
+ /// Gets a object for writing to this endpoint.
+ ///
+ ///
+ /// The caller is responsible for disposing of the stream.
+ ///
+ /// A instance for this endpoint when it is enabled; otherwise .
+ TextWriter GetTextWriter();
+ ///
+ /// Writes the specified to the endpoint.
+ ///
+ /// A string to write to the endpoint.
+ void Write(string @string);
+ ///
+ /// Writes the specified to the endpoint, followed by a line break.
+ ///
+ /// A string to write to the endpoint.
+ void WriteLine(string @string);
+ ///
+ /// Clears the endpoint's contents, leaving it blank.
+ ///
+ void Reset();
+ #endregion Methods
+ }
+}
\ No newline at end of file
diff --git a/VolumeControl.Log/ILogWriter.cs b/VolumeControl.Log/Interfaces/ILogWriter.cs
similarity index 98%
rename from VolumeControl.Log/ILogWriter.cs
rename to VolumeControl.Log/Interfaces/ILogWriter.cs
index f76049e78..e58906e92 100644
--- a/VolumeControl.Log/ILogWriter.cs
+++ b/VolumeControl.Log/Interfaces/ILogWriter.cs
@@ -1,6 +1,4 @@
-using VolumeControl.Log.Enum;
-
-namespace VolumeControl.Log
+namespace VolumeControl.Log.Interfaces
{
///
/// Represents a log writer instance.
diff --git a/VolumeControl.Log/LogMessage.cs b/VolumeControl.Log/LogMessage.cs
index 06ab4feca..7b60be9ee 100644
--- a/VolumeControl.Log/LogMessage.cs
+++ b/VolumeControl.Log/LogMessage.cs
@@ -1,6 +1,4 @@
-using VolumeControl.Log.Enum;
-
-namespace VolumeControl.Log
+namespace VolumeControl.Log
{
///
/// Represents a message to be written to the log.
@@ -11,7 +9,7 @@ public sealed class LogMessage
///
/// Creates a new instance with the specified .
///
- /// The of this message.
+ /// The of this message.
/// The lines in this message.
public LogMessage(EventType eventType, params object?[] lines)
{
@@ -21,7 +19,7 @@ public LogMessage(EventType eventType, params object?[] lines)
///
/// Creates a new empty instance with the specified .
///
- /// The of this message.
+ /// The of this message.
public LogMessage(EventType eventType)
{
EventType = eventType;
diff --git a/VolumeControl.Log/SettingsInterface.cs b/VolumeControl.Log/SettingsInterface.cs
deleted file mode 100644
index e1941be96..000000000
--- a/VolumeControl.Log/SettingsInterface.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using AppConfig;
-using System.ComponentModel;
-using VolumeControl.Log.Enum;
-
-namespace VolumeControl.Log
-{
- ///
- /// Provides a more convenient way to access the program configuration without having access to the VolumeControl.Core namespace.
- /// See the property.
- ///
- internal class SettingsInterface : INotifyPropertyChanged
- {
- #region Constructor
- private SettingsInterface() => Settings.PropertyChanged += this.HandleSettingsPropertyChanged;
- #endregion Constructor
-
- #region Fields
- private static readonly string[] _propertyNames = typeof(SettingsInterface).GetProperties().Where(p => p.CanRead && p.CanWrite).Select(p => p.Name).ToArray();
- #endregion Fields
-
- #region Events
- public event PropertyChangedEventHandler? PropertyChanged;
- private void NotifyPropertyChanged(object? sender, PropertyChangedEventArgs e) => PropertyChanged?.Invoke(sender, e);
- private void HandleSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e)
- {
- if (_propertyNames.Any(n => n.Equals(e.PropertyName, StringComparison.Ordinal)))
- {
- this.NotifyPropertyChanged(sender, e);
- }
- }
- #endregion Events
-
- #region Properties
- ///
- /// Default instance.
- ///
- public static SettingsInterface Default { get; } = new();
- private static Configuration Settings => Configuration.DefaultInstance;
-
- #region Settings
- public bool EnableLogging
- {
- get => (bool)Settings[nameof(this.EnableLogging)]!;
- set => Settings[nameof(this.EnableLogging)] = value;
- }
- public string LogPath
- {
- get => (string)Settings[nameof(this.LogPath)]!;
- set => Settings[nameof(this.LogPath)] = value;
- }
- public EventType LogFilter
- {
- get => (EventType)Settings[nameof(this.LogFilter)]!;
- set => Settings[nameof(this.LogFilter)] = value;
- }
- public bool LogClearOnInitialize
- {
- get => (bool)Settings[nameof(this.LogClearOnInitialize)]!;
- set => Settings[nameof(this.LogClearOnInitialize)] = value;
- }
- #endregion Settings
- #endregion Properties
- }
-}
diff --git a/VolumeControl.Log/ThreadedLogger.cs b/VolumeControl.Log/ThreadedLogger.cs
deleted file mode 100644
index 48ec3872d..000000000
--- a/VolumeControl.Log/ThreadedLogger.cs
+++ /dev/null
@@ -1,110 +0,0 @@
-namespace VolumeControl.Log
-{
- ///
- /// Manages a background thread to write queued log messages. The queue can be disabled at runtime to switch between asynchronous and synchronous operation.
- ///
- public abstract class ThreadedLogger : IDisposable
- {
- #region Constructor
- ///
- /// Instantiates a new instance.
- ///
- public ThreadedLogger()
- {
- _thread = new Thread(new ThreadStart(ProcessQueue)) { IsBackground = true };
- // this is performed from a bg thread, to ensure the queue is serviced from a single thread
- _thread.Start();
- }
- #endregion Constructor
-
- #region Fields
- private readonly Queue _queue = new();
- private readonly ManualResetEvent _itemAddedSignal = new(false);
- private readonly ManualResetEvent _terminateSignal = new(false);
- private readonly ManualResetEvent _isWaitingSignal = new(false);
- private readonly Thread _thread;
- #endregion Fields
-
- #region Thread Method
- private void ProcessQueue()
- {
- var waitHandles = new WaitHandle[]
- {
- _itemAddedSignal,
- _terminateSignal
- };
- Queue queueCopy;
- while (true)
- {
- _isWaitingSignal.Set();
- int i = WaitHandle.WaitAny(waitHandles);
- _isWaitingSignal.Reset();
-
- if (i == 1) return; //< terminate was signaled
-
- _itemAddedSignal.Reset();
-
- lock (_queue)
- {
- queueCopy = new Queue(_queue);
- _queue.Clear();
- }
-
- foreach (var log in queueCopy)
- {
- log();
- }
- }
- }
- #endregion Thread Method
-
- #region Methods
- ///
- /// Writes the specified to the log endpoint.
- ///
- /// The log message instance to write to the endpoint.
- protected abstract void WriteLogMessage(LogMessage message);
- ///
- /// Adds the specified to the message queue when UseAsyncQueue is ; otherwise writes the message synchronously.
- ///
- /// A log message instance.
- protected void LogMessage(LogMessage message)
- {
- message.RemoveNullLines(); //< remove all null lines
- if (message.IsEmpty) return;
-
- lock (_queue)
- {
- _queue.Enqueue(() => WriteLogMessage(message));
- }
- _itemAddedSignal.Set();
- }
- ///
- /// Flushes the message queue.
- ///
- public void Flush()
- {
- _isWaitingSignal.WaitOne();
- }
- #endregion Methods
-
- #region IDisposable Implementation
- bool disposed = false;
- ///
- /// Disposes of this instance, if it hasn't already been disposed.
- ///
- ~ThreadedLogger()
- {
- if (!disposed) Dispose();
- }
- ///
- public void Dispose()
- {
- disposed = true;
- _terminateSignal.Set();
- _thread.Join();
- GC.SuppressFinalize(this);
- }
- #endregion IDisposable Implementation
- }
-}
diff --git a/VolumeControl.Log/VolumeControl.Log.csproj b/VolumeControl.Log/VolumeControl.Log.csproj
index 6d59f3a64..de69e99e0 100644
--- a/VolumeControl.Log/VolumeControl.Log.csproj
+++ b/VolumeControl.Log/VolumeControl.Log.csproj
@@ -14,17 +14,6 @@
snupkg
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- All
-
-
-
-
diff --git a/VolumeControl.WPF/Behaviors/DisableMouseWheelBehavior.cs b/VolumeControl.WPF/Behaviors/DisableMouseWheelBehavior.cs
new file mode 100644
index 000000000..ac69b0a4e
--- /dev/null
+++ b/VolumeControl.WPF/Behaviors/DisableMouseWheelBehavior.cs
@@ -0,0 +1,29 @@
+using Microsoft.Xaml.Behaviors;
+using System.Windows.Controls;
+
+namespace VolumeControl.WPF.Behaviors
+{
+ ///
+ /// Behavior that prevent the mouse wheel from triggering events.
+ ///
+ public class DisableMouseWheelBehavior : Behavior
+ {
+ #region Behavior Method Overrides
+ protected override void OnAttached()
+ {
+ base.OnAttached();
+ AssociatedObject.PreviewMouseWheel += this.AssociatedObject_PreviewMouseWheel;
+ }
+ protected override void OnDetaching()
+ {
+ base.OnDetaching();
+ AssociatedObject.PreviewMouseWheel -= this.AssociatedObject_PreviewMouseWheel;
+ }
+ #endregion Behavior Method Overrides
+
+ #region EventHandlers
+ private void AssociatedObject_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
+ => e.Handled = true;
+ #endregion EventHandlers
+ }
+}
diff --git a/VolumeControl/App.xaml b/VolumeControl/App.xaml
index 347a53068..a1c014699 100644
--- a/VolumeControl/App.xaml
+++ b/VolumeControl/App.xaml
@@ -755,8 +755,8 @@
@@ -900,17 +900,18 @@
+
diff --git a/VolumeControl/App.xaml.cs b/VolumeControl/App.xaml.cs
index f855771b3..f318ade8e 100644
--- a/VolumeControl/App.xaml.cs
+++ b/VolumeControl/App.xaml.cs
@@ -7,6 +7,7 @@
using System.Windows.Controls;
using System.Windows.Input;
using VolumeControl.Controls;
+using VolumeControl.Core;
using VolumeControl.Helpers;
using VolumeControl.Log;
using VolumeControl.ViewModels;
@@ -39,6 +40,16 @@ public App()
TrayIcon.ShowClicked += this.HandleTrayIconClick;
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 });
+ };
+ TrayIcon.OpenLogClicked += (s, e) =>
+ {
+ Process.Start(new ProcessStartInfo(Config.Default.LogPath) { UseShellExecute = true });
+ };
+
TrayIcon.OpenLocationClicked += (s, e) =>
{
OpenFolderAndSelectItem(Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), Path.ChangeExtension(AppDomain.CurrentDomain.FriendlyName, ".exe"))));
diff --git a/VolumeControl/Controls/VolumeControlNotifyIcon.cs b/VolumeControl/Controls/VolumeControlNotifyIcon.cs
index f7eb3d392..92b9fed4f 100644
--- a/VolumeControl/Controls/VolumeControlNotifyIcon.cs
+++ b/VolumeControl/Controls/VolumeControlNotifyIcon.cs
@@ -16,6 +16,11 @@ public VolumeControlNotifyIcon(Query queryMainWindowVisible)
{ // TOOLSTRIP:
new System.Windows.Forms.ToolStripButton(GetShowText(), Properties.Resources.foreground, this.HandleShowHideClick),
new System.Windows.Forms.ToolStripButton(GetBringToFrontText(), Properties.Resources.bringtofront, this.HandleBringToFrontClick),
+ new System.Windows.Forms.ToolStripSeparator(),
+
+ new System.Windows.Forms.ToolStripButton("Open Config", Properties.Resources.codefile, this.HandleOpenConfigClicked),
+ new System.Windows.Forms.ToolStripButton("Open Log", Properties.Resources.textfile, this.HandleOpenLogClicked),
+
new System.Windows.Forms.ToolStripSeparator(),
new System.Windows.Forms.ToolStripButton(GetOpenAppDataText(), Properties.Resources.file_inverted, this.HandleOpenAppDataClick),
new System.Windows.Forms.ToolStripButton(GetOpenLocationText(), Properties.Resources.file, this.HandleOpenLocationClick),
@@ -44,6 +49,7 @@ public VolumeControlNotifyIcon(Query queryMainWindowVisible)
#endregion TranslationGetters
#region Events
+
private void Handle_CurrentLanguageChanged(object? sender, CurrentLanguageChangedEventArgs e)
{
this.Items[_idx_ShowHide].Text = GetShowHideText(_mainWindowVisibilityChecker());
@@ -82,10 +88,16 @@ private void HandleShowHideClick(object? sender, EventArgs e)
private void HandleOpenLocationClick(object? sender, EventArgs e) => OpenLocationClicked?.Invoke(sender, e);
private void HandleOpenAppDataClick(object? sender, EventArgs e) => OpenAppDataClicked?.Invoke(sender, e);
private void HandleCloseClicked(object? sender, EventArgs e) => CloseClicked?.Invoke(sender, e);
+ private void HandleOpenConfigClicked(object? sender, EventArgs e) => OpenConfigClicked?.Invoke(sender, e);
+ private void HandleOpenLogClicked(object? sender, EventArgs e) => OpenLogClicked?.Invoke(sender, e);
public event EventHandler? ShowClicked;
public event EventHandler? HideClicked;
public event EventHandler? BringToFrontClicked;
+
+ public event EventHandler? OpenConfigClicked;
+ public event EventHandler? OpenLogClicked;
+
public event EventHandler? OpenLocationClicked;
public event EventHandler? OpenAppDataClicked;
public event EventHandler? CloseClicked;
diff --git a/VolumeControl/Helpers/AddonLoader.cs b/VolumeControl/Helpers/AddonLoader.cs
new file mode 100644
index 000000000..dad10c70a
--- /dev/null
+++ b/VolumeControl/Helpers/AddonLoader.cs
@@ -0,0 +1,255 @@
+using CodingSeb.Localization.Loaders;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using VolumeControl.Core.Input;
+using VolumeControl.Log;
+using VolumeControl.TypeExtensions;
+using VolumeControl.ViewModels;
+
+namespace VolumeControl.Helpers
+{
+ public class AddonLoader
+ {
+ #region Constructor
+ public AddonLoader()
+ {
+ ManifestResourceLoader = new((JsonFileLoader?)LocalizationLoader.FileLanguageLoaders.FirstOrDefault(loader =>
+ loader.GetType().IsAssignableTo(typeof(JsonFileLoader))) ?? new JsonFileLoader());
+ }
+ #endregion Constructor
+
+ #region (class) ManifestResourceLocalizationLoader
+ class ManifestResourceLocalizationLoader
+ {
+ #region Constructor
+ public ManifestResourceLocalizationLoader(JsonFileLoader jsonFileLoader)
+ {
+ JsonFileLoader = jsonFileLoader;
+ }
+ #endregion Constructor
+
+ #region Fields
+ private readonly JsonFileLoader JsonFileLoader;
+ #endregion Fields
+
+ #region Methods
+
+ #region LoadFromStream
+ ///
+ /// Loads all translations defined in Json format from the specified .
+ ///
+ ///
+ /// The caller is responsible for disposing of the .
+ ///
+ /// The stream to load translations from.
+ /// The loader to use for loading translations from the string.
+ /// Optional source file name.
+ public void LoadFromStream(Stream stream, LocalizationLoader loader, string resourceName)
+ {
+ using var reader = new StreamReader(stream, System.Text.Encoding.UTF8, true, leaveOpen: true);
+ string content = reader.ReadToEnd();
+ LoadFromString(content, loader, resourceName);
+ }
+ #endregion LoadFromStream
+
+ #region LoadFromString
+ ///
+ /// Load all translations defined in Json format from the specified .
+ ///
+ /// String to load serialized Json format translations from.
+ /// The loader to use for loading translations from the string.
+ /// Optional source file name.
+ public void LoadFromString(string jsonString, LocalizationLoader loader, string sourceFileName = "")
+ {
+ JObject root = (JObject)JsonConvert.DeserializeObject(jsonString)!;
+
+ root.Properties().ToList()
+ .ForEach(property => ParseSubElement(property, new Stack(), loader, sourceFileName));
+ }
+ #endregion LoadFromString
+
+ #region ParseSubElement
+ private void ParseSubElement(JProperty property, Stack textId, LocalizationLoader loader, string source)
+ {
+ switch (property.Value.Type)
+ {
+ case JTokenType.Object:
+ textId.Push(property.Name);
+ ((JObject)property.Value).Properties().ToList()
+ .ForEach(subProperty => ParseSubElement(subProperty, textId, loader, source));
+ textId.Pop();
+ break;
+ case JTokenType.String:
+
+ if (JsonFileLoader.LangIdDecoding == JsonFileLoaderLangIdDecoding.InFileNameBeforeExtension)
+ {
+ textId.Push(property.Name);
+ loader.AddTranslation(
+ JsonFileLoader.LabelPathRootPrefix + string.Join(JsonFileLoader.LabelPathSeparator, textId.Reverse()) + JsonFileLoader.LabelPathSuffix,
+ Path.GetExtension(Regex.Replace(source, @"\.loc\.json", "")).Replace(".", ""),
+ property.Value.ToString(),
+ source);
+ textId.Pop();
+ }
+ else if (JsonFileLoader.LangIdDecoding == JsonFileLoaderLangIdDecoding.DirectoryName)
+ {
+ textId.Push(property.Name);
+ loader.AddTranslation(JsonFileLoader.LabelPathRootPrefix + string.Join(JsonFileLoader.LabelPathSeparator, textId.Reverse()) + JsonFileLoader.LabelPathSuffix,
+ Path.GetDirectoryName(source),
+ property.Value.ToString(),
+ source);
+ textId.Pop();
+ }
+ else
+ {
+ loader.AddTranslation(JsonFileLoader.LabelPathRootPrefix + string.Join(JsonFileLoader.LabelPathSeparator, textId.Reverse()) + JsonFileLoader.LabelPathSuffix, property.Name, property.Value.ToString(), source);
+ }
+ break;
+ default:
+ throw new FormatException($"Invalid format in Json language file for property [{property.Name}]");
+ }
+ }
+ #endregion ParseSubElement
+
+ #endregion Methods
+ }
+ #endregion (class) ManifestResourceLocalizationLoader
+
+ #region Fields
+ private static readonly Regex TranslationConfigAddonRegex = new(@"([a-z]{2}(?:-\w+){0,1}\.loc\.(?:json|yaml|yml))$", RegexOptions.Compiled);
+ private readonly ManifestResourceLocalizationLoader ManifestResourceLoader;
+ #endregion Fields
+
+ #region Properties
+ private static LocalizationLoader LocalizationLoader => LocalizationLoader.Instance;
+ #endregion Properties
+
+ #region Methods
+
+ #region LoadTranslations
+ private void LoadTranslations(Assembly assembly)
+ {
+ foreach (var resourceName in assembly.GetManifestResourceNames())
+ {
+ // filter out invalid names
+ var match = TranslationConfigAddonRegex.Match(resourceName);
+ if (!match.Success)
+ {
+ FLog.Debug($"[AddonLoader] Embedded resource \"{resourceName}\" does not have a valid name for a translation config, so it was not loaded.");
+ continue;
+ }
+
+ // load translations
+ ManifestResourceLoader.LoadFromStream(assembly.GetManifestResourceStream(resourceName)!, LocalizationLoader, resourceName);
+ }
+ }
+ #endregion LoadTranslations
+
+ #region LoadAddons
+ 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
+ LoadTranslations(hotkeyActionsAssembly);
+
+ // load default template providers
+ HotkeyActionAddonLoader.LoadProviders(ref templateProviderManager,
+ GetDataTemplateProviderTypes(Assembly.Load($"{nameof(VolumeControl)}.{nameof(SDK)}")));
+
+ // load default actions
+ inst.HotkeyAPI.HotkeyManager.HotkeyActionManager.AddActionDefinitions(HotkeyActionAddonLoader.LoadActions(templateProviderManager,
+ GetActionGroupTypes(hotkeyActionsAssembly)));
+
+ // load custom addons
+ inst.AddonDirectories.ForEach(dir =>
+ {
+ if (Directory.Exists(dir))
+ {
+ 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);
+ FLog.Debug($"[AddonLoader] Found addon DLL \"{fileName}\"");
+
+ var versionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(dllPath);
+ if (versionInfo == null)
+ {
+ FLog.Debug($"[AddonLoader] Addon DLL \"{fileName}\" does not have any version information.");
+ }
+ else
+ {
+ var authorName = versionInfo.CompanyName;
+
+ if (versionInfo.IsDebug)
+ {
+ FLog.Warning(
+ $"[AddonLoader] Addon DLL was built in DEBUG configuration \"{dllPath}\"!",
+ $" Contact {versionInfo.CompanyName ?? "the addon author"}");
+ }
+ if (FLog.FilterEventType(EventType.DEBUG))
+ {
+
+ FLog.Debug($"[AddonLoader] Found addon DLL \"{versionInfo.FileName}\":", versionInfo.ToString());
+ }
+ }
+
+ // try loading the assembly
+ try
+ {
+ var asm = Assembly.LoadFrom(dllPath);
+ var assemblyName = asm.FullName ?? dllPath;
+ var exportedTypes = asm.GetExportedTypes();
+ asm = null;
+
+ FLog.Debug($"[AddonLoader] \"{assemblyName}\" exports {exportedTypes.Length} types.");
+
+ // 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);
+ }
+ }
+ }
+ else
+ {
+ FLog.Trace($"[AddonLoader] Addon directory \"{dir}\" doesn't exist.");
+ }
+ });
+ }
+ #endregion LoadAddons
+
+ #region GetActionGroupTypes
+ ///
+ /// Gets the hotkey action types from the specified .
+ ///
+ private static Type[] GetActionGroupTypes(Assembly assembly)
+ => assembly.GetExportedTypes().Where(type => type.GetCustomAttribute() != null).ToArray();
+ #endregion GetActionGroupTypes
+
+ #region GetDataTemplateProviderTypes
+ ///
+ /// Gets the data template provider types from the specified .
+ ///
+ private static Type[] GetDataTemplateProviderTypes(Assembly assembly)
+ => assembly.GetExportedTypes().Where(type => type.GetCustomAttribute() != null).ToArray();
+ #endregion GetDataTemplateProviderTypes
+
+ #endregion Methods
+ }
+}
diff --git a/VolumeControl/Helpers/LocalizationHelper.cs b/VolumeControl/Helpers/LocalizationHelper.cs
index 54eb54770..87bf0f4e6 100644
--- a/VolumeControl/Helpers/LocalizationHelper.cs
+++ b/VolumeControl/Helpers/LocalizationHelper.cs
@@ -8,6 +8,7 @@
using System.Text.RegularExpressions;
using VolumeControl.Core;
using VolumeControl.Log;
+using VolumeControl.Log.Interfaces;
using VolumeControl.TypeExtensions;
namespace VolumeControl.Helpers
@@ -22,9 +23,6 @@ public LocalizationHelper(bool overwriteExistingConfigs, ILogWriter? log = null)
_initialized = true;
- _ = FileLoaders.AddIfUnique(new JsonFileLoader());
- _ = FileLoaders.AddIfUnique(new YamlFileLoader());
-
if (Settings.CreateDefaultTranslationFiles) //< never create default files when this (and this alone) is false!
CreateDefaultFiles(overwriteExistingConfigs);
@@ -51,10 +49,9 @@ public LocalizationHelper(bool overwriteExistingConfigs, ILogWriter? log = null)
#region Properties
private static Config Settings => Config.Default;
private static LocalizationLoader Loader => LocalizationLoader.Instance;
- private static List FileLoaders => Loader.FileLanguageLoaders;
private static string DefaultPath { get; } = Path.Combine(PathFinder.ApplicationAppDataPath, "Localization");
private static Loc Loc => Loc.Instance;
- private static readonly Regex LanguageConfigNameRegex = new(@"([a-z]{2}\.loc\.(?:json|yaml|yml))", RegexOptions.Compiled);
+ private static readonly Regex LanguageConfigNameRegex = new(@"([a-z]{2}\.loc\.(?:json|yaml|yml))$", RegexOptions.Compiled);
#endregion Properties
#region Methods
@@ -82,7 +79,7 @@ public static void ReloadLanguageConfigs(ILogWriter? log)
private static void LoadTranslationsFromDirectory(string path, ILogWriter? log)
{
- bool showTraceLogMessages = log?.FilterEventType(Log.Enum.EventType.TRACE) ?? false;
+ bool showTraceLogMessages = log?.FilterEventType(EventType.TRACE) ?? false;
if (Directory.Exists(path))
{
if (showTraceLogMessages)
diff --git a/VolumeControl/Mixer.xaml b/VolumeControl/Mixer.xaml
index 37b169aba..fea155035 100644
--- a/VolumeControl/Mixer.xaml
+++ b/VolumeControl/Mixer.xaml
@@ -15,6 +15,7 @@
xmlns:loc="clr-namespace:CodingSeb.Localization;assembly=CodingSeb.Localization"
xmlns:local="clr-namespace:VolumeControl"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:rsc="clr-namespace:VolumeControl.Properties"
xmlns:sys="clr-namespace:System;assembly=netstandard"
xmlns:vm="clr-namespace:VolumeControl.ViewModels"
xmlns:wpf="clr-namespace:VolumeControl.WPF;assembly=VolumeControl.WPF"
@@ -942,19 +943,22 @@
ItemsSource="{StaticResource KeyOptions}"
SelectedValue="{Binding Hotkey.Key, UpdateSourceTrigger=PropertyChanged}"
SelectedValuePath="Key"
- Style="{StaticResource ComboBoxStyle}" />
+ Style="{StaticResource ComboBoxStyle}">
+
+
+
+
+ DefaultText='Click and hold with the mouse, then press a key to select it. Registered hotkeys can still be triggered.'}" />
diff --git a/VolumeControl/Program.cs b/VolumeControl/Program.cs
index 0a3457574..aa60267b1 100644
--- a/VolumeControl/Program.cs
+++ b/VolumeControl/Program.cs
@@ -14,7 +14,7 @@
using VolumeControl.Core;
using VolumeControl.Helpers;
using VolumeControl.Log;
-using VolumeControl.Log.Enum;
+using VolumeControl.Log.Endpoints;
using VolumeControl.TypeExtensions;
namespace VolumeControl
@@ -341,7 +341,7 @@ private static int Main_Impl(string[] args)
else Console.Out.WriteLine($"Acquired mutex \"{appMutexName}\".");
// Initialize the log now that we aren't potentially overwriting another instance's log file
- FLog.Initialize();
+ FLog.Initialize(Settings.LogPath, Settings.EnableLogging, Settings.LogFilter, Settings.LogClearOnInitialize);
#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;
diff --git a/VolumeControl/Properties/Resources.Designer.cs b/VolumeControl/Properties/Resources.Designer.cs
index 453614b14..67e587513 100644
--- a/VolumeControl/Properties/Resources.Designer.cs
+++ b/VolumeControl/Properties/Resources.Designer.cs
@@ -90,6 +90,16 @@ internal static System.Drawing.Bitmap bringtofront {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap codefile {
+ get {
+ object obj = ResourceManager.GetObject("codefile", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
@@ -190,6 +200,16 @@ internal static System.Drawing.Icon idle {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap keyboard_key {
+ get {
+ object obj = ResourceManager.GetObject("keyboard_key", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Byte[].
///
@@ -220,6 +240,16 @@ internal static System.Drawing.Bitmap reload_white {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap textfile {
+ get {
+ object obj = ResourceManager.GetObject("textfile", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
diff --git a/VolumeControl/Properties/Resources.resx b/VolumeControl/Properties/Resources.resx
index 884ab5cbe..93d8b3e29 100644
--- a/VolumeControl/Properties/Resources.resx
+++ b/VolumeControl/Properties/Resources.resx
@@ -127,6 +127,9 @@
..\Resources\images\bringtofront.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\images\codefile.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
..\Resources\images\file.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
@@ -157,6 +160,9 @@
..\Resources\icons\idle.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\images\keyboard-key.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
..\Resources\LICENSE;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
@@ -166,6 +172,9 @@
..\Resources\images\reload-white.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\images\textfile.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
..\Resources\X.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
diff --git a/VolumeControl/Resources/images/codefile.png b/VolumeControl/Resources/images/codefile.png
new file mode 100644
index 000000000..3d448448c
Binary files /dev/null and b/VolumeControl/Resources/images/codefile.png differ
diff --git a/VolumeControl/Resources/images/file-dark.png b/VolumeControl/Resources/images/file-dark.png
new file mode 100644
index 000000000..59de00969
Binary files /dev/null and b/VolumeControl/Resources/images/file-dark.png differ
diff --git a/VolumeControl/Resources/images/textfile.png b/VolumeControl/Resources/images/textfile.png
new file mode 100644
index 000000000..a010f5ca7
Binary files /dev/null and b/VolumeControl/Resources/images/textfile.png differ
diff --git a/VolumeControl/ViewModels/AudioDeviceManagerVM.cs b/VolumeControl/ViewModels/AudioDeviceManagerVM.cs
index d0bd7edbf..66c1be556 100644
--- a/VolumeControl/ViewModels/AudioDeviceManagerVM.cs
+++ b/VolumeControl/ViewModels/AudioDeviceManagerVM.cs
@@ -23,7 +23,7 @@ public sealed class AudioDeviceManagerVM : DependencyObject, INotifyPropertyChan
{
public AudioDeviceManagerVM()
{
- var doDebugLogging = FLog.Log.FilterEventType(Log.Enum.EventType.DEBUG);
+ var doDebugLogging = FLog.Log.FilterEventType(EventType.DEBUG);
if (doDebugLogging) FLog.Debug("Started initializing CoreAudio APIs.");
// # INIT DEVICES #
diff --git a/VolumeControl/ViewModels/LogFilterFlagsVM.cs b/VolumeControl/ViewModels/LogFilterFlagsVM.cs
index 602be79d6..f217dbf61 100644
--- a/VolumeControl/ViewModels/LogFilterFlagsVM.cs
+++ b/VolumeControl/ViewModels/LogFilterFlagsVM.cs
@@ -1,14 +1,14 @@
using System;
using System.ComponentModel;
using VolumeControl.Core;
-using VolumeControl.Log.Enum;
+using VolumeControl.Log;
namespace VolumeControl.ViewModels
{
public class LogFilterFlagsVM : FlagsEnumVM
{
#region Constructor
- public LogFilterFlagsVM() : base(Settings.LogFilter, EventType.NONE | EventType.CRITICAL)
+ public LogFilterFlagsVM() : base(Settings.LogFilter, EventType.NONE | EventType.CRITICAL | EventType.FATAL, nameof(EventType.ALL))
{
Settings.PropertyChanged += this.Settings_PropertyChanged;
base.StateChanged += this.LogFilterFlagsVM_StateChanged;
diff --git a/VolumeControl/ViewModels/VolumeControlVM.cs b/VolumeControl/ViewModels/VolumeControlVM.cs
index 11a02f36a..01b7b8479 100644
--- a/VolumeControl/ViewModels/VolumeControlVM.cs
+++ b/VolumeControl/ViewModels/VolumeControlVM.cs
@@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Reflection;
using System.Windows;
+using System.Windows.Media;
using VolumeControl.Core;
using VolumeControl.Core.Input;
using VolumeControl.Core.Input.Actions;
@@ -42,74 +42,9 @@ public VolumeControlVM() : base()
// Initialize the addon API
Initializer.Initialize(this.AudioAPI.AudioDeviceManager, this.AudioAPI.AudioDeviceSelector, this.AudioAPI.AudioSessionManager, this.AudioAPI.AudioSessionMultiSelector, this.HotkeyAPI.HotkeyManager, this.MainWindowHandle, Config.Default!);
- // create a template provider manager
- var templateProviderManager = new TemplateProviderManager();
- HotkeyActionAddonLoader.LoadProviders(ref templateProviderManager, GetDefaultDataTemplateProviderTypes());
-
- var actions = HotkeyActionAddonLoader.LoadActions(templateProviderManager, GetDefaultActionGroupTypes());
-
- HotkeyAPI.HotkeyManager.HotkeyActionManager.AddActionDefinitions(actions);
-
- AddonDirectories.ForEach(dir =>
- {
- if (Directory.Exists(dir))
- {
- 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);
- FLog.Debug($"[AddonLoader] Found addon DLL \"{fileName}\"");
-
- var versionInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(dllPath);
- if (versionInfo == null)
- {
- FLog.Debug($"[AddonLoader] Addon DLL \"{fileName}\" does not have any version information.");
- }
- else
- {
- var authorName = versionInfo.CompanyName;
-
- if (versionInfo.IsDebug)
- {
- FLog.Warning(
- $"[AddonLoader] Addon DLL was built in DEBUG configuration \"{dllPath}\"!",
- $" Contact {versionInfo.CompanyName ?? "the addon author"}");
- }
- if (FLog.FilterEventType(Log.Enum.EventType.DEBUG))
- {
-
- FLog.Debug($"[AddonLoader] Found addon DLL \"{versionInfo.FileName}\":", versionInfo.ToString());
- }
- }
-
- // try loading the assembly
- try
- {
- var asm = Assembly.LoadFrom(dllPath);
- var assemblyName = asm.FullName ?? dllPath;
- var exportedTypes = asm.GetExportedTypes();
- asm = null;
-
- FLog.Debug($"[AddonLoader] \"{assemblyName}\" exports {exportedTypes.Length} types.");
-
- // load providers from addon assembly
- HotkeyActionAddonLoader.LoadProviders(ref templateProviderManager, exportedTypes);
-
- // load actions from addon assembly
- 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);
- }
- }
- }
- else
- {
- FLog.Trace($"[AddonLoader] Addon directory \"{dir}\" doesn't exist.");
- }
- });
+ // Load addons
+ AddonLoader addonLoader = new();
+ addonLoader.LoadAddons(this);
// Retrieve a sorted list of all loaded action names
this.Actions = HotkeyAPI.HotkeyManager.HotkeyActionManager.ActionDefinitions
@@ -176,31 +111,11 @@ public VolumeControlVM() : base()
public TargetBoxVM TargetBoxVM { get; }
public NotificationConfigSectionVM SessionConfigVM { get; }
public NotificationConfigSectionVM DeviceConfigVM { get; }
+ public ImageSource KeyboardKeyImageSource { get; } = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(Properties.Resources.keyboard_key.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
#endregion Properties
#region Methods
- #region GetDefaultActionGroupTypes
- ///
- /// Loads the default hotkey actions from VolumeControl.HotkeyActions
- ///
- private static Type[] GetDefaultActionGroupTypes()
- {
- var asm = Assembly.Load($"{nameof(VolumeControl)}.{nameof(HotkeyActions)}");
-
- return asm.GetExportedTypes().Where(type => type.GetCustomAttribute() != null).ToArray();
- }
- ///
- /// Loads the default data template providers from VolumeControl.SDK
- ///
- private static Type[] GetDefaultDataTemplateProviderTypes()
- {
- var asm = Assembly.Load($"{nameof(VolumeControl)}.{nameof(SDK)}");
-
- return asm.GetExportedTypes().Where(type => type.GetCustomAttribute() != null).ToArray();
- }
- #endregion GetDefaultActionGroupTypes
-
/// Displays a message box prompting the user for confirmation, and if confirmation is given, resets all hotkeys to their default settings using .
public void ResetHotkeySettings()
{