~ubuntu-branches/ubuntu/trusty/speech-dispatcher/trusty-proposed

« back to all changes in this revision

Viewing changes to src/api/python/speechd/client.py

  • Committer: Package Import Robot
  • Author(s): Luke Yelavich, Samuel Thibault, Luke Yelavich, Jason White, David Henningsson
  • Date: 2013-11-11 16:38:46 UTC
  • mfrom: (1.1.19)
  • Revision ID: package-import@ubuntu.com-20131111163846-lvu37ypp5sy9z5so
Tags: 0.8-0ubuntu1
[ Samuel Thibault ]
* debian/control: Set libspeechd2 multi-arch: same.
* debian/rules: Set multiarch libdir.
* debian/libspeechd-dev.install,libspeechd2.install,
  speech-dispatcher.install: Use multiarch libdir.
* Do not depend on dpkg | install-info, now that we use the install-info
  trigger.
* Bump Standards-Version to 3.9.5.
* Bump dotconf dependency to >= 1.3.

[ Luke Yelavich ]
* New upstream release
* debian/patches/infinite-loop.patch: Refreshed
* Dropped patches:
  - debian/patches/build-doc.patch
  - debian/patches/procname.patch
  - debian/patches/paths+files.patch
  - debian/patches/pthread.patch
* Add libltdl-dev and intltool to build-depends
* Update packaging for speech-dispatcher python 3 bindings.
* Move speech-dispatcher modules to an architecture independant dir, since
  modules can be written in any language, and i386 only modules can be
  used on amd64 systems
* Create separate audio plugins package
* Convert to debhelper 7+ packaging.
* Use dh-autoreconf to handle autotools file rebuilds.
* Update standards version to 3.9.3.
* Add X-Python-Version related fields to debian/control.
* Patch in the speech-dispatcher-cs.texi file since it was forgotten in the
  0.8 tarball
* Add translations to speech-dispatcher
* Merge from debian unreleased git.  Remaining changes:
  - Moved the flite output module to a separate package, and added
    it to suggests, we don't want flite on the Ubuntu CD image
  - Don't build depend on libaudio-dev or libao-dev, Ubuntu CD size is an
    issue, every little bit helps
  - debian/gbp.conf: Adjust for the Ubuntu git branch
  - Python3-speechd needs to conflict against python-speechd

[ Jason White ]
* Raise level of subsection in fdl.texi to correct document structure.

[ David Henningsson ]
* debian/patches/pulse-default-latency.patch:
  Default to 20 ms latency instead of 1 ms latency (LP: #1208826)

[ Luke Yelavich ]
* spd_audio: Expose dlopened library's symbols to libs it loads. Thanks to
  Christopher Brannon <chris@the-brannons.com> for the patch, taken from
  the speech-dispatcher mailing list.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2003-2008 Brailcom, o.p.s.
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
16
 
 
17
"""Python API to Speech Dispatcher
 
18
 
 
19
Basic Python client API to Speech Dispatcher is provided by the 'SSIPClient'
 
20
class.  This interface maps directly to available SSIP commands and logic.
 
21
 
 
22
A more convenient interface is provided by the 'Speaker' class.
 
23
 
 
24
"""
 
25
 
 
26
#TODO: Blocking variants for speak, char, key, sound_icon.
 
27
 
 
28
import socket, sys, os, subprocess, time, tempfile
 
29
 
 
30
try:
 
31
    import threading
 
32
except:
 
33
    import dummy_threading as threading
 
34
 
 
35
from . import paths
 
36
    
 
37
class CallbackType(object):
 
38
    """Constants describing the available types of callbacks"""
 
39
    INDEX_MARK = 'index_marks'
 
40
    """Index mark events are reported when the place they were
 
41
    included into the text by the client application is reached
 
42
    when speaking them"""
 
43
    BEGIN = 'begin'
 
44
    """The begin event is reported when Speech Dispatcher starts
 
45
    actually speaking the message."""
 
46
    END = 'end'
 
47
    """The end event is reported after the message has terminated and
 
48
    there is no longer any sound from it being produced"""
 
49
    CANCEL = 'cancel'
 
50
    """The cancel event is reported when a message is canceled either
 
51
    on request of the user, because of prioritization of messages or
 
52
    due to an error"""
 
53
    PAUSE = 'pause'
 
54
    """The pause event is reported after speaking of a message
 
55
    was paused. It no longer produces any audio."""
 
56
    RESUME = 'resume'
 
57
    """The resume event is reported right after speaking of a message
 
58
    was resumed after previous pause."""
 
59
 
 
60
class SSIPError(Exception):
 
61
    """Common base class for exceptions during SSIP communication."""
 
62
    
 
63
class SSIPCommunicationError(SSIPError):
 
64
    """Exception raised when trying to operate on a closed connection."""
 
65
 
 
66
    _additional_exception = None
 
67
 
 
68
    def __init__(self, description=None, original_exception=None, **kwargs):
 
69
        self._original_exception = original_exception
 
70
        self._description = description
 
71
        super(SSIPError, self).__init__(**kwargs)
 
72
 
 
73
    def original_exception(self):
 
74
        """Return the original exception if any
 
75
 
 
76
        If this exception is secondary, being caused by a lower
 
77
        level exception, return this original exception, otherwise
 
78
        None"""
 
79
        return self._original_exception
 
80
 
 
81
    def set_additional_exception(self, exception):
 
82
        """Set an additional exception
 
83
        
 
84
        See method additional_exception().
 
85
        """
 
86
        self._additional_exception = exception
 
87
 
 
88
    def additional_exception(self):
 
89
        """Return an additional exception
 
90
        
 
91
        Additional exceptions araise from failed attempts to resolve
 
92
        the former problem"""
 
93
        return self._additional_exception
 
94
 
 
95
    def description(self):
 
96
        """Return error description"""
 
97
        return self._description
 
98
 
 
99
    def __str__(self):
 
100
        msgs = []
 
101
        if self.description():
 
102
            msgs.append(self.description())
 
103
        if self.original_exception:
 
104
            msgs.append("Original error: " + str(self.original_exception()))
 
105
        if self.additional_exception:
 
106
            msgs.append("Additional error: " + str(self.additional_exception()))
 
107
        return "\n".join(msgs)
 
108
 
 
109
class SSIPResponseError(Exception):
 
110
    def __init__(self, code, msg, data):
 
111
        Exception.__init__(self, "%s: %s" % (code, msg))
 
112
        self._code = code
 
113
        self._msg = msg
 
114
        self._data = data
 
115
 
 
116
    def code(self):
 
117
        """Return the server response error code as integer number."""
 
118
        return self._code
 
119
        
 
120
    def msg(self):
 
121
        """Return server response error message as string."""
 
122
        return self._msg
 
123
 
 
124
 
 
125
class SSIPCommandError(SSIPResponseError):
 
126
    """Exception raised on error response after sending command."""
 
127
 
 
128
    def command(self):
 
129
        """Return the command string which resulted in this error."""
 
130
        return self._data
 
131
 
 
132
    
 
133
class SSIPDataError(SSIPResponseError):
 
134
    """Exception raised on error response after sending data."""
 
135
 
 
136
    def data(self):
 
137
        """Return the data which resulted in this error."""
 
138
        return self._data
 
139
 
 
140
    
 
141
class SpawnError(Exception):
 
142
    """Indicates failure in server autospawn."""
 
143
 
 
144
class CommunicationMethod(object):
 
145
    """Constants describing the possible methods of connection to server."""
 
146
    UNIX_SOCKET = 'unix_socket'
 
147
    """Unix socket communication using a filesystem path"""
 
148
    INET_SOCKET = 'inet_socket'
 
149
    """Inet socket communication using a host and port"""
 
150
 
 
151
class _SSIP_Connection(object):
 
152
    """Implemantation of low level SSIP communication."""
 
153
    
 
154
    _NEWLINE = b"\r\n"
 
155
    _END_OF_DATA_MARKER = b'.'
 
156
    _END_OF_DATA_MARKER_ESCAPED = b'..'
 
157
    _END_OF_DATA = _NEWLINE + _END_OF_DATA_MARKER + _NEWLINE
 
158
    _END_OF_DATA_ESCAPED = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED + _NEWLINE
 
159
    # Constants representing \r\n. and \r\n..
 
160
    _RAW_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER
 
161
    _ESCAPED_DOTLINE = _NEWLINE + _END_OF_DATA_MARKER_ESCAPED
 
162
 
 
163
    _CALLBACK_TYPE_MAP = {700: CallbackType.INDEX_MARK,
 
164
                          701: CallbackType.BEGIN,
 
165
                          702: CallbackType.END,
 
166
                          703: CallbackType.CANCEL,
 
167
                          704: CallbackType.PAUSE,
 
168
                          705: CallbackType.RESUME,
 
169
                          }
 
170
 
 
171
    def __init__(self, communication_method, socket_path, host, port):
 
172
        """Init connection: open the socket to server,
 
173
        initialize buffers, launch a communication handling
 
174
        thread.
 
175
        """
 
176
 
 
177
        if communication_method == CommunicationMethod.UNIX_SOCKET:
 
178
            socket_family = socket.AF_UNIX
 
179
            socket_connect_args = socket_path
 
180
        elif communication_method == CommunicationMethod.INET_SOCKET:
 
181
            assert host and port
 
182
            socket_family = socket.AF_INET
 
183
            socket_connect_args = (socket.gethostbyname(host), port)
 
184
        else:
 
185
            raise ValueError("Unsupported communication method")
 
186
 
 
187
        try:
 
188
            self._socket = socket.socket(socket_family, socket.SOCK_STREAM)
 
189
            self._socket.connect(socket_connect_args)
 
190
        except socket.error as ex:
 
191
            raise SSIPCommunicationError("Can't open socket using method "
 
192
                                         + communication_method,
 
193
                                         original_exception = ex)
 
194
 
 
195
        self._buffer = b""
 
196
        self._com_buffer = []
 
197
        self._callback = None
 
198
        self._ssip_reply_semaphore = threading.Semaphore(0)
 
199
        self._communication_thread = \
 
200
                threading.Thread(target=self._communication, kwargs={},
 
201
                                 name="SSIP client communication thread")
 
202
        self._communication_thread.start()
 
203
    
 
204
    def close(self):
 
205
        """Close the server connection, destroy the communication thread."""
 
206
        # Read-write shutdown here is necessary, otherwise the socket.recv()
 
207
        # function in the other thread won't return at last on some platforms.
 
208
        try:
 
209
            self._socket.shutdown(socket.SHUT_RDWR)
 
210
        except socket.error:
 
211
            pass
 
212
        self._socket.close()
 
213
        # Wait for the other thread to terminate
 
214
        self._communication_thread.join()
 
215
        
 
216
    def _communication(self):
 
217
        """Handle incomming socket communication.
 
218
 
 
219
        Listens for all incomming communication on the socket, dispatches
 
220
        events and puts all other replies into self._com_buffer list in the
 
221
        already parsed form as (code, msg, data).  Each time a new item is
 
222
        appended to the _com_buffer list, the corresponding semaphore
 
223
        'self._ssip_reply_semaphore' is incremented.
 
224
 
 
225
        This method is designed to run in a separate thread.  The thread can be
 
226
        interrupted by closing the socket on which it is listening for
 
227
        reading."""
 
228
 
 
229
        while True:
 
230
            try:
 
231
                code, msg, data = self._recv_message()
 
232
            except IOError:
 
233
                # If the socket has been closed, exit the thread
 
234
                sys.exit()
 
235
            if code//100 != 7:
 
236
                # This is not an index mark nor an event
 
237
                self._com_buffer.append((code, msg, data))
 
238
                self._ssip_reply_semaphore.release()
 
239
                continue
 
240
            # Ignore the event if no callback function has been registered.
 
241
            if self._callback is not None:
 
242
                type = self._CALLBACK_TYPE_MAP[code]
 
243
                if type == CallbackType.INDEX_MARK:
 
244
                    kwargs = {'index_mark': data[2]}
 
245
                else:
 
246
                    kwargs = {}
 
247
                # Get message and client ID of the event
 
248
                msg_id, client_id = map(int, data[:2])
 
249
                self._callback(msg_id, client_id, type, **kwargs)
 
250
                
 
251
                
 
252
    def _readline(self):
 
253
        """Read one whole line from the socket.
 
254
 
 
255
        Blocks until the line delimiter ('_NEWLINE') is read.
 
256
        
 
257
        """
 
258
        pointer = self._buffer.find(self._NEWLINE)
 
259
        while pointer == -1:
 
260
            try:
 
261
                d = self._socket.recv(1024)
 
262
            except:
 
263
                raise IOError
 
264
            if len(d) == 0:
 
265
                raise IOError
 
266
            self._buffer += d
 
267
            pointer = self._buffer.find(self._NEWLINE)
 
268
        line = self._buffer[:pointer]
 
269
        self._buffer = self._buffer[pointer+len(self._NEWLINE):]
 
270
        return line.decode('utf-8')
 
271
 
 
272
    def _recv_message(self):
 
273
        """Read server response or a callback
 
274
        and return the triplet (code, msg, data)."""
 
275
        data = []
 
276
        c = None
 
277
        while True:
 
278
            line = self._readline()
 
279
            assert len(line) >= 4, "Malformed data received from server!"
 
280
            code, sep, text = line[:3], line[3], line[4:]
 
281
            assert code.isalnum() and (c is None or code == c) and \
 
282
                   sep in ('-', ' '), "Malformed data received from server!"
 
283
            if sep == ' ':
 
284
                msg = text
 
285
                return int(code), msg, tuple(data)
 
286
            data.append(text)
 
287
 
 
288
    def _recv_response(self):
 
289
        """Read server response from the communication thread
 
290
        and return the triplet (code, msg, data)."""
 
291
        # TODO: This check is dumb but seems to work.  The main thread
 
292
        # hangs without it, when the Speech Dispatcher connection is lost.
 
293
        if not self._communication_thread.isAlive():
 
294
            raise SSIPCommunicationError
 
295
        self._ssip_reply_semaphore.acquire()
 
296
        # The list is sorted, read the first item
 
297
        response = self._com_buffer[0]
 
298
        del self._com_buffer[0]
 
299
        return response
 
300
 
 
301
    def send_command(self, command, *args):
 
302
        """Send SSIP command with given arguments and read server response.
 
303
 
 
304
        Arguments can be of any data type -- they are all stringified before
 
305
        being sent to the server.
 
306
 
 
307
        Returns a triplet (code, msg, data), where 'code' is a numeric SSIP
 
308
        response code as an integer, 'msg' is an SSIP rsponse message as string
 
309
        and 'data' is a tuple of strings (all lines of response data) when a
 
310
        response contains some data.
 
311
        
 
312
        'SSIPCommandError' is raised in case of non 2xx return code.  See SSIP
 
313
        documentation for more information about server responses and codes.
 
314
 
 
315
        'IOError' is raised when the socket was closed by the remote side.
 
316
        
 
317
        """
 
318
        if __debug__:
 
319
            if command in ('SET', 'CANCEL', 'STOP',):
 
320
                assert args[0] in (Scope.SELF, Scope.ALL) \
 
321
                       or isinstance(args[0], int)
 
322
        cmd = ' '.join((command,) + tuple(map(str, args)))
 
323
        try:
 
324
            self._socket.send(cmd.encode('utf-8') + self._NEWLINE)
 
325
        except socket.error:
 
326
            raise SSIPCommunicationError("Speech Dispatcher connection lost.")
 
327
        code, msg, data = self._recv_response()
 
328
        if code//100 != 2:
 
329
            raise SSIPCommandError(code, msg, cmd)
 
330
        return code, msg, data
 
331
        
 
332
    def send_data(self, data):
 
333
        """Send multiline data and read server response.
 
334
 
 
335
        Returned value is the same as for 'send_command()' method.
 
336
 
 
337
        'SSIPDataError' is raised in case of non 2xx return code. See SSIP
 
338
        documentation for more information about server responses and codes.
 
339
        
 
340
        'IOError' is raised when the socket was closed by the remote side.
 
341
        
 
342
        """
 
343
        data = data.encode('utf-8')
 
344
        # Escape the end-of-data marker even if present at the beginning
 
345
        # The start of the string is also the start of a line.
 
346
        if data.startswith(self._END_OF_DATA_MARKER):
 
347
            l = len(self._END_OF_DATA_MARKER)
 
348
            data = self._END_OF_DATA_MARKER_ESCAPED + data[l:]
 
349
 
 
350
        # Escape the end of data marker at the start of each subsequent
 
351
        # line.  We can do that by simply replacing \r\n. with \r\n..,
 
352
        # since the start of a line is immediately preceded by \r\n,
 
353
        # when the line is not the beginning of the string.
 
354
        data = data.replace(self._RAW_DOTLINE, self._ESCAPED_DOTLINE)
 
355
 
 
356
        try:
 
357
            self._socket.send(data + self._END_OF_DATA)
 
358
        except socket.error:
 
359
            raise SSIPCommunicationError("Speech Dispatcher connection lost.")
 
360
        code, msg, response_data = self._recv_response()
 
361
        if code//100 != 2:
 
362
            raise SSIPDataError(code, msg, data)
 
363
        return code, msg, response_data
 
364
 
 
365
    def set_callback(self, callback):
 
366
        """Register a callback function for handling asynchronous events.
 
367
 
 
368
        Arguments:
 
369
          callback -- a callable object (function) which will be called to
 
370
            handle asynchronous events (arguments described below).  Passing
 
371
            `None' results in removing the callback function and ignoring
 
372
            events.  Just one callback may be registered.  Attempts to register
 
373
            a second callback will result in the former callback being
 
374
            replaced.
 
375
 
 
376
        The callback function must accept three positional arguments
 
377
        ('message_id', 'client_id', 'event_type') and an optional keyword
 
378
        argument 'index_mark' (when INDEX_MARK events are turned on).
 
379
 
 
380
        Note, that setting the callback function doesn't turn the events on.
 
381
        The user is responsible to turn them on by sending the appropriate `SET
 
382
        NOTIFICATION' command.
 
383
 
 
384
        """
 
385
        self._callback = callback
 
386
 
 
387
class _CallbackHandler(object):
 
388
    """Internal object which handles callbacks."""
 
389
 
 
390
    def __init__(self, client_id):
 
391
        self._client_id = client_id
 
392
        self._callbacks = {}
 
393
        self._lock = threading.Lock()
 
394
 
 
395
    def __call__(self, msg_id, client_id, type, **kwargs):
 
396
        if client_id != self._client_id:
 
397
            # TODO: does that ever happen?
 
398
            return
 
399
        self._lock.acquire()
 
400
        try:
 
401
            try:
 
402
                callback, event_types = self._callbacks[msg_id]
 
403
            except KeyError:
 
404
                pass
 
405
            else:
 
406
                if event_types is None or type in event_types:
 
407
                    callback(type, **kwargs)
 
408
                if type in (CallbackType.END, CallbackType.CANCEL):
 
409
                    del self._callbacks[msg_id]
 
410
        finally:
 
411
            self._lock.release()
 
412
 
 
413
    def add_callback(self, msg_id,  callback, event_types):
 
414
        self._lock.acquire()
 
415
        try:
 
416
            self._callbacks[msg_id] = (callback, event_types)
 
417
        finally:
 
418
            self._lock.release()
 
419
 
 
420
class Scope(object):
 
421
    """An enumeration of valid SSIP command scopes.
 
422
 
 
423
    The constants of this class should be used to specify the 'scope' argument
 
424
    for the 'Client' methods.
 
425
 
 
426
    """    
 
427
    SELF = 'self'
 
428
    """The command (mostly a setting) applies to current connection only."""
 
429
    ALL = 'all'
 
430
    """The command applies to all current Speech Dispatcher connections."""
 
431
 
 
432
    
 
433
class Priority(object):
 
434
    """An enumeration of valid SSIP message priorities.
 
435
 
 
436
    The constants of this class should be used to specify the 'priority'
 
437
    argument for the 'Client' methods.  For more information about message
 
438
    priorities and their interaction, see the SSIP documentation.
 
439
    
 
440
    """
 
441
    IMPORTANT = 'important'
 
442
    TEXT = 'text'
 
443
    MESSAGE = 'message'
 
444
    NOTIFICATION = 'notification'
 
445
    PROGRESS = 'progress'
 
446
 
 
447
    
 
448
class PunctuationMode(object):
 
449
    """Constants for selecting a punctuation mode.
 
450
 
 
451
    The mode determines which characters should be read.
 
452
 
 
453
    """
 
454
    ALL = 'all'
 
455
    """Read all punctuation characters."""
 
456
    NONE = 'none'
 
457
    """Don't read any punctuation character at all."""
 
458
    SOME = 'some'
 
459
    """Only the user-defined punctuation characters are read.
 
460
 
 
461
    The set of characters is specified in Speech Dispatcher configuration.
 
462
 
 
463
    """
 
464
 
 
465
class DataMode(object):
 
466
    """Constants specifying the type of data contained within messages
 
467
    to be spoken.
 
468
 
 
469
    """
 
470
    TEXT = 'text'
 
471
    """Data is plain text."""
 
472
    SSML = 'ssml'
 
473
    """Data is SSML (Speech Synthesis Markup Language)."""
 
474
 
 
475
 
 
476
class SSIPClient(object):
 
477
    """Basic Speech Dispatcher client interface.
 
478
 
 
479
    This class provides a Python interface to Speech Dispatcher functionality
 
480
    over an SSIP connection.  The API maps directly to available SSIP commands.
 
481
    Each connection to Speech Dispatcher is represented by one instance of this
 
482
    class.
 
483
    
 
484
    Many commands take the 'scope' argument, thus it is shortly documented
 
485
    here.  It is either one of 'Scope' constants or a number of connection.  By
 
486
    specifying the connection number, you are applying the command to a
 
487
    particular connection.  This feature is only meant to be used by Speech
 
488
    Dispatcher control application, however.  More datails can be found in
 
489
    Speech Dispatcher documentation.
 
490
 
 
491
    """
 
492
    
 
493
    DEFAULT_HOST = '127.0.0.1'
 
494
    """Default host for server connections."""
 
495
    DEFAULT_PORT = 6560
 
496
    """Default port number for server connections."""
 
497
    DEFAULT_SOCKET_PATH = "speech-dispatcher/speechd.sock"
 
498
    """Default name of the communication unix socket"""
 
499
    
 
500
    def __init__(self, name, component='default', user='unknown', address=None,
 
501
                 autospawn=None,
 
502
                 # Deprecated ->
 
503
                 host=None, port=None, method=None, socket_path=None):
 
504
        """Initialize the instance and connect to the server.
 
505
 
 
506
        Arguments:
 
507
          name -- client identification string
 
508
          component -- connection identification string.  When one client opens
 
509
            multiple connections, this can be used to identify each of them.
 
510
          user -- user identification string (user name).  When multi-user
 
511
            acces is expected, this can be used to identify their connections.
 
512
          address -- server address as specified in Speech Dispatcher
 
513
            documentation (e.g. "unix:/run/user/joe/speech-dispatcher/speechd.sock"
 
514
            or "inet:192.168.0.85:6561")
 
515
          autospawn -- a flag to specify whether the library should
 
516
            try to start the server if it determines its not already
 
517
            running or not
 
518
 
 
519
        Deprecated arguments:
 
520
          method -- communication method to use, one of the constants defined in class
 
521
            CommunicationMethod
 
522
          socket_path -- for CommunicationMethod.UNIX_SOCKET, socket
 
523
            path in filesystem. By default, this is $XDG_RUNTIME_DIR/speech-dispatcher/speechd.sock
 
524
            where $XDG_RUNTIME_DIR is determined using the XDG Base Directory
 
525
            Specification.
 
526
          host -- for CommunicationMethod.INET_SOCKET, server hostname
 
527
            or IP address as a string.  If None, the default value is
 
528
            taken from SPEECHD_HOST environment variable (if it
 
529
            exists) or from the DEFAULT_HOST attribute of this class.
 
530
          port -- for CommunicationMethod.INET_SOCKET method, server
 
531
            port as number or None.  If None, the default value is
 
532
            taken from SPEECHD_PORT environment variable (if it
 
533
            exists) or from the DEFAULT_PORT attribute of this class.
 
534
         
 
535
        For more information on client identification strings see Speech
 
536
        Dispatcher documentation.
 
537
        """
 
538
 
 
539
        _home = os.path.expanduser("~")
 
540
        _runtime_dir = os.environ.get('XDG_RUNTIME_DIR', os.environ.get('XDG_CACHE_HOME', os.path.join(_home, '.cache')))
 
541
        _sock_path = os.path.join(_runtime_dir, self.DEFAULT_SOCKET_PATH)
 
542
        # Resolve connection parameters:
 
543
        connection_args = {'communication_method': CommunicationMethod.UNIX_SOCKET,
 
544
                           'socket_path': _sock_path,
 
545
                           'host': self.DEFAULT_HOST,
 
546
                           'port': self.DEFAULT_PORT,
 
547
                           }
 
548
        # Respect address method argument and SPEECHD_ADDRESS environemt variable
 
549
        _address = address or os.environ.get("SPEECHD_ADDRESS")        
 
550
 
 
551
        if _address:
 
552
            connection_args.update(self._connection_arguments_from_address(_address))
 
553
        # Respect the old (deprecated) key arguments and environment variables
 
554
        # TODO: Remove this section in 0.8 release
 
555
        else:
 
556
            # Read the environment variables
 
557
            env_speechd_host = os.environ.get("SPEECHD_HOST")
 
558
            try:
 
559
                env_speechd_port = int(os.environ.get("SPEECHD_PORT"))
 
560
            except:
 
561
                env_speechd_port = None
 
562
            env_speechd_socket_path = os.environ.get("SPEECHD_SOCKET")
 
563
            # Prefer old (deprecated) function arguments, but if
 
564
            # not specified and old (deprecated) environment variable
 
565
            # is set, use the value of the environment variable
 
566
            if method:
 
567
                connection_args['method'] = method
 
568
            if port:
 
569
                connection_args['port'] = port
 
570
            elif env_speechd_port:
 
571
                connection_args['port'] = env_speechd_port
 
572
            if socket_path:
 
573
                connection_args['socket_path'] = socket_path
 
574
            elif env_speechd_socket_path:
 
575
                connection_args['socket_path'] = env_speechd_socket_path
 
576
        self._connect_with_autospawn(connection_args, autospawn)
 
577
        self._initialize_connection(user, name, component)
 
578
 
 
579
    def _connect_with_autospawn(self, connection_args, autospawn):
 
580
        """Establish new connection (and/or autospawn server)"""
 
581
        try:
 
582
            self._conn = _SSIP_Connection(**connection_args)
 
583
        except SSIPCommunicationError as ce:
 
584
            # Suppose server might not be running, try the autospawn mechanism
 
585
            if autospawn != False:
 
586
                # Autospawn is however not guaranteed to start the server. The server
 
587
                # will decide, based on it's configuration, whether to honor the request.
 
588
                try:
 
589
                    self._server_spawn(connection_args)
 
590
                except SpawnError as se:
 
591
                    ce.set_additional_exception(se)
 
592
                    raise ce
 
593
                self._conn = _SSIP_Connection(**connection_args)
 
594
            else:
 
595
                raise
 
596
 
 
597
    def _initialize_connection(self, user, name, component):
 
598
        """Initialize connection -- Set client name, get id, register callbacks etc."""
 
599
        full_name = '%s:%s:%s' % (user, name, component)
 
600
        self._conn.send_command('SET', Scope.SELF, 'CLIENT_NAME', full_name)
 
601
        code, msg, data = self._conn.send_command('HISTORY', 'GET', 'CLIENT_ID')
 
602
        self._client_id = int(data[0])
 
603
        self._callback_handler = _CallbackHandler(self._client_id)
 
604
        self._conn.set_callback(self._callback_handler)
 
605
        for event in (CallbackType.INDEX_MARK,
 
606
                      CallbackType.BEGIN,
 
607
                      CallbackType.END,
 
608
                      CallbackType.CANCEL,
 
609
                      CallbackType.PAUSE,
 
610
                      CallbackType.RESUME):
 
611
            self._conn.send_command('SET', 'self', 'NOTIFICATION', event, 'on')
 
612
 
 
613
    def _connection_arguments_from_address(self, address):
 
614
        """Parse a Speech Dispatcher address line and return a dictionary
 
615
        of connection arguments"""
 
616
        connection_args = {}
 
617
        address_params = address.split(":")
 
618
        try:
 
619
            _method = address_params[0]
 
620
        except:
 
621
            raise SSIPCommunicationErrror("Wrong format of server address")
 
622
        connection_args['communication_method'] = _method
 
623
        if _method == CommunicationMethod.UNIX_SOCKET:
 
624
            try:
 
625
                connection_args['socket_path'] = address_params[1]
 
626
            except IndexError:
 
627
                pass # The additional parameters was not set, let's stay with defaults
 
628
        elif _method == CommunicationMethod.INET_SOCKET:
 
629
            try:
 
630
                connection_args['host'] = address_params[1]
 
631
                connection_args['port'] = int(address_params[2])
 
632
            except ValueError: # Failed conversion to int
 
633
                raise SSIPCommunicationError("Third parameter of inet_socket address must be a port number")
 
634
            except IndexError:
 
635
                pass # The additional parameters was not set, let's stay with defaults
 
636
        else:
 
637
            raise SSIPCommunicationError("Unknown communication method in address.");
 
638
        return connection_args
 
639
    
 
640
    def __del__(self):
 
641
        """Close the connection"""
 
642
        self.close()
 
643
 
 
644
    def _server_spawn(self, connection_args):
 
645
        """Attempts to spawn the speech-dispatcher server."""
 
646
        # Check whether we are not connecting to a remote host
 
647
        # TODO: This is a hack. inet sockets specific code should
 
648
        # belong to _SSIPConnection. We do not however have an _SSIPConnection
 
649
        # yet.
 
650
        if connection_args['communication_method'] == 'inet_socket':
 
651
            addrinfos = socket.getaddrinfo(connection_args['host'],
 
652
                                           connection_args['port'])
 
653
            # Check resolved addrinfos for presence of localhost
 
654
            ip_addresses = [addrinfo[4][0] for addrinfo in addrinfos]
 
655
            localhost=False
 
656
            for ip in ip_addresses:
 
657
                if ip.startswith("127.") or ip == "::1":
 
658
                    connection_args['host'] = ip
 
659
                    localhost=True
 
660
            if not localhost:
 
661
                # The hostname didn't resolve on localhost in neither case,
 
662
                # do not spawn server on localhost...
 
663
                raise SpawnError(
 
664
                    "Can't start server automatically (autospawn), requested address %s "
 
665
                    "resolves on %s which seems to be a remote host. You must start the "
 
666
                    "server manually or choose another connection address." % (connection_args['host'],
 
667
                                                                               str(ip_addresses),))
 
668
        if os.path.exists(paths.SPD_SPAWN_CMD):
 
669
            connection_params = []
 
670
            for param, value in connection_args.items():
 
671
                if param not in ["host",]:
 
672
                    connection_params += ["--"+param.replace("_","-"), str(value)]
 
673
 
 
674
            server = subprocess.Popen([paths.SPD_SPAWN_CMD, "--spawn"]+connection_params,
 
675
                                      stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
676
            stdout_reply, stderr_reply = server.communicate()
 
677
            retcode = server.wait()
 
678
            if retcode != 0:
 
679
                raise SpawnError("Server refused to autospawn, stating this reason: %s" % (stderr_reply,))
 
680
            return server.pid
 
681
        else:
 
682
            raise SpawnError("Can't find Speech Dispatcher spawn command %s"
 
683
                                         % (paths.SPD_SPAWN_CMD,))
 
684
 
 
685
    def set_priority(self, priority):
 
686
        """Set the priority category for the following messages.
 
687
 
 
688
        Arguments:
 
689
          priority -- one of the 'Priority' constants.
 
690
 
 
691
        """
 
692
        assert priority in (Priority.IMPORTANT, Priority.MESSAGE,
 
693
                            Priority.TEXT, Priority.NOTIFICATION,
 
694
                            Priority.PROGRESS), priority
 
695
        self._conn.send_command('SET', Scope.SELF, 'PRIORITY', priority)
 
696
 
 
697
    def set_data_mode(self, value):
 
698
        """Set the data mode for further speech commands.
 
699
 
 
700
        Arguments:
 
701
          value - one of the constants defined by the DataMode class.
 
702
 
 
703
        """
 
704
        if value == DataMode.SSML:
 
705
            ssip_val = 'on'
 
706
        elif value == DataMode.TEXT:
 
707
            ssip_val = 'off'
 
708
        else:
 
709
            raise ValueError(
 
710
                'Value "%s" is not one of the constants from the DataMode class.' % \
 
711
                    value)
 
712
        self._conn.send_command('SET', Scope.SELF, 'SSML_MODE', ssip_val)
 
713
 
 
714
    def speak(self, text, callback=None, event_types=None):
 
715
        """Say given message.
 
716
 
 
717
        Arguments:
 
718
          text -- message text to be spoken.  This may be either a UTF-8
 
719
            encoded byte string or a Python unicode string.
 
720
          callback -- a callback handler for asynchronous event notifications.
 
721
            A callable object (function) which accepts one positional argument
 
722
            `type' and one keyword argument `index_mark'.  See below for more
 
723
            details.
 
724
          event_types -- a tuple of event types for which the callback should
 
725
            be called.  Each item must be one of `CallbackType' constants.
 
726
            None (the default value) means to handle all event types.  This
 
727
            argument is irrelevant when `callback' is not used.
 
728
 
 
729
        The callback function will be called whenever one of the events occurs.
 
730
        The event type will be passed as argument.  Its value is one of the
 
731
        `CallbackType' constants.  In case of an index mark event, additional
 
732
        keyword argument `index_mark' will be passed and will contain the index
 
733
        mark identifier as specified within the text.
 
734
 
 
735
        The callback function should not perform anything complicated and is
 
736
        not allowed to issue any further SSIP client commands.  An attempt to
 
737
        do so would lead to a deadlock in SSIP communication.
 
738
 
 
739
        This method is non-blocking;  it just sends the command, given
 
740
        message is queued on the server and the method returns immediately.
 
741
 
 
742
        """
 
743
        self._conn.send_command('SPEAK')
 
744
        result = self._conn.send_data(text)
 
745
        if callback:
 
746
            msg_id = int(result[2][0])
 
747
            # TODO: Here we risk, that the callback arrives earlier, than we
 
748
            # add the item to `self._callback_handler'.  Such a situation will
 
749
            # lead to the callback being ignored.
 
750
            self._callback_handler.add_callback(msg_id, callback, event_types)
 
751
        return result
 
752
 
 
753
    def char(self, char):
 
754
        """Say given character.
 
755
 
 
756
        Arguments:
 
757
          char -- a character to be spoken.  Either a Python unicode string or
 
758
            a UTF-8 encoded byte string.
 
759
 
 
760
        This method is non-blocking;  it just sends the command, given
 
761
        message is queued on the server and the method returns immediately.
 
762
 
 
763
        """
 
764
        self._conn.send_command('CHAR', char.replace(' ', 'space'))
 
765
        
 
766
    def key(self, key):
 
767
        """Say given key name.
 
768
 
 
769
        Arguments:
 
770
          key -- the key name (as defined in SSIP); string.
 
771
 
 
772
        This method is non-blocking;  it just sends the command, given
 
773
        message is queued on the server and the method returns immediately.
 
774
 
 
775
        """
 
776
        self._conn.send_command('KEY', key)
 
777
 
 
778
    def sound_icon(self, sound_icon):
 
779
        """Output given sound_icon.
 
780
 
 
781
        Arguments:
 
782
          sound_icon -- the name of the sound icon as defined by SSIP; string.
 
783
 
 
784
        This method is non-blocking; it just sends the command, given message
 
785
        is queued on the server and the method returns immediately.
 
786
 
 
787
        """        
 
788
        self._conn.send_command('SOUND_ICON', sound_icon)
 
789
                    
 
790
    def cancel(self, scope=Scope.SELF):
 
791
        """Immediately stop speaking and discard messages in queues.
 
792
 
 
793
        Arguments:
 
794
          scope -- see the documentation of this class.
 
795
            
 
796
        """
 
797
        self._conn.send_command('CANCEL', scope)
 
798
 
 
799
 
 
800
    def stop(self, scope=Scope.SELF):
 
801
        """Immediately stop speaking the currently spoken message.
 
802
 
 
803
        Arguments:
 
804
          scope -- see the documentation of this class.
 
805
        
 
806
        """
 
807
        self._conn.send_command('STOP', scope)
 
808
 
 
809
    def pause(self, scope=Scope.SELF):
 
810
        """Pause speaking and postpone other messages until resume.
 
811
 
 
812
        This method is non-blocking.  However, speaking can continue for a
 
813
        short while even after it's called (typically to the end of the
 
814
        sentence).
 
815
 
 
816
        Arguments:
 
817
          scope -- see the documentation of this class.
 
818
        
 
819
        """
 
820
        self._conn.send_command('PAUSE', scope)
 
821
 
 
822
    def resume(self, scope=Scope.SELF):
 
823
        """Resume speaking of the currently paused messages.
 
824
 
 
825
        This method is non-blocking.  However, speaking can continue for a
 
826
        short while even after it's called (typically to the end of the
 
827
        sentence).
 
828
 
 
829
        Arguments:
 
830
          scope -- see the documentation of this class.
 
831
        
 
832
        """
 
833
        self._conn.send_command('RESUME', scope)
 
834
 
 
835
    def list_output_modules(self):
 
836
        """Return names of all active output modules as a tuple of strings."""
 
837
        code, msg, data = self._conn.send_command('LIST', 'OUTPUT_MODULES')
 
838
        return data
 
839
 
 
840
    def list_synthesis_voices(self):
 
841
        """Return names of all available voices for the current output module.
 
842
 
 
843
        Returns a tuple of tripplets (name, language, dialect).
 
844
 
 
845
        'name' is a string, 'language' is an ISO 639-1 Alpha-2 language code
 
846
        and 'dialect' is a string.  Language and dialect may be None.
 
847
 
 
848
        """
 
849
        try:
 
850
            code, msg, data = self._conn.send_command('LIST', 'SYNTHESIS_VOICES')
 
851
        except SSIPCommandError:
 
852
            return ()
 
853
        def split(item):
 
854
            name, lang, dialect = tuple(item.rsplit(' ', 3))
 
855
            return (name, lang or None, dialect or None)
 
856
        return tuple([split(item) for item in data])
 
857
 
 
858
    def set_language(self, language, scope=Scope.SELF):
 
859
        """Switch to a particular language for further speech commands.
 
860
 
 
861
        Arguments:
 
862
          language -- two letter language code according to RFC 1776 as string.
 
863
          scope -- see the documentation of this class.
 
864
            
 
865
        """
 
866
        assert isinstance(language, str) and len(language) == 2
 
867
        self._conn.send_command('SET', scope, 'LANGUAGE', language)
 
868
 
 
869
    def set_output_module(self, name, scope=Scope.SELF):
 
870
        """Switch to a particular output module.
 
871
 
 
872
        Arguments:
 
873
          name -- module (string) as returned by 'list_output_modules()'.
 
874
          scope -- see the documentation of this class.
 
875
        
 
876
        """
 
877
        self._conn.send_command('SET', scope, 'OUTPUT_MODULE', name)
 
878
 
 
879
    def set_pitch(self, value, scope=Scope.SELF):
 
880
        """Set the pitch for further speech commands.
 
881
 
 
882
        Arguments:
 
883
          value -- integer value within the range from -100 to 100, with 0
 
884
            corresponding to the default pitch of the current speech synthesis
 
885
            output module, lower values meaning lower pitch and higher values
 
886
            meaning higher pitch.
 
887
          scope -- see the documentation of this class.
 
888
          
 
889
        """
 
890
        assert isinstance(value, int) and -100 <= value <= 100, value
 
891
        self._conn.send_command('SET', scope, 'PITCH', value)
 
892
 
 
893
    def set_rate(self, value, scope=Scope.SELF):
 
894
        """Set the speech rate (speed) for further speech commands.
 
895
 
 
896
        Arguments:
 
897
          value -- integer value within the range from -100 to 100, with 0
 
898
            corresponding to the default speech rate of the current speech
 
899
            synthesis output module, lower values meaning slower speech and
 
900
            higher values meaning faster speech.
 
901
          scope -- see the documentation of this class.
 
902
            
 
903
        """
 
904
        assert isinstance(value, int) and -100 <= value <= 100
 
905
        self._conn.send_command('SET', scope, 'RATE', value)
 
906
 
 
907
    def set_volume(self, value, scope=Scope.SELF):
 
908
        """Set the speech volume for further speech commands.
 
909
 
 
910
        Arguments:
 
911
          value -- integer value within the range from -100 to 100, with 100
 
912
            corresponding to the default speech volume of the current speech
 
913
            synthesis output module, lower values meaning softer speech.
 
914
          scope -- see the documentation of this class.
 
915
            
 
916
        """
 
917
        assert isinstance(value, int) and -100 <= value <= 100
 
918
        self._conn.send_command('SET', scope, 'VOLUME', value)
 
919
 
 
920
    def set_punctuation(self, value, scope=Scope.SELF):
 
921
        """Set the punctuation pronounciation level.
 
922
 
 
923
        Arguments:
 
924
          value -- one of the 'PunctuationMode' constants.
 
925
          scope -- see the documentation of this class.
 
926
            
 
927
        """
 
928
        assert value in (PunctuationMode.ALL, PunctuationMode.SOME,
 
929
                         PunctuationMode.NONE), value
 
930
        self._conn.send_command('SET', scope, 'PUNCTUATION', value)
 
931
 
 
932
    def set_spelling(self, value, scope=Scope.SELF):
 
933
        """Toogle the spelling mode or on off.
 
934
 
 
935
        Arguments:
 
936
          value -- if 'True', all incomming messages will be spelled
 
937
            instead of being read as normal words. 'False' switches
 
938
            this behavior off.
 
939
          scope -- see the documentation of this class.
 
940
            
 
941
        """
 
942
        assert value in [True, False]
 
943
        if value == True:
 
944
            self._conn.send_command('SET', scope, 'SPELLING', "on")
 
945
        else:
 
946
            self._conn.send_command('SET', scope, 'SPELLING', "off")
 
947
 
 
948
    def set_cap_let_recogn(self, value, scope=Scope.SELF):
 
949
        """Set capital letter recognition mode.
 
950
 
 
951
        Arguments:
 
952
          value -- one of 'none', 'spell', 'icon'. None means no signalization
 
953
            of capital letters, 'spell' means capital letters will be spelled
 
954
            with a syntetic voice and 'icon' means that the capital-letter icon
 
955
            will be prepended before each capital letter.
 
956
          scope -- see the documentation of this class.
 
957
            
 
958
        """
 
959
        assert value in ("none", "spell", "icon")
 
960
        self._conn.send_command('SET', scope, 'CAP_LET_RECOGN', value)
 
961
 
 
962
    def set_voice(self, value, scope=Scope.SELF):
 
963
        """Set voice by a symbolic name.
 
964
 
 
965
        Arguments:
 
966
          value -- one of the SSIP symbolic voice names: 'MALE1' .. 'MALE3',
 
967
            'FEMALE1' ... 'FEMALE3', 'CHILD_MALE', 'CHILD_FEMALE'
 
968
          scope -- see the documentation of this class.
 
969
 
 
970
        Symbolic voice names are mapped to real synthesizer voices in the
 
971
        configuration of the output module.  Use the method
 
972
        'set_synthesis_voice()' if you want to work with real voices.
 
973
            
 
974
        """
 
975
        assert isinstance(value, str) and \
 
976
               value.lower() in ("male1", "male2", "male3", "female1",
 
977
                                 "female2", "female3", "child_male",
 
978
                                 "child_female")
 
979
        self._conn.send_command('SET', scope, 'VOICE', value)
 
980
 
 
981
    def set_synthesis_voice(self, value, scope=Scope.SELF):
 
982
        """Set voice by its real name.
 
983
 
 
984
        Arguments:
 
985
          value -- voice name as returned by 'list_synthesis_voices()'
 
986
          scope -- see the documentation of this class.
 
987
            
 
988
        """
 
989
        self._conn.send_command('SET', scope, 'SYNTHESIS_VOICE', value)
 
990
        
 
991
    def set_pause_context(self, value, scope=Scope.SELF):
 
992
        """Set the amount of context when resuming a paused message.
 
993
 
 
994
        Arguments:
 
995
          value -- a positive or negative value meaning how many chunks of data
 
996
            after or before the pause should be read when resume() is executed.
 
997
          scope -- see the documentation of this class.
 
998
            
 
999
        """
 
1000
        assert isinstance(value, int)
 
1001
        self._conn.send_command('SET', scope, 'PAUSE_CONTEXT', value)
 
1002
 
 
1003
    def set_debug(self, val):
 
1004
        """Switch debugging on and off. When switched on,
 
1005
        debugging files will be created in the chosen destination
 
1006
        (see set_debug_destination()) for Speech Dispatcher and all
 
1007
        its running modules. All logging information will then be
 
1008
        written into these files with maximal verbosity until switched
 
1009
        off. You should always first call set_debug_destination.
 
1010
 
 
1011
        The intended use of this functionality is to switch debuging
 
1012
        on for a period of time while the user will repeat the behavior
 
1013
        and then send the logs to the appropriate bug-reporting place.
 
1014
 
 
1015
        Arguments:
 
1016
          val -- a boolean value determining whether debugging
 
1017
                 is switched on or off
 
1018
          scope -- see the documentation of this class.
 
1019
        
 
1020
        """
 
1021
        assert isinstance(val, bool)
 
1022
        if val == True:
 
1023
            ssip_val = "ON"
 
1024
        else:
 
1025
            ssip_val = "OFF"
 
1026
 
 
1027
        self._conn.send_command('SET', scope.ALL, 'DEBUG', ssip_val)
 
1028
 
 
1029
 
 
1030
    def set_debug_destination(self, path):
 
1031
        """Set debug destination.
 
1032
 
 
1033
        Arguments:
 
1034
          path -- path (string) to the directory where debuging
 
1035
                  files will be created
 
1036
          scope -- see the documentation of this class.
 
1037
        
 
1038
        """
 
1039
        assert isinstance(val, string)
 
1040
 
 
1041
        self._conn.send_command('SET', scope.ALL, 'DEBUG_DESTINATION', val)
 
1042
 
 
1043
    def block_begin(self):
 
1044
        """Begin an SSIP block.
 
1045
 
 
1046
        See SSIP documentation for more details about blocks.
 
1047
 
 
1048
        """
 
1049
        self._conn.send_command('BLOCK', 'BEGIN')
 
1050
 
 
1051
    def block_end(self):
 
1052
        """Close an SSIP block.
 
1053
 
 
1054
        See SSIP documentation for more details about blocks.
 
1055
 
 
1056
        """
 
1057
        self._conn.send_command('BLOCK', 'END')
 
1058
 
 
1059
    def close(self):
 
1060
        """Close the connection to Speech Dispatcher."""
 
1061
        if hasattr(self, '_conn'):
 
1062
            self._conn.close()
 
1063
            del self._conn
 
1064
 
 
1065
 
 
1066
class Client(SSIPClient):
 
1067
    """A DEPRECATED backwards-compatible API.
 
1068
 
 
1069
    This Class is provided only for backwards compatibility with the prevoius
 
1070
    unofficial API.  It will be removed in future versions.  Please use either
 
1071
    'SSIPClient' or 'Speaker' interface instead.  As deprecated, the API is no
 
1072
    longer documented.
 
1073
 
 
1074
    """
 
1075
    def __init__(self, name=None, client=None, **kwargs):
 
1076
        name = name or client or 'python'
 
1077
        super(Client, self).__init__(name, **kwargs)
 
1078
        
 
1079
    def say(self, text, priority=Priority.MESSAGE):
 
1080
        self.set_priority(priority)
 
1081
        self.speak(text)
 
1082
 
 
1083
    def char(self, char, priority=Priority.TEXT):
 
1084
        self.set_priority(priority)
 
1085
        super(Client, self).char(char)
 
1086
 
 
1087
    def key(self, key, priority=Priority.TEXT):
 
1088
        self.set_priority(priority)
 
1089
        super(Client, self).key(key)
 
1090
 
 
1091
    def sound_icon(self, sound_icon, priority=Priority.TEXT):
 
1092
        self.set_priority(priority)
 
1093
        super(Client, self).sound_icon(sound_icon)
 
1094
        
 
1095
 
 
1096
class Speaker(SSIPClient):
 
1097
    """Extended Speech Dispatcher Interface.
 
1098
 
 
1099
    This class provides an extended intercace to Speech Dispatcher
 
1100
    functionality and tries to hide most of the lower level details of SSIP
 
1101
    (such as a more sophisticated handling of blocks and priorities and
 
1102
    advanced event notifications) under a more convenient API.
 
1103
    
 
1104
    Please note that the API is not yet stabilized and thus is subject to
 
1105
    change!  Please contact the authors if you plan using it and/or if you have
 
1106
    any suggestions.
 
1107
 
 
1108
    Well, in fact this class is currently not implemented at all.  It is just a
 
1109
    draft.  The intention is to hide the SSIP details and provide a generic
 
1110
    interface practical for screen readers.
 
1111
    
 
1112
    """
 
1113
 
 
1114
 
 
1115
# Deprecated but retained for backwards compatibility
 
1116
 
 
1117
# This class was introduced in 0.7 but later renamed to CommunicationMethod
 
1118
class ConnectionMethod(object):
 
1119
    """Constants describing the possible methods of connection to server.
 
1120
 
 
1121
    Retained for backwards compatibility but DEPRECATED. See CommunicationMethod."""
 
1122
    UNIX_SOCKET = 'unix_socket'
 
1123
    """Unix socket communication using a filesystem path"""
 
1124
    INET_SOCKET = 'inet_socket'
 
1125
    """Inet socket communication using a host and port"""