All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v3 00/22] Implement AVRCP 1.3 for TG role
@ 2011-08-10 13:06 Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 01/22] audio: move interface declarations to their headers Lucas De Marchi
                   ` (21 more replies)
  0 siblings, 22 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Changes from previous version:
- Removal of patch to use C99 instead of gcc extension
- Use of heap instead of relying on VLA to use memory from stack
- Coding style fixes
- SettingChanged signal and ChangeSetting method were renamed to
  PropertyChanged and SetProperty in order to use the common infrastructure
  already present in BlueZ.


regads,
Lucas De Marchi



Lucas De Marchi (22):
  audio: move interface declarations to their headers
  avrcp: add skeleton of MediaPlayer interface
  avrcp: implement SetProperty() method of MediaPlayer
  avrcp: implement ChangePlayback() method
  avrcp: implement ChangeTrack() method
  avrcp: handle query for company ids
  avrcp: handle ListPlayerApplicationSettingAttributes pdu
  avrcp: handle ListPlayerApplicationSettingValues pdu
  avrcp: handle GetCurrentPlayerAplicationSettingValue pdu
  avrcp: handle SetPlayerApplicationSettingValue pdu
  avrcp: handle commands for future extension
  avrcp: handle InformDisplayableCharacterSet pdu
  avrcp: handle InformBatteryStatusOfCT pdu
  avrcp: handle GetPlayStatus pdu
  avrcp: handle RegisterNotification pdu
  avrcp: handle query for supported events
  avrcp: handle GetElementAttributes pdu
  avrcp: send response for registered events
  avrcp: change TG record to use version 1.3
  avrcp: update copyright
  Add script to test MediaPlayer interface
  Update Control documentation

 audio/control.c        | 1399 +++++++++++++++++++++++++++++++++++++++++++++++-
 audio/control.h        |    4 +-
 audio/device.h         |    4 -
 audio/manager.c        |   11 +-
 audio/manager.h        |    1 +
 audio/unix.c           |    1 +
 doc/control-api.txt    |   94 ++--
 test/test-media-player |  108 ++++
 8 files changed, 1562 insertions(+), 60 deletions(-)
 create mode 100755 test/test-media-player

-- 
1.7.6


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH v3 01/22] audio: move interface declarations to their headers
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 02/22] avrcp: add skeleton of MediaPlayer interface Lucas De Marchi
                   ` (20 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/device.h |    4 ----
 audio/unix.c   |    1 +
 2 files changed, 1 insertions(+), 4 deletions(-)

diff --git a/audio/device.h b/audio/device.h
index 35af788..5117fca 100644
--- a/audio/device.h
+++ b/audio/device.h
@@ -38,10 +38,6 @@
 #define AVRCP_REMOTE_UUID	"0000110e-0000-1000-8000-00805f9b34fb"
 #define AVRCP_TARGET_UUID	"0000110c-0000-1000-8000-00805f9b34fb"
 
-/* Move these to respective .h files once they exist */
-#define AUDIO_SOURCE_INTERFACE		"org.bluez.AudioSource"
-#define AUDIO_CONTROL_INTERFACE		"org.bluez.Control"
-
 struct source;
 struct control;
 struct target;
diff --git a/audio/unix.c b/audio/unix.c
index 8ce50b0..1e0ab30 100644
--- a/audio/unix.c
+++ b/audio/unix.c
@@ -49,6 +49,7 @@
 #include "a2dp.h"
 #include "headset.h"
 #include "sink.h"
+#include "source.h"
 #include "gateway.h"
 #include "unix.h"
 #include "glib-helper.h"
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 02/22] avrcp: add skeleton of MediaPlayer interface
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 01/22] audio: move interface declarations to their headers Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 03/22] avrcp: implement SetProperty() method of MediaPlayer Lucas De Marchi
                   ` (19 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |   56 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 audio/control.h |    4 ++-
 audio/manager.c |   11 ++++++++-
 audio/manager.h |    1 +
 4 files changed, 67 insertions(+), 5 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index c3ef737..cc6532e 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -185,8 +185,12 @@ struct avctp_server {
 	uint32_t ct_record_id;
 };
 
+struct media_player {
+};
+
 struct control {
 	struct audio_device *dev;
+	struct media_player *mp;
 
 	avctp_state_t state;
 
@@ -1125,6 +1129,14 @@ static GDBusSignalTable control_signals[] = {
 	{ NULL, NULL }
 };
 
+static GDBusMethodTable mp_methods[] = {
+	{ }
+};
+
+static GDBusSignalTable mp_signals[] = {
+	{ }
+};
+
 static void path_unregister(void *data)
 {
 	struct audio_device *dev = data;
@@ -1140,10 +1152,29 @@ static void path_unregister(void *data)
 	dev->control = NULL;
 }
 
+static void mp_path_unregister(void *data)
+{
+	struct audio_device *dev = data;
+	struct control *control = dev->control;
+	struct media_player *mp = control->mp;
+
+	DBG("Unregistered interface %s on path %s",
+		MEDIA_PLAYER_INTERFACE, dev->path);
+
+	g_free(mp);
+	control->mp = NULL;
+}
+
 void control_unregister(struct audio_device *dev)
 {
+	struct control *control = dev->control;
+
+	if (control->mp)
+		g_dbus_unregister_interface(dev->conn, dev->path,
+						MEDIA_PLAYER_INTERFACE);
+
 	g_dbus_unregister_interface(dev->conn, dev->path,
-		AUDIO_CONTROL_INTERFACE);
+						AUDIO_CONTROL_INTERFACE);
 }
 
 void control_update(struct audio_device *dev, uint16_t uuid16)
@@ -1154,7 +1185,8 @@ void control_update(struct audio_device *dev, uint16_t uuid16)
 		control->target = TRUE;
 }
 
-struct control *control_init(struct audio_device *dev, uint16_t uuid16)
+struct control *control_init(struct audio_device *dev, uint16_t uuid16,
+							gboolean media_player)
 {
 	struct control *control;
 
@@ -1172,6 +1204,26 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16)
 	control->state = AVCTP_STATE_DISCONNECTED;
 	control->uinput = -1;
 
+	if (media_player) {
+		struct media_player *mp;
+
+		mp = g_new0(struct media_player, 1);
+
+		if (!g_dbus_register_interface(dev->conn, dev->path,
+						MEDIA_PLAYER_INTERFACE,
+						mp_methods, mp_signals, NULL,
+						dev, mp_path_unregister)) {
+			error("D-Bus failed do register %s on path %s",
+					MEDIA_PLAYER_INTERFACE, dev->path);
+			g_free(mp);
+		}
+
+		DBG("Registered interface %s on path %s",
+					MEDIA_PLAYER_INTERFACE, dev->path);
+
+		control->mp = mp;
+	}
+
 	if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
 		control->target = TRUE;
 
diff --git a/audio/control.h b/audio/control.h
index 49f25c2..635a2d5 100644
--- a/audio/control.h
+++ b/audio/control.h
@@ -23,6 +23,7 @@
  */
 
 #define AUDIO_CONTROL_INTERFACE "org.bluez.Control"
+#define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer"
 
 typedef enum {
 	AVCTP_STATE_DISCONNECTED = 0,
@@ -44,7 +45,8 @@ void avrcp_unregister(const bdaddr_t *src);
 gboolean avrcp_connect(struct audio_device *dev);
 void avrcp_disconnect(struct audio_device *dev);
 
-struct control *control_init(struct audio_device *dev, uint16_t uuid16);
+struct control *control_init(struct audio_device *dev, uint16_t uuid16,
+							gboolean media_player);
 void control_update(struct audio_device *dev, uint16_t uuid16);
 void control_unregister(struct audio_device *dev);
 gboolean control_is_active(struct audio_device *dev);
diff --git a/audio/manager.c b/audio/manager.c
index 05ca654..4afa688 100644
--- a/audio/manager.c
+++ b/audio/manager.c
@@ -117,7 +117,8 @@ static struct enabled_interfaces enabled = {
 	.source		= FALSE,
 	.control	= TRUE,
 	.socket		= TRUE,
-	.media		= FALSE
+	.media		= FALSE,
+	.media_player	= FALSE,
 };
 
 static struct audio_adapter *find_adapter(GSList *list,
@@ -220,7 +221,8 @@ static void handle_uuid(const char *uuidstr, struct audio_device *device)
 		if (device->control)
 			control_update(device, uuid16);
 		else
-			device->control = control_init(device, uuid16);
+			device->control = control_init(device, uuid16,
+							enabled.media_player);
 		if (device->sink && sink_is_active(device))
 			avrcp_connect(device);
 		break;
@@ -1169,6 +1171,9 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
 			enabled.socket = TRUE;
 		else if (g_str_equal(list[i], "Media"))
 			enabled.media = TRUE;
+		else if (g_str_equal(list[i], "MediaPlayer"))
+			enabled.media_player = TRUE;
+
 	}
 	g_strfreev(list);
 
@@ -1189,6 +1194,8 @@ int audio_manager_init(DBusConnection *conn, GKeyFile *conf,
 			enabled.socket = FALSE;
 		else if (g_str_equal(list[i], "Media"))
 			enabled.media = FALSE;
+		else if (g_str_equal(list[i], "MediaPlayer"))
+			enabled.media_player = FALSE;
 	}
 	g_strfreev(list);
 
diff --git a/audio/manager.h b/audio/manager.h
index 0bf7663..cfc646c 100644
--- a/audio/manager.h
+++ b/audio/manager.h
@@ -31,6 +31,7 @@ struct enabled_interfaces {
 	gboolean control;
 	gboolean socket;
 	gboolean media;
+	gboolean media_player;
 };
 
 int audio_manager_init(DBusConnection *conn, GKeyFile *config,
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 03/22] avrcp: implement SetProperty() method of MediaPlayer
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 01/22] audio: move interface declarations to their headers Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 02/22] avrcp: add skeleton of MediaPlayer interface Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 04/22] avrcp: implement ChangePlayback() method Lucas De Marchi
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

SetProperty() is used by an application to set player specific
settings.
---
 audio/control.c |  153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 153 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index cc6532e..d2365bf 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -104,6 +104,37 @@
 
 #define QUIRK_NO_RELEASE	1 << 0
 
+enum player_setting {
+	PLAYER_SETTING_EQUALIZER =	1,
+	PLAYER_SETTING_REPEAT =		2,
+	PLAYER_SETTING_SHUFFLE =	3,
+	PLAYER_SETTING_SCAN =		4,
+};
+
+enum equalizer_mode {
+	EQUALIZER_MODE_OFF =	1,
+	EQUALIZER_MODE_ON =	2,
+};
+
+enum repeat_mode {
+	REPEAT_MODE_OFF =	1,
+	REPEAT_MODE_SINGLE =	2,
+	REPEAT_MODE_ALL =	3,
+	REPEAT_MODE_GROUP =	4,
+};
+
+enum shuffle_mode {
+	SHUFFLE_MODE_OFF =	1,
+	SHUFFLE_MODE_ALL =	2,
+	SHUFFLE_MODE_GROUP =	3,
+};
+
+enum scan_mode {
+	SCAN_MODE_OFF =		1,
+	SCAN_MODE_ALL =		2,
+	SCAN_MODE_GROUP =	3,
+};
+
 static DBusConnection *connection = NULL;
 
 static GSList *servers = NULL;
@@ -186,6 +217,7 @@ struct avctp_server {
 };
 
 struct media_player {
+	uint8_t settings[PLAYER_SETTING_SCAN + 1];
 };
 
 struct control {
@@ -423,6 +455,82 @@ static void handle_panel_passthrough(struct control *control,
 						operands[0] & 0x7F, status);
 }
 
+static int attrval_to_val(uint8_t attr, const char *value)
+{
+	int ret;
+
+	switch (attr) {
+	case PLAYER_SETTING_EQUALIZER:
+		if (!strcmp(value, "off"))
+			ret = EQUALIZER_MODE_OFF;
+		else if (!strcmp(value, "on"))
+			ret = EQUALIZER_MODE_ON;
+		else
+			ret = -EINVAL;
+
+		return ret;
+	case PLAYER_SETTING_REPEAT:
+		if (!strcmp(value, "off"))
+			ret = REPEAT_MODE_OFF;
+		else if (!strcmp(value, "singletrack"))
+			ret = REPEAT_MODE_SINGLE;
+		else if (!strcmp(value, "alltracks"))
+			ret = REPEAT_MODE_ALL;
+		else if (!strcmp(value, "group"))
+			ret = REPEAT_MODE_GROUP;
+		else
+			ret = -EINVAL;
+
+		return ret;
+	case PLAYER_SETTING_SHUFFLE:
+		if (!strcmp(value, "off"))
+			ret = SHUFFLE_MODE_OFF;
+		else if (!strcmp(value, "alltracks"))
+			ret = SHUFFLE_MODE_ALL;
+		else if (!strcmp(value, "group"))
+			ret = SHUFFLE_MODE_GROUP;
+		else
+			ret = -EINVAL;
+
+		return ret;
+	case PLAYER_SETTING_SCAN:
+		if (!strcmp(value, "off"))
+			ret = SCAN_MODE_OFF;
+		else if (!strcmp(value, "alltracks"))
+			ret = SCAN_MODE_ALL;
+		else if (!strcmp(value, "group"))
+			ret = SCAN_MODE_GROUP;
+		else
+			ret = -EINVAL;
+
+		return ret;
+	}
+
+	return -EINVAL;
+}
+
+static int attr_to_val(const char *str)
+{
+	if (!strcmp(str, "Equalizer"))
+		return PLAYER_SETTING_EQUALIZER;
+	else if (!strcmp(str, "Repeat"))
+		return PLAYER_SETTING_REPEAT;
+	else if (!strcmp(str, "Shuffle"))
+		return PLAYER_SETTING_SHUFFLE;
+	else if (!strcmp(str, "Scan"))
+		return PLAYER_SETTING_SCAN;
+
+	return -EINVAL;
+}
+
+static void mp_set_attribute(struct media_player *mp,
+						uint8_t attr, uint8_t val)
+{
+	DBG("Change attribute: %u %u", attr, val);
+
+	mp->settings[attr] = val;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -1129,7 +1237,52 @@ static GDBusSignalTable control_signals[] = {
 	{ NULL, NULL }
 };
 
+static DBusMessage *mp_set_property(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessageIter iter;
+	DBusMessageIter var;
+	const char *attrstr, *valstr;
+	int attr, val;
+
+	if (!dbus_message_iter_init(msg, &iter))
+		return btd_error_invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_get_basic(&iter, &attrstr);
+
+	attr = attr_to_val(attrstr);
+	if (attr < 0)
+		return btd_error_not_supported(msg);
+
+	dbus_message_iter_next(&iter);
+
+	if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_recurse(&iter, &var);
+
+	/* Only string arguments are supported for now */
+	if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_STRING)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_get_basic(&var, &valstr);
+
+	val = attrval_to_val(attr, valstr);
+	if (val < 0)
+		return btd_error_not_supported(msg);
+
+	mp_set_attribute(control->mp, attr, val);
+
+	return dbus_message_new_method_return(msg);
+}
+
 static GDBusMethodTable mp_methods[] = {
+	{ "SetProperty",	"sv",		"",	mp_set_property },
 	{ }
 };
 
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 04/22] avrcp: implement ChangePlayback() method
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (2 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 03/22] avrcp: implement SetProperty() method of MediaPlayer Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 05/22] avrcp: implement ChangeTrack() method Lucas De Marchi
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

ChangePlayback() is used by applications to inform bluetoothd of the
current status of playback.
---
 audio/control.c |   77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 77 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index d2365bf..672f640 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -135,6 +135,15 @@ enum scan_mode {
 	SCAN_MODE_GROUP =	3,
 };
 
+enum play_status {
+	PLAY_STATUS_STOPPED =		0x00,
+	PLAY_STATUS_PLAYING =		0x01,
+	PLAY_STATUS_PAUSED =		0x02,
+	PLAY_STATUS_FWD_SEEK =		0x03,
+	PLAY_STATUS_REV_SEEK =		0x04,
+	PLAY_STATUS_ERROR =		0xFF
+};
+
 static DBusConnection *connection = NULL;
 
 static GSList *servers = NULL;
@@ -216,8 +225,16 @@ struct avctp_server {
 	uint32_t ct_record_id;
 };
 
+struct media_info {
+	uint32_t elapsed;
+};
+
 struct media_player {
 	uint8_t settings[PLAYER_SETTING_SCAN + 1];
+	enum play_status status;
+
+	struct media_info mi;
+	GTimer *timer;
 };
 
 struct control {
@@ -523,6 +540,40 @@ static int attr_to_val(const char *str)
 	return -EINVAL;
 }
 
+static int play_status_to_val(const char *status)
+{
+	if (!strcmp(status, "stopped"))
+		return PLAY_STATUS_STOPPED;
+	else if (!strcmp(status, "playing"))
+		return PLAY_STATUS_PLAYING;
+	else if (!strcmp(status, "paused"))
+		return PLAY_STATUS_PAUSED;
+	else if (!strcmp(status, "forward-seek"))
+		return PLAY_STATUS_FWD_SEEK;
+	else if (!strcmp(status, "reverse-seek"))
+		return PLAY_STATUS_REV_SEEK;
+	else if (!strcmp(status, "error"))
+		return PLAY_STATUS_ERROR;
+
+	return -EINVAL;
+}
+
+static void mp_set_playback_status(struct control *control, uint8_t status,
+							uint32_t elapsed)
+{
+	struct media_player *mp = control->mp;
+
+	DBG("Change playback: %u %u", status, elapsed);
+
+	mp->mi.elapsed = elapsed;
+	g_timer_start(mp->timer);
+
+	if (status == mp->status)
+		return;
+
+	mp->status = status;
+}
+
 static void mp_set_attribute(struct media_player *mp,
 						uint8_t attr, uint8_t val)
 {
@@ -1281,8 +1332,32 @@ static DBusMessage *mp_set_property(DBusConnection *conn,
 	return dbus_message_new_method_return(msg);
 }
 
+static DBusMessage *mp_change_playback(DBusConnection *conn,
+					DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	const char *statusstr;
+	int status;
+	uint32_t elapsed;
+
+	if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &statusstr,
+						DBUS_TYPE_UINT32, &elapsed,
+						DBUS_TYPE_INVALID))
+		return btd_error_invalid_args(msg);
+
+	status = play_status_to_val(statusstr);
+	if (status < 0)
+		return btd_error_invalid_args(msg);
+
+	mp_set_playback_status(control, status, elapsed);
+
+	return dbus_message_new_method_return(msg);
+}
+
 static GDBusMethodTable mp_methods[] = {
 	{ "SetProperty",	"sv",		"",	mp_set_property },
+	{ "ChangePlayback",	"su",		"",	mp_change_playback },
 	{ }
 };
 
@@ -1314,6 +1389,7 @@ static void mp_path_unregister(void *data)
 	DBG("Unregistered interface %s on path %s",
 		MEDIA_PLAYER_INTERFACE, dev->path);
 
+	g_timer_destroy(mp->timer);
 	g_free(mp);
 	control->mp = NULL;
 }
@@ -1374,6 +1450,7 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16,
 		DBG("Registered interface %s on path %s",
 					MEDIA_PLAYER_INTERFACE, dev->path);
 
+		mp->timer = g_timer_new();
 		control->mp = mp;
 	}
 
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 05/22] avrcp: implement ChangeTrack() method
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (3 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 04/22] avrcp: implement ChangePlayback() method Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 06/22] avrcp: handle query for company ids Lucas De Marchi
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

ChangeTrack() is used by applications to inform bluetoothd that current
track changed, passing also the metadata.
---
 audio/control.c |  163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 163 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 672f640..cf3bd1f 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -226,6 +226,13 @@ struct avctp_server {
 };
 
 struct media_info {
+	char *title;
+	char *artist;
+	char *album;
+	char *genre;
+	uint32_t ntracks;
+	uint32_t track;
+	uint32_t track_len;
 	uint32_t elapsed;
 };
 
@@ -582,6 +589,41 @@ static void mp_set_attribute(struct media_player *mp,
 	mp->settings[attr] = val;
 }
 
+static void mp_set_media_attributes(struct control *control,
+							struct media_info *mi)
+{
+	struct media_player *mp = control->mp;
+
+	g_free(mp->mi.title);
+	mp->mi.title = g_strdup(mi->title);
+
+	g_free(mp->mi.artist);
+	mp->mi.artist = g_strdup(mi->artist);
+
+	g_free(mp->mi.album);
+	mp->mi.album = g_strdup(mi->album);
+
+	g_free(mp->mi.genre);
+	mp->mi.genre = g_strdup(mi->genre);
+
+	mp->mi.ntracks = mi->ntracks;
+	mp->mi.track = mi->track;
+	mp->mi.track_len = mi->track_len;
+
+	/*
+	 * elapsed is special. Whenever the track changes, we reset it to 0,
+	 * so client doesn't have to make another call to change_playback
+	 */
+	mp->mi.elapsed = 0;
+	g_timer_start(mp->timer);
+
+	DBG("Track changed:\n\ttitle: %s\n\tartist: %s\n\talbum: %s\n"
+			   "\tgenre: %s\n\tNumber of tracks: %u\n"
+			   "\tTrack number: %u\n\tTrack duration: %u",
+			   mi->title, mi->artist, mi->album, mi->genre,
+			   mi->ntracks, mi->track, mi->track_len);
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -864,6 +906,19 @@ static void init_uinput(struct control *control)
 		DBG("AVRCP: uinput initialized for %s", address);
 }
 
+static void media_info_init(struct media_info *mi)
+{
+	memset(mi, 0, sizeof(*mi));
+
+	/*
+	 * As per section 5.4.1 of AVRCP 1.3 spec, return 0xFFFFFFFF if TG
+	 * does not support these attributes (i.e. they were never set via
+	 * D-Bus)
+	 */
+	mi->track_len = 0xFFFFFFFF;
+	mi->elapsed = 0xFFFFFFFF;
+}
+
 static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
 {
 	struct control *control = data;
@@ -1355,9 +1410,116 @@ static DBusMessage *mp_change_playback(DBusConnection *conn,
 	return dbus_message_new_method_return(msg);
 }
 
+static gboolean media_info_parse(DBusMessageIter *iter, struct media_info *mi)
+{
+	DBusMessageIter dict;
+	DBusMessageIter var;
+	int ctype;
+
+	ctype = dbus_message_iter_get_arg_type(iter);
+	if (ctype != DBUS_TYPE_ARRAY)
+		return FALSE;
+
+	media_info_init(mi);
+	dbus_message_iter_recurse(iter, &dict);
+
+	while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+							DBUS_TYPE_INVALID) {
+		DBusMessageIter entry;
+		const char *key;
+
+		if (ctype != DBUS_TYPE_DICT_ENTRY)
+			return FALSE;
+
+		dbus_message_iter_recurse(&dict, &entry);
+		if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
+			return FALSE;
+
+		dbus_message_iter_get_basic(&entry, &key);
+		dbus_message_iter_next(&entry);
+
+		if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
+			return FALSE;
+
+		dbus_message_iter_recurse(&entry, &var);
+
+		if (!strcmp(key, "Title")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_STRING)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->title);
+		} else if (!strcmp(key, "Artist")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_STRING)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->artist);
+		} else if (!strcmp(key, "Album")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_STRING)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->album);
+		} else if (!strcmp(key, "Genre")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_STRING)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->genre);
+		} else if (!strcmp(key, "NumberOfTracks")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_UINT32)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->ntracks);
+		} else if (!strcmp(key, "TrackNumber")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_UINT32)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->track);
+		} else if (!strcmp(key, "TrackDuration")) {
+			if (dbus_message_iter_get_arg_type(&var) !=
+							DBUS_TYPE_UINT32)
+				return FALSE;
+
+			dbus_message_iter_get_basic(&var, &mi->track_len);
+		} else {
+			return FALSE;
+		}
+
+		dbus_message_iter_next(&dict);
+	}
+
+	if (mi->title == NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
+static DBusMessage *mp_change_track(DBusConnection *conn,
+						DBusMessage *msg, void *data)
+{
+	struct audio_device *device = data;
+	struct control *control = device->control;
+	DBusMessageIter iter;
+	struct media_info mi;
+
+
+	dbus_message_iter_init(msg, &iter);
+	if (!media_info_parse(&iter, &mi))
+		return btd_error_invalid_args(msg);
+
+	mp_set_media_attributes(control, &mi);
+
+	return dbus_message_new_method_return(msg);
+}
+
 static GDBusMethodTable mp_methods[] = {
 	{ "SetProperty",	"sv",		"",	mp_set_property },
 	{ "ChangePlayback",	"su",		"",	mp_change_playback },
+	{ "ChangeTrack",	"a{sv}",	"",	mp_change_track },
 	{ }
 };
 
@@ -1451,6 +1613,7 @@ struct control *control_init(struct audio_device *dev, uint16_t uuid16,
 					MEDIA_PLAYER_INTERFACE, dev->path);
 
 		mp->timer = g_timer_new();
+		media_info_init(&mp->mi);
 		control->mp = mp;
 	}
 
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 06/22] avrcp: handle query for company ids
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (4 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 05/22] avrcp: implement ChangeTrack() method Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 07/22] avrcp: handle ListPlayerApplicationSettingAttributes pdu Lucas De Marchi
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Example of response for PTS test TC_TG_CFG_BV_02_C:

> ACL data: handle 11 flags 0x02 dlen 18
    L2CAP(d): cid 0x0043 len 14 [psm 23]
      AVCTP: Command : pt 0x00 transaction 2 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetCapabilities: pt 0x00 len 0x0001
            CapabilityID: 0x02 (CompanyID)
< ACL data: handle 11 flags 0x02 dlen 22
    L2CAP(d): cid 0x0043 len 18 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetCapabilities: pt 0x00 len 0x0005
            CapabilityID: 0x02 (CompanyID)
            CapabilityCount: 0x01
            CompanyID: 0x001958
---
 audio/control.c |   99 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index cf3bd1f..a42cafd 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -102,6 +102,21 @@
 #define FORWARD_OP		0x4b
 #define BACKWARD_OP		0x4c
 
+/* Company IDs for vendor dependent commands */
+#define IEEEID_BTSIG		0x001958
+
+/* Error codes for metadata transfer */
+#define E_INVALID_COMMAND	0x00
+#define E_INVALID_PARAM		0x01
+#define E_PARAM_NOT_FOUND	0x02
+#define E_INTERNAL		0x03
+
+/* PDU types for metadata transfer */
+#define AVRCP_GET_CAPABILITIES		0x10
+
+/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
+#define CAP_COMPANY_ID		0x02
+
 #define QUIRK_NO_RELEASE	1 << 0
 
 enum player_setting {
@@ -277,6 +292,11 @@ static struct {
 	{ NULL }
 };
 
+/* Company IDs supported by this device */
+static uint32_t company_ids[] = {
+	IEEEID_BTSIG,
+};
+
 static GSList *avctp_callbacks = NULL;
 
 static void auth_cb(DBusError *derr, void *user_data);
@@ -624,13 +644,88 @@ static void mp_set_media_attributes(struct control *control,
 			   mi->ntracks, mi->track, mi->track_len);
 }
 
+static int avrcp_handle_get_capabilities(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	unsigned int i;
+
+	if (len != 1)
+		goto err;
+
+	DBG("id=%u", pdu->params[0]);
+
+	switch (pdu->params[0]) {
+	case CAP_COMPANY_ID:
+		for (i = 0; i < G_N_ELEMENTS(company_ids); i++) {
+			pdu->params[2 + i * 3] = company_ids[i] >> 16;
+			pdu->params[3 + i * 3] = (company_ids[i] >> 8) & 0xFF;
+			pdu->params[4 + i * 3] = company_ids[i] & 0xFF;
+		}
+
+		pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
+		pdu->params[1] = G_N_ELEMENTS(company_ids);
+
+		return 2 + (3 * G_N_ELEMENTS(company_ids));
+	}
+
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+	return -EINVAL;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
 					int operand_count)
 {
-	avrcp->code = CTYPE_NOT_IMPLEMENTED;
-	return AVRCP_HEADER_LENGTH;
+	struct avrcp_spec_avc_pdu *pdu = (void *) avrcp + AVRCP_HEADER_LENGTH;
+	uint32_t company_id = (pdu->company_id[0] << 16) |
+				(pdu->company_id[1] << 8) |
+				(pdu->company_id[2]);
+	int len;
+
+	if (company_id != IEEEID_BTSIG ||
+				pdu->packet_type != AVCTP_PACKET_SINGLE) {
+		avrcp->code = CTYPE_NOT_IMPLEMENTED;
+		return AVRCP_HEADER_LENGTH;
+	}
+
+	pdu->packet_type = 0;
+	pdu->rsvd = 0;
+
+	if (operand_count + 3 < AVRCP_SPECAVCPDU_HEADER_LENGTH) {
+		pdu->params[0] = E_INVALID_COMMAND;
+		goto err_metadata;
+	}
+
+	switch (pdu->pdu_id) {
+	case AVRCP_GET_CAPABILITIES:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_get_capabilities(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
+	default:
+		/* Invalid pdu_id */
+		pdu->params[0] = E_INVALID_COMMAND;
+		goto err_metadata;
+	}
+
+	return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + len;
+
+err_metadata:
+	avrcp->code = CTYPE_REJECTED;
+	pdu->params_len = htons(1);
+
+	return AVRCP_HEADER_LENGTH + AVRCP_SPECAVCPDU_HEADER_LENGTH + 1;
 }
 
 static void avctp_disconnected(struct audio_device *dev)
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 07/22] avrcp: handle ListPlayerApplicationSettingAttributes pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (5 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 06/22] avrcp: handle query for company ids Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 08/22] avrcp: handle ListPlayerApplicationSettingValues pdu Lucas De Marchi
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Example of response obtained with PTS test TC_TG_PAS_BV_06_C:

> ACL data: handle 11 flags 0x02 dlen 17
    L2CAP(d): cid 0x0043 len 13 [psm 23]
      AVCTP: Command : pt 0x00 transaction 2 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: ListPlayerApplicationSettingAttributes: pt 0x00 len 0x0000
< ACL data: handle 11 flags 0x02 dlen 20
    L2CAP(d): cid 0x0043 len 16 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: ListPlayerApplicationSettingAttributes: pt 0x00 len 0x0003
            AttributeCount: 0x02
            AttributeID: 0x01 (Equalizer ON/OFF Status)
            AttributeID: 0x04 (Scan ON/OFF Status)
---
 audio/control.c |   53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 53 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index a42cafd..36ddde2 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -113,6 +113,7 @@
 
 /* PDU types for metadata transfer */
 #define AVRCP_GET_CAPABILITIES		0x10
+#define AVRCP_LIST_PLAYER_ATTRIBUTES	0X11
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -609,6 +610,13 @@ static void mp_set_attribute(struct media_player *mp,
 	mp->settings[attr] = val;
 }
 
+static int mp_get_attribute(struct media_player *mp, uint8_t attr)
+{
+	DBG("Get attribute: %u", attr);
+
+	return mp->settings[attr];
+}
+
 static void mp_set_media_attributes(struct control *control,
 							struct media_info *mi)
 {
@@ -674,6 +682,38 @@ err:
 	return -EINVAL;
 }
 
+static int avrcp_handle_list_player_attributes(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	struct media_player *mp = control->mp;
+	unsigned int i;
+
+	if (len != 0) {
+		pdu->params[0] = E_INVALID_PARAM;
+		return -EINVAL;
+	}
+
+	if (!mp)
+		goto done;
+
+	for (i = 1; i <= PLAYER_SETTING_SCAN; i++) {
+		if (!mp_get_attribute(mp, i)) {
+			DBG("Ignoring setting %u: not supported by player", i);
+			continue;
+		}
+
+		len++;
+		pdu->params[len] = i;
+	}
+
+done:
+	pdu->params[0] = len;
+	pdu->params_len = htons(len + 1);
+
+	return len + 1;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -713,6 +753,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_LIST_PLAYER_ATTRIBUTES:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_list_player_attributes(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 08/22] avrcp: handle ListPlayerApplicationSettingValues pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (6 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 07/22] avrcp: handle ListPlayerApplicationSettingAttributes pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 09/22] avrcp: handle GetCurrentPlayerAplicationSettingValue pdu Lucas De Marchi
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Example of response obtained with PTS test TC_TG_PAS_BV_06_C:

> ACL data: handle 11 flags 0x02 dlen 18
    L2CAP(d): cid 0x0043 len 14 [psm 23]
      AVCTP: Command : pt 0x00 transaction 3 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: ListPlayerApplicationSettingValues: pt 0x00 len 0x0001
            AttributeID: 0x01 (Equalizer ON/OFF Status)
< ACL data: handle 11 flags 0x02 dlen 20
    L2CAP(d): cid 0x0043 len 16 [psm 23]
      AVCTP: Response : pt 0x00 transaction 3 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: ListPlayerApplicationSettingValues: pt 0x00 len 0x0003
            ValueCount: 0x02
            ValueID: 0x01 (OFF)
            ValueID: 0x02 (ON)
---
 audio/control.c |   59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 59 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 36ddde2..d82409d 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -114,6 +114,7 @@
 /* PDU types for metadata transfer */
 #define AVRCP_GET_CAPABILITIES		0x10
 #define AVRCP_LIST_PLAYER_ATTRIBUTES	0X11
+#define AVRCP_LIST_PLAYER_VALUES	0x12
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -500,6 +501,22 @@ static void handle_panel_passthrough(struct control *control,
 						operands[0] & 0x7F, status);
 }
 
+static unsigned int attr_get_max_val(uint8_t attr)
+{
+	switch (attr) {
+	case PLAYER_SETTING_EQUALIZER:
+		return EQUALIZER_MODE_ON;
+	case PLAYER_SETTING_REPEAT:
+		return REPEAT_MODE_GROUP;
+	case PLAYER_SETTING_SHUFFLE:
+		return SHUFFLE_MODE_GROUP;
+	case PLAYER_SETTING_SCAN:
+		return SCAN_MODE_GROUP;
+	}
+
+	return 0;
+}
+
 static int attrval_to_val(uint8_t attr, const char *value)
 {
 	int ret;
@@ -714,6 +731,35 @@ done:
 	return len + 1;
 }
 
+static int avrcp_handle_list_player_values(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	struct media_player *mp = control->mp;
+	unsigned int i;
+
+	if (len != 1 || !mp)
+		goto err;
+
+	len = attr_get_max_val(pdu->params[0]);
+	if (!len) {
+		error("Attribute is invalid: %u", pdu->params[0]);
+		goto err;
+	}
+
+	for (i = 1; i <= len; i++)
+		pdu->params[i] = i;
+
+	pdu->params[0] = len;
+	pdu->params_len = htons(len + 1);
+
+	return len + 1;
+
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+	return -EINVAL;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -766,6 +812,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_LIST_PLAYER_VALUES:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_list_player_values(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 09/22] avrcp: handle GetCurrentPlayerAplicationSettingValue pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (7 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 08/22] avrcp: handle ListPlayerApplicationSettingValues pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 10/22] avrcp: handle SetPlayerApplicationSettingValue pdu Lucas De Marchi
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Example response for PTS test TC_TG_PAS_BV_10_C:

> ACL data: handle 11 flags 0x02 dlen 19
    L2CAP(d): cid 0x0043 len 15 [psm 23]
      AVCTP: Command : pt 0x00 transaction 3 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetCurrentPlayerApplicationSettingValue: pt 0x00 len 0x0002
            AttributeCount: 0x01
            AttributeID: 0x01 (Equalizer ON/OFF Status)
< ACL data: handle 11 flags 0x02 dlen 20
    L2CAP(d): cid 0x0043 len 16 [psm 23]
      AVCTP: Response : pt 0x00 transaction 3 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetCurrentPlayerApplicationSettingValue: pt 0x00 len 0x0003
            ValueCount: 0x01
            AttributeID: 0x01 (Equalizer ON/OFF Status)
            ValueID: 0x02 (ON)
---
 audio/control.c |   76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 76 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index d82409d..14c56ba 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -115,6 +115,7 @@
 #define AVRCP_GET_CAPABILITIES		0x10
 #define AVRCP_LIST_PLAYER_ATTRIBUTES	0X11
 #define AVRCP_LIST_PLAYER_VALUES	0x12
+#define AVRCP_GET_CURRENT_PLAYER_VALUE	0x13
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -760,6 +761,68 @@ err:
 	return -EINVAL;
 }
 
+static int avrcp_handle_get_current_player_value(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	struct media_player *mp = control->mp;
+	uint8_t *settings;
+	unsigned int i;
+
+	if (mp == NULL || len <= 1 || pdu->params[0] != len - 1)
+		goto err;
+
+	/*
+	 * Save a copy of requested settings because we can override them
+	 * while responding
+	 */
+	settings = g_malloc(pdu->params[0]);
+	memcpy(settings, &pdu->params[1], pdu->params[0]);
+	len = 0;
+
+	/*
+	 * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs
+	 * and send a response with the existent ones. Only if all IDs are
+	 * non-existent we should send an error.
+	 */
+	for (i = 0; i < pdu->params[0]; i++) {
+		uint8_t val;
+
+		if (settings[i] < PLAYER_SETTING_EQUALIZER ||
+				settings[i] > PLAYER_SETTING_SCAN) {
+			DBG("Ignoring %u", settings[i]);
+			continue;
+		}
+
+		val = mp_get_attribute(mp, settings[i]);
+		if (!val) {
+			DBG("Ignoring %u: not supported by player",
+								settings[i]);
+			continue;
+		}
+
+		pdu->params[len] = settings[i];
+		pdu->params[len + 1] = val;
+		len += 2;
+	}
+
+	g_free(settings);
+
+	if (len) {
+		pdu->params[0] = len;
+		pdu->params_len = htons(2 * len + 1);
+
+		return 2 * len + 1;
+	}
+
+	error("No valid attributes in request");
+
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+
+	return -EINVAL;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -825,6 +888,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_GET_CURRENT_PLAYER_VALUE:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_get_current_player_value(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 10/22] avrcp: handle SetPlayerApplicationSettingValue pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (8 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 09/22] avrcp: handle GetCurrentPlayerAplicationSettingValue pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 11/22] avrcp: handle commands for future extension Lucas De Marchi
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |  125 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 125 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 14c56ba..cb40490 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -116,6 +116,7 @@
 #define AVRCP_LIST_PLAYER_ATTRIBUTES	0X11
 #define AVRCP_LIST_PLAYER_VALUES	0x12
 #define AVRCP_GET_CURRENT_PLAYER_VALUE	0x13
+#define AVRCP_SET_PLAYER_VALUE		0x14
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -518,6 +519,49 @@ static unsigned int attr_get_max_val(uint8_t attr)
 	return 0;
 }
 
+static const char *attrval_to_str(uint8_t attr, uint8_t value)
+{
+	switch (attr) {
+	case PLAYER_SETTING_EQUALIZER:
+		switch (value) {
+		case EQUALIZER_MODE_ON:
+			return "on";
+		case EQUALIZER_MODE_OFF:
+			return "off";
+		}
+
+		break;
+	case PLAYER_SETTING_REPEAT:
+		switch (value) {
+		case REPEAT_MODE_OFF:
+			return "off";
+		case REPEAT_MODE_SINGLE:
+			return "singletrack";
+		case REPEAT_MODE_ALL:
+			return "alltracks";
+		case REPEAT_MODE_GROUP:
+			return "group";
+		}
+
+		break;
+	/* Shuffle and scan have the same values */
+	case PLAYER_SETTING_SHUFFLE:
+	case PLAYER_SETTING_SCAN:
+		switch (value) {
+		case SCAN_MODE_OFF:
+			return "off";
+		case SCAN_MODE_ALL:
+			return "alltracks";
+		case SCAN_MODE_GROUP:
+			return "group";
+		}
+
+		break;
+	}
+
+	return NULL;
+}
+
 static int attrval_to_val(uint8_t attr, const char *value)
 {
 	int ret;
@@ -572,6 +616,22 @@ static int attrval_to_val(uint8_t attr, const char *value)
 	return -EINVAL;
 }
 
+static const char *attr_to_str(uint8_t attr)
+{
+	switch (attr) {
+	case PLAYER_SETTING_EQUALIZER:
+		return "Equalizer";
+	case PLAYER_SETTING_REPEAT:
+		return "Repeat";
+	case PLAYER_SETTING_SHUFFLE:
+		return "Shuffle";
+	case PLAYER_SETTING_SCAN:
+		return "Scan";
+	}
+
+	return NULL;
+}
+
 static int attr_to_val(const char *str)
 {
 	if (!strcmp(str, "Equalizer"))
@@ -823,6 +883,57 @@ err:
 	return -EINVAL;
 }
 
+static int avrcp_handle_set_player_value(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	unsigned int i;
+
+	if (len < 3 || !control->mp)
+		goto err;
+
+	len = 0;
+
+	/*
+	 * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs
+	 * and set the existent ones. Sec. 5.2.4 is not clear however how to
+	 * indicate that a certain ID was not accepted. If at least one
+	 * attribute is valid, we respond with no parameters. Otherwise an
+	 * E_INVALID_PARAM is sent.
+	 */
+	for (i = 1; i < pdu->params[0]; i += 2) {
+		uint8_t attr = pdu->params[i];
+		uint8_t val = pdu->params[i + 1];
+		const char *attrstr;
+		const char *valstr;
+
+		attrstr = attr_to_str(attr);
+		if (!attrstr)
+			continue;
+
+		valstr = attrval_to_str(attr, val);
+		if (!valstr)
+			continue;
+
+		len++;
+
+		mp_set_attribute(control->mp, attr, val);
+		emit_property_changed(control->dev->conn, control->dev->path,
+					MEDIA_PLAYER_INTERFACE, attrstr,
+					DBUS_TYPE_STRING, &valstr);
+	}
+
+	if (len) {
+		pdu->params_len = 0;
+
+		return 0;
+	}
+
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+	return -EINVAL;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -901,6 +1012,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_SET_PLAYER_VALUE:
+		if (avrcp->code != CTYPE_CONTROL) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_set_player_value(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
@@ -1807,6 +1931,7 @@ static GDBusMethodTable mp_methods[] = {
 };
 
 static GDBusSignalTable mp_signals[] = {
+	{ "PropertyChanged",		"sv"	},
 	{ }
 };
 
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 11/22] avrcp: handle commands for future extension
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (9 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 10/22] avrcp: handle SetPlayerApplicationSettingValue pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 12/22] avrcp: handle InformDisplayableCharacterSet pdu Lucas De Marchi
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

AVRCP_GET_PLAYER_ATTRIBUTE_TEXT and AVRCP_GET_PLAYER_VALUE_TEXT shall
only be used if TG has extended attributes.

For the ones defined in AVRCP spec these commands should not be called.
Since we do not have extended attributes yet we can ignore those
commands.
---
 audio/control.c |   18 ++++++++++++++++++
 1 files changed, 18 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index cb40490..009cb81 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -117,6 +117,8 @@
 #define AVRCP_LIST_PLAYER_VALUES	0x12
 #define AVRCP_GET_CURRENT_PLAYER_VALUE	0x13
 #define AVRCP_SET_PLAYER_VALUE		0x14
+#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT	0x15
+#define AVRCP_GET_PLAYER_VALUE_TEXT	0x16
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -1025,6 +1027,22 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_GET_PLAYER_ATTRIBUTE_TEXT:
+	case AVRCP_GET_PLAYER_VALUE_TEXT:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		/*
+		 * As per sec. 5.2.5 of AVRCP 1.3 spec, this command is
+		 * expected to be used only for extended attributes, i.e.
+		 * custom attributes defined by the application. As we
+		 * currently don't have any such attribute, we respond with
+		 * invalid param id.
+		 */
+		pdu->params[0] = E_INVALID_PARAM;
+		goto err_metadata;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 12/22] avrcp: handle InformDisplayableCharacterSet pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (10 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 11/22] avrcp: handle commands for future extension Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 13/22] avrcp: handle InformBatteryStatusOfCT pdu Lucas De Marchi
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |   21 +++++++++++++++++++++
 1 files changed, 21 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 009cb81..6590cbb 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -119,6 +119,7 @@
 #define AVRCP_SET_PLAYER_VALUE		0x14
 #define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT	0x15
 #define AVRCP_GET_PLAYER_VALUE_TEXT	0x16
+#define AVRCP_DISPLAYABLE_CHARSET	0x17
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -1043,6 +1044,26 @@ static int handle_vendordep_pdu(struct control *control,
 		 */
 		pdu->params[0] = E_INVALID_PARAM;
 		goto err_metadata;
+	case AVRCP_DISPLAYABLE_CHARSET:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		if (pdu->params[0] < 3) {
+			pdu->params[0] = E_INVALID_PARAM;
+			goto err_metadata;
+		}
+
+		/*
+		 * We acknowledge the commands, but we always use UTF-8 for
+		 * encoding since CT is obliged to support it.
+		 */
+		pdu->params_len = 0;
+		avrcp->code = CTYPE_STABLE;
+		len = 0;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 13/22] avrcp: handle InformBatteryStatusOfCT pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (11 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 12/22] avrcp: handle InformDisplayableCharacterSet pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 14/22] avrcp: handle GetPlayStatus pdu Lucas De Marchi
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |   65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 65 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 6590cbb..9d87d0f 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -120,6 +120,7 @@
 #define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT	0x15
 #define AVRCP_GET_PLAYER_VALUE_TEXT	0x16
 #define AVRCP_DISPLAYABLE_CHARSET	0x17
+#define AVRCP_CT_BATTERY_STATUS		0x18
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -166,6 +167,14 @@ enum play_status {
 	PLAY_STATUS_ERROR =		0xFF
 };
 
+enum battery_status {
+	BATTERY_STATUS_NORMAL =		0,
+	BATTERY_STATUS_WARNING =	1,
+	BATTERY_STATUS_CRITICAL =	2,
+	BATTERY_STATUS_EXTERNAL =	3,
+	BATTERY_STATUS_FULL_CHARGE =	4,
+};
+
 static DBusConnection *connection = NULL;
 
 static GSList *servers = NULL;
@@ -667,6 +676,24 @@ static int play_status_to_val(const char *status)
 	return -EINVAL;
 }
 
+static const char *battery_status_to_str(enum battery_status status)
+{
+	switch (status) {
+	case BATTERY_STATUS_NORMAL:
+		return "normal";
+	case BATTERY_STATUS_WARNING:
+		return "warning";
+	case BATTERY_STATUS_CRITICAL:
+		return "critical";
+	case BATTERY_STATUS_EXTERNAL:
+		return "external";
+	case BATTERY_STATUS_FULL_CHARGE:
+		return "fullcharge";
+	}
+
+	return NULL;
+}
+
 static void mp_set_playback_status(struct control *control, uint8_t status,
 							uint32_t elapsed)
 {
@@ -937,6 +964,31 @@ err:
 	return -EINVAL;
 }
 
+static int avrcp_handle_ct_battery_status(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	const char *valstr;
+
+	if (len != 1)
+		goto err;
+
+	valstr = battery_status_to_str(pdu->params[0]);
+	if (valstr == NULL)
+		goto err;
+
+	emit_property_changed(control->dev->conn, control->dev->path,
+					MEDIA_PLAYER_INTERFACE, "Battery",
+					DBUS_TYPE_STRING, &valstr);
+	pdu->params_len = 0;
+
+	return 0;
+
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+	return -EINVAL;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -1064,6 +1116,19 @@ static int handle_vendordep_pdu(struct control *control,
 		len = 0;
 
 		break;
+	case AVRCP_CT_BATTERY_STATUS:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_ct_battery_status(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 14/22] avrcp: handle GetPlayStatus pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (12 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 13/22] avrcp: handle InformBatteryStatusOfCT pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 15/22] avrcp: handle RegisterNotification pdu Lucas De Marchi
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Example response for PTS test TC_TG_MDI_BV_02_C:

> ACL data: handle 11 flags 0x02 dlen 17
    L2CAP(d): cid 0x0043 len 13 [psm 23]
      AVCTP: Command : pt 0x00 transaction 2 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetPlayStatus: pt 0x00 len 0x0000
< ACL data: handle 11 flags 0x02 dlen 26
    L2CAP(d): cid 0x0043 len 22 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetPlayStatus: pt 0x00 len 0x0009
            SongLength: 0x0000a7f8 (43000 miliseconds)
            SongPosition: 0x00012fad (77741 miliconds)
            PlayStatus: 0x01 (PLAYING)
---
 audio/control.c |   71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 71 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 9d87d0f..d3cc06fa 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -121,6 +121,7 @@
 #define AVRCP_GET_PLAYER_VALUE_TEXT	0x16
 #define AVRCP_DISPLAYABLE_CHARSET	0x17
 #define AVRCP_CT_BATTERY_STATUS		0x18
+#define AVRCP_GET_PLAY_STATUS		0x30
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -694,6 +695,30 @@ static const char *battery_status_to_str(enum battery_status status)
 	return NULL;
 }
 
+static void mp_get_playback_status(struct media_player *mp, uint8_t *status,
+					uint32_t *elapsed, uint32_t *track_len)
+{
+	if (status)
+		*status = mp->status;
+	if (track_len)
+		*track_len = mp->mi.track_len;
+
+	if (!elapsed)
+		return;
+
+	*elapsed = mp->mi.elapsed;
+
+	if (mp->status == PLAY_STATUS_PLAYING) {
+		double timedelta = g_timer_elapsed(mp->timer, NULL);
+		uint32_t sec, msec;
+
+		sec = (uint32_t) timedelta;
+		msec = (uint32_t)((timedelta - sec) * 1000);
+
+		*elapsed += sec * 1000 + msec;
+	}
+}
+
 static void mp_set_playback_status(struct control *control, uint8_t status,
 							uint32_t elapsed)
 {
@@ -989,6 +1014,39 @@ err:
 	return -EINVAL;
 }
 
+static int avrcp_handle_get_play_status(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	uint32_t elapsed;
+	uint32_t track_len;
+	uint8_t status;
+
+	if (len != 0) {
+		pdu->params[0] = E_INVALID_PARAM;
+		return -EINVAL;
+	}
+
+	if (control->mp) {
+		mp_get_playback_status(control->mp, &status,
+							&elapsed, &track_len);
+		track_len = htonl(track_len);
+		elapsed = htonl(elapsed);
+	} else {
+		track_len = 0xFFFFFFFF;
+		elapsed = 0xFFFFFFFF;
+		status = PLAY_STATUS_ERROR;
+	}
+
+	memcpy(&pdu->params[0], &track_len, 4);
+	memcpy(&pdu->params[4], &elapsed, 4);
+	pdu->params[8] = status;
+
+	pdu->params_len = htons(9);
+
+	return 9;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -1129,6 +1187,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_GET_PLAY_STATUS:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_get_play_status(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 15/22] avrcp: handle RegisterNotification pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (13 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 14/22] avrcp: handle GetPlayStatus pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 16/22] avrcp: handle query for supported events Lucas De Marchi
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Handle mandatory events according to AVRCP 1.3 spec.
---
 audio/control.c |   74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 74 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index d3cc06fa..ea240d9 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -74,10 +74,12 @@
 /* ctype entries */
 #define CTYPE_CONTROL		0x0
 #define CTYPE_STATUS		0x1
+#define CTYPE_NOTIFY		0x3
 #define CTYPE_NOT_IMPLEMENTED	0x8
 #define CTYPE_ACCEPTED		0x9
 #define CTYPE_REJECTED		0xA
 #define CTYPE_STABLE		0xC
+#define CTYPE_INTERIM		0xF
 
 /* opcodes */
 #define OP_VENDORDEP		0x00
@@ -122,6 +124,11 @@
 #define AVRCP_DISPLAYABLE_CHARSET	0x17
 #define AVRCP_CT_BATTERY_STATUS		0x18
 #define AVRCP_GET_PLAY_STATUS		0x30
+#define AVRCP_REGISTER_NOTIFICATION	0x31
+
+/* Notification events */
+#define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED		0x01
+#define AVRCP_EVENT_TRACK_CHANGED			0x02
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
@@ -292,6 +299,8 @@ struct control {
 	gboolean target;
 
 	uint8_t key_quirks[256];
+
+	uint16_t registered_events;
 };
 
 static struct {
@@ -1047,6 +1056,58 @@ static int avrcp_handle_get_play_status(struct control *control,
 	return 9;
 }
 
+static int avrcp_handle_register_notification(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	uint8_t status;
+
+	/*
+	 * 1 byte for EventID, 4 bytes for Playback interval but the latest
+	 * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP
+	 * 1.3 spec, section 5.4.2.
+	 */
+	if (len != 5)
+		goto err;
+
+	switch (pdu->params[0]) {
+	case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
+		len = 2;
+		if (control->mp) {
+			mp_get_playback_status(control->mp, &status,
+								NULL, NULL);
+			pdu->params[1] = status;
+		} else {
+			pdu->params[1] = PLAY_STATUS_ERROR;
+		}
+
+		break;
+	case AVRCP_EVENT_TRACK_CHANGED:
+		len = 9;
+
+		if (!control->mp)
+			memset(&pdu->params[1], 0xFF, 8);
+		else
+			memset(&pdu->params[1], 0, 8);
+
+		break;
+	default:
+		/* All other events are not supported yet */
+		goto err;
+	}
+
+	/* Register event */
+	control->registered_events |= (1 << pdu->params[0]);
+
+	pdu->params_len = htons(len);
+
+	return len;
+
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+	return -EINVAL;
+}
+
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
 					struct avrcp_header *avrcp,
@@ -1200,6 +1261,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_REGISTER_NOTIFICATION:
+		if (avrcp->code != CTYPE_NOTIFY) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_register_notification(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_INTERIM;
+
+		break;
 	default:
 		/* Invalid pdu_id */
 		pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 16/22] avrcp: handle query for supported events
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (14 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 15/22] avrcp: handle RegisterNotification pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 17/22] avrcp: handle GetElementAttributes pdu Lucas De Marchi
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |    8 ++++++++
 1 files changed, 8 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index ea240d9..66bb55b 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -132,6 +132,7 @@
 
 /* Capabilities for AVRCP_GET_CAPABILITIES pdu */
 #define CAP_COMPANY_ID		0x02
+#define CAP_EVENTS_SUPPORTED	0x03
 
 #define QUIRK_NO_RELEASE	1 << 0
 
@@ -817,6 +818,13 @@ static int avrcp_handle_get_capabilities(struct control *control,
 		pdu->params[1] = G_N_ELEMENTS(company_ids);
 
 		return 2 + (3 * G_N_ELEMENTS(company_ids));
+	case CAP_EVENTS_SUPPORTED:
+		pdu->params_len = htons(4);
+		pdu->params[1] = 2;
+		pdu->params[2] = AVRCP_EVENT_PLAYBACK_STATUS_CHANGED;
+		pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED;
+
+		return 4;
 	}
 
 err:
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 17/22] avrcp: handle GetElementAttributes pdu
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (15 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 16/22] avrcp: handle query for supported events Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 18/22] avrcp: send response for registered events Lucas De Marchi
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Example responses for PTS test TC_TG_MDI_BV_04_C:

> ACL data: handle 11 flags 0x02 dlen 26
    L2CAP(d): cid 0x0042 len 22 [psm 23]
      AVCTP: Command : pt 0x00 transaction 2 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetElementAttributes: pt 0x00 len 0x0009
            Identifier: 0x0 (PLAYING)
            AttributeCount: 0x00
< ACL data: handle 11 flags 0x02 dlen 70
    L2CAP(d): cid 0x0042 len 66 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetElementAttributes: pt 0x00 len 0x0035
            AttributeCount: 0x03
            Attribute: 0x00000001 (Title)
            CharsetID: 0x006a (UTF-8)
            AttributeValueLength: 0x0012
            AttributeValue: Walking on the Sun
            Attribute: 0x00000002 (Artist)
            CharsetID: 0x006a (UTF-8)
            AttributeValueLength: 0x0005
            AttributeValue: Lucas
            Attribute: 0x00000007 (Progress)
            CharsetID: 0x006a (UTF-8)
            AttributeValueLength: 0x0005
            AttributeValue: 32029

And TC_TG_MDI_BV_05_C:

> ACL data: handle 11 flags 0x02 dlen 30
    L2CAP(d): cid 0x0043 len 26 [psm 23]
      AVCTP: Command : pt 0x00 transaction 2 pid 0x110e
        AV/C: Status: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetElementAttributes: pt 0x00 len 0x000d
            Identifier: 0x0 (PLAYING)
            AttributeCount: 0x01
            Attribute: 0x00000001 (Title)
< ACL data: handle 11 flags 0x02 dlen 44
    L2CAP(d): cid 0x0043 len 40 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Stable: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: GetElementAttributes: pt 0x00 len 0x001b
            AttributeCount: 0x01
            Attribute: 0x00000001 (Title)
            CharsetID: 0x006a (UTF-8)
            AttributeValueLength: 0x0012
            AttributeValue: Walking on the Sun
---
 audio/control.c |  189 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 189 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 66bb55b..c4a947c 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -123,6 +123,7 @@
 #define AVRCP_GET_PLAYER_VALUE_TEXT	0x16
 #define AVRCP_DISPLAYABLE_CHARSET	0x17
 #define AVRCP_CT_BATTERY_STATUS		0x18
+#define AVRCP_GET_ELEMENT_ATTRIBUTES	0x20
 #define AVRCP_GET_PLAY_STATUS		0x30
 #define AVRCP_REGISTER_NOTIFICATION	0x31
 
@@ -184,6 +185,16 @@ enum battery_status {
 	BATTERY_STATUS_FULL_CHARGE =	4,
 };
 
+enum media_info_id {
+	MEDIA_INFO_TITLE =		1,
+	MEDIA_INFO_ARTIST =		2,
+	MEDIA_INFO_ALBUM =		3,
+	MEDIA_INFO_TRACK =		4,
+	MEDIA_INFO_N_TRACKS =		5,
+	MEDIA_INFO_GENRE =		6,
+	MEDIA_INFO_CURRENT_POSITION =	7,
+};
+
 static DBusConnection *connection = NULL;
 
 static GSList *servers = NULL;
@@ -745,6 +756,103 @@ static void mp_set_playback_status(struct control *control, uint8_t status,
 	mp->status = status;
 }
 
+/*
+ * Copy media_info field to a buffer, intended to be used in a response to
+ * GetElementAttributes message.
+ *
+ * It assumes there's enough space in the buffer and on success it returns the
+ * size written.
+ *
+ * If @param id is not valid, -EINVAL is returned. If there's no such media
+ * attribute, -ENOENT is returned.
+ */
+static int mp_get_media_attribute(struct media_player *mp,
+						uint32_t id, uint8_t *buf)
+{
+	struct media_info_elem {
+		uint32_t id;
+		uint16_t charset;
+		uint16_t len;
+		uint8_t val[];
+	};
+	const struct media_info *mi = &mp->mi;
+	struct media_info_elem *elem = (void *)buf;
+	uint16_t len;
+	char valstr[20];
+
+	switch (id) {
+	case MEDIA_INFO_TITLE:
+		if (mi->title) {
+			len = strlen(mi->title);
+			memcpy(elem->val, mi->title, len);
+		} else {
+			len = 0;
+		}
+
+		break;
+	case MEDIA_INFO_ARTIST:
+		if (mi->artist == NULL)
+			return -ENOENT;
+
+		len = strlen(mi->artist);
+		memcpy(elem->val, mi->artist, len);
+		break;
+	case MEDIA_INFO_ALBUM:
+		if (mi->album == NULL)
+			return -ENOENT;
+
+		len = strlen(mi->album);
+		memcpy(elem->val, mi->album, len);
+		break;
+	case MEDIA_INFO_GENRE:
+		if (mi->genre == NULL)
+			return -ENOENT;
+
+		len = strlen(mi->genre);
+		memcpy(elem->val, mi->genre, len);
+		break;
+
+	case MEDIA_INFO_TRACK:
+		if (!mi->track)
+			return -ENOENT;
+
+		snprintf(valstr, 20, "%u", mi->track);
+		len = strlen(valstr);
+		memcpy(elem->val, valstr, len);
+		break;
+	case MEDIA_INFO_N_TRACKS:
+		if (!mi->ntracks)
+			return -ENOENT;
+
+		snprintf(valstr, 20, "%u", mi->ntracks);
+		len = strlen(valstr);
+		memcpy(elem->val, valstr, len);
+		break;
+	case MEDIA_INFO_CURRENT_POSITION:
+		if (mi->elapsed != 0xFFFFFFFF) {
+			uint32_t elapsed;
+
+			mp_get_playback_status(mp, NULL, &elapsed, NULL);
+
+			snprintf(valstr, 20, "%u", elapsed);
+			len = strlen(valstr);
+			memcpy(elem->val, valstr, len);
+		} else {
+			return -ENOENT;
+		}
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	elem->id = htonl(id);
+	elem->charset = htons(0x6A); /* Always use UTF-8 */
+	elem->len = htons(len);
+
+	return sizeof(struct media_info_elem) + len;
+}
+
 static void mp_set_attribute(struct media_player *mp,
 						uint8_t attr, uint8_t val)
 {
@@ -893,6 +1001,74 @@ err:
 	return -EINVAL;
 }
 
+static int avrcp_handle_get_element_attributes(struct control *control,
+						struct avrcp_spec_avc_pdu *pdu)
+{
+	uint16_t len = ntohs(pdu->params_len);
+	uint64_t *identifier = (void *) &pdu->params[0];
+	uint16_t pos;
+	uint8_t nattr;
+	int size;
+	unsigned int i;
+
+	if (len < 8 || *identifier != 0 || !control->mp)
+		goto err;
+
+	len = 0;
+	pos = 1; /* Keep track of current position in reponse */
+	nattr = pdu->params[8];
+
+	if (!control->mp)
+		goto done;
+
+	if (!nattr) {
+		/*
+		 * Return all available information, at least
+		 * title must be returned.
+		 */
+		for (i = 1; i <= MEDIA_INFO_CURRENT_POSITION; i++) {
+			size = mp_get_media_attribute(control->mp, i,
+							&pdu->params[pos]);
+
+			if (size > 0) {
+				len++;
+				pos += size;
+			}
+		}
+	} else {
+		uint32_t *attr_ids = g_malloc(sizeof(uint32_t) * nattr);
+
+		/* save a copy of requested attributes */
+		memcpy(&attr_ids[0], &pdu->params[9], nattr * 4);
+
+		for (i = 0; i < nattr; i++) {
+			uint32_t attr = ntohl(attr_ids[i]);
+
+			size = mp_get_media_attribute(control->mp, attr,
+							&pdu->params[pos]);
+
+			if (size > 0) {
+				len++;
+				pos += size;
+			}
+		}
+
+		g_free(attr_ids);
+
+		if (!len)
+			goto err;
+	}
+
+done:
+	pdu->params[0] = len;
+	pdu->params_len = htons(pos);
+
+	return pos;
+err:
+	pdu->params[0] = E_INVALID_PARAM;
+	return -EINVAL;
+}
+
 static int avrcp_handle_get_current_player_value(struct control *control,
 						struct avrcp_spec_avc_pdu *pdu)
 {
@@ -1181,6 +1357,19 @@ static int handle_vendordep_pdu(struct control *control,
 		avrcp->code = CTYPE_STABLE;
 
 		break;
+	case AVRCP_GET_ELEMENT_ATTRIBUTES:
+		if (avrcp->code != CTYPE_STATUS) {
+			pdu->params[0] = E_INVALID_COMMAND;
+			goto err_metadata;
+		}
+
+		len = avrcp_handle_get_element_attributes(control, pdu);
+		if (len < 0)
+			goto err_metadata;
+
+		avrcp->code = CTYPE_STABLE;
+
+		break;
 	case AVRCP_GET_CURRENT_PLAYER_VALUE:
 		if (avrcp->code != CTYPE_STATUS) {
 			pdu->params[0] = E_INVALID_COMMAND;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 18/22] avrcp: send response for registered events
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (16 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 17/22] avrcp: handle GetElementAttributes pdu Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-11  8:15   ` Luiz Augusto von Dentz
  2011-08-10 13:06 ` [PATCH v3 19/22] avrcp: change TG record to use version 1.3 Lucas De Marchi
                   ` (3 subsequent siblings)
  21 siblings, 1 reply; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

