mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-19 19:25:41 +02:00
module/mpris: Improved error handeling
This commit is contained in:
parent
1b8962d296
commit
32ab5b9309
1 changed files with 124 additions and 77 deletions
201
modules/mpris.c
201
modules/mpris.c
|
@ -27,6 +27,29 @@
|
||||||
#include "../log.h"
|
#include "../log.h"
|
||||||
#include "../plugin.h"
|
#include "../plugin.h"
|
||||||
|
|
||||||
|
/* TODO: Process 'NameOwnerChanged'/'PropertiesChanged' signals.
|
||||||
|
* Should we listen for signals, instead of querying?
|
||||||
|
* + We get notified, if something changed, reducing our own overhead
|
||||||
|
* + A convinient way to handle missing properties, since we will
|
||||||
|
* never be notified there nonexistence.
|
||||||
|
* - "MPRIS compatible" clients do not necessarily correctly implement
|
||||||
|
* the MPRIS interface (for example, firefox reports some missing
|
||||||
|
* properties as 'InvalidArg', when the error should be
|
||||||
|
* 'UnhandledProperty'). While this is also an issue for querying,
|
||||||
|
* the impact is a lot more managable, since we know right away
|
||||||
|
* what properties we are missing, and don't have to wait for them
|
||||||
|
* to change. This also assumes that the client emits the
|
||||||
|
* 'PropertiesChanged' signal correctly, which (again) does not seem to be
|
||||||
|
* for everyone ('').
|
||||||
|
* - The relevant signals are only emitted when, well, something changed.
|
||||||
|
* This means that we have fall back to querying if we want to build an initial state. */
|
||||||
|
|
||||||
|
/* TODO: Move from 'Get' to the 'GetAll' method on the 'org.freedesktop.DBus.Properties' interface.
|
||||||
|
* + A generalized way to handle missing properties
|
||||||
|
* + Reduced overhead, since we only call a single method
|
||||||
|
* + Buid a list of available properties (bitmap)
|
||||||
|
* - Complex parsing logic */
|
||||||
|
|
||||||
enum mpris_playback_state {
|
enum mpris_playback_state {
|
||||||
MPRIS_PLAYBACK_INVALID,
|
MPRIS_PLAYBACK_INVALID,
|
||||||
MPRIS_PLAYBACK_STOPPED,
|
MPRIS_PLAYBACK_STOPPED,
|
||||||
|
@ -42,7 +65,7 @@ enum mpris_loop_state {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct mpris_metadata {
|
struct mpris_metadata {
|
||||||
uint64_t length_nsec;
|
uint64_t length_usec;
|
||||||
char *trackid;
|
char *trackid;
|
||||||
char *artists;
|
char *artists;
|
||||||
char *album;
|
char *album;
|
||||||
|
@ -63,7 +86,7 @@ struct private
|
||||||
{
|
{
|
||||||
struct particle *label;
|
struct particle *label;
|
||||||
/* TODO: This should be an array of options */
|
/* TODO: This should be an array of options */
|
||||||
const char *target_identity;
|
const char *desired_bus_name;
|
||||||
DBusConnection *connection;
|
DBusConnection *connection;
|
||||||
|
|
||||||
const char *target_bus_name;
|
const char *target_bus_name;
|
||||||
|
@ -94,7 +117,6 @@ struct private
|
||||||
#define MPRIS_DBUS_BUS "org.freedesktop.DBus"
|
#define MPRIS_DBUS_BUS "org.freedesktop.DBus"
|
||||||
#define MPRIS_DBUS_INTERFACE "org.freedesktop.DBus"
|
#define MPRIS_DBUS_INTERFACE "org.freedesktop.DBus"
|
||||||
|
|
||||||
/* TODO: Don't block */
|
|
||||||
static DBusMessage *
|
static DBusMessage *
|
||||||
mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message)
|
mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message)
|
||||||
{
|
{
|
||||||
|
@ -111,25 +133,24 @@ mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message)
|
||||||
/* Handle and gracrfully return different error types
|
/* Handle and gracrfully return different error types
|
||||||
* ('org.freedesktop.DBus.Error.NotSupported' etc.)*/
|
* ('org.freedesktop.DBus.Error.NotSupported' etc.)*/
|
||||||
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
|
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
|
||||||
if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
|
DBusError error = {0};
|
||||||
const char *error_name = dbus_message_get_error_name(reply);
|
if (dbus_set_error_from_message(&error, reply)) {
|
||||||
if (dbus_message_is_error(reply, error_name)) {
|
if ((strcmp(error.name, DBUS_ERROR_INVALID_ARGS) != 0) && (strcmp(error.name, DBUS_ERROR_NOT_SUPPORTED) != 0)) {
|
||||||
DBusError error = {0};
|
LOG_ERR("Unhandled error: '%s' (%s)", error.message, error.name);
|
||||||
LOG_ERR("Message call returned an error: %s", error_name);
|
} else {
|
||||||
if (dbus_set_error_from_message(&error, message)) {
|
LOG_DBG("%s: %s", error.name, error.message);
|
||||||
LOG_ERR("Report: %s", error.message);
|
|
||||||
}
|
|
||||||
dbus_error_free(&error);
|
|
||||||
dbus_pending_call_unref(pending);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbus_message_unref(reply);
|
||||||
|
dbus_error_free(&error);
|
||||||
|
reply = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
dbus_pending_call_unref(pending);
|
dbus_pending_call_unref(pending);
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((unused)) static DBusMessage *
|
static DBusMessage *
|
||||||
mpris_get_property(DBusConnection *connection, const char *bus_name, const char *interface, const char *property_name)
|
mpris_get_property(DBusConnection *connection, const char *bus_name, const char *interface, const char *property_name)
|
||||||
{
|
{
|
||||||
assert(bus_name != NULL && strlen(bus_name) > 0);
|
assert(bus_name != NULL && strlen(bus_name) > 0);
|
||||||
|
@ -143,6 +164,10 @@ mpris_get_property(DBusConnection *connection, const char *bus_name, const char
|
||||||
return mpris_call_method_and_block(connection, message);
|
return mpris_call_method_and_block(connection, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* TODO: Handle name changes
|
||||||
|
* We essentially have two options:
|
||||||
|
* 1. Listen for 'NameOwnerChanged' */
|
||||||
|
|
||||||
static char *
|
static char *
|
||||||
mpris_get_bus_name(DBusConnection *connection, const char *identity_name)
|
mpris_get_bus_name(DBusConnection *connection, const char *identity_name)
|
||||||
{
|
{
|
||||||
|
@ -227,7 +252,7 @@ mpris_unwrap_iter(DBusMessageIter *iter, dbus_int32_t type, void *target)
|
||||||
break;
|
break;
|
||||||
default:;
|
default:;
|
||||||
char *signature = dbus_message_iter_get_signature(&type_iter);
|
char *signature = dbus_message_iter_get_signature(&type_iter);
|
||||||
LOG_WARN("Trying to unwrap unsupported type: %s", signature);
|
LOG_WARN("Trying to unwrap unsupported type: %s. Ignoring", signature);
|
||||||
dbus_free(signature);
|
dbus_free(signature);
|
||||||
status = false;
|
status = false;
|
||||||
}
|
}
|
||||||
|
@ -278,7 +303,9 @@ mpris_metadata_parse(const char *entry_name, DBusMessageIter *entry_iter, struct
|
||||||
|
|
||||||
} else if (strcmp(entry_name, "mpris:length") == 0) {
|
} else if (strcmp(entry_name, "mpris:length") == 0) {
|
||||||
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_INT64);
|
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_INT64);
|
||||||
dbus_message_iter_get_basic(entry_iter, &buffer->length_nsec);
|
dbus_message_iter_get_basic(entry_iter, &buffer->length_usec);
|
||||||
|
} else {
|
||||||
|
/*LOG_DBG("Ignoring unhandled metadata property: %s", entry_name);*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -311,7 +338,7 @@ mpris_unwrap_metadata_message(DBusMessage *message, struct mpris_metadata *metad
|
||||||
|
|
||||||
dbus_message_iter_next(&entry_iter);
|
dbus_message_iter_next(&entry_iter);
|
||||||
dbus_message_iter_recurse(&entry_iter, &entry_sub_iter);
|
dbus_message_iter_recurse(&entry_iter, &entry_sub_iter);
|
||||||
mpris_metadata_parse(entry_name, &entry_sub_iter, metadata);
|
status = mpris_metadata_parse(entry_name, &entry_sub_iter, metadata);
|
||||||
|
|
||||||
dbus_message_iter_next(&array_iter);
|
dbus_message_iter_next(&array_iter);
|
||||||
}
|
}
|
||||||
|
@ -358,7 +385,7 @@ destroy(struct module *mod)
|
||||||
dbus_connection_close(m->connection);
|
dbus_connection_close(m->connection);
|
||||||
|
|
||||||
free((void *)m->target_bus_name);
|
free((void *)m->target_bus_name);
|
||||||
free((void *)m->target_identity);
|
free((void *)m->desired_bus_name);
|
||||||
|
|
||||||
mpris_clear(&m->property);
|
mpris_clear(&m->property);
|
||||||
|
|
||||||
|
@ -382,59 +409,67 @@ content(struct module *mod)
|
||||||
|
|
||||||
/* usec -> msec -> sec */
|
/* usec -> msec -> sec */
|
||||||
uint32_t position_sec = m->property.position_usec / 1000 / 1000;
|
uint32_t position_sec = m->property.position_usec / 1000 / 1000;
|
||||||
uint32_t length_sec = metadata.length_nsec / 1000 / 1000;
|
uint32_t length_sec = metadata.length_usec / 1000 / 1000;
|
||||||
|
|
||||||
char pos[16], end[16];
|
char pos_buffer[16] = {0}, end_buffer[16] = {0};
|
||||||
secs_to_str(position_sec, pos, sizeof(pos));
|
if (length_sec > 0) {
|
||||||
secs_to_str(length_sec, end, sizeof(end));
|
secs_to_str(position_sec, pos_buffer, sizeof(pos_buffer));
|
||||||
|
secs_to_str(length_sec, end_buffer, sizeof(end_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
char *playback_str = NULL;
|
char *tag_playback_value = NULL;
|
||||||
switch (m->property.playback_status) {
|
switch (m->property.playback_status) {
|
||||||
case MPRIS_PLAYBACK_STOPPED:
|
case MPRIS_PLAYBACK_STOPPED:
|
||||||
playback_str = "stopped";
|
tag_playback_value = "stopped";
|
||||||
break;
|
break;
|
||||||
case MPRIS_PLAYBACK_PLAYING:
|
case MPRIS_PLAYBACK_PLAYING:
|
||||||
playback_str = "playing";
|
tag_playback_value = "playing";
|
||||||
break;
|
break;
|
||||||
case MPRIS_PLAYBACK_PAUSED:
|
case MPRIS_PLAYBACK_PAUSED:
|
||||||
playback_str = "paused";
|
tag_playback_value = "paused";
|
||||||
break;
|
break;
|
||||||
case MPRIS_PLAYBACK_INVALID:
|
case MPRIS_PLAYBACK_INVALID:
|
||||||
playback_str = "offline";
|
tag_playback_value = "offline";
|
||||||
}
|
}
|
||||||
|
|
||||||
char *loop_str = NULL;
|
char *tag_loop_value = NULL;
|
||||||
switch (m->property.loop_status) {
|
switch (m->property.loop_status) {
|
||||||
case MPRIS_LOOP_NONE:
|
case MPRIS_LOOP_NONE:
|
||||||
loop_str = "none";
|
tag_loop_value = "none";
|
||||||
break;
|
break;
|
||||||
case MPRIS_LOOP_TRACK:
|
case MPRIS_LOOP_TRACK:
|
||||||
loop_str = "track";
|
tag_loop_value = "track";
|
||||||
break;
|
break;
|
||||||
case MPRIS_LOOP_PLAYLIST:
|
case MPRIS_LOOP_PLAYLIST:
|
||||||
loop_str = "playlist";
|
tag_loop_value = "playlist";
|
||||||
break;
|
break;
|
||||||
default:
|
case MPRIS_LOOP_INVALID:
|
||||||
loop_str = "";
|
tag_loop_value = "";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t volume = (m->property.volume >= 0.995) ? 100 : 100 * m->property.volume;
|
const char *tag_identity_value = (m->desired_bus_name == NULL) ? "" : m->desired_bus_name;
|
||||||
|
const char *tag_album_value = (metadata.album == NULL) ? "" : metadata.album;
|
||||||
|
const char *tag_artists_value = (metadata.album == NULL) ? "" : metadata.artists;
|
||||||
|
const char *tag_title_value = (metadata.album == NULL) ? "" : metadata.title;
|
||||||
|
const char *tag_end_value = end_buffer;
|
||||||
|
const char *tag_pos_value = pos_buffer;
|
||||||
|
|
||||||
|
uint32_t tag_volume_value = (m->property.volume >= 0.995) ? 100 : 100 * m->property.volume;
|
||||||
|
bool tag_shuffle_value = m->property.shuffle;
|
||||||
|
|
||||||
struct tag_set tags = {
|
struct tag_set tags = {
|
||||||
.tags = (struct tag *[]){
|
.tags = (struct tag *[]){
|
||||||
tag_new_string(mod, "state", playback_str),
|
tag_new_string(mod, "state", tag_playback_value),
|
||||||
tag_new_string(mod, "identity", m->target_identity),
|
tag_new_string(mod, "identity", tag_identity_value),
|
||||||
/* Stay consistent with existing modules naming
|
tag_new_bool(mod, "random", tag_shuffle_value),
|
||||||
* conventions (mpd)? */
|
tag_new_string(mod, "loop", tag_loop_value),
|
||||||
tag_new_bool(mod, "random", m->property.shuffle),
|
tag_new_int_range(mod, "volume", tag_volume_value, 0, 100),
|
||||||
tag_new_string(mod, "loop", loop_str),
|
tag_new_string(mod, "album", tag_album_value),
|
||||||
tag_new_int_range(mod, "volume", volume, 0, 100),
|
tag_new_string(mod, "artist", tag_artists_value),
|
||||||
tag_new_string(mod, "album", metadata.album),
|
tag_new_string(mod, "title", tag_title_value),
|
||||||
tag_new_string(mod, "artist", metadata.artists),
|
tag_new_string(mod, "pos", tag_end_value),
|
||||||
tag_new_string(mod, "title", metadata.title),
|
tag_new_string(mod, "end", tag_pos_value),
|
||||||
tag_new_string(mod, "pos", pos),
|
|
||||||
tag_new_string(mod, "end", end),
|
|
||||||
tag_new_int_realtime(
|
tag_new_int_realtime(
|
||||||
mod, "elapsed", position_sec, 0, length_sec, TAG_REALTIME_SECS),
|
mod, "elapsed", position_sec, 0, length_sec, TAG_REALTIME_SECS),
|
||||||
},
|
},
|
||||||
|
@ -458,49 +493,61 @@ update_status(struct module *mod)
|
||||||
/* Property: Metadata */
|
/* Property: Metadata */
|
||||||
mpris_clear(&m->property);
|
mpris_clear(&m->property);
|
||||||
DBusMessage *message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Metadata");
|
DBusMessage *message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Metadata");
|
||||||
mpris_unwrap_metadata_message(message, &m->property.metadata);
|
if (message != NULL) {
|
||||||
dbus_message_unref(message);
|
mpris_unwrap_metadata_message(message, &m->property.metadata);
|
||||||
|
dbus_message_unref(message);
|
||||||
|
}
|
||||||
|
|
||||||
/* Update remaining properties */
|
/* Update remaining properties */
|
||||||
/* Property: PlaybackStatus */
|
/* Property: PlaybackStatus */
|
||||||
char *string = NULL;
|
char *string = NULL;
|
||||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "PlaybackStatus");
|
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "PlaybackStatus");
|
||||||
mpris_unwrap_message(message, DBUS_TYPE_STRING, &string);
|
if (message != NULL) {
|
||||||
if (strcmp(string, "Stopped")) {
|
mpris_unwrap_message(message, DBUS_TYPE_STRING, &string);
|
||||||
m->property.playback_status = MPRIS_PLAYBACK_STOPPED;
|
if (strcmp(string, "Stopped")) {
|
||||||
} else if (strcmp(string, "Paused")) {
|
m->property.playback_status = MPRIS_PLAYBACK_STOPPED;
|
||||||
m->property.playback_status = MPRIS_PLAYBACK_PAUSED;
|
} else if (strcmp(string, "Paused")) {
|
||||||
} else if (strcmp(string, "Playing")) {
|
m->property.playback_status = MPRIS_PLAYBACK_PAUSED;
|
||||||
m->property.playback_status = MPRIS_PLAYBACK_PLAYING;
|
} else if (strcmp(string, "Playing")) {
|
||||||
|
m->property.playback_status = MPRIS_PLAYBACK_PLAYING;
|
||||||
|
}
|
||||||
|
dbus_message_unref(message);
|
||||||
}
|
}
|
||||||
dbus_message_unref(message);
|
|
||||||
|
|
||||||
/* Property: LoopStatus */
|
/* Property: LoopStatus */
|
||||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "LoopStatus");
|
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "LoopStatus");
|
||||||
mpris_unwrap_message(message, DBUS_TYPE_STRING, &string);
|
if (message != NULL) {
|
||||||
if (strcmp(string, "None")) {
|
mpris_unwrap_message(message, DBUS_TYPE_STRING, &string);
|
||||||
m->property.loop_status = MPRIS_LOOP_NONE;
|
if (strcmp(string, "None")) {
|
||||||
} else if (strcmp(string, "Track")) {
|
m->property.loop_status = MPRIS_LOOP_NONE;
|
||||||
m->property.loop_status = MPRIS_LOOP_TRACK;
|
} else if (strcmp(string, "Track")) {
|
||||||
} else if (strcmp(string, "Playlist")) {
|
m->property.loop_status = MPRIS_LOOP_TRACK;
|
||||||
m->property.loop_status = MPRIS_LOOP_PLAYLIST;
|
} else if (strcmp(string, "Playlist")) {
|
||||||
|
m->property.loop_status = MPRIS_LOOP_PLAYLIST;
|
||||||
|
}
|
||||||
|
dbus_message_unref(message);
|
||||||
}
|
}
|
||||||
dbus_message_unref(message);
|
|
||||||
|
|
||||||
/* Property: Volume */
|
/* Property: Volume */
|
||||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Volume");
|
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Volume");
|
||||||
mpris_unwrap_message(message, DBUS_TYPE_DOUBLE, &m->property.volume);
|
if (message != NULL) {
|
||||||
dbus_message_unref(message);
|
mpris_unwrap_message(message, DBUS_TYPE_DOUBLE, &m->property.volume);
|
||||||
|
dbus_message_unref(message);
|
||||||
|
}
|
||||||
|
|
||||||
/* Property: Rate */
|
/* Property: Rate */
|
||||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Rate");
|
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Rate");
|
||||||
mpris_unwrap_message(message, DBUS_TYPE_DOUBLE, &m->property.rate);
|
if (message != NULL) {
|
||||||
dbus_message_unref(message);
|
mpris_unwrap_message(message, DBUS_TYPE_DOUBLE, &m->property.rate);
|
||||||
|
dbus_message_unref(message);
|
||||||
|
}
|
||||||
|
|
||||||
/* Property: Position */
|
/* Property: Position */
|
||||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Position");
|
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Position");
|
||||||
mpris_unwrap_message(message, DBUS_TYPE_INT64, &m->property.position_usec);
|
if (message != NULL) {
|
||||||
dbus_message_unref(message);
|
mpris_unwrap_message(message, DBUS_TYPE_INT64, &m->property.position_usec);
|
||||||
|
dbus_message_unref(message);
|
||||||
|
}
|
||||||
|
|
||||||
mtx_unlock(&mod->lock);
|
mtx_unlock(&mod->lock);
|
||||||
|
|
||||||
|
@ -543,16 +590,16 @@ run(struct module *mod)
|
||||||
|
|
||||||
/* TODO: Set up listener to catch disconnect events */
|
/* TODO: Set up listener to catch disconnect events */
|
||||||
if (m->target_bus_name == NULL) {
|
if (m->target_bus_name == NULL) {
|
||||||
m->target_bus_name = mpris_get_bus_name(m->connection, "ncspot");
|
m->target_bus_name = mpris_get_bus_name(m->connection, m->desired_bus_name);
|
||||||
if (m->target_bus_name == NULL) {
|
if (m->target_bus_name == NULL) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: This call might fail the the client does not
|
|
||||||
* respect the mpris spec */
|
|
||||||
DBusMessage *message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE, "Identity");
|
DBusMessage *message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE, "Identity");
|
||||||
mpris_unwrap_message(message, DBUS_TYPE_STRING, &m->target_identity);
|
if (message != NULL) {
|
||||||
LOG_DBG("Player identity: %s", m->target_identity);
|
mpris_unwrap_message(message, DBUS_TYPE_STRING, &m->desired_bus_name);
|
||||||
|
LOG_DBG("Player identity: %s", m->desired_bus_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aborted = !update_status(mod);
|
aborted = !update_status(mod);
|
||||||
|
@ -579,7 +626,7 @@ mpris_new(const char *identity, struct particle *label)
|
||||||
{
|
{
|
||||||
struct private *priv = calloc(1, sizeof(*priv));
|
struct private *priv = calloc(1, sizeof(*priv));
|
||||||
priv->label = label;
|
priv->label = label;
|
||||||
priv->target_identity = identity;
|
priv->desired_bus_name = strdup(identity);
|
||||||
|
|
||||||
struct module *mod = module_common_new();
|
struct module *mod = module_common_new();
|
||||||
mod->private = priv;
|
mod->private = priv;
|
||||||
|
|
Loading…
Add table
Reference in a new issue