Skip to content

Commit be9e0f7

Browse files
authored
Merge 3107312 into f99644e
2 parents f99644e + 3107312 commit be9e0f7

2 files changed

Lines changed: 181 additions & 24 deletions

File tree

source/winVersion.py

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2006-2020 NV Access Limited
2+
# Copyright (C) 2006-2020 NV Access Limited, Bill Dengler, Joseph Lee
33
# This file is covered by the GNU General Public License.
44
# See the file COPYING for more details.
55

66
import sys
77
import os
8+
import functools
89
import winUser
910

1011
winVersion=sys.getwindowsversion()
@@ -15,6 +16,103 @@
1516
winVersionText+=".%d"%winVersion.service_pack_minor
1617
winVersionText+=" %s" % ("workstation","domain controller","server")[winVersion.product_type-1]
1718

19+
20+
@functools.total_ordering
21+
class WinVersion(object):
22+
"""
23+
Represents a Windows release.
24+
Includes version major, minor, build, service pack information,
25+
as well as tools such as checking for specific Windows 10 releases.
26+
"""
27+
28+
def __init__(
29+
self,
30+
release: str = None,
31+
major: int = 0,
32+
minor: int = 0,
33+
build: int = 0,
34+
servicePack: str = ""
35+
):
36+
if release in (None, ""):
37+
self.major = major
38+
self.minor = minor
39+
self.build = build
40+
elif release == "7":
41+
self.major = 6
42+
self.minor = 1
43+
self.build = 7601
44+
elif release == "8":
45+
self.major = 6
46+
self.minor = 2
47+
self.build = 9200
48+
elif release == "8.1":
49+
self.major = 6
50+
self.minor = 3
51+
self.build = 9600
52+
elif release in ("10", "1507"):
53+
self.major = 10
54+
self.minor = 0
55+
self.build = 10240
56+
elif release in WIN10_VERSIONS_TO_BUILDS:
57+
self.major = 10
58+
self.minor = 0
59+
self.build = WIN10_VERSIONS_TO_BUILDS[release]
60+
else:
61+
raise ValueError("Cannot create Windows version information for the specified release")
62+
self.servicePack = "1" if release == "7" else ""
63+
64+
def __eq__(self, other):
65+
return (
66+
(self.major, self.minor, self.build)
67+
== (other.major, other.minor, other.build)
68+
)
69+
70+
def __ge__(self, other):
71+
return (
72+
(self.major, self.minor, self.build)
73+
>= (other.major, other.minor, other.build)
74+
)
75+
76+
def isWin10(self, release: str = "1507", atLeast: bool = True):
77+
"""
78+
Returns True if NVDA is running on the supplied release version of Windows 10.
79+
If no argument is supplied, returns True for all public Windows 10 releases.
80+
@param release: a release version of Windows 10 (such as 1903).
81+
@param atLeast: return True if NVDA is running on at least this Windows 10 build
82+
(i.e. this version or higher).
83+
"""
84+
if self.major != 10:
85+
return False
86+
# #11795: special cases.
87+
# Remove this workaround in a future API compatibility (year.1) release.
88+
# October 2020 Update is 20H2, not 2009.
89+
if release == "2009":
90+
release = "20H2"
91+
if release not in WIN10_VERSIONS_TO_BUILDS:
92+
raise ValueError(f"Unknown Windows 10 release {release}")
93+
if atLeast:
94+
return self.build >= WIN10_VERSIONS_TO_BUILDS[release]
95+
else:
96+
return self.build > WIN10_VERSIONS_TO_BUILDS[release]
97+
98+
99+
def getWinVer():
100+
return WinVersion(
101+
major=winVersion.major,
102+
minor=winVersion.minor,
103+
build=winVersion.build,
104+
servicePack=winVersion.service_pack
105+
)
106+
107+
108+
def getWinVerFromVersionText(versionText: str):
109+
major, minor, build = versionText.split(".")
110+
return WinVersion(
111+
major=int(major),
112+
minor=int(minor),
113+
build=int(build)
114+
)
115+
18116
def isSupportedOS():
19117
# NVDA can only run on Windows 7 Service pack 1 and above
20118
return (winVersion.major,winVersion.minor,winVersion.service_pack_major) >= (6,1,1)
@@ -28,38 +126,28 @@ def isUwpOcrAvailable():
28126

29127

