so://phie

rx

A terminal-based radio player
git clone git://soophie.de/rx

blob: 155ba2dd4c9693e1068133142f3baa4dd3fe1c08 (7368 bytes)[doc][blame][raw]

1#include <stdio.h>

2#include <stdlib.h>

3#include <string.h>

4#include <dlfcn.h>

5#include <mpv/client.h>

6#define LIB_TERM_IMPL

7#define LIB_TERM_POSIX

8#include <so/term.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, "lst.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 self->term = term_init();

81 term_rawmode_enable(&self->term);

82 term_write(&self->term, "\x1b[?25l");

83}

84

85void rx_update(rx_t *self) {

86 int key = term_poll_key(&self->term, 100);

87 switch (key) {

88 case 'q': {

89 self->quit = true;

90 break;

91 }

92 case 'j': {

93 if ((size_t) self->idx < self->stations_len - 1) {

94 self->idx++;

95 }

96 break;

97 }

98 case 'k': {

99 if (self->idx > 0) {

100 self->idx--;

101 }

102 break;

103 }

104 case ' ': {

105 const rx_station_t *station = &self->stations[self->idx];

106 char *resolver = station->resolver;

107 char *url = NULL;

108 if (resolver != NULL) {

109 char *resolver_path = NULL;

110 char *home_path = getenv("HOME");

111 if (home_path != NULL) {

112 str_appendf(&resolver_path, "%s/.config/rx/mods/", home_path);

113 }

114 str_append(&resolver_path, resolver);

115 void *handle = dlopen(resolver_path, RTLD_LAZY);

116 free(resolver_path);

117 if (handle == NULL) {

118 fprintf(stderr, "%s\n", dlerror());

119 break;

120 }

121 char *(*resolve_fn)(void) = (char *(*)(void)) dlsym(handle, "rx_resolve");

122 const char *err = dlerror();

123 if (err != 0) {

124 fprintf(stderr, "%s\n", err);

125 break;

126 }

127 url = resolve_fn();

128 dlclose(handle);

129 }

130 else {

131 size_t len = strlen(station->url);

132 url = malloc(sizeof(char) * (len + 1));

133 memcpy(url, station->url, len);

134 url[len] = '\0';

135 }

136 if (url != NULL) {

137 const char *cmd[] = { "loadfile", url, NULL };

138 mpv_command(self->ctx, cmd);

139 free(url);

140 }

141 self->curr = station;

142 self->title = NULL;

143 self->loading = 1;

144 self->error = 0;

145 break;

146 }

147 case KEY_BACKSPACE: {

148 const char *cmd[] = { "stop", NULL };

149 mpv_command(self->ctx, cmd);

150 self->curr = NULL;

151 self->loading = 0;

152 self->error = 0;

153 break;

154 }

155 default:

156 break;

157 }

158 if (self->count == RX_UPDATE_COUNT) {

159 if (self->title != NULL) {

160 mpv_free(self->title);

161 self->title = NULL;

162 }

163 self->title = mpv_get_property_string(self->ctx, "metadata/icy-title");

164 self->count = 0;

165 }

166 self->count++;

167 mpv_get_property(self->ctx, "core-idle", MPV_FORMAT_FLAG, &self->loading);

168 mpv_event *event = mpv_wait_event(self->ctx, 0);

169 if (event->event_id == MPV_EVENT_END_FILE) {

170 mpv_event_end_file *end_file = (mpv_event_end_file *) event->data;

171 if (end_file->reason == MPV_END_FILE_REASON_ERROR) {

172 self->error = 1;

173 const char* err = mpv_error_string(end_file->error);

174 size_t len = strlen(err);

175 memcpy(self->message, err, len);

176 self->message[len] = '\0';

177 }

178 }

179}

180

181void rx_draw(rx_t *self) {

182 term_write(&self->term, "\x1b[2J");

183 term_write(&self->term, "\x1b[H");

184 term_write(&self->term, "\r\n");

185 size_t rows = 0;

186 size_t cols = 0;

187 term_read_window(&self->term, &rows, &cols);

188 size_t len = rows - 5;

189 for (size_t i = 0; i < len; i++) {

190 if (i + self->idx >= self->stations_len) {

191 term_write(&self->term, "\r\n");

192 continue;

193 }

194 const rx_station_t *station = &self->stations[i + self->idx];

195 if (i + self->idx == (size_t) self->idx) {

196 term_write(&self->term, "> ");

197 }

198 else {

199 term_write(&self->term, " ");

200 }

201 int pad = 3; // floor(log10(self->stations_len) + 1);

202 term_writef(&self->term, "%0*d ", pad, i + self->idx + 1);

203 term_writef(&self->term, "[%s] %s", station->uid, station->name);

204 if (station->addr != NULL) {

205 term_writef(&self->term, " (%s)", station->addr);

206 }

207 term_write(&self->term, "\r\n");

208 }

209 term_write(&self->term, "\r\n");

210 const rx_station_t *station = self->curr;

211 if (station != NULL) {

212 term_writef(&self->term, "[%s] %s\r\n", station->uid, station->name);

213 if (self->error) {

214 term_writef(&self->term, "error: %s\r\n", self->message);

215 }

216 else {

217 if (self->loading) {

218 term_write(&self->term, "loading...\r\n");

219 }

220 else {

221 if (self->title != NULL && strlen(self->title) > 0) {

222 term_writef(&self->term, "title: %s\r\n", self->title);

223 }

224 else {

225 term_write(&self->term, "title: ???\r\n");

226 }

227 }

228 }

229 }

230 else {

231 term_write(&self->term, "[???] no station\r\n");

232 term_write(&self->term, "...\r\n");

233 }

234 term_flush(&self->term);

235}

236

237void rx_free(rx_t *self) {

238 mpv_terminate_destroy(self->ctx);

239 term_write(&self->term, "\x1b[2J");

240 term_write(&self->term, "\x1b[H");

241 term_write(&self->term, "\x1b[?25h");

242 term_flush(&self->term);

243 term_rawmode_disable(&self->term);

244 term_cleanup(&self->term);

245 for (size_t i = 0; i < self->stations_len; i++) {

246 rx_station_t *station = &self->stations[i];

247 if (station->addr != NULL) {

248 free(station->addr);

249 }

250 if (station->url != NULL) {

251 free(station->url);

252 }

253 if (station->resolver != NULL) {

254 free(station->resolver);

255 }

256 }

257 free(self->stations);

258 self->stations_len = 0;

259}