Re: [orca-list] Patch: Experimental eSpeak support using python-espeak



Hi,
I also noticed the significant volume decreese.
It's faster, cause the max rate is set to 450wpm, and in
speech-dispatcher's ESpeak driver the default max rate is 390wpm.
I'm using ESpeak at 590wpm for some time, too.

I also noticed another thing - it sounds like some pauses have been
removed between the read text and system messages, e.g. if you walk
between the items in Thunderbird's Folders Pain (which is a treeview),
you'll notice that there is no pause between announcing levels (when
you move from level one to two, two to three etc.).
It's actually nice and I would like to have an option to keep this
behaviour.

-- 
Best wishes,
Zahari

На 24.08.2015 в 02:57, Storm Dragon написа:
Howdy, First of all, thanks a ton for doing this. This is something
I, and a few others, have wanted for a long time. I installed it
using your instructions, and it works as advertised. The only
things I have noticed are: When you change the voice, en-us in my
case, if you hit apply, then change something else, it doesn't
remember the voice if you hit apply again and goes back to the
default. The espeak volume doesn't seem to match the orca settings
volume. If it is set at 10.0, that should be the equivalant of
espeak -a 200, but it sounds closer to the default espeak volume,
or about half that (espeak -a 100). When you change the speech
rate, the voice doesn't accurately speed up with the actual end
result. I was worried that the speech was going to be a bit slow
for my tastes, but when I hit reply it was actually quite a bit
faster than I usually use. I was very happy to get the fast speech,
and I know of at least one person who listens to espeak much faster
than I do, so they will be thrilled too. Thanks again for doing
this. I can't wait for when this is included into orca propper.
From the test run, it seems like that will be very soon. Thanks 
Storm On Mon, Aug 24, 2015 at 12:25:18AM +0200, Peter Vágner
wrote:
Hello, Today I was playing with orca a bit more than usual. The
result is that I have implemented an alternative speech server
subclass. I have taken speechdispatcherfactory module and I have
replaced python-speechd API with python-espeak one accross the
whole module. So by doing this I think I have created
experimental direct eSpeak support for orca. It requires
python-espeak to be installed. I tryed to do this because a while
ago we were discussing this approach here on the list. You guys
were hoping for additional responsiveness increase and easier way
on how to add eSpeak specific features like variants support into
orca. Also I have come up with another little advantage as I have
been thinking about this more. By default eSpeak is built with
both pulseaudio and portaudio sound outputs. It first tryes to
use pulseaudio and if that is not available it plays its audio
through portaudio thus using alsa directly. It can't switch
between this dynamically at runtime but it at least works out
what to use at initialization what translates to when launching 
the application when using python-espeak. There are no eSpeak
specific features yet. Things which are working include speaking,
changing voices, changing pitch, rate and volume. Say all support
with proper progress tracking is also working. I am not sure I
like this better than using eSpeak through speech-dispatcher like
we are doing for years. I am using this experiment for just a few
hours now. I am attaching a patch for those of you who are not
afraid of risking a bit. Please make sure you know how to revert
this and troubleshoot your system when it fails to speak. This is
my first attempt at trying to implement something into orca so it
is very likelly it may have loads of issues I even can't think of
at the moment. So how to apply it if you really want to test it
out. - Install python-espeak. If you are on arch linux, get it
from the AUR. I guess on Debian or Ubuntu you can just do apt-get
install python-espeak. With other distributions I don't know
whether there are prebuilt packages. Python-espeak is hosted on
launchpad and if there are no packages for your distro then you
can get it from there and install using python setup tools. To
check whether the patch applies cleanly into your cloned orca
git tree you can run git apply --check espeakfactory.patch If
there are no conflicts found you can then apply it by doing git
apply espeakfactory.patch After doing this you can just build and
install orca like normal.

Greetings

Peter


From 59b1e77f97edbde96f351b2408fcf21415536887 Mon Sep 17 00:00:00
2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= <pvdeejay gmail com> 
Date: Sun, 23 Aug 2015 20:23:27 +0200 Subject: [PATCH 1/2] direct
eSpeak support using python-espeak. espeakfactory is based off of
speechdispatcherfactory replacing speechd API calls with espeak
API.

