QUOTE: Kindness is always fashionable.

rx

A terminal-based radio player

rx.c (6956B)


      1#include <stdio.h>
      2#include <stdlib.h>
      3#include <string.h>
      4#include <dlfcn.h>
      5#include <math.h>
      6#include <mpv/client.h>
      7#define LIB_TERM_IMPL
      8#include <libterm.h>
      9#define LIB_STR_IMPL
     10#include <libstr.h>
     11
     12#include "../include/rx.h"
     13#define INI_IMPL
     14#include "../include/ini.h"
     15
     16static int rx_ini_load(void *user, const char *section, const char *name, const char *value) {
     17  rx_t *self = (rx_t *) user;
     18  if (strcmp(section, "stations") == 0) {
     19    if (strcmp(name, "uid") == 0) {
     20      self->stations = realloc(self->stations, sizeof(rx_station_t) * (self->stations_len + 1));
     21      self->stations[self->stations_len] = (rx_station_t) {
     22        .addr = NULL,
     23        .url = NULL,
     24        .resolver = NULL,
     25      };
     26      self->stations_len++;
     27    }
     28    rx_station_t *station = &self->stations[self->stations_len - 1];
     29    if (strcmp(name, "uid") == 0) {
     30      size_t len = strlen(value);
     31      memcpy(station->uid, value, len);
     32      station->uid[len] = '\0';
     33    }
     34    if (strcmp(name, "name") == 0) {
     35      size_t len = strlen(value);
     36      memcpy(station->name, value, len);
     37      station->name[len] = '\0';
     38    }
     39    if (strcmp(name, "addr") == 0) {
     40      size_t len = strlen(value);
     41      station->addr = malloc(sizeof(char) * (len + 1));
     42      memcpy(station->addr, value, len);
     43      station->addr[len] = '\0';
     44    }
     45    if (strcmp(name, "url") == 0) {
     46      size_t len = strlen(value);
     47      station->url = malloc(sizeof(char) * (len + 1));
     48      memcpy(station->url, value, len);
     49      station->url[len] = '\0';
     50    }
     51    if (strcmp(name, "resolver") == 0) {
     52      size_t len = strlen(value);
     53      station->resolver = malloc(sizeof(char) * (len + 1));
     54      memcpy(station->resolver, value, len);
     55      station->resolver[len] = '\0';
     56    }
     57  }
     58  return 1;
     59}
     60
     61void rx_init(rx_t *self) {
     62  self->stations = NULL;
     63  self->stations_len = 0;
     64  char *config_path = NULL;
     65  char *home_path = getenv("HOME");
     66  if (home_path != NULL) {
     67    str_appendf(&config_path, "%s/.config/rx/", home_path);
     68  }
     69  str_append(&config_path, "rx.ini");
     70  ini_parse(config_path, rx_ini_load, self);
     71  free(config_path);
     72  self->curr = NULL;
     73  self->ctx = mpv_create();
     74  mpv_initialize(self->ctx);
     75  self->idx = 0;
     76  self->count = 0;
     77  self->title = NULL;
     78  self->loading = 0;
     79  self->error = 0;
     80  term_enable_raw_mode();
     81  term_write("\x1b[?25l");
     82}
     83
     84void rx_update(rx_t *self) {
     85  int key = term_poll_key(100);
     86  switch (key) {
     87    case 'q': {
     88      self->quit = true;
     89      break;
     90    }
     91    case 'j': {
     92      if ((size_t) self->idx < self->stations_len - 1) {
     93        self->idx++;
     94      }
     95      break;
     96    }
     97    case 'k': {
     98      if (self->idx > 0) {
     99        self->idx--;
    100      }
    101      break;
    102    }
    103    case KEY_ENTER: {
    104      const rx_station_t *station = &self->stations[self->idx];
    105      char *resolver = station->resolver;
    106      char *url = NULL;
    107      if (resolver != NULL) {
    108        char *resolver_path = NULL;
    109        char *home_path = getenv("HOME");
    110        if (home_path != NULL) {
    111          str_appendf(&resolver_path, "%s/.config/rx/mods/", home_path);
    112        }
    113        str_append(&resolver_path, resolver);
    114        void *handle = dlopen(resolver_path, RTLD_LAZY);
    115        free(resolver_path);
    116        if (handle == NULL) {
    117          fprintf(stderr, "%s\n", dlerror());
    118          break;
    119        }
    120        char *(*resolve_fn)(void) = (char *(*)(void)) dlsym(handle, "rx_resolve");
    121        const char *err = dlerror();
    122        if (err != 0) {
    123          fprintf(stderr, "%s\n", err);
    124          break;
    125        }
    126        url = resolve_fn();
    127        dlclose(handle);
    128      }
    129      else {
    130        size_t len = strlen(station->url);
    131        url = malloc(sizeof(char) * (len + 1));
    132        memcpy(url, station->url, len);
    133        url[len] = '\0';
    134      }
    135      if (url != NULL) {
    136        const char *cmd[] = { "loadfile", url, NULL };
    137        mpv_command(self->ctx, cmd);
    138        free(url);
    139      }
    140      self->curr = station;
    141      self->title = NULL;
    142      self->loading = 1;
    143      self->error = 0;
    144      break;
    145    }
    146    case KEY_BACKSPACE: {
    147      const char *cmd[] = { "stop", NULL };
    148      mpv_command(self->ctx, cmd);
    149      self->curr = NULL;
    150      self->loading = 0;
    151      self->error = 0;
    152      break;
    153    }
    154    default:
    155      break;
    156  }
    157  if (self->count == RX_UPDATE_COUNT) {
    158    if (self->title != NULL) {
    159      mpv_free(self->title);
    160      self->title = NULL;
    161    }
    162    self->title = mpv_get_property_string(self->ctx, "metadata/icy-title");
    163    self->count = 0;
    164  }
    165  self->count++;
    166  mpv_get_property(self->ctx, "core-idle", MPV_FORMAT_FLAG, &self->loading);
    167  mpv_event *event = mpv_wait_event(self->ctx, 0);
    168  if (event->event_id == MPV_EVENT_END_FILE) {
    169    mpv_event_end_file *end_file = (mpv_event_end_file *) event->data;
    170    if (end_file->reason == MPV_END_FILE_REASON_ERROR) {
    171      self->error = 1;
    172      const char* err = mpv_error_string(end_file->error);
    173      size_t len = strlen(err);
    174      memcpy(self->message, err, len);
    175      self->message[len] = '\0';
    176    }
    177  }
    178}
    179
    180void rx_draw(rx_t *self) {
    181  term_write("\x1b[2J");
    182  term_write("\x1b[H");
    183  term_write("\r\n");
    184  int rows = 0;
    185  int cols = 0;
    186  term_read_window_size(&rows, &cols);
    187  size_t len = rows - 5;
    188  for (size_t i = 0; i < len; i++) {
    189    if (i + self->idx >= self->stations_len) {
    190      term_write("\r\n");
    191      continue;
    192    }
    193    const rx_station_t *station = &self->stations[i + self->idx];
    194    if (i + self->idx == (size_t) self->idx) {
    195      term_write("> ");
    196    }
    197    else {
    198      term_write("  ");
    199    }
    200    int pad = 3; // floor(log10(self->stations_len) + 1);
    201    term_writef("%0*d ", pad, i + self->idx + 1);
    202    term_writef("[%s] %s", station->uid, station->name);
    203    if (station->addr != NULL) {
    204      term_writef(" (%s)", station->addr);
    205    }
    206    term_write("\r\n");
    207  }
    208  term_write("\r\n");
    209  const rx_station_t *station = self->curr;
    210  if (station != NULL) {
    211    term_writef("[%s] %s\r\n", station->uid, station->name);
    212    if (self->error) {
    213      term_writef("error: %s\r\n", self->message);
    214    }
    215    else {
    216      if (self->loading) {
    217        term_write("loading...\r\n");
    218      }
    219      else {
    220        if (self->title != NULL && strlen(self->title) > 0) {
    221          term_writef("title: %s\r\n", self->title);
    222        }
    223        else {
    224          term_write("title: ???\r\n");
    225        }
    226      }
    227    }
    228  }
    229  else {
    230    term_write("[???] no station\r\n");
    231    term_write("...\r\n");
    232  }
    233  term_flush();
    234}
    235
    236void rx_free(rx_t *self) {
    237  mpv_terminate_destroy(self->ctx);
    238  term_write("\x1b[2J");
    239  term_write("\x1b[H");
    240  term_write("\x1b[?25h");
    241  term_flush();
    242  term_disable_raw_mode();
    243  for (size_t i = 0; i < self->stations_len; i++) {
    244    rx_station_t *station = &self->stations[i];
    245    if (station->addr != NULL) {
    246      free(station->addr);
    247    }
    248    if (station->url != NULL) {
    249      free(station->url);
    250    }
    251    if (station->resolver != NULL) {
    252      free(station->resolver);
    253    }
    254  }
    255  free(self->stations);
    256  self->stations_len = 0;
    257}