A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 *
8 * $Id$
9 *
10 * Copyright (C) 2007 Jonathan Gordon
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 <stdio.h>
23#include <string.h>
24#include <stdlib.h>
25#include <stdbool.h>
26#include "string.h"
27#include <ctype.h>
28
29#include "settings.h"
30#include "debug.h"
31#include "lang.h"
32#include "kernel.h"
33#include "plugin.h"
34#include "filetypes.h"
35#include "screens.h"
36#include "dir.h"
37#include "file.h"
38#include "splash.h"
39#include "core_alloc.h"
40#include "icons.h"
41/*#define LOGF_ENABLE*/
42#include "logf.h"
43
44/* max filetypes (plugins & icons stored here) */
45#define MAX_FILETYPES 192
46/* max viewer plugins */
47#define MAX_VIEWERS 56
48
49static void fill_from_builtin(const char*,int) INIT_ATTR;
50static void read_builtin_types_init(void) INIT_ATTR;
51static void read_viewers_config_init(void) INIT_ATTR;
52static void read_config_init(int fd) INIT_ATTR;
53
54/* string array for known audio file types (tree_attr == FILE_ATTR_AUDIO) */
55static const char* inbuilt_audio_filetypes[] = {
56 "mp3", "mp2", "mpa", "mp1", "ogg", "oga", "wma", "wmv", "asf", "wav",
57 "flac", "ac3", "a52", "mpc", "wv", "m4a", "m4b", "mp4", "mod", "mpga",
58 "shn", "aif", "aiff", "spx", "opus", "sid", "adx", "nsf", "nsfe", "spc",
59 "ape", "mac", "sap", "rm", "ra", "rmvb", "cmc", "cm3", "cmr", "cms", "dmc",
60 "dlt", "mpt", "mpd", "rmt", "tmc", "tm8", "tm2", "oma", "aa3", "at3", "mmf",
61 "au", "snd", "vox", "w64", "tta", "ay", "vtx", "gbs", "hes", "sgc", "vgm",
62 "vgz", "kss", "aac",
63};
64
65struct filetype_inbuilt {
66 const char* extension;
67 int tree_attr;
68};
69
70/* a table for the known file types, besides audio */
71static const struct filetype_inbuilt inbuilt_filetypes[] = {
72 { "m3u", FILE_ATTR_M3U },
73 { "m3u8", FILE_ATTR_M3U },
74 { "cfg", FILE_ATTR_CFG },
75 { "wps", FILE_ATTR_WPS },
76#ifdef HAVE_REMOTE_LCD
77 { "rwps", FILE_ATTR_RWPS },
78#endif
79#if CONFIG_TUNER
80 { "fmr", FILE_ATTR_FMR },
81 { "fms", FILE_ATTR_FMS },
82#endif
83 { "log", FILE_ATTR_LOG },
84 { "lng", FILE_ATTR_LNG },
85 { "rock", FILE_ATTR_ROCK },
86 { "lua", FILE_ATTR_LUA },
87 { "opx", FILE_ATTR_OPX },
88 { "fnt", FILE_ATTR_FONT },
89 { "kbd", FILE_ATTR_KBD },
90 { "bmark",FILE_ATTR_BMARK },
91 { "cue", FILE_ATTR_CUE },
92 { "sbs", FILE_ATTR_SBS },
93#ifdef HAVE_REMOTE_LCD
94 { "rsbs", FILE_ATTR_RSBS },
95#if CONFIG_TUNER
96 { "rfms", FILE_ATTR_RFMS },
97#endif
98#endif
99#ifdef BOOTFILE_EXT
100 { BOOTFILE_EXT, FILE_ATTR_MOD },
101#endif
102#ifdef BOOTFILE_EXT2
103 { BOOTFILE_EXT2, FILE_ATTR_MOD },
104#endif
105};
106
107struct fileattr_icon_voice {
108 int tree_attr;
109 uint16_t icon;
110 uint16_t voiceclip;
111};
112
113/* a table for the known file types icons & voice clips */
114static const struct fileattr_icon_voice inbuilt_attr_icons_voices[] = {
115 { FILE_ATTR_AUDIO, Icon_Audio, VOICE_EXT_MPA },
116 { FILE_ATTR_M3U, Icon_Playlist, LANG_PLAYLIST },
117 { FILE_ATTR_CFG, Icon_Config, VOICE_EXT_CFG },
118 { FILE_ATTR_WPS, Icon_Wps, VOICE_EXT_WPS },
119#ifdef HAVE_REMOTE_LCD
120 {FILE_ATTR_RWPS, Icon_Wps, VOICE_EXT_RWPS },
121#endif
122#if CONFIG_TUNER
123 { FILE_ATTR_FMR, Icon_Preset, LANG_FMR },
124 { FILE_ATTR_FMS, Icon_Wps, VOICE_EXT_FMS },
125#endif
126 { FILE_ATTR_LNG, Icon_Language, LANG_LANGUAGE },
127 { FILE_ATTR_ROCK, Icon_Plugin, VOICE_EXT_ROCK },
128 { FILE_ATTR_LUA, Icon_Plugin, VOICE_EXT_ROCK },
129 { FILE_ATTR_OPX, Icon_Plugin, VOICE_EXT_ROCK },
130 { FILE_ATTR_FONT, Icon_Font, VOICE_EXT_FONT },
131 { FILE_ATTR_KBD, Icon_Keyboard, VOICE_EXT_KBD },
132 { FILE_ATTR_BMARK, Icon_Bookmark, VOICE_EXT_BMARK },
133 { FILE_ATTR_CUE, Icon_Bookmark, VOICE_EXT_CUESHEET },
134 { FILE_ATTR_SBS, Icon_Wps, VOICE_EXT_SBS },
135#ifdef HAVE_REMOTE_LCD
136 { FILE_ATTR_RSBS, Icon_Wps, VOICE_EXT_RSBS },
137#if CONFIG_TUNER
138 { FILE_ATTR_RFMS, Icon_Wps, VOICE_EXT_RFMS },
139#endif
140#endif
141#if defined(BOOTFILE_EXT) || defined(BOOTFILE_EXT2)
142 { FILE_ATTR_MOD, Icon_Firmware, VOICE_EXT_AJZ },
143#endif
144};
145
146static int filetype_inbuilt_index(int tree_attr)
147{
148 size_t count = ARRAY_SIZE(inbuilt_attr_icons_voices);
149 /* try to find a inbuilt index for the extension, if known */
150 tree_attr &= FILE_ATTR_MASK; /* file type */
151
152 for (size_t i = count - 1; i < count; i--)
153 {
154 if (tree_attr == inbuilt_attr_icons_voices[i].tree_attr)
155 {
156 logf("%s found attr %d id", __func__, tree_attr);
157 return i;
158 }
159 }
160 logf("%s not found attr %d", __func__, tree_attr);
161 return -1;
162}
163
164long tree_get_filetype_voiceclip(int attr)
165{
166 if (global_settings.talk_filetype)
167 {
168 int index = filetype_inbuilt_index(attr);
169 if (index >= 0)
170 {
171 logf("%s found attr %d id %d", __func__, attr,
172 inbuilt_attr_icons_voices[index].voiceclip);
173 return inbuilt_attr_icons_voices[index].voiceclip;
174 }
175 }
176 logf("%s not found attr %d", __func__, attr);
177 return -1;
178}
179
180#define ROCK_EXTENSION "rock"
181
182struct file_type {
183 enum themable_icons icon; /* the icon which shall be used for it, NOICON if unknown */
184 unsigned char attr; /* FILE_ATTR_MASK >> 8 */
185 const char* plugin; /* Which plugin to use, NULL if unknown, or builtin */
186 const char* extension; /* NULL for none */
187};
188
189static struct file_type filetypes[MAX_FILETYPES];
190
191static enum themable_icons custom_filetype_icons[MAX_FILETYPES];
192static bool custom_icons_loaded = false;
193
194#ifdef HAVE_LCD_COLOR
195static int custom_colors[MAX_FILETYPES];
196struct filetype_unknown {
197 enum themable_icons icon;
198 int color;
199};
200static struct filetype_unknown unknown_file = {
201 .icon = Icon_NOICON,
202 .color = -1,
203};
204#else
205struct filetype_unknown { enum themable_icons icon; };
206static struct filetype_unknown unknown_file = { .icon = Icon_NOICON };
207#endif
208
209/* index array to filetypes used in open with list. */
210static int viewers[MAX_VIEWERS];
211static int filetype_count = 0;
212static unsigned char highest_attr = 0;
213static int viewer_count = 0;
214
215static int strdup_handle, strdup_cur_idx;
216static size_t strdup_bufsize;
217static int move_callback(int handle, void* current, void* new)
218{
219 /*could compare to strdup_handle, but ops is only used once */
220 (void)handle;
221 size_t diff = new - current;
222#define FIX_PTR(x) \
223 { if ((void*)x >= current && (void*)x < (current+strdup_bufsize)) x+= diff; }
224 for(int i = 0; i < filetype_count; i++)
225 {
226 FIX_PTR(filetypes[i].extension);
227 FIX_PTR(filetypes[i].plugin);
228 }
229 return BUFLIB_CB_OK;
230}
231
232static struct buflib_callbacks ops = {
233 .move_callback = move_callback,
234 .shrink_callback = NULL,
235};
236
237static const char *filetypes_strdup(const char* string)
238{
239 char *buffer = core_get_data(strdup_handle) + strdup_cur_idx;
240 strdup_cur_idx += strlcpy(buffer, string, strdup_bufsize-strdup_cur_idx)+1;
241 return buffer;
242}
243
244static const char *filetypes_store_plugin(const char *plugin, int n)
245{
246 int i;
247 /* if the plugin is in the list already, use it. */
248 for (i=0; i<viewer_count; i++)
249 {
250 if (!strcmp(filetypes[viewers[i]].plugin, plugin))
251 return filetypes[viewers[i]].plugin;
252 }
253 /* otherwise, allocate buffer */
254 if (viewer_count < MAX_VIEWERS)
255 viewers[viewer_count++] = n;
256 return filetypes_strdup(plugin);
257}
258
259static int find_extension(const char* extension)
260{
261 if (extension)
262 {
263 for (int i=1; i<filetype_count; i++)
264 {
265 if (filetypes[i].extension &&
266 !strcasecmp(extension, filetypes[i].extension))
267 return i;
268 }
269 }
270 return -1;
271}
272
273#ifdef HAVE_LCD_COLOR
274/* Colors file format is similar to icons:
275 * ext:hex_color
276 * load a colors file from a theme with:
277 * filetype colours: filename.colours */
278void read_color_theme_file(void) {
279 char buffer[MAX_PATH];
280 int fd;
281 char *ext, *color;
282 int i;
283 for (i = 0; i < MAX_FILETYPES; i++) {
284 custom_colors[i] = -1;
285 }
286 unknown_file.color = -1;
287 if (!global_settings.colors_file[0] || global_settings.colors_file[0] == '-')
288 return;
289
290 fd = open_pathfmt(buffer, sizeof(buffer), O_RDONLY,
291 THEME_DIR "/%s.colours", global_settings.colors_file);
292 if (fd < 0)
293 return;
294 while (read_line(fd, buffer, MAX_PATH) > 0)
295 {
296 if (!settings_parseline(buffer, &ext, &color))
297 continue;
298 if (!strcasecmp(ext, "folder"))
299 {
300 hex_to_rgb(color, &custom_colors[0]);
301 continue;
302 }
303 if (!strcmp(ext, "???"))
304 {
305 hex_to_rgb(color, &unknown_file.color);
306 continue;
307 }
308 i = find_extension(ext);
309 if (i >= 0)
310 hex_to_rgb(color, &custom_colors[i]);
311 }
312 close(fd);
313}
314#endif
315
316static int parse_icon(const char *line, enum themable_icons *icon)
317{
318 int num = -1;
319 if (*line == '*')
320 {
321 num = atoi(line+1);
322 *icon = num;
323 }
324 else if (*line == '-')
325 {
326 *icon = Icon_NOICON;
327 }
328 else if (*line >= '0' && *line <= '9')
329 {
330 num = atoi(line);
331 *icon = Icon_Last_Themeable + num;
332 }
333 return num;
334}
335
336void read_viewer_theme_file(void)
337{
338 char buffer[MAX_PATH];
339 int fd;
340 char *ext, *icon;
341 int i;
342 enum themable_icons *icon_dest;
343 global_status.viewer_icon_count = 0;
344 custom_icons_loaded = false;
345 /*custom_filetype_icons[0] = Icon_Folder; filetypes[0] is folder icon.. */
346 for (i=0; i<filetype_count; i++)
347 {
348 custom_filetype_icons[i] = filetypes[i].icon;
349 }
350
351 fd = open_pathfmt(buffer, sizeof(buffer), O_RDONLY,
352 ICON_DIR "/%s.icons", global_settings.viewers_icon_file);
353 if (fd < 0)
354 return;
355
356 while (read_line(fd, buffer, MAX_PATH) > 0)
357 {
358 if (!settings_parseline(buffer, &ext, &icon))
359 continue;
360 i = find_extension(ext);
361 if (i >= 0)
362 icon_dest = &custom_filetype_icons[i];
363 else if (!strcmp(ext, "???"))
364 icon_dest = &unknown_file.icon;
365 else
366 icon_dest = NULL;
367
368 if (icon_dest)
369 {
370 if (parse_icon(icon, icon_dest) > global_status.viewer_icon_count)
371 global_status.viewer_icon_count++;
372 }
373 }
374 close(fd);
375 custom_icons_loaded = true;
376}
377
378static void read_viewers_config_init(void)
379{
380 int fd = open(VIEWERS_CONFIG, O_RDONLY);
381 if(fd < 0)
382 return;
383
384 off_t filesz = filesize(fd);
385 if(filesz <= 0)
386 goto out;
387
388 /* estimate bufsize with the filesize, will not be larger */
389 strdup_bufsize = (size_t)filesz;
390 strdup_handle = core_alloc_ex(strdup_bufsize, &ops);
391 if(strdup_handle <= 0)
392 goto out;
393
394 read_config_init(fd);
395 core_shrink(strdup_handle, NULL, strdup_cur_idx);
396
397 out:
398 close(fd);
399}
400
401void filetype_init(void)
402{
403 /* set the directory item first */
404 filetypes[0].extension = NULL;
405 filetypes[0].plugin = NULL;
406 filetypes[0].attr = 0;
407 filetypes[0].icon = Icon_Folder;
408
409 viewer_count = 0;
410 filetype_count = 1;
411
412 read_builtin_types_init();
413 read_viewers_config_init();
414 read_viewer_theme_file();
415#ifdef HAVE_LCD_COLOR
416 read_color_theme_file();
417#endif
418}
419
420/* remove all white spaces from string */
421static void rm_whitespaces(char* str)
422{
423 char *s = str;
424 while (*str)
425 {
426 if (!isspace(*str))
427 {
428 *s = *str;
429 s++;
430 }
431 str++;
432 }
433 *s = '\0';
434}
435
436static void fill_from_builtin(const char *ext, int tree_attr)
437{
438 if (filetype_count >= MAX_FILETYPES)
439 return;
440
441 struct file_type *filetype = &filetypes[filetype_count];
442 filetype->icon = unknown_file.icon;
443 filetype->attr = tree_attr>>8;
444 filetype->plugin = NULL;
445 filetype->extension = ext;
446
447 if (filetype->attr > highest_attr)
448 highest_attr = filetype->attr;
449
450 int index = filetype_inbuilt_index(tree_attr);
451 if (index >= 0)
452 {
453 filetype->icon = inbuilt_attr_icons_voices[index].icon;
454 }
455
456 filetype_count++;
457}
458
459static void read_builtin_types_init(void)
460{
461 for(size_t i = 0; (i < ARRAY_SIZE(inbuilt_audio_filetypes)); i++)
462 {
463 fill_from_builtin(inbuilt_audio_filetypes[i], FILE_ATTR_AUDIO);
464 }
465
466 for(size_t i = 0; (i < ARRAY_SIZE(inbuilt_filetypes)); i++)
467 {
468 fill_from_builtin(inbuilt_filetypes[i].extension,
469 inbuilt_filetypes[i].tree_attr);
470 }
471}
472
473static void read_config_init(int fd)
474{
475 char line[64], *s, *e;
476 const char *extension, *plugin;
477 /* config file is in the format
478 <extension>,<plugin>,<icon code>
479 ignore line if either of the first two are missing */
480 while (read_line(fd, line, sizeof line) > 0)
481 {
482 if (filetype_count >= MAX_FILETYPES)
483 {
484 splash(HZ, ID2P(LANG_FILETYPES_FULL));
485 break;
486 }
487 rm_whitespaces(line);
488 /* get the extension */
489 s = line;
490 e = strchr(s, ',');
491 if (!e)
492 continue;
493 *e = '\0';
494 extension = s;
495
496 /* get the plugin */
497 s = e+1;
498 e = strchr(s, ',');
499 if (!e)
500 continue;
501 *e = '\0';
502 plugin = s;
503
504 if (!strcmp("???", extension))
505 {
506 /* get the icon */
507 s = e+1;
508 parse_icon(s, &unknown_file.icon);
509 continue;
510 }
511
512 /* ok, store this plugin/extension, check icon after */
513 struct file_type *file_type = &filetypes[filetype_count];
514 file_type->extension = filetypes_strdup(extension);
515 file_type->plugin = filetypes_store_plugin(plugin, filetype_count);
516 file_type->attr = highest_attr +1;
517 file_type->icon = Icon_Questionmark;
518 highest_attr++;
519 /* get the icon */
520 s = e+1;
521 parse_icon(s, &file_type->icon);
522 filetype_count++;
523 }
524}
525
526static int file_find_extension(const char* file)
527{
528 char *extension = strrchr(file, '.');
529 if (extension)
530 extension++;
531 return find_extension(extension);
532}
533
534int filetype_get_attr(const char* file)
535{
536 int i = file_find_extension(file);
537 if (i >= 0)
538 return (filetypes[i].attr<<8)&FILE_ATTR_MASK;
539 return 0;
540}
541
542static int find_attr(int attr)
543{
544 int i;
545 /* skip the directory item */
546 if ((attr & ATTR_DIRECTORY)==ATTR_DIRECTORY)
547 return 0;
548 for (i=1; i<filetype_count; i++)
549 {
550 if ((attr>>8) == filetypes[i].attr)
551 return i;
552 }
553 return -1;
554}
555
556#ifdef HAVE_LCD_COLOR
557int filetype_get_color(const char * name, int attr)
558{
559 if ((attr & ATTR_DIRECTORY)==ATTR_DIRECTORY)
560 return custom_colors[0];
561 int i = file_find_extension(name);
562 if (i <= 0)
563 return unknown_file.color;
564 return custom_colors[i];
565}
566#endif
567
568int filetype_get_icon(int attr)
569{
570 int index = find_attr(attr);
571 if (index < 0)
572 return unknown_file.icon;
573 if (custom_icons_loaded)
574 return custom_filetype_icons[index];
575 return filetypes[index].icon;
576}
577
578static int filetype_get_plugin_index(int attr)
579{
580 int index = find_attr(attr);
581 if (index < 0)
582 return -1;
583 struct file_type *ft_indexed = &filetypes[index];
584
585 /* attempt to find a suitable viewer by file extension */
586 if(ft_indexed->plugin == NULL && ft_indexed->extension != NULL)
587 {
588 struct file_type *ft;
589 int i = filetype_count;
590 while (--i > index)
591 {
592 ft = &filetypes[i];
593 if (ft->plugin == NULL || ft->extension == NULL)
594 continue;
595 else if (ft->plugin != NULL &&
596 strcmp(ft->extension, ft_indexed->extension) == 0)
597 {
598 /*splashf(HZ*3, "Found %d %s %s", i, ft->extension, ft->plugin);*/
599 return i;
600 }
601 }
602 }
603 if (ft_indexed->plugin == NULL)
604 index = -1;
605 return index; /* Not Found */
606}
607
608char* filetype_get_plugin(int attr, char *buffer, size_t buffer_len)
609{
610 int index = filetype_get_plugin_index(attr);
611 if (index < 0 || !buffer)
612 return NULL;
613
614 struct file_type *ft_indexed = &filetypes[index];
615
616 snprintf(buffer, buffer_len, "%s/%s." ROCK_EXTENSION,
617 PLUGIN_DIR, ft_indexed->plugin);
618 return buffer;
619}
620
621bool filetype_supported(int attr)
622{
623 return find_attr(attr) >= 0;
624}
625
626/**** Open With Screen ****/
627struct cb_data {
628 const char *current_file;
629};
630
631static enum themable_icons openwith_get_icon(int selected_item, void * data)
632{
633 (void)data;
634 return filetypes[viewers[selected_item]].icon;
635}
636
637static const char* openwith_get_name(int selected_item, void * data,
638 char * buffer, size_t buffer_len)
639{
640 (void)data; (void)buffer; (void)buffer_len;
641 const char *s = strrchr(filetypes[viewers[selected_item]].plugin, '/');
642 if (s)
643 return s+1;
644 else return filetypes[viewers[selected_item]].plugin;
645}
646
647static int openwith_get_talk(int selected_item, void * data)
648{
649 (void)data;
650 char viewer_filename[MAX_FILENAME];
651 snprintf(viewer_filename, MAX_FILENAME, "%s." ROCK_EXTENSION,
652 filetypes[viewers[selected_item]].plugin);
653 talk_file_or_spell(PLUGIN_DIR, viewer_filename,
654 NULL, false);
655 return 0;
656}
657
658char* filetype_get_viewer(char *buffer, size_t buffer_len, const char* current_file)
659{
660 int attr = filetype_get_attr(current_file);
661
662 struct simplelist_info info;
663 simplelist_info_init(&info, str(LANG_ONPLAY_OPEN_WITH), viewer_count, NULL);
664
665 int default_index = filetype_get_plugin_index(attr);
666
667 if (default_index >= 0)
668 {
669 for (int i = 0; i < viewer_count; i++)
670 if (viewers[i] == default_index)
671 {
672 info.selection = i;
673 break;
674 }
675 }
676
677 info.get_name = openwith_get_name;
678 info.get_icon = global_settings.show_icons?openwith_get_icon:NULL;
679 info.get_talk = openwith_get_talk;
680
681 simplelist_show_list(&info);
682
683 if (info.selection >= 0) /* run user selected viewer */
684 {
685 int i = viewers[info.selection];
686 snprintf(buffer, buffer_len, "%s/%s." ROCK_EXTENSION,
687 PLUGIN_DIR, filetypes[i].plugin);
688 return buffer;
689 }
690 return NULL;
691
692}
693
694int filetype_list_viewers(const char* current_file)
695{
696 int ret = PLUGIN_ERROR;
697 char plugin[MAX_PATH];
698 if (filetype_get_viewer(plugin, sizeof(plugin), current_file) != NULL)
699 ret = plugin_load(plugin, current_file);
700 return ret;
701}
702
703int filetype_load_plugin(const char* plugin, const char* file)
704{
705 int i;
706 char plugin_name[MAX_PATH];
707 char *s;
708
709 for (i=1;i<filetype_count;i++)
710 {
711 if (filetypes[i].plugin)
712 {
713 s = strrchr(filetypes[i].plugin, '/');
714 if (s)
715 {
716 if (!strcmp(s+1, plugin))
717 break;
718 }
719 else if (!strcmp(filetypes[i].plugin, plugin))
720 break;
721 }
722 }
723 if (i >= filetype_count)
724 return PLUGIN_ERROR;
725 snprintf(plugin_name, MAX_PATH, "%s/%s." ROCK_EXTENSION,
726 PLUGIN_DIR, filetypes[i].plugin);
727 return plugin_load(plugin_name, file);
728}