如何使用C#以编程方式更改Windows 10 Display Scaling [英] How can I change Windows 10 Display Scaling Programmatically using C#

查看:115
本文介绍了如何使用C#以编程方式更改Windows 10 Display Scaling的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试找到一种方法来使用C#在Windows 10中以编程方式更改显示比例。



我也要说,我不是在尝试创建一个自动强制用户屏幕更改分辨率/缩放比例的应用程序。它只是我可以从托盘切换秤的工具,这是我经常需要进行测试的东西。因此,我专门为此操作而设计。



因此,我能够跟踪哪些注册表项( HKEY_CURRENT_USER\控制面板\桌面)当用户通过如下所示的官方对话框手动执行此操作时设置:





但是,显然,直接使用注册表意味着我需要重新启动计算机才能生效。



我知道您可以使用Pinvoke更改屏幕分辨率:



从本质上讲,从EDID构造监视器ID所需的数据如下。


  1. 制造商ID


    • EDID(大字节序)的字节8、9。

    • 例如。对于Dell显示器,EDID为这些字节提供10AC。除位15外,请一次使用其余15位(位0至14)中的5位。 (10AC) 16 等于(0001-0000-1010-1100) 2 。从LSB开始,将该二进制文件分成5位块,得到(0-00100-00101-01100) 2 。将每个块转换为十进制(0-4-5-12) 10 ,现在'D'是第4 个字母,'E'是第5 th , L为第12

    • Fallback: @@@


  2. 产品ID


    • 字节10 ,即EDID中的11(小尾数)

    • 例如。对于Dell显示器,EDID具有BCA0。由于这是小端,因此只需将其转换为A0BC即可获得产品ID。

    • 回扣: 000


  3. 序列号


    • 使用了DDT序列号。 EDID的基本块(前128个字节)具有4个称为DTD的数据块。它们可以用于存储时序信息或任意数据。 4个DTD块位于字节54、72、90和108。具有序列号的DTD块的前2个字节(字节0和1)为零,另外2 nd 个字节为0。 0和3 rd 字节为0xFF。第4 th 再次为零。从字节5开始具有ASCII序列号。序列号最多可占用13个字节(DTD块的字节5至17)。如果序列号少于13个字符(13个字节),则它将由换行符( 0x0A )终止。

    • 戴尔显示器,它是 00-00-00-FF-00-39-44-52-58-56-36-38-41-30-4C-57-4C-0A 。请注意,序列号有12个字节,并以换行符( 0x0A )终止。将 39-44-52-58-56-36-38-41-30-4C-57-4C 转换为ASCII可得到 9DRXV68A0LWL

    • 回退:EDID字节12处的序列号。 EDID可以在2个位置存储序列号,如果未找到DTD块EDID,则OS使用字节12至15(32位小端)的序列号。对于Dell显示器,它是(4C-57-4C-30) 16 ,因为是小端,所以序列号是(304C574C) 16 ,即‭(810309452309) ) 10 。操作系统将使用该值(以10为基数作为备用),即使不存在此值,也将使用 0


  4. 制造周


    • EDID的字节16(可以有一些变化,请参见< a href = https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#EDID_1.4_data_format rel = noreferrer>维基百科文章)

    • 用于Dell显示它是(21) 16

    • Fallback: 00


  5. 制造年份


    • EDID的字节17

    • 自1990年以来的生产年份。在字节17的值上加上1990。

    • 对于Dell显示器,它是(1A) 16 。 (1A) 16 +(1990) 10 =(07C6) 16

    • 回扣: 0000


  6. 基本块校验和


    • EDID的字节127

    • 来自维基百科-校验和。所有128个字节的总和应等于0(mod 256)。

    • 没有后备。有效的EDID必须具有此值。


请注意,仅需要EDID的前128个字节。



后备注意事项



如果不存在构造监视器ID所需的某些数据,则OS使用回退。上面的列表中列出了我在Windows 10计算机上观察到的构造监视器ID所需的每个数据的备用。我手动编辑了DELL显示器的EDID(链接1 link2 link3 -请注意-中建议的方法链接3可能会损坏您的系统,只有在确定的情况下才可以进行;强烈建议使用链接1)删除上述所有6项,操作系统为我构造的显示器ID(不带MD5后缀)为 @@@@ 0000810309452_00_0000_85 ,当我甚至删除了字节12的序列号时,构造的监视器ID为 @@@ 00000_00_0000_A4



更新4



DPI缩放是源的属性,而不是目标的属性,因此在 DisplayConfigGetDeviceInfo() DisplayConfigSe tDeviceInfo()是源ID,而不是目标ID。



上面建议的注册表方法在大多数情况下应该可以正常工作,但是具有2个缺点。其一是,它不能使我们与系统设置应用程序保持一致(就设置生效时间而言)。其次,在极少数情况下(无法再复制),我已经看到OS生成的Monitor ID字符串略有不同-它具有如上图所示的更多组件。



我已经成功创建了一个API,我们可以使用它以与系统设置应用程序完全相同的方式来获取/设置DPI缩放比例。将发布一个新的答案,因为这更多地是我寻找解决方案所采用的方法。


I'm trying to find a way to change the Display Scaling in Windows 10 Programmatically using C#.

Let me also say that, I'm not trying to create a application that automatically forces the users screen to change resolution/scaling. Its just a tool for me to beable to toggle scales from the tray, as its something I often have to do for testing. So purposely designed for this action.

So, I was able to track down what registry entries (HKEY_CURRENT_USER\Control Panel\Desktop) are set when a User does this manually via the official dialog seen below:

However, obviously working with the registry directly means I need to restart the machine to take affect.

I am aware that you can use the Pinvoke to change Screen Resolutions: Setting my Display Resolution

I was wondering if there is a way to change this "%" for a given Screen too? i.e.. my the screen above it says 150%, I'd like to beable to programmatically change it through the full range of 100-500%.

解决方案

Here is my learning from the RnD I did on system settings app (immersive control panel). (see my other answer for a simple C++ API I created from this learning - https://stackoverflow.com/a/58066736/981766)

  1. System Settings app (new immersive control panel that comes with Windows 10) is able to do it. This means Certainly there is an API, only that Microsoft has not made it public.
  2. The Systems settings app is a UWP app, but can be hooked with a debugger - WinDbg.

I used WinDbg to go through calls made by this app. I found that as soon as a particular function is executed - user32!_imp_NtUserDisplayConfigSetDeviceInfo the new DPI setting takes effect on my machine.

I wasn't able to set a break-point on this function, but was able to set one on DisplayConfigSetDeviceInfo() (bp user32!DisplayConfigSetDeviceInfo).

DisplayConfigSetDeviceInfo (msdn link) is a public function, but it seems that the settings app is sending it parameters which are not documented. Here are the parameters I found during my debugging session.

((user32!DISPLAYCONFIG_DEVICE_INFO_HEADER *)0x55df8fba30)                 : 0x55df8fba30 [Type: DISPLAYCONFIG_DEVICE_INFO_HEADER *]
    [+0x000] type             : -4 [Type: DISPLAYCONFIG_DEVICE_INFO_TYPE]
    [+0x004] size             : 0x18 [Type: unsigned int]
    [+0x008] adapterId        [Type: _LUID]
    [+0x010] id               : 0x0 [Type: unsigned int]
0:003> dx -r1 (*((user32!_LUID *)0x55df8fba38))
(*((user32!_LUID *)0x55df8fba38))                 [Type: _LUID]
    [+0x000] LowPart          : 0xcbae [Type: unsigned long]
    [+0x004] HighPart         : 0 [Type: long]

Basically the values of the members of DISPLAYCONFIG_DEVICE_INFO_HEADER struct which gets passed to DisplayConfigSetDeviceInfo() are:

type : -4
size : 0x18
adapterId : LowPart : 0xcbae HighPart :0

The enum type, as defined in wingdi.h is :

typedef enum
{
      DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME                 = 1,
      DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME                 = 2,
      DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE       = 3,
      DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME                = 4,
      DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE          = 5,
      DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE            = 6,
      DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION  = 7,
      DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION  = 8,
      DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO         = 9,
      DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE        = 10,
      DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32                = 0xFFFFFFFF
} DISPLAYCONFIG_DEVICE_INFO_TYPE;

While the settings app is trying to send -4 for type, we can see that the enum has no negative value.

If we are able to reverse engineer this fully, we will have a working API to set DPI of a monitor.

It seems incredibly unfair that Microsoft has some special API for its own apps, which others cannot use.

UPDATE 1 :

To verify my theory, I copied (using WinDbg), the bytes of the DISPLAYCONFIG_DEVICE_INFO_HEADER struct which are sent to DisplayConfigSetDeviceInfo() as parameter; when DPI scaling is changed from System Settings app (tried setting 150% DPI scaling).

I then wrote a simple C program to send these bytes (24 bytes - 0x18 bytes) to DisplayConfigSetDeviceInfo().
I then changed my DPI scaling back to 100%, and ran my code. Sure enough, the DPI scaling did change on running the code!!!

BYTE buf[] = { 0xFC,0xFF,0xFF,0xFF,0x18,0x00,0x00,0x00,0xAE,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00 };
DISPLAYCONFIG_DEVICE_INFO_HEADER* packet = (DISPLAYCONFIG_DEVICE_INFO_HEADER*)buf;
    DisplayConfigSetDeviceInfo(packet);

Note that the same code may not work for you as the LUID, and id parameters, which points to a display on a system would be different (LUID generally is used for GPU, id could be source ID, target ID, or some other ID, this parameter depends on DISPLAYCONFIG_DEVICE_INFO_HEADER::type).

I now have to figure out the meaning of these 24 bytes.

UPDATE 2:

Here are the bytes I got when trying to set 175% dpi scaling.

BYTE buf[] = { 0xFC,0xFF,0xFF,0xFF,0x18,0x00,0x00,0x00,0xAE,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00 };

If we compare the two byte buffers, we can draw the following conclusions.

  1. Byte number 21 is being used to specify DPI scaling, as all other bytes are same between 150%, and 175%.
  2. For 150% scaling, the value of Byte 21 is 1, while for 175% it is 2. The default (recommended) DPI scaling for this monitor is 125%.
  3. From the technet article mentioned by @Dodge, in Windows parlance, 0 corresponds to recommended DPI scaling value. Other integers correspond to relative dpi scaling with respect to this recommended value. 1 means one step ahead in scaling, -1 means one step down. eg. if recommended is 125%, a value of 1 would mean 150% scaling. This is indeed what we saw.

The only thing remaining is now to figure out how to get recommended DPI scaling value for a display, we will then be able to write an API of the following form - SetDPIScaling(monitor_LUID, DPIScale_percent).

UPDATE 3:

If we check the registry entries mentioned in @Dodge's answer, we come to know that these integers are stored as DWORD, and since my computer is little endian it implies that the last 4 bytes (bytes 21 to 24) are being used for them.Thus to send negative numbers we will have to use 2's complement of the DWORD, and write the bytes as little endian.

UPDATE 4:

I have also been researching on how Windows tries to generate Monitor Ids for storing DPI scaling values. For any monitor, the DPI scaling value selected by a user is stored at :

HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\
*MonitorID*

For a Dell display connected to my machine, the monitor ID was DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297. I was able to figure out the structure of monitor ID. I verified my theory with 4 different monitors.

For the Dell display (dpi scaling stored at HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\ DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297), it is as follows (Sorry for adding image, couldn't figure out a way to represent the information as succinctly).

Essentially, the data required from EDID to construct monitor ID is as follows.

  1. Manufacturer ID
    • Bytes 8, 9 of EDID (big endian).
    • Eg. for the Dell display, the EDID has 10AC for these bytes. Except bit 15, use rest of the 15 bits (bits 0 to 14), 5 at a time. (10AC)16 equals (0001-0000-1010-1100)2. Breaking this binary into chunks of 5 bits, starting from LSB gives us (0-00100-00101-01100)2. Converting each chunk to decimal, (0-4-5-12)10, now 'D' is 4th alphabet, 'E' is 5th, and 'L' is 12th.
    • Fallback : @@@
  2. Product ID
    • Bytes 10, 11 of EDID (little endian)
    • Eg. for the Dell display, the EDID has BCA0. Since this is little endian, simply converting it to A0BC gives us product ID.
    • Fallback : 000
  3. Serial number
    • DTD serial number is used. Base block of EDID (first 128 bytes) has 4 blocks of data called DTD. They can either be used to store timing information, or arbitrary data. The 4 DTD blocks are located at bytes 54, 72, 90, and 108. The DTD block which has serial number has first 2 bytes (byte 0, and 1) as zero, 2nd bytes also as zero, and 3rd byte as 0xFF. 4th is again zero. Byte 5 onward has serial number in ASCII. The serial number can occupy a maximum of 13 bytes (byte 5 to 17 of the DTD block). If Serial number is less than 13 characters (13 bytes), then it would be terminated by Line Feed (0x0A).
    • For the Dell display, it was 00-00-00-FF-00-39-44-52-58-56-36-38-41-30-4C-57-4C-0A. Note that the serial number has 12 bytes, and is terminated by line feed (0x0A). Converting 39-44-52-58-56-36-38-41-30-4C-57-4C to ASCII gives us 9DRXV68A0LWL.
    • Fallback : serial number at byte 12 of EDID. EDID can store Serial number at 2 places, if the DTD block EDID is not found, OS uses the serial number present at bytes 12 to 15 (32 bits little endian). For the Dell display it is (4C-57-4C-30)16, since little endian, the serial number is (304C574C)16, which is ‭(810309452‬)10. OS will use this value (in base 10 as a fallback) If even this is not present, then 0 is used.
  4. Manufacture week
    • Byte 16 of EDID (can have some variations, see Wikipedia article)
    • For the Dell display it is (21)16.
    • Fallback : 00
  5. Manufacture year
    • Byte 17 of EDID
    • Year of manufacture since 1990. Add 1990 to value at byte 17.
    • For the Dell display it is (1A)16. (1A)16 + (1990)10 = (07C6)16
    • Fallback : 0000
  6. Edid base block checksum
    • Byte 127 of EDID
    • From Wikipedia - Checksum. Sum of all 128 bytes should equal 0 (mod 256).
    • No fallback. A valid EDID has to have this value.

Note that only first 128 bytes of EDID is ever required.

A note on fallback

If some of the data required for constructing monitor ID are not present, then OS uses fallback. The fallback for each of the datum required for constructing the monitor ID, as I observed on my Windows 10 machine are given in the list above. I manually edited the EDID of my DELL display (link1 link2, link3 - beware - the method suggested in link 3 may damage your system, proceed only if sure; Link1 is most recommended) to remove all 6 items given above, the monitor ID which OS constructed for me (without MD5 suffix) was @@@0000810309452_00_0000_85, when I even removed the serial number at byte 12, the monitor ID constructed was @@@00000_00_0000_A4.

UPDATE 4:

DPI scaling is a property of source, and not of target, hence the id parameter used in DisplayConfigGetDeviceInfo(), and DisplayConfigSetDeviceInfo() is the source ID, and not the target ID.

The registry method suggested above should work fine in most cases, but has 2 drawbacks. One is that it doesn't give us parity with system settings app (in terms of the time at which settings are effected). Secondly in some rare cases (not able to repro any more) I have seen that the Monitor ID string generated by OS is slightly different - it has more components that shown in the pic above.

I have successfully created an API which we can use to get/set DPI scaling in exactly the same way, as done by system settings app. Will post in a new answer, as this is more about the approach I took for finding a solution.

这篇关于如何使用C#以编程方式更改Windows 10 Display Scaling的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