diff --git a/CommonHelpers/FromLibreHardwareMonitor/StartupManager.cs b/CommonHelpers/FromLibreHardwareMonitor/StartupManager.cs
index 27d9700..3061f02 100644
--- a/CommonHelpers/FromLibreHardwareMonitor/StartupManager.cs
+++ b/CommonHelpers/FromLibreHardwareMonitor/StartupManager.cs
@@ -25,10 +25,13 @@ namespace CommonHelpers.FromLibreHardwareMonitor
public String NameOf { get; set; }
public String Description { get; set; }
- public StartupManager(String name, String description)
+ public StartupManager(String name, String description = null)
{
NameOf = name;
- Description = description;
+ if (description != null)
+ Description = description;
+ else
+ Description = String.Format("Starts {0} on Windows startup", name);
if (Environment.OSVersion.Platform >= PlatformID.Unix)
{
diff --git a/FanControl/FanControlForm.Designer.cs b/FanControl/FanControlForm.Designer.cs
index 38886eb..9fa8d02 100644
--- a/FanControl/FanControlForm.Designer.cs
+++ b/FanControl/FanControlForm.Designer.cs
@@ -82,12 +82,12 @@
this.toolStripSeparator1,
this.toolStripMenuItem1});
this.contextMenu.Name = "fanModeSelectMenu";
- this.contextMenu.Size = new System.Drawing.Size(311, 140);
+ this.contextMenu.Size = new System.Drawing.Size(311, 87);
//
// toolStripMenuItem2
//
this.toolStripMenuItem2.Name = "toolStripMenuItem2";
- this.toolStripMenuItem2.Size = new System.Drawing.Size(310, 38);
+ this.toolStripMenuItem2.Size = new System.Drawing.Size(310, 22);
this.toolStripMenuItem2.Text = "&Show";
this.toolStripMenuItem2.Click += new System.EventHandler(this.formShow_Event);
//
@@ -101,7 +101,7 @@
this.fanModeSelectNotifyMenu.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.fanModeSelectNotifyMenu.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.fanModeSelectNotifyMenu.Name = "fanModeSelectNotifyMenu";
- this.fanModeSelectNotifyMenu.Size = new System.Drawing.Size(250, 40);
+ this.fanModeSelectNotifyMenu.Size = new System.Drawing.Size(250, 23);
this.fanModeSelectNotifyMenu.SelectedIndexChanged += new System.EventHandler(this.fanModeSelect_SelectedValueChanged);
//
// toolStripSeparator1
@@ -112,7 +112,7 @@
// toolStripMenuItem1
//
this.toolStripMenuItem1.Name = "toolStripMenuItem1";
- this.toolStripMenuItem1.Size = new System.Drawing.Size(310, 38);
+ this.toolStripMenuItem1.Size = new System.Drawing.Size(310, 22);
this.toolStripMenuItem1.Text = "&Exit";
this.toolStripMenuItem1.Click += new System.EventHandler(this.formClose_Event);
//
@@ -124,7 +124,8 @@
this.controlToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
- this.menuStrip1.Size = new System.Drawing.Size(712, 44);
+ this.menuStrip1.Padding = new System.Windows.Forms.Padding(3, 1, 0, 1);
+ this.menuStrip1.Size = new System.Drawing.Size(383, 25);
this.menuStrip1.TabIndex = 3;
this.menuStrip1.Text = "menuStrip1";
//
@@ -134,7 +135,7 @@
this.fanModeSelectMenu.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.fanModeSelectMenu.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.fanModeSelectMenu.Name = "fanModeSelectMenu";
- this.fanModeSelectMenu.Size = new System.Drawing.Size(249, 40);
+ this.fanModeSelectMenu.Size = new System.Drawing.Size(136, 23);
this.fanModeSelectMenu.SelectedIndexChanged += new System.EventHandler(this.fanModeSelect_SelectedValueChanged);
//
// controlToolStripMenuItem
@@ -145,32 +146,32 @@
this.toolStripSeparator2,
this.exitToolStripMenuItem});
this.controlToolStripMenuItem.Name = "controlToolStripMenuItem";
- this.controlToolStripMenuItem.Size = new System.Drawing.Size(113, 40);
+ this.controlToolStripMenuItem.Size = new System.Drawing.Size(59, 23);
this.controlToolStripMenuItem.Text = "&Control";
//
// toolStripMenuItemStartupOnBoot
//
this.toolStripMenuItemStartupOnBoot.Name = "toolStripMenuItemStartupOnBoot";
- this.toolStripMenuItemStartupOnBoot.Size = new System.Drawing.Size(359, 44);
+ this.toolStripMenuItemStartupOnBoot.Size = new System.Drawing.Size(180, 22);
this.toolStripMenuItemStartupOnBoot.Text = "&Startup on Boot";
this.toolStripMenuItemStartupOnBoot.Click += new System.EventHandler(this.toolStripMenuItemStartupOnBoot_Click);
//
// toolStripMenuItemAlwaysOnTop
//
this.toolStripMenuItemAlwaysOnTop.Name = "toolStripMenuItemAlwaysOnTop";
- this.toolStripMenuItemAlwaysOnTop.Size = new System.Drawing.Size(359, 44);
+ this.toolStripMenuItemAlwaysOnTop.Size = new System.Drawing.Size(180, 22);
this.toolStripMenuItemAlwaysOnTop.Text = "&Always on Top";
this.toolStripMenuItemAlwaysOnTop.Click += new System.EventHandler(this.toolStripMenuItemAlwaysOnTop_Click);
//
// toolStripSeparator2
//
this.toolStripSeparator2.Name = "toolStripSeparator2";
- this.toolStripSeparator2.Size = new System.Drawing.Size(356, 6);
+ this.toolStripSeparator2.Size = new System.Drawing.Size(177, 6);
//
// exitToolStripMenuItem
//
this.exitToolStripMenuItem.Name = "exitToolStripMenuItem";
- this.exitToolStripMenuItem.Size = new System.Drawing.Size(359, 44);
+ this.exitToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
this.exitToolStripMenuItem.Text = "&Exit";
this.exitToolStripMenuItem.Click += new System.EventHandler(this.formClose_Event);
//
@@ -189,7 +190,8 @@
this.tableLayoutPanel1.Controls.Add(this.sensorWarningLabel, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.propertyGrid1, 0, 0);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
- this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 44);
+ this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 25);
+ this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 4;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
@@ -197,7 +199,7 @@
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
- this.tableLayoutPanel1.Size = new System.Drawing.Size(712, 1046);
+ this.tableLayoutPanel1.Size = new System.Drawing.Size(383, 486);
this.tableLayoutPanel1.TabIndex = 5;
//
// label1
@@ -207,9 +209,10 @@
this.label1.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
this.label1.ForeColor = System.Drawing.Color.Red;
- this.label1.Location = new System.Drawing.Point(3, 950);
+ this.label1.Location = new System.Drawing.Point(2, 441);
+ this.label1.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label1.Name = "label1";
- this.label1.Size = new System.Drawing.Size(706, 64);
+ this.label1.Size = new System.Drawing.Size(379, 30);
this.label1.TabIndex = 9;
this.label1.Text = "This application is highly experimental.\r\nUse at your own risk!";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -222,9 +225,10 @@
this.helpLabel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.helpLabel.Font = new System.Drawing.Font("Segoe UI", 9F, ((System.Drawing.FontStyle)((System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Underline))), System.Drawing.GraphicsUnit.Point);
this.helpLabel.ForeColor = System.Drawing.SystemColors.HotTrack;
- this.helpLabel.Location = new System.Drawing.Point(3, 1014);
+ this.helpLabel.Location = new System.Drawing.Point(2, 471);
+ this.helpLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.helpLabel.Name = "helpLabel";
- this.helpLabel.Size = new System.Drawing.Size(706, 32);
+ this.helpLabel.Size = new System.Drawing.Size(379, 15);
this.helpLabel.TabIndex = 8;
this.helpLabel.Text = "https://github.com/ayufan-research/steam-deck-tools";
this.helpLabel.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
@@ -237,9 +241,10 @@
this.sensorWarningLabel.FlatStyle = System.Windows.Forms.FlatStyle.Flat;
this.sensorWarningLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
this.sensorWarningLabel.ForeColor = System.Drawing.Color.Red;
- this.sensorWarningLabel.Location = new System.Drawing.Point(3, 854);
+ this.sensorWarningLabel.Location = new System.Drawing.Point(2, 396);
+ this.sensorWarningLabel.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.sensorWarningLabel.Name = "sensorWarningLabel";
- this.sensorWarningLabel.Size = new System.Drawing.Size(706, 96);
+ this.sensorWarningLabel.Size = new System.Drawing.Size(379, 45);
this.sensorWarningLabel.TabIndex = 6;
this.sensorWarningLabel.Text = "Some sensors are missing.\r\nThe fan behavior might be incorrect.\r\nWhich might resu" +
"lt in device overheating.\r\n";
@@ -251,22 +256,24 @@
this.propertyGrid1.DisabledItemForeColor = System.Drawing.SystemColors.ControlText;
this.propertyGrid1.Dock = System.Windows.Forms.DockStyle.Fill;
this.propertyGrid1.HelpVisible = false;
- this.propertyGrid1.Location = new System.Drawing.Point(3, 3);
+ this.propertyGrid1.Location = new System.Drawing.Point(2, 1);
+ this.propertyGrid1.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1);
this.propertyGrid1.Name = "propertyGrid1";
- this.propertyGrid1.Size = new System.Drawing.Size(706, 848);
+ this.propertyGrid1.Size = new System.Drawing.Size(379, 394);
this.propertyGrid1.TabIndex = 1;
this.propertyGrid1.ToolbarVisible = false;
//
// FanControlForm
//
- this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 32F);
+ this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoSize = true;
- this.ClientSize = new System.Drawing.Size(712, 1090);
+ this.ClientSize = new System.Drawing.Size(383, 511);
this.Controls.Add(this.tableLayoutPanel1);
this.Controls.Add(this.menuStrip1);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.MainMenuStrip = this.menuStrip1;
+ this.Margin = new System.Windows.Forms.Padding(2, 1, 2, 1);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FanControlForm";
diff --git a/PerformanceOverlay/Controller.cs b/PerformanceOverlay/Controller.cs
index 085dd93..c0362e8 100644
--- a/PerformanceOverlay/Controller.cs
+++ b/PerformanceOverlay/Controller.cs
@@ -85,7 +85,6 @@ namespace PerformanceOverlay
notifyIcon.Text = TitleWithVersion;
notifyIcon.Visible = true;
notifyIcon.ContextMenuStrip = contextMenu;
- notifyIcon.Click += NotifyIcon_Click;
osdTimer = new System.Windows.Forms.Timer(components);
osdTimer.Tick += OsdTimer_Tick;
@@ -169,6 +168,8 @@ namespace PerformanceOverlay
try
{
+ osdClose();
+
if (osd == null)
osd = new OSD("PerformanceOverlay");
@@ -201,11 +202,11 @@ namespace PerformanceOverlay
{
if (osd != null)
osd.Dispose();
+ osd = null;
}
catch (SystemException)
{
}
- osd = null;
}
private uint osdEmbedGraph(ref uint offset, ref String osdOverlay, String name, int dwWidth, int dwHeight, int dwMargin, float fltMin, float fltMax, EMBEDDED_OBJECT_GRAPH dwFlags)
diff --git a/PowerControl/App.config b/PowerControl/App.config
new file mode 100644
index 0000000..c500d4a
--- /dev/null
+++ b/PowerControl/App.config
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+ Ctrl+Win+Numpad8
+
+
+ Ctrl+Win+Numpad2
+
+
+ Ctrl+Win+Numpad4
+
+
+ Ctrl+Win+Numpad6
+
+
+ True
+
+
+
+
\ No newline at end of file
diff --git a/PowerControl/Controller.cs b/PowerControl/Controller.cs
new file mode 100644
index 0000000..08526bd
--- /dev/null
+++ b/PowerControl/Controller.cs
@@ -0,0 +1,267 @@
+using CommonHelpers;
+using CommonHelpers.FromLibreHardwareMonitor;
+using Microsoft.VisualBasic.Logging;
+using PowerControl.External;
+using RTSSSharedMemoryNET;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using static System.Windows.Forms.AxHost;
+
+namespace PowerControl
+{
+ internal class Controller : IDisposable
+ {
+ public const String Title = "Power Control";
+ public readonly String TitleWithVersion = Title + " v" + Application.ProductVersion.ToString();
+
+ Container components = new Container();
+ System.Windows.Forms.NotifyIcon notifyIcon;
+ StartupManager startupManager = new StartupManager(Title);
+
+ Menu.MenuRoot rootMenu = MenuStack.Root;
+ OSD osd;
+ System.Windows.Forms.Timer osdDismissTimer;
+
+ hidapi.HidDevice neptuneDevice = new hidapi.HidDevice(0x28de, 0x1205, 64);
+ SDCInput neptuneLastDeviceState = new SDCInput();
+ SDCInput neptuneDeviceState = new SDCInput();
+ System.Windows.Forms.Timer neptuneTimer;
+
+ public Controller()
+ {
+ Instance.RunOnce(TitleWithVersion, "Global\\PowerControl");
+
+ var contextMenu = new System.Windows.Forms.ContextMenuStrip(components);
+
+ contextMenu.Opening += delegate (object? sender, CancelEventArgs e)
+ {
+ rootMenu.Update();
+ };
+
+ rootMenu.Visible = false;
+ rootMenu.Update();
+ rootMenu.CreateMenu(contextMenu.Items);
+ rootMenu.VisibleChanged = delegate ()
+ {
+ showOSD();
+ };
+ contextMenu.Items.Add(new ToolStripSeparator());
+
+ if (startupManager.IsAvailable)
+ {
+ var startupItem = new ToolStripMenuItem("Run On Startup");
+ startupItem.Checked = startupManager.Startup;
+ startupItem.Click += delegate
+ {
+ startupManager.Startup = !startupManager.Startup;
+ startupItem.Checked = startupManager.Startup;
+ };
+ contextMenu.Items.Add(startupItem);
+ }
+
+ var helpItem = contextMenu.Items.Add("&Help");
+ helpItem.Click += delegate
+ {
+ System.Diagnostics.Process.Start("explorer.exe", "http://github.com/ayufan-research/steam-deck-tools");
+ };
+
+ contextMenu.Items.Add(new ToolStripSeparator());
+
+ var exitItem = contextMenu.Items.Add("&Exit");
+ exitItem.Click += ExitItem_Click;
+
+ notifyIcon = new System.Windows.Forms.NotifyIcon(components);
+ notifyIcon.Icon = Resources.poll;
+ notifyIcon.Text = TitleWithVersion;
+ notifyIcon.Visible = true;
+ notifyIcon.ContextMenuStrip = contextMenu;
+
+ osdDismissTimer = new System.Windows.Forms.Timer(components);
+ osdDismissTimer.Interval = 3000;
+ osdDismissTimer.Tick += delegate(object ? sender, EventArgs e)
+ {
+ hideOSD();
+ };
+
+ var osdTimer = new System.Windows.Forms.Timer(components);
+ osdTimer.Tick += delegate (object? sender, EventArgs e)
+ {
+ updateOSD();
+ };
+ osdTimer.Interval = 250;
+ osdTimer.Enabled = true;
+
+ GlobalHotKey.RegisterHotKey(Settings.Default.MenuUpKey, () =>
+ {
+ rootMenu.Prev();
+ });
+
+ GlobalHotKey.RegisterHotKey(Settings.Default.MenuDownKey, () =>
+ {
+ rootMenu.Next();
+ });
+
+ GlobalHotKey.RegisterHotKey(Settings.Default.MenuLeftKey, () =>
+ {
+ rootMenu.SelectPrev();
+ });
+
+ GlobalHotKey.RegisterHotKey(Settings.Default.MenuRightKey, () =>
+ {
+ rootMenu.SelectNext();
+ });
+
+ if (Settings.Default.EnableNeptuneController)
+ {
+ neptuneTimer = new System.Windows.Forms.Timer(components);
+ neptuneTimer.Interval = 50;
+ neptuneTimer.Tick += NeptuneTimer_Tick;
+ neptuneTimer.Enabled = true;
+
+ neptuneDevice.OnInputReceived += NeptuneDevice_OnInputReceived;
+ neptuneDevice.OpenDevice();
+ neptuneDevice.BeginRead();
+ }
+ }
+
+ private void NeptuneDevice_OnInputReceived(object? sender, hidapi.HidDeviceInputReceivedEventArgs e)
+ {
+ var input = SDCInput.FromBuffer(e.Buffer);
+
+ neptuneDeviceState = new SDCInput()
+ {
+ buttons0 = input.buttons0,
+ buttons1 = input.buttons1,
+ buttons2 = input.buttons2,
+ buttons3 = input.buttons3,
+ buttons4 = input.buttons4,
+ buttons5 = input.buttons5
+ };
+
+ // Consume only some events to avoid under-running SWICD
+ if ((input.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) != 0)
+ Thread.Sleep(50);
+ else
+ Thread.Sleep(250);
+ }
+
+ private bool isForeground()
+ {
+ try
+ {
+ var processId = Helpers.TopLevelWindow.GetTopLevelProcessId();
+ if (processId is null)
+ return true;
+
+ foreach (var app in OSD.GetAppEntries(AppFlags.MASK))
+ {
+ if (app.ProcessId == processId)
+ return true;
+ }
+
+ return false;
+ }
+ catch
+ {
+ return true;
+ }
+ }
+
+ private void NeptuneTimer_Tick(object? sender, EventArgs e)
+ {
+ if (neptuneDeviceState.Equals(neptuneLastDeviceState))
+ return;
+
+ var input = neptuneLastDeviceState = neptuneDeviceState;
+
+ if ((input.buttons5 & (byte)SDCButton5.BTN_QUICK_ACCESS) == 0 || !isForeground())
+ {
+ hideOSD();
+ return;
+ }
+
+ rootMenu.Show();
+
+ if (input.buttons1 != 0 || input.buttons2 != 0 || input.buttons3 != 0 || input.buttons4 != 0)
+ {
+ return;
+ }
+ else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_LEFT)
+ {
+ rootMenu.SelectPrev();
+ }
+ else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_RIGHT)
+ {
+ rootMenu.SelectNext();
+ }
+ else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_UP)
+ {
+ rootMenu.Prev();
+ }
+ else if (input.buttons0 == (ushort)SDCButton0.BTN_DPAD_DOWN)
+ {
+ rootMenu.Next();
+ }
+ }
+
+ private void showOSD()
+ {
+ osdDismissTimer.Stop();
+ osdDismissTimer.Start();
+ updateOSD();
+ }
+
+ private void hideOSD()
+ {
+ if (!rootMenu.Visible)
+ return;
+
+ rootMenu.Visible = false;
+ osdDismissTimer.Stop();
+ updateOSD();
+ }
+
+ public void updateOSD()
+ {
+ if (!rootMenu.Visible)
+ {
+ try
+ {
+ if (osd != null)
+ osd.Dispose();
+ osd = null;
+ }
+ catch (SystemException)
+ {
+ }
+ return;
+ }
+
+ try
+ {
+ if (osd == null)
+ osd = new OSD("Power Control");
+ osd.Update(rootMenu.Render(null));
+ }
+ catch (SystemException)
+ {
+ }
+ }
+
+ private void ExitItem_Click(object? sender, EventArgs e)
+ {
+ Application.Exit();
+ }
+
+ public void Dispose()
+ {
+ components.Dispose();
+ hideOSD();
+ }
+ }
+}
diff --git a/PowerControl/External/GlobalHotKey.cs b/PowerControl/External/GlobalHotKey.cs
new file mode 100644
index 0000000..df5ab1f
--- /dev/null
+++ b/PowerControl/External/GlobalHotKey.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Windows.Forms;
+using System.Windows.Input;
+
+namespace PowerControl.External
+{
+ public class GlobalHotKey : IDisposable
+ {
+ ///
+ /// Registers a global hotkey
+ ///
+ /// e.g. Alt + Shift + Control + Win + S
+ /// Action to be called when hotkey is pressed
+ /// true, if registration succeeded, otherwise false
+ public static bool RegisterHotKey(string aKeyGestureString, Action aAction)
+ {
+ if (aKeyGestureString == "")
+ return false;
+
+ bool success = false;
+ var c = new KeyGestureConverter();
+ foreach (var gesture in aKeyGestureString.Split(","))
+ {
+ KeyGesture aKeyGesture = (KeyGesture)c.ConvertFrom(gesture);
+ if (RegisterHotKey(aKeyGesture.Modifiers, aKeyGesture.Key, aAction))
+ success = true;
+ }
+ return success;
+ }
+
+ public static bool RegisterHotKey(ModifierKeys aModifier, Key aKey, Action aAction)
+ {
+ if (aModifier == ModifierKeys.None && false)
+ {
+ throw new ArgumentException("Modifier must not be ModifierKeys.None");
+ }
+ if (aAction is null)
+ {
+ throw new ArgumentNullException(nameof(aAction));
+ }
+
+ System.Windows.Forms.Keys aVirtualKeyCode = (System.Windows.Forms.Keys)KeyInterop.VirtualKeyFromKey(aKey);
+ currentID = currentID + 1;
+ bool aRegistered = RegisterHotKey(window.Handle, currentID,
+ (uint)aModifier | MOD_NOREPEAT,
+ (uint)aVirtualKeyCode);
+
+ if (aRegistered)
+ {
+ registeredHotKeys.Add(new HotKeyWithAction(aModifier, aKey, aAction));
+ }
+ return aRegistered;
+ }
+
+ public void Dispose()
+ {
+ // unregister all the registered hot keys.
+ for (int i = currentID; i > 0; i--)
+ {
+ UnregisterHotKey(window.Handle, i);
+ }
+
+ // dispose the inner native window.
+ window.Dispose();
+ }
+
+ static GlobalHotKey()
+ {
+ window.KeyPressed += (s, e) =>
+ {
+ registeredHotKeys.ForEach(x =>
+ {
+ if (e.Modifier == x.Modifier && e.Key == x.Key)
+ {
+ x.Action();
+ }
+ });
+ };
+ }
+
+ private static readonly InvisibleWindowForMessages window = new InvisibleWindowForMessages();
+ private static int currentID;
+ private static uint MOD_NOREPEAT = 0x4000;
+ private static List registeredHotKeys = new List();
+
+ private class HotKeyWithAction
+ {
+
+ public HotKeyWithAction(ModifierKeys modifier, Key key, Action action)
+ {
+ Modifier = modifier;
+ Key = key;
+ Action = action;
+ }
+
+ public ModifierKeys Modifier { get; }
+ public Key Key { get; }
+ public Action Action { get; }
+ }
+
+ // Registers a hot key with Windows.
+ [DllImport("user32.dll")]
+ private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
+ // Unregisters the hot key with Windows.
+ [DllImport("user32.dll")]
+ private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
+
+ private class InvisibleWindowForMessages : System.Windows.Forms.NativeWindow, IDisposable
+ {
+ public InvisibleWindowForMessages()
+ {
+ CreateHandle(new System.Windows.Forms.CreateParams());
+ }
+
+ private static int WM_HOTKEY = 0x0312;
+ protected override void WndProc(ref System.Windows.Forms.Message m)
+ {
+ base.WndProc(ref m);
+
+ if (m.Msg == WM_HOTKEY)
+ {
+ var aWPFKey = KeyInterop.KeyFromVirtualKey(((int)m.LParam >> 16) & 0xFFFF);
+ ModifierKeys modifier = (ModifierKeys)((int)m.LParam & 0xFFFF);
+ if (KeyPressed != null)
+ {
+ KeyPressed(this, new HotKeyPressedEventArgs(modifier, aWPFKey));
+ }
+ }
+ }
+
+ public class HotKeyPressedEventArgs : EventArgs
+ {
+ private ModifierKeys _modifier;
+ private Key _key;
+
+ internal HotKeyPressedEventArgs(ModifierKeys modifier, Key key)
+ {
+ _modifier = modifier;
+ _key = key;
+ }
+
+ public ModifierKeys Modifier
+ {
+ get { return _modifier; }
+ }
+
+ public Key Key
+ {
+ get { return _key; }
+ }
+ }
+
+
+ public event EventHandler KeyPressed;
+
+ #region IDisposable Members
+
+ public void Dispose()
+ {
+ this.DestroyHandle();
+ }
+
+ #endregion
+ }
+ }
+
+}
diff --git a/PowerControl/External/SDStructs.cs b/PowerControl/External/SDStructs.cs
new file mode 100644
index 0000000..43c81fd
--- /dev/null
+++ b/PowerControl/External/SDStructs.cs
@@ -0,0 +1,159 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+
+namespace PowerControl.External
+{
+ internal enum SDCPacketType
+ {
+ PT_INPUT = 0x01,
+ PT_HOTPLUG = 0x03,
+ PT_IDLE = 0x04,
+ PT_OFF = 0x9f,
+ PT_AUDIO = 0xb6,
+ PT_CLEAR_MAPPINGS = 0x81,
+ PT_CONFIGURE = 0x87,
+ PT_LED = 0x87,
+ PT_CALIBRATE_JOYSTICK = 0xbf,
+ PT_CALIBRATE_TRACKPAD = 0xa7,
+ PT_SET_AUDIO_INDICES = 0xc1,
+ PT_LIZARD_BUTTONS = 0x85,
+ PT_LIZARD_MOUSE = 0x8e,
+ PT_FEEDBACK = 0x8f,
+ PT_RESET = 0x95,
+ PT_GET_SERIAL = 0xAE,
+ }
+ internal enum SDCPacketLength
+ {
+ PL_LED = 0x03,
+ PL_OFF = 0x04,
+ PL_FEEDBACK = 0x07,
+ PL_CONFIGURE = 0x15,
+ PL_CONFIGURE_BT = 0x0f,
+ PL_GET_SERIAL = 0x15,
+ }
+ internal enum SDCConfigType
+ {
+ CT_LED = 0x2d,
+ CT_CONFIGURE = 0x32,
+ CONFIGURE_BT = 0x18,
+ }
+
+ internal enum SDCButton0
+ {
+ BTN_L5 = 0b1000000000000000,
+ BTN_OPTIONS = 0b0100000000000000,
+ BTN_STEAM = 0b0010000000000000,
+ BTN_MENU = 0b0001000000000000,
+ BTN_DPAD_DOWN = 0b0000100000000000,
+ BTN_DPAD_LEFT = 0b0000010000000000,
+ BTN_DPAD_RIGHT = 0b0000001000000000,
+ BTN_DPAD_UP = 0b0000000100000000,
+ BTN_A = 0b0000000010000000,
+ BTN_X = 0b0000000001000000,
+ BTN_B = 0b0000000000100000,
+ BTN_Y = 0b0000000000010000,
+ BTN_L1 = 0b0000000000001000,
+ BTN_R1 = 0b0000000000000100,
+ BTN_L2 = 0b0000000000000010,
+ BTN_R2 = 0b0000000000000001,
+ }
+
+ internal enum SDCButton1
+ {
+ BTN_LSTICK_PRESS = 0b01000000,
+ BTN_LPAD_TOUCH = 0b00001000,
+ BTN_LPAD_PRESS = 0b00000010,
+ BTN_RPAD_PRESS = 0b00010000,
+ BTN_RPAD_TOUCH = 0b00000100,
+ BTN_R5 = 0b00000001,
+ }
+
+ internal enum SDCButton2
+ {
+ BTN_RSTICK_PRESS = 0b00000100,
+ }
+
+ internal enum SDCButton4
+ {
+ BTN_LSTICK_TOUCH = 0b01000000,
+ BTN_RSTICK_TOUCH = 0b10000000,
+ BTN_R4 = 0b00000100,
+ BTN_L4 = 0b00000010,
+ }
+
+ internal enum SDCButton5
+ {
+ BTN_QUICK_ACCESS = 0b00000100,
+ }
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SDCInput
+ {
+ public byte ptype; //0x00
+ public byte _a1; //0x01
+ public byte _a2; //0x02
+ public byte _a3; //0x03
+ public uint seq; //0x04
+ public ushort buttons0; //0x09
+ public byte buttons1; //0x0A
+ public byte buttons2; //0x0C
+ public byte buttons3; //0x0D
+ public byte buttons4; //0x0E
+ public byte buttons5; //0x0E
+ public short lpad_x; //0x10
+ public short lpad_y; //0x12
+ public short rpad_x; //0x13
+ public short rpad_y; //0x16
+ public short accel_x; //0x18
+ public short accel_y; //0x1A
+ public short accel_z; //0x1C
+ public short gpitch; //0x1E
+ public short gyaw; //0x20
+ public short groll; //0x22
+ public short q1; //0x24
+ public short q2; //0x26
+ public short q3; //0x28
+ public short q4; //0x2A
+ public short ltrig; //0x2C
+ public short rtrig; //0x2E
+ public short lthumb_x; //0x30
+ public short lthumb_y; //0x32
+ public short rthumb_x; //0x34
+ public short rthumb_y; //0x36
+ public short lpad_pressure; //0x38
+ public short rpad_pressure; //0x3A
+
+ internal static SDCInput FromBuffer(byte[] bytes)
+ {
+ var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
+ try
+ {
+ return (SDCInput)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(SDCInput));
+ }
+ catch
+ {
+ return new SDCInput();
+ }
+ finally
+ {
+ handle.Free();
+ }
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SDCHapticPacket
+ {
+ public byte packet_type; // = 0x8f;
+ public byte len; // = 0x07;
+ public byte position; // = 1;
+ public ushort amplitude;
+ public ushort period;
+ public ushort count;
+ }
+}
diff --git a/PowerControl/Helpers/PhysicalMonitorBrightnessController.cs b/PowerControl/Helpers/PhysicalMonitorBrightnessController.cs
new file mode 100644
index 0000000..d7a81f5
--- /dev/null
+++ b/PowerControl/Helpers/PhysicalMonitorBrightnessController.cs
@@ -0,0 +1,379 @@
+using Microsoft.VisualBasic.Logging;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+
+namespace PowerControl.Helpers
+{
+ // Taken from: https://stackoverflow.com/questions/4013622/adjust-screen-brightness-using-c-sharp
+ public class PhysicalMonitorBrightnessController : IDisposable
+ {
+ #region DllImport
+ [DllImport("dxva2.dll", EntryPoint = "GetNumberOfPhysicalMonitorsFromHMONITOR")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool GetNumberOfPhysicalMonitorsFromHMONITOR(IntPtr hMonitor, ref uint pdwNumberOfPhysicalMonitors);
+
+ [DllImport("dxva2.dll", EntryPoint = "GetPhysicalMonitorsFromHMONITOR")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool GetPhysicalMonitorsFromHMONITOR(IntPtr hMonitor, uint dwPhysicalMonitorArraySize, [Out] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
+
+ [DllImport("dxva2.dll", EntryPoint = "GetMonitorBrightness")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool GetMonitorBrightness(IntPtr handle, ref uint minimumBrightness, ref uint currentBrightness, ref uint maxBrightness);
+
+ [DllImport("dxva2.dll", EntryPoint = "SetMonitorBrightness")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool SetMonitorBrightness(IntPtr handle, uint newBrightness);
+
+ [DllImport("dxva2.dll", EntryPoint = "DestroyPhysicalMonitor")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ private static extern bool DestroyPhysicalMonitor(IntPtr hMonitor);
+
+ [DllImport("dxva2.dll", EntryPoint = "DestroyPhysicalMonitors")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool DestroyPhysicalMonitors(uint dwPhysicalMonitorArraySize, [In] PHYSICAL_MONITOR[] pPhysicalMonitorArray);
+
+ [DllImport("user32.dll")]
+ static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData);
+ delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);
+
+ enum DISP_CHANGE : int
+ {
+ Successful = 0,
+ Restart = 1,
+ Failed = -1,
+ BadMode = -2,
+ NotUpdated = -3,
+ BadFlags = -4,
+ BadParam = -5,
+ BadDualView = -6
+ }
+
+ [Flags()]
+ enum DM : int
+ {
+ Orientation = 0x1,
+ PaperSize = 0x2,
+ PaperLength = 0x4,
+ PaperWidth = 0x8,
+ Scale = 0x10,
+ Position = 0x20,
+ NUP = 0x40,
+ DisplayOrientation = 0x80,
+ Copies = 0x100,
+ DefaultSource = 0x200,
+ PrintQuality = 0x400,
+ Color = 0x800,
+ Duplex = 0x1000,
+ YResolution = 0x2000,
+ TTOption = 0x4000,
+ Collate = 0x8000,
+ FormName = 0x10000,
+ LogPixels = 0x20000,
+ BitsPerPixel = 0x40000,
+ PelsWidth = 0x80000,
+ PelsHeight = 0x100000,
+ DisplayFlags = 0x200000,
+ DisplayFrequency = 0x400000,
+ ICMMethod = 0x800000,
+ ICMIntent = 0x1000000,
+ MeduaType = 0x2000000,
+ DitherType = 0x4000000,
+ PanningWidth = 0x8000000,
+ PanningHeight = 0x10000000,
+ DisplayFixedOutput = 0x20000000
+ }
+
+ struct POINTL
+ {
+ public Int32 x;
+ public Int32 y;
+ };
+
+ [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
+ struct DEVMODE
+ {
+ public const int CCHDEVICENAME = 32;
+ public const int CCHFORMNAME = 32;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
+ [System.Runtime.InteropServices.FieldOffset(0)]
+ public string dmDeviceName;
+ [System.Runtime.InteropServices.FieldOffset(32)]
+ public Int16 dmSpecVersion;
+ [System.Runtime.InteropServices.FieldOffset(34)]
+ public Int16 dmDriverVersion;
+ [System.Runtime.InteropServices.FieldOffset(36)]
+ public Int16 dmSize;
+ [System.Runtime.InteropServices.FieldOffset(38)]
+ public Int16 dmDriverExtra;
+ [System.Runtime.InteropServices.FieldOffset(40)]
+ public DM dmFields;
+
+ [System.Runtime.InteropServices.FieldOffset(44)]
+ Int16 dmOrientation;
+ [System.Runtime.InteropServices.FieldOffset(46)]
+ Int16 dmPaperSize;
+ [System.Runtime.InteropServices.FieldOffset(48)]
+ Int16 dmPaperLength;
+ [System.Runtime.InteropServices.FieldOffset(50)]
+ Int16 dmPaperWidth;
+ [System.Runtime.InteropServices.FieldOffset(52)]
+ Int16 dmScale;
+ [System.Runtime.InteropServices.FieldOffset(54)]
+ Int16 dmCopies;
+ [System.Runtime.InteropServices.FieldOffset(56)]
+ Int16 dmDefaultSource;
+ [System.Runtime.InteropServices.FieldOffset(58)]
+ Int16 dmPrintQuality;
+
+ [System.Runtime.InteropServices.FieldOffset(44)]
+ public POINTL dmPosition;
+ [System.Runtime.InteropServices.FieldOffset(52)]
+ public Int32 dmDisplayOrientation;
+ [System.Runtime.InteropServices.FieldOffset(56)]
+ public Int32 dmDisplayFixedOutput;
+
+ [System.Runtime.InteropServices.FieldOffset(60)]
+ public short dmColor; // See note below!
+ [System.Runtime.InteropServices.FieldOffset(62)]
+ public short dmDuplex; // See note below!
+ [System.Runtime.InteropServices.FieldOffset(64)]
+ public short dmYResolution;
+ [System.Runtime.InteropServices.FieldOffset(66)]
+ public short dmTTOption;
+ [System.Runtime.InteropServices.FieldOffset(68)]
+ public short dmCollate; // See note below!
+ //[System.Runtime.InteropServices.FieldOffset(70)]
+ //[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
+ //public string dmFormName;
+ [System.Runtime.InteropServices.FieldOffset(102)]
+ public Int16 dmLogPixels;
+ [System.Runtime.InteropServices.FieldOffset(104)]
+ public Int32 dmBitsPerPel;
+ [System.Runtime.InteropServices.FieldOffset(108)]
+ public Int32 dmPelsWidth;
+ [System.Runtime.InteropServices.FieldOffset(112)]
+ public Int32 dmPelsHeight;
+ [System.Runtime.InteropServices.FieldOffset(116)]
+ public Int32 dmDisplayFlags;
+ [System.Runtime.InteropServices.FieldOffset(116)]
+ public Int32 dmNup;
+ [System.Runtime.InteropServices.FieldOffset(120)]
+ public Int32 dmDisplayFrequency;
+ }
+
+ [Flags()]
+ public enum ChangeDisplaySettingsFlags : uint
+ {
+ CDS_NONE = 0,
+ CDS_UPDATEREGISTRY = 0x00000001,
+ CDS_TEST = 0x00000002,
+ CDS_FULLSCREEN = 0x00000004,
+ CDS_GLOBAL = 0x00000008,
+ CDS_SET_PRIMARY = 0x00000010,
+ CDS_VIDEOPARAMETERS = 0x00000020,
+ CDS_ENABLE_UNSAFE_MODES = 0x00000100,
+ CDS_DISABLE_UNSAFE_MODES = 0x00000200,
+ CDS_RESET = 0x40000000,
+ CDS_RESET_EX = 0x20000000,
+ CDS_NORESET = 0x10000000
+ }
+
+ const int ENUM_CURRENT_SETTINGS = -1;
+ const int ENUM_REGISTRY_SETTINGS = -2;
+
+ [DllImport("user32.dll")]
+ static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
+
+ [DllImport("user32.dll")]
+ static extern DISP_CHANGE ChangeDisplaySettingsEx(string lpszDeviceName, ref DEVMODE lpDevMode, IntPtr hwnd, ChangeDisplaySettingsFlags dwflags, IntPtr lParam);
+ #endregion
+
+ private IReadOnlyCollection Monitors { get; set; }
+
+ public PhysicalMonitorBrightnessController()
+ {
+ UpdateMonitors();
+ }
+
+ #region Get & Set
+ public void Set(uint brightness)
+ {
+ Set(brightness, true);
+ }
+
+ public static int[] GetRefreshRates()
+ {
+ List refreshRates = new List();
+ DEVMODE current = new DEVMODE();
+
+ if (!EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref current))
+ return new int[0];
+
+ DEVMODE dm = new DEVMODE();
+ for (int i = 0; EnumDisplaySettings(null, i, ref dm); i++)
+ {
+ if (dm.dmPelsWidth != current.dmPelsWidth || dm.dmPelsHeight != current.dmPelsHeight || dm.dmBitsPerPel != current.dmBitsPerPel)
+ continue;
+ refreshRates.Add(dm.dmDisplayFrequency);
+ }
+
+ return refreshRates.ToArray();
+ }
+
+ public static int GetRefreshRate()
+ {
+ DEVMODE dm = new DEVMODE();
+
+ if (!EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref dm))
+ return -1;
+
+ return dm.dmDisplayFrequency;
+ }
+
+ public static bool SetRefreshRate(int hz)
+ {
+ DEVMODE dm = new DEVMODE();
+
+ if (!EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref dm))
+ return false;
+
+ dm.dmFields |= DM.DisplayFrequency;
+ dm.dmDisplayFrequency = hz;
+
+ var dispChange = ChangeDisplaySettingsEx(null, ref dm, IntPtr.Zero, ChangeDisplaySettingsFlags.CDS_NONE, IntPtr.Zero);
+
+ if (!EnumDisplaySettings(null, ENUM_CURRENT_SETTINGS, ref dm))
+ return false;
+
+ Trace.WriteLine("DispChange: " + dispChange.ToString() + " HZ:" + hz.ToString() + " SetHZ:" + dm.dmDisplayFrequency.ToString());
+
+ if (dispChange == DISP_CHANGE.Successful)
+ return true;
+
+ return true;
+ }
+
+ private void Set(uint brightness, bool refreshMonitorsIfNeeded)
+ {
+ bool isSomeFail = false;
+ foreach (var monitor in Monitors)
+ {
+ uint realNewValue = (monitor.MaxValue - monitor.MinValue) * brightness / 100 + monitor.MinValue;
+ if (SetMonitorBrightness(monitor.Handle, realNewValue))
+ {
+ monitor.CurrentValue = realNewValue;
+ }
+ else if (refreshMonitorsIfNeeded)
+ {
+ isSomeFail = true;
+ break;
+ }
+ }
+
+ if (refreshMonitorsIfNeeded && (isSomeFail || !Monitors.Any()))
+ {
+ UpdateMonitors();
+ Set(brightness, false);
+ return;
+ }
+ }
+
+ public int Get()
+ {
+ if (!Monitors.Any())
+ {
+ return -1;
+ }
+ return (int)Monitors.Average(d => d.CurrentValue);
+ }
+ #endregion
+
+ private void UpdateMonitors()
+ {
+ DisposeMonitors(this.Monitors);
+
+ var monitors = new List();
+ EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) =>
+ {
+ uint physicalMonitorsCount = 0;
+ if (!GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, ref physicalMonitorsCount))
+ {
+ // Cannot get monitor count
+ return true;
+ }
+
+ var physicalMonitors = new PHYSICAL_MONITOR[physicalMonitorsCount];
+ if (!GetPhysicalMonitorsFromHMONITOR(hMonitor, physicalMonitorsCount, physicalMonitors))
+ {
+ // Cannot get physical monitor handle
+ return true;
+ }
+
+ foreach (PHYSICAL_MONITOR physicalMonitor in physicalMonitors)
+ {
+ uint minValue = 0, currentValue = 0, maxValue = 0;
+ var info = new MonitorInfo
+ {
+ Handle = physicalMonitor.hPhysicalMonitor,
+ MinValue = minValue,
+ CurrentValue = currentValue,
+ MaxValue = maxValue,
+ };
+ monitors.Add(info);
+ }
+
+ return true;
+ }, IntPtr.Zero);
+
+ this.Monitors = monitors;
+ }
+
+ public void Dispose()
+ {
+ DisposeMonitors(Monitors);
+ GC.SuppressFinalize(this);
+ }
+
+ private static void DisposeMonitors(IEnumerable monitors)
+ {
+ if (monitors?.Any() == true)
+ {
+ PHYSICAL_MONITOR[] monitorArray = monitors.Select(m => new PHYSICAL_MONITOR { hPhysicalMonitor = m.Handle }).ToArray();
+ DestroyPhysicalMonitors((uint)monitorArray.Length, monitorArray);
+ }
+ }
+
+ #region Classes
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ public struct PHYSICAL_MONITOR
+ {
+ public IntPtr hPhysicalMonitor;
+
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+ public string szPhysicalMonitorDescription;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Rect
+ {
+ public int left;
+ public int top;
+ public int right;
+ public int bottom;
+ }
+
+ public class MonitorInfo
+ {
+ public uint MinValue { get; set; }
+ public uint MaxValue { get; set; }
+ public IntPtr Handle { get; set; }
+ public uint CurrentValue { get; set; }
+ }
+ #endregion
+ }
+
+}
diff --git a/PowerControl/Helpers/TopLevelWindow.cs b/PowerControl/Helpers/TopLevelWindow.cs
new file mode 100644
index 0000000..d89d87d
--- /dev/null
+++ b/PowerControl/Helpers/TopLevelWindow.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PowerControl.Helpers
+{
+ public class TopLevelWindow
+ {
+ [DllImport("user32.dll", SetLastError= true)]
+ static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr GetForegroundWindow();
+
+ public static uint? GetTopLevelProcessId()
+ {
+ var hWnd = GetForegroundWindow();
+ var result = GetWindowThreadProcessId(hWnd, out uint processId);
+ if (result != 0)
+ return processId;
+ return null;
+ }
+ }
+}
diff --git a/PowerControl/Helpers/WindowsMasterVolume.cs b/PowerControl/Helpers/WindowsMasterVolume.cs
new file mode 100644
index 0000000..da194a9
--- /dev/null
+++ b/PowerControl/Helpers/WindowsMasterVolume.cs
@@ -0,0 +1,629 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PowerControl.Helpers
+{
+ ///
+ /// Controls audio using the Windows CoreAudio API
+ /// from: http://stackoverflow.com/questions/14306048/controling-volume-mixer
+ /// and: http://netcoreaudio.codeplex.com/
+ ///
+ public static class AudioManager
+ {
+ #region Master Volume Manipulation
+
+ ///
+ /// Gets the current master volume in scalar values (percentage)
+ ///
+ /// -1 in case of an error, if successful the value will be between 0 and 100
+ public static int GetMasterVolume()
+ {
+ IAudioEndpointVolume masterVol = null;
+ try
+ {
+ masterVol = GetMasterVolumeObject();
+ if (masterVol != null)
+ {
+ float volumeLevel;
+ masterVol.GetMasterVolumeLevelScalar(out volumeLevel);
+ return (int)(volumeLevel * 100);
+ }
+
+ return -1;
+ }
+ finally
+ {
+ if (masterVol != null)
+ Marshal.ReleaseComObject(masterVol);
+ }
+ }
+
+ public static int GetMasterVolume10()
+ {
+ return (int)(Math.Round(GetMasterVolume() / 10.0) * 10.0);
+ }
+
+ ///
+ /// Gets the mute state of the master volume.
+ /// While the volume can be muted the will still return the pre-muted volume value.
+ ///
+ /// false if not muted, true if volume is muted
+ public static bool GetMasterVolumeMute()
+ {
+ IAudioEndpointVolume masterVol = null;
+ try
+ {
+ masterVol = GetMasterVolumeObject();
+ if (masterVol == null)
+ return false;
+
+ bool isMuted;
+ masterVol.GetMute(out isMuted);
+ return isMuted;
+ }
+ finally
+ {
+ if (masterVol != null)
+ Marshal.ReleaseComObject(masterVol);
+ }
+ }
+
+ ///
+ /// Sets the master volume to a specific level
+ ///
+ /// Value between 0 and 100 indicating the desired scalar value of the volume
+ public static void SetMasterVolume(int newLevel)
+ {
+ IAudioEndpointVolume masterVol = null;
+ try
+ {
+ masterVol = GetMasterVolumeObject();
+ if (masterVol == null)
+ return;
+
+ masterVol.SetMasterVolumeLevelScalar((float)newLevel / 100.0f, Guid.Empty);
+ }
+ finally
+ {
+ if (masterVol != null)
+ Marshal.ReleaseComObject(masterVol);
+ }
+ }
+
+ ///
+ /// Increments or decrements the current volume level by the .
+ ///
+ /// Value between -100 and 100 indicating the desired step amount. Use negative numbers to decrease
+ /// the volume and positive numbers to increase it.
+ /// the new volume level assigned
+ public static float StepMasterVolume(float stepAmount)
+ {
+ IAudioEndpointVolume masterVol = null;
+ try
+ {
+ masterVol = GetMasterVolumeObject();
+ if (masterVol == null)
+ return -1;
+
+ float stepAmountScaled = stepAmount / 100;
+
+ // Get the level
+ float volumeLevel;
+ masterVol.GetMasterVolumeLevelScalar(out volumeLevel);
+
+ // Calculate the new level
+ float newLevel = volumeLevel + stepAmountScaled;
+ newLevel = Math.Min(1, newLevel);
+ newLevel = Math.Max(0, newLevel);
+
+ masterVol.SetMasterVolumeLevelScalar(newLevel, Guid.Empty);
+
+ // Return the new volume level that was set
+ return newLevel * 100;
+ }
+ finally
+ {
+ if (masterVol != null)
+ Marshal.ReleaseComObject(masterVol);
+ }
+ }
+
+ ///
+ /// Mute or unmute the master volume
+ ///
+ /// true to mute the master volume, false to unmute
+ public static void SetMasterVolumeMute(bool isMuted)
+ {
+ IAudioEndpointVolume masterVol = null;
+ try
+ {
+ masterVol = GetMasterVolumeObject();
+ if (masterVol == null)
+ return;
+
+ masterVol.SetMute(isMuted, Guid.Empty);
+ }
+ finally
+ {
+ if (masterVol != null)
+ Marshal.ReleaseComObject(masterVol);
+ }
+ }
+
+ ///
+ /// Switches between the master volume mute states depending on the current state
+ ///
+ /// the current mute state, true if the volume was muted, false if unmuted
+ public static bool ToggleMasterVolumeMute()
+ {
+ IAudioEndpointVolume masterVol = null;
+ try
+ {
+ masterVol = GetMasterVolumeObject();
+ if (masterVol == null)
+ return false;
+
+ bool isMuted;
+ masterVol.GetMute(out isMuted);
+ masterVol.SetMute(!isMuted, Guid.Empty);
+
+ return !isMuted;
+ }
+ finally
+ {
+ if (masterVol != null)
+ Marshal.ReleaseComObject(masterVol);
+ }
+ }
+
+ private static IAudioEndpointVolume GetMasterVolumeObject()
+ {
+ IMMDeviceEnumerator deviceEnumerator = null;
+ IMMDevice speakers = null;
+ try
+ {
+ deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
+ deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
+
+ Guid IID_IAudioEndpointVolume = typeof(IAudioEndpointVolume).GUID;
+ object o;
+ speakers.Activate(ref IID_IAudioEndpointVolume, 0, IntPtr.Zero, out o);
+ IAudioEndpointVolume masterVol = (IAudioEndpointVolume)o;
+
+ return masterVol;
+ }
+ finally
+ {
+ if (speakers != null) Marshal.ReleaseComObject(speakers);
+ if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator);
+ }
+ }
+
+ #endregion
+
+ #region Individual Application Volume Manipulation
+
+ public static float? GetApplicationVolume(int pid)
+ {
+ ISimpleAudioVolume volume = GetVolumeObject(pid);
+ if (volume == null)
+ return null;
+
+ float level;
+ volume.GetMasterVolume(out level);
+ Marshal.ReleaseComObject(volume);
+ return level * 100;
+ }
+
+ public static bool? GetApplicationMute(int pid)
+ {
+ ISimpleAudioVolume volume = GetVolumeObject(pid);
+ if (volume == null)
+ return null;
+
+ bool mute;
+ volume.GetMute(out mute);
+ Marshal.ReleaseComObject(volume);
+ return mute;
+ }
+
+ public static void SetApplicationVolume(int pid, float level)
+ {
+ ISimpleAudioVolume volume = GetVolumeObject(pid);
+ if (volume == null)
+ return;
+
+ Guid guid = Guid.Empty;
+ volume.SetMasterVolume(level / 100, ref guid);
+ Marshal.ReleaseComObject(volume);
+ }
+
+ public static void SetApplicationMute(int pid, bool mute)
+ {
+ ISimpleAudioVolume volume = GetVolumeObject(pid);
+ if (volume == null)
+ return;
+
+ Guid guid = Guid.Empty;
+ volume.SetMute(mute, ref guid);
+ Marshal.ReleaseComObject(volume);
+ }
+
+ private static ISimpleAudioVolume GetVolumeObject(int pid)
+ {
+ IMMDeviceEnumerator deviceEnumerator = null;
+ IAudioSessionEnumerator sessionEnumerator = null;
+ IAudioSessionManager2 mgr = null;
+ IMMDevice speakers = null;
+ try
+ {
+ // get the speakers (1st render + multimedia) device
+ deviceEnumerator = (IMMDeviceEnumerator)(new MMDeviceEnumerator());
+ deviceEnumerator.GetDefaultAudioEndpoint(EDataFlow.eRender, ERole.eMultimedia, out speakers);
+
+ // activate the session manager. we need the enumerator
+ Guid IID_IAudioSessionManager2 = typeof(IAudioSessionManager2).GUID;
+ object o;
+ speakers.Activate(ref IID_IAudioSessionManager2, 0, IntPtr.Zero, out o);
+ mgr = (IAudioSessionManager2)o;
+
+ // enumerate sessions for on this device
+ mgr.GetSessionEnumerator(out sessionEnumerator);
+ int count;
+ sessionEnumerator.GetCount(out count);
+
+ // search for an audio session with the required process-id
+ ISimpleAudioVolume volumeControl = null;
+ for (int i = 0; i < count; ++i)
+ {
+ IAudioSessionControl2 ctl = null;
+ try
+ {
+ sessionEnumerator.GetSession(i, out ctl);
+
+ // NOTE: we could also use the app name from ctl.GetDisplayName()
+ int cpid;
+ ctl.GetProcessId(out cpid);
+
+ if (cpid == pid)
+ {
+ volumeControl = ctl as ISimpleAudioVolume;
+ break;
+ }
+ }
+ finally
+ {
+ if (ctl != null) Marshal.ReleaseComObject(ctl);
+ }
+ }
+
+ return volumeControl;
+ }
+ finally
+ {
+ if (sessionEnumerator != null) Marshal.ReleaseComObject(sessionEnumerator);
+ if (mgr != null) Marshal.ReleaseComObject(mgr);
+ if (speakers != null) Marshal.ReleaseComObject(speakers);
+ if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator);
+ }
+ }
+
+ #endregion
+
+ }
+
+ #region Abstracted COM interfaces from Windows CoreAudio API
+
+ [ComImport]
+ [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
+ internal class MMDeviceEnumerator
+ {
+ }
+
+ internal enum EDataFlow
+ {
+ eRender,
+ eCapture,
+ eAll,
+ EDataFlow_enum_count
+ }
+
+ internal enum ERole
+ {
+ eConsole,
+ eMultimedia,
+ eCommunications,
+ ERole_enum_count
+ }
+
+ [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IMMDeviceEnumerator
+ {
+ int NotImpl1();
+
+ [PreserveSig]
+ int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
+
+ // the rest is not implemented
+ }
+
+ [Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IMMDevice
+ {
+ [PreserveSig]
+ int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
+
+ // the rest is not implemented
+ }
+
+ [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAudioSessionManager2
+ {
+ int NotImpl1();
+ int NotImpl2();
+
+ [PreserveSig]
+ int GetSessionEnumerator(out IAudioSessionEnumerator SessionEnum);
+
+ // the rest is not implemented
+ }
+
+ [Guid("E2F5BB11-0570-40CA-ACDD-3AA01277DEE8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAudioSessionEnumerator
+ {
+ [PreserveSig]
+ int GetCount(out int SessionCount);
+
+ [PreserveSig]
+ int GetSession(int SessionCount, out IAudioSessionControl2 Session);
+ }
+
+ [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface ISimpleAudioVolume
+ {
+ [PreserveSig]
+ int SetMasterVolume(float fLevel, ref Guid EventContext);
+
+ [PreserveSig]
+ int GetMasterVolume(out float pfLevel);
+
+ [PreserveSig]
+ int SetMute(bool bMute, ref Guid EventContext);
+
+ [PreserveSig]
+ int GetMute(out bool pbMute);
+ }
+
+ [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IAudioSessionControl2
+ {
+ // IAudioSessionControl
+ [PreserveSig]
+ int NotImpl0();
+
+ [PreserveSig]
+ int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
+
+ [PreserveSig]
+ int SetDisplayName([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
+
+ [PreserveSig]
+ int GetIconPath([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
+
+ [PreserveSig]
+ int SetIconPath([MarshalAs(UnmanagedType.LPWStr)] string Value, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
+
+ [PreserveSig]
+ int GetGroupingParam(out Guid pRetVal);
+
+ [PreserveSig]
+ int SetGroupingParam([MarshalAs(UnmanagedType.LPStruct)] Guid Override, [MarshalAs(UnmanagedType.LPStruct)] Guid EventContext);
+
+ [PreserveSig]
+ int NotImpl1();
+
+ [PreserveSig]
+ int NotImpl2();
+
+ // IAudioSessionControl2
+ [PreserveSig]
+ int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
+
+ [PreserveSig]
+ int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
+
+ [PreserveSig]
+ int GetProcessId(out int pRetVal);
+
+ [PreserveSig]
+ int IsSystemSoundsSession();
+
+ [PreserveSig]
+ int SetDuckingPreference(bool optOut);
+ }
+
+ // http://netcoreaudio.codeplex.com/SourceControl/latest#trunk/Code/CoreAudio/Interfaces/IAudioEndpointVolume.cs
+ [Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IAudioEndpointVolume
+ {
+ [PreserveSig]
+ int NotImpl1();
+
+ [PreserveSig]
+ int NotImpl2();
+
+ ///
+ /// Gets a count of the channels in the audio stream.
+ ///
+ /// The number of channels.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetChannelCount(
+ [Out][MarshalAs(UnmanagedType.U4)] out UInt32 channelCount);
+
+ ///
+ /// Sets the master volume level of the audio stream, in decibels.
+ ///
+ /// The new master volume level in decibels.
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int SetMasterVolumeLevel(
+ [In][MarshalAs(UnmanagedType.R4)] float level,
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Sets the master volume level, expressed as a normalized, audio-tapered value.
+ ///
+ /// The new master volume level expressed as a normalized value between 0.0 and 1.0.
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int SetMasterVolumeLevelScalar(
+ [In][MarshalAs(UnmanagedType.R4)] float level,
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Gets the master volume level of the audio stream, in decibels.
+ ///
+ /// The volume level in decibels.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetMasterVolumeLevel(
+ [Out][MarshalAs(UnmanagedType.R4)] out float level);
+
+ ///
+ /// Gets the master volume level, expressed as a normalized, audio-tapered value.
+ ///
+ /// The volume level expressed as a normalized value between 0.0 and 1.0.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetMasterVolumeLevelScalar(
+ [Out][MarshalAs(UnmanagedType.R4)] out float level);
+
+ ///
+ /// Sets the volume level, in decibels, of the specified channel of the audio stream.
+ ///
+ /// The channel number.
+ /// The new volume level in decibels.
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int SetChannelVolumeLevel(
+ [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
+ [In][MarshalAs(UnmanagedType.R4)] float level,
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Sets the normalized, audio-tapered volume level of the specified channel in the audio stream.
+ ///
+ /// The channel number.
+ /// The new master volume level expressed as a normalized value between 0.0 and 1.0.
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int SetChannelVolumeLevelScalar(
+ [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
+ [In][MarshalAs(UnmanagedType.R4)] float level,
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Gets the volume level, in decibels, of the specified channel in the audio stream.
+ ///
+ /// The zero-based channel number.
+ /// The volume level in decibels.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetChannelVolumeLevel(
+ [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
+ [Out][MarshalAs(UnmanagedType.R4)] out float level);
+
+ ///
+ /// Gets the normalized, audio-tapered volume level of the specified channel of the audio stream.
+ ///
+ /// The zero-based channel number.
+ /// The volume level expressed as a normalized value between 0.0 and 1.0.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetChannelVolumeLevelScalar(
+ [In][MarshalAs(UnmanagedType.U4)] UInt32 channelNumber,
+ [Out][MarshalAs(UnmanagedType.R4)] out float level);
+
+ ///
+ /// Sets the muting state of the audio stream.
+ ///
+ /// True to mute the stream, or false to unmute the stream.
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int SetMute(
+ [In][MarshalAs(UnmanagedType.Bool)] Boolean isMuted,
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Gets the muting state of the audio stream.
+ ///
+ /// The muting state. True if the stream is muted, false otherwise.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetMute(
+ [Out][MarshalAs(UnmanagedType.Bool)] out Boolean isMuted);
+
+ ///
+ /// Gets information about the current step in the volume range.
+ ///
+ /// The current zero-based step index.
+ /// The total number of steps in the volume range.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetVolumeStepInfo(
+ [Out][MarshalAs(UnmanagedType.U4)] out UInt32 step,
+ [Out][MarshalAs(UnmanagedType.U4)] out UInt32 stepCount);
+
+ ///
+ /// Increases the volume level by one step.
+ ///
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int VolumeStepUp(
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Decreases the volume level by one step.
+ ///
+ /// A user context value that is passed to the notification callback.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int VolumeStepDown(
+ [In][MarshalAs(UnmanagedType.LPStruct)] Guid eventContext);
+
+ ///
+ /// Queries the audio endpoint device for its hardware-supported functions.
+ ///
+ /// A hardware support mask that indicates the capabilities of the endpoint.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int QueryHardwareSupport(
+ [Out][MarshalAs(UnmanagedType.U4)] out UInt32 hardwareSupportMask);
+
+ ///
+ /// Gets the volume range of the audio stream, in decibels.
+ ///
+ /// The minimum volume level in decibels.
+ /// The maximum volume level in decibels.
+ /// The volume increment level in decibels.
+ /// An HRESULT code indicating whether the operation passed of failed.
+ [PreserveSig]
+ int GetVolumeRange(
+ [Out][MarshalAs(UnmanagedType.R4)] out float volumeMin,
+ [Out][MarshalAs(UnmanagedType.R4)] out float volumeMax,
+ [Out][MarshalAs(UnmanagedType.R4)] out float volumeStep);
+ }
+
+ #endregion
+}
diff --git a/PowerControl/Helpers/WindowsSettingsBrightnessController.cs b/PowerControl/Helpers/WindowsSettingsBrightnessController.cs
new file mode 100644
index 0000000..687b678
--- /dev/null
+++ b/PowerControl/Helpers/WindowsSettingsBrightnessController.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Management;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PowerControl.Helpers
+{
+ public static class WindowsSettingsBrightnessController
+ {
+ public static int Get()
+ {
+ using var mclass = new ManagementClass("WmiMonitorBrightness")
+ {
+ Scope = new ManagementScope(@"\\.\root\wmi")
+ };
+ using var instances = mclass.GetInstances();
+ foreach (ManagementObject instance in instances)
+ {
+ return (byte)instance.GetPropertyValue("CurrentBrightness");
+ }
+ return -1;
+ }
+
+ public static int Get10()
+ {
+ return (int)(Math.Round(Get() / 10.0) * 10.0);
+ }
+
+ public static void Set(int brightness)
+ {
+ using var mclass = new ManagementClass("WmiMonitorBrightnessMethods")
+ {
+ Scope = new ManagementScope(@"\\.\root\wmi")
+ };
+ using var instances = mclass.GetInstances();
+ var args = new object[] { 1, brightness };
+ foreach (ManagementObject instance in instances)
+ {
+ instance.InvokeMethod("WmiSetBrightness", args);
+ }
+ }
+ }
+}
diff --git a/PowerControl/Menu.cs b/PowerControl/Menu.cs
new file mode 100644
index 0000000..8e7852c
--- /dev/null
+++ b/PowerControl/Menu.cs
@@ -0,0 +1,354 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Transactions;
+using System.Windows.Forms.VisualStyles;
+using System.Xml.Schema;
+
+namespace PowerControl
+{
+ internal class Menu
+ {
+ public static readonly String[] Helpers =
+ {
+ "",
+ "",
+ };
+
+ public enum Colors : int
+ {
+ Green,
+ Blue,
+ Redish,
+ Red,
+ White
+ }
+
+ public abstract class MenuItem
+ {
+ public String Name { get; set; }
+ public bool Visible { get; set; } = true;
+ public bool Selectable { get; set; }
+
+ protected string Color(String text, Colors index)
+ {
+ return String.Format("{0}", text, (int)index);
+ }
+
+ public abstract string Render(MenuItem selected);
+
+ public abstract void CreateMenu(ToolStripItemCollection collection);
+ public abstract void Update();
+
+ public abstract void SelectNext();
+ public abstract void SelectPrev();
+ };
+
+ public class MenuItemWithOptions : MenuItem
+ {
+ public delegate object CurrentValueDelegate();
+ public delegate object[] OptionsValueDelegate();
+ public delegate object ApplyValueDelegate(object selected);
+
+ public IList