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 16 static 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 61 void 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 84 void 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 180 void 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 236 void 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 }