【问题标题】:Autohotkey: Change Ultrawide 21:9 aspect ratio to 16:9Autohotkey:将 Ultrawide 21:9 纵横比更改为 16:9
【发布时间】:2022-01-18 22:45:32
【问题描述】:

我有一台 38 英寸的超宽显示器。我有一个可以在 3840x1600 和 1920x1080 分辨率之间切换的工作 AHK 脚本,但它不会更改为 16:9 的纵横比,这应该会在显示器的两侧留下黑条(当保持 21:9 的比例时,一切水平缩放不成比例)

当您在 Windows 显示设置中将分辨率从 3840 更改为 1920 时,它也会将比例更改为 16:9,这正是我所需要的。

这是我的解决方案工作脚本:请告诉我需要更改什么\添加以将比率更改为 16:9。

x := A_ScreenWidth
;# y := A_ScreenHeight
;# MsgBox, Your screen resolution is : %x%x%y%


if (x = 1920)
{
ChangeResolution(3840, 1600)
Sleep, 500
Run, J:\PRESETS\AUTOHOTKEY SCRIPTS\PRODUCTIVITY\ResizeDefaultWindows.ahk
}
else
ChangeResolution(1920, 1080)
Return

ChangeResolution(Screen_Width := 1920, Screen_Height := 1080, Color_Depth := 32)
{
    VarSetCapacity(Device_Mode,156,0)
    NumPut(156,Device_Mode,36) 
    DllCall( "EnumDisplaySettingsA", UInt,0, UInt,-1, UInt,&Device_Mode )
    NumPut(0x5c0000,Device_Mode,40) 
    NumPut(Color_Depth,Device_Mode,104)
    NumPut(Screen_Width,Device_Mode,108)
    NumPut(Screen_Height,Device_Mode,112)
    Return DllCall( "ChangeDisplaySettingsA", UInt,&Device_Mode, UInt,0 )
}
ExitApp
Return

【问题讨论】:

  • 你能接受一个非常老套且有点慢(大约 5 秒)的解决方案,它打开显示设置的用户界面,并使用 {Tab} {Spacebar} 和箭头键的组合在设置之间切换?
  • 如果没有别的办法,那就是我的计划。我会使用宏记录器来记录这些步骤。我将它用于许多其他事情。
  • 如果没有人为其提供适当的 dllCall 解决方案,这可能是正确的方法。如果您知道如何使用它,Macro Recorder 肯定比我能提供的任何东西都要好,因为它可以在您的特定系统和硬件上进行微调(而且我没有 38 英寸的超宽显示器,我可以用大声笑测试它)。
  • 奇怪的是,宏记录器一旦进入分辨率下拉菜单就不会记录。即使是脚本(发送,{Enter})也不会在分辨率 Dropbox 上执行。所以是的,我会很感激你的版本。
  • 对于@all:This and other answers on this SU question 显示有关如何更改显示分辨率的替代方法。

标签: autohotkey aspect-ratio


【解决方案1】:

dmDisplayFixedOutput 设置为DMDFO_CENTER(docs) 可能会奏效。
但您也可能会在所有四个侧面都看到黑条。
它也可能没有任何区别。尝试此操作时,我在不同显示器上的结果确实不一致。您的显示器设置肯定也会在这里发挥作用。
但无论如何,我想值得一试。


让我们从 _devicemodeA(docs) 结构开始。
我添加了每个成员的大小(以字节为单位)及其偏移量:

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;

我们对dmSizedmFieldsdmDisplayFixedOutputdmPelsWidthdmPelsHeight 成员感兴趣。
在其他实现上,例如 dmDeviceNamedmPositiondmDisplayOrientationdmDisplayFrequency 成员也可能非常有趣。


所以我们首先创建一个 DEVMODE 结构并用我们的显示设备的当前信息填充它。这样我们就可以只改变我们想要的,而不是在那里设置每一条所需的信息。

所以我们创建了一个变量 _DEVMODE 并为其保留 156 个字节。
156 来自我们上面计算的大小。
保留的东西只是你为AHK DllCalling做的事情,没什么特别的。

VarSetCapacity(_DEVMODE, 156)

然后我们可以使用EnumDisplaySettingsA(docs)函数来填充结构。
但首先我们注意到文档状态:

在调用 EnumDisplaySettings 之前,将 dmSize 成员设置为 sizeof(DEVMODE)

让我们这样做吧,我们知道大小为 156 字节,并且我们知道 dmSize 成员的偏移量为 36 字节。
NumPut()(docs) 被使用在某个内存地址操作内存:

NumPut(156, _DEVMODE, 36)

那么我们就可以打电话给EnumDisplaySettingsA了:

