From 5710ea69f5ab6321e12a9ffb0505ac73fa0b9ce9 Mon Sep 17 00:00:00 2001 From: kyros Date: Sat, 9 Aug 2025 00:27:33 -0400 Subject: [PATCH] Add project files. --- README.md | 16 ++ WinPowerDMS.sln | 31 +++ WinPowerDMS/WinPowerDMS.c | 298 ++++++++++++++++++++++++ WinPowerDMS/WinPowerDMS.rc | Bin 0 -> 5516 bytes WinPowerDMS/WinPowerDMS.vcxproj | 142 +++++++++++ WinPowerDMS/WinPowerDMS.vcxproj.filters | 32 +++ WinPowerDMS/resource.h | 22 ++ 7 files changed, 541 insertions(+) create mode 100644 README.md create mode 100644 WinPowerDMS.sln create mode 100644 WinPowerDMS/WinPowerDMS.c create mode 100644 WinPowerDMS/WinPowerDMS.rc create mode 100644 WinPowerDMS/WinPowerDMS.vcxproj create mode 100644 WinPowerDMS/WinPowerDMS.vcxproj.filters create mode 100644 WinPowerDMS/resource.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..7242287 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# WinPowerDMS + +A utility for switching the resolution of your laptop's display based on the current power state. + +Currently a work in progress. Not very useful yet since it doesn't save your preferences. It doesn't even have an icon. + +This is a simple C program written using Visual Studio 2022 that has no external dependencies. It should support Windows Vista or higher, but this hasn't been tested yet. + +## TODO +- [ ] Save preferences to the Windows registry. +- [ ] Add a checkbox to preferences for starting at login. +- [ ] Add an icon. +- [ ] Add behavior for when multiple displays are connected. +- [ ] Block program from running when the system has no battery. +- [ ] Make the default display mode the current one instead of the highest resolution if there are no preferences set. +- [ ] Make an actual about dialog. \ No newline at end of file diff --git a/WinPowerDMS.sln b/WinPowerDMS.sln new file mode 100644 index 0000000..d9c4e92 --- /dev/null +++ b/WinPowerDMS.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36401.2 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinPowerDMS", "WinPowerDMS\WinPowerDMS.vcxproj", "{C852C6DD-1C8D-417B-89B9-CA30D4C924C5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Debug|x64.ActiveCfg = Debug|x64 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Debug|x64.Build.0 = Debug|x64 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Debug|x86.ActiveCfg = Debug|Win32 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Debug|x86.Build.0 = Debug|Win32 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Release|x64.ActiveCfg = Release|x64 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Release|x64.Build.0 = Release|x64 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Release|x86.ActiveCfg = Release|Win32 + {C852C6DD-1C8D-417B-89B9-CA30D4C924C5}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {44B6AC94-3745-48DC-B59D-E64FB27666CC} + EndGlobalSection +EndGlobal diff --git a/WinPowerDMS/WinPowerDMS.c b/WinPowerDMS/WinPowerDMS.c new file mode 100644 index 0000000..ca3963d --- /dev/null +++ b/WinPowerDMS/WinPowerDMS.c @@ -0,0 +1,298 @@ +#include +#include +#include +#include + +#include "resource.h" + +// Link Common Controls v6 for visual styling +#pragma comment(linker,"\"/manifestdependency:type='win32' \ +name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \ +processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") + +#define WM_TRAYICON (WM_USER + 1) +#define TRAY_ICON_ID 1001 + +typedef struct { + int width; + int height; + int refresh; +} DISPLAY_MODE; + +static DISPLAY_MODE modeBatt = { 0 }; +static DISPLAY_MODE modeAC = { 0 }; + +BOOL DisplayModeEquals(const DISPLAY_MODE* a, const DISPLAY_MODE* b) { + return a->width == b->width && a->height == b->height && a->refresh == b->refresh; +} + +#define MODE_SELECTED(mode) (!DisplayModeEquals(&mode, &(DISPLAY_MODE) { 0 })) + +// I hate parsing the string here, but the alternative requires extra memory management. +static DISPLAY_MODE GetModeFromCB(HWND hDlg, int nIDDlgItem) { + DISPLAY_MODE mode = { 0 }; + HWND hComboBox = GetDlgItem(hDlg, nIDDlgItem); + size_t selectedIndex = SendMessage(hComboBox, CB_GETCURSEL, 0, 0); + if (selectedIndex != CB_ERR) { + LRESULT len = SendMessage(hComboBox, CB_GETLBTEXTLEN, selectedIndex, 0); + if (len != CB_ERR) { + LPWSTR text = HeapAlloc(GetProcessHeap(), 0, len * sizeof(*text)); + if (text) { + SendMessage(hComboBox, CB_GETLBTEXT, selectedIndex, (LPARAM) text); + swscanf_s(text, L"%dx%d @ %d Hz", &mode.width, &mode.height, &mode.refresh); + HeapFree(GetProcessHeap(), 0, text); + } + } + } + + return mode; +} + +// returns the result of the ChangeDisplaySettings call that this results in. +static LONG ChangeDisplayMode(const DISPLAY_MODE* mode, DWORD dwFlags) { + DEVMODE devMode = { + .dmSize = sizeof(devMode), + .dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY, + .dmPelsWidth = mode->width, + .dmPelsHeight = mode->height, + .dmDisplayFrequency = mode->refresh + }; + + return ChangeDisplaySettings(&devMode, dwFlags); +} + +struct MessageBoxParams { + HWND hWnd; + LPCWSTR lpText; + LPCWSTR lpCaption; + UINT uType; +}; + +static DWORD WINAPI MessageBoxAsync(LPVOID lpParam) { + struct MessageBoxParams* params = lpParam; + return MessageBox(params->hWnd, params->lpText, params->lpCaption, params->uType); +} + +struct TestDisplayModeParams { + HWND hDlg; + DISPLAY_MODE mode; +}; + +static DWORD WINAPI TestDisplayModeThread(LPVOID lpParam) { + struct TestDisplayModeParams* params = lpParam; + DEVMODE originalMode = { .dmSize = sizeof(originalMode) }; + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &originalMode); + ChangeDisplayMode(¶ms->mode, CDS_FULLSCREEN); + + // Create the message box about the resolution change + WCHAR msgText[96]; + swprintf_s(msgText, sizeof(msgText) / sizeof(msgText[0]), + L"Testing %dx%d @ %d Hz\nThe display mode will reset back in 10 seconds.", + params->mode.width, params->mode.height, params->mode.refresh); + struct MessageBoxParams mbParams = { + .hWnd = params->hDlg, + .lpText = msgText, + .lpCaption = L"Resolution Test", + .uType = MB_OK | MB_ICONINFORMATION + }; + HANDLE mbThread = CreateThread(NULL, 0, MessageBoxAsync, &mbParams, 0, NULL); + if (mbThread) { + CloseHandle(mbThread); + Sleep(10000); + ChangeDisplaySettings(&originalMode, 0); + } + else { + MessageBox(params->hDlg, L"Failed to test resolution.", L"Error", MB_OK | MB_ICONERROR); + } + HeapFree(GetProcessHeap(), 0, params); + return 0; +} + +static void TestDisplayMode(HWND hDlg, DISPLAY_MODE* mode) { + struct TestDisplayModeParams* tdmParams = HeapAlloc(GetProcessHeap(), 0, sizeof(*tdmParams)); + if (tdmParams) { + tdmParams->hDlg = hDlg; + tdmParams->mode = *mode; + HANDLE tdmThread = CreateThread(NULL, 0, TestDisplayModeThread, tdmParams, 0, NULL); + if (tdmThread) CloseHandle(tdmThread); + else { + HeapFree(GetProcessHeap(), 0, tdmParams); + MessageBox(hDlg, L"Failed to test resolution.", L"Error", MB_OK | MB_ICONERROR); + } + } + else { + MessageBox(hDlg, L"Failed to test resolution.", L"Error", MB_OK | MB_ICONERROR); + } +} + +static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_INITDIALOG: { + HWND hComboBatt = GetDlgItem(hDlg, IDC_COMBO_BATT); + HWND hComboAC = GetDlgItem(hDlg, IDC_COMBO_AC); + + // devMode object that will be enumerated + DEVMODE devMode; + ZeroMemory(&devMode, sizeof(devMode)); + devMode.dmSize = sizeof(devMode); + + size_t modeCount = 0; + int modeNum = 0; + DISPLAY_MODE lastMode = { 0 }; + while (EnumDisplaySettings(NULL, modeNum++, &devMode)) { + DISPLAY_MODE currentMode = { + .width = devMode.dmPelsWidth, + .height = devMode.dmPelsHeight, + .refresh = devMode.dmDisplayFrequency + }; + + if (!DisplayModeEquals(¤tMode, &lastMode)) { + // Format resolution as "WidthxHeight @ Refresh Hz" + WCHAR resText[32]; + swprintf_s(resText, sizeof(resText) / sizeof(resText[0]), L"%dx%d @ %d Hz", + currentMode.width, currentMode.height, currentMode.refresh); + + // Add to both combo boxes and check if this is the mode that was set. + SendMessage(hComboBatt, CB_ADDSTRING, 0, (LPARAM)resText); + if (DisplayModeEquals(&modeBatt, ¤tMode)) + SendMessage(hComboBatt, CB_SETCURSEL, modeCount, 0); + + SendMessage(hComboAC, CB_ADDSTRING, 0, (LPARAM)resText); + if (DisplayModeEquals(&modeAC, ¤tMode)) + SendMessage(hComboAC, CB_SETCURSEL, modeCount, 0); + + ++modeCount; + lastMode = currentMode; + } + } + + // select the highest display mode if one hasn't been selected yet + if (!MODE_SELECTED(modeBatt)) SendMessage(hComboBatt, CB_SETCURSEL, modeCount - 1, 0); + if (!MODE_SELECTED(modeAC)) SendMessage(hComboAC, CB_SETCURSEL, modeCount - 1, 0); + return TRUE; + } + + case WM_COMMAND: { + switch (LOWORD(wParam)) { + case IDC_BUTTON_APPLY: + case IDOK: { + modeBatt = GetModeFromCB(hDlg, IDC_COMBO_BATT); + modeAC = GetModeFromCB(hDlg, IDC_COMBO_AC); + if (LOWORD(wParam) == IDC_BUTTON_APPLY) return TRUE; + } + case IDCANCEL: { + EndDialog(hDlg, LOWORD(wParam)); + return TRUE; + } + case IDC_BUTTON_TEST_BATT: { + DISPLAY_MODE mode = GetModeFromCB(hDlg, IDC_COMBO_BATT); + TestDisplayMode(hDlg, &mode); + return TRUE; + } + case IDC_BUTTON_TEST_AC: { + DISPLAY_MODE mode = GetModeFromCB(hDlg, IDC_COMBO_AC); + TestDisplayMode(hDlg, &mode); + return TRUE; + } + break; + } + } + } + return FALSE; +} + +enum TRAY_IDS { + ID_TRAY_PREFS = 2001, + ID_TRAY_ABOUT, + ID_TRAY_EXIT +}; + +static NOTIFYICONDATA nid; +static HWND hWnd; +static HMENU hMenu; + +static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_TRAYICON: + if (lParam == WM_RBUTTONUP) { + POINT pt; + GetCursorPos(&pt); + SetForegroundWindow(hwnd); // Required for menu to disappear correctly + + TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL); + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case ID_TRAY_PREFS: + DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_PREFSDIALOG), hWnd, PrefsDialogProc); + break; + case ID_TRAY_ABOUT: + MessageBoxW(NULL, L"WinPowerDMS\nA utility for switching the resolution of your laptop's display based on the current power state.", L"About", MB_OK | MB_ICONINFORMATION); + break; + case ID_TRAY_EXIT: + DestroyWindow(hwnd); + break; + } + break; + + case WM_DESTROY: + Shell_NotifyIcon(NIM_DELETE, &nid); + PostQuitMessage(0); + break; + + case WM_POWERBROADCAST: + if (LOWORD(wParam) == PBT_POWERSETTINGCHANGE) { + OutputDebugString(L"Power status changed\n"); + SYSTEM_POWER_STATUS powerStatus; + if (GetSystemPowerStatus(&powerStatus)) { + ChangeDisplayMode(powerStatus.ACLineStatus ? &modeAC : &modeBatt, CDS_UPDATEREGISTRY); + } + } + break; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { + // Initialize the controls + InitCommonControls(); + + WNDCLASS wc = { 0 }; + wc.lpfnWndProc = WindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = L"TrayAppClass"; + + RegisterClass(&wc); + + hWnd = CreateWindowEx(0, L"TrayAppClass", NULL, 0, 0, 0, 0, 0, + HWND_MESSAGE, NULL, hInstance, NULL); + RegisterPowerSettingNotification(hWnd, &GUID_ACDC_POWER_SOURCE, DEVICE_NOTIFY_WINDOW_HANDLE); + + // Create context menu + hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MF_STRING, ID_TRAY_PREFS, L"Preferences"); + AppendMenu(hMenu, MF_STRING, ID_TRAY_ABOUT, L"About"); + AppendMenu(hMenu, MF_STRING, ID_TRAY_EXIT, L"Exit"); + + // Setup tray icon + nid.cbSize = sizeof(NOTIFYICONDATA); + nid.hWnd = hWnd; + nid.uID = TRAY_ICON_ID; + nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; + nid.uCallbackMessage = WM_TRAYICON; + nid.hIcon = LoadIcon(NULL, IDI_APPLICATION); // Replace this with my own icon at some point + wcscpy_s(nid.szTip, sizeof(nid.szTip) / sizeof(nid.szTip[0]), L"WinPowerDMS"); + + Shell_NotifyIcon(NIM_ADD, &nid); + + // Message loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + return 0; +} \ No newline at end of file diff --git a/WinPowerDMS/WinPowerDMS.rc b/WinPowerDMS/WinPowerDMS.rc new file mode 100644 index 0000000000000000000000000000000000000000..67606d69a225fcc20c9a1cc28e56ca313951a81e GIT binary patch literal 5516 zcmd^@+iuf95QgU(iFYvW4Ty*~DbRA6ItQXAA!^b>rAiT^1S*9THLU=NX9vE2H;e7W zNoheS2wC>7_b|tQX4d}xZPPX_vW}hE8@sS`n{torlCdMkrgmkoZOmI@54j?HXK!u7 zeZlx8?I~jywAyz??+yKkH&HvYkF0rQckK>bHeI{H=!DrZt54n9cwzq|dvCE@t(_Zg z-N@||?W*;xZv#8DnzihO4eh|{jMnIPt!>AQ^m%q{kC8V0z+W<~-xZ__ZDdXCGH>G% zRywy2=zocIWQPG-zv9_K>jXVzk2>QO?s<<~?}JayzJ6cc{_AJ4AdyGM6}A$RM(BMz zYy`^(y6e>j=_0&4A^ygAY6=D-yN}N&yoEUI)ALa|wFd<#m}X+?%-QQ+F1AjgShWVI z@-fvXeh!JQ1kOXy)#ei2uUyg6odakbfZ-v#y6k`8xV9PXGI~hcbT|zoeq9sVEn@Y# zm7v{c-5w);&-{uZvp%Xl0;J1w1Smd^&}1L3wSO)b6i0c$gj(HYY*xH7a`SuW-gKEl ztE1UfoOw@dYnFZGKW{PZQeL?YkD`np+hV?n?S#B_0-}BHPo-QY(e^DRx+$Lxmyb`0 zH02n7DMu9Ln?>%zd*7iIP1W*acB^*PcvJPRGuD7!msa^E%yby5IbIF8SE=#Z zmCiTd{faTsIp+3rFCEn$dwXP#UT!pjN)IkNNpy2Fo`N_ii%HV?wJ|$YF-P2Lb*|A# z$m2Jaf7j_7BdsLryZSwLr>YvycdVCv9%G@brMB!daV@^Z(W`3Ta2hFV?jhrV%WENw z#`O0cZFx-nlI?Im;nGvQ%5W8jfNI&Nn3`V^V}sW-J;y(3%Pu;D)bU~ouS(xlT-wO# z&n|&)zKzjFbxd{b!oFnq2pg&|@>c>E+01KIaa!%>v`Ns~p8|Cf6cY27?2>@Dblnin zqCWUZwM=gpJ|)|Byb@Ljqc!j+Jjt>z(cV|DfL4#VBUUDEohoM*i_$@K%=Id=! zg=WOAb8@KcBlHAK^42Mnvnw-Awvk_&WWAm0{pj9tGPoY$mA8a) zVh_w7JB=&&yEvlrCj?4jNU*#%aF_ zQ!GZ+mgAQC!^exxK}BcfKlAmvUO6QzjkM3+K69-4H?jDwkfpa?eQ73%Li@Xasr2o( f`Y% literal 0 HcmV?d00001 diff --git a/WinPowerDMS/WinPowerDMS.vcxproj b/WinPowerDMS/WinPowerDMS.vcxproj new file mode 100644 index 0000000..e9c8385 --- /dev/null +++ b/WinPowerDMS/WinPowerDMS.vcxproj @@ -0,0 +1,142 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {c852c6dd-1c8d-417b-89b9-ca30d4c924c5} + WinPowerDMS + 10.0 + WinPowerDMS + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Windows + true + ComCtl32.lib;%(AdditionalDependencies) + + + PerMonitorHighDPIAware + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WinPowerDMS/WinPowerDMS.vcxproj.filters b/WinPowerDMS/WinPowerDMS.vcxproj.filters new file mode 100644 index 0000000..7040bfa --- /dev/null +++ b/WinPowerDMS/WinPowerDMS.vcxproj.filters @@ -0,0 +1,32 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/WinPowerDMS/resource.h b/WinPowerDMS/resource.h new file mode 100644 index 0000000..4a437cb --- /dev/null +++ b/WinPowerDMS/resource.h @@ -0,0 +1,22 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by PowerDMS.rc +// +#define IDD_PREFSDIALOG 101 +#define IDC_COMBO_BATT 1001 +#define IDC_COMBO_AC 1002 +#define IDC_BUTTON_TEST_BATT 1003 +#define IDC_BUTTON_TEST_AC 1004 +#define IDC_BUTTON_APPLY 1005 +#define IDC_LIST1 1006 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 104 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1007 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif