Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use modern DPI awareness settings #13254

Merged
merged 21 commits into from Aug 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 5 additions & 2 deletions source/core.py
Expand Up @@ -27,6 +27,7 @@
import addonHandler
import extensionPoints
import garbageHandler
import NVDAState


# inform those who want to know that NVDA has finished starting up.
Expand Down Expand Up @@ -427,8 +428,10 @@ def main():
Finally, it starts the wx main loop.
"""
log.debug("Core starting")

ctypes.windll.user32.SetProcessDPIAware()
if NVDAState.isRunningAsSource():
# When running as packaged version, DPI awareness is set via the app manifest.
from winAPI.dpiAwareness import setDPIAwareness
setDPIAwareness()

import config
if not globalVars.appArgs.configPath:
Expand Down
50 changes: 50 additions & 0 deletions source/manifest.template.xml
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly
xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="asInvoker"
uiAccess="%(uiAccess)s"
/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 7 -->
<supportedOS
Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"
/>
<!-- Windows 8 -->
<supportedOS
Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"
/>
<!-- Windows 8.1 -->
<supportedOS
Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"
/>
<!-- Windows 10/11 -->
<supportedOS
Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
/>
</application>
</compatibility>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"
>
true/pm
</dpiAware>
<dpiAwareness
xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
>
PerMonitorV2, PerMonitor
</dpiAwareness>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
67 changes: 19 additions & 48 deletions source/setup.py
@@ -1,70 +1,39 @@
# -*- coding: UTF-8 -*-
#setup.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2018 NV Access Limited, Peter Vágner, Joseph Lee
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2022 NV Access Limited, Peter Vágner, Joseph Lee
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import os
import sys
import copy
import gettext
gettext.install("nvda")
from setuptools import setup
import py2exe as py2exeModule
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
# While the import of py2exe appears unused it is required.
# py2exe monkey patches distutils when importing py2exe for the first time.
import py2exe as py2exeModule # noqa: F401, E402
from glob import glob
import fnmatch
# versionInfo names must be imported after Gettext
# Suppress E402 (module level import not at top of file)
from versionInfo import (
copyright as NVDAcopyright, # copyright is a reserved python keyword
description,
formatBuildVersionString,
name,
publisher,
url,
version,
publisher
) # noqa: E402
from versionInfo import *
from py2exe import distutils_buildexe
from py2exe.dllfinder import DllFinder
import wx
import importlib.machinery
# Explicitly put the nvda_dmp dir on the build path so the DMP library is included
sys.path.append(os.path.join("..", "include", "nvda_dmp"))
RT_MANIFEST = 24
manifest_template = """\
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="asInvoker"
uiAccess="%(uiAccess)s"
/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 7 -->
<supportedOS
Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"
/>
<!-- Windows 8 -->
<supportedOS
Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"
/>
<!-- Windows 8.1 -->
<supportedOS
Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"
/>
<!-- Windows 10 -->
<supportedOS
Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
/>
</application>
</compatibility>
</assembly>
"""
manifestTemplateFilePath = "manifest.template.xml"

# py2exe's idea of whether a dll is a system dll appears to be wrong sometimes, so monkey patch it.
orig_determine_dll_type = DllFinder.determine_dll_type
Expand Down Expand Up @@ -92,6 +61,8 @@ def initialize_options(self):
self.enable_uiAccess = False

def run(self):
with open(manifestTemplateFilePath, "r", encoding="utf-8") as manifestTemplateFile:
manifestTemplate = manifestTemplateFile.read()
dist = self.distribution
if self.enable_uiAccess:
# Add a target for nvda_uiAccess, using nvda_noUIAccess as a base.
Expand All @@ -108,7 +79,7 @@ def run(self):
(
RT_MANIFEST,
1,
(manifest_template % dict(uiAccess=target['uiAccess'])).encode("utf-8")
(manifestTemplate % dict(uiAccess=target['uiAccess'])).encode("utf-8")
),
]
super(py2exe, self).run()
Expand Down Expand Up @@ -167,7 +138,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
"description":"NVDA application",
"product_name":name,
"product_version":version,
"copyright":copyright,
"copyright": NVDAcopyright,
"company_name":publisher,
},
# The nvda_uiAccess target will be added at runtime if required.
Expand All @@ -180,7 +151,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
"description": name,
"product_name":name,
"product_version": version,
"copyright": copyright,
"copyright": NVDAcopyright,
"company_name": publisher,
},
{
Expand All @@ -193,7 +164,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
"description": "NVDA Ease of Access proxy",
"product_name":name,
"product_version": version,
"copyright": copyright,
"copyright": NVDAcopyright,
"company_name": publisher,
},
],
Expand All @@ -207,7 +178,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
"description": "NVDA Diff-match-patch proxy",
"product_name": name,
"product_version": version,
"copyright": f"{copyright}, Bill Dengler",
"copyright": f"{NVDAcopyright}, Bill Dengler",
"company_name": f"Bill Dengler, {publisher}",
},
],
Expand Down
19 changes: 19 additions & 0 deletions source/winAPI/constants.py
@@ -0,0 +1,19 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2022 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import enum


class HResult(enum.IntEnum):
# https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
S_OK = 0x00000000
E_ACCESS_DENIED = 0x80070005 # E_ACCESSDENIED
E_INVALID_ARG = 0x80070057 # E_INVALIDARG


class SystemErrorCodes(enum.IntEnum):
# https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
ACCESS_DENIED = 0x5
INVALID_PARAMETER = 0x57
93 changes: 93 additions & 0 deletions source/winAPI/dpiAwareness.py
@@ -0,0 +1,93 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2022 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import ctypes

from logHandler import log

from .constants import (
HResult,
SystemErrorCodes,
)


def setDPIAwareness() -> None:
"""
Different versions of Windows inconsistently support different styles of DPI Awareness.
This function attempts to set process DPI awareness using the most modern Windows API method available.