--- src/orca/Makefile.am      |   1 + src/orca/espeakfactory.py |
461 ++++++++++++++++++++++++++++++++++++++++++++++ 
src/orca/guilabels.py     |   4 + src/orca/settings.py      |   2
+- 4 files changed, 467 insertions(+), 1 deletion(-) create mode
100644 src/orca/espeakfactory.py

diff --git a/src/orca/Makefile.am b/src/orca/Makefile.am index
f9f3086..275e5f7 100644 --- a/src/orca/Makefile.am +++
b/src/orca/Makefile.am @@ -22,6 +22,7 @@ orca_python_PYTHON = \ 
common_keyboardmap.py \ debug.py \ desktop_keyboardmap.py \ +
espeakfactory.py \ event_manager.py \ eventsynthesizer.py \ 
find.py \ diff --git a/src/orca/espeakfactory.py
b/src/orca/espeakfactory.py new file mode 100644 index
0000000..4585694 --- /dev/null +++ b/src/orca/espeakfactory.py @@
-0,0 +1,461 @@ +# This library is free software; you can
redistribute it and/or +# modify it under the terms of the GNU
Lesser General Public +# License as published by the Free
Software Foundation; either +# version 2.1 of the License, or (at
your option) any later version. +# +# This library 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 +# Lesser General
Public License for more details. +# +# You should have received a
copy of the GNU Lesser General Public +# License along with this
library; if not, write to the +# Free Software Foundation, Inc.,
Franklin Street, Fifth Floor, +# Boston MA  02110-1301 USA. + + 
+"""Provides an Orca speech server for eSpeak backend.""" + 
+__id__ = "$Id$" +__version__   = "$Revision$" +__date__      =
"$Date$" +__license__   = "LGPL" + +from gi.repository import
GLib +import re +import time +import os.path + +from . import
chnames +from . import debug +from . import guilabels +from .
import messages +from . import speechserver +from . import
settings +from . import orca_state +from . import
punctuation_settings +from .acss import ACSS + +try: +    from
espeak import espeak +except: +    _espeak_available = False 
+else:    +    _espeak_available = True + +PUNCTUATION =
re.compile('[^\w\s]', re.UNICODE) +ELLIPSIS =
re.compile('(\342\200\246|\.\.\.\s*)') + +#Parameter bounds 
+minRate=80 +maxRate=450 +minPitch=0 +maxPitch=99 + +class
SpeechServer(speechserver.SpeechServer): +    # See the parent
class for documentation. + +    _active_servers = {} +    +
DEFAULT_SERVER_ID = 'default' +    _SERVER_NAMES =
{DEFAULT_SERVER_ID: guilabels.DEFAULT_SYNTHESIZER} + +    def
getFactoryName(): +        return guilabels.ESPEAK +
getFactoryName = staticmethod(getFactoryName) + +    def
getSpeechServers(): +        servers = [] +        default = 
SpeechServer._getSpeechServer(SpeechServer.DEFAULT_SERVER_ID) +
if default is not None: +            servers.append(default) +
return servers +    getSpeechServers =
staticmethod(getSpeechServers) + +    def _getSpeechServer(cls,
serverId): +        """Return an active server for given id. + +
Attempt to create the server if it doesn't exist yet. Returns
None +        when it is not possible to create the server. +
+        """ +        if serverId not in cls._active_servers: +
cls(serverId) +        # Don't return the instance, unless it is
succesfully added +        # to `_active_Servers'. +
return cls._active_servers.get(serverId) +    _getSpeechServer =
classmethod(_getSpeechServer) + +    def
getSpeechServer(info=None): +        if info is not None: +
thisId = info[1] +        else: +            thisId =
SpeechServer.DEFAULT_SERVER_ID +        return
SpeechServer._getSpeechServer(thisId) +    getSpeechServer =
staticmethod(getSpeechServer) + +    def
shutdownActiveServers(): +        for server in
list(SpeechServer._active_servers.values()): +
server.shutdown() +    shutdownActiveServers =
staticmethod(shutdownActiveServers) + +    # *** Instance methods
*** + +    def __init__(self, serverId): +
super(SpeechServer, self).__init__() +        self._id =
serverId +        self._client = None +
self._current_voice_properties = {} +
self._acss_manipulators = ( +            (ACSS.RATE,
self._set_rate), +            (ACSS.AVERAGE_PITCH,
self._set_pitch), +            (ACSS.GAIN, self._set_volume), +
(ACSS.FAMILY, self._set_family), +            ) +        if not
_espeak_available: +
debug.println(debug.LEVEL_WARNING, +
"eSpeak interface not installed.") +            return +
self._PUNCTUATION_MODE_MAP = { +
settings.PUNCTUATION_STYLE_ALL:  espeak.Punctuation.All, +
settings.PUNCTUATION_STYLE_MOST: espeak.Punctuation.Custom, +
settings.PUNCTUATION_STYLE_SOME: espeak.Punctuation.Custom, +
settings.PUNCTUATION_STYLE_NONE: espeak.Punctuation.Any, +
} +        self._CALLBACK_TYPE_MAP = { +
espeak.event_SENTENCE: speechserver.SayAllContext.PROGRESS, +
#espeak.event_END: speechserver.SayAllContext.INTERRUPTED, +
espeak.event_MSG_TERMINATED: 
speechserver.SayAllContext.COMPLETED, +
#espeak.event_MARK:speechserver.SayAllContext.PROGRESS, +
} + +        self._default_voice_name =
guilabels.SPEECH_DEFAULT_VOICE % serverId +        +        try: 
+            self._init() +        except: +
debug.println(debug.LEVEL_WARNING, +
"eSpeak failed to initialize:") +
debug.printException(debug.LEVEL_WARNING) +        else: +
SpeechServer._active_servers[serverId] = self + +
self._lastKeyEchoTime = None + +    def _init(self): +
self._current_voice_properties = {} +        mode = 
self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle] +
espeak.set_parameter(espeak.Parameter.Punctuation,mode,0) + +
def updateCapitalizationStyle(self): +        """Updates the
capitalization style used by the speech server.""" +        pass 
+ +    def updatePunctuationLevel(self): +        """ Punctuation
level changed, inform this speechServer. """ +        mode = 
self._PUNCTUATION_MODE_MAP[settings.verbalizePunctuationStyle] +
espeak.set_parameter(espeak.Parameter.Punctuation,mode,0) + +
def _paramToPercent(self, current, min, max): +        """Convert
a raw parameter value to a percentage given the current, minimum
and maximum raw values. +        @param current: The current
value. +        @type current: int +        @param min: The
minimum value. +        @type current: int +        @param max:
The maximum value. +        @type max: int +        """ +
return int(round(float(current - min) / (max - min) * 100)) + +
def _percentToParam(self, percent, min, max): +        """Convert
a percentage to a raw parameter value given the current
percentage and the minimum and maximum raw parameter values. +
@param percent: The current percentage. +        @type percent:
int +        @param min: The minimum raw parameter value. +
@type min: int +        @param max: The maximum raw parameter
value. +        @type max: int +        """ +        return
int(round(float(percent) / 100 * (max - min) + min)) + +    def
_set_rate(self, acss_rate): +        rate =
self._percentToParam(acss_rate, minRate, maxRate) +
espeak.set_parameter(espeak.Parameter.Rate, rate, 0) + +    def
_set_pitch(self, acss_pitch): +        pitch =
self._percentToParam((acss_pitch *10), minPitch, maxPitch) +
espeak.set_parameter(espeak.Parameter.Pitch, pitch, 0) + +    def
_set_volume(self, acss_volume): +        volume = int(acss_volume
*10) +        espeak.set_parameter(espeak.Parameter.Volume,
volume, 0) + +    def _set_family(self, acss_family): +
familyLocale = acss_family.get(speechserver.VoiceFamily.LOCALE) +
if not familyLocale: +            import locale +
familyLocale, encoding = locale.getdefaultlocale() +        if
familyLocale: +            lang = familyLocale.split('_')[0] +
if lang: +                espeak.set_voice(lang) +        else: +
name = acss_family.get(speechserver.VoiceFamily.NAME) +
if name != self._default_voice_name: +
espeak.set_voice(name) + +    def _apply_acss(self, acss): +
if acss is None: +            acss =
settings.voices[settings.DEFAULT_VOICE] +        current =
self._current_voice_properties +        for acss_property, method
in self._acss_manipulators: +            value =
acss.get(acss_property) +            if value is not None: +
if current.get(acss_property) != value: +
method(value) +                    current[acss_property] =
value +            elif acss_property == ACSS.AVERAGE_PITCH: +
method(5.0) +                current[acss_property] = 5.0 +
elif acss_property == ACSS.FAMILY \ +                    and acss
== settings.voices[settings.DEFAULT_VOICE]: +                # We
need to explicitly reset (at least) the family. +
# See bgo#626072. +                # +                method({}) 
+                current[acss_property] = {} + +    def
__addVerbalizedPunctuation(self, oldText): +        """Depending
upon the users verbalized punctuation setting, +        adjust
punctuation symbols in the given text to their pronounced +
equivalents. The pronounced text will either replace the +
punctuation symbol or be inserted before it. In the latter case, 
+        this is to retain spoken prosity. + +        Arguments: 
+        - oldText: text to be parsed for punctuation. + +
Returns a text string with the punctuation symbols adjusted 
accordingly. +        """ + +        spokenEllipsis =
messages.SPOKEN_ELLIPSIS + " " +        newText =
re.sub(ELLIPSIS, spokenEllipsis, oldText) +        symbols =
set(re.findall(PUNCTUATION, newText)) +        for symbol in
symbols: +            try: +                level, action = 
punctuation_settings.getPunctuationInfo(symbol) +
except: +                continue + +            if level !=
punctuation_settings.LEVEL_NONE: +                # eSpeak should
handle it. +                # +                continue + +
charName = " %s " % chnames.getCharacterName(symbol) +
if action == punctuation_settings.PUNCTUATION_INSERT: +
charName += symbol +            newText = re.sub(symbol,
charName, newText) + +        if orca_state.activeScript: +
newText = 
orca_state.activeScript.utilities.adjustForDigits(newText) + +
return newText + +    def _speak(self, text, acss,
callback=None): +        if isinstance(text, ACSS): +
text = '' +        text = self.__addVerbalizedPunctuation(text) +
if orca_state.activeScript: +            text =
orca_state.activeScript.\ +
utilities.adjustForPronunciation(text) + +        # We need to
make several replacements. +        text = text.translate({ +
0x1: None, # used for embedded commands +            0x3C:
u"&lt;", # <: because of XML +            0x3E: u"&gt;", # >:
because of XML +            0x5B: u" [", # [: [[ indicates
phonemes +        }) + +        self._apply_acss(acss) +
espeak.set_SynthCallback(callback) +        espeak.synth(text) + 
+    def _say_all(self, iterator, orca_callback): +
"""Process another sayAll chunk. + +        Called by the gidle
thread. + +        """ +        try: +            context, acss =
next(iterator) +        except StopIteration: +            pass +
else: +            def callback(event, pos, len): +
t = self._CALLBACK_TYPE_MAP[event] +                if t ==
speechserver.SayAllContext.PROGRESS: +                    if pos
1: +                        context.currentOffset =
(context.startOffset +pos -1) +                    else: +
context.currentOffset = context.startOffset +                elif
t == speechserver.SayAllContext.COMPLETED: +
context.currentOffset = context.endOffset +
GLib.idle_add(orca_callback, context, t) +                if t ==
speechserver.SayAllContext.COMPLETED: +
GLib.idle_add(self._say_all, iterator, orca_callback) +
self._speak(context.utterance, acss, callback=callback) +
return False # to indicate, that we don't want to be called 
again. + +    def _cancel(self): +        espeak.cancel() + +
def _change_default_speech_rate(self, step, decrease=False): +
acss = settings.voices[settings.DEFAULT_VOICE] +        delta =
step * (decrease and -1 or +1) +        try: +            rate =
acss[ACSS.RATE] +        except KeyError: +            rate = 50 
+        acss[ACSS.RATE] = max(0, min(99, rate + delta)) +
debug.println(debug.LEVEL_CONFIGURATION, +
"Speech rate is now %d" % rate) + +        self.speak(decrease
and messages.SPEECH_SLOWER \ +                   or
messages.SPEECH_FASTER, acss=acss) + +    def
_change_default_speech_pitch(self, step, decrease=False): +
acss = settings.voices[settings.DEFAULT_VOICE] +        delta =
step * (decrease and -1 or +1) +        try: +            pitch =
acss[ACSS.AVERAGE_PITCH] +        except KeyError: +
pitch = 5 +        acss[ACSS.AVERAGE_PITCH] = max(0, min(9, pitch
+ delta)) +        debug.println(debug.LEVEL_CONFIGURATION, +
"Speech pitch is now %d" % pitch) + +        self.speak(decrease
and messages.SPEECH_LOWER \ +                   or
messages.SPEECH_HIGHER, acss=acss) + +    def
_change_default_speech_volume(self, step, decrease=False): +
acss = settings.voices[settings.DEFAULT_VOICE] +        delta =
step * (decrease and -1 or +1) +        try: +            volume
= acss[ACSS.GAIN] +        except KeyError: +            volume =
5 +        acss[ACSS.GAIN] = max(0, min(9, volume + delta)) +
debug.println(debug.LEVEL_CONFIGURATION, +
"Speech volume is now %d" % volume) + +
self.speak(decrease and messages.SPEECH_SOFTER \ +
or messages.SPEECH_LOUDER, acss=acss) + +    def getInfo(self): +
return [self._SERVER_NAMES.get(self._id, self._id), self._id] + +
def getVoiceFamilies(self): +        # Always offer the
configured default voice with a language +        # set according
to the current locale. +        from locale import getlocale,
LC_MESSAGES +        locale = getlocale(LC_MESSAGES)[0] +
if locale is None or locale == 'C': +            lang = None +
dialect = None +        else: +            lang, dialect =
locale.split('_') +        list_synthesis_voices =
espeak.list_voices() +        families =
[speechserver.VoiceFamily({ \ +
speechserver.VoiceFamily.NAME: self._default_voice_name, +
#speechserver.VoiceFamily.GENDER: speechserver.VoiceFamily.MALE, 
+              speechserver.VoiceFamily.DIALECT: dialect, +
speechserver.VoiceFamily.LOCALE: lang})] +        for voice in
list_synthesis_voices: +
families.append(speechserver.VoiceFamily({ \ +
speechserver.VoiceFamily.NAME: voice.name, +
speechserver.VoiceFamily.DIALECT: voice.variant, +
speechserver.VoiceFamily.LOCALE: 
voice.identifier.split("/")[-1]})) +        return families + +
def speak(self, text=None, acss=None, interrupt=True): +
#if interrupt: +        #    self._cancel() + +        # "We will
not interrupt a key echo in progress." (Said the comment in +
# speech.py where these next two lines used to live. But the code
here +        # suggests we haven't been doing anything with the 
lastKeyEchoTime in +        # years. TODO - JD: Dig into this and
if it's truly useless, kill it.) +        if
self._lastKeyEchoTime: +            interrupt = interrupt and
(time.time() - self._lastKeyEchoTime) > 0.5 + +        if text: +
self._speak(text, acss) + +    def speakUtterances(self,
utteranceList, acss=None, interrupt=True): +        #if
interrupt: +        #    self._cancel() +        for utterance in
utteranceList: +            if utterance: +
self._speak(utterance, acss) + +    def sayAll(self,
utteranceIterator, progressCallback): +
GLib.idle_add(self._say_all, utteranceIterator, 
progressCallback) + +    def speakCharacter(self, character,
acss=None): +        #self._apply_acss(acss) + +        name =
chnames.getCharacterName(character) +        if not name: +
self.speak(character, acss) +            return + +        if
orca_state.activeScript: +            name =
orca_state.activeScript.\ +
utilities.adjustForPronunciation(name) +        self.speak(name,
acss) + +    def speakKeyEvent(self, event): +        if
event.isPrintableKey() and event.event_string.isupper(): +
acss = settings.voices[settings.UPPERCASE_VOICE] +        else: +
acss = ACSS(settings.voices[settings.DEFAULT_VOICE]) + +
event_string = event.getKeyName() +        if
orca_state.activeScript: +            event_string =
orca_state.activeScript.\ +
utilities.adjustForPronunciation(event_string) + +
lockingStateString = event.getLockingStateString() +
event_string = "%s %s" % (event_string, lockingStateString) +
self.speak(event_string, acss=acss) +
self._lastKeyEchoTime = time.time() + +    def
increaseSpeechRate(self, step=5): +
self._change_default_speech_rate(step) + +    def
decreaseSpeechRate(self, step=5): +
self._change_default_speech_rate(step, decrease=True) + +    def
increaseSpeechPitch(self, step=0.5): +
self._change_default_speech_pitch(step) + +    def
decreaseSpeechPitch(self, step=0.5): +
self._change_default_speech_pitch(step, decrease=True) + +    def
increaseSpeechVolume(self, step=0.5): +
self._change_default_speech_volume(step) + +    def
decreaseSpeechVolume(self, step=0.5): +
self._change_default_speech_volume(step, decrease=True) + +
def stop(self): +        self._cancel() + +    def
shutdown(self): +        pass + +    def reset(self, text=None,
acss=None): +        pass + diff --git a/src/orca/guilabels.py
b/src/orca/guilabels.py index 8d8d9cf..b5e365e 100644 ---
a/src/orca/guilabels.py +++ b/src/orca/guilabels.py @@ -700,6
+700,10 @@ SPEECH_VOICE_TYPE_UPPERCASE = C_("VoiceType", 
"Uppercase") # system. (http://devel.freebsoft.org/speechd) 
SPEECH_DISPATCHER = _("Speech Dispatcher")

+# Translators this label refers to the name of particular
speech synthesiser. +# (http://espeak.sourceforge.net/) +ESPEAK =
_("eSpeak") + # Translators: This is a label for a group of
options related to Orca's behavior # when presenting an
application's spell check dialog. SPELL_CHECK = C_("OptionGroup",
"Spell Check") diff --git a/src/orca/settings.py
b/src/orca/settings.py index 045817d..933ec52 100644 ---
a/src/orca/settings.py +++ b/src/orca/settings.py @@ -187,7
+187,7 @@ activeProfile   = ['Default', 'default'] profile
= ['Default', 'default']

# Speech -speechFactoryModules         =
["speechdispatcherfactory"] +speechFactoryModules         = 
["speechdispatcherfactory","espeakfactory"] speechServerFactory
= "speechdispatcherfactory" speechServerInfo             = None #
None means let the factory decide. enableSpeech                 =
True -- 2.5.0


From 62136a85f539b27782cc6e6191a732f5ef75b302 Mon Sep 17 00:00:00
2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= <pvdeejay gmail com> 
Date: Sun, 23 Aug 2015 21:03:23 +0200 Subject: [PATCH 2/2] Added
author credits and fixes issue where less than and greater
symbols where spoken as their xml entities

--- src/orca/espeakfactory.py | 9 +++++++-- 1 file changed, 7
insertions(+), 2 deletions(-)

diff --git a/src/orca/espeakfactory.py
b/src/orca/espeakfactory.py index 4585694..f4f5008 100644 ---
a/src/orca/espeakfactory.py +++ b/src/orca/espeakfactory.py @@
-1,3 +1,10 @@ +# Orca +# +# Copyright 2015 Peter V??gner
<pvdeejay gmail com> +# Copyright 2006, 2007, 2008, 2009
Brailcom, o.p.s. +# +# Author: Tomas Cerha <cerha brailcom org> 
+# # This library is free software; you can redistribute it
and/or # modify it under the terms of the GNU Lesser General
Public # License as published by the Free Software Foundation;
either @@ -273,8 +280,6 @@ class
SpeechServer(speechserver.SpeechServer): # We need to make
several replacements. text = text.translate({ 0x1: None, # used
for embedded commands -            0x3C: u"&lt;", # <: because of
XML -            0x3E: u"&gt;", # >: because of XML 0x5B: u" [",
# [: [[ indicates phonemes })

-- 2.5.0


_______________________________________________ orca-list mailing
list orca-list gnome org 
https://mail.gnome.org/mailman/listinfo/orca-list Visit
http://live.gnome.org/Orca for more information on Orca. The
manual is at 
http://library.gnome.org/users/gnome-access-guide/nightly/ats-2.html


The FAQ is at http://live.gnome.org/Orca/FrequentlyAskedQuestions
Log bugs and feature requests at http://bugzilla.gnome.org Find
out how to help at http://live.gnome.org/Orca/HowCanIHelp




_______________________________________________ orca-list mailing
list orca-list gnome org 
https://mail.gnome.org/mailman/listinfo/orca-list Visit
http://live.gnome.org/Orca for more information on Orca. The manual
is at
http://library.gnome.org/users/gnome-access-guide/nightly/ats-2.html


The FAQ is at http://live.gnome.org/Orca/FrequentlyAskedQuestions
Log bugs and feature requests at http://bugzilla.gnome.org Find out
how to help at http://live.gnome.org/Orca/HowCanIHelp



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]