76
70
winKernel.kernel32.CloseHandle(FSnapshotHandle)
78
72
appName=os.path.splitext(appName)[0].lower()
79
log.debug("appName: %s"%appName)
82
def getKeyMapFileName(appName,layout):
83
"""Finds the file path for the key map file, given the application name and keyboard layout.
84
@param appName: name of application
86
@returns: file path of key map file (.kbd file)
89
for dir in appModules.__path__+['.\\appModules']:
90
# Python's import paths aren't unicode, but we prefer to deal with unicode, so convert them.
91
dir = dir.decode("mbcs")
92
fname = os.path.join(dir, '%s_%s.kbd' % (appName, layout))
93
if os.path.isfile(fname):
94
log.debug("Found keymap file for %s at %s"%(appName,fname))
98
# Fall back to desktop.
99
return getKeyMapFileName(appName,'desktop')
101
log.debug("No keymapFile for %s"%appName)
104
75
def getAppModuleForNVDAObject(obj):
105
76
if not isinstance(obj,NVDAObjects.NVDAObject):
118
89
appName=getAppNameFromProcessID(processID)
119
90
mod=fetchAppModule(processID,appName)
121
mod=fetchAppModule(processID,appName,useDefault=True)
123
92
raise RuntimeError("error fetching default appModule")
124
93
runningTable[processID]=mod
127
96
def update(processID):
128
"""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.
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.
129
98
@param processID: the ID of the process.
130
99
@type processID: int
132
101
for deadMod in [mod for mod in runningTable.itervalues() if not mod.isAlive]:
133
102
log.debug("application %s closed"%deadMod.appName)
134
del runningTable[deadMod.processID];
103
del runningTable[deadMod.processID]
135
104
if deadMod in set(o.appModule for o in api.getFocusAncestors()+[api.getFocusObject()] if o and o.appModule):
136
105
if hasattr(deadMod,'event_appLoseFocus'):
137
deadMod.event_appLoseFocus();
138
getAppModuleFromProcessID(processID)
106
deadMod.event_appLoseFocus()
110
log.exception("Error terminating app module %r" % deadMod)
111
# This creates a new app module if necessary.
112
getAppModuleFromProcessID(processID)
140
114
def doesAppModuleExist(name):
141
115
return any(importer.find_module("appModules.%s" % name) for importer in _importers)
143
def fetchAppModule(processID,appName,useDefault=False):
117
def fetchAppModule(processID,appName):
144
118
"""Returns an appModule found in the appModules directory, for the given application name.
145
119
@param processID: process ID for it to be associated with
146
120
@type processID: integer
149
123
@returns: the appModule, or None if not found
150
124
@rtype: AppModule
152
friendlyAppName=appName
156
126
# First, check whether the module exists.
157
127
# 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.
159
exists = doesAppModuleExist(appName)
160
except UnicodeEncodeError:
161
# Since Python can't handle unicode characters in module names, we need to decompose unicode string and strip out accents.
162
appName = unicodedata.normalize("NFD", appName)
163
exists = doesAppModuleExist(appName)
165
# It is not an error if the module doesn't exist.
169
mod = __import__("appModules.%s" % appName, globals(), locals(), ("appModules",)).AppModule(processID, friendlyAppName)
171
log.error("error in appModule %s"%appName, exc_info=True)
172
ui.message(_("Error in appModule %s")%appName)
128
# Python 2.x can't properly handle unicode module names, so convert them.
129
modName = appName.encode("mbcs")
131
if doesAppModuleExist(modName):
133
return __import__("appModules.%s" % modName, globals(), locals(), ("appModules",)).AppModule(processID, appName)
135
log.error("error in appModule %r"%modName, exc_info=True)
136
# We can't present a message which isn't unicode, so use appName, not modName.
137
ui.message(_("Error in appModule %s")%appName)
139
# Use the base AppModule.
140
return AppModule(processID, appName)
142
def reloadAppModules():
143
"""Reloads running appModules.
144
especially, it clears the cache of running appModules and deletes them from sys.modules.
145
Each appModule will be reloaded immediately as a reaction on a first event coming from the process.
150
mods=[k for k,v in sys.modules.iteritems() if k.startswith("appModules") and v is not None]
178
156
def initialize():
179
157
"""Initializes the appModule subsystem.
181
159
global NVDAProcessID,_importers
182
160
NVDAProcessID=os.getpid()
183
161
config.addConfigDirsToPythonPackagePath(appModules)
184
_importers=list(pkgutil.iter_importers("appModules._default"))
162
_importers=list(pkgutil.iter_importers("appModules.__init__"))
165
for processID, app in runningTable.iteritems():
169
log.exception("Error terminating app module %r" % app)
186
172
#base class for appModules
187
173
class AppModule(baseObject.ScriptableObject):
188
"""AppModule base class
189
@var appName: the application name
191
@var processID: the ID of the process this appModule is for.
175
App modules provide specific support for a single application.
176
Each app module should be a Python module in the appModules package named according to the executable it supports;
177
e.g. explorer.py for the explorer.exe application.
178
It should containa C{AppModule} class which inherits from this base class.
179
App modules can implement and bind gestures to scripts.
180
These bindings will only take effect while an object in the associated application has focus.
181
See L{ScriptableObject} for details.
182
App modules can also receive NVDAObject events for objects within the associated application.
183
This is done by implementing methods called C{event_eventName},
184
where C{eventName} is the name of the event; e.g. C{event_gainFocus}.
185
These event methods take two arguments: the NVDAObject on which the event was fired
186
and a callable taking no arguments which calls the next event handler.
195
selfVoicing=False #Set to true so all undefined events and script requests are silently dropped.
197
_overlayClassCache={}
189
#: Whether NVDA should sleep while in this application (e.g. the application is self-voicing).
190
#: If C{True}, all events and script requests inside this application are silently dropped.
199
194
def __init__(self,processID,appName=None):
195
super(AppModule,self).__init__()
196
#: The ID of the process this appModule is for.
200
198
self.processID=processID
201
199
self.helperLocalBindingHandle=NVDAHelper.localLib.createConnection(processID)
202
200
if appName is None:
203
201
appName=getAppNameFromProcessID(processID)
202
#: The application name.
204
204
self.appName=appName
205
205
self.processHandle=winKernel.openProcess(winKernel.SYNCHRONIZE,False,processID)
207
207
def __repr__(self):
208
return "<%s (appName %s, process ID %s) at address %x>"%(self.appModuleName,self.appName,self.processID,id(self))
208
return "<%r (appName %r, process ID %s) at address %x>"%(self.appModuleName,self.appName,self.processID,id(self))
210
210
def _get_appModuleName(self):
211
211
return self.__class__.__module__.split('.')[-1]
213
213
def _get_isAlive(self):
214
214
return bool(winKernel.waitForSingleObject(self.processHandle,0))
217
"""Terminate this app module.
218
This is called to perform any clean up when this app module is being destroyed.
219
Subclasses should call the superclass method first.
217
221
winKernel.closeHandle(self.processHandle)
218
222
NVDAHelper.localLib.destroyConnection(self.helperLocalBindingHandle)
227
231
@param clsList: The list of classes, which will be modified by this method if appropriate.
228
232
@type clsList: list of L{NVDAObjects.NVDAObject}
231
def loadKeyMap(self):
232
"""Loads a key map in to this appModule . if the key map exists. It takes in to account what layout NVDA is currently set to.
234
if '_keyMap' in self.__dict__:
236
layout=config.conf["keyboard"]["keyboardLayout"]
237
for modClass in reversed(list(itertools.takewhile(lambda x: issubclass(x,AppModule) and x is not AppModule,self.__class__.__mro__))):
238
name=modClass.__module__.split('.')[-1]
239
keyMapFileName=getKeyMapFileName(name,layout)
240
if not keyMapFileName:
242
keyMapFile=open(keyMapFileName,'r')
244
#If the appModule already has a running keyMap, clear it
245
for line in (x for x in keyMapFile if not x.startswith('#') and not x.isspace()):
246
m=re_keyScript.match(line)
249
self.bindKey_runtime(m.group('key'),m.group('script'))
252
log.error("error binding %s to %s in appModule %s"%(m.group('script'),m.group('key'),self))
253
log.debug("added %s bindings to appModule %s from file %s"%(bindCount,self,keyMapFileName))