ladish/lashd/jack_mgr.c

836 lines
19 KiB
C

/*
* LASH
*
* Copyright (C) 2002 Robert Ham <rah@bash.sh>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <stdarg.h>
#include <jack/jack.h>
#include <lash/lash.h>
#include <lash/internal_headers.h>
#include "jack_mgr_client.h"
#include "jack_mgr.h"
#include "server_event.h"
#include "server.h"
#include "jack_fport.h"
#define BACKUP_INTERVAL ((time_t)(30))
static void *jack_mgr_callback_run(void *data);
static void jack_mgr_new_client_port(jack_mgr_t * jack_mgr,
const char *port_name_port,
jack_mgr_client_t * client);
const char *
get_jack_port_name_only(const char *port_name)
{
static char *name = NULL;
char *ptr;
ptr = strchr(port_name, ':');
if (!ptr)
return NULL;
ptr++;
if (name)
free(name);
name = lash_strdup(ptr);
return name;
}
const char *
get_jack_client_name_only(const char *port_name)
{
static char *name = NULL;
char *ptr;
if (name)
free(name);
name = lash_strdup(port_name);
ptr = strchr(name, ':');
if (!ptr)
return NULL;
*ptr = '\0';
return name;
}
static jack_mgr_client_t *
jack_mgr_find_client(jack_mgr_t * jack_mgr, uuid_t id)
{
lash_list_t *list;
jack_mgr_client_t *client;
for (list = jack_mgr->clients; list; list = lash_list_next(list)) {
client = (jack_mgr_client_t *) list->data;
if (uuid_compare(id, client->id) == 0)
return client;
}
return NULL;
}
static void
jack_mgr_jack_error(const char *message)
{
fprintf(stderr, "JACK error: %s\n", message);
}
static void
jack_mgr_shutdown_cb(void *data)
{
server_t *server;
server = (server_t *) data;
fprintf(stderr, "%s: JACK server shut us down; telling server to quit\n",
__FUNCTION__);
server->quit = 1;
pthread_cond_signal(&server->server_event_cond);
}
static void
jack_mgr_port_registration_cb(jack_port_id_t port_id, int registered,
void *data)
{
jack_mgr_t *jack_mgr;
ssize_t err;
char port_notify = 1;
char reg = registered;
jack_mgr = (jack_mgr_t *) data;
err =
write(jack_mgr->callback_write_socket, &port_notify,
sizeof(port_notify));
if (err == -1) {
fprintf(stderr,
"%s: could not send data to jack manager callback, aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
err = write(jack_mgr->callback_write_socket, &port_id, sizeof(port_id));
if (err == -1) {
fprintf(stderr,
"%s: could not send data to jack manager callback, aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
err = write(jack_mgr->callback_write_socket, &reg, sizeof(reg));
if (err == -1) {
fprintf(stderr,
"%s: could not send data to jack manager callback, aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
}
static int
jack_mgr_graph_reorder_cb(void *data)
{
jack_mgr_t *jack_mgr;
ssize_t err;
char port_notify = 0;
jack_mgr = (jack_mgr_t *) data;
err =
write(jack_mgr->callback_write_socket, &port_notify,
sizeof(port_notify));
if (err == -1) {
fprintf(stderr,
"%s: could not send data to jack manager callback, aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
return 0;
}
static void
jack_mgr_init_jack(jack_mgr_t * jack_mgr)
{
int err;
const char *client_name = "LASH_Server";
jack_set_error_function(jack_mgr_jack_error);
jack_mgr->jack_client = jack_client_new(client_name);
if (!jack_mgr->jack_client) {
fprintf(stderr, "%s: could not connect to jackd, exiting\n",
__FUNCTION__);
exit(1);
}
printf("Connected to JACK server with client name '%s'\n", client_name);
err =
jack_set_port_registration_callback(jack_mgr->jack_client,
jack_mgr_port_registration_cb,
jack_mgr);
if (err) {
fprintf(stderr, "%s: could not set jack port registration callback\n",
__FUNCTION__);
abort();
}
err =
jack_set_graph_order_callback(jack_mgr->jack_client,
jack_mgr_graph_reorder_cb, jack_mgr);
if (err) {
fprintf(stderr, "%s: could not set jack graph order callback\n",
__FUNCTION__);
abort();
}
jack_on_shutdown(jack_mgr->jack_client, jack_mgr_shutdown_cb,
jack_mgr->server);
err = jack_activate(jack_mgr->jack_client);
if (err) {
fprintf(stderr, "%s: could not activate jack client\n", __FUNCTION__);
abort();
}
}
jack_mgr_t *
jack_mgr_new(server_t * server)
{
jack_mgr_t *jack_mgr;
int sockets[2];
int err;
jack_mgr = lash_malloc(sizeof(jack_mgr_t));
jack_mgr->server = server;
jack_mgr->quit = 0;
jack_mgr->clients = NULL;
jack_mgr->foreign_ports = NULL;
pthread_mutex_init(&jack_mgr->lock, NULL);
err = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
if (err == -1) {
fprintf(stderr, "%s: could not create unix socket pair: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
jack_mgr->callback_read_socket = sockets[0];
jack_mgr->callback_write_socket = sockets[1];
jack_mgr_init_jack(jack_mgr);
pthread_create(&jack_mgr->callback_thread, NULL, jack_mgr_callback_run,
jack_mgr);
return jack_mgr;
}
void
jack_mgr_destroy(jack_mgr_t * jack_mgr)
{
int err;
char dummy;
jack_mgr->quit = 1;
err = jack_deactivate(jack_mgr->jack_client);
if (err) {
fprintf(stderr, "%s: could not deactivate jack client\n",
__FUNCTION__);
}
LASH_PRINT_DEBUG("stopping");
err = write(jack_mgr->callback_write_socket, &dummy, sizeof(dummy));
if (err == -1) {
fprintf(stderr,
"%s: could not send data to jack manager callback, aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
err = pthread_join(jack_mgr->callback_thread, NULL);
if (err) {
fprintf(stderr, "%s: error joining callback thread: %s\n",
__FUNCTION__, strerror(errno));
return;
}
LASH_PRINT_DEBUG("stopped");
jack_client_close(jack_mgr->jack_client);
/* FIXME: free all the data */
free(jack_mgr);
}
void
jack_mgr_lock(jack_mgr_t * jack_mgr)
{
pthread_mutex_lock(&jack_mgr->lock);
}
void
jack_mgr_unlock(jack_mgr_t * jack_mgr)
{
pthread_mutex_unlock(&jack_mgr->lock);
}
/* caller must hold clients lock */
void
jack_mgr_check_client_ports(jack_mgr_t * jack_mgr, jack_mgr_client_t * client)
{
char *port_name;
lash_list_t *list;
jack_fport_t *fport;
for (list = jack_mgr->foreign_ports; list;) {
fport = (jack_fport_t *) list->data;
list = lash_list_next(list);
if (strcmp(client->name, get_jack_client_name_only(fport->name)) == 0) {
port_name = lash_strdup(get_jack_port_name_only(fport->name));
LASH_DEBUGARGS("resuming previously foreign port '%s'",
fport->name);
jack_mgr_new_client_port(jack_mgr, port_name, client);
free(port_name);
jack_mgr->foreign_ports =
lash_list_remove(jack_mgr->foreign_ports, fport);
jack_fport_destroy(fport);
}
}
}
void
jack_mgr_add_client(jack_mgr_t * jack_mgr,
uuid_t id,
const char *jack_client_name, lash_list_t * jack_patches)
{
jack_mgr_client_t *client;
LASH_DEBUGARGS("adding client '%s'", jack_client_name);
client = jack_mgr_client_new();
jack_mgr_client_set_id(client, id);
jack_mgr_client_set_name(client, jack_client_name);
client->old_patches = jack_patches;
jack_mgr->clients = lash_list_append(jack_mgr->clients, client);
/* check if it's registered some ports already */
jack_mgr_check_client_ports(jack_mgr, client);
LASH_PRINT_DEBUG("client added");
}
lash_list_t *
jack_mgr_remove_client(jack_mgr_t * jack_mgr, uuid_t id)
{
jack_mgr_client_t *client;
lash_list_t *backup_patches;
LASH_PRINT_DEBUG("removing client");
client = jack_mgr_find_client(jack_mgr, id);
if (!client) {
fprintf(stderr, "%s: unknown client\n", __FUNCTION__);
return NULL;
}
jack_mgr->clients = lash_list_remove(jack_mgr->clients, client);
LASH_DEBUGARGS("removed client '%s'", client->name);
backup_patches = client->backup_patches;
client->backup_patches = NULL;
jack_mgr_client_destroy(client);
return backup_patches;
}
lash_list_t *
jack_mgr_get_client_patches(jack_mgr_t * jack_mgr, uuid_t id)
{
jack_mgr_client_t *client;
lash_list_t *node;
lash_list_t *uniq_node;
lash_list_t *patches;
lash_list_t *uniq_patches = NULL;
jack_patch_t *patch;
jack_patch_t *uniq_patch;
client = jack_mgr_find_client(jack_mgr, id);
if (!client) {
fprintf(stderr, "%s: unknown client\n", __FUNCTION__);
return NULL;
}
patches = jack_mgr_client_dup_patches(client);
for (node = patches; node; node = lash_list_next(node)) {
patch = (jack_patch_t *) node->data;
jack_patch_set(patch, jack_mgr->clients);
for (uniq_node = uniq_patches; uniq_node;
uniq_node = lash_list_next(uniq_node)) {
uniq_patch = (jack_patch_t *) uniq_node->data;
if (strcmp(patch->src_client, uniq_patch->src_client) == 0 &&
strcmp(patch->src_port, uniq_patch->src_port) == 0 &&
strcmp(patch->dest_client, uniq_patch->dest_client) == 0 &&
strcmp(patch->dest_port, uniq_patch->dest_port) == 0)
break;
}
if (uniq_node)
jack_patch_destroy(patch);
else
uniq_patches = lash_list_append(uniq_patches, patch);
}
lash_list_free(patches);
return uniq_patches;
}
/* caller must hold client lock */
static void
jack_mgr_add_foreign_port(jack_mgr_t * jack_mgr, jack_fport_t * port)
{
jack_mgr->foreign_ports = lash_list_append(jack_mgr->foreign_ports, port);
}
static void
jack_mgr_remove_foreign_port(jack_mgr_t * jack_mgr, jack_port_id_t id)
{
jack_fport_t *port;
lash_list_t *list;
for (list = jack_mgr->foreign_ports; list; list = lash_list_next(list)) {
port = (jack_fport_t *) list->data;
if (id == port->id) {
jack_mgr->foreign_ports =
lash_list_remove(jack_mgr->foreign_ports, port);
LASH_DEBUGARGS("removed foreign port '%s'", port->name);
jack_fport_destroy(port);
return;
}
}
}
static int
jack_mgr_resume_patch(jack_mgr_t * jack_mgr, jack_patch_t * patch)
{
int err;
char *src;
char *dest;
src = lash_strdup(jack_patch_get_src(patch));
dest = lash_strdup(jack_patch_get_dest(patch));
err = jack_connect(jack_mgr->jack_client, src, dest);
if (err) {
LASH_DEBUGARGS("could not (yet?) resume patch %s",
jack_patch_get_desc(patch));
} else
printf("Connected JACK port '%s' to '%s'\n", src, dest);
free(src);
free(dest);
return err;
}
/*
* Here we have to go through each patch, see if it's the specified port,
* unset it, see if we can connect it, and remove it. Other clients will
* have connections for this one if they're not registered yet.
*/
static void
jack_mgr_new_client_port(jack_mgr_t * jack_mgr, const char *port,
jack_mgr_client_t * client)
{
lash_list_t *node;
jack_patch_t *patch, *unset_patch = NULL;
int err;
for (node = client->old_patches; node;) {
patch = (jack_patch_t *) node->data;
node = lash_list_next(node);
LASH_DEBUGARGS("checking patch %s", jack_patch_get_desc(patch));
unset_patch = jack_patch_dup(patch);
err = jack_patch_unset(unset_patch, jack_mgr->clients);
if (err) {
LASH_DEBUGARGS
("could not resume jack patch '%s' as the other client hasn't registered yet",
jack_patch_get_desc(patch));
client->old_patches =
lash_list_remove(client->old_patches, patch);
jack_patch_destroy(patch);
jack_patch_destroy(unset_patch);
continue;
}
if ((strcmp(client->name, unset_patch->src_client) == 0 &&
strcmp(port, unset_patch->src_port) == 0) ||
(strcmp(client->name, unset_patch->dest_client) == 0 &&
strcmp(port, unset_patch->dest_port) == 0)) {
LASH_DEBUGARGS("attempting to resume patch %s",
jack_patch_get_desc(unset_patch));
err = jack_mgr_resume_patch(jack_mgr, unset_patch);
if (!err) {
client->old_patches =
lash_list_remove(client->old_patches, patch);
jack_patch_destroy(patch);
}
}
jack_patch_destroy(unset_patch);
}
}
static void
jack_mgr_port_notification(jack_mgr_t * jack_mgr, jack_port_id_t port_id)
{
jack_mgr_client_t *client;
jack_port_t *jack_port;
char *client_name;
lash_list_t *list;
char *ptr;
/* get the client name */
jack_port = jack_port_by_id(jack_mgr->jack_client, port_id);
if (!jack_port) {
fprintf(stderr,
"%s: jack port registration notification for non-existant port\n",
__FUNCTION__);
return;
}
LASH_DEBUGARGS("recieved port registration notification for port '%u'",
port_id);
client_name = lash_strdup(jack_port_name(jack_port));
ptr = strchr(client_name, ':');
if (!ptr) {
fprintf(stderr,
"%s: port name '%s' contains no client name/port name seperator\n",
__FUNCTION__, client_name);
return;
}
*ptr = '\0';
for (list = jack_mgr->clients; list; list = lash_list_next(list)) {
client = (jack_mgr_client_t *) list->data;
if (strcmp(jack_mgr_client_get_name(client), client_name) == 0)
break;
}
if (!list) {
jack_fport_t *fport;
LASH_DEBUGARGS("adding foreign port '%s'", jack_port_name(jack_port));
fport = jack_fport_new_with_id(port_id);
jack_fport_set_name(fport, jack_port_name(jack_port));
jack_mgr_add_foreign_port(jack_mgr, fport);
return;
}
ptr++;
jack_mgr_new_client_port(jack_mgr, ptr, client);
free(client_name);
}
lash_list_t *
jack_mgr_get_patches_with_type(const char *client_name,
jack_client_t * jack_client, int type_flags)
{
jack_patch_t *patch;
char *port_name_regex;
jack_port_t *port;
char **port_names;
char **connected_port_names;
int i, j;
lash_list_t *list = NULL;
port_name_regex = lash_malloc(strlen(client_name) + 4);
sprintf(port_name_regex, "%s:.*", client_name);
port_names =
(char **)jack_get_ports(jack_client, port_name_regex, NULL,
type_flags);
free(port_name_regex);
if (!port_names)
return NULL;
for (i = 0; port_names[i]; i++) {
port = jack_port_by_name(jack_client, port_names[i]);
connected_port_names =
(char **)jack_port_get_all_connections(jack_client, port);
if (!connected_port_names)
continue;
for (j = 0; connected_port_names[j]; j++) {
patch = jack_patch_new();
jack_patch_set_src(patch, port_names[i]);
jack_patch_set_dest(patch, connected_port_names[j]);
if (type_flags & JackPortIsInput)
jack_patch_switch_clients(patch);
list = lash_list_append(list, patch);
LASH_DEBUGARGS("found patch: '%s'/'%s'",
jack_patch_get_src(patch),
jack_patch_get_dest(patch));
}
/* free (connected_port_names); */
}
/* free (port_names); */
return list;
}
static void
jack_mgr_get_patches(jack_mgr_t * jack_mgr, jack_mgr_client_t * client)
{
lash_list_t *patches = NULL;
jack_mgr_client_free_patches(client);
patches = jack_mgr_get_patches_with_type(client->name,
jack_mgr->jack_client,
JackPortIsInput);
client->patches = patches;
patches = jack_mgr_get_patches_with_type(client->name,
jack_mgr->jack_client,
JackPortIsOutput);
client->patches = lash_list_concat(client->patches, patches);
}
static void
jack_mgr_check_foreign_ports(jack_mgr_t * jack_mgr)
{
lash_list_t *list;
jack_fport_t *fport;
jack_port_t *port;
for (list = jack_mgr->foreign_ports; list;) {
fport = (jack_fport_t *) list->data;
list = lash_list_next(list);
port =
jack_port_by_id(jack_mgr->jack_client, jack_fport_get_id(fport));
if (!port) {
jack_mgr->foreign_ports =
lash_list_remove(jack_mgr->foreign_ports, fport);
LASH_DEBUGARGS("removed foreign port '%s'", fport->name);
jack_fport_destroy(fport);
}
}
}
static void
jack_mgr_graph_reorder_notification(jack_mgr_t * jack_mgr)
{
lash_list_t *list;
jack_mgr_client_t *client;
for (list = jack_mgr->clients; list; list = lash_list_next(list)) {
client = (jack_mgr_client_t *) list->data;
jack_mgr_get_patches(jack_mgr, client);
}
jack_mgr_check_foreign_ports(jack_mgr);
}
static void
jack_mgr_read_callback(jack_mgr_t * jack_mgr)
{
char port_notify;
char registered;
jack_port_id_t port_id;
int err;
err =
read(jack_mgr->callback_read_socket, &port_notify,
sizeof(port_notify));
if (err == -1) {
fprintf(stderr,
"%s: could not read data from callback socket; aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
if (port_notify) {
LASH_PRINT_DEBUG("jack port reg callback happened");
err = read(jack_mgr->callback_read_socket, &port_id, sizeof(port_id));
if (err == -1) {
fprintf(stderr,
"%s: could not read data from callback socket; aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
err =
read(jack_mgr->callback_read_socket, &registered,
sizeof(registered));
if (err == -1) {
fprintf(stderr,
"%s: could not read data from callback socket; aborting: %s\n",
__FUNCTION__, strerror(errno));
abort();
}
if (registered)
jack_mgr_port_notification(jack_mgr, port_id);
else
jack_mgr_remove_foreign_port(jack_mgr, port_id);
} else {
LASH_PRINT_DEBUG("jack graph order callback happened");
jack_mgr_graph_reorder_notification(jack_mgr);
}
}
static void
jack_mgr_backup_patches(jack_mgr_t * jack_mgr)
{
static time_t last_backup = 0;
time_t now;
lash_list_t *list, *patches;
jack_mgr_client_t *client;
jack_patch_t *patch;
now = time(NULL);
if (now - last_backup < BACKUP_INTERVAL)
return;
for (list = jack_mgr->clients; list; list = lash_list_next(list)) {
client = (jack_mgr_client_t *) list->data;
jack_mgr_client_free_backup_patches(client);
client->backup_patches = jack_mgr_client_dup_patches(client);
for (patches = client->backup_patches; patches;
patches = lash_list_next(patches)) {
patch = (jack_patch_t *) patches->data;
jack_patch_set(patch, jack_mgr->clients);
}
}
last_backup = now;
}
static void *
jack_mgr_callback_run(void *data)
{
jack_mgr_t *jack_mgr;
fd_set socket_set;
fd_set select_set;
struct timeval timeout;
int err;
jack_mgr = (jack_mgr_t *) data;
FD_ZERO(&socket_set);
FD_SET(jack_mgr->callback_read_socket, &socket_set);
while (!jack_mgr->quit) {
/* backup timeout */
timeout.tv_sec = BACKUP_INTERVAL;
timeout.tv_usec = 0;
select_set = socket_set;
err =
select(jack_mgr->callback_read_socket + 1, &select_set, NULL,
NULL, &timeout);
if (err == -1) {
if (errno == EINTR)
continue;
fprintf(stderr, "%s: error calling select(): %s\n", __FUNCTION__,
strerror(errno));
}
if (jack_mgr->quit)
break;
jack_mgr_lock(jack_mgr);
if (FD_ISSET(jack_mgr->callback_read_socket, &select_set))
jack_mgr_read_callback(jack_mgr);
jack_mgr_backup_patches(jack_mgr);
jack_mgr_unlock(jack_mgr);
}
LASH_PRINT_DEBUG("finished");
return NULL;
}
/* EOF */