Compare commits

...

8 Commits

Author SHA1 Message Date
d29f57fb13 Change all instances of DEVMODE to DEVMODEW since the program is unicode 2025-08-15 22:38:03 -07:00
5048cafb9f Add display name to display mode changing and testing functions 2025-08-15 19:20:12 -07:00
0f66725951 Separate DISPLAY_MODE struct and associated functions into another file. 2025-08-15 18:36:55 -07:00
863471e08e Add UI items for selecting a display 2025-08-15 18:32:47 -07:00
cedb800012 Fix annotations for WinMain
Fix "C28251 Inconsistent annotation for function" compiler warning by correctly defining WinMain.
2025-08-15 11:44:51 -07:00
5911ca0100 Update README.md
Update the readme to reflect the current state of the project. The program is much more complete now, and has been tested and works flawlessly on both Windows Vista and 7.
2025-08-14 23:20:15 -07:00
e4157e70b3 Fix 32-bit profiles 2025-08-12 12:40:59 -04:00
a5555b4ee1 Guarantee Vista support
Define NT version headers so that Windows Vista support should reasonably be guaranteed, since we will not be using any newer APIs.
2025-08-12 09:16:34 -04:00
8 changed files with 162 additions and 122 deletions

View File

@ -2,9 +2,9 @@
A utility for switching the resolution of your laptop's display based on the current power state. 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. Currently a work in progress. It works reasonably well for a standard single display configuration, but other scenarios have not been extensively tested, and there are some edge cases that may need to be worked out.
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. This is a simple C program written using Visual Studio 2022 that has no external dependencies. It supports Windows Vista or higher.
## TODO ## TODO
- [x] Save preferences to the Windows registry. - [x] Save preferences to the Windows registry.

109
WinPowerDMS/DisplayMode.c Normal file
View File

