6
\$\begingroup\$

I recently overclocked my monitor and I am trying to create a script to just click and do the work for me so whenever I want to change my refresh rate I dont have to open windows settings.

I think this works, but I thought I would post this here for any optimizations or any comments you guys that are far more experienced than me probably are going to add.

import ctypes
import time
class DEVMODE(ctypes.Structure):
 _fields_ = [
 ("dmDeviceName", ctypes.c_wchar * 32),
 ("dmSpecVersion", ctypes.c_ushort),
 ("dmDriverVersion", ctypes.c_ushort),
 ("dmSize", ctypes.c_ushort),
 ("dmDriverExtra", ctypes.c_ushort),
 ("dmFields", ctypes.c_ulong),
 ("dmOrientation", ctypes.c_short),
 ("dmPaperSize", ctypes.c_short),
 ("dmPaperLength", ctypes.c_short),
 ("dmPaperWidth", ctypes.c_short),
 ("dmScale", ctypes.c_short),
 ("dmCopies", ctypes.c_short),
 ("dmDefaultSource", ctypes.c_short),
 ("dmPrintQuality", ctypes.c_short),
 ("dmColor", ctypes.c_short),
 ("dmDuplex", ctypes.c_short),
 ("dmYResolution", ctypes.c_short),
 ("dmTTOption", ctypes.c_short),
 ("dmCollate", ctypes.c_short),
 ("dmFormName", ctypes.c_wchar * 32),
 ("dmLogPixels", ctypes.c_ushort),
 ("dmBitsPerPel", ctypes.c_ulong),
 ("dmPelsWidth", ctypes.c_ulong),
 ("dmPelsHeight", ctypes.c_ulong),
 ("dmDisplayFlags", ctypes.c_ulong),
 ("dmDisplayFrequency", ctypes.c_ulong),
 ("dmICMMethod", ctypes.c_ulong),
 ("dmICMIntent", ctypes.c_ulong),
 ("dmMediaType", ctypes.c_ulong),
 ("dmDitherType", ctypes.c_ulong),
 ("dmReserved1", ctypes.c_ulong),
 ("dmReserved2", ctypes.c_ulong),
 ("dmPanningWidth", ctypes.c_ulong),
 ("dmPanningHeight", ctypes.c_ulong)
 ]
def change_refresh_rate(refresh_rate):
 devmode = DEVMODE()
 devmode.dmSize = ctypes.sizeof(DEVMODE)
 if not ctypes.windll.user32.EnumDisplaySettingsW(None, 0, ctypes.byref(devmode)):
 print("Error: Could not retrieve display settings.")
 return
 devmode.dmDisplayFrequency = refresh_rate
 devmode.dmFields = 0x400000 # DM_DISPLAYFREQUENCY
 if ctypes.windll.user32.ChangeDisplaySettingsW(ctypes.byref(devmode), 0) != 0:
 print(f"Error: Could not change to {refresh_rate} Hz.")
 else:
 print(f"Changed to {refresh_rate} Hz.")
def main():
 change_refresh_rate(170) # Change value to your desired
 time.sleep(5)
 change_refresh_rate(144) # Change value to your desired
if __name__ == "__main__":
 main()
Toby Speight
88k14 gold badges104 silver badges325 bronze badges
asked Jun 3, 2024 at 0:47
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

Read What structure packing do the Windows SDK header files expect?. You should translate that packing specification to an explicit setting for _pack_.

Similarly, you should be using an explicit LittleEndianStructure rather than Structure.

It's educational to examine the true definition for DEVMODE in the Windows header. Observe this type-punning union:

 union {
 /* printer only fields */
 struct {
 short dmOrientation;
 short dmPaperSize;
 short dmPaperLength;
 short dmPaperWidth;
 short dmScale;
 short dmCopies;
 short dmDefaultSource;
 short dmPrintQuality;
 };
 /* display only fields */
 struct {
 POINTL dmPosition;
 DWORD dmDisplayOrientation;
 DWORD dmDisplayFixedOutput;
 };
 };

