|
From: NVDA S. <sv...@nv...> - 2008-09-26 12:05:16
|
Author: mdcurran
Date: Fri Sep 26 12:05:16 2008
New Revision: 2395
Log:
NVDA now loads and unloads appModules by using process IDs rather than window handles.
This means that NVDA no longer needs to find application main windows, plus it is now much more accurate in loading and unloading at the appropriate times. E.g. the NVDA appModule is no longer loaded each time the menu gets focus, its only loaded once, as the NVDA process never dies (at least from the point of view of NVDA itself). This will also fix issues such as where more than one thunderbird or windows mail appModule would be loaded at the same time, due to multiple foreground windows of the same application.
An interesting side affect of these changes is also that an application such as Lotus Symphony, where the document is in a separate process to the rest of the user interface, there could actually be two appModules in use, as there would be one for each process ID.
One feature has been removed though, and that is the firing of event_appGainFocus and event_appLooseFocus. These events need to be re-thought a bit, to make sure that they are the most appropriate way of doing things. They are currently not used at all in any appModules officially in NVDA as far as I am aware.
Specific changes follow:
*appModuleHandler.getAppModuleFromWindow is now getAppModuleFromProcessID. This function has now been changed so that if the module can not be found in the running table, it fetches the class, creates the module, caches it in the runningTable, and then returns it as usual. Note that appModuleHandler.update used to be the only way modules could be created.
*renamed appModuleHandler.fetchModule to fetchAppModuleClass.
*renamed appModuleHandler.getAppName to getAppNameFromProcessID.
*the speakApplicationName script in the default appModule now asks the focus object for its processID and gets its appName from that. Previously it used the foreground object. Also it now uses the focus object to get its appModule if it has one, and speaks its name. Also the script no longer spells out the app name as speech.speakSpelling is currently not synchronis, so therefore the spelling was happening after everything else, not where it was called. It also no longer says that the default appModule is the module in use if there is not a specific appModule. This was not technically correct as the default appModule is always in use, its just that the specific appModule sits on top.
*AppModule classes now are initialized with a processID, and an optional appName. If the appName is not provided then it gets it using the processID. When initializing, the appModule now opens a handle to the process identified by the processID, so that it has a way of detecting if the process dies.
*appModule classes have an isAlive property, which can be used to tell if the appModule should still be classed as being alive. It checks the process handle to see if the process still is running.
*appModuleHandler.update now takes a processID rather than a windowHandle. It firstly finds any running appModules to see if they are no longer alive with their isAlive property, and removes the dead ones from the runningTable. It then calls getAppModuleFromProcessID in order to possibly load a new appModule for the given processID.
Modified:
trunk/source/IAccessibleHandler.py
trunk/source/appModuleHandler.py
trunk/source/appModules/_default.py
Modified: trunk/source/IAccessibleHandler.py
==============================================================================
--- trunk/source/IAccessibleHandler.py (original)
+++ trunk/source/IAccessibleHandler.py Fri Sep 26 12:05:16 2008
@@ -759,7 +759,7 @@
@rtype: boolean
"""
#Notify appModuleHandler of this new foreground window
- appModuleHandler.update(window)
+ appModuleHandler.update(winUser.getWindowThreadProcessID(window)[0])
#Handle particular events for the special MSAA caret object just as if they were for the focus object
focus=liveNVDAObjectTable.get('focus',None)
if focus and objectID==OBJID_CARET and eventID in (winUser.EVENT_OBJECT_LOCATIONCHANGE,winUser.EVENT_OBJECT_SHOW):
@@ -804,7 +804,7 @@
# However, it is still a valid event, so return True.
return True
#Notify appModuleHandler of this new foreground window
- appModuleHandler.update(window)
+ appModuleHandler.update(winUser.getWindowThreadProcessID(window)[0])
#If Java access bridge is running, and this is a java window, then pass it to java and forget about it
if JABHandler.isRunning and JABHandler.isJavaWindow(window):
JABHandler.event_enterJavaWindow(window)
@@ -884,7 +884,7 @@
if oldFocus and window==oldFocus.event_windowHandle and objectID==oldFocus.event_objectID and childID==oldFocus.event_childID:
return False
#Notify appModuleHandler of this new foreground window
- appModuleHandler.update(window)
+ appModuleHandler.update(winUser.getWindowThreadProcessID(window)[0])
#If Java access bridge is running, and this is a java window, then pass it to java and forget about it
if JABHandler.isRunning and JABHandler.isJavaWindow(window):
JABHandler.event_enterJavaWindow(window)
Modified: trunk/source/appModuleHandler.py
==============================================================================
--- trunk/source/appModuleHandler.py (original)
+++ trunk/source/appModuleHandler.py Fri Sep 26 12:05:16 2008
@@ -28,7 +28,7 @@
#This is here so that the appModules are able to import modules from the appModules dir themselves
__path__=['.\\appModules']
-#Dictionary of windowHandle:appModule paires used to hold the currently running modules
+#Dictionary of processID:appModule paires used to hold the currently running modules
runningTable={}
#Variable to hold the active (focused) appModule
activeModule=None
@@ -54,16 +54,15 @@
("szExeFile", ctypes.c_char * 259)
]
-def getAppName(window,includeExt=False):
- """Finds out the application name of the given window.
- @param window: the window handle of the application you wish to get the name of.
- @type window: int
+def getAppNameFromProcessID(processID,includeExt=False):
+ """Finds out the application name of the given process.
+ @param processID: the ID of the process handle of the application you wish to get the name of.
+ @type processID: int
@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: str
"""
- processID=winUser.getWindowThreadProcessID(winUser.getAncestor(window,winUser.GA_ROOTOWNER))[0]
if processID==NVDAProcessID:
return "nvda.exe" if includeExt else "nvda"
FSnapshotHandle = winKernel.kernel32.CreateToolhelp32Snapshot (2,0)
@@ -124,68 +123,36 @@
def getAppModuleForNVDAObject(obj):
if not isinstance(obj,NVDAObjects.window.Window):
return
- return getAppModuleFromWindow(obj.windowHandle)
+ return getAppModuleFromProcessID(obj.windowProcessID)
-def getAppModuleFromWindow(windowHandle):
- """Finds the appModule that is for the given window handle.
- This window handle can be any window with in an application, not just the app main window.
- @param windowHandle: The window for which you wish to find the appModule.
- @type windowHandle: int
+def getAppModuleFromProcessID(processID):
+ """Finds the appModule that is for the given process ID. The module is also cached for later retreavals.
+ @param processID: The ID of the process for which you wish to find the appModule.
+ @type processID: int
@returns: the appModule, or None if there isn't one
@rtype: appModule
"""
- appWindow=winUser.getAncestor(windowHandle,winUser.GA_ROOTOWNER)
- log.debug("appWindow %s, from window %s"%(appWindow,windowHandle))
- mod=runningTable.get(appWindow)
- if mod:
- log.debug("found appModule %s"%mod)
- else:
- log.debug("no appModule")
+ mod=runningTable.get(processID)
+ if not mod:
+ appName=getAppNameFromProcessID(processID)
+ modClass=fetchAppModuleClass(appName)
+ if modClass:
+ mod=modClass(processID,appName=appName)
+ if modClass!=AppModule:
+ log.info("Loaded appModule %s"%(mod.appName))
+ loadKeyMap(mod.appName,mod)
+ runningTable[processID]=mod
return mod
-def update(windowHandle):
- """Removes any appModules connected with windows that no longer exist, and uses the given window handle to try and load a new appModule if need be.
- @param windowHandle: any window in an application
- @type windowHandle: int
- """
- global activeModule
- for w in [x for x in runningTable if not winUser.isWindow(x)]:
- if isinstance(activeModule,AppModule) and w==activeModule.appWindow:
- if hasattr(activeModule,"event_appLooseFocus"):
- log.debug("calling appLoseFocus event on appModule %s"%activeModule)
- activeModule.event_appLooseFocus()
- activeModule=None
- log.debug("application %s closed, window %s"%(runningTable[w].appName,w))
- del runningTable[w]
- appWindow=winUser.getAncestor(windowHandle,winUser.GA_ROOTOWNER)
- log.debug("Using window %s, got appWindow %s"%(windowHandle,appWindow))
- if appWindow<=0:
- log.debug("bad appWindow")
- return
- if appWindow not in runningTable:
- log.debug("new appWindow")
- appName=getAppName(appWindow)
- if not appName:
- log.debugWarning("could not get application name from window %s (%s)"%(appWindow,winUser.getClassName(appWindow)))
- return
- mod=fetchModule(appName)
- if mod:
- mod=mod(appName,appWindow)
- if mod.__class__!=AppModule:
- log.info("Loaded appModule %s"%(mod.appName))
- loadKeyMap(appName,mod)
- runningTable[appWindow]=mod
- activeAppWindow=winUser.getAncestor(winUser.getForegroundWindow(),winUser.GA_ROOTOWNER)
- if isinstance(activeModule,AppModule) and activeAppWindow!=activeModule.appWindow:
- log.debug("appModule %s lost focus"%activeModule)
- if hasattr(activeModule,"event_appLooseFocus"):
- activeModule.event_appLooseFocus()
- activeModule=None
- if not activeModule and activeAppWindow in runningTable:
- activeModule=runningTable[activeAppWindow]
- log.debug("appModule %s gained focus"%activeModule)
- if hasattr(activeModule,"event_appGainFocus"):
- activeModule.event_appGainFocus()
+def update(processID):
+ """Removes any appModules from te cache who's process has died, and also tries to load a new appModule for the given process ID if need be.
+ @param processID: the ID of the process.
+ @type processID: int
+ """
+ for deadMod in [mod for mod in runningTable.itervalues() if not mod.isAlive]:
+ log.debug("application %s closed"%deadMod.appName)
+ del runningTable[deadMod.processID];
+ getAppModuleFromProcessID(processID)
def loadKeyMap(appName,mod):
"""Loads a key map in to the given appModule, with the given name. if the key map exists. It takes in to account what layout NVDA is currently set to.
@@ -214,7 +181,7 @@
log.debug("added %s bindings to module %s from file %s"%(bindCount,appName,keyMapFileName))
return True
-def fetchModule(appName):
+def fetchAppModuleClass(appName):
"""Returns an appModule found in the appModules directory, for the given application name.
It only returns the class, it must be initialized with a name and a window to actually be used.
@param appName: the application name for which an appModule should be found.
@@ -239,7 +206,7 @@
"""
global NVDAProcessID,default
NVDAProcessID=os.getpid()
- defaultModClass=fetchModule('_default')
+ defaultModClass=fetchAppModuleClass('_default')
if defaultModClass:
default=defaultModClass('_default',winUser.getDesktopWindow())
if default:
@@ -257,13 +224,22 @@
"""AppModule base class
@var appName: the application name
@type appName: str
- @var appWindow: the application main window
- @type appWindow: int
+ @var processID: the ID of the process this appModule is for.
+ @type processID: int
"""
- def __init__(self,appName,appWindow):
+ def __init__(self,processID,appName=None):
+ self.processID=processID
+ if appName is None:
+ appName=getAppNameFromProcessID(processID)
self.appName=appName
- self.appWindow=appWindow
+ self.processHandle=winKernel.openProcess(winKernel.SYNCHRONIZE,False,processID)
def __repr__(self):
return "AppModule (appName %s, appWindow %s) at address %x"%(self.appName,self.appWindow,id(self))
+
+ def _get_isAlive(self):
+ return bool(winKernel.waitForSingleObject(self.processHandle,0))
+
+ def __del__(self):
+ winKernel.closeHandle(self.processHandle)
Modified: trunk/source/appModules/_default.py
==============================================================================
--- trunk/source/appModules/_default.py (original)
+++ trunk/source/appModules/_default.py Fri Sep 26 12:05:16 2008
@@ -739,14 +739,12 @@
script_passNextKeyThrough.__doc__=_("The next key that is pressed will not be handled at all by NVDA, it will be passed directly through to Windows.")
def script_speakApplicationName(self,keyPress):
- s=appModuleHandler.getAppName(api.getForegroundObject().windowHandle,True)
+ focus=api.getFocusObject()
+ s=appModuleHandler.getAppNameFromProcessID(focus.windowProcessID,True)
speech.speakMessage(_("Currently running application is %s.")%s)
- speech.speakSpelling(s)
- if appModuleHandler.moduleExists(appModuleHandler.activeModule.appName):
- mod = appModuleHandler.activeModule.appName
- else:
- mod = _("default module")
- speech.speakMessage(_("and currently loaded module is %s") % mod)
+ mod=focus.appModule
+ if isinstance(mod,appModuleHandler.AppModule) and type(mod)!=appModuleHandler.AppModule:
+ speech.speakMessage(_("and currently loaded module is %s") % mod.appName)
script_speakApplicationName.__doc__ = _("Speaks filename of the active application along with name of the currently loaded appmodule")
def script_activateGeneralSettingsDialog(self,keyPress):
|