When a certain event occur, check if CT registered to receive that
notification and send a response.

Example event for PTS test TC_TG_NFY_BV_02_C:

> ACL data: handle 11 flags 0x02 dlen 22
    L2CAP(d): cid 0x0043 len 18 [psm 23]
      AVCTP: Command : pt 0x00 transaction 2 pid 0x110e
        AV/C: Notify: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: RegisterNotification: pt 0x00 len 0x0005
            EventID: 0x02 (EVENT_TRACK_CHANGED)
            Interval: 0x00000000 (0 seconds)
< ACL data: handle 11 flags 0x02 dlen 26
    L2CAP(d): cid 0x0043 len 22 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Interim: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: RegisterNotification: pt 0x00 len 0x0009
            EventID: 0x02 (EVENT_TRACK_CHANGED)
            Identifier: 0x0 (PLAYING)

[...]

< ACL data: handle 11 flags 0x02 dlen 26
    L2CAP(d): cid 0x0043 len 22 [psm 23]
      AVCTP: Response : pt 0x00 transaction 2 pid 0x110e
        AV/C: Changed: address 0x48 opcode 0x00
          Subunit: Panel
          Opcode: Vendor Dependent
          Company ID: 0x001958
          AVRCP: RegisterNotification: pt 0x00 len 0x0009
            EventID: 0x02 (EVENT_TRACK_CHANGED)
            Identifier: 0x0 (PLAYING)
---
 audio/control.c |   95 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 91 insertions(+), 4 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index c4a947c..44a2808 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -79,6 +79,7 @@
 #define CTYPE_ACCEPTED		0x9
 #define CTYPE_REJECTED		0xA
 #define CTYPE_STABLE		0xC
+#define CTYPE_CHANGED		0xD
 #define CTYPE_INTERIM		0xF
 
 /* opcodes */
@@ -313,6 +314,7 @@ struct control {
 	uint8_t key_quirks[256];
 
 	uint16_t registered_events;
+	uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
 };
 
 static struct {
@@ -716,6 +718,73 @@ static const char *battery_status_to_str(enum battery_status status)
 	return NULL;
 }
 
+static int avctp_send_event(struct control *control, uint8_t id, void *data)
+{
+	uint8_t buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
+					AVRCP_SPECAVCPDU_HEADER_LENGTH + 9];
+	struct avctp_header *avctp = (void *) buf;
+	struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
+	struct avrcp_spec_avc_pdu *pdu = (void *) &buf[AVCTP_HEADER_LENGTH +
+							AVRCP_HEADER_LENGTH];
+	int sk = g_io_channel_unix_get_fd(control->io);
+	uint16_t size;
+
+	memset(buf, 0, sizeof(buf));
+
+	avctp->transaction = control->transaction_events[id];
+	avctp->packet_type = AVCTP_PACKET_SINGLE;
+	avctp->cr = AVCTP_RESPONSE;
+	avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+	avrcp->code = CTYPE_CHANGED;
+	avrcp->subunit_type = SUBUNIT_PANEL;
+	avrcp->opcode = OP_VENDORDEP;
+
+	pdu->company_id[0] = IEEEID_BTSIG >> 16;
+	pdu->company_id[1] = (IEEEID_BTSIG >> 8) & 0xFF;
+	pdu->company_id[2] = IEEEID_BTSIG & 0xFF;
+
+	pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
+	pdu->params[0] = id;
+
+	DBG("id=%u", id);
+
+	switch (id) {
+	case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
+		size = 2;
+		pdu->params[1] = *((uint8_t *)data);
+
+		break;
+	case AVRCP_EVENT_TRACK_CHANGED: {
+		size = 9;
+
+		/*
+		 * AVRCP 1.3 supports only one track identifier: PLAYING
+		 * (0x0). When 1.4 version is added, this shall be changed to
+		 * contain the identifier of the track.
+		 */
+		memset(&pdu->params[1], 0, 8);
+
+		break;
+	}
+	default:
+		error("Unknown event %u", id);
+		return -EINVAL;
+	}
+
+	pdu->params_len = htons(size);
+	size += AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH +
+					AVRCP_SPECAVCPDU_HEADER_LENGTH;
+
+	if (write(sk, buf, size) < 0)
+		return -errno;
+
+	/* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
+	control->registered_events ^= 1 << id;
+
+	return 0;
+}
+
 static void mp_get_playback_status(struct media_player *mp, uint8_t *status,
 					uint32_t *elapsed, uint32_t *track_len)
 {
@@ -754,6 +823,13 @@ static void mp_set_playback_status(struct control *control, uint8_t status,
 		return;
 
 	mp->status = status;
+
+	if (control->state == AVCTP_STATE_CONNECTED &&	!control->target &&
+			(control->registered_events &
+				(1 << AVRCP_EVENT_PLAYBACK_STATUS_CHANGED))) {
+		avctp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
+								&status);
+	}
 }
 
 /*
@@ -901,6 +977,12 @@ static void mp_set_media_attributes(struct control *control,
 			   "\tTrack number: %u\n\tTrack duration: %u",
 			   mi->title, mi->artist, mi->album, mi->genre,
 			   mi->ntracks, mi->track, mi->track_len);
+
+	if (control->state == AVCTP_STATE_CONNECTED && !control->target &&
+					(control->registered_events &
+					 (1 << AVRCP_EVENT_TRACK_CHANGED))) {
+		avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
+	}
 }
 
 static int avrcp_handle_get_capabilities(struct control *control,
@@ -1241,7 +1323,8 @@ static int avrcp_handle_get_play_status(struct control *control,
 }
 
 static int avrcp_handle_register_notification(struct control *control,
-						struct avrcp_spec_avc_pdu *pdu)
+						struct avrcp_spec_avc_pdu *pdu,
+						uint8_t transaction)
 {
 	uint16_t len = ntohs(pdu->params_len);
 	uint8_t status;
@@ -1280,8 +1363,9 @@ static int avrcp_handle_register_notification(struct control *control,
 		goto err;
 	}
 
-	/* Register event */
+	/* Register event and save the transaction used */
 	control->registered_events |= (1 << pdu->params[0]);
