From b55d5a67392c6b47d940723f97b821bd0e7d33a0 Mon Sep 17 00:00:00 2001 From: haruInDisguise Date: Mon, 16 Sep 2024 17:44:59 +0200 Subject: [PATCH] modules/mpris: Moved code over to 'sd-bus' - Ported d-bus specific code from 'libdbus' to 'sd-bus' - 'sd_bus' has been added to mesons dependencies. If systemd (>=221) is not installed, we fall back to [basu](https://git.sr.ht/~emersion/basu) v2.1 - Fixed a memory leak upon metadata change --- meson.build | 9 +- modules/meson.build | 4 +- modules/mpris.c | 755 ++++++++++++++++++++++-------------------- subprojects/basu.wrap | 3 + 4 files changed, 412 insertions(+), 359 deletions(-) create mode 100644 subprojects/basu.wrap diff --git a/meson.build b/meson.build index 84ab374..e16b7d6 100644 --- a/meson.build +++ b/meson.build @@ -4,13 +4,15 @@ project('yambar', 'c', meson_version: '>=0.59.0', default_options: ['c_std=c18', 'warning_level=1', - 'werror=true', 'b_ndebug=if-release']) is_debug_build = get_option('buildtype').startswith('debug') plugs_as_libs = get_option('core-plugins-as-shared-libraries') cc = meson.get_compiler('c') +cc_flags = [ + '-Werror=all' + ] if cc.has_function('memfd_create', args: ['-D_GNU_SOURCE=200809L'], @@ -75,7 +77,12 @@ backend_wayland = wayland_client.found() and wayland_cursor.found() tllist = dependency('tllist', version: '>=1.0.1', fallback: 'tllist') fcft = dependency('fcft', version: ['>=3.0.0', '<4.0.0'], fallback: 'fcft') +# DBus dependency. Used by 'modules/mpris' +dbus = dependency('libsystemd', version: '>=221', fallback: 'basu') +# dbus = dependency('basu') + add_project_arguments( + cc_flags + ['-D_GNU_SOURCE'] + (is_debug_build ? ['-D_DEBUG'] : []) + (backend_x11 ? ['-DENABLE_X11'] : []) + diff --git a/modules/meson.build b/modules/meson.build index a225d05..c80990a 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -22,8 +22,8 @@ plugin_mem_enabled = get_option('plugin-mem').allowed() mpd = dependency('libmpdclient', required: get_option('plugin-mpd')) plugin_mpd_enabled = mpd.found() -mpris = dependency('dbus-1', required: get_option('plugin-mpris')) -plugin_mpris_enabled = mpris.found() +mpris = dbus +plugin_mpris_enabled = dbus.found() json_i3 = dependency('json-c', required: get_option('plugin-i3')) plugin_i3_enabled = json_i3.found() diff --git a/modules/mpris.c b/modules/mpris.c index f903b50..bad1bf1 100644 --- a/modules/mpris.c +++ b/modules/mpris.c @@ -13,7 +13,7 @@ #include -#include +#include #define LOG_MODULE "mpris" #define LOG_ENABLE_DBG 1 @@ -23,33 +23,40 @@ #include "../log.h" #include "../plugin.h" -#define MPRIS_QUERY_TIMEOUT 50 -#define MPRIS_EVENT_TIMEOUT 100 +#define QUERY_TIMEOUT 100 -#define MPRIS_PATH "/org/mpris/MediaPlayer2" -#define MPRIS_BUS_NAME "org.mpris.MediaPlayer2" -#define MPRIS_SERVICE "org.mpris.MediaPlayer2" -#define MPRIS_INTERFACE_ROOT "org.mpris.MediaPlayer2" -#define MPRIS_INTERFACE_PLAYER MPRIS_INTERFACE_ROOT ".Player" +#define PATH "/org/mpris/MediaPlayer2" +#define BUS_NAME "org.mpris.MediaPlayer2" +#define SERVICE "org.mpris.MediaPlayer2" +#define INTERFACE_ROOT "org.mpris.MediaPlayer2" +#define INTERFACE_PLAYER INTERFACE_ROOT ".Player" -enum mpris_status { - MPRIS_STATUS_OFFLINE, - MPRIS_STATUS_PLAYING, - MPRIS_STATUS_PAUSED, - MPRIS_STATUS_STOPPED, - MPRIS_STATUS_ERROR, +#define DBUS_PATH "/org/freedesktop/DBus" +#define DBUS_BUS_NAME "org.freedesktop.DBus" +#define DBUS_SERVICE "org.freedesktop.DBus" +#define DBUS_INTERFACE_MONITORING "org.freedesktop.DBus.Monitoring" +#define DBUS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" + +enum status { + STATUS_OFFLINE, + STATUS_PLAYING, + STATUS_PAUSED, + STATUS_STOPPED, + STATUS_ERROR, }; -struct mpris_metadata { +typedef tll(char *) string_array; + +struct metadata { uint64_t length_us; char *trackid; - char *artists; + string_array artists; char *album; char *title; }; -struct mpris_property { - struct mpris_metadata metadata; +struct property { + struct metadata metadata; char *playback_status; char *loop_status; uint64_t position_us; @@ -58,30 +65,30 @@ struct mpris_property { bool shuffle; }; -struct mpris_client { +struct client { bool has_seeked_support; - enum mpris_status status; + enum status status; const char *bus_name; const char *bus_unique_name; - struct mpris_property property; + struct property property; /* The unix timestamp of the last position change (ie. * seeking, pausing) */ struct timespec seeked_when; }; -struct mpris_context { - DBusConnection *monitor_connection; - DBusMessage *update_message; +struct context { + sd_bus *monitor_connection; + sd_bus_message *update_message; /* FIXME: There is no nice way to pass the desired identities to * the event handler for validation. */ - const char **identities_ref; - const size_t identities_count; + char **identities_ref; + size_t identities_count; - tll(struct mpris_client *) clients; - struct mpris_client *current_client; + tll(struct client *) clients; + struct client *current_client; bool has_update; }; @@ -95,90 +102,106 @@ struct private const char **identities; struct particle *label; - struct mpris_context context; + struct context context; }; +#if 0 static void -property_reset(struct mpris_property *property) +debug_print_argument_type(sd_bus_message *message) { - struct mpris_metadata *metadata = &property->metadata; + char type; + const char *content; + sd_bus_message_peek_type(message, &type, &content); + LOG_DBG("peek_message_type: %c -> %s", type, content); +} +#endif + +#if defined(LOG_ENABLE_DBG) +#define dump_type(message) \ + { \ + char type; \ + const char *content; \ + sd_bus_message_peek_type(message, &type, &content); \ + LOG_DBG("argument layout: %c -> %s", type, content); \ + } +#endif + +static void +metadata_clear(struct metadata *metadata) +{ + tll_free_and_free(metadata->artists, free); + if (metadata->album != NULL) { free(metadata->album); } - if (metadata->artists != NULL) { - free(metadata->artists); - } + if (metadata->title != NULL) { free(metadata->title); } + if (metadata->trackid != NULL) { + free(metadata->trackid); + } +} + +static void +property_clear(struct property *property) +{ + metadata_clear(&property->metadata); memset(property, 0, sizeof(*property)); } -static bool -client_free(struct mpris_client *client) +static void +client_free(struct client *client) { - property_reset(&client->property); + property_clear(&client->property); free((void *)client->bus_name); free((void *)client->bus_unique_name); free(client); - - return true; } -static bool -clients_free_by_unique_name(struct mpris_context *context, const char *unique_name) +static void +clients_free_by_unique_name(struct context *context, const char *unique_name) { tll_foreach(context->clients, it) { - struct mpris_client *client = it->item; + struct client *client = it->item; if (strcmp(client->bus_unique_name, unique_name) == 0) { LOG_DBG("client_remove: Removing client %s", client->bus_name); client_free(client); tll_remove(context->clients, it); } } - - return true; } -static bool -client_free_all(struct mpris_context *context) +static void +client_free_all(struct context *context) { - tll_foreach(context->clients, it) - { - client_free(it->item); - tll_remove(context->clients, it); - } - - return true; + tll_free_and_free(context->clients, client_free); } -static bool -client_add(struct mpris_context *context, const char *name, const char *unique_name) +static void +client_add(struct context *context, const char *name, const char *unique_name) { - struct mpris_client *client = malloc(sizeof(*client)); - (*client) = (struct mpris_client){ + struct client *client = malloc(sizeof(*client)); + (*client) = (struct client){ .bus_name = strdup(name), .bus_unique_name = strdup(unique_name), }; tll_push_back(context->clients, client); LOG_DBG("client_add: name='%s' unique_name='%s'", name, unique_name); - - return true; } -static struct mpris_client * -client_lookup_by_unique_name(struct mpris_context *context, const char *unique_name) +static struct client * +client_lookup_by_unique_name(struct context *context, const char *unique_name) { tll_foreach(context->clients, it) { - struct mpris_client *client = it->item; - /*LOG_DBG("client_lookup: client '%s' against '%s'", client->bus_unique_name, unique_name);*/ + struct client *client = it->item; if (strcmp(client->bus_unique_name, unique_name) == 0) { - LOG_DBG("client_lookup: unique name: %s", client->bus_unique_name); + LOG_DBG("client_lookup: name: %s", client->bus_name); return client; } } @@ -186,33 +209,27 @@ client_lookup_by_unique_name(struct mpris_context *context, const char *unique_n return NULL; } -static bool -client_change_unique_name(struct mpris_client *client, const char *new_name) +static void +client_change_unique_name(struct client *client, const char *new_name) { if (client->bus_unique_name != NULL) { free((void *)client->bus_unique_name); } client->bus_unique_name = strdup(new_name); - - return true; } static bool -verify_bus_name(const char **idents, const size_t ident_count, const char *name) +verify_bus_name(char **idents, const size_t ident_count, const char *name) { - assert(idents != NULL); - assert(name != NULL); - assert(dbus_validate_bus_name(name, NULL)); - for (size_t i = 0; i < ident_count; i++) { const char *ident = idents[i]; - if (strlen(name) < strlen(MPRIS_BUS_NAME ".") + strlen(ident)) { + if (strlen(name) < strlen(BUS_NAME ".") + strlen(ident)) { continue; } - const char *cmp = name + strlen(MPRIS_BUS_NAME "."); + const char *cmp = name + strlen(BUS_NAME "."); if (strncmp(cmp, ident, strlen(ident)) != 0) { continue; } @@ -223,149 +240,188 @@ verify_bus_name(const char **idents, const size_t ident_count, const char *name) return false; } -static DBusMessage * -mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message, uint32_t timeout_ms) -{ - DBusPendingCall *pending = NULL; - - if (!dbus_connection_send_with_reply(connection, message, &pending, timeout_ms)) { - LOG_ERR("dbus: error: failed to allocate message object"); - return NULL; - } - - dbus_pending_call_block(pending); - dbus_message_unref(message); - - DBusMessage *reply = dbus_pending_call_steal_reply(pending); - DBusError error = {0}; - if (dbus_set_error_from_message(&error, reply)) { - LOG_WARN("Unhandled error: '%s' (%s)", error.message, error.name); - - if (strcmp(error.name, DBUS_ERROR_NO_REPLY) == 0) - LOG_WARN("The client took too long to respont. Try increasing the poll timeout"); - - dbus_message_unref(reply); - dbus_error_free(&error); - reply = NULL; - } - - dbus_pending_call_unref(pending); - return reply; -} - static bool -mpris_metadata_parse_property(const char *property_name, DBusMessageIter *iter, struct mpris_metadata *buffer) +read_string_array(sd_bus_message *message, string_array *list) { - const char *string_value = NULL; - DBusMessageIter array_iter = {0}; - __attribute__((unused)) dbus_int32_t type = dbus_message_iter_get_arg_type(iter); + int status = 0; - /* Do not parse empty arrays */ - if (type == DBUS_TYPE_ARRAY && dbus_message_iter_get_element_count(iter) == 0) { - return true; + /* message argument layout: 'vas' */ + /* enter variant */ + status = sd_bus_message_enter_container(message, SD_BUS_TYPE_VARIANT, "as"); + if (status <= 0) { + LOG_DBG("unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; } - if (strcmp(property_name, "mpris:trackid") == 0) { - assert(type == DBUS_TYPE_OBJECT_PATH || type == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(iter, &string_value); - buffer->trackid = strdup(string_value); + /* enter array */ + status = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "s"); + assert(status >= 0); - } else if (strcmp(property_name, "xesam:album") == 0) { - assert(type == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(iter, &string_value); - buffer->album = strdup(string_value); - - } else if (strcmp(property_name, "xesam:artist") == 0) { - /* TODO: Properly format string arrays */ - /* NOTE: Currently, only the first artist will be shown, as we - * ignore the rest */ - assert(type == DBUS_TYPE_ARRAY); - dbus_message_iter_recurse(iter, &array_iter); - assert(dbus_message_iter_get_arg_type(&array_iter) == DBUS_TYPE_STRING); - - dbus_message_iter_get_basic(&array_iter, &string_value); - buffer->artists = strdup(string_value); - - } else if (strcmp(property_name, "xesam:title") == 0) { - assert(type == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(iter, &string_value); - buffer->title = strdup(string_value); - - } else if (strcmp(property_name, "mpris:length") == 0) { - assert(type == DBUS_TYPE_INT64 || type == DBUS_TYPE_UINT64); - dbus_message_iter_get_basic(iter, &buffer->length_us); - } else { - /*LOG_DBG("Ignoring metadata property: %s", entry_name);*/ + const char *string; + while ((status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &string)) > 0) { + if (strlen(string) > 0) { + tll_push_back(*list, strdup(string)); + } } + if (status < 0) { + LOG_ERR("metadata: unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + + /* close array */ + sd_bus_message_exit_container(message); + /* close variant */ + sd_bus_message_exit_container(message); + return true; } static bool -mpris_metadata_parse_array(struct mpris_metadata *metadata, DBusMessageIter *iter) +metadata_parse_property(const char *property_name, sd_bus_message *message, struct metadata *buffer) { - bool status = true; - DBusMessageIter array_iter = {0}; + int status = 0; + const char *string = NULL; - assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_ARRAY); - dbus_message_iter_recurse(iter, &array_iter); + char argument_type = 0; + const char *argument_layout = NULL; + sd_bus_message_peek_type(message, &argument_type, &argument_layout); + assert(argument_type == SD_BUS_TYPE_VARIANT); + assert(argument_layout != NULL && strlen(argument_layout) > 0); - dbus_int32_t current_type = 0; - while ((current_type = dbus_message_iter_get_arg_type(&array_iter)) != DBUS_TYPE_INVALID) { - assert(current_type == DBUS_TYPE_DICT_ENTRY); + if (strcmp(property_name, "mpris:trackid") == 0) { + if (argument_layout[0] != SD_BUS_TYPE_STRING && argument_layout[0] != SD_BUS_TYPE_OBJECT_PATH) + goto unexpected_type; - const char *entry_name = NULL; - DBusMessageIter entry_iter = {0}, entry_sub_iter = {0}; + status = sd_bus_message_read(message, "v", argument_layout, &string); + if (status > 0) + buffer->trackid = strdup(string); - dbus_message_iter_recurse(&array_iter, &entry_iter); - assert(dbus_message_iter_get_arg_type(&entry_iter) == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(&entry_iter, &entry_name); + /* FIXME: "strcmp matches both 'album' as well as 'albumArtist'" */ + } else if (strcmp(property_name, "xesam:album") == 0) { + status = sd_bus_message_read(message, "v", argument_layout, &string); + if (status > 0 && strlen(string) > 0) + buffer->album = strdup(string); - dbus_message_iter_next(&entry_iter); - dbus_message_iter_recurse(&entry_iter, &entry_sub_iter); - status = mpris_metadata_parse_property(entry_name, &entry_sub_iter, metadata); + } else if (strcmp(property_name, "xesam:artist") == 0) { + status = read_string_array(message, &buffer->artists); - dbus_message_iter_next(&array_iter); + } else if (strcmp(property_name, "xesam:title") == 0) { + status = sd_bus_message_read(message, "v", "s", &string); + if(status > 0) + buffer->title = strdup(string); + + } else if (strcmp(property_name, "mpris:length") == 0) { + /* MPRIS requires 'mpris:length' to be an i64 (the wording is a bit ambiguous), however some client + * use a u64 instead. */ + if (argument_layout[0] != SD_BUS_TYPE_INT64 && argument_layout[0] != SD_BUS_TYPE_UINT64) + goto unexpected_type; + + status = sd_bus_message_read(message, "v", argument_layout, &buffer->length_us); + + } else { + LOG_DBG("metadata: ignoring property: %s", property_name); + sd_bus_message_skip(message, NULL); + return true; } - return status; + if (status < 0) { + LOG_ERR("metadata: failed to read property: arg_type='%c' arg_layout='%s' errno=%d (%s)", argument_type, + argument_layout, status, strerror(-status)); + return false; + } + + return true; +unexpected_type: + LOG_ERR("metadata: unexpected type for '%s'", property_name); + return false; } static bool -mpris_property_parse(struct mpris_property *prop, const char *property_name, DBusMessageIter *iter_) +metadata_parse_array(struct metadata *metadata, sd_bus_message *message) { - DBusMessageIter iter = {0}; - bool status = true; + int status = sd_bus_message_enter_container(message, SD_BUS_TYPE_VARIANT, "a{sv}"); + if (status <= 0) { + LOG_DBG("unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + status = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "{sv}"); + assert(status >= 0); + while ((status = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *property_name = NULL; + status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); + if (status <= 0) { + LOG_DBG("unexpected layout: errno=%d (%s)", status, strerror(-status)); + return false; + } + + status = metadata_parse_property(property_name, message, metadata); + if (status == 0) { + return false; + } + + status = sd_bus_message_exit_container(message); + assert(status >= 0); + } + + /* close array */ + sd_bus_message_exit_container(message); + /* close variant */ + sd_bus_message_exit_container(message); + + return status >= 0; +} + +static bool +property_parse(struct property *prop, const char *property_name, sd_bus_message *message) +{ /* This function is called in two different ways: * 1. update_status(): The property is passed directly * 2. update_status_from_message(): The property is passed wrapped * inside a variant and has to be unpacked */ - if (dbus_message_iter_get_arg_type(iter_) == DBUS_TYPE_VARIANT) { - dbus_message_iter_recurse(iter_, &iter); - } else { - iter = *iter_; - } + const char *argument_layout = NULL; + char argument_type = 0; + int status = sd_bus_message_peek_type(message, &argument_type, &argument_layout); + assert(status > 0); + assert(argument_type == SD_BUS_TYPE_VARIANT); + assert(argument_layout != NULL && strlen(argument_layout) > 0); + + const char *string; if (strcmp(property_name, "PlaybackStatus") == 0) { - assert(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(&iter, &prop->playback_status); + status = sd_bus_message_read(message, "v", "s", &string); + if (status) + prop->playback_status = strdup(string); + } else if (strcmp(property_name, "LoopStatus") == 0) { - assert(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(&iter, &prop->loop_status); + status = sd_bus_message_read(message, "v", "s", &string); + if (status) + prop->loop_status = strdup(string); + } else if (strcmp(property_name, "Position") == 0) { - assert(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_INT64); - dbus_message_iter_get_basic(&iter, &prop->position_us); + /* MPRIS requires 'Position' to be a i64, however some client + * use a u64 instead. */ + if (argument_layout[0] != SD_BUS_TYPE_INT64 && argument_layout[0] != SD_BUS_TYPE_UINT64) { + LOG_ERR("property: unexpected type for '%s'", property_name); + return false; + } + status = sd_bus_message_read(message, "v", argument_layout[0], &prop->position_us); + } else if (strcmp(property_name, "Shuffle") == 0) { - assert(dbus_message_iter_get_arg_type(&iter) == DBUS_TYPE_BOOLEAN); - dbus_message_iter_get_basic(&iter, &prop->shuffle); + status = sd_bus_message_read(message, "v", "b", &prop->shuffle); + } else if (strcmp(property_name, "Metadata") == 0) { - status = mpris_metadata_parse_array(&prop->metadata, &iter); + metadata_clear(&prop->metadata); + status = metadata_parse_array(&prop->metadata, message); + } else { - /*LOG_DBG("Ignoring property: %s", property_name);*/ + LOG_DBG("property: ignoring property: %s", property_name); + sd_bus_message_skip(message, NULL); + return true; } - return status; + return status > 0; } /* ------------- */ @@ -388,11 +444,11 @@ static void destroy(struct module *mod) { struct private *m = mod->private; - struct mpris_context *context = &m->context; + struct context *context = &m->context; client_free_all(context); - dbus_connection_close(m->context.monitor_connection); + sd_bus_close(context->monitor_connection); module_default_destroy(mod); m->label->destroy(m->label); @@ -400,28 +456,24 @@ destroy(struct module *mod) } static void -context_event_handle_name_owner_changed(DBusConnection *connection, DBusMessage *message, struct mpris_context *context) +context_event_handle_name_owner_changed(sd_bus_message *message, struct context *context) { /* NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner) */ /* This signal indicates that the owner of a name has changed, ie. * it was acquired, lost or changed */ const char *bus_name = NULL, *old_owner = NULL, *new_owner = NULL; - DBusError error = {0}; - dbus_error_init(&error); - if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &bus_name, DBUS_TYPE_STRING, &old_owner, - DBUS_TYPE_STRING, &new_owner, DBUS_TYPE_INVALID)) { - LOG_ERR("%s", error.message); - dbus_error_free(&error); - return; - } + int status = sd_bus_message_read(message, "sss", &bus_name, &old_owner, &new_owner); + assert(status > 0); - /*LOG_DBG("event_handler: 'NameOwnerChanged': bus_name: '%s' old_owner: '%s' new_ower: '%s'", bus_name, old_owner,*/ - /* new_owner);*/ +#if 1 + LOG_DBG("event_handler: 'NameOwnerChanged': bus_name: '%s' old_owner: '%s' new_ower: '%s'", bus_name, old_owner, + new_owner); +#endif if (strlen(new_owner) == 0 && strlen(old_owner) > 0) { /* Target bus has been lost */ - struct mpris_client *client = client_lookup_by_unique_name(context, old_owner); + struct client *client = client_lookup_by_unique_name(context, old_owner); if (client == NULL) return; @@ -442,110 +494,131 @@ context_event_handle_name_owner_changed(DBusConnection *connection, DBusMessage assert(new_owner != NULL && strlen(new_owner) > 0); assert(old_owner != NULL && strlen(old_owner) > 0); - struct mpris_client *client = client_lookup_by_unique_name(context, old_owner); - LOG_DBG("event_handler: 'NameOwnerChanged': Name changed from '%s' to '%s' for client '%s'", old_owner, new_owner, + struct client *client = client_lookup_by_unique_name(context, old_owner); + LOG_DBG("'NameOwnerChanged': Name changed from '%s' to '%s' for client '%s'", old_owner, new_owner, client->bus_name); client_change_unique_name(client, new_owner); } static void -context_event_handle_name_acquired(DBusConnection *connection, DBusMessage *message, struct mpris_context *context) +context_event_handle_name_acquired(sd_bus_message *message, struct context *context) { /* Spy on applications that requested an "MPRIS style" bus name */ /* NameAcquired (STRING name) */ /* " This signal is sent to a specific application when it gains ownership of a name. " */ - char *name = NULL; - DBusError error; + const char *name = NULL; + int status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &name); + assert(status > 0); - dbus_error_init(&error); - dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID); - if (dbus_error_is_set(&error)) { - LOG_ERR("event_handler: 'NameAcquired': %s", error.message); - dbus_error_free(&error); - return; - } + /*LOG_DBG("event_handler: 'NameAcquired': name: '%s'", name);*/ - LOG_DBG("event_handler: 'NameAcquired': name: '%s'", name); - - if (strncmp(name, MPRIS_BUS_NAME, strlen(MPRIS_BUS_NAME)) != 0) { - LOG_DBG("event_handler: 'NameAcquired': Ignoring unrelated name: %s", name); + if (strncmp(name, BUS_NAME, strlen(BUS_NAME)) != 0) { return; } if (verify_bus_name(context->identities_ref, context->identities_count, name)) { - const char *unique_name = dbus_message_get_destination(message); - LOG_DBG("event_handler: 'NameAcquired': Acquired new client: %s unique: %s", name, unique_name); + const char *unique_name = sd_bus_message_get_destination(message); + LOG_DBG("'NameAcquired': Acquired new client: %s unique: %s", name, unique_name); client_add(context, name, unique_name); } } -static DBusHandlerResult -context_event_handler(DBusConnection *connection, DBusMessage *message, void *userdata) +static int +context_event_handler(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - struct mpris_context *context = userdata; + struct context *context = userdata; - const char *member = dbus_message_get_member(message); - const char *sender = dbus_message_get_sender(message); - const char *path_name = dbus_message_get_path(message); - - if (tll_length(context->clients) == 0 && strcmp(member, "NameAcquired") != 0) { - return DBUS_HANDLER_RESULT_HANDLED; - } + const char *member = sd_bus_message_get_member(message); + const char *sender = sd_bus_message_get_sender(message); + const char *path_name = sd_bus_message_get_path(message); #if 0 - const char *self = dbus_bus_get_unique_name(connection); - const char *destination = dbus_message_get_destination(message); - LOG_DBG("event_handler: member: '%s' self: '%s' dest: '%s' sender: '%s' destination: %s", member, self, destination, - sender, destination); + const char *destination = sd_bus_message_get_destination(message); + const char *self = sd_bus_message_get_sender(message); + LOG_DBG("member: '%s' self: '%s' dest: '%s' sender: '%s'", member, self, + destination, sender); #endif - /* TODO: Allow multiple clients to connect */ - if (strcmp(path_name, DBUS_PATH_DBUS) == 0 && strcmp(member, "NameAcquired") == 0) { - context_event_handle_name_acquired(connection, message, context); + if (tll_length(context->clients) == 0 && strcmp(member, "NameAcquired") != 0) { + return 1; } - if (strcmp(path_name, DBUS_PATH_DBUS) == 0 && strcmp(member, "NameOwnerChanged") == 0) { - context_event_handle_name_owner_changed(connection, message, context); - return DBUS_HANDLER_RESULT_HANDLED; + /* TODO: Allow multiple clients to connect */ + if (strcmp(path_name, DBUS_PATH) == 0 && strcmp(member, "NameAcquired") == 0) { + context_event_handle_name_acquired(message, context); + } + + if (strcmp(path_name, DBUS_PATH) == 0 && strcmp(member, "NameOwnerChanged") == 0) { + context_event_handle_name_owner_changed(message, context); + return 1; } /* Copy the 'PropertiesChanged/Seeked' message, so it can be parsed * later on */ - if (strcmp(path_name, MPRIS_PATH) == 0 - && (strcmp(member, "PropertiesChanged") == 0 || strcmp(member, "Seeked") == 0)) { - struct mpris_client *client = client_lookup_by_unique_name(context, sender); + if (strcmp(path_name, PATH) == 0 && (strcmp(member, "PropertiesChanged") == 0 || strcmp(member, "Seeked") == 0)) { + struct client *client = client_lookup_by_unique_name(context, sender); if (client == NULL) - return DBUS_HANDLER_RESULT_HANDLED; + return 1; + + LOG_DBG("event_handler: '%s': name: '%s' unique_name: '%s'", member, client->bus_name, client->bus_unique_name); - LOG_DBG("event_handler: 'PropertiesChanged': name: '%s' unique_name: '%s'", client->bus_name, - client->bus_unique_name); context->has_update = true; context->current_client = client; - context->update_message = dbus_message_copy(message); + context->update_message = sd_bus_message_ref(message); + + assert(context->update_message != NULL); } - return DBUS_HANDLER_RESULT_HANDLED; + return 1; } static bool -context_new(struct private *m, struct mpris_context *context) +context_process_events(struct context *context, uint32_t timeout_ms) { - DBusError error = {0}; - bool status = true; + int status = -1; - DBusConnection *connection = dbus_bus_get_private(DBUS_BUS_SESSION, &error); - if (dbus_error_is_set(&error)) { - LOG_ERR("Failed to connect to the desktop bus: %s", error.message); + status = sd_bus_wait(context->monitor_connection, timeout_ms); + if (status < 0) { + if (status == -ENOTCONN) + LOG_DBG("Disconnect signal has been processed"); + else + LOG_ERR("Failed to query monitor connection: errno=%d", status); + + return false; + } + + /* 'sd_bus_process' processes one 'action' per call. + * This includes: connection, authentication, message processing */ + status = sd_bus_process(context->monitor_connection, NULL); + + if (status < 0) { + if (status == -ENOTCONN) + LOG_DBG("Disconnect signal has been processed"); + else + LOG_ERR("Failed to query monitor connection: errno=%d", status); + + return false; + } + + return true; +} + +static bool +context_new(struct private *m, struct context *context) +{ + int status = true; + sd_bus *connection; + if ((status = sd_bus_default_user(&connection)) < 0) { + LOG_ERR("Failed to connect to the desktop bus. errno: %d", status); return -1; } - dbus_connection_add_filter(connection, context_event_handler, context, NULL); - /* Turn this connection into a monitor */ - DBusMessage *message - = dbus_message_new_method_call(DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_MONITORING, "BecomeMonitor"); - DBusMessageIter args_iter = {0}, array_iter = {0}; + sd_bus_message *message; + status = sd_bus_message_new_method_call(connection, &message, DBUS_SERVICE, DBUS_PATH, DBUS_INTERFACE_MONITORING, + "BecomeMonitor"); + const char *matching_rules[] = { /* Listen for... */ /* ... new MPRIS clients */ @@ -562,56 +635,39 @@ context_new(struct private *m, struct mpris_context *context) "path='/org/mpris/MediaPlayer2'", }; - /* "BecomeMonitor": (Rules: String[], Flags: UINT32) */ + /* TODO: Error handling */ + /* "BecomeMonitor" ('asu'): (Rules: String[], Flags: UINT32) */ /* https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-become-monitor */ - /* FIXME: Most of the subsequent dbus function fail on OOM (out of memory) */ - dbus_message_iter_init_append(message, &args_iter); - dbus_message_iter_open_container(&args_iter, DBUS_TYPE_ARRAY, "s", &array_iter); - + status = sd_bus_message_open_container(message, SD_BUS_TYPE_ARRAY, "s"); for (uint32_t i = 0; i < sizeof(matching_rules) / sizeof(matching_rules[0]); i++) { - dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &matching_rules[i]); + status = sd_bus_message_append(message, "s", matching_rules[i]); } + status = sd_bus_message_close_container(message); + status = sd_bus_message_append_basic(message, SD_BUS_TYPE_UINT32, &(uint32_t){0}); - dbus_message_iter_close_container(&args_iter, &array_iter); - dbus_message_iter_append_basic(&args_iter, DBUS_TYPE_UINT32, &(uint32_t){0}); + sd_bus_message *reply = NULL; + sd_bus_error error = {}; + status = sd_bus_call(NULL, message, QUERY_TIMEOUT, &error, &reply); - DBusMessage *reply = mpris_call_method_and_block(connection, message, MPRIS_QUERY_TIMEOUT); - - if (reply == NULL) { - LOG_ERR("Failed to setup monitor connection. Your dbus implementation does not support monitoring"); - dbus_connection_close(context->monitor_connection); + if (status < 0 && sd_bus_error_is_set(&error)) { + LOG_ERR("context_new: got error response with error: %s: %s (%d)", error.name, error.message, + sd_bus_error_get_errno(&error)); return false; } - struct mpris_context content = { + sd_bus_message_unref(message); + sd_bus_message_unref(reply); + + (*context) = (struct context){ .monitor_connection = connection, - .identities_ref = m->identities, + .identities_ref = (char **)m->identities, .identities_count = m->identities_count, + .clients = tll_init(), }; - dbus_connection_flush(connection); - dbus_message_unref(reply); + sd_bus_add_filter(connection, NULL, context_event_handler, context); - memcpy(context, &content, sizeof(content)); - - return status; -} - -static bool -context_process_events(struct mpris_context *context, uint32_t timeout_ms) -{ - if (!dbus_connection_read_write_dispatch(context->monitor_connection, timeout_ms)) { - /* Figure out what might terminate our connection (with the - * exception of calling disconnect manually) */ - LOG_DBG("event_handler: Disconnect signal has been processed"); - } - - if (dbus_connection_get_dispatch_status(context->monitor_connection) == DBUS_DISPATCH_NEED_MEMORY) { - /* TODO: Handle OOM */ - return false; - } - - return true; + return status >= 0; } static uint64_t @@ -626,91 +682,74 @@ timespec_diff_us(const struct timespec *a, const struct timespec *b) } static bool -update_status_from_message(struct module *mod, DBusMessage *message) +update_status_from_message(struct module *mod, sd_bus_message *message) { struct private *m = mod->private; mtx_lock(&mod->lock); - struct mpris_client *client = m->context.current_client; + struct client *client = m->context.current_client; + int status = 1; /* Player.Seeked (UINT64 position)*/ - if (strcmp(dbus_message_get_member(message), "Seeked") == 0) { + if (strcmp(sd_bus_message_get_member(message), "Seeked") == 0) { client->has_seeked_support = true; - DBusMessageIter iter = {0}; - dbus_message_iter_init(message, &iter); - dbus_message_iter_get_basic(&iter, &client->property.position_us); + + status = sd_bus_message_read_basic(message, SD_BUS_TYPE_INT64, &client->property.position_us); + if (status <= 0) + return status; clock_gettime(CLOCK_MONOTONIC, &client->seeked_when); - return true; } /* Properties.PropertiesChanged (STRING interface_name, * ARRAY of DICT_ENTRY changed_properties, * ARRAY invalidated_properties); */ - assert(strcmp(dbus_message_get_member(message), "PropertiesChanged") == 0); + assert(strcmp(sd_bus_message_get_member(message), "PropertiesChanged") == 0); + assert(strcmp(sd_bus_message_get_signature(message, 1), "sa{sv}as") == 0); - DBusMessageIter iter = {0}; - dbus_message_iter_init(message, &iter); - - dbus_int32_t current_type = 0; + /* argument: 'interface_name' layout: 's' */ const char *interface_name = NULL; + sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &interface_name); - /* Signature: s */ - current_type = dbus_message_iter_get_arg_type(&iter); - assert(current_type == DBUS_TYPE_STRING); - dbus_message_iter_get_basic(&iter, &interface_name); - dbus_message_iter_next(&iter); - - /* Signature: a{sv} */ - DBusMessageIter changed_properties_iter = {0}; - current_type = dbus_message_iter_get_arg_type(&iter); - assert(current_type == DBUS_TYPE_ARRAY); - dbus_message_iter_recurse(&iter, &changed_properties_iter); - dbus_message_iter_next(&iter); - - /* Signature: as */ - /* The MPRIS interface should not change on the fly ie. we can - * probably ignore the 'invalid_properties' argument */ - current_type = dbus_message_iter_get_arg_type(&iter); - assert(current_type == DBUS_TYPE_ARRAY); - assert(dbus_message_iter_get_element_count(&iter) == 0); - - if (strcmp(interface_name, MPRIS_INTERFACE_PLAYER) != 0) { + if (strcmp(interface_name, INTERFACE_PLAYER) != 0) { LOG_DBG("Ignoring interface: %s", interface_name); mtx_unlock(&mod->lock); return true; } + /* argument: 'changed_properties' layout: 'a{sv}' */ + /* Make sure we reset the position on metadata change unless the * update contains its own position value */ bool should_reset_position = true; - while ((current_type = dbus_message_iter_get_arg_type(&changed_properties_iter)) != DBUS_TYPE_INVALID) { - DBusMessageIter dict_iter = {0}; - dbus_message_iter_recurse(&changed_properties_iter, &dict_iter); + bool has_entries = sd_bus_message_enter_container(message, SD_BUS_TYPE_ARRAY, "{sv}"); + while ((has_entries = sd_bus_message_enter_container(message, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { const char *property_name = NULL; - dbus_message_iter_get_basic(&dict_iter, &property_name); - assert(dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_STRING); + int status = sd_bus_message_read_basic(message, SD_BUS_TYPE_STRING, &property_name); + assert(status > 0); - dbus_message_iter_next(&dict_iter); - current_type = dbus_message_iter_get_arg_type(&dict_iter); - assert(dbus_message_iter_get_arg_type(&dict_iter) == DBUS_TYPE_VARIANT); + if (!property_parse(&client->property, property_name, message)) { + return false; + } - LOG_DBG("update_status: reding property: %s", property_name); - mpris_property_parse(&client->property, property_name, &dict_iter); + status = sd_bus_message_exit_container(message); + assert(status >= 0); if (strcmp(property_name, "PlaybackStatus") == 0) { if (strcmp(client->property.playback_status, "Stopped") == 0) { - client->status = MPRIS_STATUS_STOPPED; + client->status = STATUS_STOPPED; + } else if (strcmp(client->property.playback_status, "Playing") == 0) { clock_gettime(CLOCK_MONOTONIC, &client->seeked_when); - client->status = MPRIS_STATUS_PLAYING; + client->status = STATUS_PLAYING; + } else if (strcmp(client->property.playback_status, "Paused") == 0) { /* Update our position to include the elapsed time */ struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - client->status = MPRIS_STATUS_PAUSED; + client->status = STATUS_PAUSED; client->property.position_us += timespec_diff_us(&now, &client->seeked_when); } } @@ -721,17 +760,18 @@ update_status_from_message(struct module *mod, DBusMessage *message) if (client->property.playback_status == NULL) { client->property.playback_status = "Paused"; - client->status = MPRIS_STATUS_PAUSED; + client->status = STATUS_PAUSED; } } if (strcmp(property_name, "Position") == 0) { should_reset_position = false; } - - dbus_message_iter_next(&changed_properties_iter); } + status = sd_bus_message_exit_container(message); + assert(status > 0); + mtx_unlock(&mod->lock); return true; } @@ -771,14 +811,14 @@ static struct exposable * content(struct module *mod) { const struct private *m = mod->private; - const struct mpris_client *client = m->context.current_client; + const struct client *client = m->context.current_client; if (client == NULL) { return content_empty(mod); } - const struct mpris_metadata *metadata = &client->property.metadata; - const struct mpris_property *property = &client->property; + const struct metadata *metadata = &client->property.metadata; + const struct property *property = &client->property; /* Calculate the current playback position */ struct timespec now; @@ -787,7 +827,7 @@ content(struct module *mod) uint64_t elapsed_us = client->property.position_us; uint64_t length_us = metadata->length_us; - if (client->has_seeked_support && client->status == MPRIS_STATUS_PLAYING) { + if (client->has_seeked_support && client->status == STATUS_PLAYING) { elapsed_us += timespec_diff_us(&now, &client->seeked_when); if (elapsed_us > length_us) { LOG_DBG("dynamic update of elapsed overflowed: " @@ -811,32 +851,31 @@ content(struct module *mod) char *tag_state_value = NULL; switch (client->status) { - case MPRIS_STATUS_ERROR: + case STATUS_ERROR: tag_state_value = "error"; break; - case MPRIS_STATUS_OFFLINE: + case STATUS_OFFLINE: tag_state_value = "offline"; break; - case MPRIS_STATUS_PLAYING: + case STATUS_PLAYING: tag_state_value = "playing"; break; - case MPRIS_STATUS_PAUSED: + case STATUS_PAUSED: tag_state_value = "paused"; break; - case MPRIS_STATUS_STOPPED: + case STATUS_STOPPED: tag_state_value = "stopped"; break; } 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_artists_value = (metadata->artists == NULL) ? "" : metadata->artists; + const char *tag_artists_value = (tll_length(metadata->artists) <= 0) ? "" : tll_front(metadata->artists); const char *tag_title_value = (metadata->title == NULL) ? "" : metadata->title; const uint32_t tag_volume_value = (property->volume >= 0.995) ? 100 : 100 * property->volume; const bool tag_shuffle_value = property->shuffle; - const enum tag_realtime_unit realtime_unit = (client->has_seeked_support && client->status == MPRIS_STATUS_PLAYING) - ? TAG_REALTIME_MSECS - : TAG_REALTIME_NONE; + const enum tag_realtime_unit realtime_unit + = (client->has_seeked_support && client->status == STATUS_PLAYING) ? TAG_REALTIME_MSECS : TAG_REALTIME_NONE; struct tag_set tags = { .tags = (struct tag *[]){ @@ -971,7 +1010,7 @@ run(struct module *mod) return -1; } - struct mpris_context *context = &m->context; + struct context *context = &m->context; int ret = 0; bool aborted = false; @@ -993,25 +1032,29 @@ run(struct module *mod) break; } - if (!context_process_events(context, MPRIS_EVENT_TIMEOUT)) { + if (!context_process_events(context, QUERY_TIMEOUT)) { aborted = true; break; } - /* Process dynamic updates, revieved through the contexts - * monitor connection */ + /* Process dynamic updates, received through the contexts + * monitor connection. The 'upate_message' attribute is set + * inside the contexts event callback, if there are any + * updates to be processed. */ if (context->has_update) { assert(context->current_client != NULL); + assert(context->update_message != NULL); context->has_update = false; aborted = !update_status_from_message(mod, context->update_message); - dbus_message_unref(context->update_message); - context->update_message = NULL; + context->update_message = sd_bus_message_unref(context->update_message); } bar->refresh(bar); } + LOG_DBG("exiting"); + return ret; } @@ -1082,5 +1125,5 @@ const struct module_iface module_mpris_iface = { }; #if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES) -extern const struct module_iface iface __attribute__((weak, alias("module_mpris_iface"))); +extern const struct module_mpris_iface iface __attribute__((weak, alias("module_iface"))); #endif diff --git a/subprojects/basu.wrap b/subprojects/basu.wrap new file mode 100644 index 0000000..9e9adab --- /dev/null +++ b/subprojects/basu.wrap @@ -0,0 +1,3 @@ +[wrap-git] +url = https://git.sr.ht/~emersion/basu +revision = v0.2.1