We should trust what it says, and since you're strictly using this structure for display operations, you should not write the printer fields; only write the display fields and add requisite padding. (A thorough API treatment would offer two entirely different structure definitions for these two cases.) Whereas ctypes does have direct Union support, you shouldn't use it in this case; it's less type-safe than writing for one access mode and defining only one parameter struct.

Use more specific ctypes types for the Windows SDK:

The ctypes.wintypes module provides quite some other Windows specific data types, for example HWND, WPARAM, or DWORD. Some useful structures like MSG or RECT are also defined.

Whereas what you've written is probably equivalent, it's not as informative or safe as using the purpose-built types.

Don't print at the level of change_refresh_rate. Read about WinError and throw one of those, potentially using the from keyword to wrap it in a descriptive RuntimeError.

If you want to get fancy, DM_DISPLAYFREQUENCY is in my opinion better represented by a bitfield than an integer, because it then becomes a 0/1 assignment to a position that the structure inherently understands. It would accommodate bits with positions derived from

/* field selection bits */
#define DM_ORIENTATION 0x00000001L
#define DM_PAPERSIZE 0x00000002L
#define DM_PAPERLENGTH 0x00000004L
#define DM_PAPERWIDTH 0x00000008L
#define DM_SCALE 0x00000010L
#if(WINVER >= 0x0500)
#define DM_POSITION 0x00000020L
#define DM_NUP 0x00000040L
#endif /* WINVER >= 0x0500 */
#if(WINVER >= 0x0501)
#define DM_DISPLAYORIENTATION 0x00000080L
#endif /* WINVER >= 0x0501 */
#define DM_COPIES 0x00000100L
#define DM_DEFAULTSOURCE 0x00000200L
#define DM_PRINTQUALITY 0x00000400L
#define DM_COLOR 0x00000800L
#define DM_DUPLEX 0x00001000L
#define DM_YRESOLUTION 0x00002000L
#define DM_TTOPTION 0x00004000L
#define DM_COLLATE 0x00008000L
#define DM_FORMNAME 0x00010000L
#define DM_LOGPIXELS 0x00020000L
#define DM_BITSPERPEL 0x00040000L
#define DM_PELSWIDTH 0x00080000L
#define DM_PELSHEIGHT 0x00100000L
#define DM_DISPLAYFLAGS 0x00200000L
#define DM_DISPLAYFREQUENCY 0x00400000L
#if(WINVER >= 0x0400)
#define DM_ICMMETHOD 0x00800000L
#define DM_ICMINTENT 0x01000000L
#define DM_MEDIATYPE 0x02000000L
#define DM_DITHERTYPE 0x04000000L
#define DM_PANNINGWIDTH 0x08000000L
#define DM_PANNINGHEIGHT 0x10000000L
#endif /* WINVER >= 0x0400 */
#if(WINVER >= 0x0501)
#define DM_DISPLAYFIXEDOUTPUT 0x20000000L
#endif /* WINVER >= 0x0501 */

Don't write the magic number 32. The header defines these as CCHDEVICENAME and CCHFORMNAME; you should too.

answered Jun 4, 2024 at 12:57
\$\endgroup\$
5
\$\begingroup\$
print("Error: Could not retrieve display settings.")
return

Printing an error sends it to stdout when we want it to go to stderr. If we instead raise an exception, we can (a) send the message to stderr and (b) quit execution automatically:

raise("Could not retrieve display settings.")

Also note that ideally we'd just pass along the original error without writing our own. This is typically done by catching exceptions.

I don't have any experience with ctypes.windll, but it doesn't seem to support the standard try-except pattern. It seems to require using use_last_error and errcheck: Get error message from ctypes windll - Stack Overflow

answered Jun 3, 2024 at 13:05
\$\endgroup\$
0

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.