+	control->transaction_events[pdu->params[0]] = transaction;
 
 	pdu->params_len = htons(len);
 
@@ -1294,6 +1378,7 @@ err:
 
 /* handle vendordep pdu inside an avctp packet */
 static int handle_vendordep_pdu(struct control *control,
+					struct avctp_header *avctp,
 					struct avrcp_header *avrcp,
 					int operand_count)
 {
@@ -1464,7 +1549,8 @@ static int handle_vendordep_pdu(struct control *control,
 			goto err_metadata;
 		}
 
-		len = avrcp_handle_register_notification(control, pdu);
+		len = avrcp_handle_register_notification(control, pdu,
+							avctp->transaction);
 		if (len < 0)
 			goto err_metadata;
 
@@ -1662,7 +1748,8 @@ static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
 		int r_size;
 		operand_count -= 3;
 		avctp->cr = AVCTP_RESPONSE;
-		r_size = handle_vendordep_pdu(control, avrcp, operand_count);
+		r_size = handle_vendordep_pdu(control, avctp, avrcp,
+								operand_count);
 		packet_size = AVCTP_HEADER_LENGTH + r_size;
 	} else {
 		avctp->cr = AVCTP_RESPONSE;
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 19/22] avrcp: change TG record to use version 1.3
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (17 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 18/22] avrcp: send response for registered events Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 20/22] avrcp: update copyright Lucas De Marchi
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 44a2808..d0c44fd 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -414,7 +414,7 @@ static sdp_record_t *avrcp_tg_record(void)
 	sdp_record_t *record;
 	sdp_data_t *psm, *version, *features;
 	uint16_t lp = AVCTP_PSM;
