Доброе утро!
Как это иногда бывает, шило бьёт не в то место, и делает разработчик что-то прикольное, но в целом бесполезное. В подобном порыве я решил, что хочу видеть информацию об играемой музыке прямо на рабочем столе. Чем выводить вопрос не стоял — пускай conky своё место отрабатывают. А вот откуда её брать — другой вопрос. Музыку мне играет чудо по имени DeaDBeeF. Сразу вспомнил, что тот умеет в плагины. Немножко погуглил, выяснил, что некоторые разработчики не умеют в документацию. К счастью, код плеера чем-то страшным не назовёшь, так что в данной заметке я опишу, с чего можно начать тем, кто хочет писать для этого прекрасного плеера.
Сам по себе плагин представляет некий разделяемый файл, который кладётся в $HOME/.local/lib/deadbeef/, откуда уже автоматом вытягивается плеером при запуске. Плеер вызовет функцию "имя_плагина_load", в которой можно сохранить себе указатель на структуру с данными плеера и передать ему список своих методов.
Согласно жиденькой документации, есть несколько типов плагинов. Ознакомиться можно по ссылке: https://github.com/DeaDBeeF-Player/deadbeef/wiki/Developing-your-own-plugin.
Далеко ходить не будем, для наших задач хватит типа «Misc» из списка по ссылке выше. Приведу весь код (слегка „подправленнй” пример из исходников самого плеера):
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/file.h>
#include <unistd.h>
#include <deadbeef/deadbeef.h>
static DB_functions_t *deadbeef;
#define METAINFO_FILE "/tmp/DB_current_track_metainfo"
#define PERROR(msg) \
fprintf(stderr, __FILE__":%d: "msg": %s\n", __LINE__, strerror(errno))
static int
dbmetainfo_message (uint32_t id, uintptr_t ctx, uint32_t p1, uint32_t p2) {
if (id == DB_EV_SONGSTARTED) {
int fd = open(METAINFO_FILE,
O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
);
if (-1 == fd) {
PERROR("open");
return -1;
}
if (-1 == flock(fd, LOCK_EX)) {
PERROR("flock");
return -2;
}
DB_playItem_t *it = deadbeef->streamer_get_playing_track();
if (NULL == it) return 0;
struct DB_metaInfo_s *mi = deadbeef->pl_get_metadata_head(it);
if (NULL == mi) return 0;
while (mi) {
if (mi->key[0] != ':') {
char buf[1024];
snprintf(buf, sizeof buf, "%s: %s\n", mi->key, mi->value);
if (-1 == write(fd, buf, strlen(buf))) {
PERROR("write");
flock(fd, LOCK_UN);
close(fd);
return -3;
}
}
mi = mi->next;
}
flock(fd, LOCK_UN);
close(fd);
return 0;
}
return 1;
}
static int
dbmetainfo_stop (void) {
if (-1 == unlink(METAINFO_FILE)) {
PERROR("unlink");
return -1;
}
return 0;
}
DB_misc_t plugin = {
.plugin.api_vmajor = DB_API_VERSION_MAJOR,
.plugin.api_vminor = DB_API_VERSION_MINOR,
.plugin.type = DB_PLUGIN_MISC,
.plugin.version_major = 1,
.plugin.version_minor = 0,
.plugin.id = "dbmetainfo",
.plugin.name ="Track's metainfo exporter",
.plugin.descr = "Track's metainfo exporter plugin",
.plugin.copyright = "Valeriy Kireev, 2018, free for use and modification",
.plugin.message = dbmetainfo_message,
.plugin.stop = dbmetainfo_stop
};
DB_plugin_t *
dbmetainfo_load (DB_functions_t *ddb) {
deadbeef = ddb;
return DB_PLUGIN(&plugin);
}
Начну как получится. Функция dbmetainfo_load() принимает структуру с API плеера, а возвращает — структуру со своим. Самых интересных здесь два поля: .plugin.stop — функция, которая будет вызвана при завершении и .plugin.message — обработчик событий плеера.
Если с первой всё понятно, то о второй стоит чуть подробнее. Суть такая: есть ряд событий (трек сменился, громкость поменяли…), а .plugin.message содержит указатель на функцию обратного вызова, обращение к которой произойдёт в случае происшествия этих событий.
Как можно видеть из кода, я реагирую только на событие с говорящим именем DB_EV_SONGSTARTED. Информацию об очередном треке можно достать из структуры DB_playItem, которую, в свою очередь, отдаст метод API streamer_get_playing_track(). Оттуда можно получить указатель на структуру с метаинформацией. Это и есть самое интересное — теги. Сначала идут «нормальные», потом :URI — идентификатор файла (ну или путь в файловой системе, в типовом случае) и ряд других тегов, начинающихся на двоеточие, которые в рамках решаемой задачи неинтересны и могут быть опущены. Сами теги с точки зрения кода есть ничто иное, кроме как связанный список.
Весь остальной код служит для создания файла, записи в него данных и удаления его на выключение плеера.
Теперь подставим какую-нибудь подобную строчку в ~/.conkyrc:
${hr}
${font Linux Biolinum O-9}${color gray}${execi 2 sed -e 's/^\(.\{1,66\}\).*$/\1/' /tmp/DB_current_track_metainfo | sort} $font
и вот у нас есть новый красивый блок в коньках:
