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 }