Autohotkey: Change Ultrawide 21:9 aspect ratio to 16:9
Solution 1:
Setting dmDisplayFixedOutput
to DMDFO_CENTER
(docs) might do the trick.
But you might also get black bars on all four sides.
It might also not make any difference. I had really inconsistent results on different displays when trying this. Your monitor settings will surely also play a part here.
But anyway, worth a try I guess.
Lets start off by looking at the _devicemodeA
(docs) structure.
I added in the sizes of each member (in bytes) and their offsets:
typedef struct _devicemodeA { // size | offset
BYTE dmDeviceName[CCHDEVICENAME]; // 32 0
WORD dmSpecVersion; // 2 32
WORD dmDriverVersion; // 2 34
WORD dmSize; // 2 36
WORD dmDriverExtra; // 2 38
DWORD dmFields; // 4 40
union {
/* printer only fields */
struct {
short dmOrientation; // 2 44
short dmPaperSize; // 2 46
short dmPaperLength; // 2 48
short dmPaperWidth; // 2 50
short dmScale; // 2 52
short dmCopies; // 2 54
short dmDefaultSource; // 2 56
short dmPrintQuality; // 2 58
} DUMMYSTRUCTNAME;
/* display only fields */
struct {
POINTL dmPosition; // 8 44
DWORD dmDisplayOrientation; // 4 52
DWORD dmDisplayFixedOutput; // 4 56
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME;
short dmColor; // 2 60
short dmDuplex; // 2 62
short dmYResolution; // 2 64
short dmTTOption; // 2 66
short dmCollate; // 2 68
BYTE dmFormName[CCHFORMNAME]; // 32 70
WORD dmLogPixels; // 2 102
DWORD dmBitsPerPel; // 4 104
DWORD dmPelsWidth; // 4 108
DWORD dmPelsHeight; // 4 112
union {
DWORD dmDisplayFlags; // 4 116
DWORD dmNup; // 4 116
} DUMMYUNIONNAME2;
DWORD dmDisplayFrequency; // 4 120
#if(WINVER >= 0x0400)
DWORD dmICMMethod; // 4 124
DWORD dmICMIntent; // 4 128
DWORD dmMediaType; // 4 132
DWORD dmDitherType; // 4 136
DWORD dmReserved1; // 4 140
DWORD dmReserved2; // 4 144
#if (WINVER >= 0x0500) || (_WIN32_WINNT >= _WIN32_WINNT_NT4)
DWORD dmPanningWidth; // 4 148
DWORD dmPanningHeight; // 4 152
#endif //-----
#endif /* WINVER >= 0x0400 */ // 156 (total)
} DEVMODEA, *PDEVMODEA, *NPDEVMODEA, *LPDEVMODEA;
We're interested in the dmSize
, dmFields
, dmDisplayFixedOutput
, dmPelsWidth
and dmPelsHeight
members.
On other implementations e.g the dmDeviceName
, dmPosition
, dmDisplayOrientation
and dmDisplayFrequency
members could be very interesting as well.
So we start off by creating a DEVMODE structure and filling it with the our display device's current information. This way we we can only change what we want, as opposed to setting every single required piece of information in there.
So we create a variable _DEVMODE
and reserve 156 bytes for it.
156 comes from the size we calculated for it above.
The reserving thing is just a thing you have to do for AHK DllCalling, nothing more special to it.
VarSetCapacity(_DEVMODE, 156)
Then we can use the EnumDisplaySettingsA
(docs) function to fill the structure.
But first we notice the documentation state:
Before calling EnumDisplaySettings, set the dmSize member to
sizeof(DEVMODE)
So lets do that, we know the size to be 156 bytes and we know the offset of the dmSize
member to be 36 bytes.NumPut()
(docs) is used to manipulate memory at a certain memory address:
NumPut(156, _DEVMODE, 36)
Then we're ready to call EnumDisplaySettingsA
:
DllCall("EnumDisplaySettingsA", Ptr, 0, UInt, ENUM_CURRENT_SETTINGS := -1, UInt, &_DEVMODE)
Parameters:
-
lpszDeviceName
: Ptr0
passed in to indicate NULL(docs), which means use the current display device. A fancier solution could choose here which monitor to use. -
iModeNum
: UIntENUM_CURRENT_SETTINGS
(its value is -1) passed in to indicate using the currently used settings, as opposed to settings stored in the registry. -
*lpDevMode
: UInt&_DEVMODE
passed in, which means the pointer of our_DEVMODE
structure.&
(docs) is used to get the pointer.
And now the structure should be successfully filled.
Probably good to test it out, so lets try to get the refresh rate with NumGet()
(docs) as a test:
MsgBox, % NumGet(_DEVMODE, 120) " Hz"
Now we can get onto changing the resolution and setting the DMDFO_CENTER
flag to hopefully get the desired black bars.
First we set the flags to the dmFields
members to indicate we are using some members.
We need to set the DM_PELSHEIGHT
, DM_PELSWIDTH
and DM_DISPLAYFIXEDOUTPUT
flags, since we're changing the height, width, and "fixed output" status.
DM_PELSHEIGHT := 0x00100000
DM_PELSWIDTH := 0x00080000
DM_DISPLAYFIXEDOUTPUT := 0x20000000
; technically + instead of | (bitwise or) would work the same
flags := DM_PELSHEIGHT | DM_PELSWIDTH | DM_DISPLAYFIXEDOUTPUT
Then we can NumPut
. We know dmFields
to be at offset 40.
NumPut(flags, _DEVMODE, 40)
Then we can NumPut
our desired resolution to dmPelsWidth
(offset 108) and dmPelsHeight
(offset 112):
NumPut(1920, _DEVMODE, 108)
NumPut(1080, _DEVMODE, 112)
And then NumPut
to set the DMDFO_CENTER
flag to dmDisplayFixedOutput
(offset 56):
NumPut(DMDFO_CENTER := 2, _DEVMODE, 56)
Now our structure should be filled with what we want.
Note: If you're changing back to the native resolution of your monitor, don't try to use
DMDFO_CENTER
orDMDFO_STRETCH
flags. UseDMDFO_DEFAULT
, or don't use thedmDisplayFixedOutput
member at all.
Now all that's left, is just using the ChangeDisplaySettingsA
(docs) function to set our changes:
DllCall("ChangeDisplaySettingsA", UInt, &_DEVMODE, UInt, 0)
Again, passing in the pointer of our structure and then UInt 0 flag to indicate changing the display settings immediately.
Full example script
#NoEnv
ENUM_CURRENT_SETTINGS := -1
DM_PELSHEIGHT := 0x00100000
DM_PELSWIDTH := 0x00080000
DM_DISPLAYFIXEDOUTPUT := 0x20000000
DMDFO_DEFAULT := 0
DMDFO_STRETCH := 1
DMDFO_CENTER := 2
VarSetCapacity(_DEVMODE, 156)
NumPut(156, _DEVMODE, 36) ;dmSize
DllCall("EnumDisplaySettingsA", Ptr, 0, UInt, ENUM_CURRENT_SETTINGS, UInt, &_DEVMODE)
MsgBox, % NumGet(_DEVMODE, 120) " Hz"
; technically + instead of | (bitwise or) would work the same
flags := DM_PELSHEIGHT | DM_PELSWIDTH | DM_DISPLAYFIXEDOUTPUT
NumPut(flags, _DEVMODE, 40) ;dmFields
NumPut(1920, _DEVMODE, 108) ;dmPelsWidth
NumPut(1080, _DEVMODE, 112) ;dmPelsHeight
NumPut(DMDFO_CENTER, _DEVMODE, 56) ;dmDisplayFixedOutput
DllCall("ChangeDisplaySettingsA", UInt, &_DEVMODE, UInt, 0)
Full example script as a function
#NoEnv
DMDFO_DEFAULT := 0
DMDFO_STRETCH := 1
DMDFO_CENTER := 2
F1::ChangeDisplaySettings(1920, 1080, DMDFO_CENTER)
F2::ChangeDisplaySettings(3840, 1600, DMDFO_DEFAULT)
ChangeDisplaySettings(width, height, DMDFO)
{
static ENUM_CURRENT_SETTINGS := -1
, DM_PELSHEIGHT := 0x00100000
, DM_PELSWIDTH := 0x00080000
, DM_DISPLAYFIXEDOUTPUT := 0x20000000
VarSetCapacity(_DEVMODE, 156)
NumPut(156, _DEVMODE, 36) ;dmSize
DllCall("EnumDisplaySettingsA", Ptr, 0, UInt, ENUM_CURRENT_SETTINGS, UInt, &_DEVMODE)
; technically + instead of | (bitwise or) would work the same
flags := DM_PELSHEIGHT | DM_PELSWIDTH | DM_DISPLAYFIXEDOUTPUT
NumPut(flags, _DEVMODE, 40) ;dmFields
NumPut(width, _DEVMODE, 108) ;dmPelsWidth
NumPut(height, _DEVMODE, 112) ;dmPelsHeight
NumPut(DMDFO, _DEVMODE, 56) ;dmDisplayFixedOutput
return DllCall("ChangeDisplaySettingsA", UInt, &_DEVMODE, UInt, 0)
}
Quite long and possibly overly detailed post for something you might not end up using, but meh, I'm avoiding school work.
It was fun to write and maybe at least someone can learn something from it.