tools

various tools I have been using throughout the years
Log | Files | Refs | README | LICENSE

mpd_now_playing.c (6399B)


      1 #define _POSIX_C_SOURCE 200809L
      2 
      3 #include "mpd_common.h"
      4 
      5 #include <errno.h>
      6 #include <signal.h>
      7 #include <string.h>
      8 #include <time.h>
      9 
     10 static volatile sig_atomic_t received_signal;
     11 
     12 static void handle_signal(int signal_number) {
     13   received_signal = signal_number;
     14 }
     15 
     16 static void usage(FILE *file, const char *program) {
     17   fprintf(file, "Usage: %s [--watch] [--json]\n", program);
     18 }
     19 
     20 static const char *state_name(enum mpd_state state) {
     21   switch (state) {
     22   case MPD_STATE_STOP:
     23     return "stopped";
     24   case MPD_STATE_PLAY:
     25     return "playing";
     26   case MPD_STATE_PAUSE:
     27     return "paused";
     28   case MPD_STATE_UNKNOWN:
     29   default:
     30     return "unknown";
     31   }
     32 }
     33 
     34 static void print_json_string(const char *value) {
     35   if (value == NULL) {
     36     fputs("null", stdout);
     37     return;
     38   }
     39   putchar('"');
     40   for (const unsigned char *cursor = (const unsigned char *)value;
     41        *cursor != '\0'; cursor++) {
     42     switch (*cursor) {
     43     case '"':
     44       fputs("\\\"", stdout);
     45       break;
     46     case '\\':
     47       fputs("\\\\", stdout);
     48       break;
     49     case '\b':
     50       fputs("\\b", stdout);
     51       break;
     52     case '\f':
     53       fputs("\\f", stdout);
     54       break;
     55     case '\n':
     56       fputs("\\n", stdout);
     57       break;
     58     case '\r':
     59       fputs("\\r", stdout);
     60       break;
     61     case '\t':
     62       fputs("\\t", stdout);
     63       break;
     64     default:
     65       if (*cursor < 0x20U)
     66         printf("\\u%04x", (unsigned)*cursor);
     67       else
     68         putchar((int)*cursor);
     69     }
     70   }
     71   putchar('"');
     72 }
     73 
     74 static bool emit_snapshot(struct mpd_connection *connection, bool json) {
     75   struct mpd_status *status = mpd_run_status(connection);
     76   if (status == NULL) {
     77     mt_report_error(connection, "get now-playing status");
     78     return false;
     79   }
     80   enum mpd_state state = mpd_status_get_state(status);
     81   if (state != MPD_STATE_PLAY) {
     82     mpd_status_free(status);
     83     return true;
     84   }
     85   int position = mpd_status_get_song_pos(status);
     86   int song_id = mpd_status_get_song_id(status);
     87   unsigned elapsed_ms = mpd_status_get_elapsed_ms(status);
     88   unsigned duration = mpd_status_get_total_time(status);
     89   mpd_status_free(status);
     90 
     91   struct mpd_song *song = mpd_run_current_song(connection);
     92   if (song == NULL &&
     93       mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) {
     94     mt_report_error(connection, "get now-playing song");
     95     return false;
     96   }
     97 
     98   const char *artist =
     99       song == NULL ? NULL : mpd_song_get_tag(song, MPD_TAG_ARTIST, 0U);
    100   const char *album_artist =
    101       song == NULL ? NULL : mpd_song_get_tag(song, MPD_TAG_ALBUM_ARTIST, 0U);
    102   const char *album =
    103       song == NULL ? NULL : mpd_song_get_tag(song, MPD_TAG_ALBUM, 0U);
    104   const char *title =
    105       song == NULL ? NULL : mpd_song_get_tag(song, MPD_TAG_TITLE, 0U);
    106   const char *uri = song == NULL ? NULL : mpd_song_get_uri(song);
    107 
    108   if (json) {
    109     fputs("{\"state\":", stdout);
    110     print_json_string(state_name(state));
    111     fputs(",\"artist\":", stdout);
    112     print_json_string(artist);
    113     fputs(",\"album_artist\":", stdout);
    114     print_json_string(album_artist);
    115     fputs(",\"album\":", stdout);
    116     print_json_string(album);
    117     fputs(",\"title\":", stdout);
    118     print_json_string(title);
    119     fputs(",\"uri\":", stdout);
    120     print_json_string(uri);
    121     fputs(",\"elapsed_seconds\":", stdout);
    122     if (song_id < 0)
    123       fputs("null", stdout);
    124     else
    125       printf("%.3f", (double)elapsed_ms / 1000.0);
    126     fputs(",\"duration_seconds\":", stdout);
    127     if (song_id < 0 || duration == 0U)
    128       fputs("null", stdout);
    129     else
    130       printf("%u", duration);
    131     fputs(",\"queue_position\":", stdout);
    132     if (position < 0)
    133       fputs("null", stdout);
    134     else
    135       printf("%d", position);
    136     fputs(",\"song_id\":", stdout);
    137     if (song_id < 0)
    138       fputs("null", stdout);
    139     else
    140       printf("%d", song_id);
    141     fputs("}\n", stdout);
    142   } else if (artist != NULL && title != NULL) {
    143     printf("%s - %s\n", artist, title);
    144   } else if (title != NULL) {
    145     printf("%s\n", title);
    146   } else if (uri != NULL) {
    147     printf("%s\n", uri);
    148   } else {
    149     printf("%s\n", state_name(state));
    150   }
    151 
    152   if (song != NULL)
    153     mpd_song_free(song);
    154   if (fflush(stdout) != 0) {
    155     perror("mpd: flush now-playing output");
    156     return false;
    157   }
    158   return true;
    159 }
    160 
    161 static void reconnect_delay(unsigned failures) {
    162   unsigned milliseconds = failures < 5U ? 250U << failures : 4000U;
    163   if (milliseconds > 4000U)
    164     milliseconds = 4000U;
    165   struct timespec delay = {.tv_sec = (time_t)(milliseconds / 1000U),
    166                            .tv_nsec = (long)(milliseconds % 1000U) * 1000000L};
    167   while (received_signal == 0 && nanosleep(&delay, &delay) < 0 &&
    168          errno == EINTR) {
    169   }
    170 }
    171 
    172 int main(int argc, char **argv) {
    173   bool watch = false;
    174   bool json = false;
    175   for (int i = 1; i < argc; i++) {
    176     if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
    177       usage(stdout, argv[0]);
    178       return 0;
    179     }
    180     if (strcmp(argv[i], "--watch") == 0) {
    181       watch = true;
    182     } else if (strcmp(argv[i], "--json") == 0) {
    183       json = true;
    184     } else {
    185       usage(stderr, argv[0]);
    186       return 2;
    187     }
    188   }
    189 
    190   if (!watch) {
    191     struct mpd_connection *connection = mt_connect();
    192     if (connection == NULL)
    193       return 1;
    194     bool ok = emit_snapshot(connection, json);
    195     mpd_connection_free(connection);
    196     return ok ? 0 : 1;
    197   }
    198 
    199   struct sigaction action;
    200   memset(&action, 0, sizeof(action));
    201   action.sa_handler = handle_signal;
    202   sigemptyset(&action.sa_mask);
    203   if (sigaction(SIGINT, &action, NULL) != 0 ||
    204       sigaction(SIGTERM, &action, NULL) != 0) {
    205     perror("mpd: install signal handlers");
    206     return 1;
    207   }
    208 
    209   unsigned failures = 0U;
    210   while (received_signal == 0) {
    211     struct mpd_connection *connection = mt_connect();
    212     if (connection == NULL) {
    213       reconnect_delay(failures++);
    214       continue;
    215     }
    216     if (!emit_snapshot(connection, json)) {
    217       mpd_connection_free(connection);
    218       reconnect_delay(failures++);
    219       continue;
    220     }
    221     failures = 0U;
    222 
    223     while (received_signal == 0) {
    224       enum mpd_idle events =
    225           mpd_run_idle_mask(connection, MPD_IDLE_PLAYER | MPD_IDLE_QUEUE);
    226       if (received_signal != 0)
    227         break;
    228       if ((events & (MPD_IDLE_PLAYER | MPD_IDLE_QUEUE)) == 0)
    229         break;
    230       if (!emit_snapshot(connection, json))
    231         break;
    232     }
    233     mpd_connection_free(connection);
    234     if (received_signal == 0)
    235       reconnect_delay(failures++);
    236   }
    237   return 128 + (int)received_signal;
    238 }