diff --git a/.gitmodules b/.gitmodules index 95d46bf3d50..561e130e4c4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,8 +21,7 @@ ignore = untracked [submodule "include/comtypes"] path = include/comtypes - url = https://github.com/enthought/comtypes.git - ignore = untracked + url = https://github.com/nvaccess/comtypes-bin [submodule "include/scons"] path = include/scons url = https://github.com/SConsProject/scons @@ -38,3 +37,6 @@ [submodule "include/cldr-emoji-annotation"] path = include/cldr-emoji-annotation url = https://github.com/fujiwarat/cldr-emoji-annotation +[submodule "include/py2exe"] + path = include/py2exe + url = https://github.com/nvaccess/py2exe-bin diff --git a/appveyor.yml b/appveyor.yml index b5e2ab5908e..e2d23a3e65d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,6 +3,8 @@ version: "{branch}-{build}" branches: only: + - threshold + - threshold_py3_staging - master - beta - rc @@ -11,7 +13,7 @@ branches: - /release-.*/ environment: - PY_PYTHON: 2.7-32 + PY_PYTHON: 3.7-32 encFileKey: secure: ekOvuyywHuDdGZmRmoj+b3jfrq39A2xlx4RD5ZUGd/8= mozillaSymsAuthToken: @@ -149,7 +151,7 @@ test_script: $errorCode=0 $outDir = (Resolve-Path .\testOutput\unit\) $unitTestsXml = "$outDir\unitTests.xml" - py -m nose --with-xunit --xunit-file="$unitTestsXml" ./tests/unit + py -m nose -sv --with-xunit --xunit-file="$unitTestsXml" ./tests/unit if($LastExitCode -ne 0) { $errorCode=$LastExitCode } Push-AppveyorArtifact $unitTestsXml $wc = New-Object 'System.Net.WebClient' diff --git a/appveyor/mozillaSyms.py b/appveyor/mozillaSyms.py index 6adcfe68f46..f3d982a0803 100644 --- a/appveyor/mozillaSyms.py +++ b/appveyor/mozillaSyms.py @@ -6,7 +6,6 @@ To update the list of symbols uploaded to Mozilla, see the DLL_NAMES constant below. """ -from __future__ import print_function import argparse import os import subprocess @@ -43,7 +42,8 @@ def __init__(self, returncode, stderr): def check_output(command): proc = subprocess.Popen(command, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.PIPE, + text=True) stdout, stderr = proc.communicate() if proc.returncode != 0: raise ProcError(proc.returncode, stderr) diff --git a/appx/sconscript b/appx/sconscript index bc165397444..da3e9672040 100644 --- a/appx/sconscript +++ b/appx/sconscript @@ -31,7 +31,7 @@ def getCertPublisher(env): return env['publisher'] certPassword=env.get('certPassword','') cmd=['certutil','-dump','-p',certPassword,File('#'+certFile).abspath.replace('/','\\')] - lines=subprocess.check_output(cmd).splitlines() + lines=subprocess.run(cmd,check=True,capture_output=True,text=True).stdout.splitlines() linePrefix='Subject: ' for line in lines: if line.startswith(linePrefix): diff --git a/cldrDict_sconscript b/cldrDict_sconscript index 8a9f55bb5a6..aba880c0ce0 100644 --- a/cldrDict_sconscript +++ b/cldrDict_sconscript @@ -29,7 +29,7 @@ def createCLDRAnnotationsDict(sources, dest): assert cldrDict, "cldrDict is empty" with codecs.open(dest, "w", "utf_8_sig", errors="replace") as dictFile: dictFile.write(u"symbols:\r\n") - for pattern, description in cldrDict.iteritems(): + for pattern, description in cldrDict.items(): dictFile.write(u"{pattern}\t{description}\tsome\r\n".format( pattern=pattern, description=description @@ -119,7 +119,7 @@ NVDAToCLDRLocales = { annotationsDir = env.Dir("include/cldr-emoji-annotation/annotations") annotationsDerivedDir = env.Dir("include/cldr-emoji-annotation/annotationsDerived") -for destLocale, sourceLocales in NVDAToCLDRLocales.iteritems(): +for destLocale, sourceLocales in NVDAToCLDRLocales.items(): cldrSources = [] # First add all annotations, then the derived ones. for sourceLocale in sourceLocales: diff --git a/developerGuide.t2t b/developerGuide.t2t index d075e6ad25c..a91a4127868 100644 --- a/developerGuide.t2t +++ b/developerGuide.t2t @@ -796,6 +796,15 @@ These variables are: - brlRegions: The braille regions from the active braille buffer - +++ Tab completion ++ +The input control supports tab-completion of variables and member attributes names. +Hit the tab key once to complete the current input if there is one single candidate. +If there is more than one, hit the tab key a second time to open a menu listing all matching possibilities. +By default, only "public" member attributes are listed. +That is, if the input is "nav.", attribute names with no leading underscore are proposed. +If the input is "nav._", attribute names with a single leading underscore are proposed. +Similarly, if the input is "nav.__", attribute names with two leading underscores are proposed. + + Remote Python Console + A remote Python console is available for situations where remote debugging of NVDA is useful. It is similar to the [local Python console #PythonConsole] discussed above, but is accessed via TCP. diff --git a/include/comtypes b/include/comtypes index 1d3d38b2a61..8c45582fe49 160000 --- a/include/comtypes +++ b/include/comtypes @@ -1 +1 @@ -Subproject commit 1d3d38b2a616674309e7eabe6b5c581042caf32a +Subproject commit 8c45582fe497269594f127f93d1a786d6bc868fb diff --git a/include/py2exe b/include/py2exe new file mode 160000 index 00000000000..c496ae65e4c --- /dev/null +++ b/include/py2exe @@ -0,0 +1 @@ +Subproject commit c496ae65e4cb67fb5a1c17087ec33e4fd69be859 diff --git a/include/pyserial b/include/pyserial index 8bec5552882..c54c81d933b 160000 --- a/include/pyserial +++ b/include/pyserial @@ -1 +1 @@ -Subproject commit 8bec55528827d09937f411e27195ec396993d75c +Subproject commit c54c81d933b847458d465cd77e96cd702ff2e7be diff --git a/include/wxPython b/include/wxPython index 5d878c302f9..11de9371b6b 160000 --- a/include/wxPython +++ b/include/wxPython @@ -1 +1 @@ -Subproject commit 5d878c302f91caaa8970826e9c16d714efec50bd +Subproject commit 11de9371b6b737673ad5ea2703d214ada69a158a diff --git a/keyCommandsDoc.py b/keyCommandsDoc.py index 07f3cbcec6c..c37e56b9195 100644 --- a/keyCommandsDoc.py +++ b/keyCommandsDoc.py @@ -3,7 +3,7 @@ #A part of NonVisual Desktop Access (NVDA) #This file is covered by the GNU General Public License. #See the file COPYING for more details. -#Copyright (C) 2010-2015 NV Access Limited, Mesar Hameed +#Copyright (C) 2010-2019 NV Access Limited, Mesar Hameed, Takuya Nishimoto """Utilities related to NVDA Key Commands documents. """ @@ -213,7 +213,7 @@ def _heading(self, m): self._headings.append(m) self._kcLastHeadingLevel = min(self._kcLastHeadingLevel, level - 1) - RE_SETTING_SINGLE_KEY = re.compile(ur"^[^|]+?[::]\s*(.+?)\s*$") + RE_SETTING_SINGLE_KEY = re.compile(r"^[^|]+?[::]\s*(.+?)\s*$") def _handleSetting(self): if not self._settingsHeaderRow: raise KeyCommandsError("%d, setting command cannot be used before settingsSection command" % self._lineNum) @@ -237,7 +237,7 @@ def _handleSetting(self): # The next few lines should be table rows for each layout. # Alternatively, if the key is common to all layouts, there will be a single line of text specifying the key after a colon. keys = [] - for layout in xrange(self._settingsNumLayouts): + for layout in range(self._settingsNumLayouts): line = next(self._ug).strip() self._lineNum += 1 m = self.RE_SETTING_SINGLE_KEY.match(line) @@ -254,7 +254,7 @@ def _handleSetting(self): if 1 == len(keys) < self._settingsNumLayouts: # The key has only been specified once, so it is the same in all layouts. key = keys[0] - keys[1:] = (key for layout in xrange(self._settingsNumLayouts - 1)) + keys[1:] = (key for layout in range(self._settingsNumLayouts - 1)) # There should now be a blank line. line = next(self._ug).strip() diff --git a/miscDeps b/miscDeps index 3a0065301ec..e2049468258 160000 --- a/miscDeps +++ b/miscDeps @@ -1 +1 @@ -Subproject commit 3a0065301ec91c52ae5bebf2c4403ce429db078c +Subproject commit e20494682583ddb9ab466e9b5a6a5dcfb7a34bc8 diff --git a/nvdaHelper/archBuild_sconscript b/nvdaHelper/archBuild_sconscript index 111896e13f3..f6dfc30ab24 100644 --- a/nvdaHelper/archBuild_sconscript +++ b/nvdaHelper/archBuild_sconscript @@ -34,7 +34,7 @@ def clsidStringToCLSIDDefine(clsidString): "0x"+d[0:8], "0x"+d[8:12], "0x"+d[12:16], - "{%s}"%(",".join("0x"+d[x:x+2] for x in xrange(16,32,2))) + "{%s}"%(",".join("0x"+d[x:x+2] for x in range(16,32,2))) ) def COMProxyDllBuilder(env,target,source,proxyClsid): diff --git a/nvdaHelper/espeak/sconscript b/nvdaHelper/espeak/sconscript index f01d64ab2ff..2f8e8147bd2 100644 --- a/nvdaHelper/espeak/sconscript +++ b/nvdaHelper/espeak/sconscript @@ -59,7 +59,7 @@ def espeak_compilePhonemeData_buildAction(target,source,env): # Unfortunately, there's no way we can flush it or use a different stream # because our eSpeak statically links the CRT. espeak=AutoFreeCDLL(espeakLib[0].abspath) - espeak.espeak_ng_InitializePath(espeakRepo.abspath) + espeak.espeak_ng_InitializePath(os.fsencode(espeakRepo.abspath)) espeak.espeak_ng_CompileIntonation(None,None) espeak.espeak_ng_CompilePhonemeData(22050,None,None) espeak.espeak_Terminate() @@ -72,14 +72,14 @@ def espeak_compileDict_buildAction(target,source,env): # Unfortunately, there's no way we can flush it or use a different stream # because our eSpeak statically links the CRT. espeak=AutoFreeCDLL(espeakLib[0].abspath) - espeak.espeak_Initialize(0,0,target[0].Dir('..').abspath,0x8000) + espeak.espeak_Initialize(0,0,os.fsencode(target[0].Dir('..').abspath),0x8000) try: - lang=source[0].name.split('_')[0] - v=espeak_VOICE(languages=lang+'\x00') + lang=source[0].name.split('_')[0].encode() + v=espeak_VOICE(languages=lang+b'\x00') if espeak.espeak_SetVoiceByProperties(ctypes.byref(v))!=0: print("espeak_compileDict_action: failed to switch to language %s"%lang) return 1 - dictPath=os.path.split(source[0].abspath)[0]+'/' + dictPath=os.fsencode(os.path.split(source[0].abspath)[0]+'/') if espeak.espeak_ng_CompileDictionary(dictPath,None,0,None)!=0: print("espeak_compileDict_action: failed to compile dictionary for language %s"%lang) return diff --git a/nvdaHelper/liblouis/sconscript b/nvdaHelper/liblouis/sconscript index 21c81437efd..0f5a58c3dda 100644 --- a/nvdaHelper/liblouis/sconscript +++ b/nvdaHelper/liblouis/sconscript @@ -1,7 +1,7 @@ ### #This file is a part of the NVDA project. #URL: https://www.nvaccess.org/ -#Copyright 2011-2017 NV Access Limited, Joseph Lee +#Copyright 2011-2018 NV Access Limited, Joseph Lee, Babbage B.V. #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License version 2.0, as published by #the Free Software Foundation. @@ -28,7 +28,7 @@ signExec=env['signExec'] if env['certFile'] else None RE_AC_INIT = re.compile(r"^AC_INIT\(\[(?P.*)\], \[(?P.*)\], \[(?P.*)\], \[(?P.*)\], \[(?P.*)\]\)") def getLouisVersion(): # Get the version from configure.ac. - with file(louisRootDir.File("configure.ac").abspath) as f: + with open(louisRootDir.File("configure.ac").abspath) as f: for line in f: m = RE_AC_INIT.match(line) if m: @@ -54,7 +54,7 @@ env.Append(CPPDEFINES=[ # variants that start with an '_'. This removes those deprecation warnings. */ "_CRT_NONSTDC_NO_DEPRECATE", ("PACKAGE_VERSION", r'\"%s\"' % getLouisVersion()), - ("UNICODE_BITS", 16), + "WIDECHARS_ARE_UCS4", # Tell liblouis.h that we're exporting liblouis dll functions, not importing them. "_EXPORTING", ]) @@ -64,7 +64,7 @@ env.Prepend(CPPPATH=[".", louisSourceDir]) env['CPPDEFINES'].remove("UNICODE") liblouisH = env.Substfile("liblouis.h", louisSourceDir.File("liblouis.h.in"), - SUBST_DICT={"@WIDECHAR_TYPE@": "unsigned short int"}) + SUBST_DICT={"@WIDECHAR_TYPE@": "unsigned int"}) sourceFiles = [ "compileTranslationTable.c", diff --git a/nvdaHelper/local/nvdaHelperLocal.def b/nvdaHelper/local/nvdaHelperLocal.def index 07d654c7e80..d4a4f109f14 100644 --- a/nvdaHelper/local/nvdaHelperLocal.def +++ b/nvdaHelper/local/nvdaHelperLocal.def @@ -57,3 +57,4 @@ EXPORTS dllImportTableHooks_unhookSingle audioDucking_shouldDelay logMessage + getOleClipboardText diff --git a/nvdaHelper/local/oleUtils.cpp b/nvdaHelper/local/oleUtils.cpp new file mode 100644 index 00000000000..e09b7da7241 --- /dev/null +++ b/nvdaHelper/local/oleUtils.cpp @@ -0,0 +1,44 @@ +/* +This file is a part of the NVDA project. +URL: http://www.nvda-project.org/ +Copyright 2019 NV Access Limited. + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2.0, as published by + the Free Software Foundation. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +This license can be found at: +http://www.gnu.org/licenses/old-licenses/gpl-2.0.html +*/ + +#include +#include +#include + +/* + * Fetches a text representation of the given OLE data object + * @param dataObject an IDataObject interface of an OLE object + * @param text a pointer to a BSTR which will hold the resulting text + * @return S_OK on success or an OLE error code. + */ +HRESULT getOleClipboardText(IDataObject* dataObject, BSTR* text) { + FORMATETC format={CF_UNICODETEXT,nullptr,DVASPECT_CONTENT,-1,TYMED_HGLOBAL}; + STGMEDIUM medium={0}; + HRESULT res=dataObject->GetData(&format,&medium); + if(FAILED(res)) { + LOG_DEBUGWARNING(L"IDataObject::getData failed with error "<nul 2>&1 if "%ERRORLEVEL%" == "0" ( rem Python launcher is present in the PATH - rem Call python 2.7 for 32 bits - py -2.7-32 "%~dp0\scons.py" %* + rem Call python 3.7 for 32 bits + py -3.7-32 "%~dp0\scons.py" %* ) else ( rem Python registers itself with the .py extension, so call scons.py. "%~dp0\scons.py" %* diff --git a/scons.py b/scons.py index a6fe33636a1..81d27c3d66f 100644 --- a/scons.py +++ b/scons.py @@ -5,7 +5,7 @@ import os import platform # Variables for storing required version of Python, and the version which is used to run this script. -requiredPythonMajor ="2" +requiredPythonMajor ="3" requiredPythonMinor = "7" requiredPythonArchitecture = "32bit" installedPythonMajor = str(sys.version_info.major) diff --git a/sconstruct b/sconstruct index 82107244f35..f7179eec952 100755 --- a/sconstruct +++ b/sconstruct @@ -1,7 +1,7 @@ ### #This file is a part of the NVDA project. #URL: https://www.nvaccess.org/ -#Copyright 2010-2017 NV Access Limited. +#Copyright 2010-2019 NV Access Limited, Babbage B.V. #This program is free software: you can redistribute it and/or modify #it under the terms of the GNU General Public License version 2.0, as published by #the Free Software Foundation. @@ -15,9 +15,10 @@ import sys import os import time -import _winreg from glob import glob import sourceEnv +from py2exe.dllfinder import pydll +import importlib.util def recursiveCopy(env,targetDir,sourceDir): targets=[] @@ -35,7 +36,7 @@ def recursiveCopy(env,targetDir,sourceDir): # Import NVDA's versionInfo module. import gettext -gettext.install("nvda", unicode=True) +gettext.install("nvda") sys.path.append("source") import versionInfo del sys.path[-1] @@ -72,14 +73,21 @@ vars.Add("certPassword", "The password for the private key in the signing certif vars.Add("certTimestampServer", "The URL of the timestamping server to use to timestamp authenticode signatures", "") vars.Add(PathVariable("outputDir", "The directory where the final built archives and such will be placed", "output",PathVariable.PathIsDirCreate)) vars.Add(ListVariable("nvdaHelperDebugFlags", "a list of debugging features you require", 'none', ["debugCRT","RTC","analyze"])) -vars.Add(EnumVariable('nvdaHelperLogLevel','The level of logging you wish to see, lower is more verbose','15',allowed_values=[str(x) for x in xrange(60)])) +vars.Add(EnumVariable('nvdaHelperLogLevel','The level of logging you wish to see, lower is more verbose','15',allowed_values=[str(x) for x in range(60)])) if "tests" in COMMAND_LINE_TARGETS: vars.Add("unitTests", "A list of unit tests to run", "") if "systemTests" in COMMAND_LINE_TARGETS: vars.Add("filter", "A filter for the name of the system test(s) to run. Wildcards accepted.", "") #Base environment for this and sub sconscripts -env = Environment(variables=vars,HOST_ARCH='x86',tools=["textfile","gettextTool","t2t",keyCommandsDocTool,'doxygen','recursiveInstall']) +env = Environment(variables=vars,HOST_ARCH='x86',tools=[ + "textfile", + "gettextTool", + "t2t" + ,keyCommandsDocTool, + "doxygen", + "recursiveInstall" +]) # speed up subsiquent runs by checking timestamps of targets and dependencies, and only using md5 if timestamps differ. env.Decider('MD5-timestamp') @@ -139,7 +147,7 @@ def signExec(target,source,env): #sys.exit(1) # #3795: signtool can quite commonly fail with timestamping, so allow it to try up to 3 times with a 1 second delay between each try. res=0 - for count in xrange(3): + for count in range(3): res=env.Execute([signExecCmd+[target[0].abspath]]) if not res: return 0 # success @@ -228,31 +236,46 @@ def NVDADistGenerator(target, source, env, for_signature): # We don't do this using normal scons mechanisms because we want it to be cleaned up immediately after this builder # and py2exe will cause bytecode files to be created for it which scons doesn't know about. updateVersionType = env["updateVersionType"] or None - action = [lambda target, source, env: file(buildVersionFn, "w").write( - 'version = {version!r}\r\n' - 'publisher = {publisher!r}\r\n' - 'updateVersionType = {updateVersionType!r}\r\n' - 'version_build = {version_build!r}\r\n' - .format(version=version, publisher=publisher, updateVersionType=updateVersionType,version_build=version_build))] + # Any '\n' characters written are translated to the system default line separator, os.linesep. + action = [lambda target, source, env: open(buildVersionFn, "w", encoding="utf-8").write( + 'version = {version!r}\n' + 'publisher = {publisher!r}\n' + 'updateVersionType = {updateVersionType!r}\n' + 'version_build = {version_build!r}\n' + .format(version=version, publisher=publisher, updateVersionType=updateVersionType,version_build=version_build) + ) + # In Python 3 write returns the number of characters written, + # which scons treats as an error code. + and None] buildCmd = ["cd", source[0].path, "&&", sys.executable] if release: buildCmd.append("-O") - buildCmd.extend(("setup.py", "build", "--build-base", buildDir.abspath, + # Issue errors about str(bytes_instance), str(bytearray_instance) + buildCmd.append("-bb") + buildCmd.extend(("setup.py", "-vv", "build", "--build-base", buildDir.abspath, "py2exe", "--dist-dir", target[0].abspath)) if release: buildCmd.append("-O1") - action.append(buildCmd) - if env.get("uiAccess"): buildCmd.append("--enable-uiAccess") + + action.append(buildCmd) + + # Python3 has started signing its main python dll. + # However, Py2exe currently tries to add a string resource to it, invalidating the signature and possibly currupting the certificate. + # Therefore, copy a fresh version of the dll one more time once py2exe has completed. + action.append(Copy(target[0],pydll)) + if certFile: for prog in "nvda_noUIAccess.exe", "nvda_uiAccess.exe", "nvda_slave.exe", "nvda_eoaProxy.exe": action.append(lambda target,source,env, progByVal=prog: signExec([target[0].File(progByVal)],source,env)) - for ext in "", "c", "o": - action.append(Delete(buildVersionFn + ext)) + action.extend(( + Delete(buildVersionFn), + Delete(importlib.util.cache_from_source(buildVersionFn)) + )) return action env["BUILDERS"]["NVDADist"] = Builder(generator=NVDADistGenerator, target_factory=Dir) @@ -353,7 +376,7 @@ def makePot(target, source, env): # Tweak the headers. potFn = str(target[0]) tmpFn = "%s.tmp" % potFn - with file(potFn, "rt") as inp, file(tmpFn, "wt") as out: + with open(potFn, "rt") as inp, open(tmpFn, "wt") as out: for lineNum, line in enumerate(inp): if lineNum == 1: line = "# %s\n" % versionInfo.copyright @@ -371,19 +394,19 @@ devDocs_nvdaHelper=env.Command(devDocsOutputDir.Dir('nvdaHelper'),devDocs_nvdaHe env.Alias('devDocs_nvdaHelper', devDocs_nvdaHelper) env.Clean('devDocs_nvdaHelper', devDocs_nvdaHelper) -devDocs_nvda = env.Command(devDocsOutputDir.Dir("nvda"), None, [[ - "cd", sourceDir.path, "&&", - sys.executable, "-c", "import sourceEnv; from epydoc.cli import cli; cli()", - "--output", "${TARGET.abspath}", - "--quiet", "--html", "--include-log", "--no-frames", - "--name", "NVDA", "--url", "https://www.nvaccess.org/", - "*.py", "appModules", "brailleDisplayDrivers", r"comInterfaces\__init__.py", - "config", "contentRecog", "extensionPoints", "globalPlugins", "gui", "mathPres", "NVDAObjects", - "speechDictHandler", "synthDrivers", "textInfos", "virtualBuffers", -]]) - -env.Alias('devDocs', [devGuide, devDocs_nvda]) -env.Clean('devDocs', [devGuide, devDocs_nvda]) +#devDocs_nvda = env.Command(devDocsOutputDir.Dir("nvda"), None, [[ +# "cd", sourceDir.path, "&&", +# sys.executable, "-c", "import sourceEnv; from epydoc.cli import cli; cli()", +# "--output", "${TARGET.abspath}", +# "--quiet", "--html", "--include-log", "--no-frames", +# "--name", "NVDA", "--url", "https://www.nvaccess.org/", +# "*.py", "appModules", "brailleDisplayDrivers", r"comInterfaces\__init__.py", +# "config", "contentRecog", "extensionPoints", "globalPlugins", "gui", "mathPres", "NVDAObjects", +# "speechDictHandler", "synthDrivers", "textInfos", "virtualBuffers", +#]]) + +#env.Alias('devDocs', [devGuide, devDocs_nvda]) +#env.Clean('devDocs', [devGuide, devDocs_nvda]) pot = env.Command(outputDir.File("%s.pot" % outFilePrefix), # Don't use sourceDir as the source, as this depends on comInterfaces and nvdaHelper. diff --git a/site_scons/site_tools/doxygen.py b/site_scons/site_tools/doxygen.py index cd2950ee73d..f99021dbfd6 100644 --- a/site_scons/site_tools/doxygen.py +++ b/site_scons/site_tools/doxygen.py @@ -23,10 +23,8 @@ import os.path import glob from fnmatch import fnmatch -try: - import _winreg as winreg # Python 2.7 import -except: - import winreg # python 3 import +from functools import reduce +import winreg def fetchDoxygenPath(): try: @@ -74,7 +72,7 @@ def append_data(data, key, new_data, token): key_token = False else: if token == "+=": - if not data.has_key(key): + if key not in data: data[key] = list() elif token == "=": data[key] = list() @@ -90,7 +88,8 @@ def append_data(data, key, new_data, token): append_data( data, key, new_data, '\\' ) # compress lists of len 1 into single strings - for (k, v) in data.items(): + # Wrap items into a list, since we're mutating the dictionary + for (k, v) in list(data.items()): if len(v) == 0: data.pop(k) @@ -121,7 +120,8 @@ def DoxySourceScan(node, env, path): sources = [] - data = DoxyfileParse(node.get_contents()) + with open(node.abspath) as contents: + data = DoxyfileParse(contents) if data.get("RECURSIVE", "NO") == "YES": recursive = True @@ -149,7 +149,7 @@ def DoxySourceScan(node, env, path): for pattern in file_patterns: sources.extend(glob.glob("/".join([node, pattern]))) - sources = map( lambda path: env.File(path), sources ) + sources = [env.File(path) for path in sources] return sources @@ -168,13 +168,14 @@ def DoxyEmitter(source, target, env): "XML": ("NO", "xml"), } - data = DoxyfileParse(source[0].get_contents()) + with open(source[0].abspath) as contents: + data = DoxyfileParse(contents) targets = [] out_dir = source[0].Dir(data.get("OUTPUT_DIRECTORY", ".")) # add our output locations - for (k, v) in output_formats.items(): + for (k, v) in list(output_formats.items()): if data.get("GENERATE_" + k, v[0]) == "YES": targets.append(out_dir.Dir(v[1])) diff --git a/source/IAccessibleHandler.py b/source/IAccessibleHandler.py index d56b86f43d1..49a76e9c881 100644 --- a/source/IAccessibleHandler.py +++ b/source/IAccessibleHandler.py @@ -111,7 +111,7 @@ def flushEvents(self): g=self._genericEventCache self._genericEventCache={} threadCounters={} - for k,v in sorted(g.iteritems(),key=lambda item: item[1],reverse=True): + for k,v in sorted(g.items(),key=lambda item: item[1],reverse=True): threadCount=threadCounters.get(k[-1],0) if threadCount>MAX_WINEVENTS_PER_THREAD: continue @@ -119,12 +119,12 @@ def flushEvents(self): threadCounters[k[-1]]=threadCount+1 f=self._focusEventCache self._focusEventCache={} - for k,v in sorted(f.iteritems(),key=lambda item: item[1])[0-self.maxFocusItems:]: + for k,v in sorted(f.items(),key=lambda item: item[1])[0-self.maxFocusItems:]: heapq.heappush(self._eventHeap,(v,)+k) e=self._eventHeap self._eventHeap=[] r=[] - for count in xrange(len(e)): + for count in range(len(e)): event=heapq.heappop(e)[1:-1] r.append(event) return r @@ -836,7 +836,7 @@ def initialize(): accPropServices=comtypes.client.CreateObject(CAccPropServices) except (WindowsError,COMError) as e: log.debugWarning("AccPropServices is not available: %s"%e) - for eventType in winEventIDsToNVDAEventNames.keys(): + for eventType in winEventIDsToNVDAEventNames: hookID=winUser.setWinEventHook(eventType,eventType,0,cWinEventCallback,0,0,0) if hookID: winEventHookIDs.append(hookID) @@ -995,8 +995,7 @@ def getRecursiveTextFromIAccessibleTextObject(obj,startOffset=0,endOffset=-1): except: return text textList=[] - for i in xrange(len(text)): - t=text[i] + for i, t in enumerate(text): if ord(t)==0xFFFC: try: childTextObject=hypertextObject.hyperlink(hypertextObject.hyperlinkIndex(i+startOffset)).QueryInterface(IAccessible) diff --git a/source/JABHandler.py b/source/JABHandler.py index 4a56ab2d86b..ec064fc803a 100644 --- a/source/JABHandler.py +++ b/source/JABHandler.py @@ -1,14 +1,11 @@ # -*- coding: UTF-8 -*- #javaAccessBridgeHandler.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2007-2017 NV Access Limited, Peter Vágner, Renaud Paquay, Babbage B.V. +#Copyright (C) 2007-2018 NV Access Limited, Peter Vágner, Renaud Paquay, Babbage B.V. #This file is covered by the GNU General Public License. #See the file COPYING for more details. -try: - import Queue as queue # Python 2.7 import -except ImportError: - import queue # Python 3 import +import queue from ctypes import * from ctypes.wintypes import * import time @@ -22,6 +19,7 @@ import controlTypes import NVDAObjects.JAB import core +import textUtils #Some utility functions to help with function defines @@ -261,7 +259,7 @@ class AccessibleKeyBindings(Structure): _fixBridgeFunc(BOOL,'getAccessibleTextSelectionInfo',c_long,JOBJECT64,POINTER(AccessibleTextSelectionInfo),errcheck=True) _fixBridgeFunc(BOOL,'getAccessibleTextAttributes',c_long,JOBJECT64,jint,POINTER(AccessibleTextAttributesInfo),errcheck=True) _fixBridgeFunc(BOOL,'getAccessibleTextLineBounds',c_long,JOBJECT64,jint,POINTER(jint),POINTER(jint),errcheck=True) - _fixBridgeFunc(BOOL,'getAccessibleTextRange',c_long,JOBJECT64,jint,jint,POINTER(c_wchar),c_short,errcheck=True) + _fixBridgeFunc(BOOL,'getAccessibleTextRange',c_long,JOBJECT64,jint,jint,POINTER(c_char),c_short,errcheck=True) _fixBridgeFunc(BOOL,'getCurrentAccessibleValueFromContext',c_long,JOBJECT64,POINTER(c_wchar),c_short,errcheck=True) _fixBridgeFunc(BOOL,'selectTextRange',c_long,JOBJECT64,c_int,c_int,errcheck=True) _fixBridgeFunc(BOOL,'getTextAttributesInRange',c_long,JOBJECT64,c_int,c_int,POINTER(AccessibleTextAttributesInfo),POINTER(c_short),errcheck=True) @@ -350,6 +348,11 @@ def __eq__(self,jabContext): else: return False + # As __eq__ was defined on this class, we must provide __hash__ to remain hashable. + # The default hash implementation is fine for our purposes. + def __hash__(self): + return super().__hash__() + def __ne__(self,jabContext): if self.vmID!=jabContext.vmID or not bridgeDll.isSameObject(self.vmID,self.accContext,jabContext.accContext): return True @@ -388,9 +391,10 @@ def getAccessibleTextRange(self,start,end): length=((end+1)-start) if length<=0: return u"" - text=create_unicode_buffer(length+1) - bridgeDll.getAccessibleTextRange(self.vmID,self.accContext,start,end,text,length) - return text.value + # Use a string buffer, as from an unicode buffer, we can't get the raw data. + buf = create_string_buffer((length +1) * 2) + bridgeDll.getAccessibleTextRange(self.vmID, self.accContext, start, end, buf, length) + return textUtils.getTextFromRawBytes(buf.raw, numChars=length, encoding=textUtils.WCHAR_ENCODING) def getAccessibleTextLineBounds(self,index): index=max(index,0) @@ -716,7 +720,7 @@ def initialize(): if ChangeWindowMessageFilter: if not ChangeWindowMessageFilter(winUser.WM_COPYDATA,1): raise WinError() - for msg in xrange(winUser.WM_USER+1,65535): + for msg in range(winUser.WM_USER+1,65535): if not ChangeWindowMessageFilter(msg,1): raise WinError() #Register java events diff --git a/source/NVDAHelper.py b/source/NVDAHelper.py index b74d032908c..822d1020151 100755 --- a/source/NVDAHelper.py +++ b/source/NVDAHelper.py @@ -6,10 +6,7 @@ import os import sys -try: - import _winreg as winreg # Python 2.7 import -except ImportError: - import winreg # Python 3 import +import winreg import msvcrt import versionInfo import winKernel @@ -304,7 +301,7 @@ def handleInputConversionModeUpdate(oldFlags,newFlags,lcid): if msg: textList.append(msg) else: - for x in xrange(32): + for x in range(32): x=2**x msgs=inputConversionModeMessages.get(x) if not msgs: continue @@ -430,7 +427,9 @@ def __init__(self): pipeRead = self._duplicateAsInheritable(pipeReadOrig) winKernel.closeHandle(pipeReadOrig) # stdout/stderr of the loader process should go to nul. - with file("nul", "w") as nul: + # Though we aren't using pythonic functions to write to nul, + # open it in binary mode as opening it in text mode (the default) doesn't make sense. + with open("nul", "wb") as nul: nulHandle = self._duplicateAsInheritable(msvcrt.get_osfhandle(nul.fileno())) # Set the process to start with the appropriate std* handles. si = winKernel.STARTUPINFO(dwFlags=winKernel.STARTF_USESTDHANDLES, hSTDInput=pipeRead, hSTDOutput=nulHandle, hSTDError=nulHandle) diff --git a/source/NVDAObjects/IAccessible/MSHTML.py b/source/NVDAObjects/IAccessible/MSHTML.py index dd71569cde3..a2cb7b443a5 100644 --- a/source/NVDAObjects/IAccessible/MSHTML.py +++ b/source/NVDAObjects/IAccessible/MSHTML.py @@ -156,7 +156,7 @@ def getZoomFactorsFromHTMLDocument(HTMLDocument): except (COMError,NameError,AttributeError,TypeError): log.debugWarning("unable to fetch DPI factors") return (1,1) - return (devX/logX,devY/logY) + return (devX // logX, devY // logY) def IAccessibleFromHTMLNode(HTMLNode): try: @@ -483,7 +483,7 @@ def kwargsFromSuper(cls,kwargs,relation=None): # #3494: MSHTML's internal coordinates are always at a hardcoded DPI (usually 96) no matter the system DPI or zoom level. xFactor,yFactor=getZoomFactorsFromHTMLDocument(HTMLNode.document) try: - HTMLNode=HTMLNode.document.elementFromPoint(p.x/xFactor,p.y/yFactor) + HTMLNode=HTMLNode.document.elementFromPoint(p.x // xFactor, p.y // yFactor) except: HTMLNode=None if not HTMLNode: @@ -670,7 +670,7 @@ def _get_name(self): title=self.HTMLAttributes['title'] # #2121: MSHTML sometimes returns a node for the title attribute. # This doesn't make any sense, so ignore it. - if title and isinstance(title,basestring): + if title and isinstance(title,str): return title return "" return super(MSHTML,self).name @@ -1139,7 +1139,7 @@ def findExtraIAccessibleOverlayClasses(obj, clsList): clsList.append(MSAATextLeaf) return - if iaRole == oleacc.ROLE_SYSTEM_WINDOW and obj.event_objectID > 0: + if iaRole == oleacc.ROLE_SYSTEM_WINDOW and obj.event_objectID is not None and obj.event_objectID > 0: clsList.append(PluginWindow) elif iaRole == oleacc.ROLE_SYSTEM_CLIENT and obj.event_objectID == winUser.OBJID_CLIENT: clsList.append(RootClient) diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 6da88c0cdb8..d238d1e40cd 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -10,10 +10,12 @@ import os import re import itertools +import importlib from comInterfaces.tom import ITextDocument import tones import languageHandler import textInfos.offsets +import textUtils import colors import time import displayModel @@ -116,6 +118,9 @@ class IA2TextTextInfo(textInfos.offsets.OffsetsTextInfo): detectFormattingAfterCursorMaybeSlow=False + def _get_encoding(self): + return super().encoding + def _getOffsetFromPoint(self,x,y): if self.obj.IAccessibleTextObject.nCharacters>0: offset = self.obj.IAccessibleTextObject.OffsetAtPoint(x,y,IAccessibleHandler.IA2_COORDTYPE_SCREEN_RELATIVE) @@ -198,7 +203,7 @@ def _getSelectionOffsets(self): return [min(start,end),max(start,end)] def _setSelectionOffsets(self,start,end): - for selIndex in xrange(self.obj.IAccessibleTextObject.NSelections): + for selIndex in range(self.obj.IAccessibleTextObject.NSelections): self.obj.IAccessibleTextObject.RemoveSelection(selIndex) if start!=end: self.obj.IAccessibleTextObject.AddSelection(start,end) @@ -253,9 +258,14 @@ def _getCharacterOffsets(self,offset): except COMError: pass try: - return self.obj.IAccessibleTextObject.TextAtOffset(offset,IAccessibleHandler.IA2_TEXT_BOUNDARY_CHAR)[0:2] + start,end,text = self.obj.IAccessibleTextObject.TextAtOffset(offset,IAccessibleHandler.IA2_TEXT_BOUNDARY_CHAR) except COMError: return super(IA2TextTextInfo,self)._getCharacterOffsets(offset) + if textUtils.isHighSurrogate(text) or textUtils.isLowSurrogate(text): + # #8953: Some IA2 implementations, including Gecko and Chromium, + # erroneously report one offset for surrogates. + return super(IA2TextTextInfo,self)._getCharacterOffsets(offset) + return start, end def _getWordOffsets(self,offset): try: @@ -325,7 +335,7 @@ def _iterTextWithEmbeddedObjects(self, withFields, formatConfig=None): items = [self.text] offset = self._startOffset for item in items: - if not isinstance(item, basestring): + if not isinstance(item, str): # This is a field. yield item continue @@ -438,7 +448,8 @@ def findOverlayClasses(self,clsList): if classString and classString.find('.')>0: modString,classString=os.path.splitext(classString) classString=classString[1:] - mod=__import__(modString,globals(),locals(),[]) + # #8712: Python 3 wants a dot (.) when loading a module from the same folder via relative imports, and this is done via package argument. + mod=importlib.import_module("NVDAObjects.IAccessible.%s"%modString, package="NVDAObjects.IAccessible") newCls=getattr(mod,classString) elif classString: newCls=globals()[classString] @@ -449,14 +460,14 @@ def findOverlayClasses(self,clsList): if windowClassName=="Frame Notification Bar" and role==oleacc.ROLE_SYSTEM_CLIENT: clsList.append(IEFrameNotificationBar) elif self.event_objectID==winUser.OBJID_CLIENT and self.event_childID==0 and windowClassName=="_WwG": - from winword import WordDocument + from .winword import WordDocument clsList.append(WordDocument) elif self.event_objectID==winUser.OBJID_CLIENT and self.event_childID==0 and windowClassName in ("_WwN","_WwO"): if self.windowControlID==18: - from winword import SpellCheckErrorField + from .winword import SpellCheckErrorField clsList.append(SpellCheckErrorField) else: - from winword import WordDocument_WwN + from .winword import WordDocument_WwN clsList.append(WordDocument_WwN) elif windowClassName=="DirectUIHWND" and role==oleacc.ROLE_SYSTEM_TOOLBAR: parentWindow=winUser.getAncestor(self.windowHandle,winUser.GA_PARENT) @@ -471,13 +482,13 @@ def findOverlayClasses(self,clsList): from . import mscandui mscandui.findExtraOverlayClasses(self,clsList) elif windowClassName=="GeckoPluginWindow" and self.event_objectID==0 and self.IAccessibleChildID==0: - from mozilla import GeckoPluginWindowRoot + from .mozilla import GeckoPluginWindowRoot clsList.append(GeckoPluginWindowRoot) maybeFlash = False if ((windowClassName in ("MozillaWindowClass", "GeckoPluginWindow") and not isinstance(self.IAccessibleObject, IAccessibleHandler.IAccessible2)) or windowClassName in ("MacromediaFlashPlayerActiveX", "ApolloRuntimeContentWindow", "ShockwaveFlash", "ShockwaveFlashLibrary", "ShockwaveFlashFullScreen", "GeckoFPSandboxChildWindow")): maybeFlash = True - elif windowClassName == "Internet Explorer_Server" and self.event_objectID > 0: + elif windowClassName == "Internet Explorer_Server" and self.event_objectID is not None and self.event_objectID > 0: # #2454: In Windows 8 IE, Flash is exposed in the same HWND as web content. from .MSHTML import MSHTML # This is only possibly Flash if it isn't MSHTML. @@ -611,7 +622,7 @@ def __init__(self,windowHandle=None,IAccessibleObject=None,IAccessibleChildID=No try: left,top,width,height = IAccessibleObject.accLocation(0) windowHandle=winUser.user32.WindowFromPoint(winUser.POINT(left,top)) - except COMError, e: + except COMError as e: log.debugWarning("accLocation failed: %s" % e) if not windowHandle: raise InvalidNVDAObject("Can't get a window handle from IAccessible") @@ -744,14 +755,14 @@ def _get_name(self): res=self.IAccessibleObject.accName(self.IAccessibleChildID) except COMError: res=None - return res if isinstance(res,basestring) and not res.isspace() else None + return res if isinstance(res,str) and not res.isspace() else None def _get_value(self): try: res=self.IAccessibleObject.accValue(self.IAccessibleChildID) except COMError: res=None - return res if isinstance(res,basestring) and not res.isspace() else None + return res if isinstance(res,str) and not res.isspace() else None def _get_actionCount(self): if hasattr(self,'IAccessibleActionObject'): @@ -827,7 +838,7 @@ def _get_role(self): superRole=super(IAccessible,self).role if superRole!=controlTypes.ROLE_WINDOW: return superRole - if isinstance(IARole,basestring): + if isinstance(IARole,str): IARole=IARole.split(',')[0].lower() log.debug("IARole: %s"%IARole) return IAccessibleHandler.IAccessibleRolesToNVDARoles.get(IARole,controlTypes.ROLE_UNKNOWN) @@ -851,12 +862,12 @@ def _get_states(self): except COMError: log.debugWarning("could not get IAccessible states",exc_info=True) else: - states.update(IAccessibleHandler.IAccessibleStatesToNVDAStates[x] for x in (y for y in (1<0: indexInGroup=self.IAccessibleChildID parent=self.parent @@ -1451,11 +1463,10 @@ def event_alert(self): api.processPendingEvents() if self in api.getFocusAncestors(): return - speech.cancelSpeech() - speech.speakObject(self, reason=controlTypes.REASON_FOCUS) + speech.speakObject(self, reason=controlTypes.REASON_FOCUS,priority=speech.SPRI_NOW) for child in self.recursiveDescendants: if controlTypes.STATE_FOCUSABLE in child.states: - speech.speakObject(child, reason=controlTypes.REASON_FOCUS) + speech.speakObject(child, reason=controlTypes.REASON_FOCUS,priority=speech.SPRI_NOW) def event_caret(self): focus = api.getFocusObject() @@ -1509,7 +1520,7 @@ def _get_devInfo(self): info.append("IAccessible accName: %s" % ret) try: ret = iaObj.accRole(childID) - for name, const in oleacc.__dict__.iteritems(): + for name, const in oleacc.__dict__.items(): if not name.startswith("ROLE_"): continue if ret == const: @@ -1523,7 +1534,7 @@ def _get_devInfo(self): try: temp = iaObj.accState(childID) ret = ", ".join( - name for name, const in oleacc.__dict__.iteritems() + name for name, const in oleacc.__dict__.items() if name.startswith("STATE_") and temp & const ) + " (%d)" % temp except Exception as e: @@ -1552,7 +1563,7 @@ def _get_devInfo(self): info.append("IAccessible2 uniqueID: %s" % ret) try: ret = iaObj.role() - for name, const in itertools.chain(oleacc.__dict__.iteritems(), IAccessibleHandler.__dict__.iteritems()): + for name, const in itertools.chain(oleacc.__dict__.items(), IAccessibleHandler.__dict__.items()): if not name.startswith("ROLE_") and not name.startswith("IA2_ROLE_"): continue if ret == const: @@ -1566,7 +1577,7 @@ def _get_devInfo(self): try: temp = iaObj.states ret = ", ".join( - name for name, const in IAccessibleHandler.__dict__.iteritems() + name for name, const in IAccessibleHandler.__dict__.items() if name.startswith("IA2_STATE_") and temp & const ) + " (%d)" % temp except Exception as e: diff --git a/source/NVDAObjects/IAccessible/adobeAcrobat.py b/source/NVDAObjects/IAccessible/adobeAcrobat.py index 13240e3417c..15acf0eefaa 100644 --- a/source/NVDAObjects/IAccessible/adobeAcrobat.py +++ b/source/NVDAObjects/IAccessible/adobeAcrobat.py @@ -39,7 +39,7 @@ } def normalizeStdName(stdName): - if "H1" <= stdName <= "H6": + if stdName and "H1" <= stdName <= "H6": return controlTypes.ROLE_HEADING, stdName[1] try: @@ -58,9 +58,9 @@ def initOverlayClass(self): log.debugWarning("Could not get IServiceProvider") return - if self.event_objectID > 0: + if self.event_objectID is not None and self.event_objectID > 0: self.accID = self.event_objectID - elif self.event_childID > 0: + elif self.event_childID is not None and self.event_childID > 0: self.accID = self.event_childID else: try: @@ -118,7 +118,7 @@ def _getNodeMathMl(self, node): if val: yield val else: - for childNum in xrange(node.GetChildCount()): + for childNum in range(node.GetChildCount()): try: subNode = node.GetChild(childNum).QueryInterface(IPDDomElement) except COMError: @@ -129,7 +129,7 @@ def _getNodeMathMl(self, node): def _get_mathMl(self): # There could be other stuff before the math element. Ug. - for childNum in xrange(self.pdDomNode.GetChildCount()): + for childNum in range(self.pdDomNode.GetChildCount()): try: child = self.pdDomNode.GetChild(childNum).QueryInterface(IPDDomElement) except COMError: diff --git a/source/NVDAObjects/IAccessible/adobeFlash.py b/source/NVDAObjects/IAccessible/adobeFlash.py index 159b02921b6..64c0bf5ae5c 100644 --- a/source/NVDAObjects/IAccessible/adobeFlash.py +++ b/source/NVDAObjects/IAccessible/adobeFlash.py @@ -21,7 +21,7 @@ def _getStoryText(self): def _getRawSelectionOffsets(self): try: return self.obj.ISimpleTextSelectionObject.GetSelection() - except COMError, e: + except COMError as e: if e.hresult == hresult.E_FAIL: # The documentation says that an empty field should return 0 for both values, but instead, we seem to get E_FAIL. # An empty field still has a valid caret. diff --git a/source/NVDAObjects/IAccessible/ia2TextMozilla.py b/source/NVDAObjects/IAccessible/ia2TextMozilla.py index 143ad1d4595..c2d214f6461 100644 --- a/source/NVDAObjects/IAccessible/ia2TextMozilla.py +++ b/source/NVDAObjects/IAccessible/ia2TextMozilla.py @@ -8,7 +8,6 @@ This is now used by other applications as well. """ -import itertools from comtypes import COMError import winUser import textInfos @@ -18,15 +17,16 @@ from NVDAObjects import NVDAObject, NVDAObjectTextInfo from . import IA2TextTextInfo, IAccessible from compoundDocuments import CompoundTextInfo -from locationHelper import RectLTWH +import locationHelper class FakeEmbeddingTextInfo(textInfos.offsets.OffsetsTextInfo): + encoding = None def _getStoryLength(self): return self.obj.childCount def _iterTextWithEmbeddedObjects(self, withFields, formatConfig=None): - return xrange(self._startOffset, self._endOffset) + return range(self._startOffset, self._endOffset) def _getUnitOffsets(self,unit,offset): if unit in (textInfos.UNIT_WORD,textInfos.UNIT_LINE): @@ -111,7 +111,7 @@ def __init__(self, obj, position): self._startObj = self._endObj = tempObj else: self._start, self._startObj, self._end, self._endObj = self._findUnitEndpoints(tempTi, position) - elif isinstance(position, textInfos.Point): + elif isinstance(position, locationHelper.Point): startObj = api.getDesktopObject().objectFromPoint(position.x, position.y) while startObj and startObj.role == controlTypes.ROLE_STATICTEXT: # Skip text leaf nodes. @@ -232,7 +232,7 @@ def _iterRecursiveText(self, ti, controlStack, formatConfig): for item in ti._iterTextWithEmbeddedObjects(controlStack is not None, formatConfig=formatConfig): if item is None: yield u"" - elif isinstance(item, basestring): + elif isinstance(item, str): yield item elif isinstance(item, int): # Embedded object. embedded = _getEmbedded(ti.obj, item) @@ -628,7 +628,7 @@ def compareEndPoints(self, other, which): otherAncs = self._getAncestors(otherTi, otherObj) # Find the first common ancestor. maxAncIndex = min(len(selfAncs), len(otherAncs)) - 1 - for (selfAncTi, selfAncObj), (otherAncTi, otherAncObj) in itertools.izip(selfAncs[maxAncIndex::-1], otherAncs[maxAncIndex::-1]): + for (selfAncTi, selfAncObj), (otherAncTi, otherAncObj) in zip(selfAncs[maxAncIndex::-1], otherAncs[maxAncIndex::-1]): if selfAncObj == otherAncObj: break else: diff --git a/source/NVDAObjects/IAccessible/ia2Web.py b/source/NVDAObjects/IAccessible/ia2Web.py index 43343799a5a..2ef69638e57 100644 --- a/source/NVDAObjects/IAccessible/ia2Web.py +++ b/source/NVDAObjects/IAccessible/ia2Web.py @@ -20,6 +20,7 @@ class Ia2Web(IAccessible): IAccessibleTableUsesTableCellIndexAttrib=True + caretMovementDetectionUsesEvents = False def _get_positionInfo(self): info=super(Ia2Web,self).positionInfo diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 690a2c75cc9..55f899f75db 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -88,7 +88,7 @@ class Gecko1_9(Mozilla): def _get_description(self): rawDescription=super(Mozilla,self).description - if isinstance(rawDescription,basestring) and rawDescription.startswith('Description: '): + if isinstance(rawDescription,str) and rawDescription.startswith('Description: '): return rawDescription[13:] else: return "" diff --git a/source/NVDAObjects/IAccessible/qt.py b/source/NVDAObjects/IAccessible/qt.py index 1822fb0c806..78cd4c15658 100644 --- a/source/NVDAObjects/IAccessible/qt.py +++ b/source/NVDAObjects/IAccessible/qt.py @@ -13,7 +13,7 @@ def _getActiveChild(obj): # QT doesn't do accFocus properly, so find the active child ourselves. child = obj.firstChild - for i in xrange(obj.childCount): + for i in range(obj.childCount): states = child.states if controlTypes.STATE_FOCUSED in states or controlTypes.STATE_SELECTED in states: return child diff --git a/source/NVDAObjects/IAccessible/sysListView32.py b/source/NVDAObjects/IAccessible/sysListView32.py index 802e5f3a154..ae535fe3427 100644 --- a/source/NVDAObjects/IAccessible/sysListView32.py +++ b/source/NVDAObjects/IAccessible/sysListView32.py @@ -404,7 +404,7 @@ def _get_name(self): return self.displayText return name textList = [] - for col in xrange(1, self.childCount + 1): + for col in range(1, self.childCount + 1): content = self._getColumnContent(col) if not content: continue diff --git a/source/NVDAObjects/IAccessible/winword.py b/source/NVDAObjects/IAccessible/winword.py index c446c76b7ca..612237ecebb 100644 --- a/source/NVDAObjects/IAccessible/winword.py +++ b/source/NVDAObjects/IAccessible/winword.py @@ -63,7 +63,7 @@ def _get_states(self): def populateHeaderCellTrackerFromHeaderRows(self,headerCellTracker,table): rows=table.rows numHeaderRows=0 - for rowIndex in xrange(rows.count): + for rowIndex in range(rows.count): try: row=rows.item(rowIndex+1) except COMError: @@ -175,7 +175,7 @@ def fetchAssociatedHeaderCellText(self,cell,columnHeader=False): for info in headerCellTracker.iterPossibleHeaderCellInfosFor(rowNumber,columnNumber,columnHeader=columnHeader): textList=[] if columnHeader: - for headerRowNumber in xrange(info.rowNumber,info.rowNumber+info.rowSpan): + for headerRowNumber in range(info.rowNumber,info.rowNumber+info.rowSpan): tempColumnNumber=columnNumber while tempColumnNumber>=1: try: @@ -186,7 +186,7 @@ def fetchAssociatedHeaderCellText(self,cell,columnHeader=False): break textList.append(headerCell.range.text) else: - for headerColumnNumber in xrange(info.columnNumber,info.columnNumber+info.colSpan): + for headerColumnNumber in range(info.columnNumber,info.columnNumber+info.colSpan): tempRowNumber=rowNumber while tempRowNumber>=1: try: @@ -281,9 +281,9 @@ def script_reportCurrentComment(self,gesture): commentReference=field.field.get('comment') if commentReference: offset=int(commentReference) - range=self.WinwordDocumentObject.range(offset,offset+1) + textRange=self.WinwordDocumentObject.range(offset, offset + 1) try: - text=range.comments[1].range.text + text = textRange.comments[1].range.text except COMError: break if text: @@ -410,7 +410,7 @@ def _get_errorText(self): inBold=False textList=[] for field in fields: - if isinstance(field,basestring): + if isinstance(field,str): if inBold: textList.append(field) elif field.field: inBold=field.field.get('bold',False) diff --git a/source/NVDAObjects/JAB/__init__.py b/source/NVDAObjects/JAB/__init__.py index 51130f8dc96..edba41b4cdd 100644 --- a/source/NVDAObjects/JAB/__init__.py +++ b/source/NVDAObjects/JAB/__init__.py @@ -240,7 +240,7 @@ def _get_keyboardShortcut(self): if not bindings or bindings.keyBindingsCount<1: return None shortcutsList=[] - for index in xrange(bindings.keyBindingsCount): + for index in range(bindings.keyBindingsCount): binding=bindings.keyBindingInfo[index] # We don't support these modifiers if binding.modifiers&(JABHandler.ACCESSIBLE_META_KEYSTROKE|JABHandler.ACCESSIBLE_ALT_GRAPH_KEYSTROKE|JABHandler.ACCESSIBLE_BUTTON1_KEYSTROKE|JABHandler.ACCESSIBLE_BUTTON2_KEYSTROKE|JABHandler.ACCESSIBLE_BUTTON3_KEYSTROKE): @@ -284,7 +284,7 @@ def _get_states(self): stateString=self.JABStates stateStrings=stateString.split(',') for state in stateStrings: - if JABStatesToNVDAStates.has_key(state): + if state in JABStatesToNVDAStates: stateSet.add(JABStatesToNVDAStates[state]) if "visible" not in stateStrings: stateSet.add(controlTypes.STATE_INVISIBLE) @@ -431,7 +431,7 @@ def _get_childCount(self): def _get_children(self): children=[] - for index in xrange(self._JABAccContextInfo.childrenCount): + for index in range(self._JABAccContextInfo.childrenCount): jabContext=self.jabContext.getAccessibleChildFromContext(index) if jabContext: obj=JAB(jabContext=jabContext) @@ -599,7 +599,7 @@ def _get_rowHeaderText(self): if headerTableInfo and headerTableInfo.jabTable: textList=[] row=self.rowNumber-1 - for col in xrange(headerTableInfo.columnCount): + for col in range(headerTableInfo.columnCount): cellInfo=headerTableInfo.jabTable.getAccessibleTableCellInfo(row,col) if cellInfo and cellInfo.jabContext: obj=JAB(jabContext=cellInfo.jabContext) @@ -617,7 +617,7 @@ def _get_columnHeaderText(self): if headerTableInfo and headerTableInfo.jabTable: textList=[] col=self.columnNumber-1 - for row in xrange(headerTableInfo.rowCount): + for row in range(headerTableInfo.rowCount): cellInfo=headerTableInfo.jabTable.getAccessibleTableCellInfo(row,col) if cellInfo and cellInfo.jabContext: obj=JAB(jabContext=cellInfo.jabContext) diff --git a/source/NVDAObjects/UIA/__init__.py b/source/NVDAObjects/UIA/__init__.py index f4479ca8429..695b1a811fe 100644 --- a/source/NVDAObjects/UIA/__init__.py +++ b/source/NVDAObjects/UIA/__init__.py @@ -30,7 +30,7 @@ from NVDAObjects import NVDAObjectTextInfo, InvalidNVDAObject from NVDAObjects.behaviors import ProgressBar, EditableTextWithoutAutoSelectDetection, Dialog, Notification, EditableTextWithSuggestions import braille -from locationHelper import RectLTWH +import locationHelper import ui class UIATextInfo(textInfos.TextInfo): @@ -235,8 +235,10 @@ def _getFormatFieldAtRange(self,textRange,formatConfig,ignoreMixedValues=False): formatField['link']=True if formatConfig["reportHeadings"]: styleIDValue=fetcher.getValue(UIAHandler.UIA_StyleIdAttributeId,ignoreMixedValues=ignoreMixedValues) - if UIAHandler.StyleId_Heading1<=styleIDValue<=UIAHandler.StyleId_Heading9: - formatField["heading-level"]=(styleIDValue-UIAHandler.StyleId_Heading1)+1 + # #9842: styleIDValue can sometimes be a pointer to IUnknown. + # In Python 3, comparing an int with a pointer raises a TypeError. + if isinstance(styleIDValue, int) and UIAHandler.StyleId_Heading1 <= styleIDValue <= UIAHandler.StyleId_Heading9: + formatField["heading-level"] = (styleIDValue - UIAHandler.StyleId_Heading1) + 1 if fetchAnnotationTypes: annotationTypes=fetcher.getValue(UIAHandler.UIA_AnnotationTypesAttributeId,ignoreMixedValues=ignoreMixedValues) # Some UIA implementations return a single value rather than a tuple. @@ -308,10 +310,9 @@ def __init__(self,obj,position,_rangeObj=None): raise LookupError # sometimes rangeFromChild can return a NULL range if not self._rangeObj: raise LookupError - elif isinstance(position,textInfos.Point): + elif isinstance(position,locationHelper.Point): #rangeFromPoint used to cause a freeze in UIA client library! - p=POINT(position.x,position.y) - self._rangeObj=self.obj.UIATextPattern.RangeFromPoint(p) + self._rangeObj=self.obj.UIATextPattern.RangeFromPoint(position.toPOINT()) elif isinstance(position,UIAHandler.IUIAutomationTextRange): self._rangeObj=position.clone() else: @@ -322,6 +323,11 @@ def __eq__(self,other): if self.__class__ is not other.__class__: return False return bool(self._rangeObj.compare(other._rangeObj)) + # As __eq__ was defined on this class, we must provide __hash__ to remain hashable. + # The default hash implementation is fine for our purposes. + def __hash__(self): + return super().__hash__() + def _get_NVDAObjectAtStart(self): e=self.UIAElementAtStart if e: @@ -417,12 +423,12 @@ def _getControlFieldForObject(self, obj,isEmbedded=False,startOfNode=False,endOf pass return field - def _getTextFromUIARange(self,range): + def _getTextFromUIARange(self, textRange): """ Fetches plain text from the given UI Automation text range. Just calls getText(-1). This only exists to be overridden for filtering. """ - return range.getText(-1) + return textRange.getText(-1) def _getTextWithFields_text(self,textRange,formatConfig,UIAFormatUnits=None): """ @@ -570,7 +576,7 @@ def _getTextWithFieldsForUIARange(self,rootElement,textRange,formatConfig,includ lastChildEndDelta=0 documentTextPattern=self.obj.UIATextPattern rootElementControlType=rootElement.cachedControlType - for index in xrange(childCount): + for index in range(childCount): childElement=childElements.getElement(index) if not childElement or UIAHandler.handler.clientObject.compareElements(childElement,enclosingElement): log.debug("NULL childElement. Skipping") @@ -646,7 +652,7 @@ def getTextWithFields(self,formatConfig=None): def _get_text(self): return self._getTextFromUIARange(self._rangeObj) - def _getBoundingRectsFromUIARange(self,range): + def _getBoundingRectsFromUIARange(self, textRange): """ Fetches per line bounding rectangles from the given UI Automation text range. Note that if the range object doesn't cover a whole line (e.g. a character), @@ -654,11 +660,11 @@ def _getBoundingRectsFromUIARange(self,range): @rtype: [locationHelper.RectLTWH] """ rects = [] - rectArray = range.GetBoundingRectangles() + rectArray = textRange.GetBoundingRectangles() if not rectArray: return rects - rectIndexes = xrange(0, len(rectArray), 4) - rectGen = (RectLTWH.fromFloatCollection(*rectArray[i:i+4]) for i in rectIndexes) + rectIndexes = range(0, len(rectArray), 4) + rectGen = (locationHelper.RectLTWH.fromFloatCollection(*rectArray[i:i+4]) for i in rectIndexes) rects.extend(rectGen) return rects @@ -1039,11 +1045,11 @@ def _get_devInfo(self): info.append("UIA className: %s"%ret) patternsAvailable = [] patternAvailableConsts = dict( - (const, name) for name, const in UIAHandler.__dict__.iteritems() + (const, name) for name, const in UIAHandler.__dict__.items() if name.startswith("UIA_Is") and name.endswith("PatternAvailablePropertyId") ) self._prefetchUIACacheForPropertyIDs(list(patternAvailableConsts)) - for const, name in patternAvailableConsts.iteritems(): + for const, name in patternAvailableConsts.items(): try: res = self._getUIACacheablePropertyValue(const) except COMError: @@ -1260,7 +1266,7 @@ def _get_children(self): if not cachedChildren: # GetCachedChildren returns null if there are no children. return children - for index in xrange(cachedChildren.length): + for index in range(cachedChildren.length): e=cachedChildren.getElement(index) windowHandle=self.windowHandle children.append(self.correctAPIForRelation(UIA(windowHandle=windowHandle,UIAElement=e))) @@ -1284,7 +1290,7 @@ def _get_rowHeaderText(self): raise NotImplementedError val=val.QueryInterface(UIAHandler.IUIAutomationElementArray) textList=[] - for i in xrange(val.length): + for i in range(val.length): e=val.getElement(i) if UIAHandler.handler.clientObject.compareElements(e,self.UIAElement): continue @@ -1312,7 +1318,7 @@ def _get_columnHeaderText(self): raise NotImplementedError val=val.QueryInterface(UIAHandler.IUIAutomationElementArray) textList=[] - for i in xrange(val.length): + for i in range(val.length): e=val.getElement(i) if UIAHandler.handler.clientObject.compareElements(e,self.UIAElement): continue @@ -1352,7 +1358,7 @@ def _get_location(self): if r is None: return # r is a tuple of floats representing left, top, width and height. - return RectLTWH.fromFloatCollection(*r) + return locationHelper.RectLTWH.fromFloatCollection(*r) def _get_value(self): val=self._getUIACacheablePropertyValue(UIAHandler.UIA_RangeValueValuePropertyId,True) @@ -1439,7 +1445,7 @@ def _get_controllerFor(self): return None a=e.QueryInterface(UIAHandler.IUIAutomationElementArray) objList=[] - for index in xrange(a.length): + for index in range(a.length): e=a.getElement(index) e=e.buildUpdatedCache(UIAHandler.handler.baseCacheRequest) obj=UIA(UIAElement=e) @@ -1698,7 +1704,7 @@ class PlaceholderNetUITWMenuItem(UIA): def _get_focusRedirect(self): # Locate the containing menu and focus that instead. parent=self.parent - for count in xrange(4): + for count in range(4): if not parent: return if parent.role==controlTypes.ROLE_POPUPMENU: diff --git a/source/NVDAObjects/UIA/edge.py b/source/NVDAObjects/UIA/edge.py index 84755b7b9d6..0f37ae3dddc 100644 --- a/source/NVDAObjects/UIA/edge.py +++ b/source/NVDAObjects/UIA/edge.py @@ -80,10 +80,10 @@ def _get_UIAElementAtStartWithReplacedContent(self): if ('label=' in ariaProperties) or ('labelledby=' in ariaProperties): return element try: - range=self.obj.UIATextPattern.rangeFromChild(element) + textRange=self.obj.UIATextPattern.rangeFromChild(element) except COMError: return - text=range.getText(-1) + text = textRange.getText(-1) if not text or text.isspace(): return element element=walker.getParentElementBuildCache(element,cacheRequest) @@ -94,15 +94,15 @@ def _moveToEdgeOfReplacedContent(self,back=False): if not element: return try: - range=self.obj.UIATextPattern.rangeFromChild(element) + textRange=self.obj.UIATextPattern.rangeFromChild(element) except COMError: return if not back: - range.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_Start,range,UIAHandler.TextPatternRangeEndpoint_End) - range.move(UIAHandler.TextUnit_Character,-1) + textRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_Start, textRange, UIAHandler.TextPatternRangeEndpoint_End) + textRange.move(UIAHandler.TextUnit_Character, -1) else: - range.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End,range,UIAHandler.TextPatternRangeEndpoint_Start) - self._rangeObj=range + textRange.MoveEndpointByRange(UIAHandler.TextPatternRangeEndpoint_End, textRange, UIAHandler.TextPatternRangeEndpoint_Start) + self._rangeObj=textRange def _collapsedMove(self,unit,direction,skipReplacedContent): """A simple collapsed move (i.e. both ends move together), but whether it classes replaced content as one character stop can be configured via the skipReplacedContent argument.""" @@ -202,7 +202,7 @@ def getTextWithFields(self,formatConfig=None): index=0 while index1 and isinstance(field,basestring) and field.isspace(): + if index>1 and isinstance(field,str) and field.isspace(): prevField=fields[index-2] if isinstance(prevField,textInfos.FieldCommand) and prevField.command=="controlEnd": del fields[index-1:index+1] @@ -212,9 +212,9 @@ def getTextWithFields(self,formatConfig=None): startCount=0 lastStartIndex=None numFields=len(fields) - for index in xrange(numFields-1,-1,-1): + for index in range(numFields-1,-1,-1): field=fields[index] - if isinstance(field,basestring): + if isinstance(field,str): break elif isinstance(field,textInfos.FieldCommand) and field.command=="controlStart" and not field.field.get('embedded'): startCount+=1 @@ -224,7 +224,7 @@ def getTextWithFields(self,formatConfig=None): # Remove any content from fields with a content attribute numFields=len(fields) curField=None - for index in xrange(numFields-1,-1,-1): + for index in range(numFields-1,-1,-1): field=fields[index] if not curField and isinstance(field,textInfos.FieldCommand) and field.command=="controlEnd" and field.field.get('content'): curField=field.field @@ -413,13 +413,13 @@ class EdgeNode(UIA): _TextInfo=EdgeTextInfo_preGapRemoval if _edgeIsPreGapRemoval else EdgeTextInfo def getNormalizedUIATextRangeFromElement(self,UIAElement): - range=super(EdgeNode,self).getNormalizedUIATextRangeFromElement(UIAElement) - if not range or not self._edgeIsPreGapRemoval: - return range + textRange = super().getNormalizedUIATextRangeFromElement(UIAElement) + if not textRange or not self._edgeIsPreGapRemoval: + return textRange #Move the start of a UIA text range past any element start character stops - lastCharInfo=EdgeTextInfo_preGapRemoval(self,None,_rangeObj=range) - lastCharInfo._rangeObj=range - charInfo=lastCharInfo.copy() + lastCharInfo = EdgeTextInfo_preGapRemoval(self,None, _rangeObj=textRange) + lastCharInfo._rangeObj = textRange + charInfo = lastCharInfo.copy() charInfo.collapse() while super(EdgeTextInfo,charInfo).move(textInfos.UNIT_CHARACTER,1)!=0: charInfo.setEndPoint(lastCharInfo,"startToStart") @@ -427,7 +427,7 @@ def getNormalizedUIATextRangeFromElement(self,UIAElement): break lastCharInfo.setEndPoint(charInfo,"startToEnd") charInfo.collapse(True) - return range + return textRange def _get_role(self): role=super(EdgeNode,self).role @@ -555,7 +555,8 @@ def EdgeHeadingQuicknavIterator(itemType,document,position,direction="next"): # However, sometimes when ARIA is used, the level on the element may not match the level in the text attributes. # Therefore we need to search for all levels 1 through 6, even if a specific level is specified. # Though this is still much faster than searching text attributes alone - levels=range(1,7) + # #9078: this must be wrapped inside a list, as Python 3 will treat this as iteration. + levels=list(range(1,7)) condition=createUIAMultiPropertyCondition({UIAHandler.UIA_ControlTypePropertyId:UIAHandler.UIA_TextControlTypeId,UIAHandler.UIA_LevelPropertyId:levels}) levelString=itemType[7:] for item in UIAControlQuicknavIterator(itemType,document,position,condition,direction=direction,itemClass=EdgeHeadingQuickNavItem): diff --git a/source/NVDAObjects/UIA/winConsoleUIA.py b/source/NVDAObjects/UIA/winConsoleUIA.py index cae4c4f8417..41b379717bd 100644 --- a/source/NVDAObjects/UIA/winConsoleUIA.py +++ b/source/NVDAObjects/UIA/winConsoleUIA.py @@ -244,17 +244,14 @@ def _reportNewText(self, line): if len(line.strip()) < max(len(speech.curWordChars) + 1, 3): return if self._hasNewLines: - # Clear the typed word buffer for new text lines. - # This will need to be changed once #8110 is merged. - speech.curWordChars = [] + # Clear the queued characters buffer for new text lines. self._queuedChars = [] super(WinConsoleUIA, self)._reportNewText(line) def event_typedCharacter(self, ch): if ch == '\t': # Clear the typed word buffer for tab completion. - # This will need to be changed once #8110 is merged. - speech.curWordChars = [] + speech.clearTypedWordBuffer() if ( ( config.conf['keyboard']['speakTypedCharacters'] @@ -288,7 +285,7 @@ def script_flush_queuedChars(self, gesture): """ gesture.send() self._queuedChars = [] - speech.curWordChars = [] + speech.clearTypedWordBuffer() def _getTextLines(self): # Filter out extraneous empty lines from UIA diff --git a/source/NVDAObjects/UIA/wordDocument.py b/source/NVDAObjects/UIA/wordDocument.py index 1f9424fc9e1..3347a62f5a2 100644 --- a/source/NVDAObjects/UIA/wordDocument.py +++ b/source/NVDAObjects/UIA/wordDocument.py @@ -64,7 +64,7 @@ def getCommentInfoFromPosition(position): UIAElementArray=val.QueryInterface(UIAHandler.IUIAutomationElementArray) except COMError: return - for index in xrange(UIAElementArray.length): + for index in range(UIAElementArray.length): UIAElement=UIAElementArray.getElement(index) UIAElement=UIAElement.buildUpdatedCache(UIAHandler.handler.baseCacheRequest) obj=UIA(UIAElement=UIAElement) @@ -140,8 +140,8 @@ def _getControlFieldForObject(self,obj,isEmbedded=False,startOfNode=False,endOfN field['value']=field.pop('description',None) or obj.description or field.pop('name',None) or obj.name return field - def _getTextFromUIARange(self,range): - t=super(WordDocumentTextInfo,self)._getTextFromUIARange(range) + def _getTextFromUIARange(self, textRange): + t=super(WordDocumentTextInfo,self)._getTextFromUIARange(textRange) if t: # HTML emails expose a lot of vertical tab chars in their text # Really better as carage returns @@ -189,7 +189,7 @@ def getTextWithFields(self,formatConfig=None): # Sometimes embedded objects and graphics In MS Word can cause a controlStart then a controlEnd with no actual formatChange / text in the middle. # SpeakTextInfo always expects that the first lot of controlStarts will always contain some text. # Therefore ensure that the first lot of controlStarts does contain some text by inserting a blank formatChange and empty string in this case. - for index in xrange(len(fields)): + for index in range(len(fields)): field=fields[index] if isinstance(field,textInfos.FieldCommand) and field.command=="controlStart": continue @@ -203,7 +203,7 @@ def getTextWithFields(self,formatConfig=None): # Therefore, detect when at the start of a list, and strip the bullet from the text string, placing it in the text's formatField as line-prefix. listItemStarted=False lastFormatField=None - for index in xrange(len(fields)): + for index in range(len(fields)): field=fields[index] if isinstance(field,textInfos.FieldCommand) and field.command=="controlStart": if field.field.get('role')==controlTypes.ROLE_LISTITEM and field.field.get('_startOfNode'): @@ -212,7 +212,7 @@ def getTextWithFields(self,formatConfig=None): elif isinstance(field,textInfos.FieldCommand) and field.command=="formatChange": # This is the most recent formatField we have seen. lastFormatField=field.field - elif listItemStarted and isinstance(field,basestring): + elif listItemStarted and isinstance(field,str): # This is the first text string within the list. # Remove the text up to the first space, and store it as line-prefix which NVDA will appropriately speak/braille as a bullet. try: @@ -330,7 +330,7 @@ def script_reportCurrentComment(self,gesture): UIAElementArray=val.QueryInterface(UIAHandler.IUIAutomationElementArray) except COMError: return - for index in xrange(UIAElementArray.length): + for index in range(UIAElementArray.length): UIAElement=UIAElementArray.getElement(index) UIAElement=UIAElement.buildUpdatedCache(UIAHandler.handler.baseCacheRequest) obj=UIA(UIAElement=UIAElement) diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index cb823d790dd..5df553520ca 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -7,7 +7,6 @@ """Module that contains the base NVDA object type""" -from six import with_metaclass import time import re import weakref @@ -28,6 +27,7 @@ import braille import globalPluginHandler import brailleInput +import locationHelper class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo): """A default TextInfo which is used to enable text review of information about widgets that don't support text content. @@ -35,6 +35,8 @@ class NVDAObjectTextInfo(textInfos.offsets.OffsetsTextInfo): """ locationText=None + # Do not use encoded text. + encoding = None def _get_unit_mouseChunk(self): return textInfos.UNIT_STORY @@ -45,10 +47,6 @@ def _getStoryText(self): def _getStoryLength(self): return len(self._getStoryText()) - def _getTextRange(self,start,end): - text=self._getStoryText() - return text[start:end] - def _get_boundingRects(self): if self.obj.hasIrrelevantLocation: raise LookupError("Object is off screen, invisible or has no location") @@ -98,7 +96,7 @@ def __call__(self,chooseBestAPI=True,**kwargs): # Determine the bases for the new class. bases=[] - for index in xrange(len(clsList)): + for index in range(len(clsList)): # A class doesn't need to be a base if it is already implicitly included by being a superclass of a previous base. if index==0 or not issubclass(clsList[index-1],clsList[index]): bases.append(clsList[index]) @@ -146,7 +144,7 @@ def clearDynamicClassCache(cls): """ cls._dynamicClassCache.clear() -class NVDAObject(with_metaclass(DynamicNVDAObjectType, documentBase.TextContainerObject,baseObject.ScriptableObject)): +class NVDAObject(documentBase.TextContainerObject, baseObject.ScriptableObject, metaclass=DynamicNVDAObjectType): """NVDA's representation of a single control/widget. Every widget, regardless of how it is exposed by an application or the operating system, is represented by a single NVDAObject instance. This allows NVDA to work with all widgets in a uniform way. @@ -319,6 +317,11 @@ def __eq__(self,other): return False return self._isEqual(other) + # As __eq__ was defined on this class, we must provide __hash__ to remain hashable. + # The default hash implementation is fine for our purposes. + def __hash__(self): + return super().__hash__() + def __ne__(self,other): """The opposite to L{NVDAObject.__eq__} """ @@ -386,7 +389,7 @@ def _get_appModule(self): def _get_name(self): """The name or label of this object (example: the text of a button). - @rtype: basestring + @rtype: str """ return "" @@ -407,13 +410,13 @@ def _get_roleText(self): def _get_value(self): """The value of this object (example: the current percentage of a scrollbar, the selected option in a combo box). - @rtype: basestring + @rtype: str """ return "" def _get_description(self): """The description or help text of this object. - @rtype: basestring + @rtype: str """ return "" @@ -431,7 +434,7 @@ def getActionName(self,index=None): @param index: the optional 0-based index of the wanted action. @type index: int @return: the action's name - @rtype: basestring + @rtype: str """ raise NotImplementedError @@ -447,7 +450,7 @@ def _get_defaultActionIndex(self): def _get_keyboardShortcut(self): """The shortcut key that activates this object(example: alt+t). - @rtype: basestring + @rtype: str """ return "" @@ -989,7 +992,7 @@ def event_mouseMove(self,x,y): speechWasCanceled=False self._mouseEntered=True try: - info=self.makeTextInfo(textInfos.Point(x,y)) + info=self.makeTextInfo(locationHelper.Point(x,y)) except NotImplementedError: info=NVDAObjectTextInfo(self,textInfos.POSITION_FIRST) except LookupError: @@ -1031,6 +1034,10 @@ def event_gainFocus(self): braille.handler.handleGainFocus(self) brailleInput.handler.handleGainFocus(self) + def event_loseFocus(self): + # Forget the word currently being typed as focus is moving to a new control. + speech.clearTypedWordBuffer() + def event_foreground(self): """Called when the foreground window changes. This method should only perform tasks specific to the foreground window changing. @@ -1093,7 +1100,7 @@ def _get_basicText(self): newTime=time.time() oldTime=getattr(self,'_basicTextTime',0) if newTime-oldTime>0.5: - self._basicText=u" ".join([x for x in self.name, self.value, self.description if isinstance(x, basestring) and len(x) > 0 and not x.isspace()]) + self._basicText=u" ".join(x for x in (self.name, self.value, self.description) if isinstance(x, str) and len(x) > 0 and not x.isspace()) if len(self._basicText)==0: self._basicText=u"" else: @@ -1115,13 +1122,13 @@ def _formatLongDevInfoString(string, truncateLen=250): If the string is too long to be useful, it will be truncated. This string should be included as returned. There is no need to call repr. @param string: The string to format. - @type string: nbasestring + @type string: str @param truncateLen: The length at which to truncate the string. @type truncateLen: int @return: The formatted string. - @rtype: basestring + @rtype: str """ - if isinstance(string, basestring) and len(string) > truncateLen: + if isinstance(string, str) and len(string) > truncateLen: return "%r (truncated)" % string[:truncateLen] return repr(string) @@ -1139,7 +1146,7 @@ def _get_devInfo(self): info.append("name: %s" % ret) try: ret = self.role - for name, const in controlTypes.__dict__.iteritems(): + for name, const in controlTypes.__dict__.items(): if name.startswith("ROLE_") and ret == const: ret = name break @@ -1147,7 +1154,7 @@ def _get_devInfo(self): ret = "exception: %s" % e info.append("role: %s" % ret) try: - stateConsts = dict((const, name) for name, const in controlTypes.__dict__.iteritems() if name.startswith("STATE_")) + stateConsts = dict((const, name) for name, const in controlTypes.__dict__.items() if name.startswith("STATE_")) ret = ", ".join( stateConsts.get(state) or str(state) for state in self.states) @@ -1233,7 +1240,7 @@ def _get_mathMl(self): raise NotImplementedError #: The language/locale of this object. - #: @type: basestring + #: @type: str language = None def _get__hasNavigableText(self): diff --git a/source/NVDAObjects/behaviors.py b/source/NVDAObjects/behaviors.py index ddfc57eab26..312ef21b289 100755 --- a/source/NVDAObjects/behaviors.py +++ b/source/NVDAObjects/behaviors.py @@ -51,8 +51,8 @@ def event_valueChange(self): left,top,width,height=self.location except: left=top=width=height=0 - x=left+(width/2) - y=top+(height/2) + x = left + (width // 2) + y = top+ (height // 2) lastBeepProgressValue=self.progressValueCache.get("beep,%d,%d"%(x,y),None) if pbConf["progressBarOutputMode"] in ("beep","both") and (lastBeepProgressValue is None or abs(percentage-lastBeepProgressValue)>=pbConf["beepPercentageInterval"]): tones.beep(pbConf["beepMinHZ"]*2**(percentage/25.0),40) @@ -77,7 +77,7 @@ def getDialogText(cls,obj,allowFocusedDescendants=True): children=obj.children textList=[] childCount=len(children) - for index in xrange(childCount): + for index in range(childCount): child=children[index] childStates=child.states childRole=child.role @@ -323,7 +323,7 @@ def _calculateNewText(self, newLines, oldLines): textLen = len(text) prevTextLen = len(prevText) # Find the first character that differs between the two lines. - for pos in xrange(min(textLen, prevTextLen)): + for pos in range(min(textLen, prevTextLen)): if text[pos] != prevText[pos]: start = pos break @@ -336,7 +336,7 @@ def _calculateNewText(self, newLines, oldLines): # The lines are different lengths, so assume the rest of the line changed. end = textLen else: - for pos in xrange(textLen - 1, start - 1, -1): + for pos in range(textLen - 1, start - 1, -1): if text[pos] != prevText[pos]: end = pos + 1 break @@ -363,6 +363,7 @@ def event_gainFocus(self): self.startMonitoring() def event_loseFocus(self): + super(Terminal, self).event_loseFocus() self.stopMonitoring() class CandidateItem(NVDAObject): @@ -554,7 +555,7 @@ def _get_firstChild(self): return self._makeCell(1) def _get_children(self): - return [self._makeCell(column) for column in xrange(1, self.childCount + 1)] + return [self._makeCell(column) for column in range(1, self.childCount + 1)] def getChild(self, index): return self._makeCell(index + 1) diff --git a/source/NVDAObjects/inputComposition.py b/source/NVDAObjects/inputComposition.py index 769a2107eaa..03474ecbdd6 100644 --- a/source/NVDAObjects/inputComposition.py +++ b/source/NVDAObjects/inputComposition.py @@ -14,11 +14,11 @@ def calculateInsertedChars(oldComp,newComp): minLen=min(oldLen,newLen) diffStart=0 diffEnd=newLen - for index in xrange(minLen): + for index in range(minLen): if newComp[index]!=oldComp[index]: break diffStart=index+1 - for index in xrange(minLen,0,-1): + for index in range(minLen,0,-1): backIndex=index-minLen-1 if newComp[backIndex]!=oldComp[backIndex]: break @@ -27,6 +27,7 @@ def calculateInsertedChars(oldComp,newComp): return newComp[diffStart:diffEnd] class InputCompositionTextInfo(OffsetsTextInfo): + encoding = None def _getSelectionOffsets(self): return self.obj.readingSelectionOffsets if self.obj.isReading else self.obj.compositionSelectionOffsets diff --git a/source/NVDAObjects/window/_msOfficeChart.py b/source/NVDAObjects/window/_msOfficeChart.py index 0583f50d4ce..f783ded232e 100644 --- a/source/NVDAObjects/window/_msOfficeChart.py +++ b/source/NVDAObjects/window/_msOfficeChart.py @@ -530,7 +530,7 @@ def __init__(self,windowHandle, officeApplicationObject, officeChartObject, init except: seriesCount=None if seriesCount: - for i in xrange(seriesCount): + for i in range(seriesCount): self.addElement( OfficeChartElementSeries(windowHandle=self.windowHandle, officeChartObject = self.officeChartObject , elementID = xlSeries , arg1 = i +1 ) , self) self.addElement( OfficeChartElementCollection(windowHandle=self.windowHandle, officeChartObject = self.officeChartObject ) , self ) @@ -563,7 +563,7 @@ def _get_description(self): else: # Translators: Indicates the number of series in a chart where there are multiple series. seriesValueString = _( "There are total %d series in this chart" ) %(count) - for i in xrange(1, count+1): + for i in range(1, count+1): # Translators: Specifies the number and name of a series when listing series in a chart. seriesValueString += ", " + _("series {number} {name}").format(number=i, name=self.officeChartObject.SeriesCollection(i).Name) text += seriesValueString @@ -620,7 +620,7 @@ def __init__(self, windowHandle=None , officeChartObject=None , elementID=None self.addElement(OfficeChartElementLegend(windowHandle=self.windowHandle, officeChartObject = officeChartObject ) , self ) self.legendEntryCount = self.officeChartObject.Legend.LegendEntries().Count - for legendEntryIndex in xrange( 1 , self.legendEntryCount + 1 ) : + for legendEntryIndex in range( 1 , self.legendEntryCount + 1 ) : legendEntry = OfficeChartElementLegendEntry(windowHandle=self.windowHandle, officeChartObject = self.officeChartObject , elementID= xlLegendEntry , arg1 = legendEntryIndex , arg2 = self.legendEntryCount ) legendEntry.eventDriven = False self.addElement ( legendEntry , self ) @@ -649,11 +649,11 @@ def __init__(self,windowHandle, officeChartObject , elementID , arg1 = None , ar self.pointsCollection=self.officeChartObject.SeriesCollection(self.seriesIndex).Points() self.pointsCount=self.pointsCollection.Count - for pointIndex in xrange(1,self.pointsCount +1) : + for pointIndex in range(1,self.pointsCount +1) : self.addElement ( OfficeChartElementPoint(windowHandle=self.windowHandle, officeChartObject = self.officeChartObject , elementID= xlSeries, arg1 =self.seriesIndex, arg2 =pointIndex) , self ) self.trendlinesCount = self.officeChartObject.SeriesCollection(self.seriesIndex).Trendlines().Count - for trendlineIndex in xrange( 1 , self.trendlinesCount + 1 ) : + for trendlineIndex in range( 1 , self.trendlinesCount + 1 ) : self.addElement ( OfficeChartElementTrendline(windowHandle=self.windowHandle, officeChartObject = self.officeChartObject , elementID= xlTrendline , arg1 = self.seriesIndex , arg2 = trendlineIndex ) , self ) def _get_name(self): diff --git a/source/NVDAObjects/window/edit.py b/source/NVDAObjects/window/edit.py index ce623664917..f59b99b6e7f 100644 --- a/source/NVDAObjects/window/edit.py +++ b/source/NVDAObjects/window/edit.py @@ -8,11 +8,10 @@ import struct import ctypes from comtypes import COMError -import pythoncom -import win32clipboard import oleTypes import colors import globalVars +import NVDAHelper import eventHandler import comInterfaces.tom from logHandler import log @@ -32,6 +31,8 @@ from ..behaviors import EditableTextWithAutoSelectDetection import braille import watchdog +import locationHelper +import textUtils selOffsetsAtLastCaretEvent=None @@ -67,13 +68,7 @@ class CharRangeStruct(ctypes.Structure): ('cpMax',ctypes.c_long), ] -class TextRangeUStruct(ctypes.Structure): - _fields_=[ - ('chrg',CharRangeStruct), - ('lpstrText',ctypes.c_wchar_p), - ] - -class TextRangeAStruct(ctypes.Structure): +class TextRangeStruct(ctypes.Structure): _fields_=[ ('chrg',CharRangeStruct), ('lpstrText',ctypes.c_char_p), @@ -130,6 +125,10 @@ class getTextExStruct(ctypes.Structure): ] class getTextLengthExStruct(ctypes.Structure): + """ + For documentation, see: + https://docs.microsoft.com/en-us/windows/desktop/api/richedit/ns-richedit-_gettextlengthex + """ _fields_=[ ('flags',ctypes.wintypes.DWORD), ('codepage',ctypes.c_uint), @@ -170,10 +169,10 @@ def _getPointFromOffset(self,offset): winKernel.readProcessMemory(processHandle,internalP,ctypes.byref(p),ctypes.sizeof(p),None) finally: winKernel.virtualFreeEx(processHandle,internalP,0,winKernel.MEM_RELEASE) - point=textInfos.Point(p.x,p.y) + point=locationHelper.Point(p.x,p.y) else: res=watchdog.cancellableSendMessage(self.obj.windowHandle,winUser.EM_POSFROMCHAR,offset,None) - point=textInfos.Point(winUser.GET_X_LPARAM(res),winUser.GET_Y_LPARAM(res)) + point=locationHelper.Point(winUser.GET_X_LPARAM(res),winUser.GET_Y_LPARAM(res)) # A returned coordinate can be a negative value if # the specified character is not displayed in the edit control's client area. # If the specified index is greater than the index of the last character in the control, @@ -181,8 +180,7 @@ def _getPointFromOffset(self,offset): if point.x <0 or point.y <0: raise LookupError("Point with client coordinates x=%d, y=%d not within client area of object" % (point.x, point.y)) - point.x, point.y = winUser.ClientToScreen(self.obj.windowHandle, point.x, point.y) - return point + return point.toScreen(self.obj.windowHandle) def _getOffsetFromPoint(self,x,y): @@ -258,7 +256,8 @@ def _getFormatFieldAndOffsets(self,offset,formatConfig,calculateOffsets=True): formatField["font-name"]=charFormat.szFaceName if formatConfig["reportFontSize"]: if charFormat is None: charFormat=self._getCharFormat(offset) - formatField["font-size"]="%spt"%(charFormat.yHeight/20) + # Font size is supposed to be an integral value + formatField["font-size"]="%spt"%(charFormat.yHeight//20) if formatConfig["reportFontAttributes"]: if charFormat is None: charFormat=self._getCharFormat(offset) formatField["bold"]=bool(charFormat.dwEffects&CFE_BOLD) @@ -322,7 +321,7 @@ def _setCaretOffset(self,offset): def _getStoryText(self): if controlTypes.STATE_PROTECTED in self.obj.states: - return u'*'*(self._getStoryLength()-1) + return u'*' * (self._getStoryLength() - 1) return self.obj.windowText def _getStoryLength(self): @@ -340,47 +339,68 @@ def _getStoryLength(self): textLen=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETTEXTLENGTHEX,internalInfo,0) finally: winKernel.virtualFreeEx(processHandle,internalInfo,0,winKernel.MEM_RELEASE) + # Py3 review: investigation with Python 2 NVDA revealed that + # adding 1 to this creates an off by one error. + # Tested using Wordpad, enforcing EditTextInfo as the textInfo implementation. return textLen+1 else: + # ForWM_GETTEXTLENGTH documentation, see + # https://docs.microsoft.com/en-us/windows/desktop/winmsg/wm-gettextlength + # It determines the length, in characters, of the text associated with a window. + # Py3 review: investigation with Python 2 NVDA revealed that + # adding 1 to this created an off by one error. + # Tested using Notepad return watchdog.cancellableSendMessage(self.obj.windowHandle,winUser.WM_GETTEXTLENGTH,0,0)+1 def _getLineCount(self): return self.obj.windowTextLineCount def _getTextRange(self,start,end): - if self.obj.editAPIVersion>=2: - bufLen=((end-start)+1)*2 - if self.obj.isWindowUnicode: - textRange=TextRangeUStruct() - else: - textRange=TextRangeAStruct() - textRange.chrg.cpMin=start - textRange.chrg.cpMax=end - processHandle=self.obj.processHandle - internalBuf=winKernel.virtualAllocEx(processHandle,None,bufLen,winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) + if self.obj.editAPIVersion >= 2: + # Calculate a buffer size that is twice the size of the text range and a NULL terminating character. + # As unicode characters are two bytes in size, + # this ensures that our buffer can hold both ANSI and unicode character strings. + bufLen = ((end - start) + 1) * 2 + # Even though this can return unicode text, we use the ANSI version of the structure. + # Using the unicode structure isn't strictly necessary and saves us some confusion + textRange = TextRangeStruct() + textRange.chrg.cpMin = start + textRange.chrg.cpMax = end + processHandle = self.obj.processHandle + internalBuf = winKernel.virtualAllocEx(processHandle, None, bufLen, winKernel.MEM_COMMIT, winKernel.PAGE_READWRITE) try: - textRange.lpstrText=internalBuf - internalTextRange=winKernel.virtualAllocEx(processHandle,None,ctypes.sizeof(textRange),winKernel.MEM_COMMIT,winKernel.PAGE_READWRITE) + textRange.lpstrText = internalBuf + internalTextRange = winKernel.virtualAllocEx(processHandle, None, ctypes.sizeof(textRange), winKernel.MEM_COMMIT, winKernel.PAGE_READWRITE) try: - winKernel.writeProcessMemory(processHandle,internalTextRange,ctypes.byref(textRange),ctypes.sizeof(textRange),None) - res=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_GETTEXTRANGE,0,internalTextRange) + winKernel.writeProcessMemory(processHandle, internalTextRange, ctypes.byref(textRange), ctypes.sizeof(textRange), None) + # EM_GETTEXTRANGE returns the number of characters copied, + # not including the terminating null character. + # See https://docs.microsoft.com/en-us/windows/desktop/controls/em-gettextrange + numChars = watchdog.cancellableSendMessage(self.obj.windowHandle, EM_GETTEXTRANGE, 0, internalTextRange) finally: - winKernel.virtualFreeEx(processHandle,internalTextRange,0,winKernel.MEM_RELEASE) - buf=(ctypes.c_byte*bufLen)() + winKernel.virtualFreeEx(processHandle, internalTextRange, 0, winKernel.MEM_RELEASE) + buf = ctypes.create_string_buffer(bufLen) + # Copy the text in the text range to our own buffer. winKernel.readProcessMemory(processHandle,internalBuf,buf,bufLen,None) finally: - winKernel.virtualFreeEx(processHandle,internalBuf,0,winKernel.MEM_RELEASE) - if self.obj.isWindowUnicode or (res>1 and (buf[res]!=0 or buf[res+1]!=0)): - text=ctypes.cast(buf,ctypes.c_wchar_p).value + winKernel.virtualFreeEx(processHandle, internalBuf, 0, winKernel.MEM_RELEASE) + # Find out which encoding to use to decode the bytes in the buffer. + if ( + # The window is unicode, the text range contains multi byte characters. + self.obj.isWindowUnicode + ): + encoding = textUtils.WCHAR_ENCODING else: - text=unicode(ctypes.cast(buf,ctypes.c_char_p).value, errors="replace", encoding=locale.getlocale()[1]) + # De encoding will be determined by L{textUtils.getTextFromRawBytes} + encoding = None + text = textUtils.getTextFromRawBytes(buf.raw, numChars, encoding) # #4095: Some protected richEdit controls do not hide their password characters. # We do this specifically. # Note that protected standard edit controls get characters hidden in _getStoryText. if text and controlTypes.STATE_PROTECTED in self.obj.states: text=u'*'*len(text) else: - text=self._getStoryText()[start:end] + text = super(EditTextInfo, self)._getTextRange(start, end) return text def _getWordOffsets(self,offset): @@ -397,7 +417,6 @@ def _getWordOffsets(self,offset): else: return super(EditTextInfo,self)._getWordOffsets(offset) - def _getLineNumFromOffset(self,offset): if self.obj.editAPIVersion>=1: res=watchdog.cancellableSendMessage(self.obj.windowHandle,EM_EXLINEFROMCHAR,0,offset) @@ -447,19 +466,19 @@ def _getParagraphOffsets(self,offset): class ITextDocumentTextInfo(textInfos.TextInfo): def _get_pointAtStart(self): - p=textInfos.Point(0,0) - (p.x,p.y)=self._rangeObj.GetPoint(comInterfaces.tom.tomStart) + p=locationHelper.Point(*self._rangeObj.GetPoint(comInterfaces.tom.tomStart)) if p.x and p.y: return p else: raise NotImplementedError - def _getFormatFieldAtRange(self,range,formatConfig): + def _getFormatFieldAtRange(self, textRange, formatConfig): formatField=textInfos.FormatField() fontObj=None paraFormatObj=None if formatConfig["reportAlignment"]: - if not paraFormatObj: paraFormatObj=range.para + if not paraFormatObj: + paraFormatObj = textRange.para alignment=paraFormatObj.alignment if alignment==comInterfaces.tom.tomAlignLeft: formatField["text-align"]="left" @@ -470,15 +489,18 @@ def _getFormatFieldAtRange(self,range,formatConfig): elif alignment==comInterfaces.tom.tomAlignJustify: formatField["text-align"]="justify" if formatConfig["reportLineNumber"]: - formatField["line-number"]=range.getIndex(comInterfaces.tom.tomLine) + formatField["line-number"] = textRange.getIndex(comInterfaces.tom.tomLine) if formatConfig["reportFontName"]: - if not fontObj: fontObj=range.font + if not fontObj: + fontObj = textRange.font formatField["font-name"]=fontObj.name if formatConfig["reportFontSize"]: - if not fontObj: fontObj=range.font + if not fontObj: + fontObj = textRange.font formatField["font-size"]="%spt"%fontObj.size if formatConfig["reportFontAttributes"]: - if not fontObj: fontObj=range.font + if not fontObj: + fontObj = textRange.font formatField["bold"]=bool(fontObj.bold) formatField["italic"]=bool(fontObj.italic) formatField["underline"]=bool(fontObj.underline) @@ -488,11 +510,12 @@ def _getFormatFieldAtRange(self,range,formatConfig): elif fontObj.subscript: formatField["text-position"]="sub" if formatConfig["reportLinks"]: - linkRange=range.Duplicate + linkRange = textRange.Duplicate linkRange.Collapse(comInterfaces.tom.tomStart) formatField["link"]=linkRange.Expand(comInterfaces.tom.tomLink)>0 if formatConfig["reportColor"]: - if not fontObj: fontObj=range.font + if not fontObj: + fontObj = textRange.font fgColor=fontObj.foreColor if fgColor==comInterfaces.tom.tomAutoColor: # Translators: The default color of text when a color has not been set by the author. @@ -513,7 +536,8 @@ def _getFormatFieldAtRange(self,range,formatConfig): formatField['background-color']=_("Unknown color") else: formatField["background-color"]=colors.RGB.fromCOLORREF(bkColor) - if not fontObj: fontObj=range.font + if not fontObj: + fontObj = textRange.font try: langId = fontObj.languageID if langId: @@ -523,10 +547,10 @@ def _getFormatFieldAtRange(self,range,formatConfig): pass return formatField - def _expandFormatRange(self,range,formatConfig): + def _expandFormatRange(self, textRange, formatConfig): startLimit=self._rangeObj.start endLimit=self._rangeObj.end - chunkRange=range.duplicate + chunkRange = textRange.duplicate if formatConfig["reportLineNumber"]: chunkRange.expand(comInterfaces.tom.tomLine) else: @@ -537,12 +561,12 @@ def _expandFormatRange(self,range,formatConfig): startLimit=chunkStart if endLimit>chunkEnd: endLimit=chunkEnd - #range.moveEnd(comInterfaces.tom.tomCharFormat,1) - range.expand(comInterfaces.tom.tomCharFormat) - if range.end>endLimit: - range.end=endLimit - if range.start endLimit: + textRange.end = endLimit + if textRange.start < startLimit: + textRange.start = startLimit def _getEmbeddedObjectLabel(self,embedRangeObj): label=None @@ -570,7 +594,7 @@ def _getEmbeddedObjectLabel(self,embedRangeObj): r.collapse(1) left,top=r.GetPoint(comInterfaces.tom.tomStart) import displayModel - label=displayModel.DisplayModelTextInfo(self.obj, textInfos.Rect(left, top, right, bottom)).text + label=displayModel.DisplayModelTextInfo(self.obj, locationHelper.RectLTRB(left, top, right, bottom)).text if label and not label.isspace(): return label # Windows Live Mail exposes the label via the embedded object's data (IDataObject) @@ -579,15 +603,9 @@ def _getEmbeddedObjectLabel(self,embedRangeObj): except comtypes.COMError: dataObj=None if dataObj: - try: - dataObj=pythoncom._univgw.interface(hash(dataObj),pythoncom.IID_IDataObject) - format=(win32clipboard.CF_UNICODETEXT, None, pythoncom.DVASPECT_CONTENT, -1, pythoncom.TYMED_HGLOBAL) - medium=dataObj.GetData(format) - buf=ctypes.create_string_buffer(medium.data) - buf=ctypes.cast(buf,ctypes.c_wchar_p) - label=buf.value - except: - pass + text=comtypes.BSTR() + res=NVDAHelper.localLib.getOleClipboardText(dataObj,ctypes.byref(text)); + label=text.value if label: return label # As a final fallback (e.g. could not get display model text for Outlook Express), use the embedded object's user type (e.g. "recipient"). @@ -607,7 +625,7 @@ def _getTextAtRange(self,rangeObj): return u'*'*len(bufText) newTextList=[] start=rangeObj.start - for offset in xrange(len(bufText)): + for offset in range(len(bufText)): if ord(bufText[offset])==0xfffc: if embedRangeObj is None: embedRangeObj=rangeObj.duplicate embedRangeObj.setRange(start+offset,start+offset+1) @@ -625,8 +643,8 @@ def __init__(self,obj,position,_rangeObj=None): if _rangeObj: self._rangeObj=_rangeObj.Duplicate return - if isinstance(position,textInfos.Point): - self._rangeObj=self.obj.ITextDocumentObject.rangeFromPoint(position.x,position.y) + if isinstance(position,locationHelper.Point): + self._rangeObj=self.obj.ITextDocumentObject.rangeFromPoint(*position) elif position==textInfos.POSITION_ALL: self._rangeObj=self.obj.ITextDocumentObject.range(0,0) self._rangeObj.expand(comInterfaces.tom.tomStory) @@ -649,22 +667,22 @@ def __init__(self,obj,position,_rangeObj=None): def getTextWithFields(self,formatConfig=None): if not formatConfig: formatConfig=config.conf["documentFormatting"] - range=self._rangeObj.duplicate - range.collapse(True) + textRange=self._rangeObj.duplicate + textRange.collapse(True) if not formatConfig["detectFormatAfterCursor"]: - range.expand(comInterfaces.tom.tomCharacter) - return [textInfos.FieldCommand("formatChange",self._getFormatFieldAtRange(range,formatConfig)), + textRange.expand(comInterfaces.tom.tomCharacter) + return [textInfos.FieldCommand("formatChange",self._getFormatFieldAtRange(textRange, formatConfig)), self._getTextAtRange(self._rangeObj)] commandList=[] endLimit=self._rangeObj.end - while range.end0 and isinstance(item,basestring) and item.isspace(): + elif index>0 and isinstance(item,str) and item.isspace(): #2047: don't expose language for whitespace as its incorrect for east-asian languages lastItem=commandList[index-1] if isinstance(lastItem,textInfos.FieldCommand) and isinstance(lastItem.field,textInfos.FormatField): @@ -1018,7 +1019,7 @@ def _get_pointAtStart(self): raise LookupError if not any((left.value, top.value, width.value, height.value)): raise LookupError - return Point(left.value, top.value) + return locationHelper.Point(left.value, top.value) def updateCaret(self): self.obj.WinwordWindowObject.ScrollIntoView(self._rangeObj) @@ -1033,9 +1034,9 @@ def getMathMl(self, field): import mathType except: raise LookupError("MathType not installed") - range = self._rangeObj.Duplicate - range.Start = int(field["shapeoffset"]) - obj = range.InlineShapes[0].OLEFormat + rangeObj = self._rangeObj.Duplicate + rangeObj.Start = int(field["shapeoffset"]) + obj = rangeObj.InlineShapes[0].OLEFormat try: return mathType.getMathMl(obj) except: diff --git a/source/UIABrowseMode.py b/source/UIABrowseMode.py index 86c285ab194..d7e305cabdc 100644 --- a/source/UIABrowseMode.py +++ b/source/UIABrowseMode.py @@ -156,9 +156,11 @@ def UIAHeadingQuicknavIterator(itemType,document,position,direction="next"): tempInfo=curPosition.copy() tempInfo.expand(textInfos.UNIT_CHARACTER) styleIDValue=getUIATextAttributeValueFromRange(tempInfo._rangeObj,UIAHandler.UIA_StyleIdAttributeId,ignoreMixedValues=True) - if (UIAHandler.StyleId_Heading1<=styleIDValue<=UIAHandler.StyleId_Heading9): - foundLevel=(styleIDValue-UIAHandler.StyleId_Heading1)+1 - wantedLevel=int(itemType[7:]) if len(itemType)>7 else None + # #9842: styleIDValue can sometimes be a pointer to IUnknown. + # In Python 3, comparing an int with a pointer raises a TypeError. + if isinstance(styleIDValue, int) and UIAHandler.StyleId_Heading1 <= styleIDValue <= UIAHandler.StyleId_Heading9: + foundLevel = (styleIDValue - UIAHandler.StyleId_Heading1) + 1 + wantedLevel = int(itemType[7:]) if len(itemType) > 7 else None if not wantedLevel or wantedLevel==foundLevel: if not firstLoop or not position: tempInfo.expand(textInfos.UNIT_PARAGRAPH) @@ -175,7 +177,7 @@ def UIAControlQuicknavIterator(itemType,document,position,UIACondition,direction # All items are requested (such as for elements list) elements=document.rootNVDAObject.UIAElement.findAll(UIAHandler.TreeScope_Descendants,UIACondition) if elements: - for index in xrange(elements.length): + for index in range(elements.length): element=elements.getElement(index) try: elementRange=document.rootNVDAObject.UIATextPattern.rangeFromChild(element) diff --git a/source/UIAUtils.py b/source/UIAUtils.py index 7704b12aa2d..b2e6e1c2f5e 100644 --- a/source/UIAUtils.py +++ b/source/UIAUtils.py @@ -19,7 +19,7 @@ def createUIAMultiPropertyCondition(*dicts): outerOrList=[] for dict in dicts: andList=[] - for key,values in dict.iteritems(): + for key,values in dict.items(): innerOrList=[] if not isinstance(values,(list,set)): values=[values] @@ -84,12 +84,12 @@ class UIAMixedAttributeError(ValueError): """Raised when a function would return a UIAutomation text attribute value that is mixed.""" pass -def getUIATextAttributeValueFromRange(range,attrib,ignoreMixedValues=False): +def getUIATextAttributeValueFromRange(rangeObj,attrib,ignoreMixedValues=False): """ Wraps IUIAutomationTextRange::getAttributeValue, returning UIAutomation's reservedNotSupportedValue on COMError, and raising UIAMixedAttributeError if a mixed value would be returned and ignoreMixedValues is False. """ try: - val=range.GetAttributeValue(attrib) + val = rangeObj.GetAttributeValue(attrib) except COMError: return UIAHandler.handler.reservedNotSupportedValue if val==UIAHandler.handler.ReservedMixedAttributeValue: @@ -195,7 +195,7 @@ def __init__(self,textRange,IDs): super(BulkUIATextRangeAttributeValueFetcher,self).__init__(textRange) IDsArray=(ctypes.c_long*len(IDs))(*IDs) values=textRange.GetAttributeValues(IDsArray,len(IDsArray)) - self.IDsToValues={IDs[x]:values[x] for x in xrange(len(IDs))} + self.IDsToValues={IDs[x]:values[x] for x in range(len(IDs))} def getValue(self,ID,ignoreMixedValues=False): val=self.IDsToValues[ID] diff --git a/source/XMLFormatting.py b/source/XMLFormatting.py index af294b53170..73fe805d56a 100755 --- a/source/XMLFormatting.py +++ b/source/XMLFormatting.py @@ -1,6 +1,13 @@ +#XMLFormatting.py +#A part of NonVisual Desktop Access (NVDA) +#Copyright (C) 2008-2019 NV Access Limited, Babbage B.V. +#This file is covered by the GNU General Public License. +#See the file COPYING for more details. + from xml.parsers import expat import textInfos from logHandler import log +from textUtils import WCHAR_ENCODING, isLowSurrogate class XMLTextParser(object): @@ -16,10 +23,10 @@ def _startElementHandler(self,tagName,attrs): data=attrs.get('value',None) if data is not None: try: - data=unichr(int(data)) + data=chr(int(data)) except ValueError: data=u'\ufffd' - self._CharacterDataHandler(data) + self._CharacterDataHandler(data, processBufferedSurrogates=isLowSurrogate(data)) return elif tagName=='control': newAttrs=textInfos.ControlField(attrs) @@ -48,16 +55,18 @@ def _EndElementHandler(self,tagName): else: raise ValueError("unknown tag name: %s"%tagName) - def _CharacterDataHandler(self,data): + def _CharacterDataHandler(self,data, processBufferedSurrogates=False): cmdList=self._commandList - if cmdList and isinstance(cmdList[-1],basestring): - cmdList[-1]+=data + if cmdList and isinstance(cmdList[-1],str): + cmdList[-1] += data + if processBufferedSurrogates: + cmdList[-1] = cmdList[-1].encode(WCHAR_ENCODING, errors="surrogatepass").decode(WCHAR_ENCODING) else: cmdList.append(data) def parse(self,XMLText): try: - self.parser.Parse(XMLText.encode('utf-8')) + self.parser.Parse(XMLText) except: log.error("XML: %s"%XMLText,exc_info=True) return self._commandList diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py index 543242ab41a..5f72a243ae0 100644 --- a/source/_UIAHandler.py +++ b/source/_UIAHandler.py @@ -158,7 +158,7 @@ ignoreWinEventsMap = { UIA_AutomationPropertyChangedEventId: list(UIAPropertyIdsToNVDAEventNames.keys()), } -for id in UIAEventIdsToNVDAEventNames.iterkeys(): +for id in UIAEventIdsToNVDAEventNames.keys(): ignoreWinEventsMap[id] = [0] class UIAHandler(COMObject): @@ -197,10 +197,10 @@ def MTAThreadFunc(self): # #7345: Instruct UIA to never map MSAA winEvents to UIA propertyChange events. # These events are not needed by NVDA, and they can cause the UI Automation client library to become unresponsive if an application firing winEvents has a slow message pump. pfm=self.clientObject.proxyFactoryMapping - for index in xrange(pfm.count): + for index in range(pfm.count): e=pfm.getEntry(index) entryChanged = False - for eventId, propertyIds in ignoreWinEventsMap.iteritems(): + for eventId, propertyIds in ignoreWinEventsMap.items(): for propertyId in propertyIds: # Check if this proxy has mapped any winEvents to the UIA propertyChange event for this property ID try: @@ -248,8 +248,9 @@ def MTAThreadFunc(self): self.reservedNotSupportedValue=self.clientObject.ReservedNotSupportedValue self.ReservedMixedAttributeValue=self.clientObject.ReservedMixedAttributeValue self.clientObject.AddFocusChangedEventHandler(self.baseCacheRequest,self) - self.clientObject.AddPropertyChangedEventHandler(self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self,UIAPropertyIdsToNVDAEventNames.keys()) - for x in UIAEventIdsToNVDAEventNames.iterkeys(): + # Use a list of keys as AddPropertyChangedEventHandler expects a sequence. + self.clientObject.AddPropertyChangedEventHandler(self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self,list(UIAPropertyIdsToNVDAEventNames)) + for x in UIAEventIdsToNVDAEventNames.keys(): self.clientObject.addAutomationEventHandler(x,self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self) # #7984: add support for notification event (IUIAutomation5, part of Windows 10 build 16299 and later). if isinstance(self.clientObject, IUIAutomation5): diff --git a/source/addonAPIVersion.py b/source/addonAPIVersion.py index be7a5798700..14f30b64c2f 100644 --- a/source/addonAPIVersion.py +++ b/source/addonAPIVersion.py @@ -13,12 +13,13 @@ """ CURRENT = (buildVersion.version_year, buildVersion.version_major, buildVersion.version_minor) -BACK_COMPAT_TO = (0, 0, 0) +BACK_COMPAT_TO = (2019, 3, 0) """ As BACK_COMPAT_TO is incremented, the changed / removed parts / or reasoning should be added below. EG: (x, y, z): Large changes to speech.py --- (0, 0, 0): API version zero, used to signify addons released prior to API version checks. +(2019, 3, 0): speech refactor, Python 3 """ #: Compiled regular expression to match an addon API version string. diff --git a/source/addonHandler/__init__.py b/source/addonHandler/__init__.py index 7dd532acd0c..10f95c8549b 100644 --- a/source/addonHandler/__init__.py +++ b/source/addonHandler/__init__.py @@ -14,7 +14,8 @@ import collections import pkgutil import shutil -from six.moves import cStringIO as StringIO, cPickle +from io import StringIO +import pickle from six import string_types import globalVars import zipfile @@ -47,7 +48,9 @@ def loadState(): global state statePath=os.path.join(globalVars.appArgs.configPath,stateFilename) try: - state = cPickle.load(file(statePath, "r")) + # #9038: Python 3 requires binary format when working with pickles. + with open(statePath, "rb") as f: + state = pickle.load(f) if "disabledAddons" not in state: state["disabledAddons"] = set() if "pendingDisableSet" not in state: @@ -67,7 +70,9 @@ def loadState(): def saveState(): statePath=os.path.join(globalVars.appArgs.configPath,stateFilename) try: - cPickle.dump(state, file(statePath, "wb")) + # #9038: Python 3 requires binary format when working with pickles. + with open(statePath, "wb") as f: + pickle.dump(state, f) except: log.debugWarning("Error saving state", exc_info=True) @@ -223,7 +228,7 @@ def getAvailableAddons(refresh=False, filterFunc=None): generators = [_getAvailableAddonsFromPath(path) for path in _getDefaultAddonPaths()] for addon in itertools.chain(*generators): _availableAddons[addon.path] = addon - return (addon for addon in _availableAddons.itervalues() if not filterFunc or filterFunc(addon)) + return (addon for addon in _availableAddons.values() if not filterFunc or filterFunc(addon)) def installAddonBundle(bundle): """Extracts an Addon bundle in to a unique subdirectory of the user addons directory, marking the addon as needing install completion on NVDA restart.""" @@ -278,13 +283,13 @@ def __init__(self, path): self.path = os.path.abspath(path) self._extendedPackages = set() manifest_path = os.path.join(path, MANIFEST_FILENAME) - with open(manifest_path) as f: + with open(manifest_path, 'r', encoding="utf_8") as f: translatedInput = None for translatedPath in _translatedManifestPaths(): p = os.path.join(self.path, translatedPath) if os.path.exists(p): log.debug("Using manifest translation from %s", p) - translatedInput = open(p, 'r') + translatedInput = open(p, 'r', encoding="utf_8") break self.manifest = AddonManifest(f, translatedInput) @@ -359,7 +364,6 @@ def addToPackagePath(self, package): if not os.path.isdir(extension_path): # This addon does not have extension points for this package return - # Python 2.x doesn't properly handle unicode import paths, so convert them before adding. converted_path = self._getPathForInclusionInPackage(package) package.__path__.insert(0, converted_path) self._extendedPackages.add(package) @@ -418,7 +422,7 @@ def isPendingDisable(self): def _getPathForInclusionInPackage(self, package): extension_path = os.path.join(self.path, package.__name__) - return extension_path.encode("mbcs") + return extension_path def loadModule(self, name): """ loads a python module from the addon directory @@ -470,9 +474,9 @@ def getDocFilePath(self, fileName=None): An add-on can specify a default documentation file name via the docFileName parameter in its manifest. @param fileName: The requested file name or C{None} for the add-on's default. - @type fileName: basestring + @type fileName: str @return: The path to the requested file or C{None} if it wasn't found. - @rtype: basestring + @rtype: str """ if not fileName: fileName = self.manifest["docFileName"] @@ -503,7 +507,7 @@ def getCodeAddon(obj=None, frameDist=1): if obj is None: obj = sys._getframe(frameDist) fileName = inspect.getfile(obj) - dir= unicode(os.path.abspath(os.path.dirname(fileName)), "mbcs") + dir= os.path.abspath(os.path.dirname(fileName)) # if fileName is not a subdir of one of the addon paths # It does not belong to an addon. for p in _getDefaultAddonPaths(): @@ -513,7 +517,7 @@ def getCodeAddon(obj=None, frameDist=1): raise AddonError("Code does not belong to an addon package.") curdir = dir while curdir not in _getDefaultAddonPaths(): - if curdir in _availableAddons.keys(): + if curdir in _availableAddons: return _availableAddons[curdir] curdir = os.path.abspath(os.path.join(curdir, "..")) # Not found! @@ -526,7 +530,7 @@ def initTranslation(): # FIXME: shall we retrieve the caller module object explicitly? try: callerFrame = inspect.currentframe().f_back - callerFrame.f_globals['_'] = translations.ugettext + callerFrame.f_globals['_'] = translations.gettext # Install our pgettext function. callerFrame.f_globals['pgettext'] = languageHandler.makePgettext(translations) finally: @@ -552,17 +556,24 @@ def __init__(self, bundlePath): """ Constructs an L{AddonBundle} from a filename. @param bundlePath: The path for the bundle file. """ - self._path = bundlePath if isinstance(bundlePath, unicode) else unicode(bundlePath, "mbcs") + self._path = bundlePath # Read manifest: translatedInput=None with zipfile.ZipFile(self._path, 'r') as z: for translationPath in _translatedManifestPaths(forBundle=True): try: + # ZipFile.open opens every file in binary mode. + # decoding is handled by configobj. translatedInput = z.open(translationPath, 'r') break except KeyError: pass - self._manifest = AddonManifest(z.open(MANIFEST_FILENAME), translatedInput=translatedInput) + self._manifest = AddonManifest( + # ZipFile.open opens every file in binary mode. + # decoding is handled by configobj. + z.open(MANIFEST_FILENAME, 'r'), + translatedInput=translatedInput + ) if self.manifest.errors is not None: _report_manifest_errors(self.manifest) raise AddonError("Manifest file has errors.") @@ -575,7 +586,7 @@ def extract(self, addonPath): """ with zipfile.ZipFile(self._path, 'r') as z: for info in z.infolist(): - if isinstance(info.filename, str): + if isinstance(info.filename, bytes): # #2505: Handle non-Unicode file names. # Most archivers seem to use the local OEM code page, even though the spec says only cp437. # HACK: Overriding info.filename is a bit ugly, but it avoids a lot of code duplication. @@ -603,7 +614,7 @@ def createAddonBundleFromPath(path, destDir=None): manifest_path = os.path.join(basedir, MANIFEST_FILENAME) if not os.path.isfile(manifest_path): raise AddonError("Can't find %s manifest file." % manifest_path) - with open(manifest_path) as f: + with open(manifest_path, 'r', encoding="utf_8") as f: manifest = AddonManifest(f) if manifest.errors is not None: _report_manifest_errors(manifest) diff --git a/source/api.py b/source/api.py index 0178b2f2d22..821095a2736 100644 --- a/source/api.py +++ b/source/api.py @@ -6,6 +6,7 @@ """General functions for NVDA""" +import ctypes import config import textInfos import review @@ -18,8 +19,6 @@ import NVDAObjects.IAccessible import winUser import controlTypes -import win32clipboard -import win32con import eventHandler import braille import watchdog @@ -85,7 +84,7 @@ def setFocusObject(obj): pass tempObj=getDesktopObject() # Scan backwards through the old ancestors looking for a match. - for index in xrange(oldFocusLineLength-1,-1,-1): + for index in range(oldFocusLineLength-1,-1,-1): watchdog.alive() if tempObj==oldFocusLine[index]: # Match! The old and new focus ancestors converge at this point. @@ -252,17 +251,14 @@ def isTypingProtected(): def createStateList(states): """Breaks down the given integer in to a list of numbers that are 2 to the power of their position.""" - return [x for x in [1<0 and not text.isspace(): - try: - win32clipboard.OpenClipboard() - except win32clipboard.error: - return False - try: - win32clipboard.EmptyClipboard() - win32clipboard.SetClipboardData(win32con.CF_UNICODETEXT, text) - finally: - win32clipboard.CloseClipboard() - win32clipboard.OpenClipboard() # there seems to be a bug so to retrieve unicode text we have to reopen the clipboard - try: - got = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT) - finally: - win32clipboard.CloseClipboard() - if got == text: - return True - return False + if not isinstance(text,str) or len(text)==0: + return False + import gui + with winUser.openClipboard(gui.mainFrame.Handle): + winUser.emptyClipboard() + winUser.setClipboardData(winUser.CF_UNICODETEXT,text) + got=getClipData() + return got == text def getClipData(): """Receives text from the windows clipboard. @returns: Clipboard text @rtype: string """ - text = "" - win32clipboard.OpenClipboard() - try: - text = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT) - finally: - win32clipboard.CloseClipboard() - return text + import gui + with winUser.openClipboard(gui.mainFrame.Handle): + return winUser.getClipboardData(winUser.CF_UNICODETEXT) or u"" def getStatusBar(): """Obtain the status bar for the current foreground object. @@ -350,7 +332,7 @@ def getStatusBarText(obj): text = obj.name or "" if text: text += " " - return text + " ".join(chunk for child in obj.children for chunk in (child.name, child.value) if chunk and isinstance(chunk, basestring) and not chunk.isspace()) + return text + " ".join(chunk for child in obj.children for chunk in (child.name, child.value) if chunk and isinstance(chunk, str) and not chunk.isspace()) def filterFileName(name): """Replaces invalid characters in a given string to make a windows compatible file name. diff --git a/source/appModuleHandler.py b/source/appModuleHandler.py index 52e1afdb7d3..e45bab88b4b 100644 --- a/source/appModuleHandler.py +++ b/source/appModuleHandler.py @@ -17,6 +17,7 @@ import sys import winVersion import pkgutil +import importlib import threading import tempfile import comtypes.client @@ -69,7 +70,7 @@ def getAppNameFromProcessID(processID,includeExt=False): @param includeExt: C{True} to include the extension of the application's executable filename, C{False} to exclude it. @type window: bool @returns: application name - @rtype: unicode or str + @rtype: str """ if processID==NVDAProcessID: return "nvda.exe" if includeExt else "nvda" @@ -77,7 +78,7 @@ def getAppNameFromProcessID(processID,includeExt=False): FProcessEntry32 = processEntry32W() FProcessEntry32.dwSize = ctypes.sizeof(processEntry32W) ContinueLoop = winKernel.kernel32.Process32FirstW(FSnapshotHandle, ctypes.byref(FProcessEntry32)) - appName = unicode() + appName = str() while ContinueLoop: if FProcessEntry32.th32ProcessID == processID: appName = FProcessEntry32.szExeFile @@ -92,9 +93,7 @@ def getAppNameFromProcessID(processID,includeExt=False): # This might be an executable which hosts multiple apps. # Try querying the app module for the name of the app being hosted. try: - # Python 2.x can't properly handle unicode module names, so convert them. - mod = __import__("appModules.%s" % appName.encode("mbcs"), - globals(), locals(), ("appModules",)) + mod = importlib.import_module("appModules.%s" % appName, package="appModules") return mod.getAppNameFromHost(processID) except (ImportError, AttributeError, LookupError): pass @@ -140,7 +139,7 @@ def update(processID,helperLocalBindingHandle=None,inprocRegistrationHandle=None def cleanup(): """Removes any appModules from the cache whose process has died. """ - for deadMod in [mod for mod in runningTable.itervalues() if not mod.isAlive]: + for deadMod in [mod for mod in runningTable.values() if not mod.isAlive]: log.debug("application %s closed"%deadMod.appName) del runningTable[deadMod.processID] if deadMod in set(o.appModule for o in api.getFocusAncestors()+[api.getFocusObject()] if o and o.appModule): @@ -161,23 +160,21 @@ def fetchAppModule(processID,appName): @param processID: process ID for it to be associated with @type processID: integer @param appName: the application name for which an appModule should be found. - @type appName: unicode or str + @type appName: str @returns: the appModule, or None if not found @rtype: AppModule """ # First, check whether the module exists. # We need to do this separately because even though an ImportError is raised when a module can't be found, it might also be raised for other reasons. - # Python 2.x can't properly handle unicode module names, so convert them. - modName = appName.encode("mbcs") + modName = appName if doesAppModuleExist(modName): try: - return __import__("appModules.%s" % modName, globals(), locals(), ("appModules",)).AppModule(processID, appName) + return importlib.import_module("appModules.%s" % modName, package="appModules").AppModule(processID, appName) except: log.error("error in appModule %r"%modName, exc_info=True) - # We can't present a message which isn't unicode, so use appName, not modName. # Translators: This is presented when errors are found in an appModule (example output: error in appModule explorer). - ui.message(_("Error in appModule %s")%appName) + ui.message(_("Error in appModule %s")%modName) # Use the base AppModule. return AppModule(processID, appName) @@ -189,7 +186,7 @@ def reloadAppModules(): """ global appModules state = [] - for mod in runningTable.itervalues(): + for mod in runningTable.values(): state.append({key: getattr(mod, key) for key in ("processID", # #2892: We must save nvdaHelperRemote handles, as we can't reinitialize without a foreground/focus event. # Also, if there is an active context handle such as a loaded buffer, @@ -203,7 +200,7 @@ def reloadAppModules(): mod._helperPreventDisconnect = True terminate() del appModules - mods=[k for k,v in sys.modules.iteritems() if k.startswith("appModules") and v is not None] + mods=[k for k,v in sys.modules.items() if k.startswith("appModules") and v is not None] for mod in mods: del sys.modules[mod] import appModules @@ -232,7 +229,7 @@ def initialize(): _importers=list(pkgutil.iter_importers("appModules.__init__")) def terminate(): - for processID, app in runningTable.iteritems(): + for processID, app in runningTable.items(): try: app.terminate() except: @@ -455,10 +452,10 @@ def dumpOnCrash(self): This should only be called if instructed by a developer. """ path = os.path.join(tempfile.gettempdir(), - "nvda_crash_%s_%d.dmp" % (self.appName, self.processID)).decode("mbcs") + "nvda_crash_%s_%d.dmp" % (self.appName, self.processID)) NVDAHelper.localLib.nvdaInProcUtils_dumpOnCrash( self.helperLocalBindingHandle, path) - print "Dump path: %s" % path + print("Dump path: %s" % path) class AppProfileTrigger(config.ProfileTrigger): """A configuration profile trigger for when a particular application has focus. diff --git a/source/appModules/devenv.py b/source/appModules/devenv.py index c752b066c87..c647a256850 100644 --- a/source/appModules/devenv.py +++ b/source/appModules/devenv.py @@ -179,13 +179,12 @@ def _createEditPoint(self): def _getOffsetFromPoint(self,x,y): yMinUnit, yMaxUnit, yVisible, yFirstVisible = self._textView.GetScrollInfo(SB_VERT) hMinUnit, hMaxUnit, hVisible, hFirstVisible = self._textView.GetScrollInfo(SB_HORZ) - # These should probably be cached as they are fairly unlikely to change, but ... lineHeight = self._textView.GetLineHeight() - charWidth = self._window.Width / hVisible + charWidth = self._window.Width // hVisible - offsetLine = (y - self._window.Top) / lineHeight + yFirstVisible - offsetChar = (x - self._window.Left) / charWidth + hFirstVisible + offsetLine = (y - self._window.Top) // lineHeight + yFirstVisible + offsetChar = (x - self._window.Left) // charWidth + hFirstVisible return self._textView.GetNearestPosition(offsetLine, offsetChar)[0] def __init__(self, obj, position): diff --git a/source/appModules/excel.py b/source/appModules/excel.py index fcbacbc6111..afe6cac130e 100644 --- a/source/appModules/excel.py +++ b/source/appModules/excel.py @@ -25,7 +25,7 @@ class Excel6(Window): def _get_focusRedirect(self): if self.role==controlTypes.ROLE_UNKNOWN: # The control is inaccessible, try several times to find the CellEdit UIA element with focus and use that instead. - for count in xrange(10): + for count in range(10): if count>=1: api.processPendingEvents(processEventQueue=False) if eventHandler.isPendingEvents("gainFocus"): diff --git a/source/appModules/explorer.py b/source/appModules/explorer.py index aeed7c4ead6..c218e641cde 100644 --- a/source/appModules/explorer.py +++ b/source/appModules/explorer.py @@ -67,18 +67,6 @@ def _get_shouldAllowIAccessibleFocusEvent(self): return True return False - -class ClassicStartMenu(Window): - # Override the name, as Windows names this the "Application" menu contrary to all documentation. - # Translators: The title of Start menu/screen in your language (only the word start). - name = _("Start") - - def event_gainFocus(self): - # In Windows XP, the Start button will get focus first, so silence this. - speech.cancelSpeech() - super(ClassicStartMenu, self).event_gainFocus() - - class NotificationArea(IAccessible): """The Windows notification area, a.k.a. system tray. """ @@ -200,14 +188,7 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList): return # Optimization: return early to avoid comparing class names and roles that will never match. if windowClass == "ToolbarWindow32": - if role == controlTypes.ROLE_POPUPMENU: - parent = obj.parent - if parent and parent.windowClassName == "SysPager" and obj.windowStyle & 0x80: - clsList.insert(0, ClassicStartMenu) - else: - # Check whether this is the notification area, a.k.a. system tray. - if isinstance(obj.parent, ClassicStartMenu): - return # This can't be a notification area + if role != controlTypes.ROLE_POPUPMENU: try: # The toolbar's immediate parent is its window object, so we need to go one further. toolbarParent = obj.parent.parent @@ -281,7 +262,7 @@ def event_NVDAObject_init(self, obj): return if windowClass == "DV2ControlHost" and role == controlTypes.ROLE_PANE: - # Windows Vista/7 start menu. + # Windows 7 start menu. obj.presentationType=obj.presType_content obj.isPresentableFocusAncestor = True # In Windows 7, the description of this pane is extremely verbose help text, so nuke it. diff --git a/source/appModules/kindle.py b/source/appModules/kindle.py index 79e0446de5d..b32f80a6a2b 100644 --- a/source/appModules/kindle.py +++ b/source/appModules/kindle.py @@ -156,7 +156,7 @@ def _iterEmbeddedObjs(self, hypertext, startIndex, direction): """Recursively iterate through all embedded objects in a given direction starting at a given hyperlink index. """ log.debug("Starting at hyperlink index %d" % startIndex) - for index in xrange(startIndex, hypertext.nHyperlinks if direction == "next" else -1, 1 if direction == "next" else -1): + for index in range(startIndex, hypertext.nHyperlinks if direction == "next" else -1, 1 if direction == "next" else -1): hl = hypertext.hyperlink(index) obj = IAccessible(IAccessibleObject=hl.QueryInterface(IAccessibleHandler.IAccessible2), IAccessibleChildID=0) log.debug("Yielding object at index %d" % index) diff --git a/source/appModules/klangoplayer.py b/source/appModules/klangoplayer.py deleted file mode 100644 index eaa94a1dabe..00000000000 --- a/source/appModules/klangoplayer.py +++ /dev/null @@ -1,7 +0,0 @@ -#appModules/klangoplayer.py - -import appModuleHandler - -class AppModule(appModuleHandler.AppModule): - - sleepMode=True diff --git a/source/appModules/lockapp.py b/source/appModules/lockapp.py index 4d66e930b61..020109426b7 100644 --- a/source/appModules/lockapp.py +++ b/source/appModules/lockapp.py @@ -34,54 +34,55 @@ def event_NVDAObject_init(self, obj): obj.parent = None SAFE_SCRIPTS = { - GlobalCommands.script_reportCurrentFocus.__func__, - GlobalCommands.script_title.__func__, - GlobalCommands.script_dateTime.__func__, - GlobalCommands.script_say_battery_status.__func__, - GlobalCommands.script_navigatorObject_current.__func__, - GlobalCommands.script_navigatorObject_currentDimensions.__func__, - GlobalCommands.script_navigatorObject_toFocus.__func__, - GlobalCommands.script_navigatorObject_moveFocus.__func__, - GlobalCommands.script_navigatorObject_parent.__func__, - GlobalCommands.script_navigatorObject_next.__func__, - GlobalCommands.script_navigatorObject_previous.__func__, - GlobalCommands.script_navigatorObject_firstChild.__func__, - GlobalCommands.script_review_activate.__func__, - GlobalCommands.script_review_top.__func__, - GlobalCommands.script_review_previousLine.__func__, - GlobalCommands.script_review_currentLine.__func__, - GlobalCommands.script_review_nextLine.__func__, - GlobalCommands.script_review_bottom.__func__, - GlobalCommands.script_review_previousWord.__func__, - GlobalCommands.script_review_currentWord.__func__, - GlobalCommands.script_review_nextWord.__func__, - GlobalCommands.script_review_startOfLine.__func__, - GlobalCommands.script_review_previousCharacter.__func__, - GlobalCommands.script_review_currentCharacter.__func__, - GlobalCommands.script_review_nextCharacter.__func__, - GlobalCommands.script_review_endOfLine.__func__, - GlobalCommands.script_review_sayAll.__func__, - GlobalCommands.script_braille_scrollBack.__func__, - GlobalCommands.script_braille_scrollForward.__func__, - GlobalCommands.script_braille_routeTo.__func__, - GlobalCommands.script_braille_previousLine.__func__, - GlobalCommands.script_braille_nextLine.__func__, - GlobalCommands.script_navigatorObject_nextInFlow.__func__, - GlobalCommands.script_navigatorObject_previousInFlow.__func__, - GlobalCommands.script_touch_changeMode.__func__, - GlobalCommands.script_touch_newExplore.__func__, - GlobalCommands.script_touch_explore.__func__, - GlobalCommands.script_touch_hoverUp.__func__, - GlobalCommands.script_moveMouseToNavigatorObject.__func__, - GlobalCommands.script_moveNavigatorObjectToMouse.__func__, - GlobalCommands.script_leftMouseClick.__func__, - GlobalCommands.script_rightMouseClick.__func__, + GlobalCommands.script_reportCurrentFocus, + GlobalCommands.script_title, + GlobalCommands.script_dateTime, + GlobalCommands.script_say_battery_status, + GlobalCommands.script_navigatorObject_current, + GlobalCommands.script_navigatorObject_currentDimensions, + GlobalCommands.script_navigatorObject_toFocus, + GlobalCommands.script_navigatorObject_moveFocus, + GlobalCommands.script_navigatorObject_parent, + GlobalCommands.script_navigatorObject_next, + GlobalCommands.script_navigatorObject_previous, + GlobalCommands.script_navigatorObject_firstChild, + GlobalCommands.script_review_activate, + GlobalCommands.script_review_top, + GlobalCommands.script_review_previousLine, + GlobalCommands.script_review_currentLine, + GlobalCommands.script_review_nextLine, + GlobalCommands.script_review_bottom, + GlobalCommands.script_review_previousWord, + GlobalCommands.script_review_currentWord, + GlobalCommands.script_review_nextWord, + GlobalCommands.script_review_startOfLine, + GlobalCommands.script_review_previousCharacter, + GlobalCommands.script_review_currentCharacter, + GlobalCommands.script_review_nextCharacter, + GlobalCommands.script_review_endOfLine, + GlobalCommands.script_review_sayAll, + GlobalCommands.script_braille_scrollBack, + GlobalCommands.script_braille_scrollForward, + GlobalCommands.script_braille_routeTo, + GlobalCommands.script_braille_previousLine, + GlobalCommands.script_braille_nextLine, + GlobalCommands.script_navigatorObject_nextInFlow, + GlobalCommands.script_navigatorObject_previousInFlow, + GlobalCommands.script_touch_changeMode, + GlobalCommands.script_touch_newExplore, + GlobalCommands.script_touch_explore, + GlobalCommands.script_touch_hoverUp, + GlobalCommands.script_moveMouseToNavigatorObject, + GlobalCommands.script_moveNavigatorObjectToMouse, + GlobalCommands.script_leftMouseClick, + GlobalCommands.script_rightMouseClick, } def _inputCaptor(self, gesture): script = gesture.script if not script: return True # Only allow specific scripts so people can't touch the clipboard, change NVDA config, etc. + # #9883: script is a bound method, __func__ gives us the underlying function. return script.__func__ in self.SAFE_SCRIPTS def event_appModule_gainFocus(self): diff --git a/source/appModules/miranda32.py b/source/appModules/miranda32.py index 8a3f5535919..54e83fb1950 100644 --- a/source/appModules/miranda32.py +++ b/source/appModules/miranda32.py @@ -122,7 +122,7 @@ def script_readMessage(self,gesture): def __init__(self, *args, **kwargs): super(AppModule, self).__init__(*args, **kwargs) - for n in xrange(1, self.MessageHistoryLength + 1): + for n in range(1, self.MessageHistoryLength + 1): self.bindGesture("kb:NVDA+control+%s" % n, "readMessage") class mirandaIMContactList(IAccessible): @@ -225,7 +225,7 @@ def _get_name(self): tc=None if tc and tc.role==controlTypes.ROLE_TABCONTROL: children=tc.children - for index in xrange(len(children)): + for index in range(len(children)): if (children[index].role==controlTypes.ROLE_TAB) and (controlTypes.STATE_SELECTED in children[index].states): name=children[index].name break diff --git a/source/appModules/msimn.py b/source/appModules/msimn.py index a8052bf557b..591a1e03745 100644 --- a/source/appModules/msimn.py +++ b/source/appModules/msimn.py @@ -65,7 +65,7 @@ def event_NVDAObject_init(self,obj): parentClassName=winUser.getClassName(parentWindow) #If this object is an email header field, and we have a custom label for it, #Then set the object's name to the label - if parentClassName=="OE_Envelope" and isinstance(obj,IAccessible) and obj.IAccessibleChildID==0 and envelopeNames.has_key(controlID): + if parentClassName=="OE_Envelope" and isinstance(obj,IAccessible) and obj.IAccessibleChildID==0 and controlID in envelopeNames: obj.name=envelopeNames[controlID] obj.useITextDocumentSupport=True obj.editValueUnit=textInfos.UNIT_STORY diff --git a/source/appModules/msnmsgr.py b/source/appModules/msnmsgr.py index cefca969d55..5ec21fccf84 100755 --- a/source/appModules/msnmsgr.py +++ b/source/appModules/msnmsgr.py @@ -52,7 +52,7 @@ def _get_basicText(self): def _get_value(self): value=super(OldMSNHistory,self).value - if not isinstance(value,basestring): + if not isinstance(value,str): value="" return value diff --git a/source/appModules/nlnotes.py b/source/appModules/nlnotes.py index 3c71adfc2b8..3a8f87b387f 100644 --- a/source/appModules/nlnotes.py +++ b/source/appModules/nlnotes.py @@ -15,8 +15,7 @@ class IrisTedit(IAccessible): def _get_name(self): - left,top,width,height=self.location - label=api.getDesktopObject().objectFromPoint(left+(width/2),top+(height/2)) + label=api.getDesktopObject().objectFromPoint(*self.location.center) if label: return label.name diff --git a/source/appModules/outlook.py b/source/appModules/outlook.py index c9777c5c133..bf0253e1862 100644 --- a/source/appModules/outlook.py +++ b/source/appModules/outlook.py @@ -108,7 +108,10 @@ def _registerCOMWithFocusJuggle(self): self._hasTriedoutlookAppSwitch=True #Make sure NVDA detects and reports focus on the waiting dialog api.processPendingEvents() - comtypes.client.PumpEvents(1) + try: + comtypes.client.PumpEvents(1) + except WindowsError: + log.debugWarning("Error while pumping com events", exc_info=True) d.Destroy() gui.mainFrame.postPopup() @@ -201,9 +204,12 @@ def chooseNVDAObjectOverlayClasses(self, obj, clsList): return if (windowClassName=="SUPERGRID" and controlID==4704) or (windowClassName=="rctrl_renwnd32" and controlID==109): outlookVersion=self.outlookVersion - if outlookVersion and outlookVersion<=9: - clsList.insert(0, MessageList_pre2003) - elif obj.event_objectID==winUser.OBJID_CLIENT and obj.event_childID==0: + if ( + outlookVersion + and outlookVersion > 9 + and obj.event_objectID==winUser.OBJID_CLIENT + and obj.event_childID==0 + ): clsList.insert(0,SuperGridClient2010) if (windowClassName == "AfxWndW" and controlID==109) or (windowClassName in ("WeekViewWnd","DayViewWnd")): clsList.insert(0,CalendarView) @@ -246,67 +252,6 @@ def event_gainFocus(self): obj.parent=self.parent eventHandler.executeEvent("gainFocus",obj) -class MessageList_pre2003(IAccessible): - - def _get_name(self): - if hasattr(self,'curMessageItem'): - return self.curMessageItem.msg.parent.name - - def _get_role(self): - return controlTypes.ROLE_LIST - - def _get_firstChild(self): - return getattr(self,"curMessageItem",None) - - def _get_children(self): - child=getattr(self,"curMessageItem",None) - if child: - return [child] - else: - return [] - - def event_gainFocus(self): - try: - msg=self.nativeOm.ActiveExplorer().selection[0] - except: - msg=None - pass - if msg: - self.curMessageItem=MessageItem(self,msg) - super(MessageList_pre2003,self).event_gainFocus() - if msg: - eventHandler.executeEvent("gainFocus",self.curMessageItem) - - def script_moveByMessage(self,gesture): - if hasattr(self,'curMessageItem'): - oldEntryID=self.curMessageItem.msg.entryID - else: - oldEntryID=None - gesture.send() - try: - msg=self.nativeOm.ActiveExplorer().selection[0] - except: - msg=None - pass - if msg: - messageItem=MessageItem(self,msg) - newEntryID=messageItem.msg.entryID - if newEntryID!=oldEntryID: - self.curMessageItem=messageItem - eventHandler.executeEvent("gainFocus",messageItem) - - __moveByMessageGestures = ( - "kb:downArrow", - "kb:upArrow", - "kb:home", - "kb:end", - "kb:delete", - ) - - def initOverlayClass(self): - for gesture in self.__moveByMessageGestures: - self.bindGesture(gesture, "moveByMessage") - class MessageItem(Window): def __init__(self,windowHandle=None,parent=None,msg=None): @@ -503,7 +448,7 @@ def _get_name(self): # This is unexpected here. log.debugWarning("Unable to get relevant children for UIAGridRow", stack_info=True) return super(UIAGridRow, self).name - for index in xrange(cachedChildren.length): + for index in range(cachedChildren.length): e=cachedChildren.getElement(index) UIAControlType=e.cachedControlType UIAClassName=e.cachedClassName @@ -533,7 +478,7 @@ def _get_name(self): columnHeaderItems=None if columnHeaderItems: columnHeaderItems=columnHeaderItems.QueryInterface(UIAHandler.IUIAutomationElementArray) - for index in xrange(columnHeaderItems.length): + for index in range(columnHeaderItems.length): columnHeaderItem=columnHeaderItems.getElement(index) columnHeaderTextList.append(columnHeaderItem.currentName) columnHeaderText=" ".join(columnHeaderTextList) diff --git a/source/appModules/powerpnt.py b/source/appModules/powerpnt.py index 627d8870e9c..4b07db56683 100644 --- a/source/appModules/powerpnt.py +++ b/source/appModules/powerpnt.py @@ -244,7 +244,7 @@ def getBulletText(ppBulletFormat): if t==ppBulletNumbered: return "%d."%ppBulletFormat.number #(ppBulletFormat.startValue+(ppBulletFormat.number-1)) elif t: - return unichr(ppBulletFormat.character) + return chr(ppBulletFormat.character) def walkPpShapeRange(ppShapeRange): for ppShape in ppShapeRange: @@ -1257,7 +1257,10 @@ def _registerCOMWithFocusJuggle(self): self.hasTriedPpAppSwitch=True #Make sure NVDA detects and reports focus on the waiting dialog api.processPendingEvents() - comtypes.client.PumpEvents(1) + try: + comtypes.client.PumpEvents(1) + except WindowsError: + log.debugWarning("Error while pumping com events", exc_info=True) d.Destroy() gui.mainFrame.postPopup() diff --git a/source/appModules/skype.py b/source/appModules/skype.py index 8131324f1b7..1c42e161b38 100644 --- a/source/appModules/skype.py +++ b/source/appModules/skype.py @@ -1,265 +1,11 @@ # -*- coding: UTF-8 -*- #appModules/skype.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2007-2019 Peter Vágner, NV Access Limited, Babbage B.V. +#Copyright (C) 2019 Peter Vágner, NV Access Limited, Babbage B.V. #This file is covered by the GNU General Public License. #See the file COPYING for more details. -import re -from comtypes import COMError -import wx import appModuleHandler -import controlTypes -import winUser -import NVDAObjects.IAccessible -import oleacc -import ui -import windowUtils -import displayModel -import queueHandler -import config -import NVDAObjects.behaviors -import api -from logHandler import log - -# Translators: The name of the NVDA command category for Skype specific commands. -SCRCAT_SKYPE = _("Skype") - -TYPING_INDICATOR_MATCH = { - ("TTypingIndicatorPanel", controlTypes.ROLE_STATICTEXT), - ("TWidgetControl", controlTypes.ROLE_LISTITEM), # Skype <= 7.2 -} - -class Conversation(NVDAObjects.IAccessible.IAccessible): - scriptCategory = SCRCAT_SKYPE - - def initOverlayClass(self): - for n in xrange(0, 10): - self.bindGesture("kb:NVDA+control+%d" % n, "reviewRecentMessage") - - def _isEqual(self, other): - # Sometimes, we get this object as an unproxied IAccessible, - # which means the location is different, so IAccessible._isEqual return False. - # This can cause us to get a gainFocus and a focusEntered on two different instances. - # We don't care about the location here. - return self.windowHandle == other.windowHandle - - def _gainedFocus(self): - # The user has entered this Skype conversation. - if self.appModule.conversation: - # Another conversation was previously focused. Clean it up. - self.appModule.conversation.lostFocus() - - self.appModule.conversation = self - try: - self.outputList = NVDAObjects.IAccessible.getNVDAObjectFromEvent( - windowUtils.findDescendantWindow(self.windowHandle, className="TChatContentControl"), - winUser.OBJID_CLIENT, 0).lastChild - except LookupError: - log.debugWarning("Couldn't find output list") - self.outputList = None - else: - self.outputList.startMonitoring() - for wClass, role in TYPING_INDICATOR_MATCH: - try: - self.typingIndicator = NVDAObjects.IAccessible.getNVDAObjectFromEvent( - windowUtils.findDescendantWindow(self.windowHandle, className=wClass), - winUser.OBJID_CLIENT, 1) - except LookupError: - continue - self.typingIndicator.startMonitoring() - break - else: - log.debugWarning("Couldn't find typing indicator") - self.typingIndicator = None - - def event_focusEntered(self): - self._gainedFocus() - super(Conversation, self).event_focusEntered() - - def event_gainFocus(self): - # A conversation might have its own top level window, - # but foreground changes often trigger gainFocus instead of focusEntered. - self._gainedFocus() - super(Conversation, self).event_gainFocus() - - def lostFocus(self): - self.appModule.conversation = None - if self.outputList: - self.outputList.stopMonitoring() - self.outputList = None - if self.typingIndicator: - self.typingIndicator.stopMonitoring() - self.typingIndicator = None - - def script_reviewRecentMessage(self, gesture): - try: - index = int(gesture.mainKeyName[-1]) - except (AttributeError, ValueError): - return - if index == 0: - index = 10 - self.outputList.reviewRecentMessage(index) - # Describes the NVDA command to review messages in Skype. - script_reviewRecentMessage.__doc__ = _("Reports and moves the review cursor to a recent message") - script_reviewRecentMessage.canPropagate = True - -class ChatOutputList(NVDAObjects.IAccessible.IAccessible): - - def startMonitoring(self): - self.oldMessageCount = None - self.update(initial=True) - displayModel.requestTextChangeNotifications(self, True) - - def stopMonitoring(self): - displayModel.requestTextChangeNotifications(self, False) - - RE_MESSAGE = re.compile(r"^From (?P.*), (?P.*), sent on (?P