diff --git a/doc/control-api.txt b/doc/control-api.txt
index e0ed04a..55cebe8 100644
--- a/doc/control-api.txt
+++ b/doc/control-api.txt
Adjust remote volume one step down
+ void FastForward()
+
+ Fast forward playback, this action is only stopped
+ when another method in this interface is called.
+
+ void Rewind()
+
+ Rewind playback, this action is only stopped
+ when another method in this interface is called.
+
Properties
boolean Connected [readonly]
diff --git a/profiles/audio/avctp.c b/profiles/audio/avctp.c
index e48c257..dcffb05 100644
--- a/profiles/audio/avctp.c
+++ b/profiles/audio/avctp.c
guint process_id;
};
+struct key_pressed {
+ uint8_t op;
+ guint timer;
+};
+
struct avctp {
struct avctp_server *server;
bdaddr_t dst;
struct avctp_channel *browsing;
uint8_t key_quirks[256];
+ struct key_pressed *key;
};
struct avctp_pdu_handler {
static void auth_cb(DBusError *derr, void *user_data);
static gboolean process_queue(gpointer user_data);
+static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count, void *user_data);
static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
{
session->auth_id = 0;
}
+ if (session->key != NULL) {
+ if (session->key->timer > 0)
+ g_source_remove(session->key->timer);
+ g_free(session->key);
+ }
+
if (session->uinput >= 0) {
char address[18];
return 0;
}
+static char *op2str(uint8_t op)
+{
+ switch (op & 0x7f) {
+ case AVC_VOLUME_UP:
+ return "VOLUME UP";
+ case AVC_VOLUME_DOWN:
+ return "VOLUME DOWN";
+ case AVC_MUTE:
+ return "MUTE";
+ case AVC_PLAY:
+ return "PLAY";
+ case AVC_STOP:
+ return "STOP";
+ case AVC_PAUSE:
+ return "PAUSE";
+ case AVC_RECORD:
+ return "RECORD";
+ case AVC_REWIND:
+ return "REWIND";
+ case AVC_FAST_FORWARD:
+ return "FAST FORWARD";
+ case AVC_EJECT:
+ return "EJECT";
+ case AVC_FORWARD:
+ return "FORWARD";
+ case AVC_BACKWARD:
+ return "BACKWARD";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static int avctp_passthrough_press(struct avctp *session, uint8_t op)
+{
+ uint8_t operands[2];
+
+ DBG("%s", op2str(op));
+
+ /* Button pressed */
+ operands[0] = op & 0x7f;
+ operands[1] = 0;
+
+ return avctp_send_req(session, AVC_CTYPE_CONTROL,
+ AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
+ operands, sizeof(operands),
+ avctp_passthrough_rsp, NULL);
+}
+
+static int avctp_passthrough_release(struct avctp *session, uint8_t op)
+{
+ uint8_t operands[2];
+
+ DBG("%s", op2str(op));
+
+ /* Button released */
+ operands[0] = op | 0x80;
+ operands[1] = 0;
+
+ return avctp_send_req(session, AVC_CTYPE_CONTROL,
+ AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
+ operands, sizeof(operands),
+ NULL, NULL);
+}
+
+static gboolean repeat_timeout(gpointer user_data)
+{
+ struct avctp *session = user_data;
+ struct key_pressed *key = session->key;
+
+ avctp_passthrough_release(session, key->op);
+ avctp_passthrough_press(session, key->op);
+
+ return TRUE;
+}
+
+static void release_pressed(struct avctp *session)
+{
+ struct key_pressed *key = session->key;
+
+ avctp_passthrough_release(session, key->op);
+
+ if (key->timer > 0)
+ g_source_remove(key->timer);
+
+ g_free(key);
+ session->key = NULL;
+}
+
+static bool set_pressed(struct avctp *session, uint8_t op)
+{
+ struct key_pressed *key;
+
+ if (session->key != NULL) {
+ if (session->key->op == op)
+ return TRUE;
+ release_pressed(session);
+ }
+
+ if (op != AVC_FAST_FORWARD && op != AVC_REWIND)
+ return FALSE;
+
+ key = g_new0(struct key_pressed, 1);
+ key->op = op;
+ key->timer = g_timeout_add_seconds(2, repeat_timeout, session);
+
+ session->key = key;
+
+ return TRUE;
+}
+
static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code,
uint8_t subunit, uint8_t *operands,
size_t operand_count, void *user_data)
if (code != AVC_CTYPE_ACCEPTED)
return FALSE;
- /* Button release */
- operands[0] |= 0x80;
+ if (set_pressed(session, operands[0]))
+ return FALSE;
- avctp_send_req(session, AVC_CTYPE_CONTROL,
- AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
- operands, operand_count,
- NULL, NULL);
+ avctp_passthrough_release(session, operands[0]);
return FALSE;
}
int avctp_send_passthrough(struct avctp *session, uint8_t op)
{
- uint8_t operands[2];
-
- /* Button pressed */
- operands[0] = op & 0x7f;
- operands[1] = 0;
+ /* Auto release if key pressed */
+ if (session->key != NULL)
+ release_pressed(session);
- return avctp_send_req(session, AVC_CTYPE_CONTROL,
- AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
- operands, sizeof(operands),
- avctp_passthrough_rsp, NULL);
+ return avctp_passthrough_press(session, op);
}
int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
diff --git a/profiles/audio/control.c b/profiles/audio/control.c
index 0013f8d..7299b7e 100644
--- a/profiles/audio/control.c
+++ b/profiles/audio/control.c
return key_pressed(conn, msg, AVC_BACKWARD, data);
}
+static DBusMessage *control_fast_forward(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ return key_pressed(conn, msg, AVC_FAST_FORWARD, data);
+}
+
+static DBusMessage *control_rewind(DBusConnection *conn, DBusMessage *msg,
+ void *data)
+{
+ return key_pressed(conn, msg, AVC_REWIND, data);
+}
+
static gboolean control_property_get_connected(
const GDBusPropertyTable *property,
DBusMessageIter *iter, void *data)
{ GDBUS_METHOD("Previous", NULL, NULL, control_previous) },
{ GDBUS_METHOD("VolumeUp", NULL, NULL, control_volume_up) },
{ GDBUS_METHOD("VolumeDown", NULL, NULL, control_volume_down) },
+ { GDBUS_METHOD("FastForward", NULL, NULL, control_fast_forward) },
+ { GDBUS_METHOD("Rewind", NULL, NULL, control_rewind) },
{ }
};