@ -0,0 +1,109 @@
#include <stdio.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include "DisplayMode.h"
BOOL DisplayModeEquals(const DISPLAY_MODE* a, const DISPLAY_MODE* b) {
return a->width == b->width && a->height == b->height && a->refresh == b->refresh;
}
// I hate parsing the string here, but the alternative requires extra memory management.
DISPLAY_MODE GetModeFromCB(HWND hComboBox) {
DISPLAY_MODE mode = { 0 };
LRESULT 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.
LONG ChangeDisplayMode(LPCWSTR displayName, const DISPLAY_MODE* mode, DWORD dwFlags) {
DEVMODEW devMode = {
.dmSize = sizeof(devMode),
.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_DISPLAYFREQUENCY,
.dmPelsWidth = mode->width,
.dmPelsHeight = mode->height,
.dmDisplayFrequency = mode->refresh
};
if (displayName) wcscpy_s(devMode.dmDeviceName, sizeof(devMode.dmDeviceName) / sizeof(devMode.dmDeviceName[0]), displayName);
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;
WCHAR displayName[32];
DISPLAY_MODE mode;
};
static DWORD WINAPI TestDisplayModeThread(LPVOID lpParam) {
struct TestDisplayModeParams* params = lpParam;
DEVMODEW originalMode = { .dmSize = sizeof(originalMode) };
EnumDisplaySettings(params->displayName, ENUM_CURRENT_SETTINGS, &originalMode);
ChangeDisplayMode(params->displayName, &params->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;
}
void TestDisplayMode(HWND hDlg, LPCWSTR displayName, DISPLAY_MODE* mode) {
struct TestDisplayModeParams* tdmParams = HeapAlloc(GetProcessHeap(), 0, sizeof(*tdmParams));
if (tdmParams) {
tdmParams->hDlg = hDlg;
if (tdmParams->displayName) wcscpy_s(tdmParams->displayName, sizeof(tdmParams->displayName) / sizeof(tdmParams->displayName[0]), displayName);
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);
}
}

19
WinPowerDMS/DisplayMode.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef struct {
DWORD width;
DWORD height;
DWORD refresh;
} DISPLAY_MODE;
BOOL DisplayModeEquals(const DISPLAY_MODE* a, const DISPLAY_MODE* b);
DISPLAY_MODE GetModeFromCB(HWND hComboBox);
// returns the result of the ChangeDisplaySettings call that this results in.
LONG ChangeDisplayMode(LPCWSTR displayName, const DISPLAY_MODE* mode, DWORD dwFlags);
void TestDisplayMode(HWND hDlg, LPCWSTR displayName, DISPLAY_MODE* mode);

View File

@ -1,9 +1,14 @@
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT _WIN32_WINNT_VISTA
#define NTDDI_VERSION NTDDI_VISTA
#include <stdio.h> #include <stdio.h>
#include <windows.h> #include <windows.h>
#include <shellapi.h> #include <shellapi.h>
#include <commctrl.h> #include <commctrl.h>
#include "resource.h" #include "resource.h"
#include "DisplayMode.h"
// Link Common Controls v6 for visual styling // Link Common Controls v6 for visual styling
#pragma comment(linker,"\"/manifestdependency:type='win32' \ #pragma comment(linker,"\"/manifestdependency:type='win32' \
@ -13,114 +18,6 @@ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#define WM_TRAYICON (WM_USER + 1) #define WM_TRAYICON (WM_USER + 1)
#define TRAY_ICON_ID 1001 #define TRAY_ICON_ID 1001
typedef struct {
DWORD width;
DWORD height;
DWORD refresh;
} DISPLAY_MODE;
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 hComboBox) {
DISPLAY_MODE mode = { 0 };
LRESULT 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(&params->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);
}
}
typedef struct { typedef struct {
DISPLAY_MODE modeBatt; DISPLAY_MODE modeBatt;
DISPLAY_MODE modeAC; DISPLAY_MODE modeAC;
@ -190,7 +87,7 @@ static BOOL LoadPrefs(void) {
} }
static DISPLAY_MODE GetCurrentDisplayMode(void) { static DISPLAY_MODE GetCurrentDisplayMode(void) {
DEVMODE currentMode = { .dmSize = sizeof(currentMode) }; DEVMODEW currentMode = { .dmSize = sizeof(currentMode) };
EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &currentMode); EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &currentMode);
return (DISPLAY_MODE) { return (DISPLAY_MODE) {
.width = currentMode.dmPelsWidth, .width = currentMode.dmPelsWidth,
@ -207,7 +104,7 @@ static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPA
switch (uMsg) { switch (uMsg) {
case WM_INITDIALOG: { case WM_INITDIALOG: {
// devMode object that will be enumerated // devMode object that will be enumerated
DEVMODE devMode; DEVMODEW devMode;
ZeroMemory(&devMode, sizeof(devMode)); ZeroMemory(&devMode, sizeof(devMode));
devMode.dmSize = sizeof(devMode); devMode.dmSize = sizeof(devMode);
@ -261,12 +158,12 @@ static INT_PTR CALLBACK PrefsDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPA
} }
case IDC_BUTTON_TEST_BATT: { case IDC_BUTTON_TEST_BATT: {
DISPLAY_MODE mode = GetModeFromCB(hComboBatt); DISPLAY_MODE mode = GetModeFromCB(hComboBatt);
TestDisplayMode(hDlg, &mode); TestDisplayMode(hDlg, NULL, &mode);
return TRUE; return TRUE;
} }
case IDC_BUTTON_TEST_AC: { case IDC_BUTTON_TEST_AC: {
DISPLAY_MODE mode = GetModeFromCB(hComboAC); DISPLAY_MODE mode = GetModeFromCB(hComboAC);
TestDisplayMode(hDlg, &mode); TestDisplayMode(hDlg, NULL, &mode);
return TRUE; return TRUE;
} }
break; break;
@ -325,7 +222,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
if (GetSystemPowerStatus(&powerStatus) && if (GetSystemPowerStatus(&powerStatus) &&
(DisplayModeEquals(&currentMode, &userPrefs.modeBatt) || DisplayModeEquals(&currentMode, &userPrefs.modeAC))) (DisplayModeEquals(&currentMode, &userPrefs.modeBatt) || DisplayModeEquals(&currentMode, &userPrefs.modeAC)))
{ {
ChangeDisplayMode(powerStatus.ACLineStatus ? &userPrefs.modeAC : &userPrefs.modeBatt, CDS_UPDATEREGISTRY); ChangeDisplayMode(NULL, powerStatus.ACLineStatus ? &userPrefs.modeAC : &userPrefs.modeBatt, CDS_UPDATEREGISTRY);
} }
} }
break; break;
@ -333,7 +230,7 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
return DefWindowProc(hWnd, uMsg, wParam, lParam); return DefWindowProc(hWnd, uMsg, wParam, lParam);
} }
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nShowCmd) {
if (!LoadPrefs()) { // set both battery and AC to current display mode if there are no preferences set if (!LoadPrefs()) { // set both battery and AC to current display mode if there are no preferences set
DISPLAY_MODE currentMode = GetCurrentDisplayMode(); DISPLAY_MODE currentMode = GetCurrentDisplayMode();
userPrefs.modeAC = currentMode; userPrefs.modeAC = currentMode;

Binary file not shown.

View File

@ -82,9 +82,10 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<MinimumRequiredVersion>6.0</MinimumRequiredVersion>
</Link> </Link>
<Manifest> <Manifest>
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness> <EnableDpiAwareness>true</EnableDpiAwareness>
</Manifest> </Manifest>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
@ -100,9 +101,10 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<MinimumRequiredVersion>6.0</MinimumRequiredVersion>
</Link> </Link>
<Manifest> <Manifest>
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness> <EnableDpiAwareness>true</EnableDpiAwareness>
</Manifest> </Manifest>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
@ -116,9 +118,10 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<MinimumRequiredVersion>6.0</MinimumRequiredVersion>
</Link> </Link>
<Manifest> <Manifest>
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness> <EnableDpiAwareness>true</EnableDpiAwareness>
</Manifest> </Manifest>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
@ -134,15 +137,18 @@
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation> <GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies> <AdditionalDependencies>ComCtl32.lib;%(AdditionalDependencies)</AdditionalDependencies>
<MinimumRequiredVersion>6.0</MinimumRequiredVersion>
</Link> </Link>
<Manifest> <Manifest>
<EnableDpiAwareness>PerMonitorHighDPIAware</EnableDpiAwareness> <EnableDpiAwareness>true</EnableDpiAwareness>
</Manifest> </Manifest>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="DisplayMode.c" />
<ClCompile Include="WinPowerDMS.c" /> <ClCompile Include="WinPowerDMS.c" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="DisplayMode.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -18,11 +18,17 @@
<ClCompile Include="WinPowerDMS.c"> <ClCompile Include="WinPowerDMS.c">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="DisplayMode.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="resource.h"> <ClInclude Include="resource.h">
<Filter>Header Files</Filter> <Filter>Header Files</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="DisplayMode.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ResourceCompile Include="WinPowerDMS.rc"> <ResourceCompile Include="WinPowerDMS.rc">

View File

@ -11,6 +11,9 @@
#define IDC_BUTTON_APPLY 1005 #define IDC_BUTTON_APPLY 1005
#define IDC_LIST1 1006 #define IDC_LIST1 1006
#define IDC_CHECK_STARTUP 1007 #define IDC_CHECK_STARTUP 1007
#define IDC_COMBO_DISPLAY 1008
#define IDC_CHECK_ENABLE_DISPLAY 1009
#define IDC_CHECK_DISPLAY 1009
// Next default values for new objects // Next default values for new objects
// //
@ -18,7 +21,7 @@
#ifndef APSTUDIO_READONLY_SYMBOLS #ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 107 #define _APS_NEXT_RESOURCE_VALUE 107
#define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1008 #define _APS_NEXT_CONTROL_VALUE 1012
#define _APS_NEXT_SYMED_VALUE 101 #define _APS_NEXT_SYMED_VALUE 101
#endif #endif
#endif #endif