2007-10-22 22:11:04 +08:00
|
|
|
/*
|
|
|
|
*
|
|
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
|
|
*
|
2010-01-07 17:02:51 +08:00
|
|
|
* Copyright (C) 2006-2010 Nokia Corporation
|
2010-01-02 09:08:17 +08:00
|
|
|
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
|
2011-08-11 23:53:31 +08:00
|
|
|
* Copyright (C) 2011 Texas Instruments, Inc.
|
2007-10-24 01:17:47 +08:00
|
|
|
*
|
2007-10-22 22:11:04 +08:00
|
|
|
*
|
|
|
|
* 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 <config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <signal.h>
|
2007-10-25 18:38:47 +08:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
2007-10-22 22:11:04 +08:00
|
|
|
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
|
|
#include <bluetooth/sdp.h>
|
|
|
|
#include <bluetooth/sdp_lib.h>
|
|
|
|
|
2008-05-09 04:23:45 +08:00
|
|
|
#include <glib.h>
|
|
|
|
#include <dbus/dbus.h>
|
2008-05-09 06:19:14 +08:00
|
|
|
#include <gdbus.h>
|
2008-05-09 04:23:45 +08:00
|
|
|
|
2010-05-21 20:26:15 +08:00
|
|
|
#include "log.h"
|
2009-03-03 07:51:40 +08:00
|
|
|
#include "error.h"
|
2007-10-22 22:11:04 +08:00
|
|
|
#include "device.h"
|
|
|
|
#include "manager.h"
|
2011-09-13 00:34:00 +08:00
|
|
|
#include "avctp.h"
|
2007-10-22 22:11:04 +08:00
|
|
|
#include "control.h"
|
2008-03-28 07:07:19 +08:00
|
|
|
#include "sdpd.h"
|
2008-05-09 02:47:21 +08:00
|
|
|
#include "glib-helper.h"
|
2008-10-03 03:48:17 +08:00
|
|
|
#include "dbus-common.h"
|
2007-10-22 22:11:04 +08:00
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
static unsigned int avctp_id = 0;
|
2007-10-22 22:11:04 +08:00
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
struct control {
|
2008-05-29 16:05:16 +08:00
|
|
|
struct audio_device *dev;
|
2011-09-13 00:34:00 +08:00
|
|
|
struct avctp *session;
|
2009-04-23 06:27:27 +08:00
|
|
|
|
|
|
|
gboolean target;
|
2007-10-22 22:11:04 +08:00
|
|
|
};
|
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
static void state_changed(struct audio_device *dev, avctp_state_t old_state,
|
|
|
|
avctp_state_t new_state, void *user_data)
|
2008-05-27 22:02:41 +08:00
|
|
|
{
|
2009-04-23 04:00:01 +08:00
|
|
|
struct control *control = dev->control;
|
|
|
|
gboolean value;
|
2008-05-27 22:02:41 +08:00
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
switch (new_state) {
|
|
|
|
case AVCTP_STATE_DISCONNECTED:
|
2011-09-13 00:34:00 +08:00
|
|
|
control->session = NULL;
|
2009-09-05 00:01:30 +08:00
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
if (old_state != AVCTP_STATE_CONNECTED)
|
|
|
|
break;
|
|
|
|
|
|
|
|
value = FALSE;
|
|
|
|
g_dbus_emit_signal(dev->conn, dev->path,
|
|
|
|
AUDIO_CONTROL_INTERFACE,
|
|
|
|
"Disconnected", DBUS_TYPE_INVALID);
|
|
|
|
emit_property_changed(dev->conn, dev->path,
|
|
|
|
AUDIO_CONTROL_INTERFACE, "Connected",
|
|
|
|
DBUS_TYPE_BOOLEAN, &value);
|
2009-09-06 07:55:27 +08:00
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
break;
|
|
|
|
case AVCTP_STATE_CONNECTING:
|
2011-09-13 00:34:00 +08:00
|
|
|
if (control->session)
|
|
|
|
break;
|
|
|
|
|
2011-09-13 00:34:01 +08:00
|
|
|
control->session = avctp_get(&dev->src, &dev->dst);
|
2011-09-13 00:34:00 +08:00
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
break;
|
|
|
|
case AVCTP_STATE_CONNECTED:
|
|
|
|
value = TRUE;
|
2011-09-13 00:34:00 +08:00
|
|
|
g_dbus_emit_signal(dev->conn, dev->path,
|
2009-04-23 04:00:01 +08:00
|
|
|
AUDIO_CONTROL_INTERFACE, "Connected",
|
|
|
|
DBUS_TYPE_INVALID);
|
2011-09-13 00:34:00 +08:00
|
|
|
emit_property_changed(dev->conn, dev->path,
|
2009-04-23 04:00:01 +08:00
|
|
|
AUDIO_CONTROL_INTERFACE, "Connected",
|
|
|
|
DBUS_TYPE_BOOLEAN, &value);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
2008-05-27 22:02:41 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-06-02 22:34:58 +08:00
|
|
|
static DBusMessage *control_is_connected(DBusConnection *conn,
|
2007-10-23 15:54:36 +08:00
|
|
|
DBusMessage *msg,
|
|
|
|
void *data)
|
|
|
|
{
|
2008-05-29 16:05:16 +08:00
|
|
|
struct audio_device *device = data;
|
2007-10-23 15:54:36 +08:00
|
|
|
struct control *control = device->control;
|
|
|
|
DBusMessage *reply;
|
|
|
|
dbus_bool_t connected;
|
|
|
|
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
|
|
if (!reply)
|
2008-06-02 22:34:58 +08:00
|
|
|
return NULL;
|
2007-10-23 15:54:36 +08:00
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
connected = (control->session != NULL);
|
2007-10-23 15:54:36 +08:00
|
|
|
|
|
|
|
dbus_message_append_args(reply, DBUS_TYPE_BOOLEAN, &connected,
|
|
|
|
DBUS_TYPE_INVALID);
|
|
|
|
|
2008-06-02 22:34:58 +08:00
|
|
|
return reply;
|
2007-10-23 15:54:36 +08:00
|
|
|
}
|
|
|
|
|
2009-03-03 07:51:40 +08:00
|
|
|
static DBusMessage *volume_up(DBusConnection *conn, DBusMessage *msg,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct audio_device *device = data;
|
|
|
|
struct control *control = device->control;
|
|
|
|
int err;
|
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
if (!control->session)
|
2010-12-07 03:10:43 +08:00
|
|
|
return btd_error_not_connected(msg);
|
2009-03-03 07:51:40 +08:00
|
|
|
|
2009-04-23 06:27:27 +08:00
|
|
|
if (!control->target)
|
2010-12-07 03:10:42 +08:00
|
|
|
return btd_error_not_supported(msg);
|
2009-04-23 06:27:27 +08:00
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
err = avctp_send_passthrough(control->session, VOL_UP_OP);
|
2009-03-03 07:51:40 +08:00
|
|
|
if (err < 0)
|
2010-12-14 05:33:05 +08:00
|
|
|
return btd_error_failed(msg, strerror(-err));
|
2009-03-03 07:51:40 +08:00
|
|
|
|
|
|
|
return dbus_message_new_method_return(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
static DBusMessage *volume_down(DBusConnection *conn, DBusMessage *msg,
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct audio_device *device = data;
|
|
|
|
struct control *control = device->control;
|
|
|
|
int err;
|
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
if (!control->session)
|
2010-12-07 03:10:43 +08:00
|
|
|
return btd_error_not_connected(msg);
|
2009-03-03 07:51:40 +08:00
|
|
|
|
2009-04-23 06:27:27 +08:00
|
|
|
if (!control->target)
|
2010-12-07 03:10:42 +08:00
|
|
|
return btd_error_not_supported(msg);
|
2009-04-23 06:27:27 +08:00
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
err = avctp_send_passthrough(control->session, VOL_DOWN_OP);
|
2009-03-03 07:51:40 +08:00
|
|
|
if (err < 0)
|
2010-12-14 05:33:05 +08:00
|
|
|
return btd_error_failed(msg, strerror(-err));
|
2009-03-03 07:51:40 +08:00
|
|
|
|
|
|
|
return dbus_message_new_method_return(msg);
|
|
|
|
}
|
|
|
|
|
2008-10-03 03:48:17 +08:00
|
|
|
static DBusMessage *control_get_properties(DBusConnection *conn,
|
|
|
|
DBusMessage *msg, void *data)
|
|
|
|
{
|
|
|
|
struct audio_device *device = data;
|
|
|
|
DBusMessage *reply;
|
|
|
|
DBusMessageIter iter;
|
|
|
|
DBusMessageIter dict;
|
|
|
|
gboolean value;
|
|
|
|
|
2008-10-03 14:29:51 +08:00
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
|
|
if (!reply)
|
|
|
|
return NULL;
|
|
|
|
|
2008-10-03 03:48:17 +08:00
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
|
|
|
|
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
|
|
|
|
DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
|
|
|
|
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
|
|
|
|
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
|
|
|
|
|
|
|
|
/* Connected */
|
2011-09-13 00:34:00 +08:00
|
|
|
value = (device->control->session != NULL);
|
2008-10-17 02:23:43 +08:00
|
|
|
dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
|
2008-10-03 03:48:17 +08:00
|
|
|
|
|
|
|
dbus_message_iter_close_container(&iter, &dict);
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
2012-05-18 11:23:25 +08:00
|
|
|
static const GDBusMethodTable control_methods[] = {
|
2012-06-27 05:11:25 +08:00
|
|
|
{ GDBUS_DEPRECATED_METHOD("IsConnected",
|
2012-05-18 11:23:29 +08:00
|
|
|
NULL, GDBUS_ARGS({ "connected", "b" }),
|
|
|
|
control_is_connected) },
|
2012-05-18 11:23:32 +08:00
|
|
|
{ GDBUS_METHOD("GetProperties",
|
2012-05-18 11:23:29 +08:00
|
|
|
NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
|
|
|
|
control_get_properties) },
|
2012-05-18 11:23:32 +08:00
|
|
|
{ GDBUS_METHOD("VolumeUp", NULL, NULL, volume_up) },
|
|
|
|
{ GDBUS_METHOD("VolumeDown", NULL, NULL, volume_down) },
|
2012-05-18 11:23:29 +08:00
|
|
|
{ }
|
2007-10-23 15:54:36 +08:00
|
|
|
};
|
|
|
|
|
2012-05-18 11:23:26 +08:00
|
|
|
static const GDBusSignalTable control_signals[] = {
|
2012-05-18 11:23:32 +08:00
|
|
|
{ GDBUS_DEPRECATED_SIGNAL("Connected", NULL) },
|
|
|
|
{ GDBUS_DEPRECATED_SIGNAL("Disconnected", NULL) },
|
|
|
|
{ GDBUS_SIGNAL("PropertyChanged",
|
2012-05-18 11:23:29 +08:00
|
|
|
GDBUS_ARGS({ "name", "s" }, { "value", "v" })) },
|
|
|
|
{ }
|
2007-10-23 15:54:36 +08:00
|
|
|
};
|
|
|
|
|
2008-07-31 04:57:50 +08:00
|
|
|
static void path_unregister(void *data)
|
|
|
|
{
|
|
|
|
struct audio_device *dev = data;
|
2009-04-23 04:00:01 +08:00
|
|
|
struct control *control = dev->control;
|
2008-07-31 04:57:50 +08:00
|
|
|
|
2010-05-21 23:54:54 +08:00
|
|
|
DBG("Unregistered interface %s on path %s",
|
2008-07-31 04:57:50 +08:00
|
|
|
AUDIO_CONTROL_INTERFACE, dev->path);
|
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
if (control->session)
|
|
|
|
avctp_disconnect(control->session);
|
2009-04-23 04:00:01 +08:00
|
|
|
|
|
|
|
g_free(control);
|
|
|
|
dev->control = NULL;
|
2008-07-31 04:57:50 +08:00
|
|
|
}
|
|
|
|
|
2011-08-11 23:53:13 +08:00
|
|
|
void control_unregister(struct audio_device *dev)
|
2009-04-23 06:27:27 +08:00
|
|
|
{
|
2011-08-11 23:53:13 +08:00
|
|
|
g_dbus_unregister_interface(dev->conn, dev->path,
|
|
|
|
AUDIO_CONTROL_INTERFACE);
|
|
|
|
}
|
|
|
|
|
2011-09-13 00:34:01 +08:00
|
|
|
void control_update(struct control *control, uint16_t uuid16)
|
2011-08-11 23:53:13 +08:00
|
|
|
{
|
2009-04-23 06:27:27 +08:00
|
|
|
if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
|
|
|
|
control->target = TRUE;
|
|
|
|
}
|
|
|
|
|
2011-09-13 00:34:01 +08:00
|
|
|
struct control *control_init(struct audio_device *dev, uint16_t uuid16)
|
2007-10-23 15:54:36 +08:00
|
|
|
{
|
2009-04-23 04:00:01 +08:00
|
|
|
struct control *control;
|
|
|
|
|
2008-06-02 22:34:58 +08:00
|
|
|
if (!g_dbus_register_interface(dev->conn, dev->path,
|
|
|
|
AUDIO_CONTROL_INTERFACE,
|
|
|
|
control_methods, control_signals, NULL,
|
2008-07-31 04:57:50 +08:00
|
|
|
dev, path_unregister))
|
2007-10-23 15:54:36 +08:00
|
|
|
return NULL;
|
|
|
|
|
2010-05-21 23:54:54 +08:00
|
|
|
DBG("Registered interface %s on path %s",
|
2008-07-29 04:15:27 +08:00
|
|
|
AUDIO_CONTROL_INTERFACE, dev->path);
|
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
control = g_new0(struct control, 1);
|
|
|
|
control->dev = dev;
|
|
|
|
|
2011-09-13 00:34:01 +08:00
|
|
|
control_update(control, uuid16);
|
2009-04-23 06:27:27 +08:00
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
if (!avctp_id)
|
|
|
|
avctp_id = avctp_add_state_cb(state_changed, NULL);
|
|
|
|
|
2009-04-23 04:00:01 +08:00
|
|
|
return control;
|
2007-10-23 15:54:36 +08:00
|
|
|
}
|
|
|
|
|
2008-05-29 16:05:16 +08:00
|
|
|
gboolean control_is_active(struct audio_device *dev)
|
2007-10-23 15:54:36 +08:00
|
|
|
{
|
|
|
|
struct control *control = dev->control;
|
|
|
|
|
2011-09-13 00:34:00 +08:00
|
|
|
if (control && control->session)
|
2007-10-23 15:54:36 +08:00
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|