2 #A part of NonVisual Desktop Access (NVDA)
\r
3 #Copyright (C) 2006-2007 NVDA Contributors <http://www.nvda-project.org/>
\r
4 #This file is covered by the GNU General Public License.
\r
5 #See the file COPYING for more details.
\r
7 """Manages appModules.
\r
8 @var runningTable: a dictionary of the currently running appModules, using their application's main window handle as a key.
\r
9 @type runningTable: dict
\r
13 import ctypes.wintypes
\r
19 from logHandler import log
\r
25 import NVDAObjects #Catches errors before loading default appModule
\r
29 #Dictionary of processID:appModule paires used to hold the currently running modules
\r
31 #: The process ID of NVDA itself.
\r
35 class processEntry32W(ctypes.Structure):
\r
37 ("dwSize",ctypes.wintypes.DWORD),
\r
38 ("cntUsage", ctypes.wintypes.DWORD),
\r
39 ("th32ProcessID", ctypes.wintypes.DWORD),
\r
40 ("th32DefaultHeapID", ctypes.wintypes.DWORD),
\r
41 ("th32ModuleID",ctypes.wintypes.DWORD),
\r
42 ("cntThreads",ctypes.wintypes.DWORD),
\r
43 ("th32ParentProcessID",ctypes.wintypes.DWORD),
\r
44 ("pcPriClassBase",ctypes.c_long),
\r
45 ("dwFlags",ctypes.wintypes.DWORD),
\r
46 ("szExeFile", ctypes.c_wchar * 260)
\r
49 def getAppNameFromProcessID(processID,includeExt=False):
\r
50 """Finds out the application name of the given process.
\r
51 @param processID: the ID of the process handle of the application you wish to get the name of.
\r
52 @type processID: int
\r
53 @param includeExt: C{True} to include the extension of the application's executable filename, C{False} to exclude it.
\r
55 @returns: application name
\r
56 @rtype: unicode or str
\r
58 if processID==NVDAProcessID:
\r
59 return "nvda.exe" if includeExt else "nvda"
\r
60 FSnapshotHandle = winKernel.kernel32.CreateToolhelp32Snapshot (2,0)
\r
61 FProcessEntry32 = processEntry32W()
\r
62 FProcessEntry32.dwSize = ctypes.sizeof(processEntry32W)
\r
63 ContinueLoop = winKernel.kernel32.Process32FirstW(FSnapshotHandle, ctypes.byref(FProcessEntry32))
\r
66 if FProcessEntry32.th32ProcessID == processID:
\r
67 appName = FProcessEntry32.szExeFile
\r
69 ContinueLoop = winKernel.kernel32.Process32NextW(FSnapshotHandle, ctypes.byref(FProcessEntry32))
\r
70 winKernel.kernel32.CloseHandle(FSnapshotHandle)
\r
72 appName=os.path.splitext(appName)[0].lower()
\r
75 def getAppModuleForNVDAObject(obj):
\r
76 if not isinstance(obj,NVDAObjects.NVDAObject):
\r
78 return getAppModuleFromProcessID(obj.processID)
\r
80 def getAppModuleFromProcessID(processID):
\r
81 """Finds the appModule that is for the given process ID. The module is also cached for later retreavals.
\r
82 @param processID: The ID of the process for which you wish to find the appModule.
\r
83 @type processID: int
\r
84 @returns: the appModule, or None if there isn't one
\r
87 mod=runningTable.get(processID)
\r
89 appName=getAppNameFromProcessID(processID)
\r
90 mod=fetchAppModule(processID,appName)
\r
92 raise RuntimeError("error fetching default appModule")
\r
93 runningTable[processID]=mod
\r
96 def update(processID,helperLocalBindingHandle=None,inprocRegistrationHandle=None):
\r
97 """Removes any appModules from the cache whose process has died, and also tries to load a new appModule for the given process ID if need be.
\r
98 @param processID: the ID of the process.
\r
99 @type processID: int
\r
100 @param helperLocalBindingHandle: an optional RPC binding handle pointing to the RPC server for this process
\r
101 @param inprocRegistrationHandle: an optional rpc context handle representing successful registration with the rpc server for this process
\r
103 for deadMod in [mod for mod in runningTable.itervalues() if not mod.isAlive]:
\r
104 log.debug("application %s closed"%deadMod.appName)
\r
105 del runningTable[deadMod.processID]
\r
106 if deadMod in set(o.appModule for o in api.getFocusAncestors()+[api.getFocusObject()] if o and o.appModule):
\r
107 if hasattr(deadMod,'event_appLoseFocus'):
\r
108 deadMod.event_appLoseFocus()
\r
110 deadMod.terminate()
\r
112 log.exception("Error terminating app module %r" % deadMod)
\r
113 # This creates a new app module if necessary.
\r
114 mod=getAppModuleFromProcessID(processID)
\r
115 if helperLocalBindingHandle:
\r
116 mod.helperLocalBindingHandle=helperLocalBindingHandle
\r
117 if inprocRegistrationHandle:
\r
118 mod._inprocRegistrationHandle=inprocRegistrationHandle
\r
120 def doesAppModuleExist(name):
\r
121 return any(importer.find_module("appModules.%s" % name) for importer in _importers)
\r
123 def fetchAppModule(processID,appName):
\r
124 """Returns an appModule found in the appModules directory, for the given application name.
\r
125 @param processID: process ID for it to be associated with
\r
126 @type processID: integer
\r
127 @param appName: the application name for which an appModule should be found.
\r
128 @type appName: unicode or str
\r
129 @returns: the appModule, or None if not found
\r
132 # First, check whether the module exists.
\r
133 # 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.
\r
134 # Python 2.x can't properly handle unicode module names, so convert them.
\r
135 modName = appName.encode("mbcs")
\r
137 if doesAppModuleExist(modName):
\r
139 return __import__("appModules.%s" % modName, globals(), locals(), ("appModules",)).AppModule(processID, appName)
\r
141 log.error("error in appModule %r"%modName, exc_info=True)
\r
142 # We can't present a message which isn't unicode, so use appName, not modName.
\r
143 # Translators: This is presented when errors are found in an appModule (example output: error in appModule explorer).
\r
144 ui.message(_("Error in appModule %s")%appName)
\r
146 # Use the base AppModule.
\r
147 return AppModule(processID, appName)
\r
149 def reloadAppModules():
\r
150 """Reloads running appModules.
\r
151 especially, it clears the cache of running appModules and deletes them from sys.modules.
\r
152 Each appModule will be reloaded immediately as a reaction on a first event coming from the process.
\r
157 mods=[k for k,v in sys.modules.iteritems() if k.startswith("appModules") and v is not None]
\r
159 del sys.modules[mod]
\r
164 """Initializes the appModule subsystem.
\r
166 global NVDAProcessID,_importers
\r
167 NVDAProcessID=os.getpid()
\r
168 config.addConfigDirsToPythonPackagePath(appModules)
\r
169 _importers=list(pkgutil.iter_importers("appModules.__init__"))
\r
172 for processID, app in runningTable.iteritems():
\r
176 log.exception("Error terminating app module %r" % app)
\r
177 runningTable.clear()
\r
179 #base class for appModules
\r
180 class AppModule(baseObject.ScriptableObject):
\r
181 """Base app module.
\r
182 App modules provide specific support for a single application.
\r
183 Each app module should be a Python module in the appModules package named according to the executable it supports;
\r
184 e.g. explorer.py for the explorer.exe application.
\r
185 It should containa C{AppModule} class which inherits from this base class.
\r
186 App modules can implement and bind gestures to scripts.
\r
187 These bindings will only take effect while an object in the associated application has focus.
\r
188 See L{ScriptableObject} for details.
\r
189 App modules can also receive NVDAObject events for objects within the associated application.
\r
190 This is done by implementing methods called C{event_eventName},
\r
191 where C{eventName} is the name of the event; e.g. C{event_gainFocus}.
\r
192 These event methods take two arguments: the NVDAObject on which the event was fired
\r
193 and a callable taking no arguments which calls the next event handler.
\r
196 #: Whether NVDA should sleep while in this application (e.g. the application is self-voicing).
\r
197 #: If C{True}, all events and script requests inside this application are silently dropped.
\r
201 def __init__(self,processID,appName=None):
\r
202 super(AppModule,self).__init__()
\r
203 #: The ID of the process this appModule is for.
\r
205 self.processID=processID
\r
206 if appName is None:
\r
207 appName=getAppNameFromProcessID(processID)
\r
208 #: The application name.
\r
210 self.appName=appName
\r
211 self.processHandle=winKernel.openProcess(winKernel.SYNCHRONIZE|winKernel.PROCESS_QUERY_INFORMATION,False,processID)
\r
212 self.helperLocalBindingHandle=None
\r
213 self._inprocRegistrationHandle=None
\r
215 def __repr__(self):
\r
216 return "<%r (appName %r, process ID %s) at address %x>"%(self.appModuleName,self.appName,self.processID,id(self))
\r
218 def _get_appModuleName(self):
\r
219 return self.__class__.__module__.split('.')[-1]
\r
221 def _get_isAlive(self):
\r
222 return bool(winKernel.waitForSingleObject(self.processHandle,0))
\r
224 def terminate(self):
\r
225 """Terminate this app module.
\r
226 This is called to perform any clean up when this app module is being destroyed.
\r
227 Subclasses should call the superclass method first.
\r
229 winKernel.closeHandle(self.processHandle)
\r
230 if self._inprocRegistrationHandle:
\r
231 ctypes.windll.rpcrt4.RpcSsDestroyClientContext(ctypes.byref(self._inprocRegistrationHandle))
\r
232 if self.helperLocalBindingHandle:
\r
233 ctypes.windll.rpcrt4.RpcBindingFree(ctypes.byref(self.helperLocalBindingHandle))
\r
235 def chooseNVDAObjectOverlayClasses(self, obj, clsList):
\r
236 """Choose NVDAObject overlay classes for a given NVDAObject.
\r
237 This is called when an NVDAObject is being instantiated after L{NVDAObjects.NVDAObject.findOverlayClasses} has been called on the API-level class.
\r
238 This allows an AppModule to add or remove overlay classes.
\r
239 See L{NVDAObjects.NVDAObject.findOverlayClasses} for details about overlay classes.
\r
240 @param obj: The object being created.
\r
241 @type obj: L{NVDAObjects.NVDAObject}
\r
242 @param clsList: The list of classes, which will be modified by this method if appropriate.
\r
243 @type clsList: list of L{NVDAObjects.NVDAObject}
\r
246 def _get_is64BitProcess(self):
\r
247 """Whether the underlying process is a 64 bit process.
\r
250 if os.environ.get("PROCESSOR_ARCHITEW6432") != "AMD64":
\r
251 # This is 32 bit Windows.
\r
252 self.is64BitProcess = False
\r
254 res = ctypes.wintypes.BOOL()
\r
255 if ctypes.windll.kernel32.IsWow64Process(self.processHandle, ctypes.byref(res)) == 0:
\r
256 self.is64BitProcess = False
\r
258 self.is64BitProcess = not res
\r
259 return self.is64BitProcess
\r