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 Options { get; set; } = new List(); + public Object SelectedOption { get; set; } + public Object ActiveOption { get; set; } + public int ApplyDelay { get; set; } + + public CurrentValueDelegate CurrentValue { get; set; } + public OptionsValueDelegate OptionsValues { get; set; } + public ApplyValueDelegate ApplyValue { get; set; } + + private System.Windows.Forms.Timer delayTimer; + private ToolStripMenuItem toolStripItem; + + public MenuItemWithOptions() + { + this.Selectable = true; + } + public override void Update() + { + if (CurrentValue != null) + { + var result = CurrentValue(); + if (result != null) + ActiveOption = result; + } + + if (OptionsValues != null) + { + var result = OptionsValues(); + if (result != null) + { + Options = result.ToList(); + updateOptions(); + } + } + + if (ActiveOption == null) + ActiveOption = Options.First(); + if (SelectedOption == null) + SelectedOption = ActiveOption; + + onUpdateToolStrip(); + } + + private void scheduleApply() + { + if (delayTimer != null) + delayTimer.Stop(); + + if (ApplyDelay == 0) + { + onApply(); + return; + } + + delayTimer = new System.Windows.Forms.Timer(); + delayTimer.Interval = ApplyDelay > 0 ? ApplyDelay : 1; + delayTimer.Tick += delegate (object? sender, EventArgs e) + { + if (delayTimer != null) + delayTimer.Stop(); + + onApply(); + }; + delayTimer.Enabled = true; + } + + private void onApply() + { + if (ApplyValue != null) + ActiveOption = ApplyValue(SelectedOption); + + SelectedOption = ActiveOption; + + onUpdateToolStrip(); + } + + private void onUpdateToolStrip() + { + if (toolStripItem == null) + return; + + foreach (ToolStripMenuItem item in toolStripItem.DropDownItems) + item.Checked = (item.Tag.ToString() == SelectedOption.ToString()); + + toolStripItem.Visible = Visible && Options.Count > 0; + } + + private void updateOptions() + { + if (toolStripItem == null) + return; + + toolStripItem.DropDownItems.Clear(); + + foreach (var option in Options) + { + var optionItem = new ToolStripMenuItem(option.ToString()); + optionItem.Tag = option; + optionItem.Click += delegate (object? sender, EventArgs e) + { + SelectedOption = option; + onApply(); + }; + toolStripItem.DropDownItems.Add(optionItem); + } + } + + public override void CreateMenu(ToolStripItemCollection collection) + { + if (toolStripItem != null) + return; + + toolStripItem = new ToolStripMenuItem(); + toolStripItem.Text = Name; + updateOptions(); + collection.Add(toolStripItem); + } + + public override void SelectNext() + { + int index = Options.IndexOf(SelectedOption); + if (index >= 0) + SelectedOption = Options[Math.Min(index + 1, Options.Count - 1)]; + else + SelectedOption = Options.First(); + + scheduleApply(); + } + + public override void SelectPrev() + { + int index = Options.IndexOf(SelectedOption); + if (index >= 0) + SelectedOption = Options[Math.Max(index - 1, 0)]; + else + SelectedOption = Options.First(); + + scheduleApply(); + } + + private String optionText(Object option) + { + String text; + + if (option == null) + text = Color("?", Colors.White); + else if (Object.Equals(option, SelectedOption)) + text = Color(option.ToString(), Colors.Red); + else if(Object.Equals(option, ActiveOption)) + text = Color(option.ToString(), Colors.White); + else + text = Color(option.ToString(), Colors.Green); + + return text; + } + + public override string Render(MenuItem selected) + { + string output = ""; + + if (selected == this) + output += Color(Name + ":", Colors.White).PadRight(30); + else + output += Color(Name + ":", Colors.Blue).PadRight(30); + + output += optionText(SelectedOption); + + if (SelectedOption != ActiveOption) + output += " (active: " + optionText(ActiveOption) + ")"; + + return output; + } + } + + public class MenuRoot : MenuItem + { + public IList Items { get; set; } = new List(); + + public MenuItem Selected; + + public delegate void VisibleChangedDelegate(); + public VisibleChangedDelegate? VisibleChanged; + + public override void CreateMenu(ToolStripItemCollection collection) + { + foreach(var item in Items) + { + item.CreateMenu(collection); + } + } + public override void Update() + { + foreach (var item in Items) + { + item.Update(); + } + } + + public override string Render(MenuItem parentSelected) + { + var sb = new StringBuilder(); + + sb.AppendJoin("", Helpers); + if (Name != "") + sb.AppendLine(Color(Name, Colors.Blue)); + + foreach (var item in Items) + { + if (!item.Visible) + continue; + var lines = item.Render(Selected).Split("\r\n").Select(line => " " + line); + foreach (var line in lines) + sb.AppendLine(line); + } + + return sb.ToString(); + } + + public bool Show() + { + if (Visible) + return false; + + Visible = true; + Update(); + + if (VisibleChanged != null) + VisibleChanged(); + return true; + } + + public void Prev() + { + if (Show()) + return; + + int index = Items.IndexOf(Selected); + + for (int i = 0; i < Items.Count; i++) + { + index = (index - 1 + Items.Count) % Items.Count; + var item = Items[index]; + if (item.Visible && item.Selectable) { + Selected = item; + if (VisibleChanged != null) + VisibleChanged(); + return; + } + } + } + + public void Next() + { + if (Show()) + return; + + int index = Items.IndexOf(Selected); + + for (int i = 0; i < Items.Count; i++) + { + index = (index + 1) % Items.Count; + var item = Items[index]; + if (item.Visible && item.Selectable) + { + Selected = item; + if (VisibleChanged != null) + VisibleChanged(); + return; + } + } + } + + public override void SelectNext() + { + if (Show()) + return; + + if (Selected != null) + { + Selected.SelectNext(); + if (VisibleChanged != null) + VisibleChanged(); + } + } + + public override void SelectPrev() + { + if (Show()) + return; + + if (Selected != null) + { + Selected.SelectPrev(); + if (VisibleChanged != null) + VisibleChanged(); + } + } + } + } +} diff --git a/PowerControl/MenuStack.cs b/PowerControl/MenuStack.cs new file mode 100644 index 0000000..d59be45 --- /dev/null +++ b/PowerControl/MenuStack.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Navigation; + +namespace PowerControl +{ + internal class MenuStack + { + public static Menu.MenuRoot Root = new Menu.MenuRoot() + { + Name = String.Format("\r\n\r\nPower Control v{0}\r\n", Application.ProductVersion.ToString()), + Items = + { + new Menu.MenuItemWithOptions() + { + Name = "Brightness", + Options = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }, + + CurrentValue = delegate() + { + return Helpers.WindowsSettingsBrightnessController.Get10(); + }, + ApplyValue = delegate(object selected) + { + Helpers.WindowsSettingsBrightnessController.Set((int)selected); + + return Helpers.WindowsSettingsBrightnessController.Get10(); + } + }, + new Menu.MenuItemWithOptions() + { + Name = "Volume", + Options = { 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 }, + CurrentValue = delegate() + { + return Helpers.AudioManager.GetMasterVolume10(); + }, + ApplyValue = delegate(object selected) + { + Helpers.AudioManager.SetMasterVolumeMute(false); + Helpers.AudioManager.SetMasterVolume((int)selected); + + return Helpers.AudioManager.GetMasterVolume10(); + } + }, + new Menu.MenuItemWithOptions() + { + Name = "Refresh Rate", + ApplyDelay = 1000, + OptionsValues = delegate() + { + return Helpers.PhysicalMonitorBrightnessController.GetRefreshRates().Select(item => (object)item).ToArray(); + }, + CurrentValue = delegate() + { + return Helpers.PhysicalMonitorBrightnessController.GetRefreshRate(); + }, + ApplyValue = delegate(object selected) + { + Helpers.PhysicalMonitorBrightnessController.SetRefreshRate((int)selected); + return Helpers.PhysicalMonitorBrightnessController.GetRefreshRate(); + } + }, + new Menu.MenuItemWithOptions() + { + Name = "TDP", + Options = { "Auto", "3W", "4W", "5W", "6W", "7W", "8W", "10W", "12W", "15W" }, + ApplyDelay = 1000, + ApplyValue = delegate(object selected) + { + int mW = 15000; + + if (selected.ToString() != "Auto") + { + mW = int.Parse(selected.ToString().Replace("W", "")) * 1000; + } + + int stampLimit = mW/10; + + Process.Start("Resources/RyzenAdj/ryzenadj.exe", new string[] { + "--stapm-limit=" + stampLimit.ToString(), + "--slow-limit=" + mW.ToString(), + "--fast-limit=" + mW.ToString(), + }); + + return selected; + } + }, + new Menu.MenuItemWithOptions() + { + Name = "GPU Clock", + Options = { "Auto", 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600 }, + ApplyDelay = 1000, + Visible = false + } + } + }; + } +} diff --git a/PowerControl/PowerControl.csproj b/PowerControl/PowerControl.csproj new file mode 100644 index 0000000..d07f1dd --- /dev/null +++ b/PowerControl/PowerControl.csproj @@ -0,0 +1,94 @@ + + + + WinExe + net6.0-windows + enable + true + enable + True + app.manifest + Resources\poll.ico + + + + + + + + + + + + + + + + + + + + + + + + + hidapi.net.dll + + + NeptuneHid\neptune-hidapi.net.dll + + + ..\PerformanceOverlay\RTSSSharedMemoryNET.dll + + + + + + True + True + Resources.resx + + + True + True + Settings.settings + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + \ No newline at end of file diff --git a/PowerControl/Program.cs b/PowerControl/Program.cs new file mode 100644 index 0000000..ad2a0fd --- /dev/null +++ b/PowerControl/Program.cs @@ -0,0 +1,23 @@ +using PowerControl; + +namespace PowerControl +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + + using (var controller = new Controller()) + { + Application.Run(); + } + } + } +} \ No newline at end of file diff --git a/PowerControl/Resources.Designer.cs b/PowerControl/Resources.Designer.cs new file mode 100644 index 0000000..fb271b9 --- /dev/null +++ b/PowerControl/Resources.Designer.cs @@ -0,0 +1,73 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PowerControl { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PowerControl.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + /// + internal static System.Drawing.Icon poll { + get { + object obj = ResourceManager.GetObject("poll", resourceCulture); + return ((System.Drawing.Icon)(obj)); + } + } + } +} diff --git a/PowerControl/Resources.resx b/PowerControl/Resources.resx new file mode 100644 index 0000000..d9b49cf --- /dev/null +++ b/PowerControl/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Resources\poll.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/PowerControl/Resources/RyzenAdj/WinRing0x64.dll b/PowerControl/Resources/RyzenAdj/WinRing0x64.dll new file mode 100644 index 0000000..4a48c7a Binary files /dev/null and b/PowerControl/Resources/RyzenAdj/WinRing0x64.dll differ diff --git a/PowerControl/Resources/RyzenAdj/WinRing0x64.sys b/PowerControl/Resources/RyzenAdj/WinRing0x64.sys new file mode 100644 index 0000000..197c255 Binary files /dev/null and b/PowerControl/Resources/RyzenAdj/WinRing0x64.sys differ diff --git a/PowerControl/Resources/RyzenAdj/inpoutx64.dll b/PowerControl/Resources/RyzenAdj/inpoutx64.dll new file mode 100644 index 0000000..82c343f Binary files /dev/null and b/PowerControl/Resources/RyzenAdj/inpoutx64.dll differ diff --git a/PowerControl/Resources/RyzenAdj/libryzenadj.dll b/PowerControl/Resources/RyzenAdj/libryzenadj.dll new file mode 100644 index 0000000..4a3b041 Binary files /dev/null and b/PowerControl/Resources/RyzenAdj/libryzenadj.dll differ diff --git a/PowerControl/Resources/RyzenAdj/ryzenadj.exe b/PowerControl/Resources/RyzenAdj/ryzenadj.exe new file mode 100644 index 0000000..defdc19 Binary files /dev/null and b/PowerControl/Resources/RyzenAdj/ryzenadj.exe differ diff --git a/PowerControl/Resources/poll.ico b/PowerControl/Resources/poll.ico new file mode 100644 index 0000000..2d1d5a9 Binary files /dev/null and b/PowerControl/Resources/poll.ico differ diff --git a/PowerControl/Settings.Designer.cs b/PowerControl/Settings.Designer.cs new file mode 100644 index 0000000..7f78a01 --- /dev/null +++ b/PowerControl/Settings.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PowerControl { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.3.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+Win+Numpad8")] + public string MenuUpKey { + get { + return ((string)(this["MenuUpKey"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+Win+Numpad2")] + public string MenuDownKey { + get { + return ((string)(this["MenuDownKey"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+Win+Numpad4")] + public string MenuLeftKey { + get { + return ((string)(this["MenuLeftKey"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("Ctrl+Win+Numpad6")] + public string MenuRightKey { + get { + return ((string)(this["MenuRightKey"])); + } + } + + [global::System.Configuration.ApplicationScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool EnableNeptuneController { + get { + return ((bool)(this["EnableNeptuneController"])); + } + } + } +} diff --git a/PowerControl/Settings.settings b/PowerControl/Settings.settings new file mode 100644 index 0000000..39119ea --- /dev/null +++ b/PowerControl/Settings.settings @@ -0,0 +1,21 @@ + + + + + + Ctrl+Win+Numpad8 + + + Ctrl+Win+Numpad2 + + + Ctrl+Win+Numpad4 + + + Ctrl+Win+Numpad6 + + + True + + + \ No newline at end of file diff --git a/PowerControl/app.manifest b/PowerControl/app.manifest new file mode 100644 index 0000000..209b401 --- /dev/null +++ b/PowerControl/app.manifest @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + true + true + + + + + + + diff --git a/PowerControl/hidapi.dll b/PowerControl/hidapi.dll new file mode 100644 index 0000000..ee929b3 Binary files /dev/null and b/PowerControl/hidapi.dll differ diff --git a/PowerControl/hidapi.net.dll b/PowerControl/hidapi.net.dll new file mode 100644 index 0000000..54f1ddf Binary files /dev/null and b/PowerControl/hidapi.net.dll differ diff --git a/README.md b/README.md index 64d85c0..908e002 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,44 @@ There are 4 modes of presentation: +## 3. Power Control + +This is a very simple application that requires [Rivatuner Statistics Server Download](https://www.rivatuner.org/) +and provides an easily accessible controls. + +Uninstall MSI Afterburner and any other OSD software. + +There are currently 4 configurable settings: + +- Volume +- Brightness +- Refresh Rate +- TDP + + + +### 3.1. Use it + +It will only work in OSD mode when rendering graphics. +The notification setting is always available. + +- SteamDeck Controller: press Quick Access (3 dots), and then together DPad Left, Rigth, Up, Down. +- Keyboard: `Ctrl+Win+Numpad2` (Down), `Ctrl+Win+Numpad4` (Left), `Ctrl+Win+Numpad6` (Right), `Ctrl+Win+Numpad8` (Up) + +### 3.2. SWICD configuration + +Since the SWICD will mess-up with double inputs you need to configure the following + +- Set 3 dots to **NONE** in button mapping + + + +- Add **Button Actions** for all 4 controls: 3 dots + DPad Left/Right/Up/Down, **CLEAR** Keyboard Shortcut + + + + + ## Author Kamil Trzciński, 2022, License GPLv3 diff --git a/SteamDeckTools.sln b/SteamDeckTools.sln index 41fc546..5101fda 100644 --- a/SteamDeckTools.sln +++ b/SteamDeckTools.sln @@ -7,7 +7,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FanControl", "FanControl\Fa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerformanceOverlay", "PerformanceOverlay\PerformanceOverlay.csproj", "{DCA71DCC-A3ED-4890-A56C-3E2183A3C023}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommonHelpers", "CommonHelpers\CommonHelpers.csproj", "{17728E90-015B-4221-8AA8-68FB359FA12F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "CommonHelpers\CommonHelpers.csproj", "{17728E90-015B-4221-8AA8-68FB359FA12F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerControl", "PowerControl\PowerControl.csproj", "{85A44F35-60C9-493E-B1A7-FB2284E5ACCF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -55,6 +57,18 @@ Global {17728E90-015B-4221-8AA8-68FB359FA12F}.Release|x64.Build.0 = Release|Any CPU {17728E90-015B-4221-8AA8-68FB359FA12F}.Release|x86.ActiveCfg = Release|Any CPU {17728E90-015B-4221-8AA8-68FB359FA12F}.Release|x86.Build.0 = Release|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Debug|x64.ActiveCfg = Debug|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Debug|x64.Build.0 = Debug|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Debug|x86.ActiveCfg = Debug|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Debug|x86.Build.0 = Debug|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Release|Any CPU.Build.0 = Release|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Release|x64.ActiveCfg = Release|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Release|x64.Build.0 = Release|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Release|x86.ActiveCfg = Release|Any CPU + {85A44F35-60C9-493E-B1A7-FB2284E5ACCF}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/images/power_control.png b/images/power_control.png new file mode 100644 index 0000000..9a01bf1 Binary files /dev/null and b/images/power_control.png differ diff --git a/images/power_control_swicd_1.png b/images/power_control_swicd_1.png new file mode 100644 index 0000000..051bb92 Binary files /dev/null and b/images/power_control_swicd_1.png differ diff --git a/images/power_control_swicd_2.png b/images/power_control_swicd_2.png new file mode 100644 index 0000000..eea702b Binary files /dev/null and b/images/power_control_swicd_2.png differ diff --git a/images/power_control_swicd_3.png b/images/power_control_swicd_3.png new file mode 100644 index 0000000..d78875b Binary files /dev/null and b/images/power_control_swicd_3.png differ