diff --git a/audio/avctp.c b/audio/avctp.c
index 5161703..d98d097 100644
--- a/audio/avctp.c
+++ b/audio/avctp.c
GSList *sessions;
};
+struct avctp_rsp_handler {
+ uint8_t id;
+ avctp_rsp_cb func;
+ void *user_data;
+};
+
struct avctp {
struct avctp_server *server;
bdaddr_t dst;
uint16_t mtu;
uint8_t key_quirks[256];
+ GSList *handlers;
};
struct avctp_pdu_handler {
static GSList *callbacks = NULL;
static GSList *servers = NULL;
static GSList *handlers = NULL;
+static uint8_t id = 0;
static void auth_cb(DBusError *derr, void *user_data);
}
server->sessions = g_slist_remove(server->sessions, session);
+ g_slist_free_full(session->handlers, g_free);
g_free(session);
}
}
}
+static void handle_response(struct avctp *session, struct avctp_header *avctp,
+ struct avc_header *avc, uint8_t *operands,
+ size_t operand_count)
+{
+ GSList *l;
+
+ for (l = session->handlers; l; l = l->next) {
+ struct avctp_rsp_handler *handler = l->data;
+
+ if (handler->id != avctp->transaction)
+ continue;
+
+ if (handler->func && handler->func(session, avc->code,
+ avc->subunit_type,
+ operands, operand_count,
+ handler->user_data))
+ return;
+
+ session->handlers = g_slist_remove(session->handlers, handler);
+ g_free(handler);
+
+ return;
+ }
+}
+
static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
gpointer data)
{
avc->code, avc->subunit_type, avc->subunit_id,
avc->opcode, operand_count);
- if (avctp->cr == AVCTP_RESPONSE)
+ if (avctp->cr == AVCTP_RESPONSE) {
+ handle_response(session, avctp, avc, operands, operand_count);
return TRUE;
+ }
packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH;
avctp->cr = AVCTP_RESPONSE;
struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
int sk;
- static uint8_t transaction = 0;
if (session->state != AVCTP_STATE_CONNECTED)
return -ENOTCONN;
memset(buf, 0, sizeof(buf));
- avctp->transaction = transaction++;
+ avctp->transaction = id++;
avctp->packet_type = AVCTP_PACKET_SINGLE;
avctp->cr = AVCTP_COMMAND;
avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
return -errno;
/* Button release */
- avctp->transaction = transaction++;
+ avctp->transaction = id++;
operands[0] |= 0x80;
if (write(sk, buf, sizeof(buf)) < 0)
return 0;
}
-int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
- uint8_t code, uint8_t subunit,
+static int avctp_send(struct avctp *session, uint8_t transaction, uint8_t cr,
+ uint8_t code, uint8_t subunit, uint8_t opcode,
uint8_t *operands, size_t operand_count)
{
uint8_t *buf;
avctp->transaction = transaction;
avctp->packet_type = AVCTP_PACKET_SINGLE;
- avctp->cr = AVCTP_RESPONSE;
+ avctp->cr = cr;
avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
avc->code = code;
avc->subunit_type = subunit;
- avc->opcode = AVC_OP_VENDORDEP;
+ avc->opcode = opcode;
memcpy(pdu, operands, operand_count);
return err;
}
+int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ uint8_t *operands, size_t operand_count)
+{
+ return avctp_send(session, transaction, AVCTP_RESPONSE, code, subunit,
+ AVC_OP_VENDORDEP, operands, operand_count);
+}
+
+int avctp_send_vendordep_req(struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count,
+ avctp_rsp_cb func, void *user_data)
+{
+ struct avctp_rsp_handler *handler;
+ int err;
+
+ err = avctp_send(session, id++, AVCTP_COMMAND, code, subunit,
+ AVC_OP_VENDORDEP, operands, operand_count);
+ if (err < 0)
+ return err;
+
+ handler = g_new0(struct avctp_rsp_handler, 1);
+ handler->id = id;
+ handler->func = func;
+ handler->user_data = user_data;
+
+ session->handlers = g_slist_prepend(session->handlers, handler);
+
+ return 0;
+}
+
unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
{
struct avctp_state_callback *state_cb;
diff --git a/audio/avctp.h b/audio/avctp.h
index 9727485..d0cbd97 100644
--- a/audio/avctp.h
+++ b/audio/avctp.h
uint8_t *code, uint8_t *subunit,
uint8_t *operands, size_t operand_count,
void *user_data);
+typedef gboolean (*avctp_rsp_cb) (struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count, void *user_data);
unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data);
gboolean avctp_remove_state_cb(unsigned int id);
int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
uint8_t code, uint8_t subunit,
uint8_t *operands, size_t operand_count);
+int avctp_send_vendordep_req(struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count,
+ avctp_rsp_cb func, void *user_data);
diff --git a/audio/avrcp.c b/audio/avrcp.c
index df39d04..6ed77a3 100644
--- a/audio/avrcp.c
+++ b/audio/avrcp.c
#include <dbus/dbus.h>
#include <gdbus.h>
+#include "../src/adapter.h"
+#include "../src/device.h"
+
#include "log.h"
#include "error.h"
#include "device.h"
#define CAP_COMPANY_ID 0x02
#define CAP_EVENTS_SUPPORTED 0x03
+#define AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH 5
+
enum battery_status {
BATTERY_STATUS_NORMAL = 0,
BATTERY_STATUS_WARNING = 1,
return AVC_CTYPE_REJECTED;
}
-
static struct pdu_handler {
uint8_t pdu_id;
uint8_t code;
return NULL;
}
+static gboolean avrcp_handle_volume_changed(struct avctp *session,
+ uint8_t code, uint8_t subunit,
+ uint8_t *operands, size_t operand_count,
+ void *user_data)
+{
+ struct avrcp_player *player = user_data;
+ struct avrcp_header *pdu = (void *) operands;
+ uint8_t abs_volume = pdu->params[1] & 0x7F;
+
+ if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED)
+ return FALSE;
+
+ if (player->cb->set_volume != NULL)
+ player->cb->set_volume(abs_volume, player->dev, player->user_data);
+
+ return TRUE;
+}
+
+static void register_volume_notification(struct avrcp_player *player)
+{
+ uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH];
+ struct avrcp_header *pdu = (void *) buf;
+ uint8_t length;
+
+ memset(buf, 0, sizeof(buf));
+
+ set_company_id(pdu->company_id, IEEEID_BTSIG);
+ pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
+ pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE;
+ pdu->params[0] = AVRCP_EVENT_VOLUME_CHANGED;
+ pdu->params_len = htons(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH);
+
+ length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
+
+ avctp_send_vendordep_req(player->session, AVC_CTYPE_NOTIFY,
+ AVC_SUBUNIT_PANEL, buf, length,
+ avrcp_handle_volume_changed, player);
+}
+
static void state_changed(struct audio_device *dev, avctp_state_t old_state,
avctp_state_t new_state, void *user_data)
{
struct avrcp_server *server;
struct avrcp_player *player;
+ const sdp_record_t *rec;
+ sdp_list_t *list;
+ sdp_profile_desc_t *desc;
server = find_server(servers, &dev->src);
if (!server)
handle_vendordep_pdu,
player);
break;
+ case AVCTP_STATE_CONNECTED:
+ rec = btd_device_get_record(dev->btd_dev, AVRCP_TARGET_UUID);
+ if (rec == NULL)
+ return;
+
+ if (sdp_get_profile_descs(rec, &list) < 0)
+ return;
+
+ desc = list->data;
+
+ if (desc && desc->version >= 0x0104)
+ register_volume_notification(player);
+
+ sdp_list_free(list, free);
default:
return;
}
diff --git a/audio/avrcp.h b/audio/avrcp.h
index 9aef081..b520ef6 100644
--- a/audio/avrcp.h
+++ b/audio/avrcp.h
GList *(*list_metadata) (void *user_data);
uint8_t (*get_status) (void *user_data);
uint32_t (*get_position) (void *user_data);
+ void (*set_volume) (uint8_t volume, struct audio_device *dev,
+ void *user_data);
};
int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config);
diff --git a/audio/media.c b/audio/media.c
index 3fe04d5..427087a 100644
--- a/audio/media.c
+++ b/audio/media.c
guint track_watch;
uint8_t status;
uint32_t position;
+ uint8_t volume;
GTimer *timer;
};
return mp->position + sec * 1000 + msec;
}
+static void set_volume(uint8_t volume, struct audio_device *dev, void *user_data)
+{
+ struct media_player *mp = user_data;
+ GSList *l;
+
+ if (mp->volume == volume)
+ return;
+
+ mp->volume = volume;
+
+ for (l = mp->adapter->endpoints; l; l = l->next) {
+
+ struct media_endpoint *endpoint;
+ struct media_transport *transport;
+
+ if (l->data == NULL)
+ continue;
+
+ endpoint = l->data;
+ transport = find_device_transport(endpoint, dev);
+
+ if (transport == NULL)
+ continue;
+
+ media_transport_update_volume(transport, volume);
+ }
+}
+
static struct avrcp_player_cb player_cb = {
.get_setting = get_setting,
.set_setting = set_setting,
.get_uid = get_uid,
.get_metadata = get_metadata,
.get_position = get_position,
- .get_status = get_status
+ .get_status = get_status,
+ .set_volume = set_volume
};
static void media_player_exit(DBusConnection *connection, void *user_data)
diff --git a/audio/transport.c b/audio/transport.c
index 753d4bf..4ad8608 100644
--- a/audio/transport.c
+++ b/audio/transport.c
uint16_t omtu; /* Transport output mtu */
uint16_t delay; /* Transport delay (a2dp only) */
unsigned int nrec_id; /* Transport nrec watch (headset only) */
+ uint8_t volume; /* Transport volume */
gboolean read_lock;
gboolean write_lock;
gboolean in_use;
{
return transport->device;
}
+
+void media_transport_update_volume(struct media_transport *transport,
+ uint8_t volume)
+{
+ /* Check if volume really changed */
+ if (transport->volume == volume)
+ return;
+
+ transport->volume = volume;
+
+ emit_property_changed(transport->conn, transport->path,
+ MEDIA_TRANSPORT_INTERFACE, "Volume",
+ DBUS_TYPE_BYTE, &transport->volume);
+}
diff --git a/audio/transport.h b/audio/transport.h
index 1f86cde..d20c327 100644
--- a/audio/transport.h
+++ b/audio/transport.h
struct audio_device *media_transport_get_dev(struct media_transport *transport);
void media_transport_update_delay(struct media_transport *transport,
uint16_t delay);
+void media_transport_update_volume(struct media_transport *transport,
+ uint8_t volume);
void transport_get_properties(struct media_transport *transport,
DBusMessageIter *iter);