diff --git a/addon/globalPlugins/webAccess/__init__.py b/addon/globalPlugins/webAccess/__init__.py index 6a003aab..57717857 100644 --- a/addon/globalPlugins/webAccess/__init__.py +++ b/addon/globalPlugins/webAccess/__init__.py @@ -57,58 +57,49 @@ import os import re -import core import wx from NVDAObjects.IAccessible import IAccessible from NVDAObjects.IAccessible.MSHTML import MSHTML from NVDAObjects.IAccessible.ia2Web import Ia2Web from NVDAObjects.IAccessible.mozilla import Mozilla -from scriptHandler import script import addonHandler import api import baseObject from buildVersion import version_detailed as NVDA_VERSION import controlTypes +import core import eventHandler import globalPluginHandler import gui from logHandler import log -import scriptHandler -import speech +from scriptHandler import script import ui import virtualBuffers -from . import nodeHandler from . import overlay -from . import webAppLib -from .webAppLib import * -from .webAppScheduler import WebAppScheduler from . import webModuleHandler +from .webAppLib import playWebAccessSound, sleep +from .webAppScheduler import WebAppScheduler addonHandler.initTranslation() -TRACE = lambda *args, **kwargs: None # @UnusedVariable -#TRACE = log.info - SCRIPT_CATEGORY = "WebAccess" - -# -# defines sound directory -# - SOUND_DIRECTORY = os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "..", "sounds") +SUPPORTED_HOSTS = ['brave', 'firefox', 'chrome', 'java', 'iexplore', 'microsoftedgecp', 'msedge'] +TRACE = lambda *args, **kwargs: None # @UnusedVariable +#TRACE = log.info +# Currently dead code, but will likely be revived for issue #17. +activeWebModule = None -supportedWebAppHosts = ['brave', 'firefox', 'chrome', 'java', 'iexplore', 'microsoftedgecp', 'msedge'] - -activeWebApp = None webAccessEnabled = True scheduler = None + class DefaultBrowserScripts(baseObject.ScriptableObject): def __init__(self, warningMessage): @@ -119,7 +110,7 @@ def __init__(self, warningMessage): self.__class__.__gestures["kb:control+shift+%s" % character] = "notAssigned" def script_notAssigned(self, gesture): # @UnusedVariable - playWebAppSound("keyError") + playWebAccessSound("keyError") sleep(0.2) ui.message(self.warningMessage) @@ -225,7 +216,7 @@ def showWebAccessGui(self): # Translators: Error message when attempting to show the Web Access GUI. ui.message(_("The current object does not support Web Access.")) return - if not supportWebApp(obj): + if not canHaveWebAccessSupport(obj): # Translators: Error message when attempting to show the Web Access GUI. ui.message(_("You must be in a web browser to use Web Access.")) return @@ -306,23 +297,23 @@ def script_toggleWebAccessSupport(self, gesture): # @UnusedVariable ui.message(_("Web Access support enabled.")) # FR: u"Support Web Access activé." -def getActiveWebApp(): - global activeWebApp - return activeWebApp +def getActiveWebModule(): + global activeWebModule + return activeWebModule -def webAppLoseFocus(obj): - global activeWebApp - if activeWebApp is not None: - sendWebAppEvent('webApp_loseFocus', obj, activeWebApp) - activeWebApp = None - #log.info("Losing webApp focus for object:\n%s\n" % ("\n".join(obj.devInfo))) +def webModuleLoseFocus(obj): + global activeWebModule + if activeWebModule is not None: + sendWebModuleEvent('webModule_loseFocus', obj, activeWebModule) + activeWebModule = None + #log.info("Losing webModule focus for object:\n%s\n" % ("\n".join(obj.devInfo))) -def supportWebApp(obj): +def canHaveWebAccessSupport(obj): if obj is None or obj.appModule is None: - return None - return obj.appModule.appName in supportedWebAppHosts + return False + return obj.appModule.appName in SUPPORTED_HOSTS def VirtualBuffer_changeNotify(cls, rootDocHandle, rootID): @@ -337,10 +328,10 @@ def virtualBuffer_loadBufferDone(self, success=True): virtualBuffer_loadBufferDone.super.__get__(self)(success=success) -def sendWebAppEvent(eventName, obj, webApp=None): - if webApp is None: +def sendWebModuleEvent(eventName, obj, webModule=None): + if webModule is None: return - scheduler.send(eventName="webApp", name=eventName, obj=obj, webApp=webApp) + scheduler.send(eventName="webModule", name=eventName, obj=obj, webModule=webModule) def eventExecuter_gen(self, eventName, obj): @@ -355,18 +346,18 @@ def eventExecuter_gen(self, eventName, obj): yield func, (obj, self.next) # webApp level - if not supportWebApp(obj) and eventName in ["gainFocus"] and activeWebApp is not None: + if not canHaveWebAccessSupport(obj) and eventName in ["gainFocus"] and activeWebModule is not None: # log.info("Received event %s on a non-hosted object" % eventName) webAppLoseFocus(obj) else: - webApp = obj.webAccess.webModule if isinstance(obj, overlay.WebAccessObject) else None - if webApp is None: - if activeWebApp is not None and obj.hasFocus: + webModule = obj.webAccess.webModule if isinstance(obj, overlay.WebAccessObject) else None + if webModule is None: + if activeWebModule is not None and obj.hasFocus: #log.info("Disabling active webApp event %s" % eventName) webAppLoseFocus(obj) else: # log.info("Getting method %s -> %s" %(webApp.name, funcName)) - func = getattr(webApp, funcName, None) + func = getattr(webModule, funcName, None) if func: yield func,(obj, self.next) diff --git a/addon/globalPlugins/webAccess/gui/webModulesManager.py b/addon/globalPlugins/webAccess/gui/webModulesManager.py index 0f33d378..db31df95 100644 --- a/addon/globalPlugins/webAccess/gui/webModulesManager.py +++ b/addon/globalPlugins/webAccess/gui/webModulesManager.py @@ -20,8 +20,12 @@ # See the file COPYING.txt at the root of this distribution for more details. -__version__ = "2024.07.25" -__author__ = "Julien Cochuyt " +__version__ = "2024.08.21" +__authors__ = ( + "Julien Cochuyt ", + "André-Abush Clause ", + "Gatien Bouyssou ", +) import os @@ -229,9 +233,11 @@ def onModulesListItemSelected(self, evt): self.moduleEditButton.Enable(item is not None) self.rulesManagerButton.Enable( item is not None - and hasattr(item, "markerManager") - and item.markerManager.isReady - ) + # FIXME: This test never succeeds as a live WebModule is not + # taken from the context. + # TODO: Remove this restriction for issue #42 + and item.ruleManager.isReady + ) self.moduleDeleteButton.Enable(item is not None) def onRulesManager(self, evt=None): @@ -290,4 +296,4 @@ def Show(self, context): self.Fit() self.modulesList.SetFocus() self.CentreOnScreen() - return super().Show() \ No newline at end of file + return super().Show() diff --git a/addon/globalPlugins/webAccess/nodeHandler.py b/addon/globalPlugins/webAccess/nodeHandler.py index a5fb5c3e..79b6340d 100644 --- a/addon/globalPlugins/webAccess/nodeHandler.py +++ b/addon/globalPlugins/webAccess/nodeHandler.py @@ -19,7 +19,7 @@ # # See the file COPYING.txt at the root of this distribution for more details. -__version__ = "2024.08.19" +__version__ = "2024.08.21" __authors__ = ( "Frédéric Brugnot ", "Julien Cochuyt ", @@ -274,7 +274,7 @@ def update(self): self.identifier = time.time() # logTime ("Update node manager %d nodes" % len(fields), t) self.updating = False - # playWebAppSound ("tick") + # playWebAccessSound("tick") self._curNode = self.caretNode = self.getCaretNode() try: info = self.treeInterceptor.makeTextInfo(textInfos.POSITION_LAST) @@ -550,7 +550,7 @@ def isReady(self): def checkNodeManager(self): if self.nodeManager is None or not self.nodeManager.isReady: - playWebAppSound("keyError") + playWebAccessSound("keyError") return False else: return True diff --git a/addon/globalPlugins/webAccess/overlay.py b/addon/globalPlugins/webAccess/overlay.py index 5f0e3904..6d080be6 100644 --- a/addon/globalPlugins/webAccess/overlay.py +++ b/addon/globalPlugins/webAccess/overlay.py @@ -24,7 +24,11 @@ """ __version__ = "2024.07.24" -__author__ = "Julien Cochuyt " +__authors__ = ( + "Julien Cochuyt ", + "André-Abush Clause ", + "Gatien Bouyssou ", +) import weakref @@ -193,7 +197,7 @@ def treeInterceptor(self): @property def webModule(self): - from . import supportWebApp, webAccessEnabled + from . import canHaveWebAccessSupport, webAccessEnabled if not webAccessEnabled: return None ti = self.treeInterceptor @@ -203,7 +207,7 @@ def webModule(self): webModule = self._webModule if not webModule: obj = ti.rootNVDAObject - if not supportWebApp(obj): + if not canHaveWebAccessSupport(obj): return None from . import webModuleHandler try: diff --git a/addon/globalPlugins/webAccess/ruleHandler/__init__.py b/addon/globalPlugins/webAccess/ruleHandler/__init__.py index e079952e..5f68ea8a 100644 --- a/addon/globalPlugins/webAccess/ruleHandler/__init__.py +++ b/addon/globalPlugins/webAccess/ruleHandler/__init__.py @@ -60,7 +60,7 @@ from ..webAppLib import ( html, logTimeStart, - playWebAppSound, + playWebAccessSound, ) from .. import webAppScheduler from . import ruleTypes @@ -101,7 +101,7 @@ def showManager(context): webModule = context["webModule"] mgr = webModule.ruleManager if not mgr.isReady: - playWebAppSound("keyError") + playWebAccessSound("keyError") time.sleep(0.2) speech.cancelSpeech() ui.message(_("Not ready")) @@ -123,7 +123,7 @@ def __init__(self, warningMessage): self.__class__.__gestures["kb:control+shift+%s" % character] = "notAssigned" def script_notAssigned(self, gesture): - playWebAppSound("keyError") + playWebAccessSound("keyError") callLater(200, ui.message, self.warningMessage) __gestures = {} @@ -391,7 +391,7 @@ def update(self, nodeManager=None, force=False): self.zone = None #logTime("update marker", t) if self.isReady: - webAppScheduler.scheduler.send(eventName="markerManagerUpdated", markerManager=self) + webAppScheduler.scheduler.send(eventName="ruleManagerUpdated", ruleManager=self) self.timerCheckAutoAction = threading.Timer( 1, # Accepts floating point number for sub-second precision self.checkAutoAction @@ -410,7 +410,12 @@ def checkPageTitle(self): webModule = self.webModule if title != webModule.activePageTitle: webModule.activePageTitle = title - webAppScheduler.scheduler.send(eventName="webApp", name="webApp_pageChanged", obj=title, webApp=webModule) + webAppScheduler.scheduler.send( + eventName="webModule", + name="webModule_pageChanged", + obj=title, + webModule=webModule + ) return True return False @@ -435,7 +440,7 @@ def checkAutoAction(self): if (lastText is None or text != lastText): self.triggeredIdentifiers[controlIdentifier] = text if autoActionName == "speak": - playWebAppSound("errorMessage") + playWebAccessSound("errorMessage") elif autoActionName == "moveto": if lastText is None: # only if it's a new identifier @@ -642,7 +647,7 @@ def quickNav( quiet=False, ): if not self.isReady: - playWebAppSound("keyError") + playWebAccessSound("keyError") ui.message(_("Not ready")) return None @@ -651,7 +656,7 @@ def quickNav( position = html.getCaretInfo() if position is None: - playWebAppSound("keyError") + playWebAccessSound("keyError") ui.message(_("Not ready")) return None @@ -669,11 +674,11 @@ def quickNav( ) if result: if not relative: - playWebAppSound("loop") + playWebAccessSound("loop") time.sleep(0.2) break else: - playWebAppSound("keyError") + playWebAccessSound("keyError") time.sleep(0.2) if quiet: return False diff --git a/addon/globalPlugins/webAccess/webAppLib/__init__.py b/addon/globalPlugins/webAccess/webAppLib/__init__.py index 1b3b34f3..57f5760f 100644 --- a/addon/globalPlugins/webAccess/webAppLib/__init__.py +++ b/addon/globalPlugins/webAccess/webAppLib/__init__.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # This file is part of Web Access for NVDA. -# Copyright (C) 2015-2019 Accessolutions (http://accessolutions.fr) +# Copyright (C) 2015-2024 Accessolutions (http://accessolutions.fr) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,8 +22,12 @@ # Get ready for Python 3 -__version__ = "2019.07.17" -__author__ = "Frédéric Brugnot " +__version__ = "2024.08.21" +__authors__ = ( + "Frédéric Brugnot ", + "Julien Cochuyt ", + "André-Abush Clause ", +) import os @@ -63,12 +67,12 @@ def speechOn(delay=0): api.processPendingEvents () speech.setSpeechMode(speech.SpeechMode.talk) -def playWebAppSound (name): +def playWebAccessSound(name): from ... import webAccess try: playSound(os.path.join(webAccess.SOUND_DIRECTORY, "%s.wav" % name)) - except: - pass + except Exception: + log.exception() def playSound(sound): sound = os.path.abspath(os.path.join(os.path.dirname(__file__), sound)) diff --git a/addon/globalPlugins/webAccess/webAppScheduler.py b/addon/globalPlugins/webAccess/webAppScheduler.py index 38949f48..a5588748 100644 --- a/addon/globalPlugins/webAccess/webAppScheduler.py +++ b/addon/globalPlugins/webAccess/webAppScheduler.py @@ -86,7 +86,7 @@ def run(self): else: log.info("event %s is not found" % eventName) - log.info ("webAppScheduler stopped !") + log.info("webAppScheduler stopped!") def send(self, **kwargs): self.queue.put(kwargs) @@ -109,10 +109,10 @@ def event_timeout(self): def fakeNext(self = None): return True - def event_webApp(self, name=None, obj=None, webApp=None): + def event_webModule(self, name=None, obj=None, webModule=None): funcName = 'event_%s' % name - #log.info("webApp %s will handle the event %s" % (webApp.name, name)) - func = getattr(webApp, funcName, None) + #log.info("webModule %s will handle the event %s" % (webModule.name, name)) + func = getattr(webModule, funcName, None) if func: func(obj, self.fakeNext) @@ -154,10 +154,9 @@ def event_nodeManagerUpdated(self, nodeManager): return nodeManager.treeInterceptor.webAccess.ruleManager.update(nodeManager) - def event_markerManagerUpdated(self, markerManager): + def event_ruleManagerUpdated(self, ruleManager): # Doesn't work outside of the main thread for Google Chrome 83 - wx.CallAfter(markerManager.checkPageTitle) - # markerManager.checkAutoAction() + wx.CallAfter(ruleManager.checkPageTitle) def event_gainFocus(self, obj): pass @@ -170,7 +169,7 @@ def onNodeMoveto(self, node, reason): if webModule is not None: scheduler.send( - eventName="webApp", + eventName="webModule", name='node_gainFocus', - obj=node, webApp=webModule - ) + obj=node, webModule=webModule + ) diff --git a/addon/globalPlugins/webAccess/webModuleHandler/webModule.py b/addon/globalPlugins/webAccess/webModuleHandler/webModule.py index 015e368c..14ef5ee0 100644 --- a/addon/globalPlugins/webAccess/webModuleHandler/webModule.py +++ b/addon/globalPlugins/webAccess/webModuleHandler/webModule.py @@ -49,7 +49,7 @@ import ui from ..lib.markdown2 import markdown from ..lib.packaging import version -from ..webAppLib import playWebAppSound +from ..webAppLib import playWebAccessSound from .. import ruleHandler class InvalidApiVersion(version.InvalidVersion): @@ -281,15 +281,17 @@ def _setLayeredProperty(self, name, value): layer.dirty = True data["overrides"][name] = overridden - def event_webApp_pageChanged(self, pageTitle, nextHandler): + def event_webModule_pageChanged(self, pageTitle, nextHandler): speech.cancelSpeech() - playWebAppSound("pageChanged") + playWebAccessSound("pageChanged") speech.speakMessage(pageTitle) - def event_webApp_gainFocus(self, obj, nextHandler): + # Currently dead code, but will likely be revived for issue #17 + def event_webModule_gainFocus(self, obj, nextHandler): if obj.role not in [controlTypes.ROLE_DOCUMENT, controlTypes.ROLE_FRAME, controlTypes.ROLE_INTERNALFRAME]: nextHandler() + # Currently dead code, but will likely be revived for issue #17 def event_focusEntered(self, obj, nextHandler): if obj.role != controlTypes.ROLE_DOCUMENT: nextHandler() @@ -297,8 +299,8 @@ def event_focusEntered(self, obj, nextHandler): def event_gainFocus(self, obj, nextHandler): nextHandler() - def event_webApp_loseFocus(self, obj, nextHandler): - playWebAppSound("webAppLoseFocus") + def event_webModule_loseFocus(self, obj, nextHandler): + playWebAccessSound("webAppLoseFocus") nextHandler() def script_contextualHelp(self, gesture):