-	uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f;
+	uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;
 
 	record = sdp_record_alloc();
 	if (!record)
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 20/22] avrcp: update copyright
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (18 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 19/22] avrcp: change TG record to use version 1.3 Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 21/22] Add script to test MediaPlayer interface Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 22/22] Update Control documentation Lucas De Marchi
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 audio/control.c |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index d0c44fd..ef73980 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -4,6 +4,7 @@
  *
  *  Copyright (C) 2006-2010  Nokia Corporation
  *  Copyright (C) 2004-2010  Marcel Holtmann <marcel@holtmann.org>
+ *  Copyright (C) 2011  Texas Instruments, Inc.
  *
  *
  *  This program is free software; you can redistribute it and/or modify
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 21/22] Add script to test MediaPlayer interface
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (19 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 20/22] avrcp: update copyright Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  2011-08-10 13:06 ` [PATCH v3 22/22] Update Control documentation Lucas De Marchi
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

---
 test/test-media-player |  108 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 108 insertions(+), 0 deletions(-)
 create mode 100755 test/test-media-player

diff --git a/test/test-media-player b/test/test-media-player
new file mode 100755
index 0000000..712cf31
--- /dev/null
+++ b/test/test-media-player
@@ -0,0 +1,108 @@
+#!/usr/bin/python
+
+import sys
+import dbus
+from optparse import OptionParser, make_option
+
+USAGE = "Usage: %prog [options] <command> [args]"
+COMMANDS = """
+Commands:
+    changetrack <bdaddr> <key> <value> [<key> <value> ...]
+
+    changeplayback <bdaddr> status elapsed-time
+        status: one of playing, stopped, paused, forward-seek, reverse-seek
+                or error.
+        elapsed-time: in milliseconds
+
+    setproperty <bdaddr> property value
+        setting: one of Equalizer, Repeat, Shuffle or Scan
+        value: value correspondent to the setting specified
+"""
+
+class MyParser(OptionParser):
+    def format_epilog(self, formatter):
+        return self.epilog
+
+bus = dbus.SystemBus()
+
+manager = dbus.Interface(bus.get_object("org.bluez", "/"), "org.bluez.Manager")
+
+option_list = [
+		make_option("-i", "--device", action="store",
+				type="string", dest="dev_id"),
+		]
+parser = MyParser(option_list=option_list, usage=USAGE, epilog=COMMANDS)
+
+(options, args) = parser.parse_args()
+
+if len(args) < 2:
+    parser.print_help()
+    sys.exit(1)
+
+if options.dev_id:
+	adapter_path = manager.FindAdapter(options.dev_id)
+else:
+	adapter_path = manager.DefaultAdapter()
+
+adapter = dbus.Interface(bus.get_object("org.bluez", adapter_path),
+							"org.bluez.Adapter")
+
+device = adapter.FindDevice(args[1])
+control = dbus.Interface(bus.get_object("org.bluez", device),
+                                "org.bluez.Control")
+mp = dbus.Interface(bus.get_object("org.bluez", device),
+                                "org.bluez.MediaPlayer")
+
+def handle_change_track(mp, args):
+    if len(args) % 2 != 0:
+        print("Don't know how to handle odd number of parameters")
+        print(USAGE)
+        sys.exit(1)
+
+    d = dict()
+    for i in range(2, len(args), 2):
+        key = args[i]
+        if key == "Title" or key == "Artist" or key == "Album" \
+                                            or key == "Genre":
+            d[key] = dbus.String(args[i + 1].decode(sys.stdin.encoding))
+        elif key == "NumberOfTracks" or key == "TrackNumber"       \
+                                            or key == "TrackDuration":
+            d[key] = dbus.UInt32(int(args[i + 1]))
+        else:
+            print("Unknown metadata: %s" % key)
+            sys.exit(1)
+
+    d = dbus.Dictionary(d, signature='sv')
+    mp.ChangeTrack(d)
+
+def handle_change_playback(mp, args):
+    if len(args) != 4:
+        print(USAGE)
+        sys.exit(1)
+
+    status = dbus.String(args[2])
+    elapsed = dbus.UInt32(long(args[3]))
+
+    mp.ChangePlayback(status, elapsed)
+
+def handle_set_property(mp, args):
+    if len(args) != 4:
+        print(USAGE)
+        sys.exit(1)
+
+    prop = dbus.String(args[2])
+    value = dbus.String(args[3])
+
+    mp.SetProperty(prop, value)
+
+
+handlers = { 'changetrack': handle_change_track,
+             'changeplayback': handle_change_playback,
+             'setproperty': handle_set_property }
+
+if not args[0] in handlers:
+    print("Unknown command -- %s" % argv[1])
+    print(USAGE)
+    sys.exit(1)
+
+handlers[args[0]](mp, args)
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH v3 22/22] Update Control documentation
  2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
                   ` (20 preceding siblings ...)
  2011-08-10 13:06 ` [PATCH v3 21/22] Add script to test MediaPlayer interface Lucas De Marchi
@ 2011-08-10 13:06 ` Lucas De Marchi
  21 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-10 13:06 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: Lucas De Marchi

