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) 2006 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#include "plugin.h"
22#include "lib/playback_control.h"
23
24#define MAX_LINE_LEN 2048
25
26
27static unsigned char *buffer;
28static size_t buffer_size;
29static size_t char_count = 0;
30static int line_count = 0;
31static int last_action_line = 0;
32static int last_char_index = 0;
33static bool audio_buf = false;
34
35static char temp_line[MAX_LINE_LEN];
36static char copy_buffer[MAX_LINE_LEN];
37static char filename[MAX_PATH];
38static char eol[3];
39static bool newfile;
40
41#define ACTION_INSERT 0
42#define ACTION_GET 1
43#define ACTION_REMOVE 2
44#define ACTION_UPDATE 3
45#define ACTION_CONCAT 4
46
47static char* _do_action(int action, char* str, int line);
48#ifndef HAVE_ADJUSTABLE_CPU_FREQ
49#define do_action _do_action
50#else
51static char* do_action(int action, char* str, int line)
52{
53 char *r;
54 rb->cpu_boost(1);
55 r = _do_action(action,str,line);
56 rb->cpu_boost(0);
57 return r;
58}
59#endif
60
61static char* _do_action(int action, char* str, int line)
62{
63 int len, lennew;
64 int i=0,c=0;
65 if (line>=last_action_line)
66 {
67 i = last_action_line;
68 c = last_char_index;
69 }
70 while (i<line && i<line_count)
71 {
72 c += rb->strlen(&buffer[c])+1;
73 i++;
74 }
75 switch (action)
76 {
77 case ACTION_INSERT:
78 len = rb->strlen(str)+1;
79 if ( char_count+ len > buffer_size )
80 return NULL;
81 rb->memmove(&buffer[c+len],&buffer[c],char_count-c);
82 rb->strcpy(&buffer[c],str);
83 char_count += len;
84 line_count++;
85 break;
86 case ACTION_GET:
87 if (line > line_count)
88 return &buffer[0];
89 break;
90 case ACTION_REMOVE:
91 if (line > line_count)
92 return NULL;
93 len = rb->strlen(&buffer[c])+1;
94 rb->memmove(&buffer[c],&buffer[c+len],char_count-c-len);
95 char_count -= len;
96 line_count--;
97 break;
98 case ACTION_UPDATE:
99 if (line > line_count)
100 return NULL;
101 len = rb->strlen(&buffer[c])+1;
102 lennew = rb->strlen(str)+1;
103 if ( char_count+ lennew-len > buffer_size )
104 return NULL;
105 rb->memmove(&buffer[c+lennew],&buffer[c+len],char_count-c-len);
106 rb->strcpy(&buffer[c],str);
107 char_count += lennew-len;
108 break;
109 case ACTION_CONCAT:
110 if (line > line_count)
111 return NULL;
112 rb->memmove(&buffer[c-1],&buffer[c],char_count-c);
113 char_count--;
114 line_count--;
115 break;
116 default:
117 return NULL;
118 }
119 last_action_line = i;
120 last_char_index = c;
121 return &buffer[c];
122}
123static const char* list_get_name_cb(int selected_item, void* data,
124 char* buf, size_t buf_len)
125{
126 (void)data;
127 char *b = do_action(ACTION_GET, 0, selected_item);
128 /* strlcpy(dst, src, siz) returns strlen(src) */
129 if (rb->strlcpy(buf, b, buf_len) >= buf_len)
130 {
131 rb->strcpy(&buf[buf_len-10], " ...");
132 }
133 return buf;
134}
135
136static void get_eol_string(char* fn)
137{
138 int fd;
139 char t;
140
141 /* assume LF first */
142 rb->strcpy(eol,"\n");
143
144 if (!fn || !fn[0])
145 return;
146 fd = rb->open(fn,O_RDONLY);
147 if (fd<0)
148 return;
149
150 while (1)
151 {
152 if (!rb->read(fd,&t,1) || t == '\n')
153 {
154 break;
155 }
156 if (t == '\r')
157 {
158 if (rb->read(fd,&t,1) && t=='\n')
159 rb->strcpy(eol,"\r\n");
160 else
161 rb->strcpy(eol,"\r");
162 break;
163 }
164 }
165 rb->close(fd);
166 return;
167}
168
169static bool save_changes(int overwrite)
170{
171 int fd;
172 int i;
173
174 if (newfile || !overwrite)
175 {
176 if(rb->kbd_input(filename,MAX_PATH, NULL) < 0)
177 {
178 newfile = true;
179 return false;
180 }
181 }
182
183 fd = rb->open(filename,O_WRONLY|O_CREAT|O_TRUNC, 0666);
184 if (fd < 0)
185 {
186 newfile = true;
187 rb->splash(HZ*2, "Changes NOT saved");
188 return false;
189 }
190
191 rb->lcd_clear_display();
192#ifdef HAVE_ADJUSTABLE_CPU_FREQ
193 rb->cpu_boost(1);
194#endif
195 for (i=0;i<line_count;i++)
196 {
197 rb->fdprintf(fd,"%s%s", do_action(ACTION_GET, 0, i), eol);
198 }
199#ifdef HAVE_ADJUSTABLE_CPU_FREQ
200 rb->cpu_boost(0);
201#endif
202 rb->close(fd);
203
204 if (newfile || !overwrite)
205 /* current directory may have changed */
206 rb->reload_directory();
207
208 newfile = false;
209 return true;
210}
211
212static void setup_lists(struct gui_synclist *lists, int sel)
213{
214 rb->gui_synclist_init(lists,list_get_name_cb,0, false, 1, NULL);
215 rb->gui_synclist_set_nb_items(lists,line_count);
216 rb->gui_synclist_select_item(lists, sel);
217 rb->gui_synclist_draw(lists);
218}
219
220enum {
221 MENU_RET_SAVE = -1,
222 MENU_RET_NO_UPDATE,
223 MENU_RET_UPDATE,
224};
225static int do_item_menu(int cur_sel)
226{
227 int ret = MENU_RET_NO_UPDATE;
228 MENUITEM_STRINGLIST(menu, "Line Options", NULL,
229 "Cut/Delete", "Copy",
230 "Insert Above", "Insert Below",
231 "Concat To Above",
232 "Save", "Playback Control");
233
234 switch (rb->do_menu(&menu, NULL, NULL, false))
235 {
236 case 0: /* cut */
237 rb->strlcpy(copy_buffer, do_action(ACTION_GET, 0, cur_sel),
238 MAX_LINE_LEN);
239 do_action(ACTION_REMOVE, 0, cur_sel);
240 ret = MENU_RET_UPDATE;
241 break;
242 case 1: /* copy */
243 rb->strlcpy(copy_buffer, do_action(ACTION_GET, 0, cur_sel),
244 MAX_LINE_LEN);
245 ret = MENU_RET_NO_UPDATE;
246 break;
247 case 2: /* insert above */
248 if (!rb->kbd_input(copy_buffer,MAX_LINE_LEN, NULL))
249 {
250 do_action(ACTION_INSERT,copy_buffer,cur_sel);
251 copy_buffer[0]='\0';
252 ret = MENU_RET_UPDATE;
253 }
254 break;
255 case 3: /* insert below */
256 if (!rb->kbd_input(copy_buffer,MAX_LINE_LEN, NULL))
257 {
258 do_action(ACTION_INSERT,copy_buffer,cur_sel+1);
259 copy_buffer[0]='\0';
260 ret = MENU_RET_UPDATE;
261 }
262 break;
263 case 4: /* cat to above */
264 if (cur_sel>0)
265 {
266 do_action(ACTION_CONCAT,0,cur_sel);
267 ret = MENU_RET_UPDATE;
268 }
269 break;
270 case 5: /* save */
271 ret = MENU_RET_SAVE;
272 break;
273 case 6: /* playback menu */
274 if (!audio_buf)
275 playback_control(NULL);
276 else
277 rb->splash(HZ, "Cannot restart playback");
278 ret = MENU_RET_NO_UPDATE;
279 break;
280 default:
281 ret = MENU_RET_NO_UPDATE;
282 break;
283 }
284 return ret;
285}
286
287#ifdef HAVE_LCD_COLOR
288
289#define hex2dec(c) (((c) >= '0' && ((c) <= '9')) ? (toupper(c)) - '0' : \
290 (toupper(c)) - 'A' + 10)
291
292static int my_hex_to_rgb(const char* hex, int* color)
293{ int ok = 1;
294 int i;
295 int red, green, blue;
296
297 if (rb->strlen(hex) == 6) {
298 for (i=0; i < 6; i++ ) {
299 if (!isxdigit(hex[i])) {
300 ok=0;
301 break;
302 }
303 }
304
305 if (ok) {
306 red = (hex2dec(hex[0]) << 4) | hex2dec(hex[1]);
307 green = (hex2dec(hex[2]) << 4) | hex2dec(hex[3]);
308 blue = (hex2dec(hex[4]) << 4) | hex2dec(hex[5]);
309 *color = LCD_RGBPACK(red,green,blue);
310 return 0;
311 }
312 }
313
314 return -1;
315}
316#endif /* HAVE_LCD_COLOR */
317
318/* this is the plugin entry point */
319enum plugin_status plugin_start(const void* parameter)
320{
321 int fd;
322
323 struct gui_synclist lists;
324 bool exit = false;
325 int button;
326 bool changed = false;
327 int cur_sel=0;
328#ifdef HAVE_LCD_COLOR
329 bool edit_colors_file = false;
330#endif
331
332 copy_buffer[0]='\0';
333
334#if LCD_DEPTH > 1
335 rb->lcd_set_backdrop(NULL);
336#endif
337 buffer = rb->plugin_get_buffer(&buffer_size);
338
339#ifdef HAVE_ADJUSTABLE_CPU_FREQ
340 rb->cpu_boost(1);
341#endif
342 if (parameter)
343 {
344#ifdef HAVE_LCD_COLOR
345 char *c = NULL;
346#endif
347 rb->strlcpy(filename, (char*)parameter, MAX_PATH);
348 get_eol_string(filename);
349 fd = rb->open(filename,O_RDONLY);
350 if (fd<0)
351 {
352 rb->splashf(HZ*2, "Couldnt open file: %s", filename);
353 return PLUGIN_ERROR;
354 }
355#ifdef HAVE_LCD_COLOR
356 c = rb->strrchr(filename, '.');
357 if (c && !rb->strcmp(c, ".colours"))
358 edit_colors_file = true;
359#endif
360 if (buffer_size <= (size_t)rb->filesize(fd) + 0x400)
361 {
362 buffer = rb->plugin_get_audio_buffer(&buffer_size);
363 audio_buf = true;
364 }
365 /* read in the file */
366 while (rb->read_line(fd,temp_line,MAX_LINE_LEN) > 0)
367 {
368 if (!do_action(ACTION_INSERT,temp_line,line_count))
369 {
370 rb->splashf(HZ*2,"Error reading file: %s",(char*)parameter);
371 rb->close(fd);
372 return PLUGIN_ERROR;
373 }
374 }
375 rb->close(fd);
376 newfile = false;
377 }
378 else
379 {
380 rb->strcpy(filename,"/");
381 rb->strcpy(eol,"\n");
382 newfile = true;
383 }
384
385#ifdef HAVE_ADJUSTABLE_CPU_FREQ
386 rb->cpu_boost(0);
387#endif
388 /* now dump it in the list */
389 setup_lists(&lists,0);
390 rb->lcd_update();
391 while (!exit)
392 {
393 rb->gui_synclist_draw(&lists);
394 cur_sel = rb->gui_synclist_get_sel_pos(&lists);
395 button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
396 if (rb->gui_synclist_do_button(&lists, &button))
397 continue;
398 switch (button)
399 {
400 case ACTION_STD_OK:
401 {
402 if (line_count)
403 rb->strlcpy(temp_line, do_action(ACTION_GET, 0, cur_sel),
404 MAX_LINE_LEN);
405#ifdef HAVE_LCD_COLOR
406 if (edit_colors_file && line_count)
407 {
408 char *name = temp_line, *value = NULL;
409 char extension[16];
410 int color, old_color;
411 bool temp_changed = false;
412
413 MENUITEM_STRINGLIST(menu, "Edit What?", NULL,
414 "Extension", "Colour");
415
416 rb->settings_parseline(temp_line, &name, &value);
417 rb->strlcpy(extension, name, sizeof(extension));
418 if (value)
419 my_hex_to_rgb(value, &color);
420 else
421 color = 0;
422
423 switch (rb->do_menu(&menu, NULL, NULL, false))
424 {
425 case 0:
426 temp_changed = !rb->kbd_input(extension, sizeof(extension), NULL);
427 break;
428 case 1:
429 old_color = color;
430 rb->set_color(rb->screens[SCREEN_MAIN], name, &color, -1);
431 temp_changed = (value == NULL) || (color != old_color);
432 break;
433 }
434
435 if (temp_changed)
436 {
437 rb->snprintf(temp_line, MAX_LINE_LEN, "%s: %02X%02X%02X",
438 extension, RGB_UNPACK_RED(color),
439 RGB_UNPACK_GREEN(color),
440 RGB_UNPACK_BLUE(color));
441 do_action(ACTION_UPDATE, temp_line, cur_sel);
442 changed = true;
443 }
444 }
445 else
446#endif
447 if (!rb->kbd_input(temp_line,MAX_LINE_LEN, NULL))
448 {
449 if (line_count)
450 do_action(ACTION_UPDATE,temp_line,cur_sel);
451 else
452 do_action(ACTION_INSERT,temp_line,cur_sel);
453 changed = true;
454 }
455 }
456 break;
457 case ACTION_STD_CONTEXT:
458/* These targets have unintuitive STD_MENU keymaps, so we use context keymap instead;
459 We don't need the "delete line" action, since this can be done via the menu. */
460#if (CONFIG_KEYPAD != SAMSUNG_YH92X_PAD) && (CONFIG_KEYPAD != SAMSUNG_YH820_PAD)
461 if (!line_count) break;
462 rb->strlcpy(copy_buffer, do_action(ACTION_GET, 0, cur_sel),
463 MAX_LINE_LEN);
464 do_action(ACTION_REMOVE, 0, cur_sel);
465 changed = true;
466 break;
467 case ACTION_STD_MENU:
468#endif
469 {
470 /* do the item menu */
471 switch (do_item_menu(cur_sel))
472 {
473 case MENU_RET_SAVE:
474 if(save_changes(1))
475 changed = false;
476 break;
477 case MENU_RET_UPDATE:
478 changed = true;
479 break;
480 case MENU_RET_NO_UPDATE:
481 break;
482 }
483 }
484 break;
485 case ACTION_STD_CANCEL:
486 if (changed)
487 {
488 MENUITEM_STRINGLIST(menu, "Do What?", NULL,
489 "Return",
490 "Playback Control", "Save Changes",
491 "Save As...", "Save and Exit",
492 "Ignore Changes and Exit");
493 switch (rb->do_menu(&menu, NULL, NULL, false))
494 {
495 case 0:
496 break;
497 case 1:
498 if (!audio_buf)
499 playback_control(NULL);
500 else
501 rb->splash(HZ, "Cannot restart playback");
502 break;
503 case 2: //save to disk
504 if(save_changes(1))
505 changed = 0;
506 break;
507 case 3:
508 if(save_changes(0))
509 changed = 0;
510 break;
511 case 4:
512 if(save_changes(1))
513 exit=1;
514 break;
515 case 5:
516 exit=1;
517 break;
518 }
519 }
520 else exit=1;
521 break;
522 }
523 rb->gui_synclist_set_nb_items(&lists,line_count);
524 if(line_count > 0 && line_count <= cur_sel)
525 rb->gui_synclist_select_item(&lists,line_count-1);
526 }
527 return PLUGIN_OK;
528}