From 595336288aa49d41afddbcdf325aee4e11cd3e3a Mon Sep 17 00:00:00 2001 From: Carsten Braun Date: Wed, 19 Jul 2023 11:54:51 +0200 Subject: [PATCH] Added automatic toggle for "Lid Action" when external display and power is connected to keep the laptop awake when lid is closed (known as Clamshell Mode). --- app/Extra.Designer.cs | 17 +++++++ app/Extra.cs | 11 +++++ app/Helpers/ClamshellModeControl.cs | 76 +++++++++++++++++++++++++++++ app/Mode/PowerNative.cs | 38 +++++++++++++++ app/Program.cs | 4 ++ app/Properties/Strings.Designer.cs | 9 ++++ app/Properties/Strings.resx | 3 ++ 7 files changed, 158 insertions(+) create mode 100644 app/Helpers/ClamshellModeControl.cs diff --git a/app/Extra.Designer.cs b/app/Extra.Designer.cs index 63753dd7..7f9f1be6 100644 --- a/app/Extra.Designer.cs +++ b/app/Extra.Designer.cs @@ -101,6 +101,7 @@ namespace GHelper pictureSettings = new PictureBox(); labelSettings = new Label(); panelSettings = new Panel(); + checkAutoToggleClamshellMode = new CheckBox(); checkAutoApplyWindowsPowerMode = new CheckBox(); checkTopmost = new CheckBox(); checkNoOverdrive = new CheckBox(); @@ -1003,6 +1004,7 @@ namespace GHelper // panelSettings.AutoSize = true; panelSettings.AutoSizeMode = AutoSizeMode.GrowAndShrink; + panelSettings.Controls.Add(checkAutoToggleClamshellMode); panelSettings.Controls.Add(checkAutoApplyWindowsPowerMode); panelSettings.Controls.Add(checkTopmost); panelSettings.Controls.Add(checkNoOverdrive); @@ -1017,6 +1019,20 @@ namespace GHelper panelSettings.Size = new Size(983, 304); panelSettings.TabIndex = 46; // + // checkAutoToggleClamshellMode + // + checkAutoToggleClamshellMode.AutoSize = true; + checkAutoToggleClamshellMode.Dock = DockStyle.Top; + checkAutoToggleClamshellMode.Location = new Point(10, 163); + checkAutoToggleClamshellMode.Margin = new Padding(2); + checkAutoToggleClamshellMode.Name = "checkAutoToggleClamshellMode"; + checkAutoToggleClamshellMode.Padding = new Padding(2); + checkAutoToggleClamshellMode.Size = new Size(481, 23); + checkAutoToggleClamshellMode.TabIndex = 58; + checkAutoToggleClamshellMode.Text = "Auto Toggle Clamshell Mode"; + checkAutoToggleClamshellMode.UseVisualStyleBackColor = true; + checkAutoToggleClamshellMode.CheckedChanged += checkAutoToggleClamshellMode_CheckedChanged; + // // checkAutoApplyWindowsPowerMode // checkAutoApplyWindowsPowerMode.AutoSize = true; @@ -1246,5 +1262,6 @@ namespace GHelper private PictureBox pictureService; private Slider sliderBrightness; private PictureBox pictureLog; + private CheckBox checkAutoToggleClamshellMode; } } \ No newline at end of file diff --git a/app/Extra.cs b/app/Extra.cs index 51750467..44d8f35a 100644 --- a/app/Extra.cs +++ b/app/Extra.cs @@ -106,6 +106,7 @@ namespace GHelper checkUSBC.Text = Properties.Strings.OptimizedUSBC; checkAutoApplyWindowsPowerMode.Text = Properties.Strings.ApplyWindowsPowerPlan; checkFnLock.Text = Properties.Strings.FnLock; + checkAutoToggleClamshellMode.Text = Properties.Strings.ToggleClamshellMode; labelBacklightKeyboard.Text = Properties.Strings.Keyboard; labelBacklightBar.Text = Properties.Strings.Lightbar; @@ -210,6 +211,9 @@ namespace GHelper } } + + checkAutoToggleClamshellMode.Checked = AppConfig.Is("toggle_clamshell_mode"); + checkTopmost.Checked = AppConfig.Is("topmost"); checkTopmost.CheckedChanged += CheckTopmost_CheckedChanged; ; @@ -466,5 +470,12 @@ namespace GHelper { AppConfig.Set("auto_apply_power_plan", checkAutoApplyWindowsPowerMode.Checked ? 1 : 0); } + + private void checkAutoToggleClamshellMode_CheckedChanged(object sender, EventArgs e) + { + AppConfig.Set("toggle_clamshell_mode", checkAutoToggleClamshellMode.Checked ? 1 : 0); + ClamshellModeControl ctrl = new ClamshellModeControl(); + ctrl.ToggleLidAction(); + } } } diff --git a/app/Helpers/ClamshellModeControl.cs b/app/Helpers/ClamshellModeControl.cs new file mode 100644 index 00000000..ba6362f1 --- /dev/null +++ b/app/Helpers/ClamshellModeControl.cs @@ -0,0 +1,76 @@ +using GHelper.Display; +using GHelper.Mode; +using Microsoft.Win32; + +namespace GHelper.Helpers +{ + internal class ClamshellModeControl + { + public bool IsExternalDisplayConnected() + { + var devices = ScreenInterrogatory.GetAllDevices().ToArray(); + + foreach (var device in devices) + { + if (device.outputTechnology != ScreenInterrogatory.DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL && + device.outputTechnology != ScreenInterrogatory.DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY.DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED) + { + Logger.WriteLine("Found external screen: " + device.monitorFriendlyDeviceName + ":" + device.outputTechnology.ToString()); + + //Already found one, we do not have to check whether there are more + return true; + } + + } + + return false; + } + + public bool IsClamshellEnabled() + { + return AppConfig.Get("toggle_clamshell_mode") != 0; + } + + public bool IsChargerConnected() + { + return SystemInformation.PowerStatus.PowerLineStatus == PowerLineStatus.Online; + } + + public bool IsInClamshellMode() + { + return IsExternalDisplayConnected() && IsChargerConnected(); + } + + public void ToggleLidAction() + { + if (IsInClamshellMode() && IsClamshellEnabled()) + { + PowerNative.SetLidAction(0, true); + Logger.WriteLine("Engaging Clamshell Mode"); + } + else + { + PowerNative.SetLidAction(1, true); + Logger.WriteLine("Disengaging Clamshell Mode"); + } + } + + public void UnregisterDisplayEvents() + { + SystemEvents.DisplaySettingsChanged -= SystemEvents_DisplaySettingsChanged; + } + + public void RegisterDisplayEvents() + { + SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; + } + + private void SystemEvents_DisplaySettingsChanged(object? sender, EventArgs e) + { + Logger.WriteLine("Display configuration changed."); + + if (IsClamshellEnabled()) + ToggleLidAction(); + } + } +} diff --git a/app/Mode/PowerNative.cs b/app/Mode/PowerNative.cs index de323b4e..06a65bb2 100644 --- a/app/Mode/PowerNative.cs +++ b/app/Mode/PowerNative.cs @@ -60,6 +60,9 @@ namespace GHelper.Mode private static Guid GUID_SLEEP_SUBGROUP = new Guid("238c9fa8-0aad-41ed-83f4-97be242c8f20"); private static Guid GUID_HIBERNATEIDLE = new Guid("9d7815a6-7ee4-497e-8888-515a05f02364"); + private static Guid GUID_SYSTEM_BUTTON_SUBGROUP = new Guid("4f971e89-eebd-4455-a8de-9e59040e7347"); + private static Guid GUID_LIDACTION = new Guid("5CA83367-6E45-459F-A27B-476B1D01C936"); + [DllImportAttribute("powrprof.dll", EntryPoint = "PowerGetActualOverlayScheme")] public static extern uint PowerGetActualOverlayScheme(out Guid ActualOverlayGuid); @@ -163,5 +166,40 @@ namespace GHelper.Mode break; } } + + public static void SetLidAction(int action, bool acOnly = false) + { + /** + * 1: Do nothing + * 2: Seelp + * 3: Hibernate + * 4: Shutdown + */ + + Guid activeSchemeGuid = GetActiveScheme(); + + var hrAC = PowerWriteACValueIndex( + IntPtr.Zero, + activeSchemeGuid, + GUID_SYSTEM_BUTTON_SUBGROUP, + GUID_LIDACTION, + action); + + PowerSetActiveScheme(IntPtr.Zero, activeSchemeGuid); + + if (!acOnly) + { + var hrDC = PowerWriteDCValueIndex( + IntPtr.Zero, + activeSchemeGuid, + GUID_SYSTEM_BUTTON_SUBGROUP, + GUID_LIDACTION, + action); + + PowerSetActiveScheme(IntPtr.Zero, activeSchemeGuid); + } + + Logger.WriteLine("Changed Lid Action to " + action); + } } } diff --git a/app/Program.cs b/app/Program.cs index ad28f402..595a7bfa 100644 --- a/app/Program.cs +++ b/app/Program.cs @@ -30,6 +30,7 @@ namespace GHelper public static ModeControl modeControl = new ModeControl(); static GPUModeControl gpuControl = new GPUModeControl(settingsForm); static ScreenControl screenControl = new ScreenControl(); + static ClamshellModeControl clamshellControl = new ClamshellModeControl(); public static ToastForm toast = new ToastForm(); @@ -99,6 +100,8 @@ namespace GHelper // Subscribing for system power change events SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged; SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; + clamshellControl.RegisterDisplayEvents(); + clamshellControl.ToggleLidAction(); // Subscribing for monitor power on events PowerSettingGuid settingGuid = new NativeMethods.PowerSettingGuid(); @@ -238,6 +241,7 @@ namespace GHelper static void OnExit(object sender, EventArgs e) { trayIcon.Visible = false; + clamshellControl.UnregisterDisplayEvents(); NativeMethods.UnregisterPowerSettingNotification(unRegPowerNotify); Application.Exit(); } diff --git a/app/Properties/Strings.Designer.cs b/app/Properties/Strings.Designer.cs index 31c72239..ead14e55 100644 --- a/app/Properties/Strings.Designer.cs +++ b/app/Properties/Strings.Designer.cs @@ -1177,6 +1177,15 @@ namespace GHelper.Properties { return ResourceManager.GetString("ToggleAura", resourceCulture); } } + + /// + /// Looks up a localized string similar to Auto Toggle Clamshell Mode. + /// + internal static string ToggleClamshellMode { + get { + return ResourceManager.GetString("ToggleClamshellMode", resourceCulture); + } + } /// /// Looks up a localized string similar to Toggle Fn-Lock. diff --git a/app/Properties/Strings.resx b/app/Properties/Strings.resx index 92074f89..fc176a9c 100644 --- a/app/Properties/Strings.resx +++ b/app/Properties/Strings.resx @@ -491,6 +491,9 @@ Do you still want to continue? Toggle Aura + + Auto Toggle Clamshell Mode + Toggle Fn-Lock