/* -*- Mode: C ; c-basic-offset: 4 -*- */ /* Copyright (C) 2011 Nedko Arnaudov 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. 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. */ #if defined(HAVE_CONFIG_H) #include "config.h" #endif #include #include #include #include #include #include "jackdbus.h" #include "controller_internal.h" #include "jack/session.h" #include "jack/control.h" #define JACK_DBUS_IFACE_NAME "org.jackaudio.SessionManager" static void jack_controller_control_send_signal_session_state_changed( jack_session_event_type_t type, const char * target) { dbus_uint32_t u32; u32 = type; if (target == NULL) { target = ""; } jack_dbus_send_signal( JACK_CONTROLLER_OBJECT_PATH, JACK_DBUS_IFACE_NAME, "StateChanged", DBUS_TYPE_UINT32, &u32, DBUS_TYPE_STRING, &target, DBUS_TYPE_INVALID); } static bool start_detached_thread(void * (* start_routine)(void *), void * arg) { int ret; static pthread_attr_t attr; pthread_t tid; ret = pthread_attr_init(&attr); if (ret != 0) { jack_error("pthread_attr_init() failed with %d", ret); goto exit; } ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (ret != 0) { jack_error("pthread_attr_setdetachstate() failed with %d", ret); goto destroy_attr; } ret = pthread_create(&tid, &attr, start_routine, arg); if (ret != 0) { jack_error("pthread_create() failed with %d", ret); goto destroy_attr; } jack_log("Detached thread %d created", (int)tid); destroy_attr: pthread_attr_destroy(&attr); exit: return ret == 0; } static void send_session_notify_reply(struct jack_session_pending_command * pending_cmd_ptr, jack_session_command_t * commands) { struct jack_dbus_method_call call; const jack_session_command_t * cmd_ptr; DBusMessageIter top_iter, array_iter, struct_iter; dbus_uint32_t u32; /* jack_dbus_error() wants call struct */ call.message = pending_cmd_ptr->message; call.connection = pending_cmd_ptr->connection; if (commands == NULL) { jack_dbus_error(&call, JACK_DBUS_ERROR_GENERIC, "jack_session_notify() failed"); goto send_reply; } jack_info("Session notify complete, commands follow:"); call.reply = dbus_message_new_method_return(pending_cmd_ptr->message); if (call.reply == NULL) { goto oom; } dbus_message_iter_init_append(call.reply, &top_iter); if (!dbus_message_iter_open_container(&top_iter, DBUS_TYPE_ARRAY, "(sssu)", &array_iter)) { goto unref; } for (cmd_ptr = commands; cmd_ptr->uuid != NULL; cmd_ptr++) { if (!dbus_message_iter_open_container(&array_iter, DBUS_TYPE_STRUCT, NULL, &struct_iter)) { goto close_array; } if (!dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &cmd_ptr->uuid)) { goto close_struct; } if (!dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &cmd_ptr->client_name)) { goto close_struct; } if (!dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_STRING, &cmd_ptr->command)) { goto close_struct; } u32 = cmd_ptr->flags; if (!dbus_message_iter_append_basic(&struct_iter, DBUS_TYPE_UINT32, &u32)) { goto close_struct; } jack_info("uuid='%s', client='%s', command='%s', flags=0x%"PRIX32, cmd_ptr->uuid, cmd_ptr->client_name, cmd_ptr->command, u32); if (!dbus_message_iter_close_container(&array_iter, &struct_iter)) { goto close_array; } } jack_info("End of session commands."); if (!dbus_message_iter_close_container(&top_iter, &array_iter)) { goto unref; } goto send_reply; close_struct: dbus_message_iter_close_container(&array_iter, &struct_iter); close_array: dbus_message_iter_close_container(&top_iter, &array_iter); unref: dbus_message_unref(call.reply); goto oom; send_reply: if (call.reply != NULL) { if (!dbus_connection_send(pending_cmd_ptr->connection, call.reply, NULL)) { jack_error("Ran out of memory trying to queue method return"); } dbus_connection_flush(pending_cmd_ptr->connection); dbus_message_unref(call.reply); } else { oom: jack_error("Ran out of memory trying to construct method return"); } } #define controller_ptr ((struct jack_controller *)context) void * jack_controller_process_session_command_thread(void * context) { struct jack_session_pending_command * pending_cmd_ptr; jack_session_command_t * commands; jack_log("jack_controller_process_session_command_thread enter"); pthread_mutex_lock(&controller_ptr->lock); loop: /* get next command */ assert(!list_empty(&controller_ptr->session_pending_commands)); pending_cmd_ptr = list_entry(controller_ptr->session_pending_commands.next, struct jack_session_pending_command, siblings); pthread_mutex_unlock(&controller_ptr->lock); jack_info("Session notify initiated. target='%s', type=%d, path='%s'", pending_cmd_ptr->target, (int)pending_cmd_ptr->type, pending_cmd_ptr->path); jack_controller_control_send_signal_session_state_changed(pending_cmd_ptr->type, pending_cmd_ptr->target); commands = jack_session_notify(controller_ptr->client, pending_cmd_ptr->target, pending_cmd_ptr->type, pending_cmd_ptr->path); send_session_notify_reply(pending_cmd_ptr, commands); if (commands != NULL) { jack_session_commands_free(commands); } pthread_mutex_lock(&controller_ptr->lock); /* keep state consistent by sending signal after to lock */ /* otherwise the main thread may receive not-to-be-queued request and fail */ jack_controller_control_send_signal_session_state_changed(0, NULL); /* remove the head of the list (queue) */ assert(!list_empty(&controller_ptr->session_pending_commands)); assert(pending_cmd_ptr == list_entry(controller_ptr->session_pending_commands.next, struct jack_session_pending_command, siblings)); list_del(&pending_cmd_ptr->siblings); /* command cleanup */ dbus_message_unref(pending_cmd_ptr->message); dbus_connection_ref(pending_cmd_ptr->connection); free(pending_cmd_ptr); /* If there are more commands, process them. Otherwise - exit the thread */ if (!list_empty(&controller_ptr->session_pending_commands)) { goto loop; } pthread_mutex_unlock(&controller_ptr->lock); jack_log("jack_controller_process_session_command_thread exit"); return NULL; } #undef controller_ptr #define controller_ptr ((struct jack_controller *)call->context) static void jack_controller_dbus_session_notify( struct jack_dbus_method_call * call) { dbus_bool_t queue; const char * target; dbus_uint32_t u32; const char * path; jack_session_event_type_t type; struct jack_session_pending_command * cmd_ptr; if (!controller_ptr->started) { jack_dbus_only_error(call, JACK_DBUS_ERROR_SERVER_NOT_RUNNING, "Can't execute method '%s' with stopped JACK server", call->method_name); return; } if (!jack_dbus_get_method_args( call, DBUS_TYPE_BOOLEAN, &queue, DBUS_TYPE_STRING, &target, DBUS_TYPE_UINT32, &u32, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID)) { /* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */ return; } if (*target == 0) { target = NULL; } type = (jack_session_event_type_t)u32; if (type != JackSessionSave && type != JackSessionSaveAndQuit && type != JackSessionSaveTemplate) { jack_dbus_error(call, JACK_DBUS_ERROR_INVALID_ARGS, "Invalid session event type %" PRIu32, u32); return; } pthread_mutex_lock(&controller_ptr->lock); if (list_empty(&controller_ptr->session_pending_commands)) { if (!start_detached_thread(jack_controller_process_session_command_thread, controller_ptr)) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "Cannot start thread to process the command"); goto unlock; } jack_log("Session notify thread started"); } else if (!queue) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "Busy"); goto unlock; } cmd_ptr = malloc(sizeof(struct jack_session_pending_command)); if (cmd_ptr == NULL) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "malloc() failed for jack_session_pending_command struct"); goto unlock; } cmd_ptr->message = dbus_message_ref(call->message); call->message = NULL; /* mark that reply will be sent asynchronously */ cmd_ptr->connection = dbus_connection_ref(call->connection); /* it is safe to use the retrieved pointers because we already made an additional message reference */ cmd_ptr->type = type; cmd_ptr->target = target; cmd_ptr->path = path; list_add_tail(&cmd_ptr->siblings, &controller_ptr->session_pending_commands); jack_log("Session notify scheduled. target='%s', type=%"PRIu32", path='%s'", target, u32, path); unlock: pthread_mutex_unlock(&controller_ptr->lock); } static void jack_controller_dbus_get_uuid_for_client_name( struct jack_dbus_method_call * call) { const char * client_name; char * client_uuid; if (!jack_dbus_get_method_args( call, DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) { /* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */ return; } client_uuid = jack_get_uuid_for_client_name(controller_ptr->client, client_name); if (client_uuid == NULL) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "jack_get_uuid_for_client_name(\"%s\") failed", client_name); return; } jack_dbus_construct_method_return_single(call, DBUS_TYPE_STRING, (message_arg_t)(const char *)client_uuid); free(client_uuid); } static void jack_controller_dbus_get_client_name_by_uuid( struct jack_dbus_method_call * call) { const char * client_uuid; char * client_name; if (!jack_dbus_get_method_args( call, DBUS_TYPE_STRING, &client_uuid, DBUS_TYPE_INVALID)) { /* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */ return; } client_name = jack_get_client_name_by_uuid(controller_ptr->client, client_uuid); if (client_name == NULL) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "jack_get_client_name_by_uuid(\"%s\") failed", client_uuid); return; } jack_dbus_construct_method_return_single(call, DBUS_TYPE_STRING, (message_arg_t)(const char *)client_name); free(client_name); } static void jack_controller_dbus_reserve_client_name( struct jack_dbus_method_call * call) { int ret; const char * client_name; const char * client_uuid; if (!jack_dbus_get_method_args( call, DBUS_TYPE_STRING, &client_name, DBUS_TYPE_STRING, &client_uuid, DBUS_TYPE_INVALID)) { /* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */ return; } ret = jack_reserve_client_name(controller_ptr->client, client_name, client_uuid); if (ret < 0) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "jack_reserve_client_name(name=\"%s\", uuid=\"%s\") failed (%d)", client_name, client_uuid, ret); return; } jack_dbus_construct_method_return_empty(call); } static void jack_controller_dbus_has_session_callback( struct jack_dbus_method_call * call) { int ret; const char * client_name; message_arg_t retval; if (!jack_dbus_get_method_args( call, DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) { /* The method call had invalid arguments meaning that jack_dbus_get_method_args() has constructed an error for us. */ return; } ret = jack_client_has_session_callback(controller_ptr->client, client_name); if (ret < 0) { jack_dbus_error(call, JACK_DBUS_ERROR_GENERIC, "jack_client_has_session_callback(\"%s\") failed (%d)", client_name, ret); return; } retval.boolean = ret; jack_dbus_construct_method_return_single(call, DBUS_TYPE_BOOLEAN, retval); } static void jack_controller_dbus_get_session_state( struct jack_dbus_method_call * call) { DBusMessageIter iter; struct jack_session_pending_command * cmd_ptr; const char * target; dbus_uint32_t type; bool append_failed; call->reply = dbus_message_new_method_return(call->message); if (call->reply == NULL) { goto oom; } dbus_message_iter_init_append(call->reply, &iter); pthread_mutex_lock(&controller_ptr->lock); if (list_empty(&controller_ptr->session_pending_commands)) { type = 0; target = ""; } else { cmd_ptr = list_entry(controller_ptr->session_pending_commands.next, struct jack_session_pending_command, siblings); type = (dbus_uint32_t)cmd_ptr->type; target = cmd_ptr->target; } append_failed = !dbus_message_iter_append_basic(&iter, DBUS_TYPE_UINT32, &type) || !dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &target); pthread_mutex_unlock(&controller_ptr->lock); if (!append_failed) { return; } dbus_message_unref(call->reply); call->reply = NULL; oom: jack_error("Ran out of memory trying to construct method return"); } #undef controller_ptr JACK_DBUS_METHOD_ARGUMENTS_BEGIN(Notify) JACK_DBUS_METHOD_ARGUMENT("queue", DBUS_TYPE_BOOLEAN_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("target", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("type", DBUS_TYPE_UINT32_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("path", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("result", "a(sssu)", true) JACK_DBUS_METHOD_ARGUMENTS_END JACK_DBUS_METHOD_ARGUMENTS_BEGIN(GetUuidForClientName) JACK_DBUS_METHOD_ARGUMENT("name", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("uuid", DBUS_TYPE_STRING_AS_STRING, true) JACK_DBUS_METHOD_ARGUMENTS_END JACK_DBUS_METHOD_ARGUMENTS_BEGIN(GetClientNameByUuid) JACK_DBUS_METHOD_ARGUMENT("uuid", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("name", DBUS_TYPE_STRING_AS_STRING, true) JACK_DBUS_METHOD_ARGUMENTS_END JACK_DBUS_METHOD_ARGUMENTS_BEGIN(ReserveClientName) JACK_DBUS_METHOD_ARGUMENT("name", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("uuid", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENTS_END JACK_DBUS_METHOD_ARGUMENTS_BEGIN(HasSessionCallback) JACK_DBUS_METHOD_ARGUMENT("client_name", DBUS_TYPE_STRING_AS_STRING, false) JACK_DBUS_METHOD_ARGUMENT("has_session_callback", DBUS_TYPE_BOOLEAN_AS_STRING, true) JACK_DBUS_METHOD_ARGUMENTS_END JACK_DBUS_METHOD_ARGUMENTS_BEGIN(GetState) JACK_DBUS_METHOD_ARGUMENT("type", DBUS_TYPE_UINT32_AS_STRING, true) JACK_DBUS_METHOD_ARGUMENT("target", DBUS_TYPE_STRING_AS_STRING, true) JACK_DBUS_METHOD_ARGUMENTS_END JACK_DBUS_SIGNAL_ARGUMENTS_BEGIN(StateChanged) JACK_DBUS_SIGNAL_ARGUMENT("type", DBUS_TYPE_UINT32_AS_STRING) JACK_DBUS_SIGNAL_ARGUMENT("target", DBUS_TYPE_STRING_AS_STRING) JACK_DBUS_SIGNAL_ARGUMENTS_END JACK_DBUS_METHODS_BEGIN JACK_DBUS_METHOD_DESCRIBE(Notify, jack_controller_dbus_session_notify) JACK_DBUS_METHOD_DESCRIBE(GetUuidForClientName, jack_controller_dbus_get_uuid_for_client_name) JACK_DBUS_METHOD_DESCRIBE(GetClientNameByUuid, jack_controller_dbus_get_client_name_by_uuid) JACK_DBUS_METHOD_DESCRIBE(ReserveClientName, jack_controller_dbus_reserve_client_name) JACK_DBUS_METHOD_DESCRIBE(HasSessionCallback, jack_controller_dbus_has_session_callback) JACK_DBUS_METHOD_DESCRIBE(GetState, jack_controller_dbus_get_session_state) JACK_DBUS_METHODS_END JACK_DBUS_SIGNALS_BEGIN JACK_DBUS_SIGNAL_DESCRIBE(StateChanged) JACK_DBUS_SIGNALS_END JACK_DBUS_IFACE_BEGIN(g_jack_controller_iface_session_manager, JACK_DBUS_IFACE_NAME) JACK_DBUS_IFACE_EXPOSE_METHODS JACK_DBUS_IFACE_EXPOSE_SIGNALS JACK_DBUS_IFACE_END