vlibras.py 8.31 KB
# -*- coding: utf-8 -*-

# NVDA imports
import api
import globalPluginHandler
import logHandler
import speech
import textInfos
import ui
import controlTypes

# python imports
import select
import socket
import threading
import time


class Cli2Srvnt(threading.Thread):

    def __init__(self, host, port, timeout=1):
        threading.Thread.__init__(self)
        self.__connected = False
        self.__sock = None
        self.__host = host
        self.__port = port
        self.__timeout = timeout
        self.__addr = (self.__host, self.__port)

    def __create(self):
        self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    def run(self):
        exceptional = []
        readable = []
        writable = []
        self.__create()
        while True:
            try:
                if (readable == [] and writable == [] and exceptional == []):
                    if (self.__sock.connect_ex((self.__host, self.__port)) == 0):
                        # ui.message("VLibras Plugin CONECTADO")
                        self.__connected = True
                try:
                    readable, writable, exceptional = select.select(
                        [self.__sock], [self.__sock], [], self.__timeout
                    )
                except:
                    pass
                if (readable != []):
                    self.__connected = False
                    try:
                        self.__sock.shutdown(1)
                        self.__sock.close()
                        self.__create()
                    except:
                        pass
                elif (writable != []):
                    self.__connected = True
            except:
                pass
            time.sleep(1)

    def send(self, msg):
        try:
            if (self.__connected and self.__sock is not None):
                return self.__sock.sendto(
                    msg.encode("utf-8", "ignore"),
                    self.__addr
                )
        except Exception as e:
            logHandler.log.error(u"%s\n%s" % (e.message, e.args))
        return False


class GlobalPlugin(globalPluginHandler.GlobalPlugin):

    __captureMouse = False
    __silentMode = False

    # keyboard shortcuts
    __kbToggleSilentMode = "NVDA+shift+v"
    __kbToggleCaptureMouseMode = "NVDA+shift+m"

    # keyboard gestures
    __gestures = {
        "kb:" + __kbToggleSilentMode: "toggleSilentMode",
        "kb:" + __kbToggleCaptureMouseMode: "toggleCaptureMouseMode"
    }

    # message to speak on enable or disable
    def toggleMessage(self, msg, enabled, kb):
        message = "VLibras Plugin: %s %s. Para %s use %s"
        if (enabled):
            message = message % (msg, "ativado", "desativar", kb)
        else:
            message = message % (msg, "desativado", "ativar", kb)
        return ui.message(message)

    # toggle silent mode by user (default is False)
    def script_toggleSilentMode(self, obj):
        self.__silentMode = not self.__silentMode
        return self.toggleMessage(
            "Modo silencioso",
            self.__silentMode,
            self.__kbToggleSilentMode
        )

    # toggle capture mouse mode by user (default is False)
    def script_toggleCaptureMouseMode(self, obj):
        self.__captureMouse = not self.__captureMouse
        return self.toggleMessage(
            "Modo captura de mouse",
            self.__captureMouse,
            self.__kbToggleCaptureMouseMode
        )

    # constructor
    def __init__(self):
        super(self.__class__, self).__init__()
        self.__lastTextSend = ""
        self.__mutex = threading.Lock()
        self.__client = Cli2Srvnt("127.0.0.1", 10001)
        self.__client.start()

    # destructor
    def __exit__(self, exc_type, exc_value, traceback):
        self.__client.join()

    # cancel speech if silent mode is true
    def handleSpeech(self, obj, nextHandler):
        try:
            if (self.__silentMode):
                return speech.cancelSpeech()
        except Exception as e:
            logHandler.log.error(u"%s\n%s" % (e.message, e.args))
        return nextHandler()

    # get the current text in focus and send to vlibras
    def processFocus(self, obj, nextHandler):
        try:
            if (obj.role == controlTypes.ROLE_UNKNOWN):
                return self.handleSpeech(obj, nextHandler)
            info = api.getReviewPosition()
            if (info is None):
                return self.handleSpeech(obj, nextHandler)
            info.expand(textInfos.UNIT_READINGCHUNK)
            textToSend = info._get_text()
            if textToSend is None:
                return self.handleSpeech(obj, nextHandler)
            textToSend = textToSend.strip(" \t\r\n")
            # mutex to protect self.__lastTextSend
            self.__mutex.acquire()
            try:
                if (textToSend and textToSend != self.__lastTextSend):
                    self.__lastTextSend = textToSend
                    if (self.__client.send(textToSend)):
                        logHandler.log.debugWarning(">> %s\n" % (textToSend))
            finally:
                self.__mutex.release()
        except Exception as e:
            logHandler.log.error(u"%s\n%s" % (e.message, e.args))
        return self.handleSpeech(obj, nextHandler)

    # get the current text in mouse focus and send to vlibras
    def processMouse(self, obj, nextHandler, x, y):
        try:
            if (not self.__captureMouse):
                return self.handleSpeech(obj, nextHandler)
            if (obj.role == controlTypes.ROLE_UNKNOWN):
                return self.handleSpeech(obj, nextHandler)
            info = api.getReviewPosition()
            if (info is None):
                return self.handleSpeech(obj, nextHandler)
            info.expand(info.unit_mouseChunk)
            textToSend = info._get_text()
            if textToSend is None:
                return self.handleSpeech(obj, nextHandler)
            textToSend = textToSend.strip(" \t\r\n")
            # mutex to protect self.__lastTextSend
            self.__mutex.acquire()
            try:
                if (textToSend and textToSend != self.__lastTextSend):
                    self.__lastTextSend = textToSend
                    if (self.__client.send(textToSend)):
                        logHandler.log.debugWarning(">> %s\n" % (textToSend))
            finally:
                self.__mutex.release()
        except Exception as e:
            logHandler.log.error(u"%s\n%s" % (e.message, e.args))
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_typedCharacter(self, obj, nextHandler, ch):
        return self.handleSpeech(obj, nextHandler)

    # Override -> processMouse
    def event_mouseMove(self, obj, nextHandler, x, y):
        return self.processMouse(obj, nextHandler, x, y)

    # Override
    def event_stateChange(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_focusEntered(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override -> processFocus
    def event_gainFocus(self, obj, nextHandler):
        return self.processFocus(obj, nextHandler)

    # Override
    def event_foreground(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_becomeNavigatorObject(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_valueChange(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_nameChange(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_descriptionChange(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_caret(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_loseFocus(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)

    # Override
    def event_locationChange(self, obj, nextHandler):
        return self.handleSpeech(obj, nextHandler)