Skip to content

Commit 83193c2

Browse files
authored
Merge fdc8bfe into f47eb66
2 parents f47eb66 + fdc8bfe commit 83193c2

5 files changed

Lines changed: 184 additions & 50 deletions

File tree

source/core.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,9 @@ def main():
427427
Finally, it starts the wx main loop.
428428
"""
429429
log.debug("Core starting")
430-
431-
ctypes.windll.user32.SetProcessDPIAware()
430+
if globalVars.runningAsSource:
431+
from winAPI.dpiAwareness import setDPIAwareness
432+
setDPIAwareness()
432433

433434
import config
434435
if not globalVars.appArgs.configPath:

source/manifest.template.xml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<assembly
3+
xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
4+
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
5+
>
6+
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
7+
<security>
8+
<requestedPrivileges>
9+
<requestedExecutionLevel
10+
level="asInvoker"
11+
uiAccess="%(uiAccess)s"
12+
/>
13+
</requestedPrivileges>
14+
</security>
15+
</trustInfo>
16+
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
17+
<application>
18+
<!-- Windows 7 -->
19+
<supportedOS
20+
Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"
21+
/>
22+
<!-- Windows 8 -->
23+
<supportedOS
24+
Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"
25+
/>
26+
<!-- Windows 8.1 -->
27+
<supportedOS
28+
Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"
29+
/>
30+
<!-- Windows 10/11 -->
31+
<supportedOS
32+
Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
33+
/>
34+
</application>
35+
</compatibility>
36+
<asmv3:application>
37+
<asmv3:windowsSettings>
38+
<dpiAware
39+
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings"
40+
>
41+
true/pm
42+
</dpiAware>
43+
<dpiAwareness
44+
xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
45+
>
46+
PerMonitorV2, PerMonitor
47+
</dpiAwareness>
48+
</asmv3:windowsSettings>
49+
</asmv3:application>
50+
</assembly>

source/setup.py

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,39 @@
11
# -*- coding: UTF-8 -*-
2-
#setup.py
3-
#A part of NonVisual Desktop Access (NVDA)
4-
#Copyright (C) 2006-2018 NV Access Limited, Peter Vágner, Joseph Lee
5-
#This file is covered by the GNU General Public License.
6-
#See the file COPYING for more details.
2+
# A part of NonVisual Desktop Access (NVDA)
3+
# Copyright (C) 2006-2022 NV Access Limited, Peter Vágner, Joseph Lee
4+
# This file is covered by the GNU General Public License.
5+
# See the file COPYING for more details.
76

87
import os
98
import sys
109
import copy
1110
import gettext
1211
gettext.install("nvda")
1312
from setuptools import setup
14-
import py2exe as py2exeModule
13+
# While the import of py2exe appears unused it is required.
14+
# py2exe monkey patches distutils when importing py2exe for the first time.
15+
import py2exe as py2exeModule # noqa: F401, E402
1516
from glob import glob
1617
import fnmatch
1718
# versionInfo names must be imported after Gettext
1819
# Suppress E402 (module level import not at top of file)
1920
from versionInfo import (
21+
copyright as NVDAcopyright, # copyright is a reserved python keyword
22+
description,
2023
formatBuildVersionString,
2124
name,
25+
publisher,
26+
url,
2227
version,
23-
publisher
2428
) # noqa: E402
25-
from versionInfo import *
2629
from py2exe import distutils_buildexe
2730
from py2exe.dllfinder import DllFinder
2831
import wx
2932
import importlib.machinery
3033
# Explicitly put the nvda_dmp dir on the build path so the DMP library is included
3134
sys.path.append(os.path.join("..", "include", "nvda_dmp"))
3235
RT_MANIFEST = 24
33-
manifest_template = """\
34-
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
35-
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
36-
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
37-
<security>
38-
<requestedPrivileges>
39-
<requestedExecutionLevel
40-
level="asInvoker"
41-
uiAccess="%(uiAccess)s"
42-
/>
43-
</requestedPrivileges>
44-
</security>
45-
</trustInfo>
46-
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
47-
<application>
48-
<!-- Windows 7 -->
49-
<supportedOS
50-
Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"
51-
/>
52-
<!-- Windows 8 -->
53-
<supportedOS
54-
Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"
55-
/>
56-
<!-- Windows 8.1 -->
57-
<supportedOS
58-
Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"
59-
/>
60-
<!-- Windows 10 -->
61-
<supportedOS
62-
Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
63-
/>
64-
</application>
65-
</compatibility>
66-
</assembly>
67-
"""
36+
manifestTemplateFilePath = "manifest.template.xml"
6837

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

9463
def run(self):
64+
with open(manifestTemplateFilePath, "r", encoding="utf-8") as manifestTemplateFile:
65+
manifestTemplate = manifestTemplateFile.read()
9566
dist = self.distribution
9667
if self.enable_uiAccess:
9768
# Add a target for nvda_uiAccess, using nvda_noUIAccess as a base.
@@ -108,7 +79,7 @@ def run(self):
10879
(
10980
RT_MANIFEST,
11081
1,
111-
(manifest_template % dict(uiAccess=target['uiAccess'])).encode("utf-8")
82+
(manifestTemplate % dict(uiAccess=target['uiAccess'])).encode("utf-8")
11283
),
11384
]
11485
super(py2exe, self).run()
@@ -167,7 +138,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
167138
"description":"NVDA application",
168139
"product_name":name,
169140
"product_version":version,
170-
"copyright":copyright,
141+
"copyright": NVDAcopyright,
171142
"company_name":publisher,
172143
},
173144
# The nvda_uiAccess target will be added at runtime if required.
@@ -180,7 +151,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
180151
"description": name,
181152
"product_name":name,
182153
"product_version": version,
183-
"copyright": copyright,
154+
"copyright": NVDAcopyright,
184155
"company_name": publisher,
185156
},
186157
{
@@ -193,7 +164,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
193164
"description": "NVDA Ease of Access proxy",
194165
"product_name":name,
195166
"product_version": version,
196-
"copyright": copyright,
167+
"copyright": NVDAcopyright,
197168
"company_name": publisher,
198169
},
199170
],
@@ -207,7 +178,7 @@ def getRecursiveDataFiles(dest,source,excludes=()):
207178
"description": "NVDA Diff-match-patch proxy",
208179
"product_name": name,
209180
"product_version": version,
210-
"copyright": f"{copyright}, Bill Dengler",
181+
"copyright": f"{NVDAcopyright}, Bill Dengler",
211182
"company_name": f"Bill Dengler, {publisher}",
212183
},
213184
],

source/winAPI/constants.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2022 NV Access Limited
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
6+
import enum
7+
8+
9+
class HResult(enum.IntEnum):
10+
# https://docs.microsoft.com/en-us/windows/win32/seccrypto/common-hresult-values
11+
S_OK = 0x00000000
12+
E_ACCESSDENIED = 0x80070005
13+
E_INVALIDARG = 0x80070057
14+
15+
16+
class SystemErrorCodes(enum.IntEnum):
17+
# https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
18+
ACCESS_DENIED = 0x5
19+
INVALID_PARAMETER = 0x57

source/winAPI/dpiAwareness.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# Copyright (C) 2022 NV Access Limited
3+
# This file is covered by the GNU General Public License.
4+
# See the file COPYING for more details.
5+
6+
import ctypes
7+
8+
from logHandler import log
9+
10+
from .constants import (
11+
HResult,
12+
SystemErrorCodes,
13+
)
14+
15+
16+
def setDPIAwareness() -> None:
17+
"""
18+
Different versions of Windows inconsistently support different styles of DPI Awareness.
19+
This function attempts to set process DPI awareness using the most modern Windows API method available.
20+
21+
Only call this function once per instance of NVDA.
22+
23+
Only call this function when running from source.
24+
It is recommended that you set the process-default DPI awareness via application manifest.
25+
Setting the process-default DPI awareness via these API calls can lead to unexpected application behavior.
26+
"""
27+
# Support is inconsistent across versions of Windows, so try/excepts are used rather than explicit
28+
# version checks.
29+
# https://docs.microsoft.com/en-us/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process
30+
try:
31+
# An advancement over the original per-monitor DPI awareness mode,
32+
# which enables applications to access new DPI-related scaling behaviors on a per top-level window basis.
33+
# For more information on behaviours, refer to:
34+
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiawarenesscontext
35+
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4
36+
# Method introduced in Windows 10
37+
# https://docs.microsoft.com/en-us/windows/win32/hidpi/dpi-awareness-context
38+
success = ctypes.windll.user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
39+
except AttributeError:
40+
log.debug("Cannot set DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2")
41+
else:
42+
if success:
43+
return
44+
else:
45+
errorCode = ctypes.GetLastError()
46+
if errorCode == SystemErrorCodes.ACCESS_DENIED:
47+
# The DPI awareness is already set,
48+
# either by calling this API previously or through the application (.exe) manifest.
49+
# This is unexpected as we should only set DPI awareness once.
50+
# NVDA sets DPI awareness from the manifest,
51+
# however this function should only be called when running from source.
52+
log.error("DPI Awareness already set.")
53+
return
54+
elif errorCode == SystemErrorCodes.INVALID_PARAMETER:
55+
log.error("DPI Awareness function provided invalid argument.")
56+
else:
57+
log.error(f"Unknown error setting DPI Awareness. Error code: {errorCode}")
58+
59+
log.debug("Falling back to older method of setting DPI Awareness")
60+
61+
try:
62+
# https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/nf-shellscalingapi-setprocessdpiawareness
63+
# This window checks for the DPI when it is created and adjusts the scale factor whenever the DPI changes.
64+
# These processes are not automatically scaled by the system.
65+
PROCESS_PER_MONITOR_DPI_AWARE = 2
66+
# Method introduced in Windows 8
67+
hResult = ctypes.windll.shcore.SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)
68+
except AttributeError:
69+
log.debug("Cannot set PROCESS_PER_MONITOR_DPI_AWARE")
70+
else:
71+
if hResult == HResult.S_OK:
72+
return
73+
elif hResult == HResult.E_ACCESSDENIED:
74+
# The DPI awareness is already set,
75+
# either by calling this API previously or through the application (.exe) manifest.
76+
# This is unexpected as we should only set DPI awareness once.
77+
# NVDA sets DPI awareness from the manifest,
78+
# however this function should only be called when running from source.
79+
log.error("DPI Awareness already set.")
80+
return
81+
elif hResult == HResult.E_INVALIDARG:
82+
log.error("DPI Awareness function provided invalid argument.")
83+
else:
84+
log.error(f"Unknown error setting DPI Awareness. HRESULT: {hResult}")
85+
86+
log.debug("Falling back to legacy method of setting DPI Awareness")
87+
88+
# Method introduced in Windows Vista
89+
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setprocessdpiaware
90+
result = ctypes.windll.user32.SetProcessDPIAware()
91+
if result == 0:
92+
errorCode = ctypes.GetLastError()
93+
log.error(f"Unknown error setting DPI Awareness. Error code: {errorCode}")

0 commit comments

Comments
 (0)