DllCall("EnumDisplaySettingsA", Ptr, 0, UInt, ENUM_CURRENT_SETTINGS := -1, UInt, &_DEVMODE)

参数:

  • lpszDeviceName:Ptr 0 传入表示NULL(docs),表示使用当前显示设备。更高级的解决方案可以在这里选择要使用的显示器。
  • iModeNum:UInt ENUM_CURRENT_SETTINGS(其值为-1)传入以指示使用当前使用的设置,而不是存储在注册表中的设置。
  • *lpDevMode:UInt &_DEVMODE传入,表示我们的_DEVMODE结构的指针。
    &(docs)用来获取指针。

现在应该成功填充结构。
测试它可能很好,所以让我们尝试使用NumGet()(docs) 来获得刷新率作为测试:

MsgBox, % NumGet(_DEVMODE, 120) " Hz"

现在我们可以开始更改分辨率并设置DMDFO_CENTER 标志以希望获得所需的黑条。

首先,我们将标志设置为dmFields 成员,以表明我们正在使用一些成员。
我们需要设置DM_PELSHEIGHTDM_PELSWIDTHDM_DISPLAYFIXEDOUTPUT 标志,因为我们要更改高度、宽度和“固定输出”状态。

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

那我们可以NumPut。我们知道dmFields 的偏移量为 40。

NumPut(flags, _DEVMODE, 40)

然后我们可以将NumPut我们想要的分辨率设置为dmPelsWidth(偏移量108)和dmPelsHeight(偏移量112):

NumPut(1920, _DEVMODE, 108)
NumPut(1080, _DEVMODE, 112)

然后NumPutDMDFO_CENTER 标志设置为dmDisplayFixedOutput(偏移量56):

NumPut(DMDFO_CENTER := 2, _DEVMODE, 56)

现在我们的结构应该被我们想要的东西填充了。

注意:如果您要改回显示器的原始分辨率,请不要尝试使用DMDFO_CENTERDMDFO_STRETCH 标志。使用DMDFO_DEFAULT,或者根本不使用dmDisplayFixedOutput 成员。


现在剩下的就是使用ChangeDisplaySettingsA(docs) 函数来设置我们的更改:

DllCall("ChangeDisplaySettingsA", UInt, &_DEVMODE, UInt, 0)

同样,传入我们结构的指针,然后是 UInt 0 标志,以指示立即更改显示设置。


完整示例脚本

#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)

作为函数的完整示例脚本

#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)
}

对于您可能最终不会使用的东西,相当长且可能过于详细的帖子,但是,我正在避免学校作业。
写起来很有趣,也许至少有人可以从中学到一些东西。

【讨论】:

  • 哇,好详细!我不只是复制\粘贴您的脚本,而是阅读了整个内容。我不需要 lpszDeviceName 的“花哨”解决方案,因为我只有一台显示器,但很高兴知道。无论如何,Hz msgbox 显示了我的显示器设置的正确 60Hz。对于初始测试,我忽略了您关于不使用某些成员返回本地并运行它的说明。结果:在 1920 的时候,脚本会改回原生!但是在本地时,脚本什么都不做(不会更改为 1920。) 1:如何在不理会您的笔记的情况下回到本地工作? 2:需要1920才能工作。
  • 在最底部的示例中,应将热键设置为 F1 转到 1920x1080(未拉伸),F2 设置为返回原生 (3840x1600)。对我来说效果很好,虽然我在 F2 上使用了不同的分辨率,因为原生对我来说不是 3840x1600(它是 2560x1440)。如果您只是直接复制粘贴最底部的示例,它不适合您吗?
  • 我确实使用了功能一。但是,我没有使用两个热键,而是使用了 if-else,正如我在原始问题中发布的那样。因此,我声明了一个变量“x := A_ScreenWidth”和“if (x = 1920)”,然后声明了“ChangeDisplaySettings(3840, 1600, DMDFO_DEFAULT)”,否则声明了“ChangeDisplaySettings(1920, 1080, DMDFO_CENTER)”。我认为使用多个热键不会产生影响。
  • 取决于x 的值何时/如何更新。但我的意思是,准确地复制粘贴 F1 F2 版本会告诉您问题是否出在您的身上,或者实现是否有问题。
  • 当我点击运行包含代码的 AHK 文件的 GENOVATION MACRO KEYPAD CP48 上的一个键时,“x”会更新。我完全复制了你的代码,除了 F1 和 F2 的两行,我用我的 if-else 替换了它们。但只是为了组织起见,我会尝试完全复制 F1 F2 并将很快更新。
猜你喜欢
  • 2017-07-30
  • 2016-08-26
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-30
  • 2020-01-03
相关资源
最近更新 更多