native-client-api: initial implementation.
authorKrisztian Litkey <kli@iki.fi>
Fri, 20 Dec 2013 14:59:04 +0000 (16:59 +0200)
committerKrisztian Litkey <kli@iki.fi>
Fri, 10 Jan 2014 15:08:06 +0000 (17:08 +0200)
Initial implementation of a 'native' client API, one
that uses plain sockets intead of D-Bus for IPC.

configure.ac
src/Makefile.am
src/plugins/native-client-interface/Makefile [new file with mode: 0644]
src/plugins/native-client-interface/native-client.c [new file with mode: 0644]
src/plugins/native-client-interface/native-client.h [new file with mode: 0644]
src/plugins/native-client-interface/native-config.h [new file with mode: 0644]
src/plugins/native-client-interface/native-messages.c [new file with mode: 0644]
src/plugins/native-client-interface/native-messages.h [new file with mode: 0644]
src/plugins/native-client-interface/native-server.c [new file with mode: 0644]
src/plugins/native-client-interface/test-client.c [new file with mode: 0644]

index 59506d6..813937e 100644 (file)
@@ -94,12 +94,15 @@ AC_SUBST(WARNING_CFLAGS)
 # Check for the necessary Murphy components.
 PKG_CHECK_MODULES(MURPHY_COMMON,    murphy-common)
 PKG_CHECK_MODULES(MURPHY_PULSE,     murphy-pulse)
+PKG_CHECK_MODULES(MURPHY_GLIB,      murphy-glib)
 PKG_CHECK_MODULES(MURPHY_RESOURCE,  murphy-resource)
 PKG_CHECK_MODULES(MURPHY_BREEDLINE, breedline-murphy)
 AC_SUBST(MURPHY_COMMON_CFLAGS)
 AC_SUBST(MURPHY_COMMON_LIBS)
 AC_SUBST(MURPHY_PULSE_CFLAGS)
 AC_SUBST(MURPHY_PULSE_LIBS)
+AC_SUBST(MURPHY_GLIB_CFLAGS)
+AC_SUBST(MURPHY_GLIB_LIBS)
 AC_SUBST(MURPHY_RESOURCE_CFLAGS)
 AC_SUBST(MURPHY_RESOURCE_LIBS)
 AC_SUBST(MURPHY_BREEDLINE_CFLAGS)
@@ -271,6 +274,7 @@ AC_CONFIG_FILES([build-aux/shave
                 Makefile
                  utils/Makefile
                  src/Makefile
+                 src/plugins/native-client-interface/srs-native.pc
                 ])
 AC_OUTPUT
 
index 38bceac..284a07d 100644 (file)
@@ -4,6 +4,11 @@ bin_PROGRAMS       =
 plugindir          = $(libdir)/srs/plugins
 plugin_LTLIBRARIES =
 
+lib_LTLIBRARIES    =
+EXTRA_DIST         =
+pkgconfig_DATA     =
+pkgconfigdir       = ${libdir}/pkgconfig
+
 AM_CPPFLAGS        = -I$(top_builddir)
 AM_CFLAGS          = $(GLIB_CFLAGS)
 
@@ -50,7 +55,7 @@ srs_daemon_LDFLAGS =                          \
                -rdynamic
 
 if DBUS_ENABLED
-# SRS command line test clinet
+# SRS command line test client
 bin_PROGRAMS += srs-client
 
 srs_client_SOURCES =                           \
@@ -93,6 +98,69 @@ plugin_dbus_client_la_LIBADD =                       \
                $(MURPHY_DBUS_LIBS)
 endif
 
+# Native client interface plugin
+plugin_LTLIBRARIES += plugin-native-client.la
+
+plugin_native_client_la_SOURCES =                                      \
+               plugins/native-client-interface/native-server.c         \
+               plugins/native-client-interface/native-messages.c
+
+plugin_native_client_la_CFLAGS =               \
+               $(AM_CFLAGS)                    \
+               $(MURPHY_COMMON_CFLAGS)
+
+plugin_native_client_la_LDFLAGS =              \
+               -module -avoid-version
+
+plugin_native_client_la_LIBADD =               \
+               $(MURPHY_COMMON_LIBS)
+
+# Native client library
+lib_LTLIBRARIES += libsrs-native.la
+EXTRA_DIST      += plugins/native-client-interface/srs-native.pc
+pkgconfig_DATA  += plugins/native-client-interface/srs-native.pc
+
+libsrs_native_ladir      = $(includedir)/srs
+libsrs_native_la_HEADERS =     \
+               plugins/native-client-interface/native-client.h \
+               plugins/native-client-interface/native-config.h \
+               plugins/native-client-interface/native-messages.h
+
+libsrs_native_la_SOURCES =     \
+               plugins/native-client-interface/native-client.c \
+               plugins/native-client-interface/native-messages.c
+
+libsrs_native_la_CFLAGS =      \
+               $(AM_CFLAGS)    \
+               $(MURPHY_COMMON_CFLAGS)
+
+libsrs_native_la_LIBADD =      \
+               $(MURPHY_COMMON_LIBS)
+
+# native client API command line test client
+bin_PROGRAMS += srs-native-client
+
+srs_native_client_SOURCES =                                            \
+               plugins/native-client-interface/test-client.c
+
+srs_native_client_CFLAGS =                     \
+               $(AM_CFLAGS)                    \
+               $(MURPHY_PULSE_CFLAGS)          \
+               $(MURPHY_BREEDLINE_CFLAGS)      \
+               $(MURPHY_COMMON_CFLAGS)         \
+               $(MURPHY_GLIB_FLAGS)
+
+srs_native_client_LDADD =                      \
+               $(MURPHY_PULSE_LIBS)            \
+               $(MURPHY_BREEDLINE_LIBS)        \
+               $(MURPHY_COMMON_LIBS)           \
+               $(MURPHY_GLIB_LIBS)             \
+               $(PULSE_LIBS)                   \
+               libsrs-native.la
+
+libsrs_native_client_la_LDFLAGS =      \
+               -version-info @SRS_VERSION_INFO@
+
 # SRS fake speech engine plugin
 plugin_LTLIBRARIES += plugin-fake-speech.la
 
