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: Ptr 0 passed in to indicate NULL(docs), which means use the current display device. A fancier solution could choose here which monitor to use.
iModeNum: UInt ENUM_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 or DMDFO_STRETCH flags. Use DMDFO_DEFAULT, or don't use the dmDisplayFixedOutput 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.