modules/mpris: Bugfixes and restructuring

- The 'update_status_from_message()' function now properly parses all
  changed properties, instead of only the first one.
- Implemented support for the 'Seekd' signal
This commit is contained in:
haruInDisguise 2024-08-25 20:46:17 +02:00
parent 63199c1378
commit eeeb33aee1

View file

@ -27,10 +27,8 @@
#include "../log.h" #include "../log.h"
#include "../plugin.h" #include "../plugin.h"
/* DBus specific */ #define MPRIS_QUERY_TIMEOUT 50
/* TODO: Turn this into a config value, since some mpris client take a lot #define MPRIS_LISTENER_TIMEOUT 100
* longer to respond than others (looking at you spotifyd) */
#define MPRIS_QUERY_TIMEOUT 100
#define MPRIS_PATH "/org/mpris/MediaPlayer2" #define MPRIS_PATH "/org/mpris/MediaPlayer2"
#define MPRIS_BUS_NAME "org.mpris.MediaPlayer2" #define MPRIS_BUS_NAME "org.mpris.MediaPlayer2"
@ -42,6 +40,7 @@ enum mpris_status {
MPRIS_STATUS_OFFLINE, MPRIS_STATUS_OFFLINE,
MPRIS_STATUS_PLAYING, MPRIS_STATUS_PLAYING,
MPRIS_STATUS_PAUSED, MPRIS_STATUS_PAUSED,
MPRIS_STATUS_STOPPED,
MPRIS_STATUS_ERROR, MPRIS_STATUS_ERROR,
}; };
@ -87,28 +86,14 @@ struct mpris_listener_context {
char *bus_name_unique; char *bus_name_unique;
bool has_update; bool has_update;
bool has_target; bool has_target;
bool name_changed;
}; };
struct private struct mpris_client {
{
thrd_t refresh_thread_id;
int refresh_abort_fd;
int listener_fd;
bool has_seeked_support; bool has_seeked_support;
enum mpris_status status; enum mpris_status status;
struct particle *label;
/* TODO: This should be an array of options */
const char *identity;
char *bus_name; char *bus_name;
DBusConnection *connection;
uint64_t previous_position_usec;
struct mpris_property property; struct mpris_property property;
uint32_t property_set_map;
uint32_t metadata_set_map;
struct { struct {
uint64_t value_usec; uint64_t value_usec;
@ -116,6 +101,21 @@ struct private
} elapsed; } elapsed;
}; };
struct private
{
uint32_t query_timeout_ms;
thrd_t refresh_thread_id;
int refresh_abort_fd;
int listener_fd;
DBusConnection *connection;
struct particle *label;
/* TODO: This should be an array of options */
const char *identity;
struct mpris_client client;
};
static bool static bool
mpris_validate_bus_name(const char *identity, const char *name) mpris_validate_bus_name(const char *identity, const char *name)
{ {
@ -135,11 +135,11 @@ mpris_validate_bus_name(const char *identity, const char *name)
} }
static DBusMessage * static DBusMessage *
mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message) mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message, uint32_t timeout_ms)
{ {
DBusPendingCall *pending = NULL; DBusPendingCall *pending = NULL;
if (!dbus_connection_send_with_reply(connection, message, &pending, MPRIS_QUERY_TIMEOUT)) { if (!dbus_connection_send_with_reply(connection, message, &pending, timeout_ms)) {
LOG_ERR("dbus: error: failed to allocate message object"); LOG_ERR("dbus: error: failed to allocate message object");
return NULL; return NULL;
} }
@ -173,7 +173,7 @@ mpris_find_bus_name(DBusConnection *connection, const char *identity)
DBusMessage *message DBusMessage *message
= dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "ListNames"); = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "ListNames");
DBusMessage *reply = mpris_call_method_and_block(connection, message); DBusMessage *reply = mpris_call_method_and_block(connection, message, MPRIS_QUERY_TIMEOUT);
if (reply == NULL) { if (reply == NULL) {
return NULL; return NULL;
@ -223,7 +223,7 @@ mpris_get_unique_name(DBusConnection *connection, const char *bus_name)
DBusMessage *message DBusMessage *message
= dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner"); = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS, "GetNameOwner");
dbus_message_append_args(message, DBUS_TYPE_STRING, &bus_name, DBUS_TYPE_INVALID); dbus_message_append_args(message, DBUS_TYPE_STRING, &bus_name, DBUS_TYPE_INVALID);
DBusMessage *reply = mpris_call_method_and_block(connection, message); DBusMessage *reply = mpris_call_method_and_block(connection, message, MPRIS_QUERY_TIMEOUT);
if (dbus_message_is_error(reply, DBUS_ERROR_NAME_HAS_NO_OWNER)) { if (dbus_message_is_error(reply, DBUS_ERROR_NAME_HAS_NO_OWNER)) {
LOG_ERR("Bus name has no owner: %s", bus_name); LOG_ERR("Bus name has no owner: %s", bus_name);
@ -242,22 +242,23 @@ mpris_metadata_parse_property(const char *property_name, DBusMessageIter *iter,
{ {
const char *string_value = NULL; const char *string_value = NULL;
DBusMessageIter array_iter = {0}; DBusMessageIter array_iter = {0};
__attribute__((unused)) uint32_t type = dbus_message_iter_get_arg_type(iter);
if (strcmp(property_name, "mpris:trackid") == 0) { if (strcmp(property_name, "mpris:trackid") == 0) {
assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_OBJECT_PATH); assert(type == DBUS_TYPE_OBJECT_PATH || type == DBUS_TYPE_STRING);
dbus_message_iter_get_basic(iter, &string_value); dbus_message_iter_get_basic(iter, &string_value);
buffer->trackid = strdup(string_value); buffer->trackid = strdup(string_value);
} else if (strcmp(property_name, "xesam:album") == 0) { } else if (strcmp(property_name, "xesam:album") == 0) {
assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING); assert(type == DBUS_TYPE_STRING);
dbus_message_iter_get_basic(iter, &string_value); dbus_message_iter_get_basic(iter, &string_value);
buffer->album = strdup(string_value); buffer->album = strdup(string_value);
} else if (strcmp(property_name, "xesam:artist") == 0) { } else if (strcmp(property_name, "xesam:artist") == 0) {
/* TODO: Propertly format string arrays */ /* TODO: Properly format string arrays */
/* NOTE: Currently, only the first artist will be shown, as we /* NOTE: Currently, only the first artist will be shown, as we
* ignore the rest */ * ignore the rest */
assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY); assert(type == DBUS_TYPE_ARRAY);
dbus_message_iter_recurse(iter, &array_iter); dbus_message_iter_recurse(iter, &array_iter);
assert(dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING); assert(dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING);
@ -265,12 +266,12 @@ mpris_metadata_parse_property(const char *property_name, DBusMessageIter *iter,
buffer->artists = strdup(string_value); buffer->artists = strdup(string_value);
} else if (strcmp(property_name, "xesam:title") == 0) { } else if (strcmp(property_name, "xesam:title") == 0) {
assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_STRING); assert(type == DBUS_TYPE_STRING);
dbus_message_iter_get_basic(iter, &string_value); dbus_message_iter_get_basic(iter, &string_value);
buffer->title = strdup(string_value); buffer->title = strdup(string_value);
} else if (strcmp(property_name, "mpris:length") == 0) { } else if (strcmp(property_name, "mpris:length") == 0) {
assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_INT64); assert(type == DBUS_TYPE_INT64 || type == DBUS_TYPE_UINT64);
dbus_message_iter_get_basic(iter, &buffer->length_usec); dbus_message_iter_get_basic(iter, &buffer->length_usec);
} else { } else {
/*LOG_DBG("Ignoring metadata property: %s", entry_name);*/ /*LOG_DBG("Ignoring metadata property: %s", entry_name);*/
@ -348,7 +349,7 @@ mpris_property_parse(struct mpris_property *prop, const char *property_name, DBu
} }
static void static void
mpris_clear(struct mpris_property *property) mpris_reset_property(struct mpris_property *property)
{ {
struct mpris_metadata *metadata = &property->metadata; struct mpris_metadata *metadata = &property->metadata;
if (metadata->album != NULL) { if (metadata->album != NULL) {
@ -364,6 +365,15 @@ mpris_clear(struct mpris_property *property)
memset(property, 0, sizeof(*property)); memset(property, 0, sizeof(*property));
} }
static void
mpris_reset_client(struct mpris_client *client)
{
if (client->bus_name != NULL)
free(client->bus_name);
memset(client, 0, sizeof(*client));
}
/* ------------- */ /* ------------- */
static void static void
@ -386,10 +396,10 @@ destroy(struct module *mod)
struct private *m = mod->private; struct private *m = mod->private;
dbus_connection_close(m->connection); dbus_connection_close(m->connection);
free((void *)m->bus_name);
free((void *)m->identity); free((void *)m->identity);
free((void *)m->client.bus_name);
mpris_clear(&m->property); mpris_reset_client(&m->client);
m->label->destroy(m->label); m->label->destroy(m->label);
@ -418,22 +428,19 @@ static struct exposable *
content(struct module *mod) content(struct module *mod)
{ {
const struct private *m = mod->private; const struct private *m = mod->private;
const struct mpris_metadata metadata = m->property.metadata; const struct mpris_client *client = &m->client;
enum mpris_status status = MPRIS_STATUS_OFFLINE; const struct mpris_metadata metadata = m->client.property.metadata;
const struct mpris_property *property = &m->client.property;
if (m->property.playback_status != NULL && strcmp(m->property.playback_status, "Playing") == 0) {
status = MPRIS_STATUS_PLAYING;
}
/* Calculate the current playback position */ /* Calculate the current playback position */
struct timespec now; struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now); clock_gettime(CLOCK_MONOTONIC, &now);
uint64_t elapsed_usec = m->elapsed.value_usec; uint64_t elapsed_usec = client->elapsed.value_usec;
uint64_t length_usec = m->property.metadata.length_usec; uint64_t length_usec = metadata.length_usec;
if (status == MPRIS_STATUS_PLAYING && length_usec > 0) { if (m->client.status == MPRIS_STATUS_PLAYING) {
elapsed_usec += timespec_diff_usec(&now, &m->elapsed.when); elapsed_usec += timespec_diff_usec(&now, &client->elapsed.when);
if (elapsed_usec > length_usec) { if (elapsed_usec > length_usec) {
LOG_DBG("dynamic update of elapsed overflowed: " LOG_DBG("dynamic update of elapsed overflowed: "
"elapsed=%" PRIu64 ", duration=%" PRIu64, "elapsed=%" PRIu64 ", duration=%" PRIu64,
@ -449,7 +456,7 @@ content(struct module *mod)
} }
char *tag_state_value = NULL; char *tag_state_value = NULL;
switch (m->status) { switch (client->status) {
case MPRIS_STATUS_ERROR: case MPRIS_STATUS_ERROR:
tag_state_value = "error"; tag_state_value = "error";
break; break;
@ -462,15 +469,18 @@ content(struct module *mod)
case MPRIS_STATUS_PAUSED: case MPRIS_STATUS_PAUSED:
tag_state_value = "paused"; tag_state_value = "paused";
break; break;
case MPRIS_STATUS_STOPPED:
tag_state_value = "stopped";
break;
} }
const char *tag_identity_value = m->identity; const char *tag_identity_value = m->identity;
const char *tag_loop_value = (m->property.loop_status == NULL) ? "" : m->property.loop_status; const char *tag_loop_value = (property->loop_status == NULL) ? "" : property->loop_status;
const char *tag_album_value = (metadata.album == NULL) ? "" : metadata.album; const char *tag_album_value = (metadata.album == NULL) ? "" : metadata.album;
const char *tag_artists_value = (metadata.album == NULL) ? "" : metadata.artists; const char *tag_artists_value = (metadata.album == NULL) ? "" : metadata.artists;
const char *tag_title_value = (metadata.album == NULL) ? "" : metadata.title; const char *tag_title_value = (metadata.album == NULL) ? "" : metadata.title;
const uint32_t tag_volume_value = (m->property.volume >= 0.995) ? 100 : 100 * m->property.volume; const uint32_t tag_volume_value = (property->volume >= 0.995) ? 100 : 100 * property->volume;
const bool tag_shuffle_value = m->property.shuffle; const bool tag_shuffle_value = property->shuffle;
struct tag_set tags = { struct tag_set tags = {
.tags = (struct tag *[]){ .tags = (struct tag *[]){
@ -485,7 +495,7 @@ content(struct module *mod)
tag_new_string(mod, "pos", tag_pos_value), tag_new_string(mod, "pos", tag_pos_value),
tag_new_string(mod, "end", tag_end_value), tag_new_string(mod, "end", tag_end_value),
tag_new_int_realtime( tag_new_int_realtime(
mod, "elapsed", 5, 0, 10, TAG_REALTIME_SECS), mod, "elapsed", elapsed_usec / 1000 / 1000, 0, length_usec / 1000 / 1000, TAG_REALTIME_SECS),
}, },
.count = 11, .count = 11,
}; };
@ -498,24 +508,25 @@ content(struct module *mod)
return exposable; return exposable;
} }
static bool __attribute__((unused)) static bool
update_status(struct module *mod) update_status(struct module *mod)
{ {
struct private *m = mod->private; struct private *m = mod->private;
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
mpris_clear(&m->property); mpris_reset_property(&m->client.property);
if (m->bus_name == NULL) { if (m->client.bus_name == NULL) {
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return true; return true;
} }
const char *interface = MPRIS_INTERFACE_PLAYER; const char *interface = MPRIS_INTERFACE_PLAYER;
DBusMessage *message = dbus_message_new_method_call(m->bus_name, MPRIS_PATH, DBUS_INTERFACE_PROPERTIES, "GetAll"); DBusMessage *message
= dbus_message_new_method_call(m->client.bus_name, MPRIS_PATH, DBUS_INTERFACE_PROPERTIES, "GetAll");
dbus_message_append_args(message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID); dbus_message_append_args(message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_INVALID);
DBusMessage *reply = mpris_call_method_and_block(m->connection, message); DBusMessage *reply = mpris_call_method_and_block(m->connection, message, m->query_timeout_ms);
if (reply == NULL) { if (reply == NULL) {
LOG_ERR("Failed to query internal state"); LOG_ERR("Failed to query internal state");
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
@ -540,18 +551,32 @@ update_status(struct module *mod)
DBusMessageIter property_iter = {0}; DBusMessageIter property_iter = {0};
dbus_message_iter_recurse(&dict_entry_iter, &property_iter); dbus_message_iter_recurse(&dict_entry_iter, &property_iter);
if (!mpris_property_parse(&m->property, property_name, &property_iter)) { if (!mpris_property_parse(&m->client.property, property_name, &property_iter)) {
LOG_ERR("Failed to parse property: %s", property_name); LOG_ERR("Failed to parse property: %s", property_name);
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return false; return false;
} }
if (strcmp(property_name, "PlaybackStatus") == 0) {
if (strcmp(m->client.property.playback_status, "Stopped") == 0) {
m->client.status = MPRIS_STATUS_STOPPED;
} else if (strcmp(m->client.property.playback_status, "Playing") == 0) {
m->client.status = MPRIS_STATUS_PLAYING;
} else if (strcmp(m->client.property.playback_status, "Paused") == 0) {
m->client.status = MPRIS_STATUS_PAUSED;
} else {
m->client.status = MPRIS_STATUS_OFFLINE;
}
}
dbus_message_iter_next(&dict_iter); dbus_message_iter_next(&dict_iter);
} }
/* Update player position, without relying on the 'Seeked' signal */ // FIXME
clock_gettime(CLOCK_MONOTONIC, &m->elapsed.when); if (!m->client.has_seeked_support) {
m->elapsed.value_usec = m->property.position_usec; clock_gettime(CLOCK_MONOTONIC, &m->client.elapsed.when);
m->client.elapsed.value_usec = m->client.property.position_usec;
}
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
@ -561,20 +586,25 @@ update_status(struct module *mod)
static bool static bool
update_status_from_message(struct module *mod, DBusMessage *message) update_status_from_message(struct module *mod, DBusMessage *message)
{ {
/* Properties.PropertiesChanged (STRING interface_name,
* ARRAY of DICT_ENTRY<STRING,VARIANT> changed_properties,
* ARRAY<STRING> invalidated_properties); */
struct private *m = mod->private; struct private *m = mod->private;
mtx_lock(&mod->lock); mtx_lock(&mod->lock);
/* Handle 'Seeked' signal */ /* Player.Seeked (UINT64 position)*/
if(strcmp(dbus_message_get_member(message), "Seeked") == 0) { if (strcmp(dbus_message_get_member(message), "Seeked") == 0) {
m->has_seeked_support = true; m->client.has_seeked_support = true;
DBusMessageIter iter = {0}; DBusMessageIter iter = {0};
dbus_message_iter_init(message, &iter); dbus_message_iter_init(message, &iter);
dbus_message_iter_get_basic(&iter, &m->client.property.position_usec);
clock_gettime(CLOCK_MONOTONIC, &m->client.elapsed.when);
m->client.elapsed.value_usec = m->client.property.position_usec;
return true;
} }
/* Handle 'PropertiesChanged' signal */ /* Properties.PropertiesChanged (STRING interface_name,
* ARRAY of DICT_ENTRY<STRING,VARIANT> changed_properties,
* ARRAY<STRING> invalidated_properties); */
assert(strcmp(dbus_message_get_member(message), "PropertiesChanged") == 0); assert(strcmp(dbus_message_get_member(message), "PropertiesChanged") == 0);
DBusMessageIter iter = {0}; DBusMessageIter iter = {0};
@ -609,31 +639,35 @@ update_status_from_message(struct module *mod, DBusMessage *message)
return true; return true;
} }
DBusMessageIter array_iter = {0}; while ((current_type = dbus_message_iter_get_arg_type(&changed_properties_iter)) != DBUS_TYPE_INVALID) {
dbus_message_iter_recurse(&changed_properties_iter, &array_iter); DBusMessageIter dict_iter = {0};
dbus_message_iter_recurse(&changed_properties_iter, &dict_iter);
while ((current_type = dbus_message_iter_get_arg_type(&array_iter)) != DBUS_TYPE_INVALID) {
const char *property_name = NULL; const char *property_name = NULL;
dbus_message_iter_get_basic(&array_iter, &property_name); dbus_message_iter_get_basic(&dict_iter, &property_name);
assert(current_type == DBUS_TYPE_STRING); assert(dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_STRING);
dbus_message_iter_next(&array_iter); dbus_message_iter_next(&dict_iter);
current_type = dbus_message_iter_get_arg_type(&array_iter); current_type = dbus_message_iter_get_arg_type(&dict_iter);
assert(current_type == DBUS_TYPE_VARIANT); assert(dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_VARIANT);
if(strcmp(property_name, "Metadata") == 0) { mpris_property_parse(&m->client.property, property_name, &dict_iter);
m->property.position_usec = 0;
if (strcmp(property_name, "PlaybackStatus") == 0) {
if (strcmp(m->client.property.playback_status, "Stopped") == 0) {
m->client.status = MPRIS_STATUS_STOPPED;
} else if (strcmp(m->client.property.playback_status, "Playing") == 0) {
m->client.status = MPRIS_STATUS_PLAYING;
} else if (strcmp(m->client.property.playback_status, "Paused") == 0) {
m->client.status = MPRIS_STATUS_PAUSED;
} else {
m->client.status = MPRIS_STATUS_OFFLINE;
}
} }
mpris_property_parse(&m->property, property_name, &array_iter); dbus_message_iter_next(&changed_properties_iter);
(void)m;
dbus_message_iter_next(&array_iter);
} }
clock_gettime(CLOCK_MONOTONIC, &m->elapsed.when);
m->elapsed.value_usec = m->property.position_usec;
mtx_unlock(&mod->lock); mtx_unlock(&mod->lock);
return true; return true;
} }
@ -658,7 +692,7 @@ listener_event_handle_name_owner_changed(DBusConnection *connection, DBusMessage
return; return;
} }
LOG_DBG("NameOwnerChanged: bus_name: '%s' old_owner: '%s' new_ower: '%s'", bus_name, old_owner, new_owner); /*LOG_DBG("listener: 'NameOwnerChanged': bus_name: '%s' old_owner: '%s' new_ower: '%s'", bus_name, old_owner, new_owner);*/
if (strcmp(bus_name, listener->bus_name_unique) != 0) { if (strcmp(bus_name, listener->bus_name_unique) != 0) {
return; return;
@ -666,12 +700,12 @@ listener_event_handle_name_owner_changed(DBusConnection *connection, DBusMessage
if (new_owner == NULL || strlen(new_owner) == 0) { if (new_owner == NULL || strlen(new_owner) == 0) {
/* Target bus has been lost */ /* Target bus has been lost */
LOG_DBG("Target bus disappeared: %s", listener->bus_name);
free(listener->bus_name_unique); free(listener->bus_name_unique);
free(listener->bus_name); free(listener->bus_name);
listener->bus_name_unique = NULL; listener->bus_name_unique = NULL;
listener->bus_name = NULL; listener->bus_name = NULL;
listener->has_target = false; listener->has_target = false;
LOG_DBG("Target bus disappeared: %s", listener->bus_name);
return; return;
} else if (old_owner == NULL || strlen(old_owner) == 0) { } else if (old_owner == NULL || strlen(old_owner) == 0) {
/* New name registered. At this point our target already /* New name registered. At this point our target already
@ -681,12 +715,11 @@ listener_event_handle_name_owner_changed(DBusConnection *connection, DBusMessage
} }
/* Name changed */ /* Name changed */
LOG_DBG("Name changed from '%s' to '%s'", old_owner, new_owner); LOG_DBG("listener: 'NameOwnerChanged': Name changed from '%s' to '%s'", old_owner, new_owner);
assert(listener->bus_name_unique != NULL); assert(listener->bus_name_unique != NULL);
free(listener->bus_name_unique); free(listener->bus_name_unique);
listener->bus_name_unique = NULL; listener->bus_name_unique = strdup(new_owner);
listener->name_changed = true;
} }
static void static void
@ -703,19 +736,19 @@ listener_event_handle_name_acquired(DBusConnection *connection, DBusMessage *mes
dbus_error_init(&error); dbus_error_init(&error);
dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID);
if (dbus_error_is_set(&error)) { if (dbus_error_is_set(&error)) {
LOG_ERR("%s", error.message); LOG_ERR("listener: 'NameAcquired': %s", error.message);
dbus_error_free(&error); dbus_error_free(&error);
} }
if (strncmp(name, MPRIS_BUS_NAME, strlen(MPRIS_BUS_NAME)) != 0) { if (strncmp(name, MPRIS_BUS_NAME, strlen(MPRIS_BUS_NAME)) != 0) {
LOG_DBG("Ignoring unrelated name: %s", name); LOG_DBG("listener: 'NameAcquired': Ignoring unrelated name: %s", name);
return; return;
} }
listener->has_target = true; listener->has_target = true;
listener->bus_name = strdup(name); listener->bus_name = strdup(name);
LOG_DBG("Found potential match: %s", name); LOG_DBG("listener: 'NameAcquired': Found potential match: %s", name);
} }
static DBusHandlerResult static DBusHandlerResult
@ -729,8 +762,9 @@ listener_filter_func(DBusConnection *connection, DBusMessage *message, void *use
const char *sender = dbus_message_get_sender(message); const char *sender = dbus_message_get_sender(message);
const char *path_name = dbus_message_get_path(message); const char *path_name = dbus_message_get_path(message);
LOG_DBG("listener: member: '%s' self: '%s' dest: '%s' sender: '%s' target: %s", member, self, destination, sender, /*LOG_DBG("listener: member: '%s' self: '%s' dest: '%s' sender: '%s' target: %s", member, self, destination,
listener->bus_name_unique); * sender,*/
/* listener->bus_name_unique);*/
/* Wait for a bus connection */ /* Wait for a bus connection */
if (listener->bus_name == NULL) { if (listener->bus_name == NULL) {
@ -741,21 +775,22 @@ listener_filter_func(DBusConnection *connection, DBusMessage *message, void *use
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
/* The bus disappeard, got an new name ... */
if (strcmp(path_name, DBUS_PATH_DBUS) == 0 && strcmp(member, "NameOwnerChanged") == 0) { if (strcmp(path_name, DBUS_PATH_DBUS) == 0 && strcmp(member, "NameOwnerChanged") == 0) {
listener_event_handle_name_owner_changed(connection, message, listener); listener_event_handle_name_owner_changed(connection, message, listener);
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
/* The messages did not originate from our targeted bus */
if (strcmp(sender, listener->bus_name_unique) != 0) { if (strcmp(sender, listener->bus_name_unique) != 0) {
LOG_DBG("Ignoring unrelated message");
return DBUS_HANDLER_RESULT_HANDLED; return DBUS_HANDLER_RESULT_HANDLED;
} }
/* Copy the 'PropertiesChanged' message, so it can be parsed LOG_DBG("listener: member: '%s' self: '%s' dest: '%s' sender: '%s' target: %s", member, self, destination, sender,
listener->bus_name_unique);
/* Copy the 'PropertiesChanged/Seeked' message, so it can be parsed
* later on */ * later on */
if (strcmp(path_name, MPRIS_PATH) == 0 && strcmp(member, "PropertiesChanged") == 0) { if (strcmp(path_name, MPRIS_PATH) == 0
&& (strcmp(member, "PropertiesChanged") == 0 || strcmp(member, "Seeked") == 0)) {
listener->update_message = dbus_message_copy(message); listener->update_message = dbus_message_copy(message);
listener->has_update = true; listener->has_update = true;
} }
@ -778,11 +813,9 @@ listener_setup(struct module *mod, struct mpris_listener_context **context)
return -1; return -1;
} }
/* NOTE: The filter function is executed in the same thread */
dbus_connection_add_filter(listener->connection, listener_filter_func, listener, NULL); dbus_connection_add_filter(listener->connection, listener_filter_func, listener, NULL);
/* Turn this connection into a monitor */ /* Turn this connection into a monitor */
/* NOTE: DBusMessage arguments must be passed as lvalues! */
DBusMessage *message DBusMessage *message
= dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor"); = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor");
DBusMessageIter args_iter = {0}, array_iter = {0}; DBusMessageIter args_iter = {0}, array_iter = {0};
@ -815,7 +848,7 @@ listener_setup(struct module *mod, struct mpris_listener_context **context)
dbus_message_iter_close_container(&args_iter, &array_iter); dbus_message_iter_close_container(&args_iter, &array_iter);
dbus_message_iter_append_basic(&args_iter, DBUS_TYPE_UINT32, &(uint32_t){0}); dbus_message_iter_append_basic(&args_iter, DBUS_TYPE_UINT32, &(uint32_t){0});
DBusMessage *reply = mpris_call_method_and_block(listener->connection, message); DBusMessage *reply = mpris_call_method_and_block(listener->connection, message, MPRIS_QUERY_TIMEOUT);
if (reply == NULL) { if (reply == NULL) {
LOG_ERR("Failed to setup monitor connection. Your dbus implementation does not support monitoring"); LOG_ERR("Failed to setup monitor connection. Your dbus implementation does not support monitoring");
@ -832,9 +865,9 @@ listener_setup(struct module *mod, struct mpris_listener_context **context)
} }
static bool static bool
listener_poll(struct mpris_listener_context *listener) listener_poll(struct mpris_listener_context *listener, uint32_t timeout_ms)
{ {
if (!dbus_connection_read_write_dispatch(listener->connection, MPRIS_QUERY_TIMEOUT)) { if (!dbus_connection_read_write_dispatch(listener->connection, timeout_ms)) {
/* Figure out what might terminate our connection (with the /* Figure out what might terminate our connection (with the
* exception of calling disconnect manually) */ * exception of calling disconnect manually) */
LOG_DBG("Listener: Disconnect signal has been processed"); LOG_DBG("Listener: Disconnect signal has been processed");
@ -949,6 +982,7 @@ run(struct module *mod)
{ {
const struct bar *bar = mod->bar; const struct bar *bar = mod->bar;
struct private *m = mod->private; struct private *m = mod->private;
struct mpris_client *client = &m->client;
/* Setup connection */ /* Setup connection */
DBusError error = {0}; DBusError error = {0};
@ -964,9 +998,9 @@ run(struct module *mod)
return -1; return -1;
} }
m->bus_name = mpris_find_bus_name(m->connection, m->identity); client->bus_name = mpris_find_bus_name(m->connection, m->identity);
listener->has_target = m->bus_name != NULL; listener->has_target = client->bus_name != NULL;
listener->bus_name = (listener->has_target) ? strdup(m->bus_name) : NULL; listener->bus_name = (listener->has_target) ? strdup(client->bus_name) : NULL;
int ret = 0; int ret = 0;
bool aborted = false; bool aborted = false;
@ -990,7 +1024,7 @@ run(struct module *mod)
/* Listen for new bus names, until we find a vaild match */ /* Listen for new bus names, until we find a vaild match */
if (!listener->has_target) { if (!listener->has_target) {
listener_poll(listener); listener_poll(listener, MPRIS_LISTENER_TIMEOUT);
if (!listener->has_target) if (!listener->has_target)
continue; continue;
@ -1003,15 +1037,14 @@ run(struct module *mod)
continue; continue;
} }
m->bus_name = strdup(listener->bus_name); client->bus_name = strdup(listener->bus_name);
} }
/* We found a match. Build an initial state by manually /* We found a match. Build an initial state by manually
* querying the connection */ * querying the client */
LOG_DBG("Found target. Performing manual update"); LOG_DBG("Found target. Performing manual update");
listener->bus_name_unique = mpris_get_unique_name(m->connection, m->bus_name);
m->status = MPRIS_STATUS_PAUSED;
update_status(mod); update_status(mod);
listener->bus_name_unique = mpris_get_unique_name(m->connection, client->bus_name);
while (ret == 0 && !aborted && listener->has_target) { while (ret == 0 && !aborted && listener->has_target) {
const uint32_t timeout_ms = 50; const uint32_t timeout_ms = 50;
@ -1032,29 +1065,19 @@ run(struct module *mod)
} }
/* Poll the listener for status updates/target changes */ /* Poll the listener for status updates/target changes */
if (!listener_poll(listener)) { if (!listener_poll(listener, MPRIS_LISTENER_TIMEOUT)) {
aborted = true; aborted = true;
break; break;
} }
/* We lost our target */ /* We lost our target */
if (!listener->has_target) { if (!listener->has_target) {
free(m->bus_name); mpris_reset_client(client);
m->bus_name = NULL;
m->status = MPRIS_STATUS_OFFLINE;
aborted = !update_status(mod);
bar->refresh(bar); bar->refresh(bar);
continue; continue;
} }
if (listener->name_changed) {
listener->bus_name_unique = mpris_get_unique_name(m->connection, listener->bus_name);
bar->refresh(bar);
continue;
}
/* Process dynamic updates, recieved through the listener/the /* Process dynamic updates, recieved through the listener/the
* 'PropertiesChanged' signal */ * 'PropertiesChanged' signal */
if (listener->has_update) { if (listener->has_update) {
@ -1066,25 +1089,33 @@ run(struct module *mod)
} }
if (aborted) { if (aborted) {
m->status = MPRIS_STATUS_OFFLINE; client->status = MPRIS_STATUS_OFFLINE;
} }
bar->refresh(bar); bar->refresh(bar);
} }
} }
mpris_reset_client(&m->client);
dbus_connection_close(m->connection);
dbus_connection_close(listener->connection);
if(listener->bus_name_unique != NULL)
free(listener->bus_name_unique);
if(listener->bus_name != NULL)
free(listener->bus_name);
free(listener); free(listener);
return ret; return ret;
} }
static struct module * static struct module *
mpris_new(const char *identity, struct particle *label) mpris_new(const char *identity, uint32_t poll, struct particle *label)
{ {
struct private *priv = calloc(1, sizeof(*priv)); struct private *priv = calloc(1, sizeof(*priv));
priv->label = label; priv->label = label;
priv->identity = strdup(identity); priv->identity = strdup(identity);
priv->status = MPRIS_STATUS_OFFLINE; priv->query_timeout_ms = poll;
struct module *mod = module_common_new(); struct module *mod = module_common_new();
mod->private = priv; mod->private = priv;
@ -1099,19 +1130,22 @@ mpris_new(const char *identity, struct particle *label)
static struct module * static struct module *
from_conf(const struct yml_node *node, struct conf_inherit inherited) from_conf(const struct yml_node *node, struct conf_inherit inherited)
{ {
const struct yml_node *identity = yml_get_value(node, "identity"); const struct yml_node *identity_node = yml_get_value(node, "identity");
const struct yml_node *c = yml_get_value(node, "content"); const struct yml_node *poll_node = yml_get_value(node, "poll");
const struct yml_node *c_node = yml_get_value(node, "content");
return mpris_new(yml_value_as_string(identity), conf_to_particle(c, inherited)); const char *identity = yml_value_as_string(identity_node);
const uint32_t poll = (poll_node != NULL) ? yml_value_as_int(poll_node) : 500;
return mpris_new(identity, poll, conf_to_particle(c_node, inherited));
} }
static bool static bool
verify_conf(keychain_t *chain, const struct yml_node *node) verify_conf(keychain_t *chain, const struct yml_node *node)
{ {
// TODO: Add the ability to display the status of the most
// recently active player. This will require a listener.
static const struct attr_info attrs[] = { static const struct attr_info attrs[] = {
{"identity", true, &conf_verify_string}, {"identity", true, &conf_verify_string},
{"poll", false, &conf_verify_unsigned},
MODULE_COMMON_ATTRS, MODULE_COMMON_ATTRS,
}; };