Compare commits

...

5 Commits

Author SHA1 Message Date
350e2960b8 Add functionality for running at startup and disabling the battery warning 2025-08-09 16:03:40 -04:00
036a20df3f Display a warning when there is no battery detected 2025-08-09 14:32:15 -04:00
6a586dedb7 Only change video mode if the current video mode matches one of the preferences.
This avoids the program resetting the video mode if you're doing something like testing different resolutions or playing a game in fullscreen.
2025-08-09 13:14:10 -04:00
4b46eafd35 Save and load preferences using the registry
Preferences are now saved and loaded using the registry. They are loaded at application startup and written to the registry every time a change is made. If no preferences are present in the registry yet, the current display mode is used for both battery and AC power.
2025-08-09 11:56:59 -04:00
046e8fd2eb Actually enter name in the license. 2025-08-09 11:52:05 -04:00
5 changed files with 136 additions and 39 deletions

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) [year] [fullname]
Copyright (c) 2025 Mina Brown
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -7,10 +7,10 @@ Currently a work in progress. Not very useful yet since it doesn't save your pre
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.
- [x] Save preferences to the Windows registry.
- [x] 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.
- [x] Warn the user when the system has no battery.
- [x] Make the default display mode the current one instead of the highest resolution if there are no preferences set.
- [ ] Make an actual about dialog.

View File

