1

I've been recently playing with WINAPI to retrieve scaling setting and screen resolution and I came across this strange behaviour.

I've got small C++ program (listing available below) that retrieves values of VERTRES, HORZRES, DESKTOPVERTRES and DESKTOPHORZRES. My program also sets DPI awareness mode to 1 (system level awareness). I noticed that values reported by VERTRES, HORZRES depends on the system scaling factor used on system start up.

So on my laptop I have configured system scaling factor to 150% and resolution as 1920x1080. That is the configuration on start up. When I retrieve HORZRES and DESKTOPHORZRES both values are reported as 1920.

If I change scaling setting to 100% and don't reboot my computer, next time when I retrieve these values they are reported as 2880 (for HORZRES) and 1920 (for DESKTOPHORZRES).

After I reboot my computer with scaling settings set to 100% both values are reported at 1920 again.

If I change scaling to 150% again, values are reported as 1280 (HORZRES) and 1920 (DESKTOPHORZRES).

The described behaviour only observed when I'm setting DPI awareness to 1, if it is set to 0 ("not aware") or 2 ("per screen awareness") values are always reported as 1280 (HORZRES) and 1920(DESKTOPHORZRES) regardless of how scaling was configured on start up.

I was wondering why does the value reported by HORZRES (or VERTRES) depends on a scaling factor used on system start up? Is that expected behaviour?

If above has already been described somewhere, l'd appreciate any references.

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <cerrno>
#include <string>
#include <sstream>
#include <math.h>
#include <Windows.h>
#include <shellscalingapi.h>
#include <winuser.h>

class Rectangular
{
public:
    Rectangular(int height, int width) : height(height), width(width) {};
    const int height;
    const int width;
};

class MonitorInfo
{
public:
    MonitorInfo(std::string device_name, Rectangular logical_resolution, Rectangular physical_resolution, Rectangular physical_size_mm) :
        device_name(device_name), logical_resolution(logical_resolution), physical_resolution(physical_resolution),
        physical_size_mm(physical_size_mm), scaling(static_cast<int>(std::round(100.0*(float)physical_resolution.height / (float)logical_resolution.height))) {};

    void print_to_stdout() const;
    const std::string device_name;
    const Rectangular logical_resolution;
    const Rectangular physical_resolution;
    const Rectangular physical_size_mm;
    const int scaling;
};


class MonitorsInformation
{
public:
    MonitorsInformation();
    const std::vector<MonitorInfo>& get_monitors_info() const { return monitors; }
    std::string get_last_error_string() const { return last_error; }
    Rectangular get_monitors_rectangular() const;
private:
    RECT rectangular_combined;
    std::vector<MonitorInfo> monitors;
    std::string  last_error;
    static BOOL CALLBACK MonitorEnum(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData);
};

void MonitorInfo::print_to_stdout() const
{
    std::cout << "\nDevice: " << device_name << "\nLogical Screen resolution: " << logical_resolution.width << "x" << logical_resolution.height;
    std::cout << "\nPhysical Screen resolution: " << physical_resolution.width << "x" << physical_resolution.height;
    std::cout << "\nDPI ratio: " << scaling;
    std::cout << "\nPhysical Size(mm): " << physical_size_mm.width << "x" << physical_size_mm.height << "\n";
}


MonitorsInformation::MonitorsInformation() : rectangular_combined(RECT()), last_error(std::string()), monitors(std::vector<MonitorInfo>())
{
    EnumDisplayMonitors(0, 0, MonitorEnum, (LPARAM)this);
}

BOOL CALLBACK MonitorsInformation::MonitorEnum(HMONITOR hMon, HDC hdc, LPRECT lprcMonitor, LPARAM pData)
{
    MonitorsInformation* pThis = reinterpret_cast<MonitorsInformation*>(pData);

    MONITORINFOEXA monitor_info;
    monitor_info.cbSize = sizeof(MONITORINFOEXA);

    if (!GetMonitorInfoA(hMon, &monitor_info))
    {
        pThis->last_error = "GetMonitorInfoA failed with error: " + errno;
        return FALSE;
    }

    UnionRect(&pThis->rectangular_combined, &pThis->rectangular_combined, lprcMonitor);

    HDC device_context = CreateDCA(NULL, monitor_info.szDevice, NULL, NULL);

    int LogicalScreenHeight = GetDeviceCaps(device_context, VERTRES);
    int LogicalScreenWidth = GetDeviceCaps(device_context, HORZRES);
    int PhysicalScreenHeight = GetDeviceCaps(device_context, DESKTOPVERTRES);
    int PhysicalScreenWidth = GetDeviceCaps(device_context, DESKTOPHORZRES);

    pThis->monitors.push_back(
        MonitorInfo(
            std::string(monitor_info.szDevice),
            Rectangular(
                LogicalScreenHeight,
                LogicalScreenWidth),
            Rectangular(
                PhysicalScreenHeight,
                PhysicalScreenWidth),
            Rectangular(
                GetDeviceCaps(device_context, VERTSIZE),
                GetDeviceCaps(device_context, HORZSIZE))));

    return TRUE;
}

Rectangular MonitorsInformation::get_monitors_rectangular() const
{
    return Rectangular(
        std::abs(rectangular_combined.top) + std::abs(rectangular_combined.bottom),
        std::abs(rectangular_combined.left) + std::abs(rectangular_combined.right));
}


int main()
{
    SetProcessDPIAware();

    for (;;)
    {
        MonitorsInformation MonitorsInfo;
        char exit_char = 'N';
        std::cout << "You have " << MonitorsInfo.get_monitors_info().size() << " monitors connected.\n";
        printf("Screen rectangular. %d x %d\n",
            MonitorsInfo.get_monitors_rectangular().width, MonitorsInfo.get_monitors_rectangular().height);

        for (auto &monitor : MonitorsInfo.get_monitors_info())
        {
            monitor.print_to_stdout();
        }
        std::cout << "Would you like to repeat? [Y]/[N]\n";
        std::cin >> exit_char;
        if (exit_char == 'N' || exit_char == 'n')
        {
            break;
        }
    }

    return 0;
}
  • 2
    Lots of things go weird unless you reboot (or at least logout) after changing DPI. It is what it is. – Jonathan Potter Jan 22 '19 at 21:54
  • 1
    I set DPI awareness using `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);` and `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_SYSTEM_AWARE);` both can't reproduce your issues. The HORZRES value always decreases when the scale factor increase. (100%-1920; 125%-1536; 150%-1280; 175%-1097) I don't reboot. – Rita Han Jan 23 '19 at 09:18
  • I test on Windows 10 Enterprise Desktop. – Rita Han Jan 23 '19 at 09:21
  • @RitaHan-MSFT,thanks for your input. Yes, I can see the same behaviour if I increase scaling factor from default 100% to e.g. 150%. In this case I see that HORZRES is getting smaller (as I would expect). My problem is happening when I keep scaling factor to 150% and power cycle my computer. – Alexander Zinovyev Jan 23 '19 at 18:42

1 Answers1

1

@AlexanderZinovyev Thanks for further explanation and I can reproduce your issue with keeping scaling factor to 150% and power cycle my computer.

After check the GetDeviceCaps API document I found the following note:

Note GetDeviceCaps reports info that the display driver provides. If the display driver declines to report any info, GetDeviceCaps calculates the info based on fixed calculations. If the display driver reports invalid info, GetDeviceCaps returns the invalid info. Also, if the display driver declines to report info, GetDeviceCaps might calculate incorrect info because it assumes either fixed DPI (96 DPI) or a fixed size (depending on the info that the display driver did and didn’t provide). Unfortunately, a display driver that is implemented to the Windows Display Driver Model (WDDM) (introduced in Windows Vista) causes GDI to not get the info, so GetDeviceCaps must always calculate the info.

So it seems the return value of GetDeviceCaps API may not reflect the real value of the device.

Rita Han
  • 9,574
  • 1
  • 11
  • 24