Move methods and signal to an experimental interface named MediaPlayer
as currently implemented.
---
 doc/control-api.txt |   94 +++++++++++++++++++++++++-------------------------
 1 files changed, 47 insertions(+), 47 deletions(-)

diff --git a/doc/control-api.txt b/doc/control-api.txt
index 1a42846..a7e5cbb 100644
--- a/doc/control-api.txt
+++ b/doc/control-api.txt
@@ -45,44 +45,6 @@ Methods		void Connect()
 			Metadata or Events defined in the AVRCP+Metadata
 			specification.
 
-		void ChangePlayback(string status, uint32 elapsed)
-
-			The status can be "playing", "stopped", "paused",
-			"forward-seek", "reverse-seek" or "error". Elapsed is
-			the position within the track in milliseconds.
-
-		void ChangeTrack(dict metadata)
-
-			Called to send the mandated TrackChange event and
-			potential metadata information.
-
-			Current defined metadata information are represented
-			with the following keys:
-
-				Title		string	(mandatory)
-				Artist		string
-				Album		string
-				Genre		string
-				NumberOfTracks	uint32
-				TrackNumber	uint32
-				TrackDuration	uint32	(in milliseconds)
-
-		void ChangeSetting(string setting, variant value)
-
-			Called to transmit Application Settings, CT Status
-			and the like.
-
-			Currenet defined settings are represented with the
-			following keys:
-
-				Equalizer	off, on
-				Repeat		off, singletrack, alltracks, group
-				Shuffle		off, alltracks, group
-				Scan		off, alltracks, group
-				Battery		normal, warning, critical, external, fullcharge
-				System		powered, unpowered, unplugged
-				Volume		uint8
-
 Signals		Connected()
 
 			Sent when a successful AVRCP connection has been made