Only call this function once per instance of NVDA.

Only call this function when running from source.
It is recommended that you set the process-default DPI awareness via application manifest.
Setting the process-default DPI awareness via these API calls can lead to unexpected application behavior.
"""
# Support is inconsistent across versions of Windows, so try/excepts are used rather than explicit
# version checks.
# https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process
try:
# An advancement over the original per-monitor DPI awareness mode,
# which enables applications to access new DPI-related scaling behaviors on a per top-level window basis.
# For more information on behaviours, refer to:
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
# Method introduced in Windows 10
# https://docs.microsoft.com/en-us/windows/win32/hidpi/dpi-awareness-context
success = ctypes.windll.user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
except AttributeError:
log.debug("Cannot set DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2")
else:
if success:
return
else:
errorCode = ctypes.GetLastError()
if errorCode == SystemErrorCodes.ACCESS_DENIED:
# The DPI awareness is already set,
# either by calling this API previously or through the application (.exe) manifest.
# This is unexpected as we should only set DPI awareness once.
# NVDA sets DPI awareness from the manifest,
# however this function should only be called when running from source.
log.error("DPI Awareness already set.")
return
elif errorCode == SystemErrorCodes.INVALID_PARAMETER:
log.error("DPI Awareness function provided invalid argument.")
else:
log.error(f"Unknown error setting DPI Awareness. Error code: {errorCode}")

log.debug("Falling back to older method of setting DPI Awareness")

try:
# https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness
# This window checks for the DPI when it is created and adjusts the scale factor whenever the DPI changes.
# These processes are not automatically scaled by the system.
PROCESS_PER_MONITOR_DPI_AWARE = 2
# Method introduced in Windows 8
hResult = ctypes.windll.shcore.SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)
except AttributeError:
log.debug("Cannot set PROCESS_PER_MONITOR_DPI_AWARE")
else:
if hResult == HResult.S_OK:
return
elif hResult == HResult.E_ACCESS_DENIED:
# The DPI awareness is already set,
# either by calling this API previously or through the application (.exe) manifest.
# This is unexpected as we should only set DPI awareness once.
# NVDA sets DPI awareness from the manifest,
# however this function should only be called when running from source.
log.error("DPI Awareness already set.")
return
elif hResult == HResult.E_INVALID_ARG:
log.error("DPI Awareness function provided invalid argument.")
else:
log.error(f"Unknown error setting DPI Awareness. HRESULT: {hResult}")

log.debug("Falling back to legacy method of setting DPI Awareness")

# Method introduced in Windows Vista
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiaware
result = ctypes.windll.user32.SetProcessDPIAware()
if result == 0:
errorCode = ctypes.GetLastError()
log.error(f"Unknown error setting DPI Awareness. Error code: {errorCode}")
9 changes: 9 additions & 0 deletions user_docs/en/changes.t2t
Expand Up @@ -45,6 +45,15 @@ What's New in NVDA
- These fixes apply to Windows 11 Sun Valley 2 (version 22H2) and later.
- Selective registration for UI Automation events and property changes now enabled by default.
-
- NVDA is now DPI aware when using multiple monitors.
There are several fixes for using a DPI setting higher than 100% or multiple monitors.
Issues may still exist with versions of Windows older than Windows 10 1809.
Applications which NVDA interacts with also need to be DPI aware. (#13254)
- Visual highlighting frames should now be correctly placed in most applications. (#13370, #3875, #12070)
- Touch screen interaction should now be accurate for most applications. (#7083)
- Mouse tracking should now work for most applications.
Note there are still known issues with Chrome. (#6722, #7915)
-
- NVDA will announce UIA item status property changes in places such as the Visual Studio 2022 create app packages dialog. (#13973)
-

Expand Down