diff --git a/src/plugins/native-client-interface/Makefile b/src/plugins/native-client-interface/Makefile
new file mode 100644 (file)
index 0000000..adcfe7e
--- /dev/null
@@ -0,0 +1,5 @@
+all:
+       $(MAKE) -C ../.. $@
+
+%:
+       $(MAKE) -C ../.. $(MAKECMDGOALS)
diff --git a/src/plugins/native-client-interface/native-client.c b/src/plugins/native-client-interface/native-client.c
new file mode 100644 (file)
index 0000000..0f0ece8
--- /dev/null
@@ -0,0 +1,765 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/glib-glue.h>
+#include <murphy/common/transport.h>
+
+#include "native-messages.h"
+#include "native-client.h"
+#include "native-config.h"
+
+
+/*
+ * SRS client library context
+ */
+
+struct srs_s {
+    mrp_mainloop_t        *ml;           /* main loop */
+    mrp_transport_t       *t;            /* transport to server */
+    void                  *user_data;    /* opaque client user data */
+    char                  *name;         /* client name */
+    char                  *appclass;     /* client application class */
+    char                 **commands;     /* client speech command set */
+    size_t                 ncommand;     /* number of speech commands */
+    srs_connect_notify_t   conn_notify;  /* connection notification callback */
+    srs_focus_notify_t     focus_notify; /* focus notification callback */
+    srs_command_notify_t   cmd_notify;   /* command notification callback */
+    int                    registered:1; /* whether we're registered */
+    mrp_list_hook_t        reqq;         /* pending request queue */
+    uint32_t               reqno;        /* next request number */
+    mrp_list_hook_t        voiceq;       /* pending rendering queue */
+    uint32_t               cvid;         /* next client voice id */
+};
+
+
+typedef struct {
+    mrp_list_hook_t      hook;
+    uint32_t             svid;
+    uint32_t             cvid;
+    srs_render_notify_t  cb;
+    void                *cb_data;
+    int                  cancelled : 1;
+} voice_req_t;
+
+
+typedef union {
+    voice_req_t voice_req;
+    struct {
+        srs_voiceqry_notify_t  cb;
+        void                  *cb_data;
+    } voice_qry;
+    struct {
+        uint32_t id;
+    } voice_ccl;
+} request_data_t;
+
+typedef struct {
+    mrp_list_hook_t hook;                /* hook to request queue */
+    uint32_t        reqno;               /* request number */
+    uint32_t        type;                /* request type */
+    request_data_t  data;                /* type-specific request data */
+} request_t;
+
+
+
+
+static void status_reply(srs_t *srs, srs_rpl_status_t *rpl);
+static void rendervoice_reply(srs_t *srs, srs_rpl_voice_t *rpl);
+static void queryvoices_reply(srs_t *srs, srs_rpl_voiceqry_t *rpl);
+static void focus_event(srs_t *srs, srs_evt_focus_t *evt);
+static void command_event(srs_t *srs, srs_evt_command_t *evt);
+static void voice_event(srs_t *srs, srs_evt_voice_t *evt);
+static mrp_mainloop_t *srs_mml;          /* Murphy mainloop to use, if any */
+static GMainLoop      *srs_gml;          /* GMainLoop to use, if any */
+
+
+static void recvfrom_message(mrp_transport_t *t, void *data, uint32_t type_id,
+                             mrp_sockaddr_t *addr, socklen_t addrlen,
+                             void *user_data)
+{
+    MRP_UNUSED(t);
+    MRP_UNUSED(data);
+    MRP_UNUSED(type_id);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+    MRP_UNUSED(user_data);
+
+    mrp_log_info("received a message of type 0x%x from the server", type_id);
+}
+
+
+static void recv_message(mrp_transport_t *t, void *data, uint32_t type_id,
+                         void *user_data)
+{
+    srs_t     *srs = (srs_t *)user_data;
+    srs_msg_t *msg = (srs_msg_t *)data;
+    request_t *req;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(user_data);
+
+    mrp_log_info("received a message of type 0x%x from the server", type_id);
+
+    switch (msg->type) {
+    case SRS_REPLY_STATUS:
+        status_reply(srs, &msg->status_rpl);
+        break;
+    case SRS_REPLY_RENDERVOICE:
+        rendervoice_reply(srs, &msg->voice_rpl);
+        break;
+    case SRS_REPLY_QUERYVOICES:
+        queryvoices_reply(srs, &msg->voice_lst);
+        break;
+    case SRS_EVENT_FOCUS:
+        focus_event(srs, &msg->focus_evt);
+        break;
+    case SRS_EVENT_COMMAND:
+        command_event(srs, &msg->command_evt);
+        break;
+    case SRS_EVENT_VOICE:
+        voice_event(srs, &msg->voice_evt);
+        break;
+
+    default:
+        mrp_log_error("Received unknown message of type 0x%x.", type_id);
+    }
+
+}
+
+
+static void closed_event(mrp_transport_t *t, int error, void *user_data)
+{
+    srs_t *srs    = (srs_t *)user_data;
+    int    status = 0;
+    char  *msg    = !error ? "connection closed" : "connection error";
+
+    mrp_debug("transport closed by server");
+
+    srs->conn_notify(srs, status, msg, srs->user_data);
+}
+
+
+static int queue_request(srs_t *srs, srs_msg_t *req, request_data_t *data)
+{
+    request_t *r;
+
+    if ((r = mrp_allocz(sizeof(*r))) == NULL)
+        return -1;
+
+    mrp_list_init(&r->hook);
+    r->type  = req->any_req.type;
+    r->reqno = req->any_req.reqno = srs->reqno++;
+
+    if (data != NULL)
+        r->data = *data;
+
+    if (send_message(srs->t, req) == 0) {
+        mrp_list_append(&srs->reqq, &r->hook);
+        return 0;
+    }
+    else {
+        mrp_free(r);
+        return -1;
+    }
+}
+
+
+static request_t *find_request(srs_t *srs, uint32_t reqno)
+{
+    mrp_list_hook_t *p, *n;
+    request_t       *r;
+
+    mrp_list_foreach(&srs->reqq, p, n) {
+        r = mrp_list_entry(p, typeof(*r), hook);
+
+        if (r->reqno == reqno)
+            return r;
+    }
+
+    return NULL;
+}
+
+
+static void purge_request(request_t *r)
+{
+    mrp_list_delete(&r->hook);
+    mrp_free(r);
+}
+
+
+static void purge_reqq(srs_t *srs)
+{
+    mrp_list_hook_t *p, *n;
+    request_t       *r;
+
+    mrp_list_foreach(&srs->reqq, p, n) {
+        r = mrp_list_entry(p, typeof(*r), hook);
+        purge_request(r);
+    }
+}
+
+
+static voice_req_t *add_voice_req(srs_t *srs, voice_req_t *req)
+{
+    voice_req_t *r;
+
+    if ((r = mrp_allocz(sizeof(*r))) == NULL)
+        return NULL;
+
+    mrp_list_init(&r->hook);
+    r->svid    = req->svid;
+    r->cvid    = req->cvid;
+    r->cb      = req->cb;
+    r->cb_data = req->cb_data;
+
+    mrp_list_append(&srs->voiceq, &r->hook);
+
+    return r;
+}
+
+
+static voice_req_t *voice_req_for_cvid(srs_t *srs, uint32_t cvid)
+{
+    mrp_list_hook_t *p, *n;
+    voice_req_t     *r;
+
+    mrp_list_foreach(&srs->voiceq, p, n) {
+        r = mrp_list_entry(p, typeof(*r), hook);
+
+        if (r->cvid == cvid)
+            return r;
+    }
+
+    return NULL;
+}
+
+
+static voice_req_t *voice_req_for_svid(srs_t *srs, uint32_t svid)
+{
+    mrp_list_hook_t *p, *n;
+    voice_req_t     *r;
+
+    mrp_list_foreach(&srs->voiceq, p, n) {
+        r = mrp_list_entry(p, typeof(*r), hook);
+
+        if (r->svid == svid)
+            return r;
+    }
+
+    return NULL;
+}
+
+
+static request_t *request_for_cvid(srs_t *srs, uint32_t cvid)
+{
+    mrp_list_hook_t *p, *n;
+    request_t       *r;
+
+    mrp_list_foreach(&srs->reqq, p, n) {
+        r = mrp_list_entry(p, typeof(*r), hook);
+
+        if (r->type == SRS_REQUEST_RENDERVOICE &&
+            r->data.voice_req.cvid == cvid)
+            return r;
+    }
+
+    return NULL;
+}
+
+
+static void purge_voice_req(voice_req_t *r)
+{
+    mrp_list_delete(&r->hook);
+    mrp_free(r);
+}
+
+
+static void purge_voiceq(srs_t *srs)
+{
+    mrp_list_hook_t *p, *n;
+    voice_req_t     *r;
+
+    mrp_list_foreach(&srs->voiceq, p, n) {
+        r = mrp_list_entry(p, typeof(*r), hook);
+        purge_voice_req(r);
+    }
+}
+
+
+void srs_set_mainloop(mrp_mainloop_t *ml)
+{
+    if (srs_mml == NULL) {
+        srs_mml = ml;
+        return;
+    }
+
+    if (ml == NULL) {
+        srs_mml = NULL;
+        return;
+    }
+
+    if (srs_mml != ml)
+        mrp_log_warning("SRS native client mainloop already set (to %p).",
+                        srs_mml);
+}
+
+
+void srs_set_gmainloop(GMainLoop *gml)
+{
+    if (srs_gml == NULL) {
+        srs_gml = g_main_loop_ref(gml);
+        return;
+    }
+
+    if (gml == NULL) {
+        g_main_loop_unref(srs_gml);
+        srs_gml = NULL;
+        return;
+    }
+
+    if (srs_gml != gml)
+        mrp_log_warning("SRS native client GMainLoop already set (to %p).",
+                        srs_gml);
+}
+
+
+srs_t *srs_create(const char *name, const char *appclass, char **commands,
+                  size_t ncommand, srs_connect_notify_t conn_notify,
+                  srs_focus_notify_t focus_notify,
+                  srs_command_notify_t cmd_notify, void *user_data)
+{
+    srs_t *srs;
+    int    i;
+
+    if (conn_notify == NULL)
+        return NULL;
+
+    if ((srs = mrp_allocz(sizeof(*srs))) == NULL)
+        return NULL;
+
+    mrp_list_init(&srs->reqq);
+    mrp_list_init(&srs->voiceq);
+    srs->reqno = 1;
+    srs->cvid  = 1;
+
+    if (srs_mml != NULL)
+        srs->ml = srs_mml;
+    else if (srs_gml != NULL)
+        srs->ml = mrp_mainloop_glib_get(srs_gml);
+    else
+        srs->ml = mrp_mainloop_create();
+
+    if (srs->ml == NULL)
+        goto fail;
+
+    srs->user_data    = user_data;
+    srs->name         = mrp_strdup(name);
+    srs->appclass     = mrp_strdup(appclass);
+    srs->conn_notify  = conn_notify;
+    srs->focus_notify = focus_notify;
+    srs->cmd_notify   = cmd_notify;
+
+    if (srs->name == NULL || srs->appclass == NULL)
+        goto fail;
+
+    if (ncommand > 0) {
+        if (cmd_notify == NULL)
+            goto fail;
+
+        srs->ncommand = ncommand;
+        srs->commands = mrp_allocz_array(char *, ncommand);
+
+        if (srs->commands == NULL)
+            goto fail;
+
+        for (i = 0; i < srs->ncommand; i++) {
+            srs->commands[i] = mrp_strdup(commands[i]);
+
+            if (srs->commands[i] == NULL)
+                goto fail;
+        }
+    }
+
+    return srs;
+
+ fail:
+    srs_destroy(srs);
+
+    return NULL;
+}
+
+
+void srs_destroy(srs_t *srs)
+{
+    int i;
+
+    if (srs == NULL)
+        return;
+
+    mrp_transport_destroy(srs->t);
+    purge_reqq(srs);
+
+    if (srs->ml != srs_mml)
+        mrp_mainloop_destroy(srs->ml);
+
+    mrp_free(srs->name);
+    mrp_free(srs->appclass);
+
+    if (srs->commands != NULL) {
+        for (i = 0; i < srs->ncommand; i++)
+            mrp_free(srs->commands[i]);
+
+        mrp_free(srs->commands);
+    }
+
+    mrp_free(srs);
+}
+
+
+int srs_connect(srs_t *srs, const char *server, int reconnect)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvnative     = recv_message     },
+        { .recvnativefrom = recvfrom_message },
+        .closed           = closed_event  ,
+        .connection       = NULL
+    };
+
+    mrp_sockaddr_t      addr;
+    socklen_t           alen;
+    const char         *atype, *opt, *val;
+    int                 flags;
+    void               *typemap;
+    srs_req_register_t  reg;
+
+    if (server == NULL)
+        server = DEFAULT_ADDRESS;
+
+    alen = mrp_transport_resolve(NULL, server, &addr, sizeof(addr), &atype);
+
+    if (alen <= 0)
+        return -1;
+
+    if ((typemap = register_message_types()) == NULL)
+        return -1;
+
+    flags  = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_MODE_NATIVE;
+    srs->t = mrp_transport_create(srs->ml, atype, &evt, srs, flags);
+
+    if (srs->t == NULL)
+        return -1;
+
+    if (!mrp_transport_setopt(srs->t, "type-map", typemap))
+        return -1;
+
+    if (!mrp_transport_connect(srs->t, &addr, alen))
+        return -1;
+
+    reg.type     = SRS_REQUEST_REGISTER;
+    reg.name     = srs->name;
+    reg.appclass = srs->appclass;
+    reg.commands = srs->commands;
+    reg.ncommand = srs->ncommand;
+
+    return queue_request(srs, (srs_msg_t *)&reg, NULL);
+}
+
+
+void srs_disconnect(srs_t *srs)
+{
+    if (srs == NULL)
+        return;
+
+    mrp_transport_destroy(srs->t);
+    srs->t = NULL;
+    purge_reqq(srs);
+    srs->registered = 0;
+}
+
+
+static int check_connection(srs_t *srs)
+{
+    if (srs == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (!srs->registered) {
+        errno = ENOTCONN;
+        return -1;
+    }
+}
+
+
+int srs_request_focus(srs_t *srs, srs_voice_focus_t focus)
+{
+    srs_req_focus_t req;
+
+    if (check_connection(srs) < 0)
+        return -1;
+
+    req.type  = SRS_REQUEST_FOCUS;
+    req.focus = focus;
+
+    return queue_request(srs, (srs_msg_t *)&req, NULL);
+}
+
+
+uint32_t srs_render_voice(srs_t *srs, const char *msg, const char *voice,
+                          int timeout, int events, srs_render_notify_t cb,
+                          void *cb_data)
+{
+    srs_req_voice_t req;
+    request_data_t  data;
+
+    if (check_connection(srs) < 0)
+        return -1;
+
+    req.type    = SRS_REQUEST_RENDERVOICE;
+    req.msg     = (char *)msg;
+    req.voice   = (char *)voice;
+    req.timeout = timeout;
+    req.events  = events;
+
+    data.voice_req.cvid    = srs->cvid++;
+    data.voice_req.svid    = SRS_VOICE_INVALID;
+    data.voice_req.cb      = cb;
+    data.voice_req.cb_data = cb_data;
+
+    return queue_request(srs, (srs_msg_t *)&req, &data);
+}
+
+
+int srs_cancel_voice(srs_t *srs, uint32_t id)
+{
+    srs_ccl_voice_t  req;
+    request_data_t   data;
+    voice_req_t     *vr;
+    request_t       *vreq;
+
+    if (check_connection(srs) < 0)
+        return -1;
+
+    if ((vr = voice_req_for_cvid(srs, id)) != NULL)
+        id = vr->svid;
+    else {
+        if ((vreq = request_for_cvid(srs, id)) != NULL) {
+            vreq->data.voice_req.cancelled = TRUE;
+            return 0;
+        }
+        else {
+            errno = ENOENT;
+            return -1;
+        }
+    }
+
+    req.type = SRS_REQUEST_CANCELVOICE;
+    req.id   = id;
+
+    data.voice_ccl.id = id;
+
+    return queue_request(srs, (srs_msg_t *)&req, &data);
+}
+
+
+int srs_query_voices(srs_t *srs, const char *language,
+                     srs_voiceqry_notify_t cb, void *cb_data)
+{
+    srs_req_voiceqry_t req;
+    request_data_t     data;
+
+    if (check_connection(srs) < 0)
+        return -1;
+
+    req.type = SRS_REQUEST_QUERYVOICES;
+    req.lang = (char *)(language ? language : "");
+
+    data.voice_qry.cb      = cb;
+    data.voice_qry.cb_data = cb_data;
+
+    return queue_request(srs, (srs_msg_t *)&req, &data);
+}
+
+
+static void status_reply(srs_t *srs, srs_rpl_status_t *rpl)
+{
+    request_t *req    = find_request(srs, rpl->reqno);
+    int        status = rpl->status;
+
+    if (req == NULL) {
+        mrp_log_warning("Received reply for unkown request #%u.", rpl->reqno);
+        return;
+    }
+
+    switch (req->type) {
+    case SRS_REQUEST_REGISTER:
+        mrp_debug("Registration to server %s.",
+                  status == 0 ? "successful" : "failed");
+
+        srs->registered = (status == 0);
+        srs->conn_notify(srs, status == 0, rpl->msg, srs->user_data);
+
+        if (!srs->registered) {
+            purge_reqq(srs);
+            purge_voiceq(srs);
+        }
+        break;
+
+    case SRS_REQUEST_UNREGISTER:
+        mrp_debug("Unregistering from server %s.",
+                  rpl->status == SRS_STATUS_OK ? "successful" : "failed");
+
+        srs->registered = FALSE;
+        purge_reqq(srs);
+        purge_voiceq(srs);
+        break;
+
+    case SRS_REQUEST_FOCUS:
+        mrp_debug("Focus request %s on server.",
+                  status == 0 ? "succeeded" : "failed");
+        break;
+
+    default:
+        mrp_log_warning("Dequeued request with invalid type 0x%x.", req->type);
+    }
+
+    purge_request(req);
+}
+
+
+static void rendervoice_reply(srs_t *srs, srs_rpl_voice_t *rpl)
+{
+    request_t         *req = find_request(srs, rpl->reqno);
+    voice_req_t       *vr;
+    srs_voice_event_t  e;
+
+    if (req == NULL) {
+        mrp_log_warning("Got reply for unknown voice request #%u.", rpl->reqno);
+        return;
+    }
+
+    vr = &req->data.voice_req;
+
+    if (rpl->id == SRS_VOICE_INVALID) {
+        if (vr->cb != NULL) {
+            e.type = SRS_VOICE_EVENT_ABORTED;
+            e.id   = vr->cvid;
+            e.data.progress.pcnt = 0;
+            e.data.progress.msec = 0;
+
+            vr->cb(srs, &e, srs->user_data, vr->cb_data);
+        }
+    }
+    else {
+        if (req->data.voice_req.cancelled)
+            srs_cancel_voice(srs, rpl->id);
+        else {
+            vr->svid = rpl->id;
+
+            if (add_voice_req(srs, vr) == NULL)
+                mrp_log_error("Failed to add active voice request #%u.",
+                              vr->cvid);
+        }
+    }
+
+    purge_request(req);
+}
+
+
+static void voice_event(srs_t *srs, srs_evt_voice_t *evt)
+{
+    voice_req_t       *vr = voice_req_for_svid(srs, evt->id);
+    srs_voice_event_t  e;
+
+    mrp_debug("Got voice event 0x%x for #%u.", evt->event, evt->id);
+
+    if (vr == NULL) {
+        mrp_log_warning("Got event for unknown voice request #%u.", evt->id);
+        return;
+    }
+
+    e.type = evt->event;
+    e.id   = vr->cvid;
+    e.data.progress.pcnt = evt->pcnt;
+    e.data.progress.msec = evt->msec;
+
+    vr->cb(srs, &e, srs->user_data, vr->cb_data);
+
+    if (evt->event == SRS_VOICE_EVENT_COMPLETED ||
+        evt->event == SRS_VOICE_EVENT_TIMEOUT   ||
+        evt->event == SRS_VOICE_EVENT_ABORTED)
+        purge_voice_req(vr);
+}
+
+
+static void queryvoices_reply(srs_t *srs, srs_rpl_voiceqry_t *rpl)
+{
+    request_t             *req = find_request(srs, rpl->reqno);
+    srs_voiceqry_notify_t  cb;
+    void                  *cb_data;
+
+    mrp_debug("Got voice query response.");
+
+    if (req == NULL) {
+        mrp_log_warning("Received voice query response for unknown request.");
+        return;
+    }
+
+    cb      = req->data.voice_qry.cb;
+    cb_data = req->data.voice_qry.cb_data;
+
+    cb(srs, rpl->actors, rpl->nactor, srs->user_data, cb_data);
+
+    purge_request(req);
+}
+
+
+static void focus_event(srs_t *srs, srs_evt_focus_t *evt)
+{
+    mrp_debug("Got focus 0x%x.", evt->focus);
+
+    if (srs->focus_notify != NULL)
+        srs->focus_notify(srs, evt->focus, srs->user_data);
+}
+
+
+static void command_event(srs_t *srs, srs_evt_command_t *evt)
+{
+    mrp_debug("Got command event #%u.", evt->idx);
+
+    if (srs->cmd_notify != NULL)
+        srs->cmd_notify(srs, evt->idx, evt->tokens, evt->ntoken,
+                        srs->user_data);
+}
diff --git a/src/plugins/native-client-interface/native-client.h b/src/plugins/native-client-interface/native-client.h
new file mode 100644 (file)
index 0000000..8088969
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SRS_NATIVE_CLIENT_H__
+#define __SRS_NATIVE_CLIENT_H__
+
+#include <murphy/common/macros.h>
+#include <glib.h>
+
+#include "srs/daemon/client-api-types.h"
+#include "srs/daemon/voice-api-types.h"
+
+MRP_CDECL_BEGIN
+
+/** Opaque SRS client context type. */
+typedef struct srs_s srs_t;
+
+/** Connection status notification callback type. */
+typedef void (*srs_connect_notify_t)(srs_t *srs, int status, const char *msg,
+                                     void *user_data);
+
+/** Command notification callback type. */
+typedef void (*srs_command_notify_t)(srs_t *srs, int idx, char **tokens,
+                                     int ntoken, void *user_data);
+
+/** Focus notification callback type. */
+typedef void (*srs_focus_notify_t)(srs_t *srs, srs_voice_focus_t granted,
+                                   void *user_data);
+
+/** Voice rendering notification callback type. */
+typedef void (*srs_render_notify_t)(srs_t *srs, srs_voice_event_t *event,
+                                    void *user_data, void *notify_data);
+
+/** Voice query notification callback type. */
+typedef void (*srs_voiceqry_notify_t)(srs_t *srs, srs_voice_actor_t *actors,
+                                      int nactor, void *user_data,
+                                      void *notify_data);
+
+/** Use the given Murphy mainloop as the underlying mainloop. */
+void srs_set_mainloop(mrp_mainloop_t *ml);
+
+/** Use the given GMainLoop as the underlying mainloop. */
+void srs_set_gmainloop(GMainLoop *gml);
+
+/** Create an SRS client context with the given class and commands. */
+srs_t *srs_create(const char *name, const char *appclass, char **commands,
+                  size_t ncommand, srs_connect_notify_t connect_notify,
+                  srs_focus_notify_t focus_notify,
+                  srs_command_notify_t command_notify, void *user_data);
+
+/** Destroy the given SRS client context. */
+void srs_destroy(srs_t *srs);
+
+/** Try to establish a connection to the server at the given address. */
+int srs_connect(srs_t *srs, const char *server, int reconnect);
+
+/** Close connection to the server if there is one. */
+void srs_disconnect(srs_t *srs);
+
+/** Request the given type of focus. */
+int srs_request_focus(srs_t *srs, srs_voice_focus_t focus);
+
+/** Request rendering the given message, subscribing for the given events. */
+uint32_t srs_render_voice(srs_t *srs, const char *msg, const char *voice,
+                          int timeout, int events, srs_render_notify_t cb,
+                          void *cb_data);
+
+/** Cancel an ongoing voice render request. */
+int srs_cancel_voice(srs_t *srs, uint32_t id);
+
+/** Query the available voices for the given language (or all if omitted). */
+int srs_query_voices(srs_t *srs, const char *language,
+                     srs_voiceqry_notify_t cb, void *cb_data);
+
+MRP_CDECL_END
+
+#endif /* __SRS_NATIVE_CLIENT_H__ */
diff --git a/src/plugins/native-client-interface/native-config.h b/src/plugins/native-client-interface/native-config.h
new file mode 100644 (file)
index 0000000..796d039
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SRS_NATIVE_CLIENT_CONFIG_H__
+#define __SRS_NATIVE_CLIENT_CONFIG_H__
+
+/** Configuration key for specifying the transport address. */
+#define CONFIG_ADDRESS  "native.address"
+
+/** Default transport address. */
+/*#define DEFAULT_ADDRESS "unxs:@/srs/native-client"*/
+#define DEFAULT_ADDRESS "tcp4:127.0.0.1:4100"
+
+
+
+#endif /* __SRS_NATIVE_CLIENT_CONFIG_H__ */
diff --git a/src/plugins/native-client-interface/native-messages.c b/src/plugins/native-client-interface/native-messages.c
new file mode 100644 (file)
index 0000000..d856a91
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/native-types.h>
+
+#include "native-messages.h"
+
+static mrp_typemap_t *map = NULL;
+
+mrp_typemap_t *register_message_types(void)
+{
+    static bool          done       = false;
+    static mrp_typemap_t type_map[] = {
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_REGISTER    },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_UNREGISTER  },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_FOCUS       },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_RENDERVOICE },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_CANCELVOICE },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REQUEST_QUERYVOICES },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REPLY_STATUS        },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REPLY_RENDERVOICE   },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_VOICE_ACTOR         },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_REPLY_QUERYVOICES   },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_EVENT_FOCUS         },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_EVENT_COMMAND       },
+        { .typeid = MRP_INVALID_TYPE, .mapped = SRS_EVENT_VOICE         },
+        { MRP_INVALID_TYPE, MRP_INVALID_TYPE },
+    };
+
+    MRP_NATIVE_TYPE(reg_req, srs_req_register_t,
+                    MRP_UINT32(srs_req_register_t, type    , DEFAULT),
+                    MRP_UINT32(srs_req_register_t, reqno   , DEFAULT),
+                    MRP_STRING(srs_req_register_t, name    , DEFAULT),
+                    MRP_STRING(srs_req_register_t, appclass, DEFAULT),
+                    MRP_ARRAY (srs_req_register_t, commands, DEFAULT, SIZED,
+                               char *, ncommand),
+                    MRP_UINT32(srs_req_register_t, ncommand, DEFAULT));
+
+    MRP_NATIVE_TYPE(bye_req, srs_req_unregister_t,
+                    MRP_UINT32(srs_req_unregister_t, type  , DEFAULT),
+                    MRP_UINT32(srs_req_unregister_t, reqno , DEFAULT));
+
+    MRP_NATIVE_TYPE(status_rpl, srs_rpl_status_t,
+                    MRP_UINT32(srs_rpl_status_t, type  , DEFAULT),
+                    MRP_UINT32(srs_rpl_status_t, reqno , DEFAULT),
+                    MRP_UINT32(srs_rpl_status_t, status, DEFAULT),
+                    MRP_STRING(srs_rpl_status_t, msg   , DEFAULT));
+
+    MRP_NATIVE_TYPE(focus_req, srs_req_focus_t,
+                    MRP_UINT32(srs_req_focus_t, type  , DEFAULT),
+                    MRP_UINT32(srs_req_focus_t, reqno , DEFAULT),
+                    MRP_UINT32(srs_req_focus_t, focus, DEFAULT));
+
+    MRP_NATIVE_TYPE(focus_evt, srs_evt_focus_t,
+                    MRP_UINT32(srs_evt_focus_t, type  , DEFAULT),
+                    MRP_UINT32(srs_evt_focus_t, focus, DEFAULT));
+
+    MRP_NATIVE_TYPE(voice_req, srs_req_voice_t,
+                    MRP_UINT32(srs_req_voice_t, type    , DEFAULT),
+                    MRP_UINT32(srs_req_voice_t, reqno   , DEFAULT),
+                    MRP_STRING(srs_req_voice_t, msg     , DEFAULT),
+                    MRP_STRING(srs_req_voice_t, voice   , DEFAULT),
+                    MRP_UINT32(srs_req_voice_t, timeout , DEFAULT),
+                    MRP_UINT32(srs_req_voice_t, events  , DEFAULT));
+
+    MRP_NATIVE_TYPE(voice_rpl, srs_rpl_voice_t,
+                    MRP_UINT32(srs_rpl_voice_t, type , DEFAULT),
+                    MRP_UINT32(srs_rpl_voice_t, reqno, DEFAULT),
+                    MRP_UINT32(srs_rpl_voice_t, id   , DEFAULT));
+
+    MRP_NATIVE_TYPE(voice_ccl, srs_ccl_voice_t,
+                    MRP_UINT32(srs_ccl_voice_t, type , DEFAULT),
+                    MRP_UINT32(srs_ccl_voice_t, reqno, DEFAULT),
+                    MRP_UINT32(srs_ccl_voice_t, id   , DEFAULT));
+
+    MRP_NATIVE_TYPE(voice_evt, srs_evt_voice_t,
+                    MRP_UINT32(srs_evt_voice_t, type , DEFAULT),
+                    MRP_UINT32(srs_evt_voice_t, event, DEFAULT),
+                    MRP_UINT32(srs_evt_voice_t, id   , DEFAULT),
+                    MRP_DOUBLE(srs_evt_voice_t, pcnt , DEFAULT),
+                    MRP_UINT32(srs_evt_voice_t, msec , DEFAULT));
+
+    MRP_NATIVE_TYPE(voice_qry, srs_req_voiceqry_t,
+                    MRP_UINT32(srs_req_voiceqry_t, type , DEFAULT),
+                    MRP_UINT32(srs_req_voiceqry_t, reqno, DEFAULT),
+                    MRP_STRING(srs_req_voiceqry_t, lang , DEFAULT));
+
+    MRP_NATIVE_TYPE(voice_act, srs_voice_actor_t,
+                    MRP_UINT32(srs_voice_actor_t, id         , DEFAULT),
+                    MRP_STRING(srs_voice_actor_t, lang       , DEFAULT),
+                    MRP_STRING(srs_voice_actor_t, dialect    , DEFAULT),
+                    MRP_UINT16(srs_voice_actor_t, gender     , DEFAULT),
+                    MRP_UINT16(srs_voice_actor_t, age        , DEFAULT),
+                    MRP_STRING(srs_voice_actor_t, name       , DEFAULT),
+                    MRP_STRING(srs_voice_actor_t, description, DEFAULT));
+
+    MRP_NATIVE_TYPE(voiceqry_rpl, srs_rpl_voiceqry_t,
+                    MRP_UINT32(srs_rpl_voiceqry_t, type  , DEFAULT),
+                    MRP_UINT32(srs_rpl_voiceqry_t, reqno , DEFAULT),
+                    MRP_ARRAY (srs_rpl_voiceqry_t, actors, DEFAULT, SIZED,
+                               srs_voice_actor_t, nactor),
+                    MRP_UINT32(srs_rpl_voiceqry_t, nactor, DEFAULT));
+
+    MRP_NATIVE_TYPE(command_evt, srs_evt_command_t,
+                    MRP_UINT32(srs_evt_command_t, type, DEFAULT),
+                    MRP_UINT32(srs_evt_command_t, idx   , DEFAULT),
+                    MRP_ARRAY (srs_evt_command_t, tokens, DEFAULT, SIZED,
+                               char *, ntoken),
+                    MRP_UINT32(srs_evt_command_t, ntoken, DEFAULT));
+
+    struct {
+        uint32_t           id;
+        mrp_native_type_t *type;
+    } types[SRS_MSG_MAX] = {
+        { SRS_REQUEST_REGISTER   , &reg_req      },
+        { SRS_REQUEST_UNREGISTER , &bye_req      },
+        { SRS_REQUEST_FOCUS      , &focus_req    },
+        { SRS_REQUEST_RENDERVOICE, &voice_req    },
+        { SRS_REQUEST_CANCELVOICE, &voice_ccl    },
+        { SRS_REQUEST_QUERYVOICES, &voice_qry    },
+        { SRS_REPLY_STATUS       , &status_rpl   },
+        { SRS_REPLY_RENDERVOICE  , &voice_rpl    },
+        { SRS_VOICE_ACTOR        , &voice_act    },
+        { SRS_REPLY_QUERYVOICES  , &voiceqry_rpl },
+        { SRS_EVENT_FOCUS        , &focus_evt    },
+        { SRS_EVENT_COMMAND      , &command_evt  },
+        { SRS_EVENT_VOICE        , &voice_evt    },
+        { MRP_INVALID_TYPE       , NULL          },
+    }, *t;
+    mrp_typemap_t *m;
+
+    if (done)
+        return map;
+
+    for (t = types, m = type_map; t->type != NULL; t++, m++) {
+        if ((m->typeid = mrp_register_native(t->type)) == MRP_INVALID_TYPE)
+            return NULL;
+    }
+
+    done = true;
+    return (map = &type_map[0]);
+}
+
+
+uint32_t message_typeid(uint32_t type)
+{
+    if (map != NULL && 0 < type && type < SRS_MSG_MAX)
+        return map[type - 1].typeid;
+    else
+        return MRP_INVALID_TYPE;
+}
+
+
+uint32_t message_type(uint32_t typeid)
+{
+    mrp_typemap_t *m;
+
+    if (map != NULL) {
+        for (m = map; m->typeid != MRP_INVALID_TYPE; m++)
+            if (m->typeid == typeid)
+                return m->mapped;
+    }
+
+    return MRP_INVALID_TYPE;
+}
+
+
+int send_message(mrp_transport_t *t, srs_msg_t *msg)
+{
+    uint32_t typeid = message_typeid(msg->type);
+
+    if (typeid != MRP_INVALID_TYPE) {
+        if (mrp_transport_sendnative(t, msg, typeid))
+            return 0;
+    }
+    else
+        errno = EINVAL;
+
+    return -1;
+}
diff --git a/src/plugins/native-client-interface/native-messages.h b/src/plugins/native-client-interface/native-messages.h
new file mode 100644 (file)
index 0000000..8fb043b
--- /dev/null
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SRS_NATIVE_MESSAGES_H__
+#define __SRS_NATIVE_MESSAGES_H__
+
+#include <stdint.h>
+#include <murphy/common/native-types.h>
+#include <murphy/common/transport.h>
+
+#include "srs/daemon/voice-api-types.h"
+
+
+/*
+ * message types
+ */
+
+typedef enum {
+    SRS_MSG_UNKNOWN = 0,
+
+    SRS_REQUEST_REGISTER,
+    SRS_REQUEST_UNREGISTER,
+    SRS_REQUEST_FOCUS,
+    SRS_REQUEST_RENDERVOICE,
+    SRS_REQUEST_CANCELVOICE,
+    SRS_REQUEST_QUERYVOICES,
+
+    SRS_REPLY_STATUS,
+    SRS_REPLY_RENDERVOICE,
+    SRS_VOICE_ACTOR,
+    SRS_REPLY_QUERYVOICES,
+
+    SRS_EVENT_FOCUS,
+    SRS_EVENT_COMMAND,
+    SRS_EVENT_VOICE,
+
+    SRS_MSG_MAX
+} srs_msg_type_t;
+
+
+/*
+ * registration request
+ */
+
+typedef struct {
+    uint32_t   type;                     /* SRS_REQUEST_REGISTER */
+    uint32_t   reqno;                    /* request number */
+    char      *name;                     /* application name */
+    char      *appclass;                 /* application class */
+    char     **commands;                 /* speech commands */
+    uint32_t   ncommand;                 /* number of speech commands */
+} srs_req_register_t;
+
+
+/*
+ * unregistration request
+ */
+
+typedef struct {
+    uint32_t type;                       /* SRS_REQUEST_UNREGISTER */
+    uint32_t reqno;                      /* request number */
+} srs_req_unregister_t;
+
+
+/*
+ * error codes
+ */
+
+enum {
+    SRS_STATUS_OK = 0,                   /* success */
+    SRS_STATUS_FAILED,                   /* request failed */
+};
+
+
+/*
+ * a status reply
+ */
+
+typedef struct {
+    uint32_t  type;                      /* message type */
+    uint32_t  reqno;                     /* request number */
+    uint32_t  status;                    /* an error code, or 0 */
+    char     *msg;                       /* error message, if any */
+} srs_rpl_status_t;
+
+
+/*
+ * voice focus request
+ */
+
+typedef struct {
+    uint32_t type;                       /* SRS_REQUEST_FOCUS */
+    uint32_t reqno;                      /* request number */
+    uint32_t focus;                      /* current voice focus */
+} srs_req_focus_t;
+
+
+/*
+ * voice focus notification
+ */
+
+typedef struct {
+    uint32_t type;                       /* SRS_EVENT_FOCUS */
+    uint32_t focus;                      /* granted focus */
+} srs_evt_focus_t;
+
+
+/*
+ * voice render request
+ */
+
+typedef struct {
+    uint32_t  type;                      /* SRS_REQUEST_VOICERENDER */
+    uint32_t  reqno;                     /* request number */
+    char     *msg;                       /* message to render */
+    char     *voice;                     /* voice to use */
+    uint32_t  timeout;                   /* message timeout */
+    uint32_t  events;                    /* mask of events to notify about */
+} srs_req_voice_t;
+
+
+/*
+ * reply to voice render request
+ */
+
+typedef struct {
+    uint32_t  type;                      /* SRS_REPLY_RENDERVOICE */
+    uint32_t  reqno;                     /* request number */
+    uint32_t  id;                        /* client-side id */
+} srs_rpl_voice_t;
+
+
+/*
+ * voice cancel request
+ */
+
+typedef struct {
+    uint32_t  type;                      /* SRS_REQUEST_VOICECANCEL */
+    uint32_t  reqno;                     /* request number */
+    uint32_t  id;                        /* voice render id */
+} srs_ccl_voice_t;
+
+
+/*
+ * voice progress notification event
+ */
+
+typedef struct {
+    uint32_t type;                       /* SRS_EVENT_VOICE */
+    uint32_t event;                      /* SRS_VOICE_EVENT_* */
+    uint32_t id;                         /* voice render id */
+    double   pcnt;                       /* progress in percentages */
+    uint32_t msec;                       /* progress in milliseconds */
+} srs_evt_voice_t;
+
+
+/*
+ * voice query request
+ */
+
+typedef struct {
+    uint32_t  type;                      /* SRS_REQUEST_QUERYVOICES */
+    uint32_t  reqno;                     /* request number */
+    char     *lang;                      /* language to request voices for */
+} srs_req_voiceqry_t;
+
+
+/*
+ * voice query reply
+ */
+
+typedef struct {
+    uint32_t           type;             /* SRS_REPLY_QUERYVOICES */
+    uint32_t           reqno;            /* request number */
+    srs_voice_actor_t *actors;           /* queried actors */
+    uint32_t           nactor;           /* number of actors */
+} srs_rpl_voiceqry_t;
+
+
+/*
+ * command notification event
+ */
+
+typedef struct {
+    uint32_t   type;                     /* SRS_EVENT_COMMAND */
+    uint32_t   idx;                      /* client command index */
+    char     **tokens;                   /* command tokens */
+    uint32_t   ntoken;                   /* number of tokens */
+} srs_evt_command_t;
+
+
+/*
+ * a generic request or reply
+ */
+
+typedef struct {
+    uint32_t type;                       /* message type */
+    uint32_t reqno;                      /* request number */
+} srs_req_any_t;
+
+typedef srs_req_any_t srs_rpl_any_t;
+
+/*
+ * message
+ */
+
+typedef union {
+    uint32_t               type;
+    srs_req_any_t          any_req;
+    srs_rpl_any_t          any_rpl;
+    srs_req_register_t     reg_req;
+    srs_req_unregister_t   bye_req;
+    srs_rpl_status_t       status_rpl;
+    srs_req_focus_t        focus_req;
+    srs_evt_focus_t        focus_evt;
+    srs_req_voice_t        voice_req;
+    srs_rpl_voice_t        voice_rpl;
+    srs_ccl_voice_t        voice_ccl;
+    srs_evt_voice_t        voice_evt;
+    srs_req_voiceqry_t     voice_qry;
+    srs_rpl_voiceqry_t     voice_lst;
+    srs_evt_command_t      command_evt;
+} srs_msg_t;
+
+
+mrp_typemap_t *register_message_types(void);
+int send_message(mrp_transport_t *t, srs_msg_t *msg);
+uint32_t message_typeid(uint32_t type);
+uint32_t message_type(uint32_t typeid);
+
+#endif /* __SRS_NATIVE_MESSAGES_H__ */
diff --git a/src/plugins/native-client-interface/native-server.c b/src/plugins/native-client-interface/native-server.c
new file mode 100644 (file)
index 0000000..dd74b11
--- /dev/null
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <errno.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/native-types.h>
+
+#include "src/daemon/plugin.h"
+#include "src/daemon/client.h"
+#include "native-messages.h"
+#include "native-config.h"
+
+#define PLUGIN_NAME    "native-client"
+#define PLUGIN_DESCR   "Native client plugin for SRS."
+#define PLUGIN_AUTHORS "Krisztian Litkey <kli@iki.fi>"
+#define PLUGIN_VERSION "0.0.1"
+
+
+/*
+ * server runtime context
+ */
+
+typedef struct {
+    srs_plugin_t    *self;               /* our plugin instance */
+    const char      *address;            /* our transport address */
+    mrp_transport_t *lt;                 /* transport we listen on */
+    mrp_list_hook_t  clients;            /* connected clients */
+    int              next_id;            /* next client id */
+} server_t;
+
+
+/*
+ * a connected client
+ */
+
+typedef struct {
+    srs_client_t    *c;                  /* associated SRS client */
+    server_t        *s;                  /* server runtime context */
+    mrp_transport_t *t;                  /* transport towards this client */
+    mrp_list_hook_t  hook;               /* to list of native clients */
+    int              id;                 /* client id */
+} client_t;
+
+
+static int focus_notify(srs_client_t *c, srs_voice_focus_t focus);
+static int command_notify(srs_client_t *c, int idx, int ntoken, char **tokens,
+                          uint32_t *start, uint32_t *end,
+                          srs_audiobuf_t *audio);
+static int voice_notify(srs_client_t *c, srs_voice_event_t *event);
+
+static int reply_status(client_t *c, uint32_t reqno, int status,
+                        const char *msg);
+#define reply_register   reply_status
+#define reply_unregister reply_status
+#define reply_focus      reply_status
+static int reply_render(client_t *c, uint32_t reqno, uint32_t id);
+static int reply_voiceqry(client_t *c, uint32_t reqno,
+                          srs_voice_actor_t *actors, int nactor);
+
+static client_t *create_client(server_t *s, mrp_transport_t *lt)
+{
+    mrp_transport_t *t;
+    client_t        *c;
+
+    c = mrp_allocz(sizeof(*c));
+
+    if (c != NULL) {
+        mrp_list_init(&c->hook);
+
+        c->s  = s;
+        c->id = s->next_id++;
+        c->t  = mrp_transport_accept(lt, c, MRP_TRANSPORT_REUSEADDR);
+
+        if (c->t != NULL) {
+            mrp_list_append(&s->clients, &c->hook);
+
+            return c;
+        }
+
+        mrp_free(c);
+    }
+    else {
+        t = mrp_transport_accept(lt, NULL, MRP_TRANSPORT_REUSEADDR);
+        mrp_transport_destroy(t);
+    }
+
+    return NULL;
+}
+
+
+static void destroy_client(client_t *c)
+{
+    mrp_list_delete(&c->hook);
+
+    mrp_transport_destroy(c->t);
+    client_destroy(c->c);
+
+    mrp_free(c);
+}
+
+
+static void register_client(client_t *c, srs_req_register_t *req)
+{
+    static srs_client_ops_t ops = {
+        .notify_focus   = focus_notify,
+        .notify_command = command_notify,
+        .notify_render  = voice_notify,
+    };
+
+    srs_context_t  *srs    = c->s->self->srs;
+    char           *name   = req->name;
+    char           *appcls = req->appclass;
+    char          **cmds   = req->commands;
+    int             ncmd   = req->ncommand;
+    char            id[64];
+
+    snprintf(id, sizeof(id), "native-client-%d", c->id);
+
+    mrp_debug("received register request from native client #%d", c->id);
+
+    c->c = client_create(srs, SRS_CLIENT_TYPE_EXTERNAL, name, appcls,
+                         cmds, ncmd, id, &ops, c);
+
+    if (c->c != NULL)
+        reply_register(c, req->reqno, SRS_STATUS_OK, "OK");
+    else {
+        reply_register(c, req->reqno, SRS_STATUS_FAILED, "failed");
+        destroy_client(c);
+    }
+}
+
+
+static void unregister_client(client_t *c, srs_req_unregister_t *req)
+{
+    mrp_debug("received unregister request from native client #%d", c->id);
+
+    reply_unregister(c, req->reqno, SRS_STATUS_OK, "OK");
+    destroy_client(c);
+}
+
+
+static void request_focus(client_t *c, srs_req_focus_t *req)
+{
+    mrp_debug("received focus request from native client #%d", c->id);
+
+    if (client_request_focus(c->c, req->focus))
+        reply_focus(c, req->reqno, SRS_STATUS_OK, "OK");
+    else
+        reply_focus(c, req->reqno, SRS_STATUS_FAILED, "failed");
+}
+
+
+static void request_voice(client_t *c, srs_req_voice_t *req)
+{
+    const char *msg     = req->msg;
+    const char *voice   = req->voice;
+    int         timeout = req->timeout;
+    int         events  = req->events;
+    uint32_t    reqid;
+
+    mrp_debug("received voice render request from native client #%d", c->id);
+
+    reqid = client_render_voice(c->c, msg, voice, timeout, events);
+
+    if (reqid != SRS_VOICE_INVALID)
+        reply_render(c, req->reqno, reqid);
+    else
+        reply_status(c, req->reqno, SRS_STATUS_FAILED, "failed");
+}
+
+
+static void cancel_voice(client_t *c, srs_ccl_voice_t *req)
+{
+    mrp_debug("received voice cancel request from native client #%d", c->id);
+
+    client_cancel_voice(c->c, req->id);
+    reply_status(c, req->reqno, SRS_STATUS_OK, "OK");
+}
+
+
+static void query_voices(client_t *c, srs_req_voiceqry_t *req)
+{
+    srs_voice_actor_t  *actors;
+    int                 nactor;
+
+    mrp_debug("received voice query request from native client #%d", c->id);
+
+    nactor = client_query_voices(c->c, req->lang, &actors);
+    reply_voiceqry(c, req->reqno, actors, nactor);
+    client_free_queried_voices(actors);
+}
+
+
+static int reply_status(client_t *c, uint32_t reqno, int status,
+                        const char *msg)
+{
+    srs_rpl_status_t rpl;
+
+    mrp_debug("replying <%d, %s> to request #%d from native client #%d",
+              status, msg, reqno, c->id);
+
+    rpl.type   = SRS_REPLY_STATUS;
+    rpl.reqno  = reqno;
+    rpl.status = status;
+    rpl.msg    = (char *)msg;
+
+    return send_message(c->t, (srs_msg_t *)&rpl);
+}
+
+
+static int reply_render(client_t *c, uint32_t reqno, uint32_t id)
+{
+    srs_rpl_voice_t rpl;
+
+    mrp_debug("replying <#%u> to request #%d from native client #%d", id,
+              reqno, c->id);
+
+    rpl.type  = SRS_REPLY_RENDERVOICE;
+    rpl.reqno = reqno;
+    rpl.id    = id;
+
+    return send_message(c->t, (srs_msg_t *)&rpl);
+}
+
+
+static int focus_notify(srs_client_t *client, srs_voice_focus_t focus)
+{
+    client_t        *c = (client_t *)client->user_data;
+    srs_evt_focus_t  evt;
+
+    mrp_debug("relaying focus event to native client #%d", c->id);
+
+    evt.type  = SRS_EVENT_FOCUS;
+    evt.focus = focus;
+
+    return send_message(c->t, (srs_msg_t *)&evt);
+}
+
+
+static int command_notify(srs_client_t *client, int idx,
+                          int ntoken, char **tokens, uint32_t *start,
+                          uint32_t *end, srs_audiobuf_t *audio)
+{
+    client_t          *c = (client_t *)client->user_data;
+    srs_evt_command_t  evt;
+
+    MRP_UNUSED(start);
+    MRP_UNUSED(end);
+    MRP_UNUSED(audio);
+
+    mrp_debug("relaying command event to native client #%d", c->id);
+
+    evt.type   = SRS_EVENT_COMMAND;
+    evt.idx    = idx;
+    evt.tokens = tokens;
+    evt.ntoken = ntoken;
+
+    return send_message(c->t, (srs_msg_t *)&evt);
+}
+
+
+static int voice_notify(srs_client_t *client, srs_voice_event_t *event)
+{
+    client_t        *c = (client_t *)client->user_data;
+    srs_evt_voice_t  evt;
+
+    mrp_debug("relaying voice event to native client #%d", c->id);
+
+    evt.type  = SRS_EVENT_VOICE;
+    evt.event = event->type;
+    evt.id    = event->id;
+
+    if (event->type == SRS_VOICE_EVENT_PROGRESS) {
+        evt.pcnt = event->data.progress.pcnt;
+        evt.msec = event->data.progress.msec;
+    }
+    else {
+        evt.pcnt = 0;
+        evt.msec = 0;
+    }
+
+    return send_message(c->t, (srs_msg_t *)&evt);
+}
+
+
+static int reply_voiceqry(client_t *c, uint32_t reqno,
+                          srs_voice_actor_t *actors, int nactor)
+{
+    srs_rpl_voiceqry_t rpl;
+
+    mrp_debug("replying to request #%u from native client #%d", reqno, c->id);
+
+    if (actors < 0)
+        actors = 0;
+
+    rpl.type   = SRS_REPLY_QUERYVOICES;
+    rpl.reqno  = reqno;
+    rpl.actors = actors;
+    rpl.nactor = nactor;
+
+    return send_message(c->t, (srs_msg_t *)&rpl);
+}
+
+
+static inline void dump_message(void *data, uint32_t type_id)
+{
+    char buf[1024];
+
+    if (mrp_print_native(buf, sizeof(buf), data, type_id) > 0)
+        mrp_debug("got message of type 0x%x: %s", type_id, buf);
+}
+
+
+static void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+    server_t *s = (server_t *)user_data;
+    client_t *c;
+
+    c = create_client(s, lt);
+
+    if (c != NULL)
+        mrp_log_info("Accepted new native client connection.");
+    else
+        mrp_log_error("Failed to accept new native client connection.");
+}
+
+
+static void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    if (error != 0)
+        mrp_log_error("Native client connection closed with error %d (%s).",
+                      error, strerror(error));
+    else
+        mrp_log_info("Native client connection closed.");
+
+    destroy_client(c);
+}
+
+
+static void recv_evt(mrp_transport_t *t, void *data, uint32_t type_id,
+                     void *user_data)
+{
+    client_t  *c   = (client_t *)user_data;
+    srs_msg_t *req = (srs_msg_t *)data;
+
+    MRP_UNUSED(t);
+
+    dump_message(data, type_id);
+
+    switch (req->type) {
+    case SRS_REQUEST_REGISTER:
+        register_client(c, &req->reg_req);
+        break;
+
+    case SRS_REQUEST_UNREGISTER:
+        unregister_client(c, &req->bye_req);
+        break;
+
+    case SRS_REQUEST_FOCUS:
+        request_focus(c, &req->focus_req);
+        break;
+
+    case SRS_REQUEST_RENDERVOICE:
+        request_voice(c, &req->voice_req);
+        break;
+
+    case SRS_REQUEST_CANCELVOICE:
+        cancel_voice(c, &req->voice_ccl);
+        break;
+
+    case SRS_REQUEST_QUERYVOICES:
+        query_voices(c, &req->voice_qry);
+        break;
+
+    default:
+        break;
+    }
+
+}
+
+
+static int transport_setup(server_t *s)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvnative     = recv_evt },
+        { .recvnativefrom = NULL     },
+        .connection       = connection_evt,
+        .closed           = closed_evt,
+    };
+
+    srs_context_t  *srs  = s->self->srs;
+    mrp_sockaddr_t  addr;
+    socklen_t       alen;
+    const char     *type, *opt, *val;
+    int             flags;
+    void           *typemap;
+
+    alen = mrp_transport_resolve(NULL, s->address, &addr, sizeof(addr), &type);
+
+    if (alen < 0) {
+        mrp_log_error("Failed to resolve transport address '%s'.", s->address);
+        goto fail;
+    }
+
+    flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_MODE_NATIVE;
+    s->lt = mrp_transport_create(srs->ml, type, &evt, s, flags);
+
+    if (s->lt == NULL) {
+        mrp_log_error("Failed to create transport for native clients.");
+        return FALSE;
+    }
+
+    if ((typemap = register_message_types()) == NULL) {
+        mrp_log_error("Failed to register native messages.");
+        goto fail;
+    }
+
+    if (!mrp_transport_setopt(s->lt, "type-map", typemap)) {
+        mrp_log_error("Failed to set transport type map.");
+        goto fail;
+    }
+
+    if (mrp_transport_bind(s->lt, &addr, alen) &&
+        mrp_transport_listen(s->lt, 0)) {
+        mrp_log_info("Listening on transport '%s'...", s->address);
+
+        return TRUE;
+    }
+    else
+        mrp_log_error("Failed to bind/listen transport.");
+
+ fail:
+    if (s->lt) {
+        mrp_transport_destroy(s->lt);
+        s->lt = NULL;
+    }
+
+    return FALSE;
+}
+
+
+static void transport_cleanup(server_t *s)
+{
+    mrp_transport_destroy(s->lt);
+    s->lt = NULL;
+}
+
+
+static int create_native(srs_plugin_t *plugin)
+{
+    server_t *s;
+
+    mrp_debug("creating native client interface plugin");
+
+    if ((s = mrp_allocz(sizeof(*s))) == NULL)
+        return FALSE;
+
+    mrp_list_init(&s->clients);
+    s->self = plugin;
+
+    plugin->plugin_data = s;
+
+    return TRUE;
+
+ fail:
+    mrp_free(s);
+
+    return FALSE;
+}
+
+
+static int config_native(srs_plugin_t *plugin, srs_cfg_t *cfg)
+{
+    server_t *s = (server_t *)plugin->plugin_data;
+
+    mrp_debug("configure native client interface plugin");
+
+    s->address = srs_get_string_config(cfg, CONFIG_ADDRESS, DEFAULT_ADDRESS);
+    mrp_log_info("Using native client transport address: '%s'.", s->address);
+
+    return TRUE;
+}
+
+
+static int start_native(srs_plugin_t *plugin)
+{
+    server_t *s = (server_t *)plugin->plugin_data;
+
+    MRP_UNUSED(plugin);
+
+    return transport_setup(s);
+}
+
+
+static void stop_native(srs_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+}
+
+
+static void destroy_native(srs_plugin_t *plugin)
+{
+    server_t *s = (server_t *)plugin->plugin_data;
+
+    transport_cleanup(s);
+    mrp_free(s);
+}
+
+
+SRS_DECLARE_PLUGIN(PLUGIN_NAME, PLUGIN_DESCR, PLUGIN_AUTHORS, PLUGIN_VERSION,
+                   create_native, config_native, start_native, stop_native,
+                   destroy_native)
diff --git a/src/plugins/native-client-interface/test-client.c b/src/plugins/native-client-interface/test-client.c
new file mode 100644 (file)
index 0000000..b327492
--- /dev/null
@@ -0,0 +1,900 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdarg.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/pulse-glue.h>
+#include <murphy/common/glib-glue.h>
+
+#include <breedline/breedline-murphy.h>
+
+#include "native-client.h"
+
+static const char *default_commands[] = {
+    "hal open the pod bay doors",
+    "hal play music",
+    "hal stop music",
+    "hal exit",
+};
+
+typedef struct {
+    GMainLoop      *gml;
+    pa_mainloop    *pa;
+    mrp_mainloop_t *ml;
+    brl_t          *brl;
+    srs_t          *srs;
+    const char     *app_class;
+    const char     *app_name;
+    int             exit_status;
+    int             server_up : 1;
+    int             registered : 1;
+    const char     *focus;
+    char          **commands;
+    int             ncommand;
+    int             autoregister : 1;
+    const char     *autofocus;
+    uint32_t        vreq;
+    int             glib : 1;
+} client_t;
+
+
+static void connect_notify(srs_t *srs, int status, const char *msg,
+                           void *user_data);
+
+static void command_notify(srs_t *srs, int idx, char **tokens, int ntoken,
+                           void *user_data);
+
+static void focus_notify(srs_t *srs, srs_voice_focus_t focus, void *user_data);
+
+static void render_notify(srs_t *srs, srs_voice_event_t *e, void *user_data,
+                          void *notify_data);
+
+
+static void execute_user_command(client_t *c, int narg, char **args);
+
+static void set_prompt(client_t *c, const char *prompt)
+{
+    brl_set_prompt(c->brl, prompt);
+}
+
+
+static void show_prompt(client_t *c)
+{
+    if (c->brl != NULL)
+        brl_show_prompt(c->brl);
+}
+
+
+static void hide_prompt(client_t *c)
+{
+    if (c->brl != NULL)
+        brl_hide_prompt(c->brl);
+}
+
+
+static void print(client_t *c, const char *format, ...)
+{
+    va_list ap;
+
+    hide_prompt(c);
+
+    va_start(ap, format);
+    vfprintf(stdout, format, ap);
+    fputc('\n', stdout);
+    fflush(stdout);
+    va_end(ap);
+
+    show_prompt(c);
+}
+
+
+static char *concat_tokens(char *buf, int size, int ntoken, char **tokens)
+{
+    char   *p, *t;
+    int     l, n;
+
+    p = buf;
+    t = "";
+    l = size - 1;
+
+    while (ntoken > 0) {
+        n  = snprintf(p, l, "%s%s", t, tokens[0]);
+
+        if (n >= l)
+            return NULL;
+
+        p += n;
+        l -= n;
+        t = " ";
+        ntoken--;
+        tokens++;
+    }
+
+    buf[size - 1] = '\0';
+    return buf;
+}
+
+
+static void add_command(client_t *c, int ntoken, char **tokens)
+{
+    char   command[1024];
+    size_t osize, nsize;
+
+    if (c->registered) {
+        print(c, "You need to unregister first to modify commands.");
+        return;
+    }
+
+    if (concat_tokens(command, sizeof(command), ntoken, tokens) == NULL) {
+        print(c, "Command too long.");
+        return;
+    }
+
+    osize = sizeof(*c->commands) *  c->ncommand;
+    nsize = sizeof(*c->commands) * (c->ncommand + 1);
+
+    if (!mrp_reallocz(c->commands, osize, nsize)) {
+        print(c, "Failed to add new command.");
+        return;
+    }
+
+    c->commands[c->ncommand] = mrp_strdup(command);
+
+    if (c->commands[c->ncommand] != NULL) {
+        c->ncommand++;
+        print(c, "Command '%s' added to command set.", command);
+    }
+    else
+        print(c, "Failed to register new command.");
+}
+
+
+static void del_command(client_t *c, int ntoken, char **tokens)
+{
+    char command[1024];
+    int  i;
+
+    if (c->registered) {
+        print(c, "You need to unregister first to modify commands.");
+        return;
+    }
+
+    if (concat_tokens(command, sizeof(command), ntoken, tokens) == NULL) {
+        print(c, "Command too long.");
+        return;
+    }
+
+    for (i = 0; i < c->ncommand; i++) {
+        if (!strcmp(c->commands[i], command)) {
+            if (i < c->ncommand - 1)
+                memmove(c->commands + i + 1, c->commands + i,
+                        (c->ncommand - 1 - i) * sizeof(*c->commands));
+
+            c->ncommand--;
+            mrp_realloc(c->commands, sizeof(*c->commands) * c->ncommand);
+
+            print(c, "Command '%s' deleted.", command);
+        }
+    }
+}
+
+
+static void reset_commands(client_t *c)
+{
+    int i;
+
+    if (c->registered){
+        print(c, "You need to unregister first to modify commands.");
+        return;
+    }
+
+    for (i = 0; i < c->ncommand; i++)
+        mrp_free(c->commands[i]);
+    mrp_free(c->commands);
+
+    c->commands = NULL;
+    c->ncommand = 0;
+
+    print(c, "Commands resetted, no current commands.");
+}
+
+
+static void list_commands(client_t *c)
+{
+    int i;
+
+    if (c->ncommand > 0) {
+        print(c, "Command set:");
+        for (i = 0; i < c->ncommand; i++)
+            print(c, "  %s", c->commands[i]);
+    }
+    else
+        print(c, "No commands defined.");
+}
+
+
+static void set_client_defaults(client_t *c, const char *argv0)
+{
+    int i;
+
+    c->app_class    = "player";
+    c->app_name     = strrchr(argv0, '/');
+
+    if (c->app_name != NULL)
+        c->app_name++;
+    else
+        c->app_name = argv0;
+
+    c->commands = mrp_allocz(sizeof(default_commands));
+    c->ncommand = MRP_ARRAY_SIZE(default_commands);
+
+    for (i = 0; i < c->ncommand; i++) {
+        c->commands[i] = mrp_strdup(default_commands[i]);
+        if (c->commands[i] == NULL) {
+            print(c, "Failed to initialize default command set.");
+            exit(1);
+        }
+    }
+}
+
+
+static void destroy_client(client_t *c)
+{
+    if (c != NULL) {
+        mrp_debug("destroying client");
+
+        if (c->ml != NULL)
+            mrp_mainloop_destroy(c->ml);
+
+        if (c->pa != NULL)
+            pa_mainloop_free(c->pa);
+
+        if (c->gml != NULL)
+            g_main_loop_unref(c->gml);
+
+        mrp_free(c);
+    }
+}
+
+
+static client_t *create_client(const char *argv0)
+{
+    client_t *c = mrp_allocz(sizeof(*c));
+
+    if (c != NULL)
+        set_client_defaults(c, argv0);
+
+    return c;
+}
+
+
+static int create_mainloop(client_t *c)
+{
+    if (!c->glib) {
+        c->pa = pa_mainloop_new();
+        c->ml = mrp_mainloop_pulse_get(pa_mainloop_get_api(c->pa));
+    }
+    else {
+        c->gml = g_main_loop_new(NULL, FALSE);
+        c->ml  = mrp_mainloop_glib_get(c->gml);
+    }
+
+    if (c->ml != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static void run_mainloop(client_t *c)
+{
+    if (c != NULL && c->pa != NULL)
+        pa_mainloop_run(c->pa, &c->exit_status);
+    else
+        g_main_loop_run(c->gml);
+}
+
+
+static void quit_mainloop(client_t *c, int exit_status)
+{
+    if (c != NULL) {
+        if (c->pa != NULL)
+            pa_mainloop_quit(c->pa, exit_status);
+        else
+            g_main_loop_quit(c->gml);
+    }
+    else
+        exit(exit_status);
+}
+
+
+static void sighandler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    MRP_UNUSED(h);
+
+    switch (signum) {
+    case SIGINT:
+        printf("Received SIGINT, exiting...");
+        quit_mainloop(c, 0);
+        break;
+
+    case SIGTERM:
+        printf("Received SIGTERM, exiting...");
+        quit_mainloop(c, 0);
+        break;
+    }
+}
+
+
+static void setup_signals(client_t *c)
+{
+    mrp_add_sighandler(c->ml, SIGINT , sighandler, c);
+    mrp_add_sighandler(c->ml, SIGTERM, sighandler, c);
+}
+
+
+
+
+static int split_input(char *input, int narg, char **args)
+{
+    int   n;
+    char *p;
+
+    n = 0;
+    p = input;
+
+    while (*p) {
+        while (*p == ' ' || *p == '\t')
+            p++;
+
+        args[n++] = p;
+
+        if (n >= narg) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        while (*p && *p != ' ' && *p != '\t')
+            p++;
+
+        if (*p)
+            *p++ = '\0';
+    }
+
+    return n;
+}
+
+
+static void process_input(brl_t *brl, const char *input, void *user_data)
+{
+    client_t *c   = (client_t *)user_data;
+    int       len = input ? strlen(input) + 1 : 0;
+    char      buf[len], *args[64];
+    int       narg;
+
+    if (len > 1) {
+        brl_add_history(brl, input);
+        hide_prompt(c);
+
+        strcpy(buf, input);
+        narg = split_input(buf, MRP_ARRAY_SIZE(args), args);
+        if (narg > 0)
+            execute_user_command(c, narg, &args[0]);
+        else
+            printf("failed to parse input '%s'\n", input);
+
+        show_prompt(c);
+    }
+}
+
+
+static void setup_input(client_t *c)
+{
+    int fd;
+
+    fd     = fileno(stdin);
+    c->brl = brl_create_with_murphy(fd, "disconnected", c->ml,
+                                    process_input, c);
+
+    if (c->brl != NULL)
+        brl_show_prompt(c->brl);
+    else {
+        fprintf(stderr, "Failed to initialize breedline for console input.");
+        exit(1);
+    }
+}
+
+
+static void cleanup_input(client_t *c)
+{
+    if (c != NULL && c->brl != NULL) {
+        brl_destroy(c->brl);
+        c->brl = NULL;
+    }
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list     ap;
+    const char *exe;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    exe = strrchr(argv0, '/');
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -N, --name=APPNAME             application name to use\n"
+           "  -C, --class=APPCLASS           application class to use\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -R, --register                 automatically register to server\n"
+           "  -F, --focus[=TYPE]             automatically request focus\n"
+           "  -h, --help                     show help on usage\n", exe);
+    printf("\n");
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void parse_cmdline(client_t *c, int argc, char **argv)
+{
+#   define OPTIONS "N:C:d:RFgh"
+    struct option options[] = {
+        { "name"      , required_argument, NULL, 'N' },
+        { "class"     , required_argument, NULL, 'C' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "register"  , no_argument      , NULL, 'R' },
+        { "focus"     , optional_argument, NULL, 'F' },
+        { "glib"      , no_argument      , NULL, 'g' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'N':
+            c->app_name = optarg;
+            break;
+
+        case 'C':
+            c->app_class = optarg;
+            break;
+
+        case 'd':
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'R':
+            c->autoregister = TRUE;
+            break;
+
+        case 'F':
+            c->autofocus = optarg ? optarg : "shared";
+            break;
+
+        case 'g':
+            c->glib = TRUE;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+}
+
+
+static void render_notify(srs_t *srs, srs_voice_event_t *e, void *user_data,
+                          void *notify_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    MRP_UNUSED(srs);
+    MRP_UNUSED(notify_data);
+
+    switch (e->type) {
+    case SRS_VOICE_EVENT_STARTED:
+        print(c, "Rendering of TTS #%u started...", e->id);
+        break;
+
+    case SRS_VOICE_EVENT_PROGRESS:
+        print(c, "%f %% (%u msec) of TTS #%u rendered...",
+              e->data.progress.pcnt, e->data.progress.msec, e->id);
+        break;
+
+    case SRS_VOICE_EVENT_COMPLETED:
+        print(c, "Rendering of TTS #%u completed.", e->id);
+        break;
+
+    case SRS_VOICE_EVENT_TIMEOUT:
+        print(c, "Rendering of TTS #%u timed out.", e->id);
+        break;
+
+    case SRS_VOICE_EVENT_ABORTED:
+        print(c, "Rendering of TTS #%u terminated abnormally.", e->id);
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static void connect_notify(srs_t *srs, int status, const char *msg,
+                           void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    if (status == 1) {
+        set_prompt(c, "connected");
+        print(c, "Connection to server established.");
+    }
+    else {
+        set_prompt(c, "disconnected");
+        print(c, "Server connection down (error: %d, %s).", status,
+              msg ? msg : "<unknown>");
+    }
+}
+
+
+static void focus_notify(srs_t *srs, srs_voice_focus_t focus, void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    print(c, "Client has now %sfocus.", !focus ? "no " :
+          focus == SRS_VOICE_FOCUS_SHARED ? "shared " : "exclusive ");
+}
+
+
+
+static void command_notify(srs_t *srs, int idx, char **tokens, int ntoken,
+                           void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+    char      cmd[1024], *p;
+    size_t    size;
+    int       i;
+
+    print(c, "Got command #%d: ", idx);
+    for (i = 0; i < ntoken; i++)
+        print(c, "    token #%d: %s", i, tokens[i]);
+}
+
+
+static void register_client(client_t *c)
+{
+    static int   mainloop_set = 0;
+
+    if (!mainloop_set) {
+        if (c->gml != NULL)
+            srs_set_gmainloop(c->gml);
+        else
+            srs_set_mainloop(c->ml);
+        mainloop_set = 1;
+    }
+
+    c->srs = srs_create(c->app_name, c->app_class, c->commands, c->ncommand,
+                        connect_notify, focus_notify, command_notify, c);
+
+    if (c->srs == NULL) {
+        print(c, "Failed to create SRS client.");
+        return;
+    }
+
+    if (srs_connect(c->srs, NULL, 0) < 0)
+        print(c, "Failed to connect SRS client.");
+}
+
+
+static void unregister_client(client_t *c)
+{
+    srs_disconnect(c->srs);
+}
+
+
+static void query_voices_reply(srs_t *srs, srs_voice_actor_t *actors,
+                               int nactor, void *user_data, void *notify_data)
+{
+    client_t          *c = (client_t *)user_data;
+    srs_voice_actor_t *a;
+    int                i;
+
+    MRP_UNUSED(notify_data);
+
+    print(c, "Server has %d available matching voices.", nactor);
+
+    for (i = 0, a = actors; i < nactor; i++, a++) {
+        print(c, "Actor %s:", a->name, a->id);
+        print(c, "     language: %s", a->lang);
+        print(c, "      dialect: %s", a->dialect);
+        print(c, "       gender: %s",
+              a->gender == SRS_VOICE_GENDER_MALE ? "male" : "female");
+        print(c, "          age: %u", a->age);
+        print(c, "  description: %s", a->description);
+    }
+}
+
+
+static void query_voices(client_t *c, const char *language)
+{
+    if (language == NULL)
+        language = "";
+
+    if (srs_query_voices(c->srs, language, query_voices_reply, NULL) < 0)
+        print(c, "Voice query failed.");
+}
+
+
+static void request_focus(client_t *c, const char *focusstr)
+{
+    srs_voice_focus_t focus;
+
+    if (!strcmp(focusstr, "none"))
+        focus = SRS_VOICE_FOCUS_NONE;
+    else if (!strcmp(focusstr, "shared"))
+        focus = SRS_VOICE_FOCUS_SHARED;
+    else if (!strcmp(focusstr, "exclusive"))
+        focus = SRS_VOICE_FOCUS_EXCLUSIVE;
+    else {
+        print(c, "Unknown focus type '%s'", focusstr);
+        return;
+    }
+
+    srs_request_focus(c->srs, focus);
+}
+
+
+static void tts_progress_cb(srs_t *srs, srs_voice_event_t *event,
+                            void *user_data, void *notify_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    MRP_UNUSED(srs);
+    MRP_UNUSED(notify_data);
+
+    print(c, "Got voice rendering event 0x%x.", event->type);
+}
+
+
+static void request_tts(client_t *c, int ntoken, char **tokens)
+{
+    const char *sep     = "";
+    const char *voice   = "english";
+    int         timeout = SRS_VOICE_QUEUE;
+    int         events  = FALSE;
+    char        msg[1024], *t, *e, *p;
+    int         i, o;
+    size_t      l;
+    ssize_t     n;
+
+    p = msg;
+    l = sizeof(msg);
+    for (i = 0; i < ntoken; i++) {
+        t = tokens[i];
+        if (*t == '-') {
+            if (!strncmp(t + 1, "timeout:", o=8)) {
+                timeout = strtol(t + 1 + o, &e, 10);
+                if (*e != '\0') {
+                    print(c, "Invalid timeout: %s.", t + 1 + o);
+                    return;
+                }
+            }
+            else if (!strncmp(t + 1, "events", o=6)) {
+                events = TRUE;
+            }
+            else if (!strncmp(t + 1, "voice:", o=6)) {
+                voice = t + 1 + o;
+            }
+        }
+        else {
+            n = snprintf(p, l, "%s%s", sep, t);
+            if (n >= l) {
+                print(c, "TTS message too long.");
+                return;
+            }
+
+            p += n;
+            l -= n;
+            sep = " ";
+        }
+    }
+
+    print(c, "Requesting TTS for message: '%s'.", msg);
+
+    c->vreq = srs_render_voice(c->srs, msg, voice, timeout,
+                               events ? SRS_VOICE_MASK_ALL:SRS_VOICE_MASK_NONE,
+                               render_notify, NULL);
+}
+
+
+static void cancel_tts(client_t *c, int ntoken, char **tokens)
+{
+    int       i;
+    uint32_t  vreq;
+    char     *end;
+
+    if (ntoken == 0) {
+        if (c->vreq)
+            srs_cancel_voice(c->srs, c->vreq);
+        else
+            print(c, "No outstanding TTS request.");
+    }
+    else {
+        for (i = 0; i < ntoken; i++) {
+            vreq = strtoul(tokens[i], &end, 10);
+
+            if (end && !*end) {
+                print(c, "Cancelling TTS request %u.", vreq);
+                srs_cancel_voice(c->srs, vreq);
+            }
+            else
+                print(c, "TTS request id '%s' is invalid.", tokens[i]);
+        }
+    }
+}
+
+
+static void execute_user_command(client_t *c, int narg, char **args)
+{
+    const char *cmd;
+
+    cmd = args[0];
+    narg--;
+    args++;
+
+    switch (narg) {
+    case 0:
+        if      (!strcmp(cmd, "register"))   register_client(c);
+        else if (!strcmp(cmd, "unregister")) unregister_client(c);
+        else if (!strcmp(cmd, "exit"))       quit_mainloop(c, 0);
+        else if (!strcmp(cmd, "quit"))       quit_mainloop(c, 0);
+        else if (!strcmp(cmd, "help")) {
+            print(c, "Available commands:");
+            print(c, "  register                     - register to server");
+            print(c, "  unregister                   - unregister from server");
+            print(c, "  focus none|shared|exclusive  - request voice focus");
+            print(c, "  add command <command>        - add new command");
+            print(c, "  del command <command>        - delete a command");
+            print(c, "  tts render '<msg>' timeout subscribe");
+            print(c, "  tts cancel '<id>'");
+            print(c, "  list commands                - list commands set");
+            print(c, "  help                         - show this help");
+            print(c, "  exit                         - exit from client");
+        }
+        else
+            print(c, "Unknown command '%s'.", cmd);
+        break;
+
+    case 1:
+        if (!strcmp(cmd, "focus")) {
+            if (strcmp(args[0], "none") &&
+                strcmp(args[0], "shared") &&
+                strcmp(args[0], "exclusive")) {
+                print(c, "Invalid focus '%s', valid foci are: "
+                      "none, shared, and exclusive.", args[0]);
+            }
+            else
+                request_focus(c, args[0]);
+        }
+        else if (!strcmp(cmd, "reset") && !strcmp(args[0], "commands"))
+            reset_commands(c);
+        else if (!strcmp(cmd, "list" ) && !strcmp(args[0], "commands"))
+            list_commands(c);
+        else if (!strcmp(cmd, "list" ) && !strcmp(args[0], "voices"))
+            query_voices(c, NULL);
+        else if (!strcmp(cmd, "cancel" ) && !strcmp(args[0], "tts"))
+            cancel_tts(c, 0, NULL);
+        else
+            print(c, "Invalid command.");
+        break;
+
+    case 2:
+        if (!strcmp(cmd, "list" ) && !strcmp(args[0], "voices"))
+            query_voices(c, args[1]);
+        else
+            print(c, "Invalid command.");
+        break;
+
+    default:
+        if (!strcmp(args[0], "command")) {
+            if (!strcmp(cmd, "add" ))
+                add_command(c, narg-1, args+1);
+            else if (!strcmp(cmd, "del" ) || !strcmp(cmd, "delete"))
+                del_command(c, narg-1, args+1);
+            else
+                print(c, "Invalid command.");
+        }
+        else if (!strcmp(args[0], "tts")) {
+            if (!strcmp(cmd, "render"))
+                request_tts(c, narg-1, args+1);
+            else if (!strcmp(cmd, "cancel"))
+                cancel_tts(c, narg-1, args+1);
+            else
+                print(c, "Invalid TTS command.");
+        }
+        else
+            print(c, "Invalid command.");
+        break;
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    client_t *c;
+
+    c = create_client(argv[0]);
+
+    if (c == NULL) {
+        fprintf(stderr, "Failed to create client.");
+        exit(1);
+    }
+
+    parse_cmdline(c, argc, &argv[0]);
+    create_mainloop(c);
+    setup_signals(c);
+    setup_input(c);
+
+    if (c->glib)
+        print(c, "Using GMainLoop...");
+    else
+        print(c, "Using pa_manloop...");
+
+    run_mainloop(c);
+    cleanup_input(c);
+    destroy_client(c);
+
+    return 0;
+}