Fut*_*e32 7 c# dpi screen-resolution
我正在尝试找到一种方法来使用C#以编程方式更改Windows 10中的显示比例。
我也要说的是,我并不是要创建一个自动强制用户屏幕更改分辨率/缩放比例的应用程序。它只是我可以从托盘切换秤的工具,这是我经常需要做的测试。因此专门为此动作而设计。
因此,当用户通过如下所示的官方对话框手动执行此操作时,我能够跟踪设置了哪些注册表项(HKEY_CURRENT_USER \ Control Panel \ Desktop):
但是,显然直接使用注册表意味着我需要重新启动计算机才能生效。
我知道您可以使用Pinvoke更改屏幕分辨率: 设置我的显示分辨率
我想知道是否也可以为给定的屏幕更改此“%”?即。我上面的屏幕显示150%,我希望能够以编程方式在100-500%的整个范围内进行更改。
Sah*_*ngh 12
C++ 获取/设置 DPI 的 API。我能够逆向工程系统设置应用程序,并提出一个 API。它的代码存在于我的 github 存储库https://github.com/lihas/windows-DPI-scaling-sample 中。
我在这个答案中跳过了解释很多术语,因为我在我之前对这个问题的回答中已经这样做了(/sf/answers/4017792761/)。
DPIScalingInfo()使用adapterID 和sourceID
调用。
DpiHelper::DPIScalingInfo DpiHelper::GetDPIScalingInfo(LUID adapterID, UINT32 sourceID)
Run Code Online (Sandbox Code Playgroud)
设置显示器的 DPI
使用要设置的适配器 ID、源 ID 和百分比 DPI 缩放调用 SetDPIScaling()。例如。如果要将源的 DPI 缩放设置为 175%,请在最后一个参数中传递 175。
bool DpiHelper::SetDPIScaling(LUID adapterID, UINT32 sourceID, UINT32 dpiPercentToSet)
Run Code Online (Sandbox Code Playgroud)
repo 中的 DpiHelper.h 有这两种方法的完整文档。
另请阅读DpiHelper.h 中的文档和repo 的README。我已经在公共领域的 repo 中发布了所有代码,所以你可以随意使用它。
我还创建了一个 MFC 应用程序,它使用这个帮助程序库来获取/设置 DPI 缩放。这将帮助您了解如何使用 DpiHelper 类。
这是它的外观。
我使用 WinDbg Preview (MS Store) 和Ghidra进行逆向工程。有一段时间,当我因为缺乏 IDA Pro 许可证而准备放弃时,有人建议我使用 Ghidra。从那以后我一直是粉丝。
非常感谢Ghidra!!!
如果要更改系统范围的 DPI 缩放(系统 DPI 缩放 - 在多显示器设置或只有单个显示器的情况下主显示器的缩放),而不是按显示器 DPI 缩放, 可以使用SystemParametersInfo() 。
该 API 有一个未记录的参数可以实现此目的:SPI_SETLOGICALDPIOVERRIDE
来自微软文档:
SPI_SETLOGICALDPIOVERRIDE Do not use.
0x009F
Run Code Online (Sandbox Code Playgroud)
用法 :
SystemParametersInfo(SPI_SETLOGICALDPIOVERRIDE, relativeIndex, (LPVOID)0, 1);
Run Code Online (Sandbox Code Playgroud)
要弄清楚上面的变量使用什么值relativeIndex,您必须了解操作系统如何期望指定 DPI 缩放值(此处解释)。
简而言之,relativeIndex告诉您想要高于或低于建议的 DPI 缩放值多少步。例如。如果建议的 DPI 缩放值为 125%,并且您希望将缩放设置为 150%,则relativeIndex 将为 1(比 125% 一级),或者如果您想设置 100%,relativeIndex 将为 -1(比 125 低一级) %)。
所有台阶的尺寸可能不同。
100,125,150,175,200,225,250,300,350, 400, 450, 500
Run Code Online (Sandbox Code Playgroud)
直到 250%,步数以 25% 为单位增加,之后以 50% 为单位增加。
因此,您必须首先获取推荐的 DPI 缩放值,可以使用相同的 API 和SPI_GETLOGICALDPIOVERRIDE参数。
SystemParametersInfo(SPI_GETLOGICALDPIOVERRIDE, 0, (LPVOID)&dpi, 1);
Run Code Online (Sandbox Code Playgroud)
上面dpi变量返回的值也要以特殊的方式来理解。该值为负数,其大小表示上面列表中 DPI 缩放百分比的索引。
因此,如果此 API 返回 -1,则建议的 DPI 缩放值为 125%。
示例代码:
#include <iostream>
#include <Windows.h>
using namespace std;
static const UINT32 DpiVals[] = { 100,125,150,175,200,225,250,300,350, 400, 450, 500 };
/*Get default DPI scaling percentage.
The OS recommented value.
*/
int GetRecommendedDPIScaling()
{
int dpi = 0;
auto retval = SystemParametersInfo(SPI_GETLOGICALDPIOVERRIDE, 0, (LPVOID)&dpi, 1);
if (retval != 0)
{
int currDPI = DpiVals[dpi * -1];
return currDPI;
}
return -1;
}
void SetDpiScaling(int percentScaleToSet)
{
int recommendedDpiScale = GetRecommendedDPIScaling();
if (recommendedDpiScale > 0)
{
int index = 0, recIndex = 0, setIndex = 0 ;
for (const auto& scale : DpiVals)
{
if (recommendedDpiScale == scale)
{
recIndex = index;
}
if (percentScaleToSet == scale)
{
setIndex = index;
}
index++;
}
int relativeIndex = setIndex - recIndex;
SystemParametersInfo(SPI_SETLOGICALDPIOVERRIDE, relativeIndex, (LPVOID)0, 1);
}
}
int main()
{
for (;;)
{
int n = 0, dpiToSet = 0;
cout << R"(
1. Show Recommended DPI
2. Set DPI
Anything else to exit
)";
cin >> n;
switch (n)
{
case 1:
cout << "recommened scaling: " << GetRecommendedDPIScaling() << "%" << endl;
break;
case 2:
cout << "enter scaling to set in percentage" << endl;
cin >> dpiToSet;
SetDpiScaling(dpiToSet);
break;
default:
exit(0);
break;
}
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
源代码: https: //github.com/lihas/windows-DPI-scaling-sample。
优点和缺点 关于
我之前的方法(/sf/answers/4064671551/,/sf/answers/4017792761/)
优点
缺点
参考
这是我在系统设置应用程序(沉浸式控制面板)上所做的RnD的学习。(有关我从此学习中创建的简单C ++ API的其他信息,请参阅我的其他答案-https: //stackoverflow.com/a/58066736/981766)
我使用WinDbg来处理此应用程序发出的呼叫。我发现,一旦执行特定功能user32!_imp_NtUserDisplayConfigSetDeviceInfo,新的DPI设置将在我的机器上生效。
我无法在此函数上设置断点,但能够在上设置一个断点DisplayConfigSetDeviceInfo() (bp user32!DisplayConfigSetDeviceInfo)。
DisplayConfigSetDeviceInfo(msdn链接)是一个公共功能,但似乎设置应用程序正在向其发送未记录的参数。这是我在调试会话期间发现的参数。
((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]
Run Code Online (Sandbox Code Playgroud)
基本上,DISPLAYCONFIG_DEVICE_INFO_HEADER传递给struct 的成员的值DisplayConfigSetDeviceInfo()是:
type : -4
size : 0x18
adapterId : LowPart : 0xcbae HighPart :0
Run Code Online (Sandbox Code Playgroud)
wingdi.h中定义的枚举类型为:
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;
Run Code Online (Sandbox Code Playgroud)
在设置应用尝试发送-4的类型时,我们可以看到枚举没有负值。
如果我们能够完全对它进行反向工程,我们将有一个有效的API来设置监视器的DPI。
微软为自己的应用程序提供了一些其他人无法使用的特殊API,这似乎非常不公平。
为了验证我的理论,我复制(使用WinDbg)DISPLAYCONFIG_DEVICE_INFO_HEADER结构的字节并DisplayConfigSetDeviceInfo()作为参数发送到该字节。当从“系统设置”应用中更改了DPI缩放比例时(尝试设置为150%DPI缩放比例)。
然后,我编写了一个简单的C程序,将这些字节(24字节-0x18字节)发送给DisplayConfigSetDeviceInfo()。
然后,我将DPI缩放比例更改回100%,并运行了我的代码。果然,在运行代码时DPI缩放比例确实发生了变化!!!
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);
Run Code Online (Sandbox Code Playgroud)
请注意,与LUID相同的代码可能对您不起作用,并且id参数指向系统上的显示会有所不同(LUID通常用于GPU,id可以是源ID,目标ID或其他一些ID ,此参数取决于DISPLAYCONFIG_DEVICE_INFO_HEADER :: type)。
我现在必须弄清楚这24个字节的含义。
这是尝试设置175%dpi缩放比例时获得的字节数。
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 };
Run Code Online (Sandbox Code Playgroud)
如果我们比较两个字节缓冲区,我们可以得出以下结论。
现在剩下的唯一事情就是弄清楚如何获得建议的显示器DPI缩放比例值,然后我们将能够编写以下形式的API- SetDPIScaling(monitor_LUID, DPIScale_percent)。
如果我们检查@Dodge的答案中提到的注册表项,就会知道这些整数存储为DWORD,并且由于我的计算机是低端字节序的,因此这意味着将使用最后4个字节(21至24字节)。因此,要发送负数,我们将必须使用DWORD的2的补码,并将字节写为little endian。
我还一直在研究Windows如何尝试生成用于存储DPI缩放值的Monitor ID。对于任何监视器,用户选择的DPI缩放值存储在:
HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\
*MonitorID*
Run Code Online (Sandbox Code Playgroud)
对于连接到本机的Dell显示器,显示器ID为DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297。我能够弄清监视器ID的结构。我用4种不同的显示器验证了我的理论。
对于Dell显示器(DPI缩放比例存储在HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\
DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297),如下所示(很抱歉,无法添加图像,无法找到一种简洁地表示信息的方式)。
从本质上讲,从EDID到构造监视器ID所需的数据如下。
@@@0000x0A)终止。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。0。000000请注意,仅需要EDID的前128个字节。
如果不存在构造监视器ID所需的某些数据,则OS使用回退。上面的列表中列出了我在Windows 10计算机上观察到的构造监视器ID所需的每个数据的备用。我手动编辑了DELL显示器的EDID(link1 link2,link3-注意-链接3中建议的方法可能会损坏您的系统,只有在确定的情况下才可以继续操作;建议使用Link1)删除上面给出的所有6项,该显示器ID为我构建的操作系统(不带MD5后缀)是@@@0000810309452_00_0000_85,甚至在删除字节12的序列号时,构建的监视器ID也为@@@00000_00_0000_A4。
DPI缩放是源的特性,而不是目标的,因此,使用在id参数DisplayConfigGetDeviceInfo(),并且DisplayConfigSetDeviceInfo()是源ID,而不是目标的ID。
上面建议的注册表方法在大多数情况下应该可以正常工作,但有两个缺点。一是它不能使我们与系统设置应用程序保持同等水平(就设置生效时间而言)。其次,在极少数情况下(无法再复制),我已经看到OS生成的Monitor ID字符串略有不同-它具有更多组件,如上图所示。
我已经成功创建了一个API,我们可以使用它以与系统设置应用程序完全相同的方式来获取/设置DPI缩放比例。将发布一个新答案,因为这更多地是我寻找解决方案所采用的方法。
While searching for exactly the same, i found your question and found a possible solution.
I found that a per monitor toggle for this % value is in registry at Computer\HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\*monitorId*\DpiValue. it looks like that the meaning of the value depends on the screen (size and dpi) see this reddit post for details.
For my 24" 1080p screen 0 means 100% and 1 means 125%. This Technet Article seems to be explainig the values a bit.
Unfortunately it is not enough to change the registry value. but you can refresh the dpi by changing the resolution after writing to the registry.
the following code sets the dpi and then switches resolution low and back high to trigger the dpi update.
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace SetDpiScale
{
public partial class Form1 : Form
{
public enum DMDO
{
DEFAULT = 0,
D90 = 1,
D180 = 2,
D270 = 3
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
struct DEVMODE
{
public const int DM_PELSWIDTH = 0x80000;
public const int DM_PELSHEIGHT = 0x100000;
private const int CCHDEVICENAME = 32;
private const int CCHFORMNAME = 32;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string dmDeviceName;
public short dmSpecVersion;
public short dmDriverVersion;
public short dmSize;
public short dmDriverExtra;
public int dmFields;
public int dmPositionX;
public int dmPositionY;
public DMDO dmDisplayOrientation;
public int dmDisplayFixedOutput;
public short dmColor;
public short dmDuplex;
public short dmYResolution;
public short dmTTOption;
public short dmCollate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
public string dmFormName;
public short dmLogPixels;
public int dmBitsPerPel;
public int dmPelsWidth;
public int dmPelsHeight;
public int dmDisplayFlags;
public int dmDisplayFrequency;
public int dmICMMethod;
public int dmICMIntent;
public int dmMediaType;
public int dmDitherType;
public int dmReserved1;
public int dmReserved2;
public int dmPanningWidth;
public int dmPanningHeight;
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern int ChangeDisplaySettings([In] ref DEVMODE lpDevMode, int dwFlags);
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ChangeDPI(0); // 100%
}
private void button2_Click(object sender, EventArgs e)
{
ChangeDPI(1); // 125%
}
void ChangeDPI(int dpi)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel", true);
key = key.OpenSubKey("Desktop", true);
key = key.OpenSubKey("PerMonitorSettings", true);
key = key.OpenSubKey("*monitor id where to change the dpi*", true); // my second monitor here
key.SetValue("DpiValue", dpi);
SetResolution(1920, 1080); // this sets the resolution on primary screen
SetResolution(2560, 1440); // returning back to my primary screens default resolution
}
private static void SetResolution(int w, int h)
{
long RetVal = 0;
DEVMODE dm = new DEVMODE();
dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));
dm.dmPelsWidth = w;
dm.dmPelsHeight = h;
dm.dmFields = DEVMODE.DM_PELSWIDTH | DEVMODE.DM_PELSHEIGHT;
RetVal = ChangeDisplaySettings(ref dm, 0);
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
6424 次 |
| 最近记录: |