1# Orca 2# 3# Copyright 2011. Orca Team. 4# Author: Joanmarie Diggs <joanmarie.diggs@gmail.com> 5# 6# This library is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# This library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with this library; if not, write to the 18# Free Software Foundation, Inc., Franklin Street, Fifth Floor, 19# Boston MA 02110-1301 USA. 20 21__id__ = "$Id$" 22__version__ = "$Revision$" 23__date__ = "$Date$" 24__copyright__ = "Copyright (c) 2011. Orca Team." 25__license__ = "LGPL" 26 27import importlib 28import pyatspi 29 30from . import debug 31from . import orca_state 32from .scripts import apps, toolkits 33 34class ScriptManager: 35 36 def __init__(self): 37 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Initializing', True) 38 self.appScripts = {} 39 self.toolkitScripts = {} 40 self.customScripts = {} 41 self._appModules = apps.__all__ 42 self._toolkitModules = toolkits.__all__ 43 self._defaultScript = None 44 self._scriptPackages = \ 45 ["orca-scripts", 46 "orca.scripts", 47 "orca.scripts.apps", 48 "orca.scripts.toolkits"] 49 self._appNames = \ 50 {'Firefox': 'Mozilla', 51 'Icedove': 'Thunderbird', 52 'Nereid': 'Banshee', 53 'empathy-chat': 'empathy', 54 'gnome-calculator': 'gcalctool', 55 'gtk-window-decorator': 'switcher', 56 'marco': 'switcher', 57 'mate-notification-daemon': 'notification-daemon', 58 'metacity': 'switcher', 59 'pluma': 'gedit', 60 } 61 62 self.setActiveScript(None, "__init__") 63 self._desktop = pyatspi.Registry.getDesktop(0) 64 self._active = False 65 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Initialized', True) 66 67 def activate(self): 68 """Called when this script manager is activated.""" 69 70 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Activating', True) 71 self._defaultScript = self.getScript(None) 72 self._defaultScript.registerEventListeners() 73 self.setActiveScript(self._defaultScript, "activate") 74 self._active = True 75 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Activated', True) 76 77 def deactivate(self): 78 """Called when this script manager is deactivated.""" 79 80 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Dectivating', True) 81 if self._defaultScript: 82 self._defaultScript.deregisterEventListeners() 83 self._defaultScript = None 84 self.setActiveScript(None, "deactivate") 85 self.appScripts = {} 86 self.toolkitScripts = {} 87 self.customScripts = {} 88 self._active = False 89 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Deactivated', True) 90 91 def getModuleName(self, app): 92 """Returns the module name of the script to use for application app.""" 93 94 try: 95 appAndNameExist = app is not None and app.name != '' 96 except (LookupError, RuntimeError): 97 appAndNameExist = False 98 msg = 'ERROR: %s no longer exists' % app 99 debug.println(debug.LEVEL_INFO, msg, True) 100 101 if not appAndNameExist: 102 return None 103 104 name = app.name 105 altNames = list(self._appNames.keys()) 106 if name.endswith(".py") or name.endswith(".bin"): 107 name = name.split('.')[0] 108 elif name.startswith("org.") or name.startswith("com."): 109 name = name.split('.')[-1] 110 111 names = [n for n in altNames if n.lower() == name.lower()] 112 if names: 113 name = self._appNames.get(names[0]) 114 else: 115 for nameList in (self._appModules, self._toolkitModules): 116 names = [n for n in nameList if n.lower() == name.lower()] 117 if names: 118 name = names[0] 119 break 120 121 msg = 'SCRIPT MANAGER: mapped %s to %s' % (app.name, name) 122 debug.println(debug.LEVEL_INFO, msg, True) 123 return name 124 125 def _toolkitForObject(self, obj): 126 """Returns the name of the toolkit associated with obj.""" 127 128 name = '' 129 if obj: 130 try: 131 attributes = obj.getAttributes() 132 except (LookupError, RuntimeError): 133 pass 134 else: 135 attrs = dict([attr.split(':', 1) for attr in attributes]) 136 name = attrs.get('toolkit', '') 137 138 return name 139 140 def _scriptForRole(self, obj): 141 try: 142 role = obj.getRole() 143 except: 144 return '' 145 146 if role == pyatspi.ROLE_TERMINAL: 147 return 'terminal' 148 149 return '' 150 151 def _newNamedScript(self, app, name): 152 """Attempts to locate and load the named module. If successful, returns 153 a script based on this module.""" 154 155 if not (app and name): 156 return None 157 158 script = None 159 for package in self._scriptPackages: 160 moduleName = '.'.join((package, name)) 161 try: 162 module = importlib.import_module(moduleName) 163 except ImportError: 164 continue 165 except OSError: 166 debug.examineProcesses() 167 168 debug.println(debug.LEVEL_INFO, 'SCRIPT MANAGER: Found %s' % moduleName, True) 169 try: 170 if hasattr(module, 'getScript'): 171 script = module.getScript(app) 172 else: 173 script = module.Script(app) 174 break 175 except: 176 debug.printException(debug.LEVEL_INFO) 177 msg = 'ERROR: Could not load %s' % moduleName 178 debug.println(debug.LEVEL_INFO, msg, True) 179 180 return script 181 182 def _createScript(self, app, obj=None): 183 """For the given application, create a new script instance.""" 184 185 moduleName = self.getModuleName(app) 186 script = self._newNamedScript(app, moduleName) 187 if script: 188 return script 189 190 objToolkit = self._toolkitForObject(obj) 191 script = self._newNamedScript(app, objToolkit) 192 if script: 193 return script 194 195 try: 196 toolkitName = getattr(app, "toolkitName", None) 197 except (LookupError, RuntimeError): 198 msg = 'ERROR: Exception getting toolkitName for: %s' % app 199 debug.println(debug.LEVEL_INFO, msg, True) 200 else: 201 if app and toolkitName: 202 script = self._newNamedScript(app, toolkitName) 203 204 if not script: 205 script = self.getDefaultScript(app) 206 msg = 'SCRIPT MANAGER: Default script created' 207 debug.println(debug.LEVEL_INFO, msg, True) 208 209 return script 210 211 def getDefaultScript(self, app=None): 212 if not app and self._defaultScript: 213 return self._defaultScript 214 215 from .scripts import default 216 script = default.Script(app) 217 218 if not app: 219 self._defaultScript = script 220 221 return script 222 223 def sanityCheckScript(self, script): 224 if not self._active: 225 return script 226 227 try: 228 appInDesktop = script.app in self._desktop 229 except: 230 appInDesktop = False 231 232 if appInDesktop: 233 return script 234 235 msg = "WARNING: %s is not in the registry's desktop" % script.app 236 debug.println(debug.LEVEL_INFO, msg, True) 237 238 newScript = self._getScriptForAppReplicant(script.app) 239 if newScript: 240 msg = "SCRIPT MANAGER: Script for app replicant found: %s" % newScript 241 debug.println(debug.LEVEL_INFO, msg, True) 242 return newScript 243 244 msg = "WARNING: Failed to get a replacement script for %s" % script.app 245 debug.println(debug.LEVEL_INFO, msg, True) 246 return script 247 248 def getScript(self, app, obj=None, sanityCheck=False): 249 """Get a script for an app (and make it if necessary). This is used 250 instead of a simple calls to Script's constructor. 251 252 Arguments: 253 - app: the Python app 254 255 Returns an instance of a Script. 256 """ 257 258 customScript = None 259 appScript = None 260 toolkitScript = None 261 262 roleName = self._scriptForRole(obj) 263 if roleName: 264 customScripts = self.customScripts.get(app, {}) 265 customScript = customScripts.get(roleName) 266 if not customScript: 267 customScript = self._newNamedScript(app, roleName) 268 customScripts[roleName] = customScript 269 self.customScripts[app] = customScripts 270 271 objToolkit = self._toolkitForObject(obj) 272 if objToolkit: 273 toolkitScripts = self.toolkitScripts.get(app, {}) 274 toolkitScript = toolkitScripts.get(objToolkit) 275 if not toolkitScript: 276 toolkitScript = self._createScript(app, obj) 277 toolkitScripts[objToolkit] = toolkitScript 278 self.toolkitScripts[app] = toolkitScripts 279 280 try: 281 if not app: 282 appScript = self.getDefaultScript() 283 elif app in self.appScripts: 284 appScript = self.appScripts[app] 285 else: 286 appScript = self._createScript(app, None) 287 self.appScripts[app] = appScript 288 except: 289 msg = 'WARNING: Exception getting app script.' 290 debug.printException(debug.LEVEL_ALL) 291 debug.println(debug.LEVEL_WARNING, msg, True) 292 appScript = self.getDefaultScript() 293 294 if customScript: 295 return customScript 296 297 try: 298 role = obj.getRole() 299 except: 300 forceAppScript = False 301 else: 302 forceAppScript = role in [pyatspi.ROLE_FRAME, pyatspi.ROLE_STATUS_BAR] 303 304 # Only defer to the toolkit script for this object if the app script 305 # is based on a different toolkit. 306 if toolkitScript and not forceAppScript \ 307 and not issubclass(appScript.__class__, toolkitScript.__class__): 308 return toolkitScript 309 310 if app and sanityCheck: 311 appScript = self.sanityCheckScript(appScript) 312 313 return appScript 314 315 def setActiveScript(self, newScript, reason=None): 316 """Set the new active script. 317 318 Arguments: 319 - newScript: the new script to be made active. 320 """ 321 322 if orca_state.activeScript == newScript: 323 return 324 325 if orca_state.activeScript: 326 orca_state.activeScript.deactivate() 327 328 orca_state.activeScript = newScript 329 if not newScript: 330 return 331 332 newScript.activate() 333 msg = 'SCRIPT MANAGER: Setting active script: %s (reason=%s)' % \ 334 (newScript.name, reason) 335 debug.println(debug.LEVEL_INFO, msg, True) 336 337 def _getScriptForAppReplicant(self, app): 338 if not self._active: 339 return None 340 341 def _pid(app): 342 try: 343 return app.get_process_id() 344 except: 345 msg = "SCRIPT MANAGER: Exception getting pid for %s" % app 346 debug.println(debug.LEVEL_INFO, msg, True) 347 return -1 348 349 def _isValidApp(app): 350 try: 351 return a in self._desktop 352 except: 353 msg = "SCRIPT MANAGER: Exception seeing if %s is in desktop" % app 354 debug.println(debug.LEVEL_INFO, msg, True) 355 return False 356 357 pid = _pid(app) 358 if pid == -1: 359 return None 360 361 items = self.appScripts.items() 362 for a, script in items: 363 if a != app and _pid(a) == pid and _isValidApp(a): 364 return script 365 366 return None 367 368 def reclaimScripts(self): 369 """Compares the list of known scripts to the list of known apps, 370 deleting any scripts as necessary. 371 """ 372 373 appList = list(self.appScripts.keys()) 374 try: 375 appList = [a for a in appList if a is not None and a not in self._desktop] 376 except: 377 debug.printException(debug.LEVEL_FINEST) 378 return 379 380 for app in appList: 381 msg = "SCRIPT MANAGER: %s is no longer in registry's desktop" % app 382 debug.println(debug.LEVEL_INFO, msg, True) 383 384 appScript = self.appScripts.pop(app) 385 newScript = self._getScriptForAppReplicant(app) 386 if newScript: 387 msg = "SCRIPT MANAGER: Script for app replicant found: %s" % newScript 388 debug.println(debug.LEVEL_INFO, msg, True) 389 390 attrs = appScript.getTransferableAttributes() 391 for attr, value in attrs.items(): 392 msg = "SCRIPT MANAGER: Setting %s to %s" % (attr, value) 393 debug.println(debug.LEVEL_INFO, msg, True) 394 setattr(newScript, attr, value) 395 396 del appScript 397 398 try: 399 toolkitScripts = self.toolkitScripts.pop(app) 400 except KeyError: 401 pass 402 else: 403 for toolkitScript in toolkitScripts.values(): 404 del toolkitScript 405 406 try: 407 customScripts = self.customScripts.pop(app) 408 except KeyError: 409 pass 410 else: 411 for customScript in customScripts.values(): 412 del customScript 413 414 del app 415 416_manager = ScriptManager() 417 418def getManager(): 419 return _manager 420