@ -14,14 +14,11 @@ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#define TRAY_ICON_ID 1001
typedef struct {
int width;
int height;
int refresh;
DWORD width;
DWORD height;
DWORD 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;
}
@ -29,9 +26,8 @@ BOOL DisplayModeEquals(const DISPLAY_MODE* a, const DISPLAY_MODE* b) {
#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) {
static DISPLAY_MODE GetModeFromCB(HWND hComboBox) {
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);
@ -125,12 +121,96 @@ static void TestDisplayMode(HWND hDlg, DISPLAY_MODE* mode) {
}
}
typedef struct {
DISPLAY_MODE modeBatt;
DISPLAY_MODE modeAC;
DWORD disableBatteryWarning;
BOOL runAtStartup;
} WINPOWERDMS_PREFS;
static WINPOWERDMS_PREFS userPrefs = { 0 };
// Save user preferences to registry.
static BOOL SavePrefs(void) {
BOOL saved = FALSE;
HKEY regKey;
if (!RegCreateKeyEx(
HKEY_CURRENT_USER, L"SOFTWARE\\WinPowerDMS", 0, NULL,
REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &regKey, NULL
)) {
RegSetKeyValue(regKey, L"Battery", L"Width", REG_DWORD, &userPrefs.modeBatt.width, sizeof(userPrefs.modeBatt.width));
RegSetKeyValue(regKey, L"Battery", L"Height", REG_DWORD, &userPrefs.modeBatt.height, sizeof(userPrefs.modeBatt.height));
RegSetKeyValue(regKey, L"Battery", L"Refresh Rate", REG_DWORD, &userPrefs.modeBatt.refresh, sizeof(userPrefs.modeBatt.refresh));
RegSetKeyValue(regKey, L"AC Power", L"Width", REG_DWORD, &userPrefs.modeAC.width, sizeof(userPrefs.modeAC.width));
RegSetKeyValue(regKey, L"AC Power", L"Height", REG_DWORD, &userPrefs.modeAC.height, sizeof(userPrefs.modeAC.height));
RegSetKeyValue(regKey, L"AC Power", L"Refresh Rate", REG_DWORD, &userPrefs.modeAC.refresh, sizeof(userPrefs.modeAC.refresh));
RegSetValueEx(regKey, L"Disable Battery Warning", 0, REG_DWORD, &userPrefs.disableBatteryWarning, sizeof(userPrefs.disableBatteryWarning));
RegCloseKey(regKey);
saved = TRUE;
}
// Create the startup entry
if (userPrefs.runAtStartup) {
WCHAR exePath[MAX_PATH];
DWORD exePathLen = GetModuleFileName(NULL, exePath, sizeof(exePath) / sizeof(exePath[0])) + 1;
RegSetKeyValue(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", L"WinPowerDMS", REG_SZ, exePath, exePathLen);
}
else { // Delete the startup entry
RegDeleteKeyValue(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", L"WinPowerDMS");
}
return saved;
}
// Load user preferences from registry.
static BOOL LoadPrefs(void) {
BOOL loaded = FALSE;
HKEY regKey;
if (!RegOpenKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\WinPowerDMS", 0, KEY_READ, &regKey)) {
DWORD keySize = sizeof(userPrefs.modeBatt.width);
RegGetValue(regKey, L"Battery", L"Width", RRF_RT_REG_DWORD, NULL, &userPrefs.modeBatt.width, &keySize);
keySize = sizeof(userPrefs.modeBatt.height);
RegGetValue(regKey, L"Battery", L"Height", RRF_RT_REG_DWORD, NULL, &userPrefs.modeBatt.height, &keySize);
keySize = sizeof(userPrefs.modeBatt.refresh);
RegGetValue(regKey, L"Battery", L"Refresh Rate", RRF_RT_REG_DWORD, NULL, &userPrefs.modeBatt.refresh, &keySize);
keySize = sizeof(userPrefs.modeAC.width);
RegGetValue(regKey, L"AC Power", L"Width", RRF_RT_REG_DWORD, NULL, &userPrefs.modeAC.width, &keySize);
keySize = sizeof(userPrefs.modeAC.height);
RegGetValue(regKey, L"AC Power", L"Height", RRF_RT_REG_DWORD, NULL, &userPrefs.modeAC.height, &keySize);
keySize = sizeof(userPrefs.modeAC.refresh);
RegGetValue(regKey, L"AC Power", L"Refresh Rate", RRF_RT_REG_DWORD, NULL, &userPrefs.modeAC.refresh, &keySize);
keySize = sizeof(userPrefs.disableBatteryWarning);
RegGetValue(regKey, NULL, L"Disable Battery Warning", RRF_RT_REG_DWORD, NULL, &userPrefs.disableBatteryWarning, &keySize);
RegCloseKey(regKey);
loaded = TRUE;
}
// Figure out if application has a startup entry
if (!RegGetValue(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", L"WinPowerDMS", RRF_RT_ANY, NULL, NULL, NULL))
userPrefs.runAtStartup = TRUE;
return loaded;
}
static DISPLAY_MODE GetCurrentDisplayMode(void) {
DEVMODE currentMode = { .dmSize = sizeof(currentMode) };
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &currentMode);
return (DISPLAY_MODE) {
.width = currentMode.dmPelsWidth,
.height = currentMode.dmPelsHeight,
.refresh = currentMode.dmDisplayFrequency
};
}
static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) {
HWND hComboBatt = GetDlgItem(hDlg, IDC_COMBO_BATT);
HWND hComboAC = GetDlgItem(hDlg, IDC_COMBO_AC);
HWND hCheckBattWarning = GetDlgItem(hDlg, IDC_CHECK_BATT_WARNING);
HWND hCheckStartup = GetDlgItem(hCheckBattWarning, IDC_CHECK_STARTUP);
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));
@ -154,11 +234,11 @@ static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT message, WPARAM wParam,
// 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, &currentMode))
if (DisplayModeEquals(&userPrefs.modeBatt, &currentMode))
SendMessage(hComboBatt, CB_SETCURSEL, modeCount, 0);
SendMessage(hComboAC, CB_ADDSTRING, 0, (LPARAM)resText);
if (DisplayModeEquals(&modeAC, &currentMode))
if (DisplayModeEquals(&userPrefs.modeAC, &currentMode))
SendMessage(hComboAC, CB_SETCURSEL, modeCount, 0);
++modeCount;
@ -166,9 +246,8 @@ static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT message, WPARAM wParam,
}
}
// 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);
SendMessage(hCheckBattWarning, BM_SETCHECK, userPrefs.disableBatteryWarning ? BST_CHECKED : BST_UNCHECKED, 0);
SendMessage(hCheckStartup, BM_SETCHECK, userPrefs.runAtStartup ? BST_CHECKED : BST_UNCHECKED, 0);
return TRUE;
}
@ -176,8 +255,11 @@ static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT message, WPARAM wParam,
switch (LOWORD(wParam)) {
case IDC_BUTTON_APPLY:
case IDOK: {
modeBatt = GetModeFromCB(hDlg, IDC_COMBO_BATT);
modeAC = GetModeFromCB(hDlg, IDC_COMBO_AC);
userPrefs.modeBatt = GetModeFromCB(hComboBatt);
userPrefs.modeAC = GetModeFromCB(hComboAC);
userPrefs.disableBatteryWarning = SendMessage(hCheckBattWarning, BM_GETCHECK, 0, 0) == BST_CHECKED;
userPrefs.runAtStartup = SendMessage(hCheckStartup, BM_GETCHECK, 0, 0) == BST_CHECKED;
SavePrefs();
if (LOWORD(wParam) == IDC_BUTTON_APPLY) return TRUE;
}
case IDCANCEL: {
@ -185,12 +267,12 @@ static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT message, WPARAM wParam,
return TRUE;
}
case IDC_BUTTON_TEST_BATT: {
DISPLAY_MODE mode = GetModeFromCB(hDlg, IDC_COMBO_BATT);
DISPLAY_MODE mode = GetModeFromCB(hComboBatt);
TestDisplayMode(hDlg, &mode);
return TRUE;
}
case IDC_BUTTON_TEST_AC: {
DISPLAY_MODE mode = GetModeFromCB(hDlg, IDC_COMBO_AC);
DISPLAY_MODE mode = GetModeFromCB(hComboAC);
TestDisplayMode(hDlg, &mode);
return TRUE;
}
@ -208,18 +290,17 @@ enum TRAY_IDS {
};
static NOTIFYICONDATA nid;
static HWND hWnd;
static HMENU hMenu;
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
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
SetForegroundWindow(hWnd); // Required for menu to disappear correctly
TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, pt.x, pt.y, 0, hWnd, NULL);
}
break;
@ -229,10 +310,10 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l
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);
MessageBoxW(hWnd, 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);
DestroyWindow(hWnd);
break;
}
break;
@ -245,19 +326,28 @@ static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM l
case WM_POWERBROADCAST:
if (LOWORD(wParam) == PBT_POWERSETTINGCHANGE) {
OutputDebugString(L"Power status changed\n");
DISPLAY_MODE currentMode = GetCurrentDisplayMode();
SYSTEM_POWER_STATUS powerStatus;
if (GetSystemPowerStatus(&powerStatus)) {
ChangeDisplayMode(powerStatus.ACLineStatus ? &modeAC : &modeBatt, CDS_UPDATEREGISTRY);
// Change display mode only if the current display mode is one of the configured options.
if (GetSystemPowerStatus(&powerStatus) &&
(DisplayModeEquals(&currentMode, &userPrefs.modeBatt) || DisplayModeEquals(&currentMode, &userPrefs.modeAC)))
{
ChangeDisplayMode(powerStatus.ACLineStatus ? &userPrefs.modeAC : &userPrefs.modeBatt, CDS_UPDATEREGISTRY);
}
}
break;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// Initialize the controls
InitCommonControls();
if (!LoadPrefs()) { // set both battery and AC to current display mode if there are no preferences set
DISPLAY_MODE currentMode = GetCurrentDisplayMode();
userPrefs.modeAC = currentMode;
userPrefs.modeBatt = currentMode;
}
InitCommonControls(); // Initialize modern controls
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WindowProc;
@ -266,9 +356,14 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
RegisterClass(&wc);
hWnd = CreateWindowEx(0, L"TrayAppClass", NULL, 0, 0, 0, 0, 0,
HWND 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);
// Check if there is a battery in the system and display a warning message if there isn't one.
SYSTEM_POWER_STATUS powerStatus;
if (!userPrefs.disableBatteryWarning && GetSystemPowerStatus(&powerStatus) && powerStatus.BatteryFlag == 128)
MessageBox(hWnd, L"There is no battery present in the system. The program will start and set your display mode to what you have set for AC power, but do nothing else afterwards.", L"WinPowerDMS", MB_OK | MB_ICONWARNING);
// Create context menu
hMenu = CreatePopupMenu();

Binary file not shown.

View File

@ -1,6 +1,6 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by PowerDMS.rc
// Used by WinPowerDMS.rc
//
#define IDD_PREFSDIALOG 101
#define IDC_COMBO_BATT 1001
@ -9,6 +9,8 @@
#define IDC_BUTTON_TEST_AC 1004
#define IDC_BUTTON_APPLY 1005
#define IDC_LIST1 1006
#define IDC_CHECK_BATT_WARNING 1007
#define IDC_CHECK_STARTUP 1008
// Next default values for new objects
//
@ -16,7 +18,7 @@
#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_CONTROL_VALUE 1008
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif