Updated accessible_output2. Add mac and a better linux support

This commit is contained in:
Manuel Cortez 2015-11-03 04:38:59 -06:00
parent 6a4a3cc94e
commit fe9f724673
22 changed files with 174 additions and 1261 deletions

@ -1,19 +1,23 @@
from __future__ import absolute_import
import ctypes import ctypes
import os import os
import types import types
from platform_utils import paths from platform_utils import paths
def load_library(libname): def load_library(libname, cdll=False):
if paths.is_frozen(): if paths.is_frozen():
libfile = os.path.join(paths.embedded_data_path(), 'accessible_output2', 'lib', libname) libfile = os.path.join(paths.embedded_data_path(), 'accessible_output2', 'lib', libname)
else: else:
libfile = os.path.join(paths.module_path(), 'lib', libname) libfile = os.path.join(paths.module_path(), 'lib', libname)
return ctypes.windll[libfile] if cdll:
return ctypes.cdll[libfile]
else:
return ctypes.windll[libfile]
def get_output_classes(): def get_output_classes():
import outputs from . import outputs
module_type = types.ModuleType module_type = types.ModuleType
classes = [m.output_class for m in outputs.__dict__.itervalues() if type(m) == module_type and hasattr(m, 'output_class')] classes = [m.output_class for m in outputs.__dict__.values() if type(m) == module_type and hasattr(m, 'output_class')]
return sorted(classes, key=lambda c: c.priority) return sorted(classes, key=lambda c: c.priority)
def find_datafiles(): def find_datafiles():

Binary file not shown.

Binary file not shown.

@ -1,16 +1,20 @@
from __future__ import absolute_import
import platform import platform
if platform.system() == 'Windows': if platform.system() == 'Windows':
import nvda from . import nvda
import jaws from . import jaws
import sapi5 from . import sapi5
import window_eyes from . import window_eyes
import system_access from . import system_access
import dolphin from . import dolphin
import pc_talker from . import pc_talker
#import sapi4 #import sapi4
elif platform.system() == "Darwin":
import voiceover
elif platform.system() == "Linux":
import speechDispatcher
import auto if platform.system() == 'Darwin':
from . import voiceover
from . import say
if platform.system() == 'Linux':
from . import e_speak
from . import auto

@ -1,24 +1,17 @@
import platform from __future__ import absolute_import
import accessible_output2 import accessible_output2
from base import Output, OutputError from .base import Output, OutputError
class Auto(Output): class Auto(Output):
def __init__(self): def __init__(self):
if platform.system() == "Darwin": output_classes = accessible_output2.get_output_classes()
import voiceover self.outputs = []
self.outputs = [voiceover.VoiceOver()] for output in output_classes:
elif platform.system() == "Linux": try:
import speechDispatcher self.outputs.append(output())
self.outputs = [speechDispatcher.SpeechDispatcher()] except OutputError:
elif platform.system() == "Windows": pass
output_classes = accessible_output2.get_output_classes()
self.outputs = []
for output in output_classes:
try:
self.outputs.append(output())
except OutputError:
pass
def get_first_available_output(self): def get_first_available_output(self):
for output in self.outputs: for output in self.outputs:
@ -40,3 +33,8 @@ class Auto(Output):
output = self.get_first_available_output() output = self.get_first_available_output()
if output: if output:
output.speak(*args, **kwargs) output.speak(*args, **kwargs)
def is_system_output(self):
output = self.get_first_available_output()
if output:
return output.is_system_output()

@ -5,27 +5,43 @@ class OutputError(Exception):
pass pass
class Output(object): class Output(object):
name = "Unnamed Output" #The name of this output name = "Unnamed Output"
lib32 = None #name of 32-bit lib lib32 = None
lib64 = None #name of 64-bit lib lib64 = None
priority = 100 #Where to sort in the list of available outputs for automaticly speaking argtypes = {}
cdll = False
priority = 100
system_output = False
def __init__(self): def __init__(self):
is_32bit = platform.architecture()[0] == "32bit" self.is_32bit = platform.architecture()[0] == "32bit"
if self.lib32 and is_32bit: if self.lib32 and self.is_32bit:
self.lib = load_library(self.lib32) self.lib = load_library(self.lib32, cdll=self.cdll)
elif self.lib64: elif self.lib64:
self.lib = load_library(self.lib64) self.lib = load_library(self.lib64, cdll=self.cdll)
else:
self.lib = None
if self.lib is not None:
for func in self.argtypes:
try:
getattr(self.lib, func).argtypes = self.argtypes[func]
except AttributeError:
pass
def output(self, text, **options): def output(self, text, **options):
output = False output = False
if hasattr(self, 'speak') and callable(self.speak): if self.speak(text, **options):
self.speak(text, **options)
output = True output = True
if hasattr(self, 'braille') and callable(self.braille): if self.braille(text, **options):
self.braille(text, **options)
output = True output = True
if not output: if not output:
raise RuntimeError("Output %r does not have any method defined to output" % self) raise RuntimeError("Output %r does not have any method defined to output" % self)
def is_system_output(self):
return self.system_output
def speak(self, **optiont):
return False
def braille(self, **options):
return False

@ -1,19 +1,25 @@
from __future__ import absolute_import
import os import os
import ctypes
from base import Output from .base import Output
class Dolphin (Output): class Dolphin (Output):
"""Supports dolphin products.""" """Supports dolphin products."""
name = 'Dolphin' name = 'Dolphin'
lib32 = 'dolapi.dll' lib32 = 'dolapi.dll'
argtypes = {
'DolAccess_Command': (ctypes.c_wchar_p, ctypes.c_int, ctypes.c_int),
'DolAccess_Action': (ctypes.c_int,),
}
def speak(self, text, interrupt=0): def speak(self, text, interrupt=0):
if interrupt: if interrupt:
self.silence() self.silence()
#If we don't call this, the API won't let us speak. #If we don't call this, the API won't let us speak.
if self.is_active(): if self.is_active():
self.lib.DolAccess_Command(unicode(text), (len(text)*2)+2, 1) self.lib.DolAccess_Command(text, (len(text)*2)+2, 1)
def silence(self): def silence(self):
self.lib.DolAccess_Action(141) self.lib.DolAccess_Action(141)

@ -0,0 +1,31 @@
from __future__ import absolute_import
from .base import Output
try:
import espeak.core
except:
raise RuntimeError("Cannot find espeak.core. Please install python-espeak")
class ESpeak(Output):
"""Speech output supporting ESpeak on Linux
Note this requires python-espeak to be installed
This can be done on Debian distros by using apt-get install python-espeak
Or through this tarball: https://launchpad.net/python-espeak
"""
name = "Linux ESpeak"
def is_active(self):
try:
import espeak.core
except:
return False
return True
def speak(self, text, interrupt = 0):
if interrupt:
self.silence()
espeak.core.synth(text)
def silence(self):
espeak.core.cancel()
output_class = ESpeak

@ -1,8 +1,9 @@
from __future__ import absolute_import
import win32gui import win32gui
from libloader.com import load_com from libloader.com import load_com
import pywintypes import pywintypes
from base import Output, OutputError from .base import Output, OutputError
class Jaws (Output): class Jaws (Output):
"""Output supporting the Jaws for Windows screen reader.""" """Output supporting the Jaws for Windows screen reader."""

@ -1,15 +1,21 @@
from __future__ import absolute_import
import os import os
import platform import platform
import ctypes
from platform_utils import paths from platform_utils import paths
from libloader import load_library from libloader import load_library
from base import Output from .base import Output
class NVDA(Output): class NVDA(Output):
"""Supports The NVDA screen reader""" """Supports The NVDA screen reader"""
name = "NVDA" name = "NVDA"
lib32 = 'nvdaControllerClient32.dll' lib32 = 'nvdaControllerClient32.dll'
lib64 = 'nvdaControllerClient64.dll' lib64 = 'nvdaControllerClient64.dll'
argtypes = {
'nvdaController_brailleMessage': (ctypes.c_wchar_p,),
'nvdaController_speakText': (ctypes.c_wchar_p,),
}
def is_active(self): def is_active(self):
try: try:
@ -18,12 +24,12 @@ class NVDA(Output):
return False return False
def braille(self, text, **options): def braille(self, text, **options):
self.lib.nvdaController_brailleMessage(unicode(text)) self.lib.nvdaController_brailleMessage(text)
def speak(self, text, interrupt=False): def speak(self, text, interrupt=False):
if interrupt: if interrupt:
self.silence() self.silence()
self.lib.nvdaController_speakText(unicode(text)) self.lib.nvdaController_speakText(text)
def silence(self): def silence(self):
self.lib.nvdaController_cancelSpeech() self.lib.nvdaController_cancelSpeech()

@ -1,14 +1,19 @@
from __future__ import absolute_import
import ctypes import ctypes
from base import Output from .base import Output
class PCTalker(Output): class PCTalker(Output):
lib32 = 'pctkusr.dll' lib32 = 'pctkusr.dll'
lib64 = 'pctkusr64.dll' lib64 = 'pctkusr64.dll'
cdll = True
argtypes = {
'PCTKPRead': (ctypes.c_char_p, ctypes.c_int, ctypes.c_int)
}
def speak(self, text, interrupt=False): def speak(self, text, interrupt=False):
if interrupt: if interrupt:
self.silence() self.silence()
self.lib.PCTKPRead(text.encode('cp932', 'replace')) self.lib.PCTKPRead(text.encode('cp932', 'replace'), 0, 1)
def silence(self): def silence(self):
self.lib.PCTKVReset() self.lib.PCTKVReset()

@ -1,5 +1,7 @@
from __future__ import absolute_import
from builtins import range
from libloader.com import load_com from libloader.com import load_com
from base import Output from .base import Output
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)

@ -1,12 +1,19 @@
# -*- coding: utf-8 -*- from __future__ import absolute_import
import config
from collections import OrderedDict from collections import OrderedDict
from libloader.com import load_com from libloader.com import load_com
from base import Output, OutputError from .base import Output, OutputError
import pywintypes import pywintypes
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
SVSFDefault = 0
SVSFlagsAsync = 1
SVSFPurgeBeforeSpeak = 2
SVSFIsFilename = 4
SVSFIsXML = 8
SVSFIsNotXML = 16
SVSFPersistXML = 32
class SAPI5(Output): class SAPI5(Output):
has_volume = True has_volume = True
has_rate = True has_rate = True
@ -19,9 +26,9 @@ class SAPI5(Output):
max_volume = 100 max_volume = 100
name = "sapi5" name = "sapi5"
priority = 101 priority = 101
system_output = True
def __init__(self): def __init__(self):
if config.app["app-settings"]["voice_enabled"] == False: raise OutputError
try: try:
self.object = load_com("SAPI.SPVoice") self.object = load_com("SAPI.SPVoice")
self._voices = self._available_voices() self._voices = self._available_voices()
@ -36,14 +43,14 @@ class SAPI5(Output):
return _voices return _voices
def list_voices(self): def list_voices(self):
return self.available_voices.keys() return list(self._voices.keys())
def get_voice(self): def get_voice(self):
return self.object.Voice.GetDescription() return self.object.Voice.GetDescription()
def set_voice(self, value): def set_voice(self, value):
log.debug("Setting SAPI5 voice to \"%s\"" % value) log.debug("Setting SAPI5 voice to \"%s\"" % value)
self.object.Voice = self.available_voices[value] self.object.Voice = self._voices[value]
# For some reason SAPI5 does not reset audio after changing the voice # For some reason SAPI5 does not reset audio after changing the voice
# By setting the audio device after changing voices seems to fix this # By setting the audio device after changing voices seems to fix this
# This was noted from information at: # This was noted from information at:
@ -75,10 +82,10 @@ class SAPI5(Output):
self.silence() self.silence()
# We need to do the pitch in XML here # We need to do the pitch in XML here
textOutput = "<pitch absmiddle=\"%d\">%s</pitch>" % (round(self._pitch), text.replace("<", "&lt;")) textOutput = "<pitch absmiddle=\"%d\">%s</pitch>" % (round(self._pitch), text.replace("<", "&lt;"))
self.object.Speak(textOutput, 1|8) self.object.Speak(textOutput, SVSFlagsAsync | SVSFIsXML)
def silence(self): def silence(self):
self.object.Speak("", 3) self.object.Speak("", SVSFlagsAsync | SVSFPurgeBeforeSpeak)
def is_active(self): def is_active(self):
if self.object: if self.object:

@ -0,0 +1,21 @@
from __future__ import absolute_import
import os
from .base import Output
class AppleSay(Output):
"""Speech output supporting the Apple Say subsystem."""
name = 'Apple Say'
def __init__(self, voice = 'Alex', rate = '300'):
self.voice = voice
self.rate = rate
super(AppleSay, self).__init__()
def is_active(self):
return not os.system('which say')
def speak(self, text, interrupt = 0):
if interrupt:
self.silence()
os.system('say -v %s -r %s "%s" &' % (self.voice, self.rate, text))
def silence(self):
os.system('killall say')
output_class = AppleSay

@ -1,29 +0,0 @@
from base import Output, OutputError
import atexit
import application
class SpeechDispatcher(Output):
"""Supports speech dispatcher on Linux.
Note that this module will use the configuration of speech dispatcher, the user will need to configure the voice, language, punctuation and rate before using this module.
"""
name = 'SpeechDispatcher'
def __init__(self, *args, **kwargs):
super(SpeechDispatcher, self).__init__(*args, **kwargs)
try:
import speechd
self.spd = speechd.SSIPClient(application.name)
except ImportError:
raise OutputError
atexit.register(self.on_exit_event)
def speak(self, text, interupt=False):
if interupt == True:
self.spd.cancel()
self.spd.speak(text)
def is_active(self):
return True
def on_exit_event(self):
self.spd.close()
del self.spd

@ -1,18 +0,0 @@
# Copyright (C) 2001, 2002 Brailcom, o.p.s.
#
# 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
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from .client import *

File diff suppressed because it is too large Load Diff

@ -1 +0,0 @@
SPD_SPAWN_CMD = "/usr/bin/speech-dispatcher"

@ -1,18 +1,24 @@
from base import Output from __future__ import absolute_import
import ctypes
from .base import Output
class SystemAccess (Output): class SystemAccess (Output):
"""Supports System Access and System Access Mobile""" """Supports System Access and System Access Mobile"""
name = "System Access" name = "System Access"
lib32 = 'saapi32.dll' lib32 = 'saapi32.dll'
argtypes = {
'SA_BrlShowTextW': (ctypes.c_wchar_p,),
'SA_SayW': (ctypes.c_wchar_p,),
}
priority = 99 priority = 99
def braille(self, text, **options): def braille(self, text, **options):
self.lib.SA_BrlShowTextW(unicode(text)) self.lib.SA_BrlShowTextW(text)
def speak(self, text, interrupt=False): def speak(self, text, interrupt=False):
if self.is_active(): if self.is_active():
self.dll.SA_SayW(unicode(text)) self.dll.SA_SayW(str(text))
def is_active(self): def is_active(self):
try: try:

@ -1,23 +1 @@
from base import Output, OutputError from __future__ import absolute_import from builtins import str import subprocess, os from .base import Output class VoiceOver(Output): """Speech output supporting the Apple VoiceOver screen reader.""" def runAppleScript(self, command, process = 'voiceover'): return subprocess.Popen(['osascript', '-e', 'tell application "' + process + '"\n' + command + '\nend tell'], stdout = subprocess.PIPE).communicate()[0] name = 'VoiceOver' def speak(self, text, interrupt=0): if interrupt: self.silence() os.system('osascript -e \"tell application \\\"voiceover\\\" to output \\\"%s\\\"\" &' % text) def silence (self): self.runAppleScript('output ""') def is_active(self): return self.runAppleScript('return (name of processes) contains "VoiceOver"', 'system events').startswith('true') and not self.runAppleScript('try\nreturn bounds of vo cursor\non error\nreturn false\nend try').startswith('false') output_class = VoiceOver
class VoiceOver (Output):
"""Supports the VoiceOver screenreader on the Mac.
Note that this will also output as a message to the braille display if VoiceOver is used with braille.
Calling this module could cause VoiceOver to be started.
"""
name = 'VoiceOver'
def __init__(self, *args, **kwargs):
super(VoiceOver, self).__init__(*args, **kwargs)
try:
from appscript import app
self.app = app('VoiceOver')
except ImportError:
raise OutputError
def speak(self, text, interupt=False):
self.app.output(text)
def is_active(self):
return True

@ -1,6 +1,7 @@
from __future__ import absolute_import
import win32gui import win32gui
from libloader.com import load_com from libloader.com import load_com
from base import Output, OutputError from .base import Output, OutputError
import pywintypes import pywintypes
class WindowEyes (Output): class WindowEyes (Output):