~stomato463/+junk/nvdajp

« back to all changes in this revision

Viewing changes to source/appModuleHandler.py

  • Committer: Masataka Shinke
  • Date: 2011-10-25 12:35:26 UTC
  • mfrom: (4185 jpmain)
  • mto: This revision was merged to the branch mainline in revision 4211.
  • Revision ID: mshinke@users.sourceforge.jp-20111025123526-ze527a2rl3z0g2ky
lp:~nishimotz/nvdajp/main : 4185 をマージ

Show diffs side-by-side

added added

removed removed

Lines of Context:
7
7
"""Manages appModules.
8
8
@var runningTable: a dictionary of the currently running appModules, using their application's main window handle as a key.
9
9
@type runningTable: dict
10
 
@var re_keyScript: a compiled regular expression that can grab a keyName and a script name from a line in a NVDA key map file (kbd file).
11
 
@type re_keyScript: regular expression
12
10
"""
13
11
 
14
12
import itertools
15
 
import re
16
13
import ctypes
17
14
import os
 
15
import sys
18
16
import pkgutil
19
17
import baseObject
20
18
import globalVars
26
24
import config
27
25
import NVDAObjects #Catches errors before loading default appModule
28
26
import api
29
 
import unicodedata
30
27
import appModules
31
28
 
32
29
#Dictionary of processID:appModule paires used to hold the currently running modules
35
32
NVDAProcessID=None
36
33
_importers=None
37
34
 
38
 
#regexp to collect the key and script from a line in a keyMap file 
39
 
re_keyScript=re.compile(r'^\s*(?P<key>[\S]+)\s*=\s*(?P<script>[\S]+)\s*$')
40
 
 
41
35
class processEntry32W(ctypes.Structure):
42
36
        _fields_ = [
43
37
                ("dwSize",ctypes.wintypes.DWORD),
76
70
        winKernel.kernel32.CloseHandle(FSnapshotHandle)
77
71
        if not includeExt:
78
72
                appName=os.path.splitext(appName)[0].lower()
79
 
        log.debug("appName: %s"%appName)
80
73
        return appName
81
74
 
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
85
 
        @type appName: str
86
 
        @returns: file path of key map file (.kbd file)
87
 
        @rtype: str
88
 
        """
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)) 
95
 
                        return fname
96
 
 
97
 
        if layout!='desktop':
98
 
                # Fall back to desktop.
99
 
                return getKeyMapFileName(appName,'desktop')
100
 
 
101
 
        log.debug("No keymapFile for %s"%appName)
102
 
        return None
103
 
 
104
75
def getAppModuleForNVDAObject(obj):
105
76
        if not isinstance(obj,NVDAObjects.NVDAObject):
106
77
                return
118
89
                appName=getAppNameFromProcessID(processID)
119
90
                mod=fetchAppModule(processID,appName)
120
91
                if not mod:
121
 
                        mod=fetchAppModule(processID,appName,useDefault=True)
122
 
                if not mod:
123
92
                        raise RuntimeError("error fetching default appModule")
124
93
                runningTable[processID]=mod
125
94
        return mod
126
95
 
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
131
100
        """
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()
 
107
                try:
 
108
                        deadMod.terminate()
 
109
                except:
 
110
                        log.exception("Error terminating app module %r" % deadMod)
 
111
        # This creates a new app module if necessary.
 
112
        getAppModuleFromProcessID(processID)
139
113
 
140
114
def doesAppModuleExist(name):
141
115
        return any(importer.find_module("appModules.%s" % name) for importer in _importers)
142
116
 
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
151
125
        """  
152
 
        friendlyAppName=appName
153
 
        if useDefault:
154
 
                appName='_default'
155
 
 
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.
158
 
        try:
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)
164
 
        if not exists:
165
 
                # It is not an error if the module doesn't exist.
166
 
                return None
167
 
 
168
 
        try:
169
 
                mod = __import__("appModules.%s" % appName, globals(), locals(), ("appModules",)).AppModule(processID, friendlyAppName)
170
 
        except:
171
 
                log.error("error in appModule %s"%appName, exc_info=True)
172
 
                ui.message(_("Error in appModule %s")%appName)
173
 
                return None
174
 
 
175
 
        mod.loadKeyMap()
176
 
        return mod
 
128
        # Python 2.x can't properly handle unicode module names, so convert them.
 
129
        modName = appName.encode("mbcs")
 
130
 
 
131
        if doesAppModuleExist(modName):
 
132
                try:
 
133
                        return __import__("appModules.%s" % modName, globals(), locals(), ("appModules",)).AppModule(processID, appName)
 
134
                except:
 
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)
 
138
 
 
139
        # Use the base AppModule.
 
140
        return AppModule(processID, appName)
 
141
 
 
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.
 
146
        """
 
147
        global appModules
 
148
        terminate()
 
149
        del appModules
 
150
        mods=[k for k,v in sys.modules.iteritems() if k.startswith("appModules") and v is not None]
 
151
        for mod in mods:
 
152
                del sys.modules[mod]
 
153
        import appModules
 
154
        initialize()
177
155
 
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__"))
 
163
 
 
164
def terminate():
 
165
        for processID, app in runningTable.iteritems():
 
166
                try:
 
167
                        app.terminate()
 
168
                except:
 
169
                        log.exception("Error terminating app module %r" % app)
 
170
        runningTable.clear()
185
171
 
186
172
#base class for appModules
187
173
class AppModule(baseObject.ScriptableObject):
188
 
        """AppModule base class
189
 
        @var appName: the application name
190
 
        @type appName: str
191
 
        @var processID: the ID of the process this appModule is for.
192
 
        @type processID: int
 
174
        """Base app module.
 
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.
193
187
        """
194
188
 
195
 
        selfVoicing=False #Set to true so all undefined events and script requests are silently dropped.
196
 
 
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.
 
191
        #: @type: bool
 
192
        sleepMode=False
198
193
 
199
194
        def __init__(self,processID,appName=None):
 
195
                super(AppModule,self).__init__()
 
196
                #: The ID of the process this appModule is for.
 
197
                #: @type: int
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.
 
203
                #: @type: str
204
204
                self.appName=appName
205
205
                self.processHandle=winKernel.openProcess(winKernel.SYNCHRONIZE,False,processID)
206
206
 
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))
209
209
 
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))
215
215
 
216
 
        def __del__(self):
 
216
        def terminate(self):
 
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.
 
220
                """
217
221
                winKernel.closeHandle(self.processHandle)
218
222
                NVDAHelper.localLib.destroyConnection(self.helperLocalBindingHandle)
219
223
 
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}
229
233
                """
230
 
 
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.
233
 
                """  
234
 
                if '_keyMap' in self.__dict__:
235
 
                        self._keyMap={}
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:
241
 
                                continue
242
 
                        keyMapFile=open(keyMapFileName,'r')
243
 
                        bindCount=0
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)
247
 
                                if m:
248
 
                                        try:
249
 
                                                self.bindKey_runtime(m.group('key'),m.group('script'))
250
 
                                                bindCount+=1
251
 
                                        except:
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))