diff options
-rw-r--r-- | example-clients/Makefile.am | 5 | ||||
-rw-r--r-- | example-clients/simple_session_client.c | 193 | ||||
-rw-r--r-- | jack/Makefile.am | 1 | ||||
-rw-r--r-- | jack/engine.h | 13 | ||||
-rw-r--r-- | jack/internal.h | 32 | ||||
-rw-r--r-- | jack/jack.h | 1 | ||||
-rw-r--r-- | jack/session.h | 229 | ||||
-rw-r--r-- | jack/types.h | 21 | ||||
-rw-r--r-- | jack/varargs.h | 3 | ||||
-rw-r--r-- | jackd/clientengine.c | 99 | ||||
-rw-r--r-- | jackd/engine.c | 242 | ||||
-rw-r--r-- | libjack/client.c | 227 | ||||
-rw-r--r-- | libjack/local.h | 5 | ||||
-rw-r--r-- | python/libjack.py | 363 | ||||
-rwxr-xr-x | python/sessionmanager.py | 277 | ||||
-rw-r--r-- | python/state.py | 136 | ||||
-rw-r--r-- | tools/Makefile.am | 5 | ||||
-rw-r--r-- | tools/connect.c | 55 | ||||
-rw-r--r-- | tools/session_notify.c | 180 |
19 files changed, 2047 insertions, 40 deletions
diff --git a/example-clients/Makefile.am b/example-clients/Makefile.am index 40eb530..9f1ef53 100644 --- a/example-clients/Makefile.am +++ b/example-clients/Makefile.am @@ -13,6 +13,7 @@ dist-check-sndfile: endif bin_PROGRAMS = jack_simple_client \ + jack_simple_session_client \ jack_transport_client \ jack_impulse_grabber \ jack_metro \ @@ -33,6 +34,10 @@ jack_simple_client_SOURCES = simple_client.c jack_simple_client_LDFLAGS = @OS_LDFLAGS@ jack_simple_client_LDADD = $(top_builddir)/libjack/libjack.la +jack_simple_session_client_SOURCES = simple_session_client.c +jack_simple_session_client_LDFLAGS = @OS_LDFLAGS@ +jack_simple_session_client_LDADD = $(top_builddir)/libjack/libjack.la + jack_transport_client_SOURCES = transport_client.c jack_transport_client_LDFLAGS = @OS_LDFLAGS@ jack_transport_client_LDADD = $(top_builddir)/libjack/libjack.la diff --git a/example-clients/simple_session_client.c b/example-clients/simple_session_client.c new file mode 100644 index 0000000..76b0cc9 --- /dev/null +++ b/example-clients/simple_session_client.c @@ -0,0 +1,193 @@ +/** @file simple_session_client.c + * + * @brief This simple client demonstrates the most basic features of JACK + * as they would be used by many applications. + * this version also adds session manager functionality. + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#include <jack/jack.h> +#include <jack/session.h> + +jack_port_t *input_port; +jack_port_t *output_port; +jack_client_t *client; + +int simple_quit = 0; + +/** + * The process callback for this JACK application is called in a + * special realtime thread once for each audio cycle. + * + * This client does nothing more than copy data from its input + * port to its output port. It will exit when stopped by + * the user (e.g. using Ctrl-C on a unix-ish operating system) + */ +int +process (jack_nframes_t nframes, void *arg) +{ + jack_default_audio_sample_t *in, *out; + + in = jack_port_get_buffer (input_port, nframes); + out = jack_port_get_buffer (output_port, nframes); + memcpy (out, in, + sizeof (jack_default_audio_sample_t) * nframes); + + return 0; +} + +void +session_callback (jack_session_event_t *event, void *arg) +{ + char retval[100]; + printf ("session notification\n"); + printf ("path %s, uuid %s, type: %s\n", event->session_dir, event->client_uuid, event->type == JackSessionSave ? "save" : "quit"); + + + snprintf (retval, 100, "jack_simple_client %s", event->client_uuid); + event->command_line = strdup (retval); + + jack_session_reply( client, event ); + + if (event->type == JackSessionSaveAndQuit) { + simple_quit = 1; + } + + jack_session_event_free (event); +} + +/** + * JACK calls this shutdown_callback if the server ever shuts down or + * decides to disconnect the client. + */ +void +jack_shutdown (void *arg) +{ + exit (1); +} + +int +main (int argc, char *argv[]) +{ + const char **ports; + const char *client_name = "simple"; + jack_status_t status; + + /* open a client connection to the JACK server */ + + if( argc == 1 ) + client = jack_client_open (client_name, JackNullOption, &status ); + else if( argc == 2 ) + client = jack_client_open (client_name, JackSessionID, &status, argv[1] ); + + if (client == NULL) { + fprintf (stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + } + exit (1); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + client_name = jack_get_client_name(client); + fprintf (stderr, "unique name `%s' assigned\n", client_name); + } + + /* tell the JACK server to call `process()' whenever + there is work to be done. + */ + + jack_set_process_callback (client, process, 0); + + /* tell the JACK server to call `jack_shutdown()' if + it ever shuts down, either entirely, or if it + just decides to stop calling us. + */ + + jack_on_shutdown (client, jack_shutdown, 0); + + /* tell the JACK server to call `session_callback()' if + the session is saved. + */ + + jack_set_session_callback (client, session_callback, NULL); + + /* display the current sample rate. + */ + + printf ("engine sample rate: %" PRIu32 "\n", + jack_get_sample_rate (client)); + + /* create two ports */ + + input_port = jack_port_register (client, "input", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsInput, 0); + output_port = jack_port_register (client, "output", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + + if ((input_port == NULL) || (output_port == NULL)) { + fprintf(stderr, "no more JACK ports available\n"); + exit (1); + } + + /* Tell the JACK server that we are ready to roll. Our + * process() callback will start running now. */ + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + exit (1); + } + + /* Connect the ports. You can't do this before the client is + * activated, because we can't make connections to clients + * that aren't running. Note the confusing (but necessary) + * orientation of the driver backend ports: playback ports are + * "input" to the backend, and capture ports are "output" from + * it. + */ + + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsOutput); + if (ports == NULL) { + fprintf(stderr, "no physical capture ports\n"); + exit (1); + } + + if (jack_connect (client, ports[0], jack_port_name (input_port))) { + fprintf (stderr, "cannot connect input ports\n"); + } + + free (ports); + + ports = jack_get_ports (client, NULL, NULL, + JackPortIsPhysical|JackPortIsInput); + if (ports == NULL) { + fprintf(stderr, "no physical playback ports\n"); + exit (1); + } + + if (jack_connect (client, jack_port_name (output_port), ports[0])) { + fprintf (stderr, "cannot connect output ports\n"); + } + + free (ports); + + /* keep running until until we get a quit event */ + + while (!simple_quit) + sleep(1); + + + jack_client_close (client); + exit (0); +} diff --git a/jack/Makefile.am b/jack/Makefile.am index 44c378c..8da1399 100644 --- a/jack/Makefile.am +++ b/jack/Makefile.am @@ -7,6 +7,7 @@ libjackinclude_HEADERS = \ jack.h \ ringbuffer.h \ statistics.h \ + session.h \ thread.h \ timestamps.h \ transport.h \ diff --git a/jack/engine.h b/jack/engine.h index bab00ba..46e0d51 100644 --- a/jack/engine.h +++ b/jack/engine.h @@ -51,6 +51,11 @@ typedef struct _jack_port_buffer_list { jack_port_buffer_info_t *info; /* jack_buffer_info_t array */ } jack_port_buffer_list_t; +typedef struct _jack_reserved_name { + jack_client_id_t uuid; + char name[JACK_CLIENT_NAME_SIZE]; +} jack_reserved_name_t; + #define JACKD_WATCHDOG_TIMEOUT 10000 #define JACKD_CLIENT_EVENT_TIMEOUT 2000 @@ -109,6 +114,11 @@ struct _jack_engine { char fifo_prefix[PATH_MAX+1]; int *fifo; unsigned long fifo_size; + + /* session handling */ + int session_reply_fd; + int session_pending_replies; + unsigned long external_client_cnt; int rtpriority; volatile char freewheeling; @@ -130,6 +140,7 @@ struct _jack_engine { /* these lists are protected by `client_lock' */ JSList *clients; JSList *clients_waiting; + JSList *reserved_client_names; jack_port_internal_t *internal_ports; jack_client_internal_t *timebase_client; @@ -234,5 +245,7 @@ void jack_port_registration_notify (jack_engine_t *, jack_port_id_t, int); void jack_port_release (jack_engine_t *engine, jack_port_internal_t *); void jack_sort_graph (jack_engine_t *engine); int jack_stop_freewheeling (jack_engine_t* engine, int engine_exiting); +jack_client_internal_t * +jack_client_by_name (jack_engine_t *engine, const char *name); #endif /* __jack_engine_h__ */ diff --git a/jack/internal.h b/jack/internal.h index ebcfcb7..801ddc9 100644 --- a/jack/internal.h +++ b/jack/internal.h @@ -58,6 +58,7 @@ extern void jack_info (const char *fmt, ...); #include <jack/types.h> #include <jack/port.h> #include <jack/transport.h> +#include <jack/session.h> #include <jack/thread.h> extern jack_thread_creator_t jack_thread_creator; @@ -217,14 +218,15 @@ typedef enum { StartFreewheel, StopFreewheel, ClientRegistered, - ClientUnregistered + ClientUnregistered, + SaveSession } JackEventType; typedef struct { JackEventType type; union { uint32_t n; - char name[JACK_CLIENT_NAME_SIZE]; + char name[JACK_PORT_NAME_SIZE]; jack_port_id_t port_id; jack_port_id_t self_id; } x; @@ -252,9 +254,12 @@ typedef enum { typedef volatile struct { volatile jack_client_id_t id; /* w: engine r: engine and client */ + volatile jack_client_id_t uid; /* w: engine r: engine and client */ volatile jack_nframes_t nframes; /* w: engine r: client */ volatile jack_client_state_t state; /* w: engine and client r: engine */ volatile char name[JACK_CLIENT_NAME_SIZE]; + volatile char session_command[JACK_PORT_NAME_SIZE]; + volatile jack_session_flags_t session_flags; volatile ClientType type; /* w: engine r: engine and client */ volatile int8_t active; /* w: engine r: engine and client */ volatile int8_t dead; /* r/w: engine */ @@ -292,6 +297,7 @@ typedef volatile struct { volatile uint8_t freewheel_cb_cbset; volatile uint8_t client_register_cbset; volatile uint8_t thread_cb_cbset; + volatile uint8_t session_cbset; } POST_PACKED_STRUCTURE jack_client_control_t; @@ -301,6 +307,7 @@ typedef struct { int32_t load; ClientType type; jack_options_t options; + jack_client_id_t uuid; char name[JACK_CLIENT_NAME_SIZE]; char object_path[PATH_MAX+1]; @@ -370,7 +377,11 @@ typedef enum { IntClientName = 21, IntClientUnload = 22, RecomputeTotalLatencies = 23, - RecomputeTotalLatency = 24 + RecomputeTotalLatency = 24, + SessionNotify = 25, + GetClientByUUID = 26, + ReserveName = 30, + SessionReply = 31 } RequestType; struct _jack_request { @@ -391,6 +402,11 @@ struct _jack_request { char destination_port[JACK_PORT_NAME_SIZE]; } POST_PACKED_STRUCTURE connect; struct { + char path[JACK_PORT_NAME_SIZE]; + jack_session_event_type_t type; + char target[JACK_CLIENT_NAME_SIZE]; + } POST_PACKED_STRUCTURE session; + struct { int32_t nports; const char **ports; /* this is only exposed to internal clients, so there is no 64/32 issue. external clients read the ports @@ -408,6 +424,10 @@ struct _jack_request { int32_t conditional; } POST_PACKED_STRUCTURE timebase; struct { + char name[JACK_CLIENT_NAME_SIZE]; + jack_client_id_t uuid; + } POST_PACKED_STRUCTURE reservename; + struct { //jack_options_t options; uint32_t options; jack_client_id_t id; @@ -447,6 +467,8 @@ typedef struct _jack_client_internal { int (*initialize)(jack_client_t*, const char*); /* int. clients only */ void (*finish)(void *); /* internal clients only */ int error; + + int session_reply_pending; #ifdef JACK_USE_MACH_THREADS /* specific resources for server/client real-time thread communication */ @@ -456,10 +478,6 @@ typedef struct _jack_client_internal { int portnum; #endif /* JACK_USE_MACH_THREADS */ - /* external clients: set by libjack - * internal clients: set by engine */ - //int (*deliver_request)(void*, jack_request_t*); /* JOQ: 64/32 bug! */ - //void *deliver_arg; jack_client_t *private_client; } jack_client_internal_t; diff --git a/jack/jack.h b/jack/jack.h index ac50a12..21174dc 100644 --- a/jack/jack.h +++ b/jack/jack.h @@ -406,7 +406,6 @@ int jack_set_graph_order_callback (jack_client_t *, */ int jack_set_xrun_callback (jack_client_t *, JackXRunCallback xrun_callback, void *arg) JACK_OPTIONAL_WEAK_EXPORT; - /*@}*/ /** diff --git a/jack/session.h b/jack/session.h new file mode 100644 index 0000000..8c13bfa --- /dev/null +++ b/jack/session.h @@ -0,0 +1,229 @@ +/* + Copyright (C) 2001 Paul Davis + Copyright (C) 2004 Jack O'Quin + Copyright (C) 2010 Torben Hohn + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + This 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 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __jack_session_h__ +#define __jack_session_h__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include <jack/types.h> +#include <jack/weakmacros.h> + +/** + * @defgroup SessionClientFunctions Session API for clients. + * @{ + */ + + +/** + * session event types. + * + * if a client cant save templates, i might just do a normal save. + * + * the rationale, why there is no quit without save, is that a client + * might refuse to quit when it has unsaved data. + * however some other clients might have already quit. + * this results in too much confusion, so we just dont support that. + * the session manager can check, if the saved state is different from a previous + * save, and just remove the saved stuff. + * + * (an inquiry function, whether a quit is ok, followed by a quit event + * would have a race) + */ +enum JackSessionEventType { + JackSessionSave = 1, + JackSessionSaveAndQuit = 2, + JackSessionSaveTemplate = 3 +}; + +typedef enum JackSessionEventType jack_session_event_type_t; + +enum JackSessionFlags { + /** + * an error occured while saving. + */ + JackSessionSaveError = 0x01, + + /** + * this reply indicates that a client is part of a multiclient application. + * the command reply is left empty. but the session manager should still + * consider this client part of a session. it will come up due to invocation of another + * client. + */ + JackSessionChildClient = 0x02 +}; + +typedef enum JackSessionFlags jack_session_flags_t; + +struct _jack_session_event { + /** + * the actual type of this session event. + */ + jack_session_event_type_t type; + + /** + * session_directory with trailing separator + * this is per client. so the client can do whatever it likes in here. + */ + const char *session_dir; + + /** + * client_uuid which must be specified to jack_client_open on session reload. + * client can specify it in the returned commandline as an option, or just save it + * with the state file. + */ + const char *client_uuid; + + /** + * the command_line is the reply of the client. + * it specifies in a platform dependent way, how the client must be restarted upon session reload. + * + * probably it should contain ${SESSION_DIR} instead of the actual session dir. + * this would basically make the session dir moveable. + * + * ownership of the memory is handed to jack. + * initially set to NULL by jack; + */ + char *command_line; + + /** + * flags to be set by the client. normally left 0. + */ + jack_session_flags_t flags; +}; + +typedef struct _jack_session_event jack_session_event_t; + +/** + * Prototype for the client supplied function that is called + * whenever a session notification is sent via jack_session_notify(). + * + * The session_id must be passed to jack_client_open on session reload (this can be + * done by specifying it somehow on the returned command line). + * + * @param event the event_structure. + * @param arg pointer to a client supplied structure + */ +typedef void (*JackSessionCallback)(jack_session_event_t *event, void *arg); + +/** + * Tell the JACK server to call @a save_callback the session handler wants + * to save. + * + * @return 0 on success, otherwise a non-zero error code + */ +int jack_set_session_callback(jack_client_t *client, + JackSessionCallback session_callback, + void *arg) JACK_WEAK_EXPORT; + +/** + * reply to a session_event + * + * this can either be called directly from the callback, or later from a different thread. + * so its possible to just stick the event pointer into a pipe and execute the save code + * from the gui thread. + * + * @return 0 on success, otherwise a non-zero error code + */ + +int jack_session_reply( jack_client_t *client, jack_session_event_t *event ) JACK_WEAK_EXPORT; + + +/** + * free memory used by a jack_session_event_t + * this also frees the memory used by the command_line pointer. + * if its non NULL. + */ + +void jack_session_event_free (jack_session_event_t *event); + +/*@}*/ + + +/** + * @defgroup JackSessionManagerAPI this API is intended for a sessionmanager. + * this API could be server specific. if we dont reach consensus here, + * we can just drop it. + * i know its a bit clumsy. + * but this api isnt required to be as stable as the client api. + * @{ + */ + +typedef struct { + const char *uuid; + const char *client_name; + const char *command; + jack_session_flags_t flags; +} jack_session_command_t; + +/** + * send a save or quit event, to all clients listening for session + * callbacks. the returned strings of the clients are accumulated and + * returned as an array of jack_session_command_t. + * its terminated by ret[i].uuid == NULL + * target == NULL means send to all interested clients. otherwise a clientname + */ + +jack_session_command_t *jack_session_notify (jack_client_t* client, + const char *target, + jack_session_event_type_t type, + const char *path ) JACK_WEAK_EXPORT; + +/** + * free the memory allocated by a session command. + */ + +void jack_session_commands_free (jack_session_command_t *cmds) JACK_WEAK_EXPORT; + +/** + * get the sessionid for a client name. + * the sessionmanager needs this to reassociate a client_name to the session_id. + */ + +char *jack_get_uuid_for_client_name( jack_client_t *client, const char *client_name ) JACK_WEAK_EXPORT; + +/** + * get the client name for a session_id. + * in order to snapshot the graph connections, the sessionmanager needs to map + * session_ids to client names. + */ + +char *jack_get_client_name_by_uuid( jack_client_t *client, const char *client_uuid ) JACK_WEAK_EXPORT; + +/** + * reserve a client name and associate it to a uuid. + * when a client later call jack_client_open() and specifies the uuid, + * jackd will assign the reserved name. + * this allows a session manager to know in advance under which client name + * its managed clients will appear. + * + * @return 0 on success, otherwise a non-zero error code + */ + +int +jack_reserve_client_name( jack_client_t *client, const char *name, const char *uuid ) JACK_WEAK_EXPORT; + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jack/types.h b/jack/types.h index ea49057..91d2523 100644 --- a/jack/types.h +++ b/jack/types.h @@ -113,11 +113,16 @@ enum JackOptions { * Pass optional <em>(char *) load_init</em> string to the * jack_initialize() entry point of an internal client. */ - JackLoadInit = 0x10 + JackLoadInit = 0x10, + + /** + * pass a SessionID Token this allows the sessionmanager to identify the client again. + */ + JackSessionID = 0x20 }; /** Valid options for opening an external client. */ -#define JackOpenOptions (JackServerName|JackNoStartServer|JackUseExactName) +#define JackOpenOptions (JackSessionID|JackServerName|JackNoStartServer|JackUseExactName) /** Valid options for loading an internal client. */ #define JackLoadOptions (JackLoadInit|JackLoadName|JackUseExactName) @@ -331,14 +336,14 @@ typedef void (*JackPortConnectCallback)(jack_port_id_t a, jack_port_id_t b, int * * @param starting non-zero if we start starting to freewheel, zero otherwise * @param arg pointer to a client supplied structure - */ + */ typedef void (*JackFreewheelCallback)(int starting, void *arg); typedef void *(*JackThreadCallback)(void* arg); /** * Prototype for the client supplied function that is called - * whenever jackd is shutdown. Note that after server shutdown, + * whenever jackd is shutdown. Note that after server shutdown, * the client pointer is *not* deallocated by libjack, * the application is responsible to properly use jack_client_close() * to release client ressources. Warning: jack_client_close() cannot be @@ -351,15 +356,15 @@ typedef void (*JackShutdownCallback)(void *arg); /** * Prototype for the client supplied function that is called - * whenever jackd is shutdown. Note that after server shutdown, + * whenever jackd is shutdown. Note that after server shutdown, * the client pointer is *not* deallocated by libjack, * the application is responsible to properly use jack_client_close() * to release client ressources. Warning: jack_client_close() cannot be * safely used inside the shutdown callback and has to be called outside of * the callback context. - - * @param code a shuntdown code - * @param reason a string discribing the shuntdown reason (backend failure, server crash... etc...) + * + * @param code a shutdown code + * @param reason a string describing the shutdown reason (backend failure, server crash... etc...) * @param arg pointer to a client supplied structure */ typedef void (*JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg); diff --git a/jack/varargs.h b/jack/varargs.h index a2e6a91..ae9e58a 100644 --- a/jack/varargs.h +++ b/jack/varargs.h @@ -29,6 +29,7 @@ typedef struct { char *server_name; /* server name */ char *load_name; /* load module name */ char *load_init; /* initialization string */ + char *sess_uuid; } jack_varargs_t; static inline void @@ -53,6 +54,8 @@ jack_varargs_parse (jack_options_t options, va_list ap, jack_varargs_t *va) va->load_name = va_arg(ap, char *); if ((options & JackLoadInit)) va->load_init = va_arg(ap, char *); + if ((options & JackSessionID)) + va->sess_uuid = va_arg(ap, char *); } #ifdef __cplusplus diff --git a/jackd/clientengine.c b/jackd/clientengine.c index a1599e1..3be58fe 100644 --- a/jackd/clientengine.c +++ b/jackd/clientengine.c @@ -109,6 +109,7 @@ static void jack_remove_client (jack_engine_t *engine, jack_client_internal_t *client) { JSList *node; + jack_client_id_t finalizer=0; /* caller must write-hold the client lock */ @@ -120,6 +121,20 @@ jack_remove_client (jack_engine_t *engine, jack_client_internal_t *client) jack_zombify_client (engine, client); } + if (client->session_reply_pending) { + engine->session_pending_replies -= 1; + + if (engine->session_pending_replies == 0) { + if (write (engine->session_reply_fd, &finalizer, sizeof (finalizer)) + < (ssize_t) sizeof (finalizer)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + engine->session_reply_fd, strerror (errno)); + } + engine->session_reply_fd = -1; + } + } + if (client->control->type == ClientExternal) { /* try to force the server thread to return from poll */ @@ -342,7 +357,7 @@ jack_client_unload (jack_client_internal_t *client) } } -static jack_client_internal_t * +jack_client_internal_t * jack_client_by_name (jack_engine_t *engine, const char *name) { jack_client_internal_t *client = NULL; @@ -406,6 +421,18 @@ jack_client_internal_by_id (jack_engine_t *engine, jack_client_id_t id) return client; } +int +jack_client_name_reserved( jack_engine_t *engine, const char *name ) +{ + JSList *node; + for (node = engine->reserved_client_names; node; node = jack_slist_next (node)) { + jack_reserved_name_t *reservation = (jack_reserved_name_t *) node->data; + if( !strcmp( reservation->name, name ) ) + return 1; + } + return 0; +} + /* generate a unique client name * * returns 0 if successful, updates name in place @@ -428,7 +455,7 @@ jack_generate_unique_name (jack_engine_t *engine, char *name) name[tens] = '0'; name[ones] = '1'; name[length] = '\0'; - while (jack_client_by_name (engine, name)) { + while (jack_client_by_name (engine, name) || jack_client_name_reserved( engine, name )) { if (name[ones] == '9') { if (name[tens] == '9') { jack_error ("client %s has 99 extra" @@ -456,7 +483,7 @@ jack_client_name_invalid (jack_engine_t *engine, char *name, * startup. There are no other clients at that point, anyway. */ - if (jack_client_by_name (engine, name)) { + if (jack_client_by_name (engine, name) || jack_client_name_reserved(engine, name )) { *status |= JackNameNotUnique; @@ -480,7 +507,7 @@ jack_client_name_invalid (jack_engine_t *engine, char *name, * internal and external clients. */ static jack_client_internal_t * jack_setup_client_control (jack_engine_t *engine, int fd, - ClientType type, const char *name) + ClientType type, const char *name, jack_client_id_t uuid) { jack_client_internal_t *client; @@ -530,10 +557,13 @@ jack_setup_client_control (jack_engine_t *engine, int fd, client->control->dead = FALSE; client->control->timed_out = 0; client->control->id = engine->next_client_id++; + client->control->uid = uuid; strcpy ((char *) client->control->name, name); client->subgraph_start_fd = -1; client->subgraph_wait_fd = -1; + client->session_reply_pending = FALSE; + client->control->process_cbset = FALSE; client->control->bufsize_cbset = FALSE; client->control->srate_cbset = FALSE; @@ -543,6 +573,7 @@ jack_setup_client_control (jack_engine_t *engine, int fd, client->control->graph_order_cbset = FALSE; client->control->client_register_cbset = FALSE; client->control->thread_cb_cbset = FALSE; + client->control->session_cbset = FALSE; #if 0 if (type != ClientExternal) { @@ -578,9 +609,23 @@ jack_setup_client_control (jack_engine_t *engine, int fd, return client; } +static void +jack_ensure_uuid_unique (jack_engine_t *engine, jack_client_id_t uuid) +{ + JSList *node; + + jack_lock_graph (engine); + for (node=engine->clients; node; node=jack_slist_next (node)) { + jack_client_internal_t *client = (jack_client_internal_t *) node->data; + if (client->control->uid == uuid) + client->control->uid = 0; + } + jack_unlock_graph (engine); +} + /* set up all types of clients */ static jack_client_internal_t * -setup_client (jack_engine_t *engine, ClientType type, char *name, +setup_client (jack_engine_t *engine, ClientType type, char *name, jack_client_id_t uuid, jack_options_t options, jack_status_t *status, int client_fd, const char *object_path, const char *object_data) { @@ -591,9 +636,12 @@ setup_client (jack_engine_t *engine, ClientType type, char *name, if (jack_client_name_invalid (engine, name, options, status)) return NULL; + if (uuid != 0) + jack_ensure_uuid_unique (engine, uuid); + /* create a client struct for this name */ if ((client = jack_setup_client_control (engine, client_fd, - type, name)) == NULL) { + type, name, uuid )) == NULL) { *status |= (JackFailure|JackInitFailure); jack_error ("cannot create new client object"); return NULL; @@ -687,7 +735,7 @@ jack_create_driver_client (jack_engine_t *engine, char *name) snprintf (req.name, sizeof (req.name), "%s", name); pthread_mutex_lock (&engine->request_lock); - client = setup_client (engine, ClientDriver, name, JackUseExactName, + client = setup_client (engine, ClientDriver, name, 0, JackUseExactName, &status, -1, NULL, NULL); pthread_mutex_unlock (&engine->request_lock); @@ -715,6 +763,22 @@ handle_unload_client (jack_engine_t *engine, jack_client_id_t id) return status; } +static char * +jack_get_reserved_name( jack_engine_t *engine, jack_client_id_t uuid ) +{ + JSList *node; + for (node = engine->reserved_client_names; node; node = jack_slist_next (node)) { + jack_reserved_name_t *reservation = (jack_reserved_name_t *) node->data; + if( reservation->uuid== uuid ) { + char *retval = strdup( reservation->name ); + free( reservation ); + engine->reserved_client_names = + jack_slist_remove( engine->reserved_client_names, reservation ); + return retval; + } + } + return 0; +} int jack_client_create (jack_engine_t *engine, int client_fd) { @@ -762,7 +826,14 @@ jack_client_create (jack_engine_t *engine, int client_fd) } pthread_mutex_lock (&engine->request_lock); - client = setup_client (engine, req.type, req.name, + if( req.uuid ) { + char *res_name = jack_get_reserved_name( engine, req.uuid ); + if( res_name ) { + snprintf( req.name, sizeof(req.name), "%s", res_name ); + free(res_name); + } + } + client = setup_client (engine, req.type, req.name, req.uuid, req.options, &res.status, client_fd, req.object_path, req.object_data); pthread_mutex_unlock (&engine->request_lock); @@ -818,7 +889,7 @@ int jack_client_activate (jack_engine_t *engine, jack_client_id_t id) { jack_client_internal_t *client; - JSList *node; + JSList *node, *node2; int ret = -1; jack_lock_graph (engine); @@ -843,11 +914,18 @@ jack_client_activate (jack_engine_t *engine, jack_client_id_t id) ++engine->external_client_cnt); jack_sort_graph (engine); + // send delayed notifications for ports. + for (node2 = client->ports; node2; node2 = jack_slist_next (node2)) { + jack_port_internal_t *port = (jack_port_internal_t *) node2->data; + jack_port_registration_notify (engine, port->shared->id, TRUE); + } + ret = 0; break; } } + jack_unlock_graph (engine); return ret; } @@ -886,6 +964,7 @@ jack_client_deactivate (jack_engine_t *engine, jack_client_id_t id) return ret; } + int jack_mark_client_socket_error (jack_engine_t *engine, int fd) { @@ -968,7 +1047,7 @@ jack_intclient_load_request (jack_engine_t *engine, jack_request_t *req) req->x.intclient.path, req->x.intclient.init, req->x.intclient.options); - client = setup_client (engine, ClientInternal, req->x.intclient.name, + client = setup_client (engine, ClientInternal, req->x.intclient.name, 0, req->x.intclient.options, &status, -1, req->x.intclient.path, req->x.intclient.init); diff --git a/jackd/engine.c b/jackd/engine.c index 6ae9c42..6bd68b7 100644 --- a/jackd/engine.c +++ b/jackd/engine.c @@ -134,7 +134,12 @@ static void jack_check_acyclic (jack_engine_t* engine); static void jack_compute_all_port_total_latencies (jack_engine_t *engine); static void jack_compute_port_total_latency (jack_engine_t *engine, jack_port_shared_t*); static void jack_engine_signal_problems (jack_engine_t* engine); +static int jack_do_session_notify (jack_engine_t *engine, jack_request_t *req, int reply_fd ); static int jack_check_client_status (jack_engine_t* engine); +static void jack_do_get_client_by_uuid ( jack_engine_t *engine, jack_request_t *req); +static void jack_do_reserve_name ( jack_engine_t *engine, jack_request_t *req); +static void jack_do_session_reply (jack_engine_t *engine, jack_request_t *req ); + static inline int jack_rolling_interval (jack_time_t period_usecs) @@ -1353,6 +1358,31 @@ do_request (jack_engine_t *engine, jack_request_t *req, int *reply_fd) req->status = 0; break; + case GetClientByUUID: + jack_rdlock_graph (engine); + jack_do_get_client_by_uuid (engine, req); + jack_unlock_graph (engine); + break; + case ReserveName: + jack_rdlock_graph (engine); + jack_do_reserve_name (engine, req); + jack_unlock_graph (engine); + break; + case SessionReply: + jack_rdlock_graph (engine); + jack_do_session_reply (engine, req); + jack_unlock_graph (engine); + break; + case SessionNotify: + jack_rdlock_graph (engine); + if ((req->status = + jack_do_session_notify (engine, req, *reply_fd)) + >= 0) { + /* we have already replied, don't do it again */ + *reply_fd = -1; + } + jack_unlock_graph (engine); + break; default: /* some requests are handled entirely on the client * side, by adjusting the shared memory area(s) */ @@ -1757,6 +1787,9 @@ jack_engine_new (int realtime, int rtpriority, int do_mlock, int do_unlock, engine->nozombies = nozombies; engine->removing_clients = 0; + engine->session_reply_fd = -1; + engine->session_pending_replies = 0; + engine->audio_out_cnt = 0; engine->audio_in_cnt = 0; engine->midi_out_cnt = 0; @@ -1771,6 +1804,7 @@ jack_engine_new (int realtime, int rtpriority, int do_mlock, int do_unlock, pthread_mutex_init (&engine->problem_lock, 0); engine->clients = 0; + engine->reserved_client_names = 0; engine->pfd_size = 0; engine->pfd_max = 0; @@ -2492,6 +2526,201 @@ jack_deliver_event_to_all (jack_engine_t *engine, jack_event_t *event) jack_unlock_graph (engine); } +static jack_client_id_t jack_engine_get_max_uuid( jack_engine_t *engine ) +{ + JSList *node; + jack_client_id_t retval = 0; + for (node = engine->clients; node; node = jack_slist_next (node)) { + jack_client_internal_t* client = (jack_client_internal_t*) node->data; + if( client->control->uid > retval ) + retval = client->control->uid; + } + return retval; +} + +static void jack_do_get_client_by_uuid ( jack_engine_t *engine, jack_request_t *req) +{ + JSList *node; + req->status = -1; + for (node = engine->clients; node; node = jack_slist_next (node)) { + jack_client_internal_t* client = (jack_client_internal_t*) node->data; + if( client->control->uid == req->x.client_id ) { + snprintf( req->x.port_info.name, sizeof(req->x.port_info.name), "%s", client->control->name ); + req->status = 0; + return; + } + } +} + +static void jack_do_reserve_name ( jack_engine_t *engine, jack_request_t *req) +{ + jack_reserved_name_t *reservation; + JSList *node; + // check is name is free... + for (node = engine->clients; node; node = jack_slist_next (node)) { + jack_client_internal_t* client = (jack_client_internal_t*) node->data; + if( !strcmp( (char *)client->control->name, req->x.reservename.name )) { + req->status = -1; + return; + } + } + + reservation = malloc( sizeof( jack_reserved_name_t ) ); + if( reservation == NULL ) { + req->status = -1; + return; + } + + snprintf( reservation->name, sizeof( reservation->name ), "%s", req->x.reservename.name ); + reservation->uuid = req->x.reservename.uuid; + engine->reserved_client_names = jack_slist_append( engine->reserved_client_names, reservation ); + + req->status = 0; +} + +static int jack_send_session_reply ( jack_engine_t *engine, jack_client_internal_t *client ) +{ + if (write (engine->session_reply_fd, (const void *) &client->control->uid, sizeof (client->control->uid)) + < (ssize_t) sizeof (client->control->uid)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + engine->session_reply_fd, strerror (errno)); + return -1; + } + if (write (engine->session_reply_fd, (const void *) client->control->name, sizeof (client->control->name)) + < (ssize_t) sizeof (client->control->name)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + engine->session_reply_fd, strerror (errno)); + return -1; + } + if (write (engine->session_reply_fd, (const void *) client->control->session_command, + sizeof (client->control->session_command)) + < (ssize_t) sizeof (client->control->session_command)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + engine->session_reply_fd, strerror (errno)); + return -1; + } + if (write (engine->session_reply_fd, (const void *) ( & client->control->session_flags ), + sizeof (client->control->session_flags)) + < (ssize_t) sizeof (client->control->session_flags)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + engine->session_reply_fd, strerror (errno)); + return -1; + } + + return 0; +} + +static int +jack_do_session_notify (jack_engine_t *engine, jack_request_t *req, int reply_fd ) +{ + JSList *node; + jack_event_t event; + + int reply; + jack_client_id_t finalizer=0; + + if (engine->session_reply_fd != -1) { + // we should have a notion of busy or somthing. + // just sending empty reply now. + goto send_final; + } + + engine->session_reply_fd = reply_fd; + engine->session_pending_replies = 0; + + event.type = SaveSession; + event.y.n = req->x.session.type; + + /* GRAPH MUST BE LOCKED : see callers of jack_send_connection_notification() + */ + + for (node = engine->clients; node; node = jack_slist_next (node)) { + jack_client_internal_t* client = (jack_client_internal_t*) node->data; + if (client->control->session_cbset) { + + if( client->control->uid == 0 ) { + client->control->uid=jack_engine_get_max_uuid( engine ) + 1; + } + + // in case we only want to send to a special client. + // uuid assign is still complete. not sure if thats necessary. + if( (req->x.session.target[0] != 0) && strcmp(req->x.session.target, (char *)client->control->name) ) + continue; + + snprintf (event.x.name, sizeof (event.x.name), "%s%s/", req->x.session.path, client->control->name ); + mkdir (event.x.name, 0777 ); + reply = jack_deliver_event (engine, client, &event); + + if (reply == 1) { + // delayed reply + engine->session_pending_replies += 1; + client->session_reply_pending = TRUE; + } else if (reply == 2) { + // immediate reply + if (jack_send_session_reply (engine, client)) + goto error_out; + } + } + } + + if (engine->session_pending_replies != 0) + return 0; + +send_final: + if (write (reply_fd, &finalizer, sizeof (finalizer)) + < (ssize_t) sizeof (finalizer)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + reply_fd, strerror (errno)); + goto error_out; + } + + engine->session_reply_fd = -1; + return 0; +error_out: + return -3; +} + +static void jack_do_session_reply (jack_engine_t *engine, jack_request_t *req ) +{ + jack_client_id_t client_id = req->x.client_id; + jack_client_internal_t *client = jack_client_internal_by_id (engine, client_id); + jack_client_id_t finalizer=0; + + req->status = 0; + + client->session_reply_pending = 0; + + if (engine->session_reply_fd == -1) { + jack_error ("spurious Session Reply"); + return; + } + + engine->session_pending_replies -= 1; + + if (jack_send_session_reply (engine, client)) { + // maybe need to fix all client pendings. + // but we will just get a set of spurious replies now. + engine->session_reply_fd = -1; + return; + } + + if (engine->session_pending_replies == 0) { + if (write (engine->session_reply_fd, &finalizer, sizeof (finalizer)) + < (ssize_t) sizeof (finalizer)) { + jack_error ("cannot write SessionNotify result " + "to client via fd = %d (%s)", + engine->session_reply_fd, strerror (errno)); + req->status = -1; + } + engine->session_reply_fd = -1; + } +} + static void jack_notify_all_port_interested_clients (jack_engine_t *engine, jack_client_id_t src, jack_client_id_t dst, jack_port_id_t a, jack_port_id_t b, int connected) { @@ -2522,7 +2751,7 @@ static int jack_deliver_event (jack_engine_t *engine, jack_client_internal_t *client, jack_event_t *event) { - char status; + char status=0; /* caller must hold the graph lock */ @@ -2607,7 +2836,7 @@ jack_deliver_event (jack_engine_t *engine, jack_client_internal_t *client, } if (client->error) { - status = 1; + status = -1; } else { // then we check whether there really is an error.... :) @@ -2672,7 +2901,7 @@ jack_deliver_event (jack_engine_t *engine, jack_client_internal_t *client, client->event_fd, pfd[0].revents, poll_timeout); - status = 1; + status = -2; #ifdef __linux } #endif @@ -2697,7 +2926,7 @@ jack_deliver_event (jack_engine_t *engine, jack_client_internal_t *client, event->type); } - if (status) { + if (status<0) { client->error += JACK_ERROR_WITH_SOCKETS; jack_engine_signal_problems (engine); } @@ -2705,7 +2934,7 @@ jack_deliver_event (jack_engine_t *engine, jack_client_internal_t *client, } DEBUG ("event delivered"); - return 0; + return status; } int @@ -3919,7 +4148,8 @@ next: } client->ports = jack_slist_prepend (client->ports, port); - jack_port_registration_notify (engine, port_id, TRUE); + if( client->control->active ) + jack_port_registration_notify (engine, port_id, TRUE); jack_unlock_graph (engine); VERBOSE (engine, "registered port %s, offset = %u", diff --git a/libjack/client.c b/libjack/client.c index ef6f881..793a3c8 100644 --- a/libjack/client.c +++ b/libjack/client.c @@ -514,6 +514,32 @@ jack_client_handle_port_connection (jack_client_t *client, jack_event_t *event) return 0; } +int +jack_client_handle_session_callback (jack_client_t *client, jack_event_t *event) +{ + char prefix[32]; + jack_session_event_t *s_event; + + if (! client->control->session_cbset) { + return -1; + } + + snprintf( prefix, sizeof(prefix), "%d", client->control->uid ); + + s_event = malloc( sizeof(jack_session_event_t) ); + s_event->type = event->y.n; + s_event->session_dir = strdup( event->x.name ); + s_event->client_uuid = strdup( prefix ); + s_event->command_line = NULL; + + client->session_cb_immediate_reply = 0; + client->session_cb ( s_event, client->session_cb_arg); + + if (client->session_cb_immediate_reply) { + return 2; + } + return 1; +} #if JACK_USE_MACH_THREADS static int @@ -874,6 +900,10 @@ jack_request_client (ClientType type, /* format connection request */ + if( va->sess_uuid ) + req.uuid = atoi( va->sess_uuid ); + else + req.uuid = 0; req.protocol_v = jack_protocol_version; req.load = TRUE; req.type = type; @@ -1116,6 +1146,11 @@ jack_client_open_aux (const char *client_name, client->deliver_request = oop_client_deliver_request; client->deliver_arg = client; + if( va.sess_uuid ) + client->control->uid = atoi( va.sess_uuid ); + else + client->control->uid = 0U; + if ((ev_fd = server_event_connect (client, va.server_name)) < 0) { goto fail; } @@ -1302,6 +1337,144 @@ jack_set_freewheel (jack_client_t* client, int onoff) return jack_client_deliver_request (client, &request); } +int +jack_session_reply (jack_client_t *client, jack_session_event_t *event ) +{ + int retval = 0; + + if (event->command_line) { + snprintf ((char *)client->control->session_command, + sizeof(client->control->session_command), + "%s", event->command_line); + client->control->session_flags = event->flags; + + } else { + retval = -1; + } + + if (pthread_self() == client->thread_id) { + client->session_cb_immediate_reply = 1; + } else { + jack_request_t request; + request.type = SessionReply; + request.x.client_id = client->control->id; + + retval = jack_client_deliver_request(client, &request); + } + + return retval; +} + +void +jack_session_event_free (jack_session_event_t *event) +{ + if (event->command_line) + free (event->command_line); + + free ((char *)event->session_dir); + free ((char *)event->client_uuid); + free (event); +} + +void +jack_session_commands_free (jack_session_command_t *cmds) +{ + int i=0; + while(1) { + if (cmds[i].client_name) + free ((char *)cmds[i].client_name); + if (cmds[i].command) + free ((char *)cmds[i].command); + if (cmds[i].uuid) + free ((char *)cmds[i].uuid); + else + break; + + i += 1; + } + + free(cmds); +} + +jack_session_command_t * +jack_session_notify (jack_client_t* client, const char *target, jack_session_event_type_t code, const char *path ) +{ + jack_request_t request; + + jack_session_command_t *retval = NULL; + int num_replies = 0; + request.type = SessionNotify; + if( path ) + snprintf( request.x.session.path, sizeof( request.x.session.path ), "%s", path ); + else + request.x.session.path[0] = '\0'; + + if( target ) + snprintf( request.x.session.target, sizeof( request.x.session.target ), "%s", target ); + else + request.x.session.target[0] = '\0'; + + request.x.session.type = code; + + if( (write (client->request_fd, &request, sizeof (request)) + != sizeof (request)) ) { + jack_error ("cannot send request type %d to server", + request.type); + goto out; + } + + while( 1 ) { + jack_client_id_t uid; + if (read (client->request_fd, &uid, sizeof (uid)) != sizeof (uid)) { + jack_error ("cannot read result for request type %d from" + " server (%s)", request.type, strerror (errno)); + goto out; + } + + num_replies += 1; + retval = realloc( retval, (num_replies)*sizeof(jack_session_command_t) ); + retval[num_replies-1].client_name = malloc (JACK_CLIENT_NAME_SIZE); + retval[num_replies-1].command = malloc (JACK_PORT_NAME_SIZE); + retval[num_replies-1].uuid = malloc (16); + + if ( (retval[num_replies-1].client_name == NULL) + ||(retval[num_replies-1].command == NULL) + ||(retval[num_replies-1].uuid == NULL) ) + goto out; + + if( uid == 0 ) + break; + + + if (read (client->request_fd, (char *)retval[num_replies-1].client_name, JACK_CLIENT_NAME_SIZE) + != JACK_CLIENT_NAME_SIZE) { + jack_error ("cannot read result for request type %d from" + " server (%s)", request.type, strerror (errno)); + goto out; + } + if (read (client->request_fd, (char *)retval[num_replies-1].command, JACK_PORT_NAME_SIZE) + != JACK_PORT_NAME_SIZE) { + jack_error ("cannot read result for request type %d from" + " server (%s)", request.type, strerror (errno)); + goto out; + } + if (read (client->request_fd, & retval[num_replies-1].flags, sizeof(retval[num_replies-1].flags) ) + != sizeof(retval[num_replies-1].flags) ) { + jack_error ("cannot read result for request type %d from" + " server (%s)", request.type, strerror (errno)); + goto out; + } + snprintf( (char *)retval[num_replies-1].uuid, 16, "%d", uid ); + } + free((char *)retval[num_replies-1].uuid); + retval[num_replies-1].uuid = NULL; + return retval; +out: + if( retval ) + jack_session_commands_free(retval); + return NULL; +} + void jack_start_freewheel (jack_client_t* client) { @@ -1473,6 +1646,9 @@ jack_client_process_events (jack_client_t* client) case StopFreewheel: jack_stop_freewheel (client); break; + case SaveSession: + status = jack_client_handle_session_callback (client, &event ); + break; } DEBUG ("client has dealt with the event, writing " @@ -2005,11 +2181,11 @@ jack_activate (jack_client_t *client) * usage in jack_start_thread()) */ - char buf[JACK_THREAD_STACK_TOUCH]; + //char buf[JACK_THREAD_STACK_TOUCH]; int i; for (i = 0; i < JACK_THREAD_STACK_TOUCH; i++) { - buf[i] = (char) (i & 0xff); + //buf[i] = (char) (i & 0xff); } if (client->control->type == ClientInternal || @@ -2450,6 +2626,20 @@ jack_set_process_thread(jack_client_t* client, JackThreadCallback callback, void } int +jack_set_session_callback(jack_client_t* client, JackSessionCallback callback, void *arg) +{ + if (client->control->active) { + jack_error ("You cannot set callbacks on an active client."); + return -1; + } + + client->session_cb_arg = arg; + client->session_cb = callback; + client->control->session_cbset = (callback != NULL); + return 0; +} + +int jack_get_process_done_fd (jack_client_t *client) { return client->graph_next_fd; @@ -2469,6 +2659,39 @@ jack_on_info_shutdown (jack_client_t *client, void (*function)(jack_status_t, co client->on_info_shutdown_arg = arg; } +char * +jack_get_client_name_by_uuid( jack_client_t *client, const char *uuid ) +{ + jack_request_t request; + + char *end_ptr; + jack_client_id_t uuid_int = strtol( uuid, &end_ptr, 10 ); + if( *end_ptr != '\0' ) + return NULL; + request.type = GetClientByUUID; + request.x.client_id = uuid_int; + if( jack_client_deliver_request( client, &request ) ) + return NULL; + + return strdup( request.x.port_info.name ); +} + +int +jack_reserve_client_name( jack_client_t *client, const char *name, const char *uuid ) +{ + jack_request_t request; + + char *end_ptr; + jack_client_id_t uuid_int = strtol( uuid, &end_ptr, 10 ); + if( *end_ptr != '\0' ) + return -1; + request.type = ReserveName; + snprintf( request.x.reservename.name, sizeof( request.x.reservename.name ), + "%s", name ); + request.x.reservename.uuid = uuid_int; + return jack_client_deliver_request( client, &request ); +} + const char ** jack_get_ports (jack_client_t *client, const char *port_name_pattern, diff --git a/libjack/local.h b/libjack/local.h index ef03235..1168676 100644 --- a/libjack/local.h +++ b/libjack/local.h @@ -35,6 +35,7 @@ struct _jack_client { char first_active : 1; pthread_t thread_id; char name[JACK_CLIENT_NAME_SIZE]; + int session_cb_immediate_reply; #ifdef JACK_USE_MACH_THREADS /* specific ressources for server/client real-time thread communication */ @@ -70,8 +71,10 @@ struct _jack_client { void *freewheel_arg; JackClientRegistrationCallback client_register; void *client_register_arg; - JackThreadCallback thread_cb; + JackThreadCallback thread_cb; void *thread_cb_arg; + JackSessionCallback session_cb; + void *session_cb_arg; /* external clients: set by libjack * internal clients: set by engine */ diff --git a/python/libjack.py b/python/libjack.py new file mode 100644 index 0000000..e64251f --- /dev/null +++ b/python/libjack.py @@ -0,0 +1,363 @@ + +from ctypes import * +import string +from Queue import Queue + +class jack_client_t(Structure): + pass + +class jack_port_t(Structure): + pass + +client_p = POINTER(jack_client_t) +port_p = POINTER(jack_port_t) + +libjack = cdll.LoadLibrary( "libjack.so" ) +client_new = libjack.jack_client_new +client_new.argtypes = [ c_char_p ] +client_new.restype = client_p + +client_open = libjack.jack_client_open +client_open.argtypes = [ c_char_p, c_uint, POINTER( c_uint ) ] +client_open.restype = client_p + +client_close = libjack.jack_client_close +client_close.argtypes = [ client_p ] +client_close.restype = None + +activate = libjack.jack_activate +activate.argtypes = [ client_p ] +activate.restype = None + +deactivate = libjack.jack_deactivate +deactivate.argtypes = [ client_p ] +deactivate.restype = None + +get_ports = libjack.jack_get_ports +get_ports.argtypes = [ client_p, c_char_p, c_char_p, c_ulong ] +get_ports.restype = POINTER( c_char_p ) + +port_by_name = libjack.jack_port_by_name +port_by_name.argtypes = [ client_p, c_char_p ] +port_by_name.restype = port_p + +port_get_all_connections = libjack.jack_port_get_all_connections +port_get_all_connections.argtypes = [ client_p, port_p ] +port_get_all_connections.restype = POINTER( c_char_p ) + +jack_free = libjack.jack_free +jack_free.argtypes = [ c_void_p ] +jack_free.restype = None + +rename_client = libjack.jack_rename_client +rename_client.argtypes = [ client_p, c_char_p, c_char_p ] +rename_client.restype = c_int + +reserve_client_name = libjack.jack_reserve_client_name +reserve_client_name.argtypes = [ client_p, c_char_p, c_char_p ] +reserve_client_name.restype = c_int + +class jack_session_command_t( Structure ): + _fields_ = [ ("uuid", c_char_p ), ("clientname", c_char_p), ("command", c_char_p ), ("flags", c_int) ] + +session_notify = libjack.jack_session_notify +session_notify.argtypes = [ client_p, c_char_p, c_uint, c_char_p ] +session_notify.restype = POINTER( jack_session_command_t ) + +session_commands_free = libjack.jack_session_commands_free +session_commands_free.argtypes = [ POINTER( jack_session_command_t ) ] +session_commands_free.restype = None + +get_client_name_by_uuid = libjack.jack_get_client_name_by_uuid +get_client_name_by_uuid.argtypes = [ client_p, c_char_p ] +get_client_name_by_uuid.restype = c_char_p + +connect = libjack.jack_connect +connect.argtypes = [ client_p, c_char_p, c_char_p ] +connect.restype = c_int + +disconnect = libjack.jack_disconnect +disconnect.argtypes = [ client_p, c_char_p, c_char_p ] +disconnect.restype = c_int + +PortRegistrationCallback = CFUNCTYPE( None, c_uint, c_int, c_void_p ) + +set_port_registration_callback = libjack.jack_set_port_registration_callback +set_port_registration_callback.argtypes = [ client_p, PortRegistrationCallback, c_void_p ] +set_port_registration_callback.restype = c_int + +port_by_id = libjack.jack_port_by_id +port_by_id.argtypes = [ client_p, c_uint ] +port_by_id.restype = port_p + +port_name = libjack.jack_port_name +port_name.argtypes = [ port_p ] +port_name.restype = c_char_p + +port_type = libjack.jack_port_type +port_type.argtypes = [ port_p ] +port_type.restype = c_char_p + +port_flags = libjack.jack_port_flags +port_flags.argtypes = [ port_p ] +port_flags.restype = c_int + +JACK_DEFAULT_AUDIO_TYPE="32 bit float mono audio" +JACK_DEFAULT_MIDI_TYPE="8 bit raw midi" + +JackPortIsInput = 0x1 +JackPortIsOutput = 0x2 +JackPortIsPhysical = 0x4 +JackPortCanMonitor = 0x8 +JackPortIsTerminal = 0x10 + +JackSessionSave = 1 +JackSessionQuit = 2 + + + +class Port( object ): + def __init__( self, client, name ): + self.client = client + self.name = name + self.portname = name.split(':')[1] + self.port_p = port_by_name( self.client, name ) + self.conns = self.get_live_connections() + + def get_connections( self ): + return self.conns + + def get_live_connections( self ): + conns = port_get_all_connections( self.client, self.port_p ) + if not conns: + return [] + + i=0 + retval = [] + while conns[i]: + retval.append( conns[i] ) + i+=1 + jack_free(conns) + + return retval + + def connect( self, other ): + connect( self.client, self.name, other ) + + def disconnect( self, other ): + disconnect( self.client, self.name, other ) + + def is_input( self ): + return (port_flags( self.port_p ) & JackPortIsInput) != 0 + + def is_output( self ): + return (port_flags( self.port_p ) & JackPortIsOutput) != 0 + + def is_audio( self ): + return (port_type( self.port_p ) == JACK_DEFAULT_AUDIO_TYPE) + + def is_midi( self ): + return (port_type( self.port_p ) == JACK_DEFAULT_MIDI_TYPE) + + +class Client( object ): + def __init__( self, client, name ): + self.client = client + self.name = name + self.ports = [] + self.commandline = None + self.isinfra = False + self.uuid = None + + def get_commandline( self ): + if self.commandline: + return self.commandline + else: + return "" + + def set_commandline( self, cmdline ): + self.commandline = cmdline + + def get_uuid( self ): + return self.uuid + + def set_uuid( self, uuid ): + self.uuid = uuid + + def add_port( self, portname ): + self.ports.append( Port( self.client, portname ) ) + + def rename( self, newname ): + rename_client( self.client, self.name, newname ) + self.ports = [] + ports = get_ports( self.client, newname+":.*", None, 0 ) + self.name = newname + + i=0 + while(ports[i]): + self.add_port( ports[i] ) + i+=1 + + jack_free( ports ) + + def set_infra( self, cmdline ): + self.isinfra = True + self.commandline = cmdline + + + +class JackGraph( object ): + def __init__( self, client, ports, uuids=[] ): + self.client = client + self.clients = {} + self.reserved_names = [] + + i=0 + while(ports[i]): + port_split = ports[i].split(':') + if not self.clients.has_key(port_split[0]): + self.clients[port_split[0]] = Client( client, port_split[0] ) + + self.clients[port_split[0]].add_port( ports[i] ) + i+=1 + + def get_client( self, name ): + return self.clients[name] + + def get_port_list( self ): + retval = [] + for c in self.clients.values(): + for p in c.ports: + retval.append( p.name ) + return retval + + + + def check_client_name( self, client ): + if not client.name in self.reserved_names: + return + + oldname = client.name + newname = self.get_free_name( client.name ) + + client.rename( newname ) + del self.clients[oldname] + self.clients[newname] = client + + def get_free_name( self, oldname, other_names=[] ): + cname_split = oldname.split('-') + if len(cname_split) == 1: + cname_prefix = cname_split[0] + else: + cname_prefix = string.join( cname_split[:-1], '-' ) + + num = 1 + while ("%s-%d"%(cname_prefix,num)) in (self.clients.keys()+self.reserved_names+other_names): + num+=1 + + return ("%s-%d"%(cname_prefix,num)) + + + + def remove_client( self, name ): + del self.clients[name] + for c in self.clients.values(): + for p in c.ports: + for conn in p.get_connections(): + if conn.startswith(name+":"): + p.conns.remove( conn ) + + def remove_client_only( self, name ): + del self.clients[name] + + def ensure_clientnames( self, names ): + self.reserved_names = names + for c in self.clients.values(): + self.check_client_name( c ) + + def get_taken_names( self ): + return self.clients.keys() + self.reserved_names + + def reserve_name( self, uuid, name ): + if reserve_client_name( self.client, name, uuid ): + raise Exception( "reservation failure" ) + self.reserved_names.append( name ) + + +class NotifyReply(object): + def __init__( self, uuid, clientname, commandline ): + self.uuid = uuid + self.clientname = clientname + self.commandline = commandline + + +class JackClient(object): + def __init__( self, name ): + self.client = client_open( name, 0, None ) + if not self.client: + raise Exception( "got no client name" ) + self.reg_cb = PortRegistrationCallback( self.port_registration_cb ) + set_port_registration_callback( self.client, self.reg_cb, None ) + self.port_queue = Queue() + + activate( self.client ) + + def close( self ): + client_close( self.client ) + + def get_graph( self ): + ports = get_ports( self.client, None, None, 0 ) + retval = JackGraph( self.client, ports ) + jack_free( ports ) + return retval + + def rename_client( self, old, new ): + if rename_client( self.client, old, new ): + raise Exception + + def port_registration_cb( self, port_id, reg, arg ): + port_p = port_by_id( self.client, port_id ) + self.port_queue.put( (port_p,reg) ) + + + def session_save( self, path ): + commands = session_notify( self.client, None, JackSessionSave, path ) + i=0 + retval = [] + while( commands[i].uuid ): + retval.append( NotifyReply( commands[i].uuid, commands[i].clientname, commands[i].command ) ) + i+=1 + + session_commands_free( commands ) + + return retval + + def session_save_and_quit( self, path ): + commands = session_notify( self.client, None, JackSessionQuit, path ) + i=0 + retval = [] + while( commands[i].uuid ): + retval.append( NotifyReply( commands[i].uuid, commands[i].clientname, commands[i].command ) ) + i+=1 + + session_commands_free( commands ) + + return retval + + def connect( self, a, b ): + portA_p = port_by_name( self.client, a ) + + if( port_flags( portA_p ) & JackPortIsOutput ): + connect( self.client, a, b ) + else: + connect( self.client, b, a ) + + + + + + + + + + diff --git a/python/sessionmanager.py b/python/sessionmanager.py new file mode 100755 index 0000000..97fe261 --- /dev/null +++ b/python/sessionmanager.py @@ -0,0 +1,277 @@ +#!/usr/bin/env python + +import libjack +import state +import subprocess +from optparse import OptionParser +from ConfigParser import SafeConfigParser +import os + +try: + import dbus.service + import gobject + have_dbus = True +except: + have_dbus = False + + +SESSION_PATH=os.path.join(os.getenv("HOME"), "jackSessions") +defaults = { "jackclientname": "sessionmanager", "sessiondir": "~/jackSessions" } + +class SessionManager( object ): + def __init__( self ): + self.config = SafeConfigParser( defaults ) + self.config.read( os.path.expanduser( "~/.jacksessionrc" ) ) + self.jackname = self.config.get( "DEFAULT", "jackclientname" ) + self.cl = libjack.JackClient( self.jackname ) + if self.config.has_section( "infra" ): + self.infra_clients = {} + for inf in self.config.items( "infra" ): + self.infra_clients[inf[0]] = inf[1] + else: + self.infra_clients = { "a2j": "a2jmidid" } + self.implicit_clients = [ "system" ] + + self.sessiondir = os.path.expanduser( self.config.get( "DEFAULT", "sessiondir" ) ) + "/" + if not os.path.exists( self.sessiondir ): + print "Sessiondir %s does not exist. Creating it..."%self.sessiondir + os.mkdir( self.sessiondir ) + + def list_projects( self ): + files = os.listdir( self.sessiondir ) + files = filter( lambda x: os.path.isdir( os.path.join( self.sessiondir, x ) ), files ) + return files + + def load_session( self, name ): + if not os.path.exists( self.sessiondir+name+"/session.xml" ): + print "Session %s does not exist"%name + return -1 + + sd = state.SessionDom( self.sessiondir+name+"/session.xml" ) + + g=self.cl.get_graph() + + children = [] + + for ic in sd.get_infra_clients(): + if not ic[0] in g.clients.keys(): + children.append( subprocess.Popen( ic[1], shell=True ) ) + + + print sd.get_client_names() + + if opt.renames: + g.ensure_clientnames( sd.get_reg_client_names() ) + # get graph again... renaming isnt prefect yet. + g=self.cl.get_graph() + + # fixup names, doing this unconditionally, because + # a client rename might have failed. + sd.fixup_client_names( g ) + + # now we have mangled all the names, lets reserve them. + for (uuid, clientname) in sd.get_uuid_client_pairs(): + print "reserving name %s"%clientname + g.reserve_name( uuid, clientname ) + + # build up list of port connections + conns = [] + for p in sd.get_port_names(): + for c in sd.get_connections_for_port( p ): + conns.append( (p,c) ) + print conns + + # now fire up the processes + for cname in sd.get_reg_client_names(): + cmd = sd.get_commandline_for_client( cname ) + children.append( subprocess.Popen( cmd, shell=True ) ) + + avail_ports = g.get_port_list() + # wait for ports to appear, and connect em. + + while len(conns) > 0: + p = self.cl.port_queue.get() + print p[0],p[1] + pname = libjack.port_name(p[0]) + for c1 in conns: + if c1[0]==pname: + if c1[1] in avail_ports: + self.cl.connect( pname, c1[1] ) + + conns.remove( c1 ) + if (c1[1],c1[0]) in conns: + conns.remove( (c1[1],c1[0]) ) + + break + + if c1[1]==pname: + if c1[0] in avail_ports: + self.cl.connect( pname, c1[0] ) + + conns.remove( c1 ) + if (c1[1],c1[0]) in conns: + conns.remove( (c1[1],c1[0]) ) + + break + + avail_ports.append( pname ) + + + print "session restored..." + return 0 + + def quit_session( self, name ): + self.save_session( name, True ) + + + def save_session( self, name, quit=False ): + if os.path.exists( self.sessiondir+name ): + print "session %s already exists"%name + return -1 + os.mkdir( self.sessiondir+name ) + g=self.cl.get_graph() + if quit: + notify = self.cl.session_save_and_quit( self.sessiondir+name+"/" ) + else: + notify = self.cl.session_save( self.sessiondir+name+"/" ) + + for n in notify: + c = g.get_client( n.clientname ) + c.set_commandline( n.commandline ) + c.set_uuid( n.uuid ) + + sd = state.SessionDom() + + for c in g.clients.values(): + if c.get_commandline() == "": + if not c.name in self.infra_clients.keys()+self.implicit_clients: + g.remove_client( c.name ) + elif c.name in self.implicit_clients: + g.remove_client_only( c.name ) + else: + c.set_infra( self.infra_clients[c.name] ) + + + for i in g.clients.values(): + sd.add_client(i) + + f = file( self.sessiondir+name+"/session.xml", "w" ) + f.write( sd.get_xml() ) + f.close() + + print sd.get_xml() + return 0 + + def exit( self ): + self.cl.close() + +if have_dbus: + class DbusSM( dbus.service.Object ): + def __init__( self, sm ): + self.sm = sm + dbus.service.Object.__init__( self, None, + "/org/jackaudio/sessionmanager", + dbus.service.BusName( "org.jackaudio.sessionmanager", bus=dbus.SessionBus() ) ) + @dbus.service.method( dbus_interface="org.jackaudio.sessionmanager", + in_signature="s", out_signature="i" ) + def save_as( self, name ): + return self.sm.save_session( name ) + + @dbus.service.method( dbus_interface="org.jackaudio.sessionmanager", + in_signature="s", out_signature="i" ) + def quit_as( self, name ): + return self.sm.quit_session( name ) + + @dbus.service.method( dbus_interface="org.jackaudio.sessionmanager", + in_signature="s", out_signature="i" ) + def load( self, name ): + return self.sm.load_session( name ) + + @dbus.service.method( dbus_interface="org.jackaudio.sessionmanager", + in_signature="", out_signature="as" ) + def list( self ): + return self.sm.list_projects() + + @dbus.service.method( dbus_interface="org.jackaudio.sessionmanager", + in_signature="", out_signature="" ) + def daemon_quit( self ): + loop.quit() + +oparser = OptionParser() +oparser.add_option( "--nodbus", action="store_false", dest="dbus", default=have_dbus, + help="Dont use DBUS to issue commands to a running instance" ) +oparser.add_option( "--daemon", action="store_true", dest="daemon", default=False, + help="Start in daemon mode, and listen for dbus commands" ) +#oparser.add_option( "--save", action="store_true", dest="save", default=False, +# help="Tell SessionManger to save." ) +oparser.add_option( "--saveas", action="store", type="string", dest="saveas", + help="Save Session As <name>" ) + +#oparser.add_option( "--quit", action="store_true", dest="quit", default=False, +# help="Tell SessionManager to Save And Quit" ) + +oparser.add_option( "--list", action="store_true", dest="list", default=False, + help="List Projects" ) +oparser.add_option( "--quitdaemon", action="store_true", dest="quitdaemon", default=False, + help="Tell SessionManager Daemon to Exit" ) +oparser.add_option( "--quitas", action="store", dest="quitas", type="string", + help="SaveAs And Quit" ) +oparser.add_option( "--load", action="store", dest="load", type="string", + help="Load Session with <name>" ) +oparser.add_option( "--renames", action="store_true", dest="renames", default=False, + help="Allow renaming offending clients" ) + +(opt,args) = oparser.parse_args() + +if not opt.dbus: + sm = SessionManager() + try: + if opt.saveas: + sm.save_session( opt.saveas ) + + if opt.load: + sm.load_session( opt.load ) + + if opt.quitas: + sm.quit_session( opt.quitas ) + except: + sm.exit() + raise + + sm.exit() +else: + if opt.daemon: + try: + sm = SessionManager() + from dbus.mainloop.glib import DBusGMainLoop + DBusGMainLoop(set_as_default=True) + dbsm = DbusSM( sm ) + loop = gobject.MainLoop() + loop.run() + except: + sm.exit() + raise + + sm.exit() + else: + session_bus = dbus.SessionBus() + sm_proxy = session_bus.get_object( "org.jackaudio.sessionmanager", "/org/jackaudio/sessionmanager" ) + sm_iface = dbus.Interface( sm_proxy, "org.jackaudio.sessionmanager" ) + if opt.saveas: + sm_iface.save_as( opt.saveas ) + if opt.quitas: + sm_iface.quit_as( opt.quitas ) + if opt.load: + sm_iface.load( opt.load ) + if opt.list: + projects = sm_iface.list() + for i in projects: + print i + + if opt.quitdaemon: + sm_iface.quit() + + + + + diff --git a/python/state.py b/python/state.py new file mode 100644 index 0000000..159f789 --- /dev/null +++ b/python/state.py @@ -0,0 +1,136 @@ + +from xml.dom.minidom import getDOMImplementation, parse, Element +import string + +impl = getDOMImplementation() + +class SessionDom( object ): + def __init__( self, filename=None ): + if filename: + self.dom = parse( filename ) + else: + self.dom = impl.createDocument(None,"jacksession",None) + + def add_client( self, client ): + cl_elem = Element( "jackclient" ) + cl_elem.setAttribute( "cmdline", client.get_commandline() ) + cl_elem.setAttribute( "jackname", client.name ) + if client.get_uuid(): + cl_elem.setAttribute( "uuid", client.get_uuid() ) + if client.isinfra: + cl_elem.setAttribute( "infra", "True" ) + else: + cl_elem.setAttribute( "infra", "False" ) + + for p in client.ports: + po_elem = Element( "port" ) + po_elem.setAttribute( "name", p.name ) + po_elem.setAttribute( "shortname", p.portname ) + + for c in p.get_connections(): + c_elem = Element( "conn" ) + c_elem.setAttribute( "dst", c ) + + po_elem.appendChild( c_elem ) + + cl_elem.appendChild( po_elem ) + + self.dom.documentElement.appendChild( cl_elem ) + + def get_xml(self): + return self.dom.toprettyxml() + + def get_client_names(self): + retval = [] + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "jackclient" ): + retval.append( c.getAttribute( "jackname" ) ) + + return retval + + def get_reg_client_names(self): + retval = [] + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "jackclient" ): + if c.getAttribute( "infra" ) != "True": + retval.append( c.getAttribute( "jackname" ) ) + + return retval + + def get_infra_clients(self): + retval = [] + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "jackclient" ): + if c.getAttribute( "infra" ) == "True": + retval.append( (c.getAttribute( "jackname" ), c.getAttribute( "cmdline" ) ) ) + + return retval + def get_port_names(self): + retval = [] + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "port" ): + retval.append( c.getAttribute( "name" ) ) + return retval + + def get_connections_for_port( self, portname ): + retval = [] + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "port" ): + if c.getAttribute( "name" ) == portname: + for i in c.getElementsByTagName( "conn" ): + retval.append( i.getAttribute( "dst" ) ) + return retval + + def get_commandline_for_client( self, name ): + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "jackclient" ): + if c.getAttribute( "jackname" ) == name: + return c.getAttribute( "cmdline" ) + + def get_uuid_client_pairs( self ): + retval = [] + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "jackclient" ): + if c.getAttribute( "infra" ) != "True": + retval.append( (c.getAttribute( "uuid" ), c.getAttribute( "jackname" )) ) + + return retval + + def renameclient( self, celem, newname ): + doc = self.dom.documentElement + celem.setAttribute( "jackname", newname ) + for pelem in celem.getElementsByTagName( "port" ): + old_pname = pelem.getAttribute( "name" ) + pname_split = old_pname.split(":") + pname_split[0] = newname + new_pname = string.join( pname_split, ":" ) + pelem.setAttribute( "name", new_pname ) + for dst in doc.getElementsByTagName( "conn" ): + if dst.getAttribute( "dst" ) == old_pname: + dst.setAttribute( "dst", new_pname ) + + + + def fixup_client_names( self, graph ): + doc = self.dom.documentElement + for c in doc.getElementsByTagName( "jackclient" ): + if c.getAttribute( "infra" ) == "True": + continue + cname = c.getAttribute( "jackname" ) + if cname in graph.get_taken_names(): + free_name = graph.get_free_name( cname, self.get_reg_client_names() ) + print "name taken %s.. reallocate to %s"%(cname, free_name ) + self.renameclient( c, free_name ) + + + + + + + + + + + + + diff --git a/tools/Makefile.am b/tools/Makefile.am index 9ac94c4..6616f05 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -38,6 +38,7 @@ bin_PROGRAMS = jack_load \ jack_alias \ jack_bufsize \ jack_samplerate \ + jack_session_notify \ jack_wait \ $(JACK_TRANSPORT) \ $(NETJACK_TOOLS) @@ -96,6 +97,10 @@ jack_samplerate_SOURCES = samplerate.c jack_samplerate_LDFLAGS = @OS_LDFLAGS@ jack_samplerate_LDADD = $(top_builddir)/libjack/libjack.la +jack_session_notify_SOURCES = session_notify.c +jack_session_notify_LDFLAGS = @OS_LDFLAGS@ +jack_session_notify_LDADD = $(top_builddir)/libjack/libjack.la + if HAVE_READLINE jack_transport_SOURCES = transport.c jack_transport_LDFLAGS = -lreadline @READLINE_DEPS@ @OS_LDFLAGS@ diff --git a/tools/connect.c b/tools/connect.c index 23189a3..7ddc7ba 100644 --- a/tools/connect.c +++ b/tools/connect.c @@ -27,6 +27,7 @@ #include <config.h> #include <jack/jack.h> +#include <jack/session.h> #define TRUE 1 #define FALSE 0 @@ -49,6 +50,7 @@ show_usage (char *my_name) fprintf (stderr, "For more information see http://jackaudio.org/\n"); } + int main (int argc, char *argv[]) { @@ -63,6 +65,9 @@ main (int argc, char *argv[]) jack_port_t *dst_port = 0; jack_port_t *port1 = 0; jack_port_t *port2 = 0; + char portA[300]; + char portB[300]; + int use_uuid=0; int connecting, disconnecting; int port1_flags, port2_flags; int rc = 1; @@ -71,16 +76,20 @@ main (int argc, char *argv[]) { "server", 1, 0, 's' }, { "help", 0, 0, 'h' }, { "version", 0, 0, 'v' }, + { "uuid", 0, 0, 'u' }, { 0, 0, 0, 0 } }; - while ((c = getopt_long (argc, argv, "s:hv", long_options, &option_index)) >= 0) { + while ((c = getopt_long (argc, argv, "s:hvu", long_options, &option_index)) >= 0) { switch (c) { case 's': server_name = (char *) malloc (sizeof (char) * strlen(optarg)); strcpy (server_name, optarg); options |= JackServerName; break; + case 'u': + use_uuid = 1; + break; case 'h': show_usage (my_name); return 1; @@ -123,12 +132,48 @@ main (int argc, char *argv[]) /* find the two ports */ - if ((port1 = jack_port_by_name(client, argv[argc-1])) == 0) { - fprintf (stderr, "ERROR %s not a valid port\n", argv[argc-1]); + if( use_uuid ) { + char *tmpname; + char *clientname; + char *portname; + tmpname = strdup( argv[argc-1] ); + portname = strchr( tmpname, ':' ); + portname[0] = '\0'; + portname+=1; + clientname = jack_get_client_name_by_uuid( client, tmpname ); + if( clientname ) { + + snprintf( portA, sizeof(portA), "%s:%s", clientname, portname ); + jack_free( clientname ); + } else { + snprintf( portA, sizeof(portA), "%s", argv[argc-1] ); + } + free( tmpname ); + + tmpname = strdup( argv[argc-2] ); + portname = strchr( tmpname, ':' ); + portname[0] = '\0'; + portname+=1; + clientname = jack_get_client_name_by_uuid( client, tmpname ); + if( clientname ) { + snprintf( portB, sizeof(portB), "%s:%s", clientname, portname ); + jack_free( clientname ); + } else { + snprintf( portB, sizeof(portB), "%s", argv[argc-2] ); + } + + free( tmpname ); + + } else { + snprintf( portA, sizeof(portA), "%s", argv[argc-1] ); + snprintf( portB, sizeof(portB), "%s", argv[argc-2] ); + } + if ((port1 = jack_port_by_name(client, portA)) == 0) { + fprintf (stderr, "ERROR %s not a valid port\n", portA); goto exit; } - if ((port2 = jack_port_by_name(client, argv[argc-2])) == 0) { - fprintf (stderr, "ERROR %s not a valid port\n", argv[argc-2]); + if ((port2 = jack_port_by_name(client, portB)) == 0) { + fprintf (stderr, "ERROR %s not a valid port\n", portB); goto exit; } diff --git a/tools/session_notify.c b/tools/session_notify.c new file mode 100644 index 0000000..67170cb --- /dev/null +++ b/tools/session_notify.c @@ -0,0 +1,180 @@ +/* + * session_notify.c -- ultra minimal session manager + * + * Copyright (C) 2010 Torben Hohn. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <jack/jack.h> +#include <jack/jslist.h> +#include <jack/transport.h> +#include <jack/session.h> + +char *package; /* program name */ +jack_client_t *client; + +jack_session_event_type_t notify_type; +char *save_path = NULL; + +void jack_shutdown(void *arg) +{ + fprintf(stderr, "JACK shut down, exiting ...\n"); + exit(1); +} + +void signal_handler(int sig) +{ + jack_client_close(client); + fprintf(stderr, "signal received, exiting ...\n"); + exit(0); +} + +void parse_arguments(int argc, char *argv[]) +{ + + /* basename $0 */ + package = strrchr(argv[0], '/'); + if (package == 0) + package = argv[0]; + else + package++; + + if (argc==2) { + if( !strcmp( argv[1], "quit" ) ) { + notify_type = JackSessionSaveAndQuit; + return; + } + } + if (argc==3) { + if( !strcmp( argv[1], "save" ) ) { + notify_type = JackSessionSave; + save_path = argv[2]; + return; + } + + } + fprintf(stderr, "usage: %s quit|save [path]\n", package); + exit(9); +} + +typedef struct { + char name[32]; + char uuid[16]; +} uuid_map_t; + +JSList *uuid_map = NULL; + +void add_uuid_mapping( const char *uuid ) { + char *clientname = jack_get_client_name_by_uuid( client, uuid ); + if( !clientname ) { + printf( "error... cant find client for uuid" ); + return; + } + + uuid_map_t *mapping = malloc( sizeof(uuid_map_t) ); + snprintf( mapping->uuid, sizeof(mapping->uuid), "%s", uuid ); + snprintf( mapping->name, sizeof(mapping->name), "%s", clientname ); + uuid_map = jack_slist_append( uuid_map, mapping ); +} + +char *map_port_name_to_uuid_port( const char *port_name ) +{ + JSList *node; + char retval[300]; + char *port_component = strchr( port_name,':' ); + char *client_component = strdup( port_name ); + strchr( client_component, ':' )[0] = '\0'; + + sprintf( retval, "%s", port_name ); + + for( node=uuid_map; node; node=jack_slist_next(node) ) { + uuid_map_t *mapping = node->data; + if( !strcmp( mapping->name, client_component ) ) { + sprintf( retval, "%s%s", mapping->uuid, port_component ); + break; + } + } + + return strdup(retval); +} + +int main(int argc, char *argv[]) +{ + parse_arguments(argc, argv); + jack_session_command_t *retval; + int k,i,j; + + + /* become a JACK client */ + if ((client = jack_client_open(package, JackNullOption, NULL)) == 0) { + fprintf(stderr, "JACK server not running?\n"); + exit(1); + } + + signal(SIGQUIT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGHUP, signal_handler); + signal(SIGINT, signal_handler); + + jack_on_shutdown(client, jack_shutdown, 0); + + jack_activate(client); + + + retval = jack_session_notify( client, NULL, notify_type, save_path ); + for(i=0; retval[i].uuid; i++ ) { + printf( "%s &\n", retval[i].command ); + add_uuid_mapping(retval[i].uuid); + } + + printf( "sleep 10\n" ); + + for(k=0; retval[k].uuid; k++ ) { + + char* port_regexp = alloca( jack_client_name_size()+3 ); + char* client_name = jack_get_client_name_by_uuid( client, retval[k].uuid ); + snprintf( port_regexp, jack_client_name_size()+3, "%s:.*", client_name ); + jack_free(client_name); + const char **ports = jack_get_ports( client, port_regexp, NULL, 0 ); + if( !ports ) { + continue; + } + for (i = 0; ports[i]; ++i) { + const char **connections; + if ((connections = jack_port_get_all_connections (client, jack_port_by_name(client, ports[i]))) != 0) { + for (j = 0; connections[j]; j++) { + char *src = map_port_name_to_uuid_port( ports[i] ); + char *dst = map_port_name_to_uuid_port( connections[j] ); + printf( "jack_connect -u \"%s\" \"%s\"\n", src, dst ); + } + jack_free (connections); + } + } + jack_free(ports); + + } + jack_session_commands_free(retval); + + jack_client_close(client); + + return 0; +} |