@@ -112,15 +74,6 @@ Signals		Connected()
 			connected device (except for Metadata defined in
 			Bluetooth SIG AVRCP+Metadata spec).
 
-		TrackChanged(dict metadata)
-
-			Called when Metadata is received from connected device.
-			May be multiple meta attribute/element pairs.
-
-		PlaybackChanged(string status, uint32 elapsed)
-
-		SettingChanged(string setting, variant value)
-
 Properties	uint8 SubUnitID [readonly]
 
 			The three-bit Subunit ID from the connected device.
@@ -140,3 +93,50 @@ Properties	uint8 SubUnitID [readonly]
 		array{string} Capabilities [readonly]
 
 			List of Capabilities provided by the connected device.
+
+
+MediaPlayer hierarchy  [experimental]
+=====================
+
+Service		org.bluez
+Interface	org.bluez.MediaPlayer
+Object path	[variable prefix]/{hci0,hci1,...}/dev_XX_XX_XX_XX_XX_XX
+
+Methods		void ChangePlayback(string status, uint32 elapsed)
+
+			The status can be "playing", "stopped", "paused",
+			"forward-seek", "reverse-seek" or "error". Elapsed is
+			the position within the track in milliseconds.
+
+		void ChangeTrack(dict metadata)
+
+			Called to send the mandated TrackChange event and
+			potential metadata information.
+
+			Current defined metadata information are represented
+			with the following keys:
+
+				Title		string	(mandatory)
+				Artist		string
+				Album		string
+				Genre		string
+				NumberOfTracks	uint32
+				TrackNumber	uint32
+				TrackDuration	uint32	(in milliseconds)
+
+		void SetProperty(string property, variant value)
+
+			Called to set the media-player's properties
+
+			Current defined properties are represented with the
+			following keys and values:
+
+				Equalizer	off, on
+				Repeat		off, singletrack, alltracks, group
+				Shuffle		off, alltracks, group
+				Scan		off, alltracks, group
+
+Signals		PropertyChanged(string setting, variant value)
+
+			Called when one of the settings are changed by the
+			remote device or to inform its battery status.
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH v3 18/22] avrcp: send response for registered events
  2011-08-10 13:06 ` [PATCH v3 18/22] avrcp: send response for registered events Lucas De Marchi
@ 2011-08-11  8:15   ` Luiz Augusto von Dentz
  2011-08-11 11:23     ` Lucas De Marchi
  0 siblings, 1 reply; 25+ messages in thread
From: Luiz Augusto von Dentz @ 2011-08-11  8:15 UTC (permalink / raw)
  To: Lucas De Marchi; +Cc: linux-bluetooth

Hi Lucas,

On Wed, Aug 10, 2011 at 4:06 PM, Lucas De Marchi
<lucas.demarchi@profusion.mobi> wrote:
>  static void mp_get_playback_status(struct media_player *mp, uint8_t *status,
>                                        uint32_t *elapsed, uint32_t *track_len)
>  {
> @@ -754,6 +823,13 @@ static void mp_set_playback_status(struct control *control, uint8_t status,
>                return;
>
>        mp->status = status;
> +
> +       if (control->state == AVCTP_STATE_CONNECTED &&  !control->target &&
> +                       (control->registered_events &
> +                               (1 << AVRCP_EVENT_PLAYBACK_STATUS_CHANGED))) {
> +               avctp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
> +                                                               &status);
> +       }

Are you sure it would be valid to call mp_set_playback_status when
acting as a controller? Im afraid we should not cache anything, or
perhaps not even register MediaPlayer interface in case the device is
a target.

>
>  /*
> @@ -901,6 +977,12 @@ static void mp_set_media_attributes(struct control *control,
>                           "\tTrack number: %u\n\tTrack duration: %u",
>                           mi->title, mi->artist, mi->album, mi->genre,
>                           mi->ntracks, mi->track, mi->track_len);
> +
> +       if (control->state == AVCTP_STATE_CONNECTED && !control->target &&
> +                                       (control->registered_events &
> +                                        (1 << AVRCP_EVENT_TRACK_CHANGED))) {
> +               avctp_send_event(control, AVRCP_EVENT_TRACK_CHANGED, NULL);
> +       }

Same here.


-- 
Luiz Augusto von Dentz

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH v3 18/22] avrcp: send response for registered events
  2011-08-11  8:15   ` Luiz Augusto von Dentz
