Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions source/appModuleHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,6 @@
import exceptions
import extensionPoints
from fileUtils import getFileVersionInfo
import shlobj
from functools import wraps

# Path to the native system32 directory.
nativeSys32: str = shlobj.SHGetFolderPath(None, shlobj.CSIDL.SYSTEM)
# Path to the syswow64 directory if it exists on the current system.
Syswow64Sys32: str = shlobj.SHGetFolderPath(None, shlobj.CSIDL.SYSTEMX86)
# Do we have separate system32 directories for 32 and 64-bit processes?
hasSeparateSyswow64: bool = nativeSys32 != Syswow64Sys32

#Dictionary of processID:appModule paires used to hold the currently running modules
runningTable={}
Expand Down Expand Up @@ -311,27 +302,6 @@ def handleAppSwitch(oldMods, newMods):
if not mod.sleepMode and hasattr(mod,'event_appModule_gainFocus'):
mod.event_appModule_gainFocus()


def _suspendWow64RedirectionForFileInfoRetrieval(func):
"""Decorator which should be used for functions which need to access binaries backing given appModule.
It checks if the given binary is placed in a system32 directory, and if for the current system system32
redirects 32-bit processes such as NVDA to a different syswow64 directory
disables redirection for the duration of the method call."""
@wraps(func)
def funcWrapper(self):
if (
hasSeparateSyswow64
and self.appPath
# `os.path.commonpath` is necessary to perfor case-insensitive comparisons
and os.path.commonpath([nativeSys32]) == os.path.commonpath([nativeSys32, self.appPath])
):
with winKernel.suspendWow64Redirection():
return func(self)
else:
return func(self)
return funcWrapper