30128
WIN10_VERSIONS_TO_BUILDS = {
31-
1507: 10240,
32-
1511: 10586,
33-
1607: 14393,
34-
1703: 15063,
35-
1709: 16299,
36-
1803: 17134,
37-
1809: 17763,
38-
1903: 18362,
39-
1909: 18363,
40-
2004: 19041,
41-
2009: 19042,
129+
"1507": 10240,
130+
"1511": 10586,
131+
"1607": 14393,
132+
"1703": 15063,
133+
"1709": 16299,
134+
"1803": 17134,
135+
"1809": 17763,
136+
"1903": 18362,
137+
"1909": 18363,
138+
"2004": 19041,
139+
"20H2": 19042,
42140
}
43141

44142

45143
def isWin10(version: int = 1507, atLeast: bool = True):
46144
"""
145+
@deprecated: use getWinVer().isWin10 instead.
47146
Returns True if NVDA is running on the supplied release version of Windows 10. If no argument is supplied, returns True for all public Windows 10 releases.
48147
@param version: a release version of Windows 10 (such as 1903).
49148
@param atLeast: return True if NVDA is running on at least this Windows 10 build (i.e. this version or higher).
50149
"""
51-
if winVersion.major != 10:
52-
return False
53-
try:
54-
if atLeast:
55-
return winVersion.build >= WIN10_VERSIONS_TO_BUILDS[version]
56-
else:
57-
return winVersion.build == WIN10_VERSIONS_TO_BUILDS[version]
58-
except KeyError:
59-
from logHandler import log
60-
log.error("Unknown Windows 10 version {}".format(version))
61-
return False
62-
150+
return getWinVer().isWin10(release=str(version), atLeast=atLeast)
63151

64152
def isFullScreenMagnificationAvailable():
65153
return (winVersion.major, winVersion.minor) >= (6, 2)

tests/unit/test_winVersion.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# A part of NonVisual Desktop Access (NVDA)
2+
# This file is covered by the GNU General Public License.
3+
# See the file COPYING for more details.
4+
# Copyright (C) 2020 NV Access Limited, Joseph Lee
5+
6+
"""Unit tests for the Windows version module."""
7+
8+
import unittest
9+
import sys
10+
import winVersion
11+
12+
13+
class TestWinVersion(unittest.TestCase):
14+
15+
def test_getWinVer(self):
16+
# Test a 3-tuple consisting of version major, minor, build.
17+
# sys.getwindowsversion() internally returns a named tuple, so comparing tuples is possible.
18+
currentWinVer = winVersion.getWinVer()
19+
winVerPython = sys.getwindowsversion()
20+
self.assertTupleEqual(
21+
(currentWinVer.major, currentWinVer.minor, currentWinVer.build),
22+
winVerPython[:3]
23+
)
24+
25+
def test_specificWinVer(self):
26+
# Try detecting Windows 8.1 or later.
27+
win81 = winVersion.WinVersion(release="8.1")
28+
self.assertTupleEqual(
29+
(win81.major, win81.minor, win81.build),
30+
(6, 3, 9600)
31+
)
32+
33+
def test_getWinVerFromVersionText(self):
34+
winTenInitial = winVersion.getWinVerFromVersionText("10.0.10240")
35+
self.assertTupleEqual(
36+
(winTenInitial.major, winTenInitial.minor, winTenInitial.build),
37+
(10, 0, 10240)
38+
)
39+
40+
def test_moreRecentWinVer(self):
41+
# Specificlaly to test operators.
42+
minimumWinVer = winVersion.WinVersion(
43+
major=6,
44+
minor=1,
45+
build=7601
46+
)
47+
self.assertGreaterEqual(
48+
winVersion.getWinVer(), minimumWinVer
49+
)
50+
51+
def test_isWin10(self):
52+
# Say "False" if tested on anything other than Windows 10.
53+
currentWinVer = winVersion.getWinVer()
54+
if currentWinVer.major == 10:
55+
self.assertTrue(currentWinVer.isWin10())
56+
else:
57+
self.assertFalse(currentWinVer.isWin10())
58+
59+
@unittest.skipIf(
60+
sys.getwindowsversion().major != 10,
61+
"requires Windows 10"
62+
)
63+
def test_isWin10NonexistentRelease(self):
64+
# Test the fact that there is no Version 2003 (Version 2004 exists, however).
65+
self.assertRaises(
66+
ValueError,
67+
winVersion.getWinVer().isWin10,
68+
release="2003"
69+
)

0 commit comments

Comments
 (0)