blob: adc72c9bc8c2357103cc335fc687db18274cc262 [file] [log] [blame] [edit]
/*
libspeechd.c - Shared library for easy access to Speech Dispatcher functions
*
* Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008 Brailcom, o.p.s.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* $Id: libspeechd.c,v 1.37 2008-12-23 09:15:32 pdm Exp $
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <wchar.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <errno.h>
#include <assert.h>
#include <netdb.h>
/*
* This is needed because speechd_types.h is in a different location in
* the source tree's include directory than it will be when it is
* installed on the user's system.
*/
#include <speechd_types.h>
#include <speechd_defines.h>
#include "libspeechd.h"
#include "../../common/common.h"
/* Comment/uncomment to switch debugging on/off */
// #define LIBSPEECHD_DEBUG 1
/* Unless there is an fatal error, it doesn't print anything */
#define SPD_FATAL(msg) { printf("Fatal error (libspeechd) [%s:%d]:"msg, __FILE__, __LINE__); fflush(stdout); exit(EXIT_FAILURE); }
/* -------------- Private functions headers ------------------------*/
#ifdef LIBSPEECHD_DEBUG
static FILE *spd_debug = NULL;
#endif
static int spd_set_priority(SPDConnection * connection, SPDPriority priority);
static char *escape_dot(const char *text);
static int isanum(char *str);
static char *get_reply(SPDConnection * connection);
static int get_err_code(char *reply);
static char *get_param_str(char *reply, int num, int *err);
static char *get_param_str_and_advance(char **reply, int *err);
static int get_param_int(char *reply, int num, int *err);
static int ret_ok(char *reply);
static void SPD_DBG(char *format, ...);
static void *spd_events_handler(void *);
static const int range_low = -100;
static const int range_high = 100;
pthread_mutex_t spd_logging_mutex = PTHREAD_MUTEX_INITIALIZER;
struct SPDConnection_threaddata {
pthread_t events_thread;
pthread_cond_t cond_reply_ready;
pthread_mutex_t mutex_reply_ready;
pthread_cond_t cond_reply_ack;
pthread_mutex_t mutex_reply_ack;
};
/*
* Added by Willie Walker - strndup was a GNU libc extensions
* that was adopted in the POSIX.1-2008 standard, but is not yet found
* on all systems.
*/
#ifndef HAVE_STRNDUP
char *strndup(const char *s, size_t n)
{
size_t nAvail;
char *p;
if (!s)
return 0;
if (strlen(s) > n)
nAvail = n + 1;
else
nAvail = strlen(s) + 1;
p = malloc(nAvail);
memcpy(p, s, nAvail);
p[nAvail - 1] = '\0';
return p;
}
#endif /* HAVE_STRNDUP */
#define SPD_REPLY_BUF_SIZE 65536
/** Read from the connection's buffer or socket until a newline is
read. Return a pointer to the beginning of the data, and the
length of the data (including newline). The returned value will
not be null-terminated, and will include the newline. Note that
the returned pointer will only be valid until the next call to
get_line.
If, after SPD_REPLY_BUF_SIZE bytes, there is no newline (or in the
event of a read error) return NULL.
Unlike getline, this does not handle embedded \0 bytes.
*/
static
char *get_line(SPDConnection * conn, int *n)
{
int bytes;
int i;
char *ret = NULL;
int search_start = conn->buf_start;
int message_prefix_len;
while (1) {
for (i = search_start; i < conn->buf_used; i++) {
if (conn->buf[i] == '\n') {
*n = i + 1 - conn->buf_start;
ret = conn->buf + conn->buf_start;
conn->buf_start = i + 1;
return ret;
}
}
if (conn->buf_start != 0) {
message_prefix_len = conn->buf_used - conn->buf_start;
memmove(conn->buf, conn->buf + conn->buf_start,
message_prefix_len);
search_start = message_prefix_len;
conn->buf_used = message_prefix_len;
conn->buf_start = 0;
}
if (conn->buf_used == SPD_REPLY_BUF_SIZE) {
SPD_FATAL
("No newline after reading SPD_REPLY_BUF_SIZE");
return NULL;
}
bytes =
read(conn->socket, conn->buf + conn->buf_used,
SPD_REPLY_BUF_SIZE - conn->buf_used);
if (bytes == -1)
return NULL;
if (bytes == 0) {
errno = ECONNRESET;
return NULL;
}
conn->buf_used += bytes;
}
}
/* --------------------- Public functions ------------------------- */
/* Determine address for the unix socket */
static char *_get_default_unix_socket_name(void)
{
GString *socket_filename;
char *h;
const char *rundir = g_get_user_runtime_dir();
socket_filename = g_string_new("");
g_string_printf(socket_filename, "%s/speech-dispatcher/speechd.sock",
rundir);
// Do not return glib string, but glibc string...
h = strdup(socket_filename->str);
g_string_free(socket_filename, 1);
return h;
}
void SPDConnectionAddress__free(SPDConnectionAddress * address)
{
if (!address)
return;
free(address->unix_socket_name);
free(address->inet_socket_host);
free(address->dbus_bus);
free(address);
}
SPDConnectionAddress *spd_get_default_address(char **error)
{
const gchar *env_address = g_getenv("SPEECHD_ADDRESS");
gchar **pa; /* parsed address */
SPDConnectionAddress *address = malloc(sizeof(SPDConnectionAddress));
address->unix_socket_name = NULL;
address->inet_socket_host = NULL;
address->dbus_bus = NULL;
if (env_address == NULL) { // Default method = unix sockets
address->method = SPD_METHOD_UNIX_SOCKET;
address->unix_socket_name = _get_default_unix_socket_name();
} else {
pa = g_strsplit(env_address, ":", 0);
assert(pa);
if (!g_strcmp0(pa[0], "unix_socket") || pa[0] == NULL) { // Unix sockets
address->method = SPD_METHOD_UNIX_SOCKET;
if (pa[1] == NULL) {
address->unix_socket_name =
_get_default_unix_socket_name();
} else {
address->unix_socket_name = strdup(pa[1]);
}
} else if (!g_strcmp0(pa[0], "inet_socket")) { // Inet sockets
address->method = SPD_METHOD_INET_SOCKET;
if (pa[1] == NULL) {
address->inet_socket_host = strdup("127.0.0.1");
address->inet_socket_port = 6560;
} else {
address->inet_socket_host = strdup(pa[1]);
if (pa[2] == NULL) {
address->inet_socket_port =
SPEECHD_DEFAULT_PORT;
} else {
address->inet_socket_port = atoi(pa[2]);
}
}
} else { // Unknown or unsupported method requested
*error =
strdup
("Unknown or unsupported communication method");
SPDConnectionAddress__free(address);
address = NULL;
}
g_strfreev(pa);
}
return address;
}
static void _init_debug(void)
{
#ifdef LIBSPEECHD_DEBUG
if (!spd_debug) {
spd_debug = fopen("/tmp/libspeechd.log", "w");
if (spd_debug == NULL)
SPD_FATAL("COULDN'T ACCESS FILE INTENDED FOR DEBUG");
SPD_DBG("Debugging started");
}
#endif /* LIBSPEECHD_DEBUG */
}
/* Opens a new Speech Dispatcher connection.
* Returns socket file descriptor of the created connection
* or -1 if no connection was opened. */
SPDConnection *spd_open(const char *client_name, const char *connection_name,
const char *user_name, SPDConnectionMode mode)
{
char *error;
int autospawn = 1;
SPDConnection *conn;
conn = spd_open2(client_name, connection_name, user_name,
mode, NULL, autospawn, &error);
if (!conn) {
_init_debug();
assert(error);
SPD_DBG("Could not connect to Speech Dispatcher: %s", error);
free(error);
}
return conn;
}
#define MAX_IP_SIZE 16+1
/* TODO: This only works in IPV4 */
static char *resolve_host(char *host_name_or_ip, int *is_localhost,
gchar ** error)
{
struct addrinfo *addr_result;
int err;
char *resolve_buffer = malloc(MAX_IP_SIZE * sizeof(char));
const char *resolved_ip = NULL;
char *ip;
*error = NULL;
struct sockaddr_in *addr_in;
if (resolve_buffer == NULL) {
*error = g_strdup("Failed to allocate memory.");
return NULL;
}
err = getaddrinfo(host_name_or_ip, 0, NULL, &addr_result);
if (err) {
*error =
g_strdup_printf("Can't resolve address %d due to error %s:",
err, gai_strerror(err));
free(resolve_buffer);
return NULL;
}
/* Take the first address returned as we are only interested in host ip */
addr_in = (struct sockaddr_in *)addr_result->ai_addr;
resolved_ip =
inet_ntop(AF_INET, &(addr_in->sin_addr.s_addr), resolve_buffer,
MAX_IP_SIZE);
if (resolved_ip == NULL) {
*error =
g_strdup_printf
("Could not convert address, due to the following error: %s",
strerror(errno));
freeaddrinfo(addr_result);
free(resolve_buffer);
return NULL;
}
if (!strncmp(resolved_ip, "127.", 4)) {
*is_localhost = 1;
/* In case of local addresses, use 127.0.0.1 which is guaranteed
to be local and the server listens on it */
free(resolve_buffer);
ip = strdup("127.0.0.1");
} else {
*is_localhost = 0;
ip = resolve_buffer;
}
freeaddrinfo(addr_result);
return ip;
}
static int
spawn_server(const SPDConnectionAddress * address, int is_localhost,
gchar ** spawn_error)
{
gchar *speechd_cmd[16];
gchar *stderr_output;
gboolean spawn_ok;
GError *gerror = NULL;
int exit_status;
int i;
const char *cmd;
if ((address->method == SPD_METHOD_INET_SOCKET) && (!is_localhost)) {
*spawn_error =
g_strdup
("Spawn failed, the given network address doesn't seem to be on localhost");
return 1;
}
cmd = getenv("SPEECHD_CMD");
if (!cmd)
cmd = SPD_SPAWN_CMD;
speechd_cmd[0] = g_strdup(cmd);
speechd_cmd[1] = g_strdup("--spawn");
speechd_cmd[2] = g_strdup("--communication-method");
if (address->method == SPD_METHOD_INET_SOCKET) {
speechd_cmd[3] = g_strdup("inet_socket");
speechd_cmd[4] = g_strdup("--port");
speechd_cmd[5] =
g_strdup_printf("%d", address->inet_socket_port);
speechd_cmd[6] = NULL;
} else if (address->method == SPD_METHOD_UNIX_SOCKET) {
speechd_cmd[3] = g_strdup("unix_socket");
speechd_cmd[4] = g_strdup("--socket-path");
speechd_cmd[5] =
g_strdup_printf("%s", address->unix_socket_name);
speechd_cmd[6] = NULL;
} else
assert(0);
spawn_ok =
g_spawn_sync(NULL, (gchar **) speechd_cmd, NULL,
G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL, NULL,
NULL, NULL, &stderr_output, &exit_status, &gerror);
for (i = 0; speechd_cmd[i] != NULL; i++)
g_free(speechd_cmd[i]);
if (!spawn_ok) {
*spawn_error =
g_strdup_printf("Autospawn failed. Spawn error %d: %s",
gerror->code, gerror->message);
return 1;
} else {
if (exit_status) {
*spawn_error =
g_strdup_printf
("Autospawn failed. Speech Dispatcher refused to start with error code, "
"stating this as a reason: %s", stderr_output);
g_free(stderr_output);
return 1;
} else {
*spawn_error = NULL;
g_free(stderr_output);
return 0;
}
}
assert(0);
}
SPDConnection *spd_open2(const char *client_name, const char *connection_name,
const char *user_name, SPDConnectionMode mode,
const SPDConnectionAddress * address, int autospawn,
char **error_result)
{
SPDConnection *connection = NULL;
SPDConnectionAddress *defaultAddress = NULL;
char *set_client_name = NULL;
char *conn_name = NULL;
char *usr_name = NULL;
int ret;
int tcp_no_delay = 1;
/* Autospawn related */
int spawn_err;
gchar *spawn_report;
char *host_ip;
int is_localhost = 1;
struct sockaddr_in address_inet;
struct sockaddr_un address_unix;
struct sockaddr *sock_address;
size_t sock_address_len;
_init_debug();
if (client_name == NULL) {
*error_result = strdup("ERROR: Client name not specified");
SPD_DBG(*error_result);
return NULL;
}
if (user_name == NULL) {
usr_name = strdup((char *)g_get_user_name());
} else
usr_name = strdup(user_name);
if (connection_name == NULL)
conn_name = strdup("main");
else
conn_name = strdup(connection_name);
if (address == NULL) {
char *err = NULL;
defaultAddress = spd_get_default_address(&err);
address = defaultAddress;
if (!address) {
assert(err);
*error_result = err;
SPD_DBG(*error_result);
goto out;
}
}
/* Connect to server using the selected method */
connection = malloc(sizeof(SPDConnection));
if (address->method == SPD_METHOD_INET_SOCKET) {
gchar *resolve_error;
host_ip =
resolve_host(address->inet_socket_host, &is_localhost,
&resolve_error);
if (host_ip == NULL) {
*error_result = strdup(resolve_error);
g_free(resolve_error);
free(connection);
connection = NULL;
goto out;
}
address_inet.sin_addr.s_addr = inet_addr(host_ip);
free(host_ip);
address_inet.sin_port = htons(address->inet_socket_port);
address_inet.sin_family = AF_INET;
connection->socket = socket(AF_INET, SOCK_STREAM, 0);
sock_address = (struct sockaddr *)&address_inet;
sock_address_len = sizeof(address_inet);
} else if (address->method == SPD_METHOD_UNIX_SOCKET) {
/* Create the unix socket */
address_unix.sun_family = AF_UNIX;
strncpy(address_unix.sun_path, address->unix_socket_name,
sizeof(address_unix.sun_path));
address_unix.sun_path[sizeof(address_unix.sun_path) - 1] = '\0';
connection->socket = socket(AF_UNIX, SOCK_STREAM, 0);
sock_address = (struct sockaddr *)&address_unix;
sock_address_len = SUN_LEN(&address_unix);
} else
SPD_FATAL("Unsupported connection method for spd_open2()");
if (connection->socket < 0) {
free(connection);
connection = NULL;
goto out;
}
connection->stream = (void*) 1;
ret = connect(connection->socket, sock_address, sock_address_len);
if (ret == -1) {
/* Suppose server might not be running, try to autospawn (autostart) it */
if (autospawn) {
spawn_err =
spawn_server(address, is_localhost, &spawn_report);
if (!spawn_err)
spawn_report =
g_strdup("Server successfully autospawned");
ret =
connect(connection->socket, sock_address,
sock_address_len);
} else {
spawn_report = g_strdup("Autospawn disabled");
}
if (ret == -1) {
if (address->method == SPD_METHOD_INET_SOCKET)
*error_result =
g_strdup_printf
("Error: Can't connect to %s on port %d using inet sockets: %s. "
"Autospawn: %s", address->inet_socket_host,
address->inet_socket_port, strerror(errno),
spawn_report);
else if (address->method == SPD_METHOD_UNIX_SOCKET)
*error_result =
g_strdup_printf
("Error: Can't connect to unix socket %s: %s. Autospawn: %s",
address->unix_socket_name, strerror(errno),
spawn_report);
else
assert(0);
SPD_DBG(*error_result);
close(connection->socket);
free(connection);
connection = NULL;
goto out;
}
g_free(spawn_report);
}
if (address->method == SPD_METHOD_INET_SOCKET)
setsockopt(connection->socket, IPPROTO_TCP, TCP_NODELAY,
&tcp_no_delay, sizeof(int));
connection->callback_begin = NULL;
connection->callback_end = NULL;
connection->callback_im = NULL;
connection->callback_pause = NULL;
connection->callback_resume = NULL;
connection->callback_cancel = NULL;
connection->mode = mode;
/* Set up buffer for the socket */
connection->buf_start = 0;
connection->buf_used = 0;
connection->buf = malloc(SPD_REPLY_BUF_SIZE);
if (!connection->buf) {
*error_result =
strdup("Out of memory allocating connection buffer");
SPD_DBG(*error_result);
close(connection->socket);
free(connection);
connection = NULL;
goto out;
}
pthread_mutex_init(&connection->ssip_mutex, NULL);
if (mode == SPD_MODE_THREADED) {
SPD_DBG
("Initializing threads, condition variables and mutexes...");
connection->td = malloc(sizeof(*connection->td));
pthread_cond_init(&connection->td->cond_reply_ready, NULL);
pthread_mutex_init(&connection->td->mutex_reply_ready, NULL);
pthread_cond_init(&connection->td->cond_reply_ack, NULL);
pthread_mutex_init(&connection->td->mutex_reply_ack, NULL);
ret =
pthread_create(&connection->td->events_thread, NULL,
spd_events_handler, connection);
if (ret != 0) {
*error_result = strdup("Thread initialization failed");
SPD_DBG(*error_result);
close(connection->socket);
free(connection->buf);
free(connection);
connection = NULL;
goto out;
}
connection->reply = NULL;
}
/* By now, the connection is created and operational */
set_client_name =
g_strdup_printf("SET SELF CLIENT_NAME \"%s:%s:%s\"", usr_name,
client_name, conn_name);
ret = spd_execute_command_wo_mutex(connection, set_client_name);
out:
free(usr_name);
free(conn_name);
free(set_client_name);
SPDConnectionAddress__free(defaultAddress);
return connection;
}
int spd_fd(SPDConnection * connection)
{
return connection->socket;
}
#define RET(r) \
{ \
pthread_mutex_unlock(&connection->ssip_mutex); \
return r; \
}
/* Get the client id of the connection */
int spd_get_client_id(SPDConnection * connection)
{
int ret;
int err;
char *reply = NULL;
spd_execute_command_with_reply(connection, "HISTORY GET CLIENT_ID", &reply);
ret = get_param_int(reply, 1, &err);
free(reply);
return ret;
}
/* Close a Speech Dispatcher connection */
void spd_close(SPDConnection * connection)
{
pthread_mutex_lock(&connection->ssip_mutex);
if (connection->mode == SPD_MODE_THREADED) {
pthread_cancel(connection->td->events_thread);
pthread_mutex_destroy(&connection->td->mutex_reply_ready);
pthread_mutex_destroy(&connection->td->mutex_reply_ack);
pthread_cond_destroy(&connection->td->cond_reply_ready);
pthread_cond_destroy(&connection->td->cond_reply_ack);
pthread_join(connection->td->events_thread, NULL);
connection->mode = SPD_MODE_SINGLE;
free(connection->td);
}
/* close the socket */
if (connection->socket >= 0) {
close(connection->socket);
connection->socket = -1;
connection->stream = NULL;
}
free(connection->buf);
pthread_mutex_unlock(&connection->ssip_mutex);
pthread_mutex_destroy(&connection->ssip_mutex);
free(connection);
}
/* Helper functions for spd_say. */
static inline int
spd_say_prepare(SPDConnection * connection, SPDPriority priority,
const char *text, char **escaped_text)
{
int ret = 0;
SPD_DBG("Text to say is: %s", text);
/* Insure that there is no escape sequence in the text */
*escaped_text = escape_dot(text);
/* Caller is now responsible for escaped_text. */
if (*escaped_text == NULL) { /* Out of memory. */
SPD_DBG("spd_say could not allocate memory.");
ret = -1;
} else {
/* Set priority */
SPD_DBG("Setting priority");
ret = spd_set_priority(connection, priority);
if (!ret) {
/* Start the data flow */
SPD_DBG("Sending SPEAK");
ret = spd_execute_command_wo_mutex(connection, "speak");
if (ret) {
SPD_DBG("Error: Can't start data flow!");
}
}
}
return ret;
}
static inline int spd_say_sending(SPDConnection * connection, const char *text)
{
int msg_id = -1;
int err = 0;
char *reply = NULL;
char *pret = NULL;
/* Send data */
SPD_DBG("Sending data");
pret = spd_send_data_wo_mutex(connection, text, SPD_NO_REPLY);
if (pret == NULL) {
SPD_DBG("Can't send data wo mutex");
} else {
/* Terminate data flow */
SPD_DBG("Terminating data flow");
err =
spd_execute_command_with_reply(connection, "\r\n.", &reply);
if (err) {
SPD_DBG("Can't terminate data flow");
} else {
msg_id = get_param_int(reply, 1, &err);
if (err < 0) {
SPD_DBG
("Can't determine SSIP message unique ID parameter.");
msg_id = -1;
}
}
}
free(reply);
free(pret);
return msg_id;
}
/* Say TEXT with priority PRIORITY.
* Returns msg_uid on success, -1 otherwise. */
int spd_say(SPDConnection * connection, SPDPriority priority, const char *text)
{
char *escaped_text = NULL;
int msg_id = -1;
int prepare_failed = 0;
if (text != NULL) {
pthread_mutex_lock(&connection->ssip_mutex);
prepare_failed =
spd_say_prepare(connection, priority, text, &escaped_text);
if (!prepare_failed)
msg_id = spd_say_sending(connection, escaped_text);
free(escaped_text);
pthread_mutex_unlock(&connection->ssip_mutex);
} else {
SPD_DBG("spd_say called with a NULL argument for <text>");
}
SPD_DBG("Returning from spd_say");
return msg_id;
}
/* The same as spd_say, accepts also formatted strings */
int
spd_sayf(SPDConnection * connection, SPDPriority priority, const char *format,
...)
{
static int ret;
va_list args;
char *buf;
if (format == NULL)
return -1;
/* Print the text to buffer */
va_start(args, format);
buf = g_strdup_vprintf(format, args);
va_end(args);
/* Send the buffer to Speech Dispatcher */
ret = spd_say(connection, priority, buf);
free(buf);
return ret;
}
int spd_stop(SPDConnection * connection)
{
return spd_execute_command(connection, "STOP SELF");
}
int spd_stop_all(SPDConnection * connection)
{
return spd_execute_command(connection, "STOP ALL");
}
int spd_stop_uid(SPDConnection * connection, int target_uid)
{
static char command[16];
sprintf(command, "STOP %d", target_uid);
return spd_execute_command(connection, command);
}
int spd_cancel(SPDConnection * connection)
{
return spd_execute_command(connection, "CANCEL SELF");
}
int spd_cancel_all(SPDConnection * connection)
{
return spd_execute_command(connection, "CANCEL ALL");
}
int spd_cancel_uid(SPDConnection * connection, int target_uid)
{
static char command[16];
sprintf(command, "CANCEL %d", target_uid);
return spd_execute_command(connection, command);
}
int spd_pause(SPDConnection * connection)
{
return spd_execute_command(connection, "PAUSE SELF");
}
int spd_pause_all(SPDConnection * connection)
{
return spd_execute_command(connection, "PAUSE ALL");
}
int spd_pause_uid(SPDConnection * connection, int target_uid)
{
char command[16];
sprintf(command, "PAUSE %d", target_uid);
return spd_execute_command(connection, command);
}
int spd_resume(SPDConnection * connection)
{
return spd_execute_command(connection, "RESUME SELF");
}
int spd_resume_all(SPDConnection * connection)
{
return spd_execute_command(connection, "RESUME ALL");
}
int spd_resume_uid(SPDConnection * connection, int target_uid)
{
static char command[16];
sprintf(command, "RESUME %d", target_uid);
return spd_execute_command(connection, command);
}
int
spd_key(SPDConnection * connection, SPDPriority priority, const char *key_name)
{
char *command_key;
int ret;
if (key_name == NULL)
return -1;
if (!strcmp(key_name, " "))
key_name = "space";
pthread_mutex_lock(&connection->ssip_mutex);
ret = spd_set_priority(connection, priority);
if (ret)
RET(-1);
command_key = g_strdup_printf("KEY %s", key_name);
ret = spd_execute_command_wo_mutex(connection, command_key);
free(command_key);
if (ret)
RET(-1);
pthread_mutex_unlock(&connection->ssip_mutex);
return 0;
}
int
spd_char(SPDConnection * connection, SPDPriority priority,
const char *character)
{
static char command[16];
int ret;
if (character == NULL)
return -1;
if (strlen(character) > 6)
return -1;
pthread_mutex_lock(&connection->ssip_mutex);
ret = spd_set_priority(connection, priority);
if (ret)
RET(-1);
if (!strcmp(character, " "))
sprintf(command, "CHAR space");
else
sprintf(command, "CHAR %s", character);
ret = spd_execute_command_wo_mutex(connection, command);
if (ret)
RET(-1);
pthread_mutex_unlock(&connection->ssip_mutex);
return 0;
}
int
spd_wchar(SPDConnection * connection, SPDPriority priority, wchar_t wcharacter)
{
static char command[16];
char character[8];
int ret;
pthread_mutex_lock(&connection->ssip_mutex);
ret = wcrtomb(character, wcharacter, NULL);
if (ret <= 0)
RET(-1);
character[ret] = '\0';
ret = spd_set_priority(connection, priority);
if (ret)
RET(-1);
assert(character != NULL);
sprintf(command, "CHAR %s", character);
ret = spd_execute_command_wo_mutex(connection, command);
if (ret)
RET(-1);
pthread_mutex_unlock(&connection->ssip_mutex);
return 0;
}
int
spd_sound_icon(SPDConnection * connection, SPDPriority priority,
const char *icon_name)
{
char *command;
int ret;
if (icon_name == NULL)
return -1;
pthread_mutex_lock(&connection->ssip_mutex);
ret = spd_set_priority(connection, priority);
if (ret)
RET(-1);
command = g_strdup_printf("SOUND_ICON %s", icon_name);
ret = spd_execute_command_wo_mutex(connection, command);
free(command);
if (ret)
RET(-1);
pthread_mutex_unlock(&connection->ssip_mutex);
return 0;
}
// Set functions for Punctuation
int spd_w_set_punctuation(SPDConnection * connection, SPDPunctuation type,
const char *who)
{
char command[32];
int ret;
if (type == SPD_PUNCT_ALL)
sprintf(command, "SET %s PUNCTUATION all", who);
if (type == SPD_PUNCT_NONE)
sprintf(command, "SET %s PUNCTUATION none", who);
if (type == SPD_PUNCT_SOME)
sprintf(command, "SET %s PUNCTUATION some", who);
if (type == SPD_PUNCT_MOST)
sprintf(command, "SET %s PUNCTUATION most", who);
ret = spd_execute_command(connection, command);
return ret;
}
int spd_set_punctuation(SPDConnection * connection, SPDPunctuation type)
{
return spd_w_set_punctuation(connection, type, SPD_SELF);
}
int spd_set_punctuation_all(SPDConnection * connection, SPDPunctuation type)
{
return spd_w_set_punctuation(connection, type, SPD_ALLCLIENTS);
}
int spd_set_punctuation_uid(SPDConnection * connection, SPDPunctuation type,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_punctuation(connection, type, who);
}
// Set functions for Capital Letters
int spd_w_set_capital_letters(SPDConnection * connection,
SPDCapitalLetters type, const char *who)
{
char command[64];
int ret;
if (type == SPD_CAP_NONE)
sprintf(command, "SET %s CAP_LET_RECOGN none", who);
if (type == SPD_CAP_SPELL)
sprintf(command, "SET %s CAP_LET_RECOGN spell", who);
if (type == SPD_CAP_ICON)
sprintf(command, "SET %s CAP_LET_RECOGN icon", who);
ret = spd_execute_command(connection, command);
return ret;
}
int spd_set_capital_letters(SPDConnection * connection, SPDCapitalLetters type)
{
return spd_w_set_capital_letters(connection, type, SPD_SELF);
}
int spd_set_capital_letters_all(SPDConnection * connection,
SPDCapitalLetters type)
{
return spd_w_set_capital_letters(connection, type, SPD_ALLCLIENTS);
}
int spd_set_capital_letters_uid(SPDConnection * connection,
SPDCapitalLetters type, unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_capital_letters(connection, type, who);
}
// Set functions for Spelling
int spd_w_set_spelling(SPDConnection * connection, SPDSpelling type,
const char *who)
{
char command[32];
int ret;
if (type == SPD_SPELL_ON)
sprintf(command, "SET %s SPELLING on", who);
if (type == SPD_SPELL_OFF)
sprintf(command, "SET %s SPELLING off", who);
ret = spd_execute_command(connection, command);
return ret;
}
int spd_set_spelling(SPDConnection * connection, SPDSpelling type)
{
return spd_w_set_spelling(connection, type, SPD_SELF);
}
int spd_set_spelling_all(SPDConnection * connection, SPDSpelling type)
{
return spd_w_set_spelling(connection, type, SPD_ALLCLIENTS);
}
int spd_set_spelling_uid(SPDConnection * connection, SPDSpelling type,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_spelling(connection, type, who);
}
int spd_set_data_mode(SPDConnection * connection, SPDDataMode mode)
{
char command[32];
int ret;
if (mode == SPD_DATA_TEXT)
sprintf(command, "SET SELF SSML_MODE off");
if (mode == SPD_DATA_SSML)
sprintf(command, "SET SELF SSML_MODE on");
ret = spd_execute_command(connection, command);
return ret;
}
// Set functions for Voice type
int spd_w_set_voice_type(SPDConnection * connection, SPDVoiceType type,
const char *who)
{
static char command[64];
switch (type) {
case SPD_MALE1:
sprintf(command, "SET %s VOICE_TYPE MALE1", who);
break;
case SPD_MALE2:
sprintf(command, "SET %s VOICE_TYPE MALE2", who);
break;
case SPD_MALE3:
sprintf(command, "SET %s VOICE_TYPE MALE3", who);
break;
case SPD_FEMALE1:
sprintf(command, "SET %s VOICE_TYPE FEMALE1", who);
break;
case SPD_FEMALE2:
sprintf(command, "SET %s VOICE_TYPE FEMALE2", who);
break;
case SPD_FEMALE3:
sprintf(command, "SET %s VOICE_TYPE FEMALE3", who);
break;
case SPD_CHILD_MALE:
sprintf(command, "SET %s VOICE_TYPE CHILD_MALE", who);
break;
case SPD_CHILD_FEMALE:
sprintf(command, "SET %s VOICE_TYPE CHILD_FEMALE", who);
break;
default:
return -1;
}
return spd_execute_command(connection, command);
}
int spd_set_voice_type(SPDConnection * connection, SPDVoiceType type)
{
return spd_w_set_voice_type(connection, type, SPD_SELF);
}
int spd_set_voice_type_all(SPDConnection * connection, SPDVoiceType type)
{
return spd_w_set_voice_type(connection, type, SPD_ALLCLIENTS);
}
int spd_set_voice_type_uid(SPDConnection * connection, SPDVoiceType type,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_voice_type(connection, type, who);
}
// Set function for Voice type
SPDVoiceType spd_get_voice_type(SPDConnection * connection)
{
char *command;
SPDVoiceType ret;
int err;
char *reply = NULL;
command = g_strdup_printf("GET voice_type");
spd_execute_command_with_reply(connection, command, &reply);
free(command);
ret = get_param_int(reply, 1, &err);
free(reply);
return ret;
}
static int spd_w_set_command_int(SPDConnection * connection,
const char *ssip_name, signed int val,
const char *who)
{
static char command[64];
// NOTE: if any new int ssip_name are added that don't use -100 - 100 as their
// range, these values will need to become parameters (or the new ssip_name)
// methods will need to call a different helper method.
if (val < range_low || val > range_high)
return -1;
sprintf(command, "SET %s %s %d", who, ssip_name, val);
return spd_execute_command(connection, command);
}
static int spd_get_command_int(SPDConnection * connection,
const char *ssip_name)
{
char *command;
int ret = 0;
int err;
char *reply = NULL;
command = g_strdup_printf("GET %s", ssip_name);
spd_execute_command_with_reply(connection, command, &reply);
free(command);
ret = get_param_int(reply, 1, &err);
free(reply);
return ret;
}
static int spd_w_set_command_str(SPDConnection * connection,
const char *ssip_name, const char *str,
const char *who)
{
char *command;
int ret;
if (str == NULL)
return -1;
command = g_strdup_printf("SET %s %s %s", who, ssip_name, str);
ret = spd_execute_command(connection, command);
free(command);
return ret;
}
static char *spd_get_command_str(SPDConnection * connection,
const char *ssip_name)
{
char *command;
char *ret = NULL;
int err;
char *reply = NULL;
command = g_strdup_printf("GET %s", ssip_name);
spd_execute_command_with_reply(connection, command, &reply);
free(command);
ret = get_param_str(reply, 1, &err);
free(reply);
return ret;
}
// Set functions for rate
int spd_set_voice_rate(SPDConnection * connection, signed int rate)
{
return spd_w_set_command_int(connection, SPD_RATE, rate, SPD_SELF);
}
int spd_set_voice_rate_all(SPDConnection * connection, signed int rate)
{
return spd_w_set_command_int(connection, SPD_RATE, rate,
SPD_ALLCLIENTS);
}
int spd_set_voice_rate_uid(SPDConnection * connection, signed int rate,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_int(connection, SPD_RATE, rate, who);
}
// Get function for rate
int spd_get_voice_rate(SPDConnection * connection)
{
return spd_get_command_int(connection, SPD_RATE);
}
// Set functions for pitch
int spd_set_voice_pitch(SPDConnection * connection, signed int pitch)
{
return spd_w_set_command_int(connection, SPD_PITCH, pitch, SPD_SELF);
}
int spd_set_voice_pitch_all(SPDConnection * connection, signed int pitch)
{
return spd_w_set_command_int(connection, SPD_PITCH, pitch,
SPD_ALLCLIENTS);
}
int spd_set_voice_pitch_uid(SPDConnection * connection, signed int pitch,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_int(connection, SPD_PITCH, pitch, who);
}
// Get function for pitch
int spd_get_voice_pitch(SPDConnection * connection)
{
return spd_get_command_int(connection, SPD_PITCH);
}
// Set functions for pitch_range
int spd_set_voice_pitch_range(SPDConnection * connection,
signed int pitch_range)
{
return spd_w_set_command_int(connection, SPD_PITCH_RANGE, pitch_range,
SPD_SELF);
}
int spd_set_voice_pitch_range_all(SPDConnection * connection,
signed int pitch_range)
{
return spd_w_set_command_int(connection, SPD_PITCH, pitch_range,
SPD_ALLCLIENTS);
}
int spd_set_voice_pitch_range_uid(SPDConnection * connection,
signed int pitch_range, unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_int(connection, SPD_PITCH, pitch_range, who);
}
// Set functions for volume
int spd_set_volume(SPDConnection * connection, signed int volume)
{
return spd_w_set_command_int(connection, SPD_VOLUME, volume, SPD_SELF);
}
int spd_set_volume_all(SPDConnection * connection, signed int volume)
{
return spd_w_set_command_int(connection, SPD_VOLUME, volume,
SPD_ALLCLIENTS);
}
int spd_set_volume_uid(SPDConnection * connection, signed int volume,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_int(connection, SPD_VOLUME, volume, who);
}
// Get function for volume
int spd_get_volume(SPDConnection * connection)
{
return spd_get_command_int(connection, SPD_VOLUME);
}
// Set functions for language
int spd_set_language(SPDConnection * connection, const char *language)
{
return spd_w_set_command_str(connection, SPD_LANGUAGE, language,
SPD_SELF);
}
int spd_set_language_all(SPDConnection * connection, const char *language)
{
return spd_w_set_command_str(connection, SPD_LANGUAGE, language,
SPD_ALLCLIENTS);
}
int spd_set_language_uid(SPDConnection * connection, const char *language,
unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_str(connection, SPD_LANGUAGE, language, who);
}
// Get function for language
char *spd_get_language(SPDConnection * connection)
{
return spd_get_command_str(connection, SPD_LANGUAGE);
}
// Set functions for output_module
int spd_set_output_module(SPDConnection * connection, const char *output_module)
{
return spd_w_set_command_str(connection, SPD_OUTPUT_MODULE,
output_module, SPD_SELF);
}
int spd_set_output_module_all(SPDConnection * connection,
const char *output_module)
{
return spd_w_set_command_str(connection, SPD_OUTPUT_MODULE,
output_module, SPD_ALLCLIENTS);
}
int spd_set_output_module_uid(SPDConnection * connection,
const char *output_module, unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_str(connection, SPD_OUTPUT_MODULE,
output_module, who);
}
// Get function for output_module
char *spd_get_output_module(SPDConnection * connection)
{
return spd_get_command_str(connection, SPD_OUTPUT_MODULE);
}
// Set functions for synthesis_voice
int spd_set_synthesis_voice(SPDConnection * connection, const char *voice_name)
{
return spd_w_set_command_str(connection, SPD_SYNTHESIS_VOICE,
voice_name, SPD_SELF);
}
int spd_set_synthesis_voice_all(SPDConnection * connection,
const char *voice_name)
{
return spd_w_set_command_str(connection, SPD_SYNTHESIS_VOICE,
voice_name, SPD_ALLCLIENTS);
}
int spd_set_synthesis_voice_uid(SPDConnection * connection,
const char *voice_name, unsigned int uid)
{
char who[8];
sprintf(who, "%d", uid);
return spd_w_set_command_str(connection, SPD_SYNTHESIS_VOICE,
voice_name, who);
}
int
spd_set_notification_on(SPDConnection * connection,
SPDNotification notification)
{
if (connection->mode == SPD_MODE_THREADED)
return spd_set_notification(connection, notification, "on");
else
return -1;
}
int
spd_set_notification_off(SPDConnection * connection,
SPDNotification notification)
{
if (connection->mode == SPD_MODE_THREADED)
return spd_set_notification(connection, notification, "off");
else
return -1;
}
#define NOTIFICATION_SET(val, ssip_val) \
if (notification & val){ \
sprintf(command, "SET SELF NOTIFICATION "ssip_val" %s", state);\
ret = spd_execute_command_wo_mutex(connection, command);\
if (ret < 0) RET(-1);\
}
int
spd_set_notification(SPDConnection * connection, SPDNotification notification,
const char *state)
{
static char command[64];
int ret;
if (connection->mode != SPD_MODE_THREADED)
return -1;
if (state == NULL) {
SPD_DBG("Requested state is NULL");
return -1;
}
if (strcmp(state, "on") && strcmp(state, "off")) {
SPD_DBG("Invalid argument for spd_set_notification: %s", state);
return -1;
}
pthread_mutex_lock(&connection->ssip_mutex);
NOTIFICATION_SET(SPD_INDEX_MARKS, "index_marks");
NOTIFICATION_SET(SPD_BEGIN, "begin");
NOTIFICATION_SET(SPD_END, "end");
NOTIFICATION_SET(SPD_CANCEL, "cancel");
NOTIFICATION_SET(SPD_PAUSE, "pause");
NOTIFICATION_SET(SPD_RESUME, "resume");
NOTIFICATION_SET(SPD_ALL, "all");
pthread_mutex_unlock(&connection->ssip_mutex);
return 0;
}
#undef NOTIFICATION_SET
/* spd_list_modules retrieves information about the available output modules.
The return value is a null-terminated array of strings containing output module
names.
*/
char **spd_list_modules(SPDConnection * connection)
{
char **available_modules;
available_modules =
spd_execute_command_with_list_reply(connection,
"LIST OUTPUT_MODULES");
return available_modules;
}
void free_spd_modules(char **modules)
{
int i = 0;
while (modules != NULL && modules[i] != NULL) {
free(modules[i]);
++i;
}
free(modules);
}
char **spd_list_voices(SPDConnection * connection)
{
char **voices;
voices = spd_execute_command_with_list_reply(connection, "LIST VOICES");
return voices;
}
void free_spd_symbolic_voices(char **voices)
{
int i = 0;
while (voices != NULL && voices[i] != NULL) {
free(voices[i]);
++i;
}
free(voices);
}
SPDVoice **spd_list_synthesis_voices2(SPDConnection * connection, const char *language, const char *variant)
{
char **svoices_str;
SPDVoice **svoices;
int i, num_items;
char *command = g_strdup_printf("LIST SYNTHESIS_VOICES%s%s%s%s",
language ? " " : "",
language ? language : "",
language && variant ? " " : "",
variant ? variant : "");
svoices_str = spd_execute_command_with_list_reply(connection, command);
free(command);
if (svoices_str == NULL)
return NULL;
for (i = 0;; i++)
if (svoices_str[i] == NULL)
break;
num_items = i;
svoices = (SPDVoice **) malloc((num_items + 1) * sizeof(SPDVoice *));
for (i = 0; i <= num_items; i++) {
const char delimiters[] = "\t";
char *running;
if (svoices_str[i] == NULL)
break;
running = svoices_str[i];
svoices[i] = (SPDVoice *) malloc(sizeof(SPDVoice));
svoices[i]->name = strsep(&running, delimiters);
svoices[i]->language = strsep(&running, delimiters);
svoices[i]->variant = strsep(&running, delimiters);
assert(svoices[i]->name != NULL);
}
free(svoices_str);
svoices[num_items] = NULL;
return svoices;
}
SPDVoice **spd_list_synthesis_voices(SPDConnection * connection)
{
return spd_list_synthesis_voices2(connection, NULL, NULL);
}
void free_spd_voices(SPDVoice ** voices)
{
int i = 0;
while (voices != NULL && voices[i] != NULL) {
free(voices[i]->name);
free(voices[i]);
++i;
}
free(voices);
}
char **spd_execute_command_with_list_reply(SPDConnection * connection,
const char *command)
{
char *reply = NULL;
char *line;
int err;
int max_items = 50;
char **result;
int i;
spd_execute_command_with_reply(connection, command, &reply);
if (ret_ok(reply) <= 0) {
if (reply != NULL)
free(reply);
return NULL;
}
result = malloc((max_items + 1) * sizeof(char *));
char *cur = reply;
for (i = 0;; i++) {
line = get_param_str_and_advance(&cur, &err);
if ((err) || (line == NULL))
break;
result[i] = line;
if (i >= max_items - 2) {
max_items *= 2;
result = realloc(result, max_items * sizeof(char *));
}
}
result[i] = NULL;
free(reply);
return result;
}
//int
//spd_get_client_list(SPDConnection *connection, char **client_names, int *client_ids, int* active){
// SPD_DBG("spd_get_client_list: History is not yet implemented.");
// return -1;
//
//}
int
spd_get_message_list_fd(SPDConnection * connection, int target, int *msg_ids,
char **client_names)
{
SPD_DBG("spd_get_client_list: History is not yet implemented.");
return -1;
#if 0
sprintf(command, "HISTORY GET MESSAGE_LIST %d 0 20\r\n", target);
reply = spd_send_data(fd, command, 1);
/* header_ok = parse_response_header(reply);
if(header_ok != 1){
free(reply);
return -1;
} */
for (count = 0;; count++) {
record = (char *)parse_response_data(reply, count + 1);
if (record == NULL)
break;
record_int = get_rec_int(record, 0);
msg_ids[count] = record_int;
record_str = (char *)get_rec_str(record, 1);
assert(record_str != NULL);
client_names[count] = record_str;
}
return count;
#endif
}
int spd_execute_command(SPDConnection * connection, const char *command)
{
char *reply;
int ret;
pthread_mutex_lock(&connection->ssip_mutex);
ret = spd_execute_command_with_reply(connection, command, &reply);
if (ret) {
SPD_DBG("Can't execute command in spd_execute_command");
}
free(reply);
pthread_mutex_unlock(&connection->ssip_mutex);
return ret;
}
int spd_execute_command_wo_mutex(SPDConnection * connection, const char *command)
{
char *reply;
int ret;
SPD_DBG("Executing command wo_mutex");
ret = spd_execute_command_with_reply(connection, command, &reply);
if (ret)
SPD_DBG
("Can't execute command in spd_execute_command_wo_mutex");
free(reply);
return ret;
}
int
spd_execute_command_with_reply(SPDConnection * connection, const char *command,
char **reply)
{
char *buf;
int r;
SPD_DBG("Inside execute_command_with_reply");
buf = g_strdup_printf("%s\r\n", command);
*reply = spd_send_data_wo_mutex(connection, buf, SPD_WAIT_REPLY);
free(buf);
buf = NULL;
if (*reply == NULL) {
SPD_DBG
("Can't send data wo mutex in spd_execute_command_with_reply");
return -1;
}
r = ret_ok(*reply);
if (!r)
return -1;
else
return 0;
}
char *spd_send_data(SPDConnection * connection, const char *message, int wfr)
{
char *reply;
pthread_mutex_lock(&connection->ssip_mutex);
if (connection->socket < 0)
RET(NULL);
reply = spd_send_data_wo_mutex(connection, message, wfr);
if (reply == NULL) {
SPD_DBG("Can't send data wo mutex in spd_send_data");
RET(NULL);
}
pthread_mutex_unlock(&connection->ssip_mutex);
return reply;
}
char *spd_send_data_wo_mutex(SPDConnection * connection, const char *message,
int wfr)
{
char *reply;
int bytes;
SPD_DBG("Inside spd_send_data_wo_mutex");
if (connection->socket < 0)
return NULL;
if (connection->mode == SPD_MODE_THREADED) {
/* Make sure we don't get the cond_reply_ready signal before we are in
cond_wait() */
pthread_mutex_lock(&connection->td->mutex_reply_ready);
}
/* write message to the socket */
SPD_DBG("Writing to socket");
if (!write(connection->socket, message, strlen(message))) {
SPD_DBG("Can't write to socket: %s", strerror(errno));
if (connection->mode == SPD_MODE_THREADED)
pthread_mutex_unlock(&connection->td->mutex_reply_ready);
return NULL;
}
SPD_DBG("Written to socket");
SPD_DBG(">> : |%s|", message);
/* read reply to the buffer */
if (wfr) {
if (connection->mode == SPD_MODE_THREADED) {
/* Wait until the reply is ready */
SPD_DBG
("Waiting for cond_reply_ready in spd_send_data_wo_mutex");
pthread_cond_wait(&connection->td->cond_reply_ready,
&connection->td->mutex_reply_ready);
SPD_DBG("Condition for cond_reply_ready satisfied");
pthread_mutex_unlock(&connection->td->mutex_reply_ready);
SPD_DBG
("Reading the reply in spd_send_data_wo_mutex threaded mode");
/* Read the reply */
pthread_mutex_lock(&connection->td->mutex_reply_ack);
if (connection->reply != NULL) {
reply = connection->reply;
connection->reply = NULL;
} else {
SPD_DBG
("Error: Can't read reply, broken socket in spd_send_data.");
pthread_mutex_unlock(&connection->td->mutex_reply_ack);
return NULL;
}
/* Signal the reply has been read */
pthread_cond_signal(&connection->td->cond_reply_ack);
pthread_mutex_unlock(&connection->td->mutex_reply_ack);
bytes = strlen(reply);
if (bytes == 0) {
free(reply);
SPD_DBG("Error: Empty reply, broken socket.");
return NULL;
}
} else {
reply = get_reply(connection);
}
if (reply != NULL)
SPD_DBG("<< : |%s|\n", reply);
} else {
if (connection->mode == SPD_MODE_THREADED)
pthread_mutex_unlock(&connection->td->mutex_reply_ready);
SPD_DBG("<< : no reply expected");
return strdup("NO REPLY");
}
if (reply == NULL)
SPD_DBG
("Reply from get_reply is NULL in spd_send_data_wo_mutex");
SPD_DBG("Returning from spd_send_data_wo_mutex");
return reply;
}
/* --------------------- Internal functions ------------------------- */
static int spd_set_priority(SPDConnection * connection, SPDPriority priority)
{
static char p_name[16];
static char command[64];
switch (priority) {
case SPD_IMPORTANT:
strcpy(p_name, "IMPORTANT");
break;
case SPD_MESSAGE:
strcpy(p_name, "MESSAGE");
break;
case SPD_TEXT:
strcpy(p_name, "TEXT");
break;
case SPD_NOTIFICATION:
strcpy(p_name, "NOTIFICATION");
break;
case SPD_PROGRESS:
strcpy(p_name, "PROGRESS");
break;
default:
SPD_DBG("Error: Can't set priority! Incorrect value.");
return -1;
}
sprintf(command, "SET SELF PRIORITY %s", p_name);
return spd_execute_command_wo_mutex(connection, command);
}
struct get_reply_data {
GString *str;
};
static void get_reply_cleanup(void *arg)
{
struct get_reply_data *data = arg;
g_string_free(data->str, TRUE);
}
static char *get_reply(SPDConnection * connection)
{
char *reply;
char *line;
int n;
gboolean errors = FALSE;
struct get_reply_data data;
data.str = g_string_new("");
pthread_cleanup_push(get_reply_cleanup, &data);
/* Wait for activity on the socket, when there is some,
read all the message line by line */
do {
line = get_line(connection, &n);
if (line == NULL) {
SPD_DBG
("Error: Can't read reply, broken socket in get_reply!");
if (connection->socket >= 0) {
close(connection->socket);
connection->socket = -1;
connection->stream = NULL;
}
errors = TRUE;
} else {
g_string_append_len(data.str, line, n);
}
/* terminate if we reached the last line (without '-' after numcode) */
} while (!errors && n >= 4 && line[3] != ' ');
pthread_cleanup_pop(0);
if (errors) {
/* Free the GString and its character data, and return NULL. */
g_string_free(data.str, TRUE);
reply = NULL;
} else {
/* The resulting message received from the socket is stored in reply */
/* Free the GString, but not its character data. */
reply = g_string_free(data.str, FALSE);
}
return reply;
}
static void *spd_events_handler(void *conn)
{
char *reply;
int reply_code;
SPDConnection *connection = conn;
spd_pthread_setname("events handler");
while (1) {
/* Read the reply/event (block if none is available) */
SPD_DBG("Getting reply in spd_events_handler");
reply = get_reply(connection);
if (reply == NULL) {
SPD_DBG("ERROR: BROKEN SOCKET");
reply_code = -1;
} else {
SPD_DBG("<< : |%s|\n", reply);
reply_code = get_err_code(reply);
}
if ((reply_code >= 700) && (reply_code < 800)) {
int msg_id;
int client_id;
int err;
SPD_DBG("Callback detected: %s", reply);
/* This is an index mark */
/* Extract message id */
msg_id = get_param_int(reply, 1, &err);
if (err < 0) {
SPD_DBG
("Bad reply from Speech Dispatcher: %s (code %d)",
reply, err);
free(reply);
break;
}
client_id = get_param_int(reply, 2, &err);
if (err < 0) {
SPD_DBG
("Bad reply from Speech Dispatcher: %s (code %d)",
reply, err);
free(reply);
break;
}
/* Decide if we want to call a callback */
if ((reply_code == 701) && (connection->callback_begin))
connection->callback_begin(msg_id, client_id,
SPD_EVENT_BEGIN);
if ((reply_code == 702) && (connection->callback_end))
connection->callback_end(msg_id, client_id,
SPD_EVENT_END);
if ((reply_code == 703)
&& (connection->callback_cancel))
connection->callback_cancel(msg_id, client_id,
SPD_EVENT_CANCEL);
if ((reply_code == 704) && (connection->callback_pause))
connection->callback_pause(msg_id, client_id,
SPD_EVENT_PAUSE);
if ((reply_code == 705)
&& (connection->callback_resume))
connection->callback_resume(msg_id, client_id,
SPD_EVENT_RESUME);
if ((reply_code == 700) && (connection->callback_im)) {
char *im;
int err;
im = get_param_str(reply, 3, &err);
if ((err < 0) || (im == NULL)) {
SPD_DBG
("Broken reply from Speech Dispatcher: %s",
reply);
free(reply);
break;
}
/* Call the callback */
connection->callback_im(msg_id, client_id,
SPD_EVENT_INDEX_MARK,
im);
free(im);
}
free(reply);
} else {
/* This is a protocol reply */
pthread_mutex_lock(&connection->td->mutex_reply_ready);
/* Prepare the reply to the reply buffer in connection */
if (reply != NULL) {
connection->reply = reply;
} else {
SPD_DBG("Connection reply is NULL");
connection->reply = NULL;
pthread_mutex_unlock(&connection->td->mutex_reply_ready);
break;
}
/* Signal the reply is available on the condition variable */
/* this order is correct and necessary */
pthread_cond_signal(&connection->td->cond_reply_ready);
pthread_mutex_lock(&connection->td->mutex_reply_ack);
pthread_mutex_unlock(&connection->td->mutex_reply_ready);
/* Wait until it has been read */
while (connection->reply)
pthread_cond_wait(&connection->td->cond_reply_ack,
&connection->td->mutex_reply_ack);
pthread_mutex_unlock(&connection->td->mutex_reply_ack);
/* Continue */
}
}
/* In case of broken socket, we must still signal reply ready */
if (connection->reply == NULL) {
SPD_DBG("Signalling reply ready after communication failure");
if (connection->socket >= 0) {
close(connection->socket);
connection->socket = -1;
connection->stream = NULL;
}
pthread_cond_signal(&connection->td->cond_reply_ready);
pthread_exit(0);
}
return 0; /* to please gcc */
}
static int ret_ok(char *reply)
{
int err;
if (reply == NULL)
return -1;
err = get_err_code(reply);
if ((err >= 100) && (err < 300))
return 1;
if (err >= 300)
return 0;
SPD_FATAL("Internal error during communication.");
}
static char *get_param_str_and_advance(char **reply, int *err)
{
char *tptr;
char *pos;
char *pos_begin;
char *pos_end;
char *rep;
assert(err != NULL);
pos = *reply;
if (strlen(pos) < 4)
return NULL;
*err = strtol(pos, &tptr, 10);
if (*err >= 300 && *err <= 399)
return NULL;
if ((*tptr != '-') || (tptr != pos + 3)) {
*err = -3;
return NULL;
}
pos_begin = pos + 4;
pos_end = strstr(pos_begin, "\r\n");
if (pos_end == NULL) {
*err = -4;
return NULL;
}
rep = (char *)strndup(pos_begin, pos_end - pos_begin);
*err = 0;
*reply = pos_end + 2;
return rep;
}
static char *get_param_str(char *reply, int num, int *err)
{
int i;
char *pos;
assert(err != NULL);
if (num < 1) {
*err = -1;
return NULL;
}
pos = reply;
for (i = 0; i <= num - 2; i++) {
pos = strstr(pos, "\r\n");
if (pos == NULL) {
*err = -2;
return NULL;
}
pos += 2;
}
return get_param_str_and_advance(&pos, err);
}
static int get_param_int(char *reply, int num, int *err)
{
char *rep_str;
char *tptr;
int ret;
rep_str = get_param_str(reply, num, err);
if (rep_str == NULL) {
/* err is already set to the error return code, just return */
return 0;
}
ret = strtol(rep_str, &tptr, 10);
if (*tptr != '\0') {
/* this is not a number */
*err = -3;
free(rep_str);
return 0;
}
free(rep_str);
return ret;
}
static int get_err_code(char *reply)
{
char err_code[4];
int err;
if (reply == NULL)
return -1;
SPD_DBG("spd_send_data: reply: %s\n", reply);
err_code[0] = reply[0];
err_code[1] = reply[1];
err_code[2] = reply[2];
err_code[3] = '\0';
SPD_DBG("ret_ok: err_code: |%s|\n", err_code);
if (isanum(err_code)) {
err = atoi(err_code);
} else {
SPD_DBG("ret_ok: not a number\n");
return -1;
}
return err;
}
/* isanum() tests if the given string is a number,
* returns 1 if yes, 0 otherwise. */
static int isanum(char *str)
{
int i;
if (str == NULL)
return 0;
for (i = 0; i <= strlen(str) - 1; i++) {
if (!isdigit(str[i]))
return 0;
}
return 1;
}
/*
* escape_dot: Replace . with .. at the start of lines.
* @text: text to escape
* @Returns: An allocated string, containing the escaped text.
*/
static char *escape_dot(const char *text)
{
size_t orig_len = 0;
const char *orig_end;
char *result = NULL;
char *result_ptr;
static const char *ESCAPED_DOTLINE = "\r\n..";
static const size_t ESCAPED_DOTLINELEN = 4;
static const size_t DOTLINELEN = 3;
if (text == NULL)
return NULL;
orig_len = strlen(text);
orig_end = text + orig_len;
result = malloc((orig_len * 2 + 1) * sizeof(char));
if (result == NULL)
return NULL;
result_ptr = result;
/* We're over-allocating. Even if we replaced every character
* in text with "..", the length of the escaped string can be no more
* than orig_len * 2. We could tighten that upper bound with
* a little more work.
*/
if ((orig_len >= 1) && (text[0] == '.')) {
*(result_ptr++) = '.';
*(result_ptr++) = '.';
text += 1;
}
while (text < orig_end) {
if ((text[0] == '\r') && (text[1] == '\n') && (text[2] == '.')) {
memcpy(result_ptr, ESCAPED_DOTLINE, ESCAPED_DOTLINELEN);
result_ptr += ESCAPED_DOTLINELEN;
text += DOTLINELEN;
} else {
*(result_ptr++) = *(text++);
}
}
*result_ptr = '\0';
return result;
}
#ifdef LIBSPEECHD_DEBUG
static void SPD_DBG(char *format, ...)
{
va_list args;
pthread_mutex_lock(&spd_logging_mutex);
va_start(args, format);
vfprintf(spd_debug, format, args);
va_end(args);
fprintf(spd_debug, "\n");
fflush(spd_debug);
pthread_mutex_unlock(&spd_logging_mutex);
}
#else /* LIBSPEECHD_DEBUG */
static void SPD_DBG(char *format, ...)
{
}
#endif /* LIBSPEECHD_DEBUG */