OSDN Git Service

hu: renamed chapter0.{t2t -> t2tinc}.
[nvdajp/nvdajp.git] / source / appModuleHandler.py
1 #appModuleHandler.py\r
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
6 \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
10 """\r
11 \r
12 import itertools\r
13 import ctypes.wintypes\r
14 import os\r
15 import sys\r
16 import pkgutil\r
17 import baseObject\r
18 import globalVars\r
19 from logHandler import log\r
20 import NVDAHelper\r
21 import ui\r
22 import winUser\r
23 import winKernel\r
24 import config\r
25 import NVDAObjects #Catches errors before loading default appModule\r
26 import api\r
27 import appModules\r
28 \r
29 #Dictionary of processID:appModule paires used to hold the currently running modules\r
30 runningTable={}\r
31 #: The process ID of NVDA itself.\r
32 NVDAProcessID=None\r
33 _importers=None\r
34 \r
35 class processEntry32W(ctypes.Structure):\r
36         _fields_ = [\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
47         ]\r
48 \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
54         @type window: bool\r
55         @returns: application name\r
56         @rtype: unicode or str\r
57         """\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
64         appName = unicode()\r
65         while ContinueLoop:\r
66                 if FProcessEntry32.th32ProcessID == processID:\r
67                         appName = FProcessEntry32.szExeFile\r
68                         break\r
69                 ContinueLoop = winKernel.kernel32.Process32NextW(FSnapshotHandle, ctypes.byref(FProcessEntry32))\r
70         winKernel.kernel32.CloseHandle(FSnapshotHandle)\r
71         if not includeExt:\r
72                 appName=os.path.splitext(appName)[0].lower()\r
73         return appName\r
74 \r
75 def getAppModuleForNVDAObject(obj):\r
76         if not isinstance(obj,NVDAObjects.NVDAObject):\r
77                 return\r
78         return getAppModuleFromProcessID(obj.processID)\r
79 \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
85         @rtype: appModule \r
86         """\r
87         mod=runningTable.get(processID)\r
88         if not mod:\r
89                 appName=getAppNameFromProcessID(processID)\r
90                 mod=fetchAppModule(processID,appName)\r
91                 if not mod:\r
92                         raise RuntimeError("error fetching default appModule")\r
93                 runningTable[processID]=mod\r
94         return mod\r
95 \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
102         """\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
109                 try:\r
110                         deadMod.terminate()\r
111                 except:\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
119 \r
120 def doesAppModuleExist(name):\r
121         return any(importer.find_module("appModules.%s" % name) for importer in _importers)\r
122 \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
130         @rtype: AppModule\r
131         """  \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
136 \r
137         if doesAppModuleExist(modName):\r
138                 try:\r
139                         return __import__("appModules.%s" % modName, globals(), locals(), ("appModules",)).AppModule(processID, appName)\r
140                 except:\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
145 \r
146         # Use the base AppModule.\r
147         return AppModule(processID, appName)\r
148 \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
153         """\r
154         global appModules\r
155         terminate()\r
156         del appModules\r
157         mods=[k for k,v in sys.modules.iteritems() if k.startswith("appModules") and v is not None]\r
158         for mod in mods:\r
159                 del sys.modules[mod]\r
160         import appModules\r
161         initialize()\r
162 \r
163 def initialize():\r
164         """Initializes the appModule subsystem. \r
165         """\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
170 \r
171 def terminate():\r
172         for processID, app in runningTable.iteritems():\r
173                 try:\r
174                         app.terminate()\r
175                 except:\r
176                         log.exception("Error terminating app module %r" % app)\r
177         runningTable.clear()\r
178 \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
194         """\r
195 \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
198         #: @type: bool\r
199         sleepMode=False\r
200 \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
204                 #: @type: int\r
205                 self.processID=processID\r
206                 if appName is None:\r
207                         appName=getAppNameFromProcessID(processID)\r
208                 #: The application name.\r
209                 #: @type: str\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
214 \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
217 \r
218         def _get_appModuleName(self):\r
219                 return self.__class__.__module__.split('.')[-1]\r
220 \r
221         def _get_isAlive(self):\r
222                 return bool(winKernel.waitForSingleObject(self.processHandle,0))\r
223 \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
228                 """\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
234 \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
244                 """\r
245 \r
246         def _get_is64BitProcess(self):\r
247                 """Whether the underlying process is a 64 bit process.\r
248                 @rtype: bool\r
249                 """\r
250                 if os.environ.get("PROCESSOR_ARCHITEW6432") != "AMD64":\r
251                         # This is 32 bit Windows.\r
252                         self.is64BitProcess = False\r
253                         return 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
257                         return False\r
258                 self.is64BitProcess = not res\r
259                 return self.is64BitProcess\r