@ 2011-08-11 11:23     ` Lucas De Marchi
  0 siblings, 0 replies; 25+ messages in thread
From: Lucas De Marchi @ 2011-08-11 11:23 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hi Luiz,

On Thu, Aug 11, 2011 at 5:15 AM, Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
> Hi Lucas,
>
> On Wed, Aug 10, 2011 at 4:06 PM, Lucas De Marchi
> <lucas.demarchi@profusion.mobi> wrote:
>>  static void mp_get_playback_status(struct media_player *mp, uint8_t *status,
>>                                        uint32_t *elapsed, uint32_t *track_len)
>>  {
>> @@ -754,6 +823,13 @@ static void mp_set_playback_status(struct control *control, uint8_t status,
>>                return;
>>
>>        mp->status = status;
>> +
>> +       if (control->state == AVCTP_STATE_CONNECTED &&  !control->target &&
>> +                       (control->registered_events &
>> +                               (1 << AVRCP_EVENT_PLAYBACK_STATUS_CHANGED))) {
>> +               avctp_send_event(control, AVRCP_EVENT_PLAYBACK_STATUS_CHANGED,
>> +                                                               &status);
>> +       }
>
> Are you sure it would be valid to call mp_set_playback_status when
> acting as a controller? Im afraid we should not cache anything, or

No it's not valid. But in all cases it's implemented in this patch
we're actually acting as target, not controller. The control->target
fields refers to the remote device.

> perhaps not even register MediaPlayer interface in case the device is
> a target.

We can't since MediaPlayer accepts commands before it's connected.

Lucas De Marchi

^ permalink raw reply	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2011-08-11 11:23 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-08-10 13:06 [PATCH v3 00/22] Implement AVRCP 1.3 for TG role Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 01/22] audio: move interface declarations to their headers Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 02/22] avrcp: add skeleton of MediaPlayer interface Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 03/22] avrcp: implement SetProperty() method of MediaPlayer Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 04/22] avrcp: implement ChangePlayback() method Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 05/22] avrcp: implement ChangeTrack() method Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 06/22] avrcp: handle query for company ids Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 07/22] avrcp: handle ListPlayerApplicationSettingAttributes pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 08/22] avrcp: handle ListPlayerApplicationSettingValues pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 09/22] avrcp: handle GetCurrentPlayerAplicationSettingValue pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 10/22] avrcp: handle SetPlayerApplicationSettingValue pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 11/22] avrcp: handle commands for future extension Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 12/22] avrcp: handle InformDisplayableCharacterSet pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 13/22] avrcp: handle InformBatteryStatusOfCT pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 14/22] avrcp: handle GetPlayStatus pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 15/22] avrcp: handle RegisterNotification pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 16/22] avrcp: handle query for supported events Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 17/22] avrcp: handle GetElementAttributes pdu Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 18/22] avrcp: send response for registered events Lucas De Marchi
2011-08-11  8:15   ` Luiz Augusto von Dentz
2011-08-11 11:23     ` Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 19/22] avrcp: change TG record to use version 1.3 Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 20/22] avrcp: update copyright Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 21/22] Add script to test MediaPlayer interface Lucas De Marchi
2011-08-10 13:06 ` [PATCH v3 22/22] Update Control documentation Lucas De Marchi

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.