Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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: 31 additions & 0 deletions source/appModuleHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
import watchdog
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)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could we have this initialization as part of the initialize method?

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.

I don't think this is a good idea here. I really believe we should try to avoid usages of global if at all possible - it makes any refactors unnecessarily difficult. My dislike for global aside please note that appmoduleHandler.initialize is also executed when re-initializing app modules and therefore we would be calling SHGetFolderPath for no reason ( these calls are probably pretty cheap but the return values cannot change during NVDA's lifetime). It should be possible to add additional argument to initialize - something like isFirstInit but it would make code more complex without any real benefit.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It might be my daily job being very C# oriented nowadays, but I consider executing code at the module level pretty terrible.
I don't insist though, let's see what NV Access people say.

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.

I would agree that initializing this in a method would be much better.
If _suspendWow64RedirectionForFileInfoRetrieval could be useful in other modules, I would suggest abstracting this into a new module an initializing this in core to avoid the "re-initializing" you mention.

# 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?
Comment thread
lukaszgo1 marked this conversation as resolved.
Outdated
hasSeparateSyswow64: bool = nativeSys32 != Syswow64Sys32

#Dictionary of processID:appModule paires used to hold the currently running modules
runningTable={}
Expand Down Expand Up @@ -302,6 +311,27 @@ 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.
Comment thread
lukaszgo1 marked this conversation as resolved.
Outdated
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
Comment thread
lukaszgo1 marked this conversation as resolved.
Outdated
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 @@ -379,6 +409,7 @@ 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
12 changes: 3 additions & 9 deletions source/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ 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.SHGetFolderPath(
0, shlobj.CSIDL.LOCAL_APPDATA if configInLocalAppData else shlobj.CSIDL.APPDATA
Comment thread
lukaszgo1 marked this conversation as resolved.
Outdated
)
try:
return os.path.join(configParent, "nvda")
except WindowsError:
Expand All @@ -126,14 +128,6 @@ def getUserDefaultConfigPath(useInstalledPathIfExists=False):
return installedUserConfigPath
return os.path.join(globalVars.appDir, 'userConfig')

def getSystemConfigPath():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is likely to break backwards compat. I don't think that's a problem when this will be 2022.1

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.

I've mentioned that consideration in the PR description. Given that we're branching for 2021.3 already this should not be a problem. Could you add this PR to the 2022.1 milestone so it is not forgotten?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

thanks, I must have missed that.

if isInstalledCopy():
try:
return os.path.join(shlobj.SHGetFolderPath(0, shlobj.CSIDL_COMMON_APPDATA), "nvda")
except WindowsError:
pass
return None


SCRATCH_PAD_ONLY_DIRS = (
'appModules',
Expand Down
48 changes: 27 additions & 21 deletions source/shlobj.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,43 @@
# -*- coding: UTF-8 -*-
#shlobj.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2017 NV Access Limited, Babbage B.V.
#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) 2009-2021 NV Access Limited, Babbage B.V., Łukasz Golonka
# This file is covered by the GNU General Public License.
# 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
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.
"""

from ctypes import *
from ctypes.wintypes import *
from ctypes import byref, create_unicode_buffer, windll, WinError
import enum
import typing

shell32 = windll.shell32

MAX_PATH = 260

#: 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.
CSIDL_APPDATA = 0x001a
#: 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.
CSIDL_LOCAL_APPDATA = 0x001c
#: 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.
CSIDL_COMMON_APPDATA = 0x0023

def SHGetFolderPath(owner, folder, token=0, flags=0):

class CSIDL(enum.IntEnum):
#: 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
#: 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
#: 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
# 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:
Comment thread
lukaszgo1 marked this conversation as resolved.
Outdated
Expand Down
30 changes: 30 additions & 0 deletions source/winKernel.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,36 @@ def GetSystemPowerStatus(sps):
def getThreadLocale():
return kernel32.GetThreadLocale()


ERROR_INVALID_FUNCTION = 0x1


@contextlib.contextmanager
def suspendWow64Redirection():
"""Context manager which disables Wow64 redirection for a section of code and re-enables it afterwards"""
oldValue = LPVOID()
res = kernel32.Wow64DisableWow64FsRedirection(byref(oldValue))
if res == 0:
# Disabling redirection failed.
# This can occur if we're running on 32-bit Windows (no Wow64 redirection)
# or as a 64-bit process on 64-bit Windows (Wow64 redirection not applicable)
# In this case failure is expected and there is no reason to raise an exception.
# Inspect last error code to determine reason for the failure.
errorCode = kernel32.GetLastError()
if errorCode == ERROR_INVALID_FUNCTION: # Redirection not supported or not applicable.
redirectionDisabled = False
else:
raise WinError(errorCode)
else:
redirectionDisabled = True
try:
yield
finally:
if redirectionDisabled:
if kernel32.Wow64RevertWow64FsRedirection(oldValue) == 0:
raise WinError()


class SYSTEMTIME(ctypes.Structure):
_fields_ = (
("wYear", WORD),
Expand Down