squashed rebase. git-svn-id: svn+ssh://jackaudio.org/trunk/jack@3969 0c269be4-1314-0410-8aa9-9f06e86f4224tags/0.120.1
| @@ -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 | |||
| @@ -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); | |||
| } | |||
| @@ -7,6 +7,7 @@ libjackinclude_HEADERS = \ | |||
| jack.h \ | |||
| ringbuffer.h \ | |||
| statistics.h \ | |||
| session.h \ | |||
| thread.h \ | |||
| timestamps.h \ | |||
| transport.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__ */ | |||
| @@ -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 { | |||
| @@ -390,6 +401,11 @@ struct _jack_request { | |||
| char source_port[JACK_PORT_NAME_SIZE]; | |||
| 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 | |||
| @@ -407,6 +423,10 @@ struct _jack_request { | |||
| jack_client_id_t client_id; | |||
| 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; | |||
| @@ -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; | |||
| @@ -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; | |||
| /*@}*/ | |||
| /** | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -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", | |||
| @@ -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 || | |||
| @@ -2449,6 +2625,20 @@ jack_set_process_thread(jack_client_t* client, JackThreadCallback callback, void | |||
| return 0; | |||
| } | |||
| 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) | |||
| { | |||
| @@ -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, | |||
| @@ -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 */ | |||
| @@ -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 ) | |||
| @@ -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() | |||
| @@ -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 ) | |||
| @@ -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@ | |||
| @@ -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; | |||
| } | |||
| @@ -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; | |||
| } | |||