#base class for appModules
class AppModule(baseObject.ScriptableObject):
"""Base app module.
Expand Down Expand Up @@ -409,7 +379,6 @@ def _getImmersivePackageInfo(self):
else:
return None

@_suspendWow64RedirectionForFileInfoRetrieval
def _setProductInfo(self):
"""Set productName and productVersion attributes.
There are at least two ways of obtaining product info for an app:
Expand Down
4 changes: 2 additions & 2 deletions source/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ def getInstalledUserConfigPath():
configInLocalAppData = bool(winreg.QueryValueEx(k, CONFIG_IN_LOCAL_APPDATA_SUBKEY)[0])
except WindowsError:
configInLocalAppData=False
configParent = shlobj.SHGetFolderPath(
0, shlobj.CSIDL.LOCAL_APPDATA if configInLocalAppData else shlobj.CSIDL.APPDATA
configParent = shlobj.SHGetKnownFolderPath(
shlobj.FOLDERID.LocalAppData.value if configInLocalAppData else shlobj.FOLDERID.RoamingAppData.value
)
try:
return os.path.join(configParent, "nvda")
Expand Down
39 changes: 34 additions & 5 deletions source/fileUtils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
#fileUtils.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2017-2019 NV Access Limited, Bram Duvigneau
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2017-2021 NV Access Limited, Bram Duvigneau, Łukasz Golonka
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import os
import ctypes
Expand All @@ -13,6 +12,10 @@
from logHandler import log
from six import text_type
import winKernel
import shlobj
from functools import wraps
import systemUtils


@contextmanager
def FaultTolerantFile(name):
Expand Down Expand Up @@ -40,6 +43,32 @@ def FaultTolerantFile(name):
f.close()
winKernel.moveFileEx(f.name, name, winKernel.MOVEFILE_REPLACE_EXISTING)


def _suspendWow64RedirectionForFileInfoRetrieval(func):
"""
This decorator checks if the file provided as a `filePath`
is placed in a system32 directory, and if for the current system system32
redirects 32-bit processes such as NVDA to a different syswow64 directory
disables redirection for the duration of the function call.
This is necessary when fetching file version info since NVDA is a 32-bit application
and without redirection disabled we would either access a wrong file or not be able to access it at all.
"""
@wraps(func)
def funcWrapper(filePath, *attributes):
nativeSys32 = shlobj.SHGetKnownFolderPath(shlobj.FOLDERID.System.value)
if (
systemUtils.hasSyswow64Dir()
# `os.path.commonpath` is necessary to perform case-insensitive comparisons
and os.path.commonpath([nativeSys32]) == os.path.commonpath([nativeSys32, filePath])
):
with winKernel.suspendWow64Redirection():
return func(filePath, *attributes)
else:
return func(filePath, *attributes)
return funcWrapper


@_suspendWow64RedirectionForFileInfoRetrieval
def getFileVersionInfo(name, *attributes):
"""Gets the specified file version info attributes from the provided file."""
if not isinstance(name, text_type):
Expand Down
55 changes: 33 additions & 22 deletions source/shlobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,53 @@
# See the file COPYING for more details.

r"""
This module wraps the SHGetFolderPath function in shell32.dll and defines the necessary contstants.
CSIDL (constant special item ID list) values provide a unique system-independent way to
This module wraps the `SHGetKnownFolderPath` function in shell32.dll and defines the necessary contstants.
Known folder ids provide a unique system-independent way to
identify special folders used frequently by applications, but which may not have the same name
or location on any given system. For example, the system folder may be "C:\Windows" on one system
and "C:\Winnt" on another. The CSIDL system is used to be compatible with Windows XP.
and "C:\Winnt" on another.
"""

from ctypes import byref, create_unicode_buffer, windll, WinError
import comtypes
import ctypes
import enum
import functools
import typing

shell32 = windll.shell32

MAX_PATH = 260


class CSIDL(enum.IntEnum):
class FOLDERID(enum.Enum):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class FOLDERID(enum.Enum):
class FOLDERID(str, enum.Enum):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you be able to explain what this change is for? I'm going to apply it regardless as not to block this from further reviews, but this pattern is new to me.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"""Contains guids of known folders from Knownfolders.h. Full list is availabe at:
https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid"""
#: The file system directory that serves as a common repository for application-specific data.
#: A typical path is C:\Documents and Settings\username\Application Data.
APPDATA = 0x001a
RoamingAppData = "{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}"
#: The file system directory that serves as a data repository for local (nonroaming) applications.
#: A typical path is C:\Documents and Settings\username\Local Settings\Application Data.
LOCAL_APPDATA = 0x001c
LocalAppData = "{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}"
#: The file system directory that contains application data for all users.
#: A typical path is C:\Documents and Settings\All Users\Application Data.
#: This folder is used for application data that is not user specific.
COMMON_APPDATA = 0x0023
ProgramData = "{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}"
# The Windows System folder.
# A typical path is C:\Windows\System32.
SYSTEM = 0x25
SYSTEMX86 = 0x29


def SHGetFolderPath(owner: typing.Union[None, int], folder: CSIDL, token: int = 0, flags: int = 0) -> str:
path = create_unicode_buffer(MAX_PATH)
# Note  As of Windows Vista, this function is merely a wrapper for SHGetKnownFolderPath
if shell32.SHGetFolderPathW(owner, folder, token, flags, byref(path)) != 0:
raise WinError()
return path.value
System = "{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}"
SystemX86 = "{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}"


@functools.lru_cache(maxsize=128)
def SHGetKnownFolderPath(folderGuid: str, dwFlags: int = 0, hToken: typing.Optional[int] = None) -> str:
"""Wrapper for `SHGetKnownFolderPath` which caches the results
to avoid calling the win32 function unnecessarily."""
guid = comtypes.GUID(folderGuid)
pathPointer = ctypes.c_wchar_p()
res = ctypes.windll.shell32.SHGetKnownFolderPath(
comtypes.byref(guid),
dwFlags,
hToken,
ctypes.byref(pathPointer)
)
if res != 0:
raise RuntimeError(f"SHGetKnownFolderPath failed with erro code {res}")
path = pathPointer.value
ctypes.windll.ole32.CoTaskMemFree(pathPointer)
return path
11 changes: 11 additions & 0 deletions source/systemUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@
import shellapi
import winUser
import os
import functools
import shlobj


@functools.lru_cache(maxsize=1)
def hasSyswow64Dir() -> bool:
"""Returns `True` if the current system has separate system32 directories for 32-bit processes."""
nativeSys32 = shlobj.SHGetKnownFolderPath(shlobj.FOLDERID.System.value)
Syswow64Sys32 = shlobj.SHGetKnownFolderPath(shlobj.FOLDERID.SystemX86.value)
return nativeSys32 != Syswow64Sys32


def openUserConfigurationDirectory():
"""Opens directory containing config files for the current user"""
Expand Down