A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 362 lines 9.8 kB view raw
1/*************************************************************************** 2 * __________ __ ___. 3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___ 4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / 5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < 6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ 7 * \/ \/ \/ \/ \/ 8 * $Id$ 9 * 10 * Copyright (C) 2022 Aidan MacDonald 11 * 12 * This program is free software; you can redistribute it and/or 13 * modify it under the terms of the GNU General Public License 14 * as published by the Free Software Foundation; either version 2 15 * of the License, or (at your option) any later version. 16 * 17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 18 * KIND, either express or implied. 19 * 20 ****************************************************************************/ 21 22#include "plugin.h" 23 24/* should be more than enough */ 25#define MAX_ROOTS 128 26 27static enum plugin_status plugin_status = PLUGIN_OK; 28static char tmpbuf[MAX_PATH+1]; 29static char tmpbuf2[MAX_PATH+1]; 30static char cur_root[MAX_PATH]; 31static char roots[MAX_ROOTS][MAX_PATH]; 32static int nroots; 33 34/* Read a redirect file and return the path */ 35static char* read_redirect_file(const char* filename) 36{ 37 int fd = rb->open(filename, O_RDONLY); 38 if(fd < 0) 39 return NULL; 40 41 ssize_t ret = rb->read(fd, tmpbuf, sizeof(tmpbuf)); 42 rb->close(fd); 43 if(ret < 0 || (size_t)ret >= sizeof(tmpbuf)) 44 return NULL; 45 46 /* relative paths are ignored */ 47 if(tmpbuf[0] != '/') 48 ret = 0; 49 50 /* remove trailing control chars (newlines etc.) */ 51 for(ssize_t i = ret - 1; i >= 0 && tmpbuf[i] < 0x20; --i) 52 tmpbuf[i] = '\0'; 53 54 return tmpbuf; 55} 56 57/* Search for a redirect file, like get_redirect_dir() */ 58static const char* read_redirect(void) 59{ 60 for(int vol = NUM_VOLUMES-1; vol >= MULTIBOOT_MIN_VOLUME; --vol) { 61 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR); 62 const char* path = read_redirect_file(tmpbuf); 63 if(path && path[0] == '/') { 64 /* prepend the volume because that's what we expect */ 65 rb->snprintf(tmpbuf2, sizeof(tmpbuf2), "/<%d>%s", vol, path); 66 return tmpbuf2; 67 } 68 } 69 70 tmpbuf[0] = '\0'; 71 return tmpbuf; 72} 73 74/* Remove all redirect files except for the one on volume 'exclude_vol' */ 75static int clear_redirect(int exclude_vol) 76{ 77 int ret = 0; 78 for(int vol = MULTIBOOT_MIN_VOLUME; vol < NUM_VOLUMES; ++vol) { 79 if(vol == exclude_vol) 80 continue; 81 82 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR); 83 if(rb->file_exists(tmpbuf) && rb->remove(tmpbuf) < 0) 84 ret = -1; 85 } 86 87 return ret; 88} 89 90/* Save a path to the redirect file */ 91static int write_redirect(const char* abspath) 92{ 93 /* get the volume (required) */ 94 const char* path = abspath; 95 int vol = rb->path_strip_volume(abspath, &path, false); 96 if(path == abspath) 97 return -1; 98 99 /* remove all other redirect files */ 100 if(clear_redirect(vol)) 101 return -1; 102 103 /* open the redirect file on the same volume */ 104 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s", vol, BOOT_REDIR); 105 int fd = rb->open(tmpbuf, O_WRONLY|O_CREAT|O_TRUNC, 0644); 106 if(fd < 0) 107 return fd; 108 109 size_t pathlen = rb->strlen(path); 110 ssize_t ret = rb->write(fd, path, pathlen); 111 if(ret < 0 || (size_t)ret != pathlen) { 112 rb->close(fd); 113 return -1; 114 } 115 116 ret = rb->write(fd, "\n", 1); 117 rb->close(fd); 118 if(ret != 1) 119 return -1; 120 121 return 0; 122} 123 124/* Check if the firmware file is valid 125 * TODO: this should at least check model number or something */ 126static bool check_firmware(const char* path) 127{ 128 return rb->file_exists(path); 129} 130 131static int root_compare(const void* a, const void* b) 132{ 133 const char* as = a; 134 const char* bs = b; 135 return rb->strcmp(as, bs); 136} 137 138/* Scan the filesystem for possible redirect targets. To prevent this from 139 * taking too long we only check the directories in the root of each volume 140 * look check for a rockbox firmware binary underneath /dir/.rockbox. If it 141 * exists then we report /<vol#>/dir as a root. */ 142static int find_roots(void) 143{ 144 const char* bootdir = *BOOTDIR == '/' ? &BOOTDIR[1] : BOOTDIR; 145 nroots = 0; 146 147 for(int vol = MULTIBOOT_MIN_VOLUME; vol < NUM_VOLUMES; ++vol) { 148 rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>", vol); 149 150 /* try to open the volume root; ignore failures since they'll 151 * occur if the volume is unmounted */ 152 DIR* dir = rb->opendir(tmpbuf); 153 if(!dir) 154 continue; 155 156 struct dirent* ent; 157 while((ent = rb->readdir(dir))) { 158 /* skip non-directories */ 159 if ((rb->dir_get_info(dir, ent).attribute & ATTR_DIRECTORY) == 0) { 160 continue; 161 } 162 163 const char *dname = ent->d_name; 164 /* check for bootdir in the root of the volume */ 165 if (rb->strcmp(bootdir, dname) == 0) { 166 dname = ""; 167 } 168 169 int r = rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s/%s/%s", 170 vol, dname, bootdir, BOOTFILE); 171 172 if(r < 0 || (size_t)r >= sizeof(tmpbuf)) 173 continue; 174 175 if(check_firmware(tmpbuf)) { 176 rb->snprintf(roots[nroots], MAX_PATH, "/<%d>/%s", 177 vol, dname); 178 nroots += 1; 179 180 /* quit if we hit the maximum */ 181 if(nroots == MAX_ROOTS) { 182 vol = NUM_VOLUMES; 183 break; 184 } 185 } 186 } 187 188 rb->closedir(dir); 189 } 190 191 rb->qsort(roots, nroots, MAX_PATH, root_compare); 192 return nroots; 193} 194 195static const char* picker_get_name_cb(int selected, void* data, 196 char* buffer, size_t buffer_len) 197{ 198 (void)data; 199 (void)buffer; 200 (void)buffer_len; 201 return roots[selected]; 202} 203 204static int picker_action_cb(int action, struct gui_synclist* lists) 205{ 206 (void)lists; 207 if(action == ACTION_STD_OK) 208 action = ACTION_STD_CANCEL; 209 return action; 210} 211 212static bool show_picker_menu(int* selection) 213{ 214 struct simplelist_info info; 215 rb->simplelist_info_init(&info, "Select new root", nroots, NULL); 216 info.selection = *selection; 217 info.get_name = picker_get_name_cb; 218 info.action_callback = picker_action_cb; 219 if(rb->simplelist_show_list(&info)) 220 return true; 221 222 if(0 <= info.selection && info.selection < nroots) 223 *selection = info.selection; 224 225 return false; 226} 227 228enum { 229 MB_SELECT_ROOT, 230 MB_CLEAR_REDIRECT, 231 MB_CURRENT_ROOT, 232 MB_SAVE_AND_EXIT, 233 MB_SAVE_AND_REBOOT, 234 MB_EXIT, 235 MB_NUM_ITEMS, 236}; 237 238static const char* menu_get_name_cb(int selected, void* data, 239 char* buffer, size_t buffer_len) 240{ 241 (void)data; 242 243 switch(selected) { 244 case MB_SELECT_ROOT: 245 return "Select root"; 246 247 case MB_CLEAR_REDIRECT: 248 return "Clear redirect"; 249 250 case MB_CURRENT_ROOT: 251 if(cur_root[0]) { 252 rb->snprintf(buffer, buffer_len, "Using root: %s", cur_root); 253 return buffer; 254 } 255 256 return "Using default root"; 257 258 case MB_SAVE_AND_EXIT: 259 return "Save and Exit"; 260 261 case MB_SAVE_AND_REBOOT: 262 return "Save and Reboot"; 263 264 case MB_EXIT: 265 default: 266 return "Exit"; 267 } 268} 269 270static int menu_action_cb(int action, struct gui_synclist* lists) 271{ 272 int selected = rb->gui_synclist_get_sel_pos(lists); 273 274 if(action != ACTION_STD_OK) 275 return action; 276 277 switch(selected) { 278 case MB_SELECT_ROOT: { 279 if(find_roots() <= 0) { 280 rb->splashf(3*HZ, "No roots found"); 281 break; 282 } 283 284 int root_sel = nroots-1; 285 for(; root_sel > 0; --root_sel) 286 if(!rb->strcmp(roots[root_sel], cur_root)) 287 break; 288 289 if(show_picker_menu(&root_sel)) 290 return SYS_USB_CONNECTED; 291 292 rb->strcpy(cur_root, roots[root_sel]); 293 action = ACTION_REDRAW; 294 } break; 295 296 case MB_CLEAR_REDIRECT: 297 if(cur_root[0]) { 298 memset(cur_root, 0, sizeof(cur_root)); 299 rb->splashf(HZ, "Cleared redirect"); 300 } 301 302 action = ACTION_REDRAW; 303 break; 304 305 case MB_SAVE_AND_REBOOT: 306 case MB_SAVE_AND_EXIT: { 307 int ret; 308 if(cur_root[0]) 309 ret = write_redirect(cur_root); 310 else 311 ret = clear_redirect(-1); 312 313 if(ret < 0) 314 rb->splashf(3*HZ, "Couldn't save settings"); 315 else 316 rb->splashf(HZ, "Settings saved"); 317 318 action = ACTION_STD_CANCEL; 319 plugin_status = (ret < 0) ? PLUGIN_ERROR : PLUGIN_OK; 320 321 if(ret >= 0 && selected == MB_SAVE_AND_REBOOT) 322 rb->sys_reboot(); 323 } break; 324 325 case MB_EXIT: 326 return ACTION_STD_CANCEL; 327 328 default: 329 action = ACTION_REDRAW; 330 break; 331 } 332 333 return action; 334} 335 336static bool show_menu(void) 337{ 338 struct simplelist_info info; 339 rb->simplelist_info_init(&info, "Multiboot Settings", MB_NUM_ITEMS, NULL); 340 info.get_name = menu_get_name_cb; 341 info.action_callback = menu_action_cb; 342 return rb->simplelist_show_list(&info); 343} 344 345enum plugin_status plugin_start(const void* param) 346{ 347 (void)param; 348 349#ifdef HAVE_TOUCHSCREEN 350 rb->touchscreen_set_mode(rb->global_settings->touch_mode); 351#endif 352 353 /* load the current root */ 354 const char* myroot = read_redirect(); 355 rb->strcpy(cur_root, myroot); 356 357 /* display the menu */ 358 if(show_menu()) 359 return PLUGIN_USB_CONNECTED; 360 else 361 return plugin_status; 362}