Довесок к мёртвой говядине

Доброе утро!

Как это иногда бывает, шило бьёт не в то место, и делает разработчик что-то прикольное, но в целом бесполезное. В подобном порыве я решил, что хочу видеть информацию об играемой музыке прямо на рабочем столе. Чем выводить вопрос не стоял — пускай 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 

и вот у нас есть новый красивый блок в коньках: