diff --git a/ColorControl.sln b/ColorControl.sln
index 722c607..09a46d9 100644
--- a/ColorControl.sln
+++ b/ColorControl.sln
@@ -11,6 +11,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NvAPIWrapper", "NvAPIWrappe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "novideo_srgb", "novideo_srgb\novideo_srgb.csproj", "{A6A97834-7BE1-474A-B92F-A512DB1D5186}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogViewer", "NLogViewer\NLogViewer.csproj", "{D5E4D314-CFCD-4859-98FC-68F660AC7FB4}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -51,6 +53,14 @@ Global
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|Any CPU.Build.0 = Release|Any CPU
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.ActiveCfg = Release|x64
{A6A97834-7BE1-474A-B92F-A512DB1D5186}.Release|x64.Build.0 = Release|x64
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Debug|x64.Build.0 = Debug|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|x64.ActiveCfg = Release|Any CPU
+ {D5E4D314-CFCD-4859-98FC-68F660AC7FB4}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/ColorControl/AppContext.cs b/ColorControl/AppContext.cs
index 8d57f0b..0ebcb81 100644
--- a/ColorControl/AppContext.cs
+++ b/ColorControl/AppContext.cs
@@ -1,4 +1,6 @@
using ColorControl.Common;
+using NLog.Config;
+using System;
using System.ComponentModel;
using System.Threading;
@@ -13,12 +15,15 @@ namespace ColorControl
public StartUpParams StartUpParams { get; private set; }
public string DataPath { get; private set; }
public SynchronizationContext SynchronizationContext { get; set; } = AsyncOperationManager.SynchronizationContext;
+ public LoggingRule LoggingRule { get; private set; }
+ public DateTime StartTime { get; private set; } = DateTime.Now;
- public AppContext(Config config, StartUpParams startUpParams, string dataPath)
+ public AppContext(Config config, StartUpParams startUpParams, string dataPath, LoggingRule loggingRule)
{
Config = config;
StartUpParams = startUpParams;
DataPath = dataPath;
+ LoggingRule = loggingRule;
CurrentContext = this;
}
@@ -27,5 +32,11 @@ namespace ColorControl
{
return Utils.IsServiceRunning();
}
+
+ public void SetLogLevel(NLog.LogLevel logLevel)
+ {
+ Config.LogLevel = logLevel.ToString();
+ LoggingRule.SetLoggingLevels(logLevel, NLog.LogLevel.Fatal);
+ }
}
}
diff --git a/ColorControl/ColorControl.csproj b/ColorControl/ColorControl.csproj
index 5a89eee..0c6b14d 100644
--- a/ColorControl/ColorControl.csproj
+++ b/ColorControl/ColorControl.csproj
@@ -47,6 +47,9 @@
bin\x64\Release\
MinimumRecommendedRules.ruleset
+
+
+
True
@@ -93,8 +96,6 @@
-
-
@@ -104,6 +105,12 @@
+
+ MSBuild:Compile
+
+
+
+
diff --git a/ColorControl/Common/Utils.cs b/ColorControl/Common/Utils.cs
index c2db8e0..9631d7c 100644
--- a/ColorControl/Common/Utils.cs
+++ b/ColorControl/Common/Utils.cs
@@ -1117,6 +1117,46 @@ The best and suggested method to provide this is via a Windows Service. Only whe
return null;
}
+ public static List ReadLines(string fileName, bool reverse = false)
+ {
+ var exists = File.Exists(fileName);
+
+ if (!exists)
+ {
+ return null;
+ }
+
+ try
+ {
+ var lines = new List();
+
+ using var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
+
+ using var reader = new StreamReader(fs);
+
+ string line;
+
+ while ((line = reader.ReadLine()) != null)
+ {
+ if (reverse)
+ {
+ lines.Insert(0, line + "\r\n");
+ continue;
+ }
+
+ lines.Add(line);
+ }
+
+ return lines;
+ }
+ catch (Exception e)
+ {
+ Logger.Error(e.ToLogString());
+ }
+
+ return null;
+ }
+
public static bool WriteObject(string fileName, object value)
{
var data = JsonConvert.SerializeObject(value);
diff --git a/ColorControl/Config.cs b/ColorControl/Config.cs
index eef87d7..1865167 100644
--- a/ColorControl/Config.cs
+++ b/ColorControl/Config.cs
@@ -45,10 +45,12 @@ namespace ColorControl
public ElevationMethod ElevationMethod { get; set; }
public bool ElevationMethodAsked { get; set; }
public int ProcessMonitorPollingInterval { get; set; }
+ public string LogLevel { get; set; }
public List Modules { get; set; }
public Config()
{
+ LogLevel = "DEBUG";
DisplaySettingsDelay = 1000;
ScreenSaverShortcut = string.Empty;
FormWidth = 900;
diff --git a/ColorControl/MainForm.Designer.cs b/ColorControl/MainForm.Designer.cs
index 86f3b3a..ca56c3b 100644
--- a/ColorControl/MainForm.Designer.cs
+++ b/ColorControl/MainForm.Designer.cs
@@ -41,6 +41,7 @@
lblFixChromeFontsDescription = new System.Windows.Forms.Label();
chkFixChromeFonts = new System.Windows.Forms.CheckBox();
grpGeneralOptions = new System.Windows.Forms.GroupBox();
+ btnOptionsAdvanced = new System.Windows.Forms.Button();
chkOptionsUseDarkMode = new System.Windows.Forms.CheckBox();
chkAutoInstallUpdates = new System.Windows.Forms.CheckBox();
btnStartStopService = new System.Windows.Forms.Button();
@@ -70,7 +71,7 @@
label7 = new System.Windows.Forms.Label();
lblInfo = new System.Windows.Forms.Label();
btnUpdate = new System.Windows.Forms.Button();
- btnOptionsAdvanced = new System.Windows.Forms.Button();
+ btnOptionsLog = new System.Windows.Forms.Button();
tcMain.SuspendLayout();
tabOptions.SuspendLayout();
grpOptionsModules.SuspendLayout();
@@ -216,6 +217,7 @@
//
// grpGeneralOptions
//
+ grpGeneralOptions.Controls.Add(btnOptionsLog);
grpGeneralOptions.Controls.Add(btnOptionsAdvanced);
grpGeneralOptions.Controls.Add(chkOptionsUseDarkMode);
grpGeneralOptions.Controls.Add(chkAutoInstallUpdates);
@@ -241,6 +243,17 @@
grpGeneralOptions.TabStop = false;
grpGeneralOptions.Text = "General";
//
+ // btnOptionsAdvanced
+ //
+ btnOptionsAdvanced.Location = new System.Drawing.Point(7, 172);
+ btnOptionsAdvanced.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ btnOptionsAdvanced.Name = "btnOptionsAdvanced";
+ btnOptionsAdvanced.Size = new System.Drawing.Size(88, 27);
+ btnOptionsAdvanced.TabIndex = 11;
+ btnOptionsAdvanced.Text = "Advanced...";
+ btnOptionsAdvanced.UseVisualStyleBackColor = true;
+ btnOptionsAdvanced.Click += btnOptionsAdvanced_Click;
+ //
// chkOptionsUseDarkMode
//
chkOptionsUseDarkMode.AutoSize = true;
@@ -591,16 +604,16 @@
btnUpdate.Visible = false;
btnUpdate.Click += btnUpdate_Click;
//
- // btnOptionsAdvanced
+ // btnOptionsLog
//
- btnOptionsAdvanced.Location = new System.Drawing.Point(7, 172);
- btnOptionsAdvanced.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
- btnOptionsAdvanced.Name = "btnOptionsAdvanced";
- btnOptionsAdvanced.Size = new System.Drawing.Size(88, 27);
- btnOptionsAdvanced.TabIndex = 11;
- btnOptionsAdvanced.Text = "Advanced...";
- btnOptionsAdvanced.UseVisualStyleBackColor = true;
- btnOptionsAdvanced.Click += btnOptionsAdvanced_Click;
+ btnOptionsLog.Location = new System.Drawing.Point(103, 172);
+ btnOptionsLog.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
+ btnOptionsLog.Name = "btnOptionsLog";
+ btnOptionsLog.Size = new System.Drawing.Size(88, 27);
+ btnOptionsLog.TabIndex = 68;
+ btnOptionsLog.Text = "Logging...";
+ btnOptionsLog.UseVisualStyleBackColor = true;
+ btnOptionsLog.Click += btnOptionsLog_Click;
//
// MainForm
//
@@ -686,6 +699,7 @@
private System.Windows.Forms.Label label1;
private System.Windows.Forms.CheckBox chkOptionsUseDarkMode;
private System.Windows.Forms.Button btnOptionsAdvanced;
+ private System.Windows.Forms.Button btnOptionsLog;
}
}
diff --git a/ColorControl/MainForm.cs b/ColorControl/MainForm.cs
index 28b91cb..1de9884 100644
--- a/ColorControl/MainForm.cs
+++ b/ColorControl/MainForm.cs
@@ -9,6 +9,7 @@ using ColorControl.Services.LG;
using ColorControl.Services.NVIDIA;
using ColorControl.Services.Samsung;
using ColorControl.Svc;
+using ColorControl.XForms;
using Microsoft.Extensions.DependencyInjection;
using NLog;
using novideo_srgb;
@@ -1501,5 +1502,10 @@ Currently ColorControl is {(Utils.IsAdministrator() ? "" : "not ")}running as ad
_config.ProcessMonitorPollingInterval = processPollingIntervalField.ValueAsInt;
}
+
+ private void btnOptionsLog_Click(object sender, EventArgs e)
+ {
+ LogWindow.CreateAndShow();
+ }
}
}
\ No newline at end of file
diff --git a/ColorControl/Program.cs b/ColorControl/Program.cs
index 3354a1c..f705f64 100644
--- a/ColorControl/Program.cs
+++ b/ColorControl/Program.cs
@@ -12,6 +12,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using NLog;
+using NLog.Config;
using NWin32;
using System;
using System.Diagnostics;
@@ -39,6 +40,7 @@ namespace ColorControl
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static Mutex _mutex;
private static MainForm _mainForm;
+ private static LoggingRule _loggingRule;
[STAThread] // autogenerated
public static void Main(string[] args)
@@ -70,7 +72,7 @@ namespace ColorControl
if (runAsService)
{
- AppContext = new AppContext(null, new StartUpParams(), DataDir);
+ AppContext = new AppContext(null, new StartUpParams(), DataDir, _loggingRule);
await RunService(args);
return;
@@ -93,7 +95,7 @@ namespace ColorControl
var startUpParams = StartUpParams.Parse(args);
- AppContext = new AppContext(Config, startUpParams, DataDir);
+ AppContext = new AppContext(Config, startUpParams, DataDir, _loggingRule);
var existingProcess = Utils.GetProcessByName("ColorControl");
@@ -178,7 +180,7 @@ namespace ColorControl
private static void InitLogger(bool runAsService)
{
- var config = new NLog.Config.LoggingConfiguration();
+ var config = new LoggingConfiguration();
// Targets where to log to: File and Console
var logFileAppendix = runAsService ? "_svc" : "";
@@ -187,11 +189,10 @@ namespace ColorControl
var logfile = new NLog.Targets.FileTarget("logfile") { FileName = LogFilename };
- //var logconsole = new NLog.Targets.ConsoleTarget("logconsole");
+ _loggingRule = new LoggingRule("*", LogLevel.Trace, LogLevel.Fatal, logfile);
// Rules for mapping loggers to targets
- //config.AddRule(LogLevel.Info, LogLevel.Fatal, logconsole);
- config.AddRule(LogLevel.Trace, LogLevel.Fatal, logfile);
+ config.AddRule(_loggingRule);
// Apply config
LogManager.Configuration = config;
@@ -216,6 +217,10 @@ namespace ColorControl
Config ??= new Config();
Utils.UseDedicatedElevatedProcess = Config.UseDedicatedElevatedProcess;
+
+ var minLogLevel = LogLevel.FromString(Config.LogLevel);
+
+ _loggingRule.SetLoggingLevels(minLogLevel, LogLevel.Fatal);
}
public static async Task HandleStartupParams(StartUpParams startUpParams, Process existingProcess)
diff --git a/ColorControl/Services/Samsung/SamTvConnection.cs b/ColorControl/Services/Samsung/SamTvConnection.cs
index 258f9f6..786197b 100644
--- a/ColorControl/Services/Samsung/SamTvConnection.cs
+++ b/ColorControl/Services/Samsung/SamTvConnection.cs
@@ -244,6 +244,10 @@ namespace ColorControl.Services.Samsung
// taskCompletion.TrySetResult(new { clientKey = key });
//}
+ }
+ else
+ {
+
}
//else if (_tokens.TryGetValue(id, out taskCompletion))
//{
diff --git a/ColorControl/Services/Samsung/SamsungButtons.cs b/ColorControl/Services/Samsung/SamsungButtons.cs
index e3a9f08..34c4814 100644
--- a/ColorControl/Services/Samsung/SamsungButtons.cs
+++ b/ColorControl/Services/Samsung/SamsungButtons.cs
@@ -37,6 +37,8 @@
KEY_9,
KEY_DTV,
KEY_HDMI,
- KEY_CONTENTS
+ KEY_CONTENTS,
+ KEY_TV_MODE,
+ KEY_PC_MODE
}
}
diff --git a/ColorControl/Services/Samsung/SamsungDevice.cs b/ColorControl/Services/Samsung/SamsungDevice.cs
index cab3133..9454daf 100644
--- a/ColorControl/Services/Samsung/SamsungDevice.cs
+++ b/ColorControl/Services/Samsung/SamsungDevice.cs
@@ -39,6 +39,8 @@ namespace ColorControl.Services.Samsung
External
}
+ public delegate void GenericDelegate(object sender);
+
class SamsungDevice
{
public class InvokableAction
@@ -71,9 +73,13 @@ namespace ColorControl.Services.Samsung
[JsonIgnore]
public bool PoweredOn { get; private set; }
+ [JsonIgnore]
public DateTimeOffset PoweredOffAt { get; private set; }
+ [JsonIgnore]
public PowerOffSource PoweredOffBy { get; internal set; }
+ public event GenericDelegate Connected;
+
[JsonIgnore]
private SamTvConnection _samTvConnection;
@@ -188,6 +194,11 @@ namespace ColorControl.Services.Samsung
finally
{
_connectSemaphore.Release();
+
+ if (true)
+ {
+ Connected?.Invoke(this);
+ }
}
}
@@ -336,7 +347,7 @@ namespace ColorControl.Services.Samsung
var powerState = (string)json.device?.PowerState;
- Logger.Error($"Current power state: {powerState}");
+ Logger.Debug($"Current power state: {powerState}");
return powerState;
}
@@ -563,6 +574,21 @@ namespace ColorControl.Services.Samsung
return await WakeAndConnectWithRetries(powerOnRetries);
}
+
+ internal async Task> GetAppsAsync(bool force)
+ {
+ var dynamic = new
+ {
+ @event = "ed.installedApp.get",
+ to = "host",
+ };
+
+ var message = new RequestMessage("ms.channel.emit", dynamic);
+
+ var result = await _samTvConnection.SendCommandAsync(message, true);
+
+ return new List();
+ }
}
}
diff --git a/ColorControl/Services/Samsung/SamsungPanel.cs b/ColorControl/Services/Samsung/SamsungPanel.cs
index 06618c6..2fec244 100644
--- a/ColorControl/Services/Samsung/SamsungPanel.cs
+++ b/ColorControl/Services/Samsung/SamsungPanel.cs
@@ -364,10 +364,10 @@ namespace ColorControl.Services.Samsung
}
}
- if (cbxSamsungApps.Items.Count == 0 && device != null)
- {
- //_samsungService.RefreshApps().ContinueWith((task) => BeginInvoke(() => FillLgApps(false)));
- }
+ //if (cbxSamsungApps.Items.Count == 0 && device != null)
+ //{
+ // _samsungService.RefreshAppsAsync().ContinueWith((task) => BeginInvoke(() => FillApps(false)));
+ //}
btnSamsungDeviceConvertToCustom.Enabled = devices.Any();
@@ -402,7 +402,7 @@ namespace ColorControl.Services.Samsung
}
}
- private void FillLgApps(bool forced)
+ private void FillApps(bool forced)
{
var lgApps = _samsungService?.GetApps();
if (forced && (lgApps == null || !lgApps.Any()))
@@ -410,10 +410,10 @@ namespace ColorControl.Services.Samsung
MessageForms.WarningOk("Could not refresh the apps. Check the log for details.");
return;
}
- InitLgApps();
+ InitApps();
}
- private void InitLgApps()
+ private void InitApps()
{
var lgApps = _samsungService?.GetApps();
diff --git a/ColorControl/Services/Samsung/SamsungService.cs b/ColorControl/Services/Samsung/SamsungService.cs
index d7b3532..d513651 100644
--- a/ColorControl/Services/Samsung/SamsungService.cs
+++ b/ColorControl/Services/Samsung/SamsungService.cs
@@ -37,6 +37,7 @@ namespace ColorControl.Services.Samsung
private bool _poweredOffByScreenSaver;
private int _poweredOffByScreenSaverProcessId;
private object _lastTriggeredPreset;
+ private List _samsungApps = new List();
public List Devices { get; private set; }
@@ -184,6 +185,8 @@ namespace ColorControl.Services.Samsung
var preferredDevice = Devices.FirstOrDefault(x => x.MacAddress != null && x.MacAddress.Equals(Config.PreferredMacAddress)) ?? Devices[0];
SelectedDevice = preferredDevice;
+
+ SelectedDevice.Connected += SelectedDevice_Connected;
}
else
{
@@ -216,6 +219,16 @@ namespace ColorControl.Services.Samsung
}
}
+ private async void SelectedDevice_Connected(object sender)
+ {
+ if (_samsungApps.Any())
+ {
+ return;
+ }
+
+ //await RefreshAppsAsync();
+ }
+
protected override List GetDefaultPresets()
{
return new List();
@@ -430,11 +443,27 @@ namespace ColorControl.Services.Samsung
}
}
- internal List GetApps()
+ public async Task RefreshAppsAsync(bool force = false)
{
- return new List();
+ if (SelectedDevice == null)
+ {
+ Logger.Debug("Cannot refresh apps: no device has been selected");
+ return;
+ }
+
+ var apps = await SelectedDevice.GetAppsAsync(force);
+
+ if (apps.Any())
+ {
+ _samsungApps.Clear();
+ _samsungApps.AddRange(apps);
+ }
}
+ internal List GetApps()
+ {
+ return _samsungApps;
+ }
public async Task ProcessChanged(object sender, ProcessChangedEventArgs args, CancellationToken token)
{
diff --git a/ColorControl/XForms/LogWindow.xaml b/ColorControl/XForms/LogWindow.xaml
new file mode 100644
index 0000000..ea2fe17
--- /dev/null
+++ b/ColorControl/XForms/LogWindow.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+ Log level:
+
+ Trace
+ Debug
+ Info
+ Warning
+ Error
+ Fatal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ColorControl/XForms/LogWindow.xaml.cs b/ColorControl/XForms/LogWindow.xaml.cs
new file mode 100644
index 0000000..a6c3b14
--- /dev/null
+++ b/ColorControl/XForms/LogWindow.xaml.cs
@@ -0,0 +1,119 @@
+using ColorControl.Common;
+using NLog;
+using System;
+using System.ComponentModel;
+using System.IO;
+using System.Windows;
+using System.Windows.Navigation;
+
+namespace ColorControl.XForms
+{
+ public partial class LogWindow : Window
+ {
+ private static LogWindow _window;
+
+ private int _logLevelIndex;
+
+ public int LogLevelIndex { get => _logLevelIndex; set => _logLevelIndex = value; }
+
+ public LogWindow()
+ {
+ InitializeComponent();
+
+ LogLevelIndex = LogLevel.FromString(AppContext.CurrentContext.Config.LogLevel).Ordinal;
+
+ DataContext = this;
+ }
+
+ private void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ var processStartInfo = new System.Diagnostics.ProcessStartInfo(e.Uri.AbsoluteUri)
+ {
+ UseShellExecute = true,
+ };
+ System.Diagnostics.Process.Start(processStartInfo);
+ }
+
+ public static void CreateAndShow(bool show = true)
+ {
+ if (Application.Current == null)
+ {
+ new Application();
+ }
+
+ _window ??= new LogWindow();
+
+ if (show)
+ {
+ _window.WindowState = WindowState.Normal;
+ _window.Show();
+ _window.Topmost = true;
+ _window.Topmost = false;
+ }
+ }
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ e.Cancel = true;
+
+ Hide();
+ }
+
+ protected override void OnStateChanged(EventArgs e)
+ {
+ if (WindowState == WindowState.Minimized)
+ {
+ Hide();
+ }
+
+ base.OnStateChanged(e);
+ }
+
+ private void RawLog_Click(object sender, RoutedEventArgs e)
+ {
+ var context = AppContext.CurrentContext;
+ var logFile = Path.Combine(context.DataPath, "LogFile.txt");
+
+ Utils.StartProcess(logFile);
+ }
+
+ private void LoadOlder_Click(object sender, RoutedEventArgs e)
+ {
+ var context = AppContext.CurrentContext;
+ var logFile = Path.Combine(context.DataPath, "LogFile.txt");
+
+ var lines = Utils.ReadLines(logFile);
+
+ logViewer.LoadLines(lines);
+ }
+
+ private void Close_Click(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+
+ private void LogLevel_Changed(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
+ {
+ var context = AppContext.CurrentContext;
+
+ var logLevel = LogLevel.FromOrdinal(_logLevelIndex);
+
+ context.SetLogLevel(logLevel);
+ }
+
+ private void Window_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (logViewer.HasEvents())
+ {
+ return;
+ }
+
+ var context = AppContext.CurrentContext;
+ var logFile = Path.Combine(context.DataPath, "LogFile.txt");
+
+ var lines = Utils.ReadLines(logFile);
+
+ logViewer.LoadLines(lines, context.StartTime);
+ }
+ }
+}
\ No newline at end of file
diff --git a/NLogViewer/ActionCommand.cs b/NLogViewer/ActionCommand.cs
new file mode 100644
index 0000000..67db679
--- /dev/null
+++ b/NLogViewer/ActionCommand.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Windows.Input;
+
+namespace DJ
+{
+ public sealed class ActionCommand : ICommand
+ {
+ private readonly Action _Action;
+ private readonly Action