/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2007 Nokia Corporation * Copyright (C) 2004-2007 Marcel Holtmann * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include "dbus.h" #include "dbus-helper.h" #include "logging.h" #include "avdtp.h" #include "device.h" #include "a2dp.h" #include "error.h" #include "sink.h" #define STREAM_SETUP_RETRY_TIMER 2000 struct pending_request { DBusConnection *conn; DBusMessage *msg; unsigned int id; }; struct sink { struct avdtp *session; struct avdtp_stream *stream; unsigned int cb_id; uint8_t state; struct pending_request *connect; struct pending_request *disconnect; DBusConnection *conn; }; static void pending_request_free(struct pending_request *pending) { if (pending->conn) dbus_connection_unref(pending->conn); if (pending->msg) dbus_message_unref(pending->msg); g_free(pending); } static void stream_state_changed(struct avdtp_stream *stream, avdtp_state_t old_state, avdtp_state_t new_state, struct avdtp_error *err, void *user_data) { struct device *dev = user_data; struct sink *sink = dev->sink; if (err) return; switch (new_state) { case AVDTP_STATE_IDLE: dbus_connection_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE, "Disconnected", DBUS_TYPE_INVALID); if (sink->disconnect) { DBusMessage *reply; struct pending_request *p; p = sink->disconnect; sink->disconnect = NULL; reply = dbus_message_new_method_return(p->msg); send_message_and_unref(p->conn, reply); pending_request_free(p); } if (sink->session) { avdtp_unref(sink->session); sink->session = NULL; } sink->stream = NULL; sink->cb_id = 0; break; case AVDTP_STATE_OPEN: if (old_state == AVDTP_STATE_CONFIGURED) dbus_connection_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE, "Connected", DBUS_TYPE_INVALID); else if (old_state == AVDTP_STATE_STREAMING) dbus_connection_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE, "Stopped", DBUS_TYPE_INVALID); break; case AVDTP_STATE_STREAMING: dbus_connection_emit_signal(dev->conn, dev->path, AUDIO_SINK_INTERFACE, "Playing", DBUS_TYPE_INVALID); break; case AVDTP_STATE_CONFIGURED: case AVDTP_STATE_CLOSING: case AVDTP_STATE_ABORTING: default: break; } sink->state = new_state; } static gboolean stream_setup_retry(gpointer user_data) { struct sink *sink = user_data; struct pending_request *pending = sink->connect; if (sink->state >= AVDTP_STATE_OPEN) { DBusMessage *reply; debug("Stream successfully created, after XCASE connect:connect"); reply = dbus_message_new_method_return(pending->msg); send_message_and_unref(pending->conn, reply); } else { debug("Stream setup failed, after XCASE connect:connect"); err_failed(pending->conn, pending->msg, "Stream setup failed"); } sink->connect = NULL; pending_request_free(pending); return FALSE; } static void stream_setup_complete(struct avdtp *session, struct a2dp_sep *sep, struct avdtp_stream *stream, void *user_data, struct avdtp_error *err) { struct sink *sink = user_data; struct pending_request *pending; pending = sink->connect; if (stream) { DBusMessage *reply; sink->connect = NULL; reply = dbus_message_new_method_return(pending->msg); send_message_and_unref(pending->conn, reply); pending_request_free(pending); debug("Stream successfully created"); } else { avdtp_unref(sink->session); sink->session = NULL; if (avdtp_error_type(err) == AVDTP_ERROR_ERRNO && avdtp_error_posix_errno(err) != EHOSTDOWN) { debug("connect:connect XCASE detected"); g_timeout_add(STREAM_SETUP_RETRY_TIMER, stream_setup_retry, sink); } else { sink->connect = NULL; err_failed(pending->conn, pending->msg, "Stream setup failed"); pending_request_free(pending); debug("Stream setup failed : %s", avdtp_strerror(err)); } } } static DBusHandlerResult sink_connect(DBusConnection *conn, DBusMessage *msg, void *data) { struct device *dev = data; struct sink *sink = dev->sink; struct pending_request *pending; unsigned int id; if (!sink->session) sink->session = avdtp_get(&dev->src, &dev->dst); if (!sink->session) return err_connect_failed(conn, msg, "Unable to get a session"); if (sink->connect || sink->disconnect) return err_connect_failed(conn, msg, "Connect in progress"); if (sink->state >= AVDTP_STATE_OPEN) return err_already_connected(conn, msg); pending = g_new0(struct pending_request, 1); pending->conn = dbus_connection_ref(conn); pending->msg = dbus_message_ref(msg); sink->connect = pending; id = a2dp_source_request_stream(sink->session, FALSE, stream_setup_complete, sink, NULL); if (id == 0) { pending_request_free(pending); sink->connect = NULL; avdtp_unref(sink->session); sink->session = NULL; return err_connect_failed(conn, msg, "Failed to request a stream"); } debug("stream creation in progress"); pending->id = id; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult sink_disconnect(DBusConnection *conn, DBusMessage *msg, void *data) { struct device *device = data; struct sink *sink = device->sink; struct pending_request *pending; int err; if (!sink->session) return err_not_connected(conn, msg); if (sink->connect || sink->disconnect) return err_failed(conn, msg, strerror(EBUSY)); if (sink->state < AVDTP_STATE_OPEN) { DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; avdtp_unref(sink->session); sink->session = NULL; return send_message_and_unref(conn, reply); } err = avdtp_close(sink->session, sink->stream); if (err < 0) return err_failed(conn, msg, strerror(-err)); pending = g_new0(struct pending_request, 1); pending->conn = dbus_connection_ref(conn); pending->msg = dbus_message_ref(msg); sink->disconnect = pending; return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult sink_is_connected(DBusConnection *conn, DBusMessage *msg, void *data) { struct device *device = data; struct sink *sink = device->sink; DBusMessage *reply; dbus_bool_t connected; reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; connected = (sink->state >= AVDTP_STATE_CONFIGURED); dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected, DBUS_TYPE_INVALID); send_message_and_unref(conn, reply); return DBUS_HANDLER_RESULT_HANDLED; } static DBusMethodVTable sink_methods[] = { { "Connect", sink_connect, "", "" }, { "Disconnect", sink_disconnect, "", "" }, { "IsConnected", sink_is_connected, "", "b" }, { NULL, NULL, NULL, NULL } }; static DBusSignalVTable sink_signals[] = { { "Connected", "" }, { "Disconnected", "" }, { "Playing", "" }, { "Stopped", "" }, { NULL, NULL } }; struct sink *sink_init(struct device *dev) { if (!dbus_connection_register_interface(dev->conn, dev->path, AUDIO_SINK_INTERFACE, sink_methods, sink_signals, NULL)) return NULL; return g_new0(struct sink, 1); } void sink_free(struct device *dev) { struct sink *sink = dev->sink; if (sink->cb_id) avdtp_stream_remove_cb(sink->session, sink->stream, sink->cb_id); if (sink->session) avdtp_unref(sink->session); if (sink->connect) pending_request_free(sink->connect); if (sink->disconnect) pending_request_free(sink->disconnect); g_free(sink); dev->sink = NULL; } gboolean sink_is_active(struct device *dev) { struct sink *sink = dev->sink; if (sink->session) return TRUE; return FALSE; } avdtp_state_t sink_get_state(struct device *dev) { struct sink *sink = dev->sink; return sink->state; } gboolean sink_new_stream(struct device *dev, struct avdtp *session, struct avdtp_stream *stream) { struct sink *sink = dev->sink; if (sink->stream) return FALSE; if (!sink->session) sink->session = avdtp_ref(session); sink->stream = stream; sink->cb_id = avdtp_stream_add_cb(session, stream, stream_state_changed, dev); return TRUE; }