mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-22 12:25:38 +02:00
Added 'MPRIS' module
This commit adds the ability to display status information for MPRIS compatible music players. I've adapted most of the naming conventions (and some code) from the MPD module.
This commit is contained in:
parent
739dc30323
commit
1b8962d296
5 changed files with 639 additions and 0 deletions
|
@ -178,6 +178,7 @@ summary(
|
|||
'Foreign toplevel (window tracking for Wayland)': plugin_foreign_toplevel_enabled,
|
||||
'Memory monitoring': plugin_mem_enabled,
|
||||
'Music Player Daemon (MPD)': plugin_mpd_enabled,
|
||||
'Media Player Remote Interface Specificaion (MPRIS)': plugin_mpris_enabled,
|
||||
'i3+Sway': plugin_i3_enabled,
|
||||
'Label': plugin_label_enabled,
|
||||
'Network monitoring': plugin_network_enabled,
|
||||
|
|
|
@ -26,6 +26,8 @@ option('plugin-mem', type: 'feature', value: 'auto',
|
|||
description: 'Memory monitoring support')
|
||||
option('plugin-mpd', type: 'feature', value: 'auto',
|
||||
description: 'Music Player Daemon (MPD) support')
|
||||
option('plugin-mpris', type: 'feature', value: 'enabled',
|
||||
description: 'Media Player Remote Interface Specificaion (MPRIS) support')
|
||||
option('plugin-i3', type: 'feature', value: 'auto',
|
||||
description: 'i3+Sway support')
|
||||
option('plugin-label', type: 'feature', value: 'auto',
|
||||
|
|
|
@ -22,6 +22,9 @@ 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()
|
||||
|
||||
json_i3 = dependency('json-c', required: get_option('plugin-i3'))
|
||||
plugin_i3_enabled = json_i3.found()
|
||||
|
||||
|
@ -89,6 +92,10 @@ if plugin_mpd_enabled
|
|||
mod_data += {'mpd': [[], [mpd]]}
|
||||
endif
|
||||
|
||||
if plugin_mpris_enabled
|
||||
mod_data += {'mpris': [[], [mpris]]}
|
||||
endif
|
||||
|
||||
if plugin_i3_enabled
|
||||
mod_data += {'i3': [['i3-common.c', 'i3-common.h'], [dynlist, json_i3]]}
|
||||
endif
|
||||
|
|
623
modules/mpris.c
Normal file
623
modules/mpris.c
Normal file
|
@ -0,0 +1,623 @@
|
|||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <libgen.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
|
||||
#define LOG_MODULE "mpris"
|
||||
#define LOG_ENABLE_DBG 1
|
||||
#include "../bar/bar.h"
|
||||
#include "../config-verify.h"
|
||||
#include "../config.h"
|
||||
#include "../log.h"
|
||||
#include "../plugin.h"
|
||||
|
||||
enum mpris_playback_state {
|
||||
MPRIS_PLAYBACK_INVALID,
|
||||
MPRIS_PLAYBACK_STOPPED,
|
||||
MPRIS_PLAYBACK_PLAYING,
|
||||
MPRIS_PLAYBACK_PAUSED,
|
||||
};
|
||||
|
||||
enum mpris_loop_state {
|
||||
MPRIS_LOOP_INVALID,
|
||||
MPRIS_LOOP_NONE,
|
||||
MPRIS_LOOP_TRACK,
|
||||
MPRIS_LOOP_PLAYLIST,
|
||||
};
|
||||
|
||||
struct mpris_metadata {
|
||||
uint64_t length_nsec;
|
||||
char *trackid;
|
||||
char *artists;
|
||||
char *album;
|
||||
char *title;
|
||||
};
|
||||
|
||||
struct mpris_property {
|
||||
enum mpris_playback_state playback_status;
|
||||
enum mpris_loop_state loop_status;
|
||||
struct mpris_metadata metadata;
|
||||
uint64_t position_usec;
|
||||
double rate;
|
||||
double volume;
|
||||
bool shuffle;
|
||||
};
|
||||
|
||||
struct private
|
||||
{
|
||||
struct particle *label;
|
||||
/* TODO: This should be an array of options */
|
||||
const char *target_identity;
|
||||
DBusConnection *connection;
|
||||
|
||||
const char *target_bus_name;
|
||||
const char *target_bus_identity;
|
||||
|
||||
uint64_t previous_position_usec;
|
||||
struct mpris_property property;
|
||||
|
||||
struct {
|
||||
uint64_t value_ns;
|
||||
struct timespec when;
|
||||
} elapsed;
|
||||
|
||||
thrd_t refresh_thread_id;
|
||||
int refresh_abort_fd;
|
||||
};
|
||||
|
||||
/* DBus specific */
|
||||
|
||||
#define MPRIS_QUERY_TIMEOUT 50
|
||||
|
||||
#define MPRIS_PATH "/org/mpris/MediaPlayer2"
|
||||
#define MPRIS_BUS "org.mpris.MediaPlayer2"
|
||||
#define MPRIS_INTERFACE "org.mpris.MediaPlayer2"
|
||||
#define MPRIS_INTERFACE_PLAYER MPRIS_INTERFACE ".Player"
|
||||
|
||||
#define MPRIS_DBUS_PATH "/org/mpris/MediaPlayer2"
|
||||
#define MPRIS_DBUS_BUS "org.freedesktop.DBus"
|
||||
#define MPRIS_DBUS_INTERFACE "org.freedesktop.DBus"
|
||||
|
||||
/* TODO: Don't block */
|
||||
static DBusMessage *
|
||||
mpris_call_method_and_block(DBusConnection *connection, DBusMessage *message)
|
||||
{
|
||||
DBusPendingCall *pending = NULL;
|
||||
|
||||
if (!dbus_connection_send_with_reply(connection, message, &pending, MPRIS_QUERY_TIMEOUT)) {
|
||||
LOG_ERR("dbus: error: failed to allocate message object");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dbus_pending_call_block(pending);
|
||||
dbus_message_unref(message);
|
||||
|
||||
/* Handle and gracrfully return different error types
|
||||
* ('org.freedesktop.DBus.Error.NotSupported' etc.)*/
|
||||
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
|
||||
if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
|
||||
const char *error_name = dbus_message_get_error_name(reply);
|
||||
if (dbus_message_is_error(reply, error_name)) {
|
||||
DBusError error = {0};
|
||||
LOG_ERR("Message call returned an error: %s", error_name);
|
||||
if (dbus_set_error_from_message(&error, message)) {
|
||||
LOG_ERR("Report: %s", error.message);
|
||||
}
|
||||
dbus_error_free(&error);
|
||||
dbus_pending_call_unref(pending);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
dbus_pending_call_unref(pending);
|
||||
return reply;
|
||||
}
|
||||
|
||||
__attribute__((unused)) static DBusMessage *
|
||||
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(interface != NULL && strlen(interface) > 0);
|
||||
assert(property_name != NULL && strlen(property_name) > 0);
|
||||
|
||||
DBusMessage *message
|
||||
= dbus_message_new_method_call(bus_name, MPRIS_PATH, MPRIS_DBUS_INTERFACE ".Properties", "Get");
|
||||
dbus_message_append_args(message, DBUS_TYPE_STRING, &interface, DBUS_TYPE_STRING, &property_name,
|
||||
DBUS_TYPE_INVALID);
|
||||
return mpris_call_method_and_block(connection, message);
|
||||
}
|
||||
|
||||
static char *
|
||||
mpris_get_bus_name(DBusConnection *connection, const char *identity_name)
|
||||
{
|
||||
assert(identity_name != NULL && strlen(identity_name) > 0);
|
||||
|
||||
DBusMessage *message
|
||||
= dbus_message_new_method_call(MPRIS_DBUS_BUS, MPRIS_DBUS_PATH, MPRIS_DBUS_INTERFACE, "ListNames");
|
||||
DBusMessage *reply = mpris_call_method_and_block(connection, message);
|
||||
|
||||
if (reply == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DBusError error = {0};
|
||||
char **bus_names;
|
||||
dbus_int32_t bus_count;
|
||||
|
||||
dbus_error_init(&error);
|
||||
if (!dbus_message_get_args(reply, &error, DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &bus_names, &bus_count,
|
||||
DBUS_TYPE_INVALID)) {
|
||||
LOG_ERR("%s", error.message);
|
||||
dbus_error_free(&error);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (bus_count == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *string = NULL;
|
||||
for (dbus_int32_t i = 0; i < bus_count; i++) {
|
||||
if (strlen(bus_names[i]) < strlen(MPRIS_BUS ".") + strlen(identity_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strncmp(bus_names[i] + strlen(MPRIS_BUS "."), identity_name, strlen(identity_name)) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string = strdup(bus_names[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
dbus_free_string_array(bus_names);
|
||||
dbus_error_free(&error);
|
||||
dbus_message_unref(reply);
|
||||
|
||||
LOG_DBG("Found bus name: %s", string);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
static bool
|
||||
mpris_unwrap_iter(DBusMessageIter *iter, dbus_int32_t type, void *target)
|
||||
{
|
||||
DBusMessageIter type_iter = {0};
|
||||
|
||||
assert(dbus_message_iter_get_arg_type(iter) == DBUS_TYPE_VARIANT);
|
||||
dbus_message_iter_recurse(iter, &type_iter);
|
||||
char *tmp = dbus_message_iter_get_signature(&type_iter);
|
||||
(void)tmp;
|
||||
assert(dbus_message_iter_get_arg_type(&type_iter) == type);
|
||||
bool status = !dbus_message_iter_has_next(&type_iter);
|
||||
|
||||
DBusBasicValue value = {0};
|
||||
switch (type) {
|
||||
case DBUS_TYPE_STRING:
|
||||
dbus_message_iter_get_basic(&type_iter, &value);
|
||||
*((char **)target) = strdup(value.str);
|
||||
break;
|
||||
case DBUS_TYPE_DOUBLE:
|
||||
dbus_message_iter_get_basic(&type_iter, &value);
|
||||
*((double *)target) = value.dbl;
|
||||
break;
|
||||
case DBUS_TYPE_BOOLEAN:
|
||||
dbus_message_iter_get_basic(&type_iter, &value);
|
||||
*((bool *)target) = value.bool_val;
|
||||
break;
|
||||
case DBUS_TYPE_INT64:
|
||||
dbus_message_iter_get_basic(&type_iter, &value);
|
||||
*((int64_t *)target) = value.i64;
|
||||
break;
|
||||
default:;
|
||||
char *signature = dbus_message_iter_get_signature(&type_iter);
|
||||
LOG_WARN("Trying to unwrap unsupported type: %s", signature);
|
||||
dbus_free(signature);
|
||||
status = false;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool
|
||||
mpris_unwrap_message(DBusMessage *message, dbus_int32_t type, void *target)
|
||||
{
|
||||
assert(message != NULL);
|
||||
DBusMessageIter iter = {0};
|
||||
dbus_message_iter_init(message, &iter);
|
||||
return mpris_unwrap_iter(&iter, type, target);
|
||||
}
|
||||
|
||||
static bool
|
||||
mpris_metadata_parse(const char *entry_name, DBusMessageIter *entry_iter, struct mpris_metadata *buffer)
|
||||
{
|
||||
const char *string_value = NULL;
|
||||
DBusMessageIter array_iter = {0};
|
||||
|
||||
if (strcmp(entry_name, "mpris:trackid") == 0) {
|
||||
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_OBJECT_PATH);
|
||||
dbus_message_iter_get_basic(entry_iter, &string_value);
|
||||
buffer->trackid = strdup(string_value);
|
||||
|
||||
} else if (strcmp(entry_name, "xesam:album") == 0) {
|
||||
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_STRING);
|
||||
dbus_message_iter_get_basic(entry_iter, &string_value);
|
||||
buffer->album = strdup(string_value);
|
||||
|
||||
} else if (strcmp(entry_name, "xesam:artist") == 0) {
|
||||
/* TODO: Propertly format string arrays */
|
||||
/* NOTE: Currently, only the first artist will be shown, as we
|
||||
* ignore the rest */
|
||||
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_ARRAY);
|
||||
dbus_message_iter_recurse(entry_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(entry_name, "xesam:title") == 0) {
|
||||
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_STRING);
|
||||
dbus_message_iter_get_basic(entry_iter, &string_value);
|
||||
buffer->title = strdup(string_value);
|
||||
|
||||
} else if (strcmp(entry_name, "mpris:length") == 0) {
|
||||
assert(dbus_message_iter_get_arg_type(entry_iter) == DBUS_TYPE_INT64);
|
||||
dbus_message_iter_get_basic(entry_iter, &buffer->length_nsec);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
mpris_unwrap_metadata_message(DBusMessage *message, struct mpris_metadata *metadata)
|
||||
{
|
||||
bool status = true;
|
||||
|
||||
/* Unpack values returned from DBus method calls */
|
||||
assert(dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_METHOD_RETURN);
|
||||
DBusMessageIter message_iter = {0}, outer_array_iter = {0}, array_iter = {0};
|
||||
dbus_message_iter_init(message, &message_iter);
|
||||
assert(dbus_message_iter_get_arg_type(&message_iter) == DBUS_TYPE_VARIANT);
|
||||
dbus_message_iter_recurse(&message_iter, &outer_array_iter);
|
||||
assert(dbus_message_iter_get_arg_type(&outer_array_iter) == DBUS_TYPE_ARRAY);
|
||||
dbus_message_iter_recurse(&outer_array_iter, &array_iter);
|
||||
|
||||
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);
|
||||
|
||||
const char *entry_name = NULL;
|
||||
DBusMessageIter entry_iter = {0}, entry_sub_iter = {0};
|
||||
|
||||
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);
|
||||
|
||||
dbus_message_iter_next(&entry_iter);
|
||||
dbus_message_iter_recurse(&entry_iter, &entry_sub_iter);
|
||||
mpris_metadata_parse(entry_name, &entry_sub_iter, metadata);
|
||||
|
||||
dbus_message_iter_next(&array_iter);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/* ------------- */
|
||||
|
||||
static void
|
||||
mpris_clear(struct mpris_property *property)
|
||||
{
|
||||
struct mpris_metadata *metadata = &property->metadata;
|
||||
if (metadata->album != NULL) {
|
||||
free(metadata->album);
|
||||
}
|
||||
if (metadata->artists != NULL) {
|
||||
free(metadata->artists);
|
||||
}
|
||||
if (metadata->title != NULL) {
|
||||
free(metadata->title);
|
||||
}
|
||||
|
||||
memset(property, 0, sizeof(*property));
|
||||
}
|
||||
|
||||
static void
|
||||
secs_to_str(unsigned secs, char *s, size_t sz)
|
||||
{
|
||||
unsigned hours = secs / (60 * 60);
|
||||
unsigned minutes = secs % (60 * 60) / 60;
|
||||
secs %= 60;
|
||||
|
||||
if (hours > 0)
|
||||
snprintf(s, sz, "%02u:%02u:%02u", hours, minutes, secs);
|
||||
else
|
||||
snprintf(s, sz, "%02u:%02u", minutes, secs);
|
||||
}
|
||||
|
||||
static void
|
||||
destroy(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
dbus_connection_close(m->connection);
|
||||
|
||||
free((void *)m->target_bus_name);
|
||||
free((void *)m->target_identity);
|
||||
|
||||
mpris_clear(&m->property);
|
||||
|
||||
m->label->destroy(m->label);
|
||||
|
||||
free(m);
|
||||
module_default_destroy(mod);
|
||||
}
|
||||
|
||||
static const char *
|
||||
description(const struct module *mod)
|
||||
{
|
||||
return "mpris";
|
||||
}
|
||||
|
||||
static struct exposable *
|
||||
content(struct module *mod)
|
||||
{
|
||||
const struct private *m = mod->private;
|
||||
const struct mpris_metadata metadata = m->property.metadata;
|
||||
|
||||
/* usec -> msec -> sec */
|
||||
uint32_t position_sec = m->property.position_usec / 1000 / 1000;
|
||||
uint32_t length_sec = metadata.length_nsec / 1000 / 1000;
|
||||
|
||||
char pos[16], end[16];
|
||||
secs_to_str(position_sec, pos, sizeof(pos));
|
||||
secs_to_str(length_sec, end, sizeof(end));
|
||||
|
||||
char *playback_str = NULL;
|
||||
switch (m->property.playback_status) {
|
||||
case MPRIS_PLAYBACK_STOPPED:
|
||||
playback_str = "stopped";
|
||||
break;
|
||||
case MPRIS_PLAYBACK_PLAYING:
|
||||
playback_str = "playing";
|
||||
break;
|
||||
case MPRIS_PLAYBACK_PAUSED:
|
||||
playback_str = "paused";
|
||||
break;
|
||||
case MPRIS_PLAYBACK_INVALID:
|
||||
playback_str = "offline";
|
||||
}
|
||||
|
||||
char *loop_str = NULL;
|
||||
switch (m->property.loop_status) {
|
||||
case MPRIS_LOOP_NONE:
|
||||
loop_str = "none";
|
||||
break;
|
||||
case MPRIS_LOOP_TRACK:
|
||||
loop_str = "track";
|
||||
break;
|
||||
case MPRIS_LOOP_PLAYLIST:
|
||||
loop_str = "playlist";
|
||||
break;
|
||||
default:
|
||||
loop_str = "";
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t volume = (m->property.volume >= 0.995) ? 100 : 100 * m->property.volume;
|
||||
|
||||
struct tag_set tags = {
|
||||
.tags = (struct tag *[]){
|
||||
tag_new_string(mod, "state", playback_str),
|
||||
tag_new_string(mod, "identity", m->target_identity),
|
||||
/* Stay consistent with existing modules naming
|
||||
* conventions (mpd)? */
|
||||
tag_new_bool(mod, "random", m->property.shuffle),
|
||||
tag_new_string(mod, "loop", loop_str),
|
||||
tag_new_int_range(mod, "volume", volume, 0, 100),
|
||||
tag_new_string(mod, "album", metadata.album),
|
||||
tag_new_string(mod, "artist", metadata.artists),
|
||||
tag_new_string(mod, "title", metadata.title),
|
||||
tag_new_string(mod, "pos", pos),
|
||||
tag_new_string(mod, "end", end),
|
||||
tag_new_int_realtime(
|
||||
mod, "elapsed", position_sec, 0, length_sec, TAG_REALTIME_SECS),
|
||||
},
|
||||
.count = 11,
|
||||
};
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
struct exposable *exposable = m->label->instantiate(m->label, &tags);
|
||||
|
||||
tag_set_destroy(&tags);
|
||||
return exposable;
|
||||
}
|
||||
|
||||
static bool
|
||||
update_status(struct module *mod)
|
||||
{
|
||||
struct private *m = mod->private;
|
||||
mtx_lock(&mod->lock);
|
||||
|
||||
/* Property: Metadata */
|
||||
mpris_clear(&m->property);
|
||||
DBusMessage *message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "Metadata");
|
||||
mpris_unwrap_metadata_message(message, &m->property.metadata);
|
||||
dbus_message_unref(message);
|
||||
|
||||
/* Update remaining properties */
|
||||
/* Property: PlaybackStatus */
|
||||
char *string = NULL;
|
||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "PlaybackStatus");
|
||||
mpris_unwrap_message(message, DBUS_TYPE_STRING, &string);
|
||||
if (strcmp(string, "Stopped")) {
|
||||
m->property.playback_status = MPRIS_PLAYBACK_STOPPED;
|
||||
} else if (strcmp(string, "Paused")) {
|
||||
m->property.playback_status = MPRIS_PLAYBACK_PAUSED;
|
||||
} else if (strcmp(string, "Playing")) {
|
||||
m->property.playback_status = MPRIS_PLAYBACK_PLAYING;
|
||||
}
|
||||
dbus_message_unref(message);
|
||||
|
||||
/* Property: LoopStatus */
|
||||
message = mpris_get_property(m->connection, m->target_bus_name, MPRIS_INTERFACE_PLAYER, "LoopStatus");
|
||||
mpris_unwrap_message(message, DBUS_TYPE_STRING, &string);
|
||||
if (strcmp(string, "None")) {
|
||||
m->property.loop_status = MPRIS_LOOP_NONE;
|
||||
} else if (strcmp(string, "Track")) {
|
||||
m->property.loop_status = MPRIS_LOOP_TRACK;
|
||||
} else if (strcmp(string, "Playlist")) {
|
||||
m->property.loop_status = MPRIS_LOOP_PLAYLIST;
|
||||
}
|
||||
dbus_message_unref(message);
|
||||
|
||||
/* Property: 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);
|
||||
dbus_message_unref(message);
|
||||
|
||||
/* Property: 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);
|
||||
dbus_message_unref(message);
|
||||
|
||||
/* Property: 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);
|
||||
dbus_message_unref(message);
|
||||
|
||||
mtx_unlock(&mod->lock);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int
|
||||
run(struct module *mod)
|
||||
{
|
||||
/*const struct private *m = mod->private;*/
|
||||
const struct bar *bar = mod->bar;
|
||||
struct private *m = mod->private;
|
||||
|
||||
DBusError error = {0};
|
||||
DBusConnection *connection = dbus_bus_get_private(DBUS_BUS_SESSION, &error);
|
||||
if (dbus_error_is_set(&error)) {
|
||||
LOG_ERR("Failed to connect to session bus: %s", error.message);
|
||||
return -1;
|
||||
}
|
||||
m->connection = connection;
|
||||
|
||||
int ret = 0;
|
||||
bool aborted = false;
|
||||
while (ret == 0 && !aborted) {
|
||||
const uint32_t timeout_ms = 250;
|
||||
|
||||
struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}};
|
||||
if (poll(fds, 1, timeout_ms) < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
|
||||
LOG_ERRNO("failed to poll");
|
||||
break;
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
aborted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: Set up listener to catch disconnect events */
|
||||
if (m->target_bus_name == NULL) {
|
||||
m->target_bus_name = mpris_get_bus_name(m->connection, "ncspot");
|
||||
if (m->target_bus_name == NULL) {
|
||||
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");
|
||||
mpris_unwrap_message(message, DBUS_TYPE_STRING, &m->target_identity);
|
||||
LOG_DBG("Player identity: %s", m->target_identity);
|
||||
}
|
||||
|
||||
aborted = !update_status(mod);
|
||||
bar->refresh(bar);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct refresh_context {
|
||||
struct module *mod;
|
||||
int abort_fd;
|
||||
long milli_seconds;
|
||||
};
|
||||
|
||||
static bool
|
||||
refresh_in(struct module *mod, long milli_seconds)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
mpris_new(const char *identity, struct particle *label)
|
||||
{
|
||||
struct private *priv = calloc(1, sizeof(*priv));
|
||||
priv->label = label;
|
||||
priv->target_identity = identity;
|
||||
|
||||
struct module *mod = module_common_new();
|
||||
mod->private = priv;
|
||||
mod->run = &run;
|
||||
mod->destroy = &destroy;
|
||||
mod->content = &content;
|
||||
mod->refresh_in = &refresh_in;
|
||||
mod->description = &description;
|
||||
return mod;
|
||||
}
|
||||
|
||||
static struct module *
|
||||
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 *c = yml_get_value(node, "content");
|
||||
|
||||
return mpris_new(yml_value_as_string(identity), conf_to_particle(c, inherited));
|
||||
}
|
||||
|
||||
static bool
|
||||
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[] = {
|
||||
{"identity", true, &conf_verify_string},
|
||||
MODULE_COMMON_ATTRS,
|
||||
};
|
||||
|
||||
return conf_verify_dict(chain, node, attrs);
|
||||
}
|
||||
|
||||
const struct module_iface module_mpris_iface = {
|
||||
.verify_conf = &verify_conf,
|
||||
.from_conf = &from_conf,
|
||||
};
|
||||
|
||||
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||
extern const struct module_iface iface __attribute__((weak, alias("module_mpd_iface")));
|
||||
#endif
|
6
plugin.c
6
plugin.c
|
@ -57,6 +57,9 @@ EXTERN_MODULE(mem);
|
|||
#if defined(HAVE_PLUGIN_mpd)
|
||||
EXTERN_MODULE(mpd);
|
||||
#endif
|
||||
#if defined(HAVE_PLUGIN_mpris)
|
||||
EXTERN_MODULE(mpris);
|
||||
#endif
|
||||
#if defined(HAVE_PLUGIN_i3)
|
||||
EXTERN_MODULE(i3);
|
||||
#endif
|
||||
|
@ -187,6 +190,9 @@ static void __attribute__((constructor)) init(void)
|
|||
#if defined(HAVE_PLUGIN_mpd)
|
||||
REGISTER_CORE_MODULE(mpd, mpd);
|
||||
#endif
|
||||
#if defined(HAVE_PLUGIN_mpris)
|
||||
REGISTER_CORE_MODULE(mpris, mpris);
|
||||
#endif
|
||||
#if defined(HAVE_PLUGIN_i3)
|
||||
REGISTER_CORE_MODULE(i3, i3);
|
||||
#endif
|
||||
|
|
Loading…
Add table
Reference in a new issue