88making sure NVDA can run on a minimum supported version of Windows.
99"""
1010
11+ from typing import Optional
1112import sys
1213import os
1314import functools
15+ import winreg
16+ from buildVersion import version_year
17+
18+
19+ # Records a mapping between Windows builds and release names.
20+ # These include build 10240 for Windows 10 1507 and releases with multiple release builds.
21+ # These are applicable to Windows 10 as they report the same system version (10.0).
22+ _BUILDS_TO_RELEASE_NAMES = {
23+ 10240 : "Windows 10 1507" ,
24+ 10586 : "Windows 10 1511" ,
25+ 14393 : "Windows 10 1607" ,
26+ 15063 : "Windows 10 1703" ,
27+ 16299 : "Windows 10 1709" ,
28+ 17134 : "Windows 10 1803" ,
29+ 17763 : "Windows 10 1809" ,
30+ 18362 : "Windows 10 1903" ,
31+ 18363 : "Windows 10 1909" ,
32+ 19041 : "Windows 10 2004" ,
33+ 19042 : "Windows 10 20H2" ,
34+ 19043 : "Windows 10 21H1"
35+ }
36+
37+
38+ @functools .lru_cache (maxsize = 1 )
39+ def _getRunningVersionNameFromWinReg () -> str :
40+ """Returns the Windows release name defined in Widnows Registry.
41+ This is applicable on Windows 10 Version 1511 (build 10586) and later.
42+ """
43+ # Release name is recorded in Windows Registry from Windows 10 Version 1511 (build 10586) onwards.
44+ if getWinVer () < WIN10_1511 :
45+ raise RuntimeError ("Release name is not recorded in Windows Registry on this version of Windows" )
46+ # Cache the version in use on the system.
47+ with winreg .OpenKey (
48+ winreg .HKEY_LOCAL_MACHINE , r"Software\Microsoft\Windows NT\CurrentVersion"
49+ ) as currentVersion :
50+ # Version 20H2 and later where a separate display version string is used.
51+ try :
52+ releaseId = winreg .QueryValueEx (currentVersion , "DisplayVersion" )[0 ]
53+ except OSError :
54+ # Don't set anything if this is Windows 10 1507 or earlier.
55+ try :
56+ releaseId = winreg .QueryValueEx (currentVersion , "ReleaseID" )[0 ]
57+ except OSError :
58+ releaseId = ""
59+ return releaseId
1460
1561
1662@functools .total_ordering
@@ -26,20 +72,26 @@ def __init__(
2672 major : int = 0 ,
2773 minor : int = 0 ,
2874 build : int = 0 ,
75+ releaseName : Optional [str ] = "" ,
2976 servicePack : str = "" ,
3077 productType : str = ""
3178 ):
3279 self .major = major
3380 self .minor = minor
3481 self .build = build
82+ if releaseName :
83+ self .releaseName = releaseName
84+ else :
85+ self .releaseName = self ._getWindowsReleaseName ()
3586 self .servicePack = servicePack
3687 self .productType = productType
3788
38- def _windowsVersionToReleaseName (self ):
39- """Returns release names for a given Windows version.
89+ def _getWindowsReleaseName (self ) -> str :
90+ """Returns the public release name for a given Windows release based on major, minor, and build.
91+ This is useful if release names are not defined when constructing this class.
4092 For example, 6.1 will return 'Windows 7'.
4193 For Windows 10, feature update release name will be included.
42- On server systems, client release names will be returned.
94+ On server systems, unless noted otherwise, client release names will be returned.
4395 For example, 'Windows 10 1809' will be returned on Server 2019 systems.
4496 """
4597 if (self .major , self .minor ) == (6 , 1 ):
@@ -49,17 +101,18 @@ def _windowsVersionToReleaseName(self):
49101 elif (self .major , self .minor ) == (6 , 3 ):
50102 return "Windows 8.1"
51103 elif self .major == 10 :
52- buildsToReleases = {build : release for release , build in WIN10_RELEASE_NAME_TO_BUILDS .items ()}
53- if self .build in buildsToReleases :
54- return f"Windows 10 { buildsToReleases [self .build ]} "
55- else :
56- # Windows Insider build.
57- return "Windows 10 prerelease"
104+ # From Version 1511 (build 10586), release Id/display version comes from Windows Registry.
105+ # However there are builds with no release name (Version 1507/10240)
106+ # or releases with different builds.
107+ # Look these up first before asking Windows Registry.
108+ if self .build in _BUILDS_TO_RELEASE_NAMES :
109+ return _BUILDS_TO_RELEASE_NAMES [self .build ]
110+ return "Windows 10 unknown"
58111 else :
59- raise RuntimeError ( "Unknown Windows release" )
112+ return " Windows release unknown"
60113
61114 def __repr__ (self ):
62- winVersionText = [self ._windowsVersionToReleaseName () ]
115+ winVersionText = [self .releaseName ]
63116 winVersionText .append (f"({ self .major } .{ self .minor } .{ self .build } )" )
64117 if self .servicePack != "" :
65118 winVersionText .append (f"service pack { self .servicePack } " )
@@ -103,10 +156,16 @@ def getWinVer():
103156 """Returns a record of current Windows version NVDA is running on.
104157 """
105158 winVer = sys .getwindowsversion ()
159+ # #12509: on Windows 10, fetch whatever Windows Registry says for the current build.
160+ try :
161+ releaseName = f"Windows 10 { _getRunningVersionNameFromWinReg ()} "
162+ except RuntimeError :
163+ releaseName = None
106164 return WinVersion (
107165 major = winVer .major ,
108166 minor = winVer .minor ,
109167 build = winVer .build ,
168+ releaseName = releaseName ,
110169 servicePack = winVer .service_pack ,
111170 productType = ("workstation" , "domain controller" , "server" )[winVer .product_type - 1 ]
112171 )
@@ -124,20 +183,23 @@ def isUwpOcrAvailable():
124183 return os .path .isdir (UWP_OCR_DATA_PATH )
125184
126185
127- WIN10_RELEASE_NAME_TO_BUILDS = {
128- "1507" : 10240 ,
129- "1511" : 10586 ,
130- "1607" : 14393 ,
131- "1703" : 15063 ,
132- "1709" : 16299 ,
133- "1803" : 17134 ,
134- "1809" : 17763 ,
135- "1903" : 18362 ,
136- "1909" : 18363 ,
137- "2004" : 19041 ,
138- "20H2" : 19042 ,
139- "21H1" : 19043 ,
140- }
186+ # Deprecated: Windows 10 releases will be obtained from Windows Registry, no entries will be added.
187+ # The below map will be removed in 2022.1.
188+ if version_year < 2022 :
189+ WIN10_RELEASE_NAME_TO_BUILDS = {
190+ "1507" : 10240 ,
191+ "1511" : 10586 ,
192+ "1607" : 14393 ,
193+ "1703" : 15063 ,
194+ "1709" : 16299 ,
195+ "1803" : 17134 ,
196+ "1809" : 17763 ,
197+ "1903" : 18362 ,
198+ "1909" : 18363 ,
199+ "2004" : 19041 ,
200+ "20H2" : 19042 ,
201+ "21H1" : 19043 ,
202+ }
141203
142204
143205def isFullScreenMagnificationAvailable ():
0 commit comments