/* -*- Mode: C ; c-basic-offset: 2 -*- */ /* * LADI Session Handler (ladish) * * Copyright (C) 2010,2011,2012 Nedko Arnaudov * ************************************************************************** * This file contains implementation of the JACK multicore (snake) ************************************************************************** * * LADI Session Handler 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. * * LADI Session Handler 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 LADI Session Handler. If not, see * or write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "common.h" #include #include #include #include #include #include "dbus_constants.h" extern const struct cdbus_interface_descriptor g_interface; static const char * g_dbus_unique_name; static cdbus_object_path g_object; static bool g_quit; static unsigned int g_unique_index; struct list_head g_pairs; struct port_pair { struct list_head siblings; jack_client_t * client; bool dead; bool midi; jack_port_t * input_port; jack_port_t * output_port; char * input_port_name; char * output_port_name; }; #define pair_ptr ((struct port_pair *)arg) void shutdown_callback(void * arg) { pair_ptr->dead = true; } int process_callback(jack_nframes_t nframes, void * arg) { void * input; void * output; jack_midi_event_t midi_event; jack_nframes_t midi_event_index; input = jack_port_get_buffer(pair_ptr->input_port, nframes); output = jack_port_get_buffer(pair_ptr->output_port, nframes); if (!pair_ptr->midi) { memcpy(output, input, nframes * sizeof(jack_default_audio_sample_t)); } else { jack_midi_clear_buffer(output); midi_event_index = 0; while (jack_midi_event_get(&midi_event, input, midi_event_index) == 0) { jack_midi_event_write(output, midi_event.time, midi_event.buffer, midi_event.size); midi_event_index++; } } return 0; } #undef pair_ptr static void destroy_pair(struct port_pair * pair_ptr) { list_del(&pair_ptr->siblings); jack_client_close(pair_ptr->client); free(pair_ptr->input_port_name); free(pair_ptr->output_port_name); free(pair_ptr); } static void bury_zombie_pairs(void) { struct list_head * node_ptr; struct list_head * temp_node_ptr; struct port_pair * pair_ptr; list_for_each_safe(node_ptr, temp_node_ptr, &g_pairs) { pair_ptr = list_entry(node_ptr, struct port_pair, siblings); if (pair_ptr->dead) { log_info("Bury zombie '%s':'%s'", pair_ptr->input_port_name, pair_ptr->output_port_name); destroy_pair(pair_ptr); } } } static bool connect_dbus(void) { int ret; dbus_error_init(&cdbus_g_dbus_error); cdbus_g_dbus_connection = dbus_bus_get(DBUS_BUS_SESSION, &cdbus_g_dbus_error); if (dbus_error_is_set(&cdbus_g_dbus_error)) { log_error("Failed to get bus: %s", cdbus_g_dbus_error.message); dbus_error_free(&cdbus_g_dbus_error); goto fail; } g_dbus_unique_name = dbus_bus_get_unique_name(cdbus_g_dbus_connection); if (g_dbus_unique_name == NULL) { log_error("Failed to read unique bus name"); goto unref_connection; } log_info("Connected to local session bus, unique name is \"%s\"", g_dbus_unique_name); ret = dbus_bus_request_name(cdbus_g_dbus_connection, JMCORE_SERVICE_NAME, DBUS_NAME_FLAG_DO_NOT_QUEUE, &cdbus_g_dbus_error); if (ret == -1) { log_error("Failed to acquire bus name: %s", cdbus_g_dbus_error.message); dbus_error_free(&cdbus_g_dbus_error); goto unref_connection; } if (ret == DBUS_REQUEST_NAME_REPLY_EXISTS) { log_error("Requested connection name already exists"); goto unref_connection; } g_object = cdbus_object_path_new(JMCORE_OBJECT_PATH, &g_interface, NULL, NULL); if (g_object == NULL) { goto unref_connection; } if (!cdbus_object_path_register(cdbus_g_dbus_connection, g_object)) { goto destroy_control_object; } return true; destroy_control_object: cdbus_object_path_destroy(cdbus_g_dbus_connection, g_object); unref_connection: dbus_connection_unref(cdbus_g_dbus_connection); fail: return false; } static void disconnect_dbus(void) { cdbus_object_path_destroy(cdbus_g_dbus_connection, g_object); dbus_connection_unref(cdbus_g_dbus_connection); } void term_signal_handler(int signum) { log_info("Caught signal %d (%s), terminating", signum, strsignal(signum)); g_quit = true; } bool install_term_signal_handler(int signum, bool ignore_if_already_ignored) { sig_t sigh; sigh = signal(signum, term_signal_handler); if (sigh == SIG_ERR) { log_error("signal() failed to install handler function for signal %d.", signum); return false; } if (sigh == SIG_IGN && ignore_if_already_ignored) { signal(SIGTERM, SIG_IGN); } return true; } int main(int UNUSED(argc), char ** UNUSED(argv)) { INIT_LIST_HEAD(&g_pairs); install_term_signal_handler(SIGTERM, false); install_term_signal_handler(SIGINT, true); if (!connect_dbus()) { log_error("Failed to connect to D-Bus"); return 1; } while (!g_quit) { dbus_connection_read_write_dispatch(cdbus_g_dbus_connection, 50); bury_zombie_pairs(); } while (!list_empty(&g_pairs)) { destroy_pair(list_entry(g_pairs.next, struct port_pair, siblings)); } disconnect_dbus(); return 0; } /***************************************************************************/ /* D-Bus interface implementation */ static void jmcore_get_pid(struct cdbus_method_call * call_ptr) { dbus_int64_t pid; pid = getpid(); cdbus_method_return_new_single(call_ptr, DBUS_TYPE_INT64, &pid); } static void jmcore_create(struct cdbus_method_call * call_ptr) { dbus_bool_t midi; const char * input; const char * output; struct port_pair * pair_ptr; int ret; char client_name[256]; g_unique_index++; sprintf(client_name, "jmcore-%u", g_unique_index); dbus_error_init(&cdbus_g_dbus_error); if (!dbus_message_get_args( call_ptr->message, &cdbus_g_dbus_error, DBUS_TYPE_BOOLEAN, &midi, DBUS_TYPE_STRING, &input, DBUS_TYPE_STRING, &output, DBUS_TYPE_INVALID)) { cdbus_error(call_ptr, DBUS_ERROR_INVALID_ARGS, "Invalid arguments to method \"%s\": %s", call_ptr->method_name, cdbus_g_dbus_error.message); dbus_error_free(&cdbus_g_dbus_error); goto exit; } pair_ptr = malloc(sizeof(struct port_pair)); if (pair_ptr == NULL) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "Allocation of port pair structure failed"); goto exit; } pair_ptr->input_port_name = strdup(input); if (pair_ptr->input_port_name == NULL) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "Allocation of port name buffer failed"); goto free_pair; } pair_ptr->output_port_name = strdup(output); if (pair_ptr->input_port_name == NULL) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "Allocation of port name buffer failed"); goto free_input_name; } pair_ptr->client = jack_client_open(client_name, JackNoStartServer, NULL); if (pair_ptr->client == NULL) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "Cannot connect to JACK server"); goto free_output_name; } pair_ptr->midi = midi; pair_ptr->dead = false; ret = jack_set_process_callback(pair_ptr->client, process_callback, pair_ptr); if (ret != 0) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "JACK process callback setup failed"); goto close_client; } jack_on_shutdown(pair_ptr->client, shutdown_callback, pair_ptr); pair_ptr->input_port = jack_port_register(pair_ptr->client, input, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); if (pair_ptr->input_port == NULL) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "Port '%s' registration failed.", input); goto free_output_name; } pair_ptr->output_port = jack_port_register(pair_ptr->client, output, midi ? JACK_DEFAULT_MIDI_TYPE : JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (pair_ptr->input_port == NULL) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "Port '%s' registration failed.", output); goto unregister_input_port; } list_add_tail(&pair_ptr->siblings, &g_pairs); ret = jack_activate(pair_ptr->client); if (ret != 0) { cdbus_error(call_ptr, DBUS_ERROR_FAILED, "JACK client activation failed"); goto remove_from_list; } cdbus_method_return_new_void(call_ptr); goto exit; remove_from_list: list_del(&pair_ptr->siblings); //unregister_output_port: jack_port_unregister(pair_ptr->client, pair_ptr->output_port); unregister_input_port: jack_port_unregister(pair_ptr->client, pair_ptr->input_port); close_client: jack_client_close(pair_ptr->client); free_output_name: free(pair_ptr->output_port_name); free_input_name: free(pair_ptr->input_port_name); free_pair: free(pair_ptr); exit: return; } static void jmcore_destroy(struct cdbus_method_call * call_ptr) { const char * port; struct list_head * node_ptr; struct port_pair * pair_ptr; dbus_error_init(&cdbus_g_dbus_error); if (!dbus_message_get_args(call_ptr->message, &cdbus_g_dbus_error, DBUS_TYPE_STRING, &port, DBUS_TYPE_INVALID)) { cdbus_error(call_ptr, DBUS_ERROR_INVALID_ARGS, "Invalid arguments to method \"%s\": %s", call_ptr->method_name, cdbus_g_dbus_error.message); dbus_error_free(&cdbus_g_dbus_error); return; } list_for_each(node_ptr, &g_pairs) { pair_ptr = list_entry(node_ptr, struct port_pair, siblings); if (strcmp(pair_ptr->input_port_name, port) == 0 || strcmp(pair_ptr->output_port_name, port) == 0) { destroy_pair(pair_ptr); cdbus_method_return_new_void(call_ptr); return; } } cdbus_error(call_ptr, DBUS_ERROR_INVALID_ARGS, "port '%s' not found.", port); return; } static void jmcore_exit(struct cdbus_method_call * call_ptr) { log_info("Exit command received through D-Bus"); g_quit = true; cdbus_method_return_new_void(call_ptr); } CDBUS_METHOD_ARGS_BEGIN(get_pid, "Get process ID") CDBUS_METHOD_ARG_DESCRIBE_OUT("process_id", DBUS_TYPE_INT64_AS_STRING, "Process ID") CDBUS_METHOD_ARGS_END CDBUS_METHOD_ARGS_BEGIN(create, "Create port pair") CDBUS_METHOD_ARG_DESCRIBE_IN("midi", "b", "Whether to create midi or audio ports") CDBUS_METHOD_ARG_DESCRIBE_IN("input_port", "s", "Input port name") CDBUS_METHOD_ARG_DESCRIBE_IN("output_port", "s", "Output port name") CDBUS_METHOD_ARGS_END CDBUS_METHOD_ARGS_BEGIN(destroy, "Destroy port pair") CDBUS_METHOD_ARG_DESCRIBE_IN("port", "s", "Port name") CDBUS_METHOD_ARGS_END CDBUS_METHOD_ARGS_BEGIN(exit, "Tell jmcore D-Bus service to exit") CDBUS_METHOD_ARGS_END CDBUS_METHODS_BEGIN CDBUS_METHOD_DESCRIBE(get_pid, jmcore_get_pid) CDBUS_METHOD_DESCRIBE(create, jmcore_create) CDBUS_METHOD_DESCRIBE(destroy, jmcore_destroy) CDBUS_METHOD_DESCRIBE(exit, jmcore_exit) CDBUS_METHODS_END CDBUS_INTERFACE_DEFAULT_HANDLER_METHODS_ONLY(g_interface, JMCORE_IFACE)