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) 2020 William Wilgus
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/* open_plugins.rock interfaces with the open_plugin core
23 *
24 * When opened directly it acts as a viewer for the plugin.dat file
25 * this allows you to edit the paths and parameters for
26 * core shortcuts as well as your added plugins
27 *
28 * If a plugin is supplied to the viewer it is added to the dat file
29 *
30 * If instead the plugin has previously been added then it is run
31 * with the parameters previously supplied
32 */
33
34#include "plugin.h"
35#include "lang_enum.h"
36#include "../open_plugin.h"
37
38#define ROCK_EXT "rock"
39#define ROCK_LEN 5
40
41#define OP_EXT "opx"
42#define OP_LEN 4
43
44#define OP_PLUGIN_RESTART (PLUGIN_GOTO_PLUGIN | 0x8000)
45
46#define MENU_ID_MAIN "0"
47#define MENU_ID_EDIT "1"
48
49static int fd_dat;
50static struct gui_synclist lists;
51struct open_plugin_entry_t op_entry;
52static const uint32_t open_plugin_csum = OPEN_PLUGIN_CHECKSUM;
53static const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
54
55/* we only need the names for the first menu so don't bother reading paths yet */
56const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry);
57
58static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter, bool use_key);
59
60static bool _yesno_pop(const char* text)
61{
62 const char *lines[]={text};
63 const struct text_message message={lines, 1};
64 bool ret = (rb->gui_syncyesno_run(&message,NULL,NULL)== YESNO_YES);
65 FOR_NB_SCREENS(i)
66 rb->screens[i]->clear_viewport();
67 return ret;
68}
69
70static size_t pathbasename(const char *name, const char **nameptr)
71{
72 const char *p = name;
73 const char *q = p;
74 const char *r = q;
75
76 while (*(p = GOBBLE_PATH_SEPCH(p)))
77 {
78 q = p;
79 p = GOBBLE_PATH_COMP(++p);
80 r = p;
81 }
82
83 if (r == name && p > name)
84 q = p, r = q--; /* root - return last slash */
85 /* else path is an empty string */
86
87 *nameptr = q;
88 return r - q;
89}
90static int op_entry_checksum(void)
91{
92 if (op_entry.checksum != open_plugin_csum +
93 (op_entry.lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY))
94 {
95 return 0;
96 }
97 return 1;
98}
99
100static bool op_entry_read(int fd, int selected_item, off_t data_sz)
101{
102 rb->memset(&op_entry, 0, op_entry_sz);
103 op_entry.lang_id = -1;
104 return ((selected_item >= 0) && (fd >= 0) &&
105 (rb->lseek(fd, selected_item * op_entry_sz, SEEK_SET) >= 0) &&
106 (rb->read(fd, &op_entry, data_sz) == data_sz) && op_entry_checksum() > 0);
107}
108
109static bool op_entry_read_name(int fd, int selected_item)
110{
111 return op_entry_read(fd, selected_item, op_name_sz);
112}
113
114static int op_entry_read_opx(const char *path)
115{
116 int ret = -1;
117 off_t filesize;
118 int fd_opx;
119
120 if(rb->filetype_get_attr(path) == FILE_ATTR_OPX)
121 {
122 fd_opx = rb->open(path, O_RDONLY);
123 if (fd_opx >= 0)
124 {
125 filesize = rb->filesize(fd_opx);
126 ret = filesize;
127 if (filesize == op_entry_sz && !op_entry_read(fd_opx, 0, op_entry_sz))
128 ret = 0;
129 else if (op_entry_checksum() <= 0)
130 ret = 0;
131 rb->close(fd_opx);
132 }
133 }
134 return ret;
135}
136
137static void op_entry_export(int selection)
138{
139 int len;
140 int fd = -1;
141 char filename [MAX_PATH + 1];
142
143 if (!op_entry_read(fd_dat, selection, op_entry_sz) || op_entry_checksum() <= 0)
144 goto failure;
145
146 rb->snprintf(filename, MAX_PATH, "%s/%s", PLUGIN_APPS_DIR, op_entry.name);
147
148 if( !rb->kbd_input( filename, MAX_PATH, NULL ) )
149 {
150 len = rb->strlen(filename);
151 if(len > OP_LEN && filename[len] != PATH_SEPCH &&
152 rb->strcasecmp(&((filename)[len-OP_LEN]), "." OP_EXT) != 0)
153 {
154 rb->strcat(filename, "." OP_EXT);
155 }
156
157 fd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
158
159 if (fd >= 0 && rb->write(fd, &op_entry, op_entry_sz) == op_entry_sz)
160 {
161 rb->close(fd);
162 rb->splashf( 1*HZ, "File Saved (%s)", filename );
163 return;
164 }
165 rb->close(fd);
166 }
167
168failure:
169 rb->splashf( 2*HZ, "Save Failed (%s)", filename );
170
171}
172
173static void op_entry_set_checksum(void)
174{
175 op_entry.checksum = open_plugin_csum +
176 (op_entry.lang_id <= OPEN_PLUGIN_LANG_INVALID ? 0 : LANG_LAST_INDEX_IN_ARRAY);
177}
178
179static void op_entry_set_name(void)
180{
181 char tmp_buf[OPEN_PLUGIN_NAMESZ+1];
182 rb->strlcpy(tmp_buf, op_entry.name, OPEN_PLUGIN_NAMESZ);
183 if (rb->kbd_input(tmp_buf, OPEN_PLUGIN_NAMESZ, NULL) >= 0)
184 rb->strlcpy(op_entry.name, tmp_buf, OPEN_PLUGIN_NAMESZ);
185}
186
187static int op_entry_set_path(void)
188{
189 int ret = 0;
190 char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
191
192 if (op_entry.path[0] == '\0')
193 rb->strcpy(op_entry.path, PLUGIN_DIR"/");
194
195 struct browse_context browse = {
196 .dirfilter = SHOW_ALL,
197 .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER,
198 .title = rb->str(LANG_ADD),
199 .icon = Icon_Plugin,
200 .root = op_entry.path,
201 .buf = tmp_buf,
202 .bufsize = sizeof(tmp_buf),
203 };
204
205 if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
206 {
207 ret = rb->strlcpy(op_entry.path, tmp_buf, OPEN_PLUGIN_BUFSZ);
208 if (ret > OPEN_PLUGIN_BUFSZ)
209 ret = 0;
210 }
211 return ret;
212}
213
214static int op_entry_set_param_path(void)
215{
216 int ret = 0;
217 char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
218
219 if (op_entry.param[0] == '\0')
220 rb->strcpy(tmp_buf, "/");
221 else
222 rb->strcpy(tmp_buf, op_entry.param);
223
224 struct browse_context browse = {
225 .dirfilter = SHOW_ALL,
226 .flags = BROWSE_SELECTONLY | BROWSE_DIRFILTER,
227 .title = rb->str(LANG_PARAMETER),
228 .icon = Icon_Plugin,
229 .root = tmp_buf,
230 .buf = tmp_buf,
231 .bufsize = sizeof(tmp_buf),
232 };
233
234 if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
235 {
236 ret = rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ);
237 if (ret > OPEN_PLUGIN_BUFSZ)
238 ret = 0;
239 }
240 return ret;
241}
242
243static void op_entry_set_param(void)
244{
245 if (_yesno_pop(ID2P(LANG_BROWSE)))
246 op_entry_set_param_path();
247
248 char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
249 rb->strlcpy(tmp_buf, op_entry.param, OPEN_PLUGIN_BUFSZ);
250 if (rb->kbd_input(tmp_buf, OPEN_PLUGIN_BUFSZ, NULL) >= 0)
251 rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ);
252}
253
254static int op_et_exclude_hash(struct open_plugin_entry_t *op_entry, int item, void *data)
255{
256 (void)item;
257
258 if (op_entry->hash == 0 || op_entry->name[0] == '\0')
259 return 0;
260
261 if (data)
262 {
263 uint32_t *hash = data;
264 if (op_entry->hash != *hash)
265 return 1;
266 }
267 return 0;
268}
269
270static int op_et_exclude_builtin(struct open_plugin_entry_t *op_entry, int item, void *data)
271{
272 (void)item;
273 (void)data;
274
275 if (op_entry->lang_id >= 0)
276 return 0;
277 else if(op_entry->hash == 0 || op_entry->name[0] == '\0')
278 return 0;
279
280 return 1;
281}
282
283static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, void *data)
284{
285 (void)item;
286 (void)data;
287
288 if (op_entry->lang_id < 0)
289 return 0;
290 else if (op_entry->hash == 0 || op_entry->name[0] == '\0')
291 return 0;
292
293 return 1;
294}
295
296static int op_entry_transfer(int fd, int fd_tmp,
297 int(*compfn)(struct open_plugin_entry_t*, int, void*),
298 void *data)
299{
300 int entries = -1;
301 if (fd_tmp >= 0 && fd >= 0 && rb->lseek(fd, 0, SEEK_SET) == 0)
302 {
303 entries = 0;
304 while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz)
305 {
306 if (compfn && compfn(&op_entry, entries, data) > 0 && op_entry_checksum() > 0)
307 {
308 rb->write(fd_tmp, &op_entry, op_entry_sz);
309 entries++;
310 }
311 }
312 }
313 return entries + 1;
314}
315
316static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter, bool use_key)
317{
318 char buf[MAX_PATH];
319 uint32_t hash;
320 uint32_t newhash;
321 char *pos = "";;
322 int fd_tmp = -1;
323 use_key = (use_key == true && key != NULL);
324
325 if (key)
326 {
327 open_plugin_get_hash(key, &hash);
328 op_entry.hash = hash;
329 }
330 else if (op_entry.lang_id < 0 && plugin)
331 {
332 /* need to keep the old hash so we can remove the old entry */
333 hash = op_entry.hash;
334 open_plugin_get_hash(plugin, &newhash);
335 op_entry.hash = newhash;
336 }
337 else
338 hash = op_entry.hash;
339
340 if (plugin)
341 {
342 int fattr = rb->filetype_get_attr(plugin);
343 /* name */
344 if (use_key)
345 {
346 op_entry.lang_id = -1;
347 rb->strlcpy(op_entry.name, key, OPEN_PLUGIN_NAMESZ);
348 }
349
350 if (pathbasename(plugin, (const char **)&pos) == 0)
351 pos = "\0";
352 if (op_entry.name[0] == '\0' || op_entry.lang_id >= 0)
353 rb->strlcpy(op_entry.name, pos, OPEN_PLUGIN_NAMESZ);
354
355
356
357 if ((!parameter || parameter[0] == '\0') && fattr != FILE_ATTR_ROCK && fattr != FILE_ATTR_OPX)
358 {
359 rb->strlcpy(op_entry.param, plugin, OPEN_PLUGIN_BUFSZ);
360 parameter = op_entry.param;
361 plugin = rb->filetype_get_plugin(fattr, buf, sizeof(buf));
362 if (!plugin)
363 {
364 rb->splashf(HZ * 2, ID2P(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
365 return 0;
366 }
367 }
368
369 if(fattr == FILE_ATTR_ROCK)
370 {
371 fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
372 if (fd_tmp < 0)
373 return 0;
374
375 /* path */
376 if (plugin != op_entry.path)
377 rb->strlcpy(op_entry.path, plugin, OPEN_PLUGIN_BUFSZ);
378
379 if(parameter)
380 {
381 /* param matches lang_id so we want to set parameter */
382 bool needs_param = op_entry.lang_id >= 0
383 && (rb->strcmp(parameter, rb->str(op_entry.lang_id)) == 0);
384 if (needs_param
385 || (parameter[0] == '\0' && _yesno_pop(ID2P(LANG_PARAMETER))))
386 {
387 if (needs_param)
388 op_entry.param[0] = '\0';
389 op_entry_set_param();
390 }
391 else if (parameter != op_entry.param)
392 rb->strlcpy(op_entry.param, parameter, OPEN_PLUGIN_BUFSZ);
393
394 /* hash on the parameter path if it is a file */
395 if (op_entry.lang_id <0 && key == op_entry.path &&
396 rb->file_exists(op_entry.param))
397 {
398 open_plugin_get_hash(op_entry.path, &newhash);
399 op_entry.hash = newhash;
400 }
401 }
402 op_entry_set_checksum();
403 rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
404 }
405 else if(op_entry_read_opx(plugin) == op_entry_sz)
406 {
407 fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
408 if (fd_tmp < 0)
409 return 0;
410
411 if (op_entry.lang_id <0 && rb->file_exists(op_entry.param))
412 open_plugin_get_hash(op_entry.param, &hash);
413 else
414 open_plugin_get_hash(op_entry.path, &hash);
415
416 op_entry.hash = hash;
417 op_entry_set_checksum();
418 rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
419 }
420 else
421 {
422 if (op_entry.lang_id != LANG_SHORTCUTS)
423 rb->splashf(HZ * 2, ID2P(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
424 return 0;
425 }
426 }
427
428 if (op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_hash, &hash) > 0)
429 {
430 rb->close(fd_tmp);
431 rb->close(fd_dat);
432 fd_dat = -1;
433 rb->remove(OPEN_PLUGIN_DAT);
434 rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
435 }
436 else
437 {
438 rb->close(fd_tmp);
439 rb->remove(OPEN_PLUGIN_DAT ".tmp");
440 hash = 0;
441 }
442
443 return hash;
444}
445
446void op_entry_browse_add(int selection)
447{
448 char* key;
449 op_entry_read(fd_dat, selection, op_entry_sz);
450 if (op_entry_set_path() > 0)
451 {
452 if (op_entry.lang_id >= 0)
453 key = rb->str(op_entry.lang_id);
454 else
455 key = op_entry.path;
456
457 op_entry_add_path(key, op_entry.path, NULL, false);
458 }
459}
460
461static void op_entry_remove(int selection)
462{
463
464 int entries = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz;
465 int32_t hash = 0;
466 int lang_id = -1;
467
468 if (entries > 0 && _yesno_pop(ID2P(LANG_REMOVE)))
469 {
470 op_entry_read(fd_dat, selection, op_entry_sz);
471 if (rb->lseek(fd_dat, selection * op_entry_sz, SEEK_SET) >= 0)
472 {
473 if (op_entry.lang_id >= 0)
474 {
475 lang_id = op_entry.lang_id;
476 hash = op_entry.hash;
477 }
478 rb->memset(&op_entry, 0, op_entry_sz);
479 op_entry.lang_id = lang_id;
480 op_entry.hash = hash;
481 rb->write(fd_dat, &op_entry, op_entry_sz);
482 }
483 }
484}
485
486static void op_entry_remove_empty(void)
487{
488 bool resave = false;
489 if (fd_dat >= 0 && rb->lseek(fd_dat, 0, SEEK_SET) == 0)
490 {
491 while (resave == false &&
492 rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz)
493 {
494 if (op_entry.hash == 0 || !op_entry_checksum())
495 resave = true;
496 }
497 }
498
499 if (resave)
500 {
501 int fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
502 if (fd_tmp < 0)
503 return;
504
505 if ((op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_user, NULL)
506 + op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_builtin, NULL)) > 0)
507 {
508 rb->close(fd_tmp);
509 rb->close(fd_dat);
510 fd_dat = -1;
511 rb->remove(OPEN_PLUGIN_DAT);
512 rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
513 }
514 else
515 {
516 rb->close(fd_tmp);
517 rb->remove(OPEN_PLUGIN_DAT ".tmp");
518 }
519 }
520
521}
522
523static int op_entry_run(void)
524{
525 int ret = PLUGIN_ERROR;
526 char* path;
527 char* param;
528 if (op_entry.hash != 0 && op_entry.path[0] != '\0')
529 {
530 //rb->splash(1, ID2P(LANG_OPEN_PLUGIN));
531 path = op_entry.path;
532 param = op_entry.param;
533 if (param[0] == '\0')
534 param = NULL;
535
536 ret = rb->plugin_open(path, param);
537 }
538 return ret;
539}
540
541static const char* list_get_name_cb(int selected_item, void* data,
542 char* buf, size_t buf_len)
543{
544 /*TODO memoize names so we don't keep reading the disk when not necessary */
545 if (data == (void*) &MENU_ID_MAIN) /* check address */
546 {
547 if (op_entry_read_name(fd_dat, selected_item))
548 {
549 if (op_entry.lang_id >= 0)
550 rb->snprintf(buf, buf_len, "%s [%s] ",
551 rb->str(op_entry.lang_id), op_entry.name);
552 else if (rb->strlcpy(buf, op_entry.name, buf_len) >= buf_len)
553 rb->strcpy(&buf[buf_len-10], " ...");
554 }
555 else
556 return "?";
557 }
558 else /* op_entry should already be loaded */
559 {
560 switch(selected_item)
561 {
562 case 0:
563 return ID2P(LANG_NAME);
564 case 1:
565 if (op_entry.lang_id >= 0)
566 rb->snprintf(buf, buf_len, "%s [%s] ",
567 rb->str(op_entry.lang_id), op_entry.name);
568 else if (rb->strlcpy(buf, op_entry.name, buf_len) >= buf_len)
569 rb->strcpy(&buf[buf_len-10], " ...");
570 break;
571 case 2:
572 return ID2P(LANG_DISPLAY_FULL_PATH);
573 case 3:
574 if (rb->strlcpy(buf, op_entry.path, buf_len) >= buf_len)
575 rb->strcpy(&buf[buf_len-10], " ...");
576 break;
577 case 4:
578 return ID2P(LANG_PARAMETER);
579 case 5:
580 if (op_entry.param[0] == '\0')
581 return "[NULL]";
582 else if (rb->strlcpy(buf, op_entry.param, buf_len) >= buf_len)
583 rb->strcpy(&buf[buf_len-10], " ...");
584 break;
585 case 6:
586 return "";
587 case 7:
588 return ID2P(LANG_BACK);
589 default:
590 return "?";
591 }
592 }
593
594 return buf;
595}
596
597static int list_voice_cb(int list_index, void* data)
598{
599 if (data == (void*) &MENU_ID_MAIN) /* check address */
600 {
601 if (op_entry_read_name(fd_dat, list_index))
602 {
603 if (op_entry.lang_id >= 0)
604 {
605 rb->talk_id(op_entry.lang_id, false);
606 rb->talk_id(VOICE_PAUSE, true);
607 rb->talk_force_enqueue_next();
608 }
609 return rb->talk_spell(op_entry.name, false);
610 }
611 }
612 else
613 {
614 switch(list_index)
615 {
616 case 0:
617 rb->talk_id(LANG_NAME, false);
618 rb->talk_id(VOICE_PAUSE, true);
619
620 if (op_entry.lang_id >= 0)
621 {
622 rb->talk_id(op_entry.lang_id, true);
623 rb->talk_id(VOICE_PAUSE, true);
624 rb->talk_force_enqueue_next();
625 }
626 return rb->talk_spell(op_entry.name, false);
627 case 2:
628 return rb->talk_id(LANG_DISPLAY_FULL_PATH, false);
629 case 4:
630 return rb->talk_id(LANG_PARAMETER, false);
631 case 6:
632 return rb->talk_id(LANG_BACK, false);
633 default:
634 return 0;
635 }
636 }
637
638 return 0;
639}
640
641static void synclist_set(char* menu_id, int selection, int items, int sel_size)
642{
643 if (selection < 0)
644 selection = 0;
645
646 rb->gui_synclist_init(&lists,list_get_name_cb,
647 menu_id, false, sel_size, NULL);
648
649 rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
650 rb->gui_synclist_set_nb_items(&lists,items);
651 rb->gui_synclist_select_item(&lists, selection);
652 list_voice_cb(selection, menu_id);
653}
654
655static int context_menu_cb(int action,
656 const struct menu_item_ex *this_item,
657 struct gui_synclist *this_list)
658{
659 (void)this_item;
660
661 int selection = rb->gui_synclist_get_sel_pos(this_list);
662
663 if(action == ACTION_ENTER_MENUITEM)
664 {
665 if (selection == 0 &&
666 op_entry.lang_id >= 0 && op_entry.lang_id != LANG_OPEN_PLUGIN)
667 {
668 rb->gui_synclist_set_title(this_list,
669 rb->str(op_entry.lang_id), 0);
670 }
671 }
672 else if ((action == ACTION_STD_OK))
673 {
674 /*Run, Edit, Remove, Export, Blank, Import, Add, Back*/
675 switch(selection)
676 {
677 case 0:case 1:case 2:case 3:case 5:
678 return ACTION_STD_OK;
679 case 4: /*blank*/
680 break;
681 default:
682 return ACTION_STD_CANCEL;
683 }
684 rb->gui_synclist_draw(this_list); /* redraw */
685 return 0;
686 }
687
688 return action;
689}
690
691static void edit_menu(int selection)
692{
693 int selected_item;
694 bool exit = false;
695 int action = 0;
696
697 if (!op_entry_read(fd_dat, selection, op_entry_sz))
698 return;
699
700 uint32_t crc = rb->crc_32(&op_entry, op_entry_sz, 0xffffffff);
701
702 synclist_set(MENU_ID_EDIT, 2, 8, 2);
703 rb->gui_synclist_draw(&lists);
704
705 while (!exit)
706 {
707 action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
708
709 if (rb->gui_synclist_do_button(&lists, &action))
710 continue;
711 selected_item = rb->gui_synclist_get_sel_pos(&lists);
712 switch (action)
713 {
714 case ACTION_STD_OK:
715 if (selected_item == 0)
716 op_entry_set_name();
717 else if (selected_item == 2)
718 op_entry_set_path();
719 else if (selected_item == 4)
720 op_entry_set_param();
721 else
722 exit = true;
723
724 rb->gui_synclist_draw(&lists);
725 break;
726 case ACTION_STD_CANCEL:
727 exit = true;
728 break;
729 }
730 }
731
732 if (crc != rb->crc_32(&op_entry, op_entry_sz, 0xffffffff) &&
733 _yesno_pop(ID2P(LANG_SAVE)) == true)
734 {
735 char *param = op_entry.param;
736 if (param[0] == '\0')
737 param = NULL;
738
739 op_entry_add_path(NULL, op_entry.path, param, false);
740 fd_dat = rb->open(OPEN_PLUGIN_DAT, O_RDWR, 0666);
741 }
742}
743
744static int context_menu(int selection)
745{
746 int selected_item;
747 if (op_entry_read(fd_dat, selection, op_entry_sz))
748 {
749 MENUITEM_STRINGLIST(menu, op_entry.name, context_menu_cb,
750 ID2P(LANG_RUN), ID2P(LANG_EDIT), ID2P(LANG_REMOVE), ID2P(LANG_EXPORT),
751 ID2P(VOICE_BLANK), ID2P(LANG_ADD), ID2P(LANG_BACK));
752
753 selected_item = rb->do_menu(&menu, 0, NULL, false);
754 switch (selected_item)
755 {
756 case 0: /*run*/
757 return PLUGIN_GOTO_PLUGIN;
758 case 1: /*edit*/
759 edit_menu(selection);
760 break;
761 case 2: /*remove*/
762 op_entry_remove(selection);
763 break;
764 case 3: /*export*/
765 op_entry_export(selection);
766 break;
767 case 4: /*blank*/
768 break;
769 case 5: /*add*/
770 op_entry_browse_add(-1);
771 rb->plugin_open(rb->plugin_get_current_filename(), "\0");
772 return OP_PLUGIN_RESTART;
773 default:
774 break;
775
776 }
777 return PLUGIN_OK;
778 }
779 return PLUGIN_ERROR;
780}
781
782enum plugin_status plugin_start(const void* parameter)
783{
784 int ret = PLUGIN_OK;
785 uint32_t hash = 0;
786 int item = -1;
787 int selection = -1;
788 int action;
789 int items;
790 int res;
791 char *path;
792 bool exit = false;
793
794 const int creat_flags = O_RDWR | O_CREAT;
795
796reopen_datfile:
797 fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
798 if (!fd_dat)
799 exit = true;
800
801 items = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz;
802 if (parameter)
803 {
804 path = (char*)parameter;
805 while (path[0] == ' ')
806 path++;
807
808 if (rb->strncasecmp(path, "-add", 4) == 0)
809 {
810 parameter = NULL;
811 op_entry_browse_add(-1);
812 rb->close(fd_dat);
813 goto reopen_datfile;
814 }
815 }
816
817 if (parameter)
818 {
819 path = (char*)parameter;
820 res = op_entry_read_opx(path);
821 if (res >= 0)
822 {
823 if (res == op_entry_sz)
824 {
825 exit = true;
826 ret = op_entry_run();
827 }
828 }
829 else
830 {
831 open_plugin_get_hash(parameter, &hash);
832 rb->lseek(fd_dat, 0, SEEK_SET);
833 while (rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz)
834 {
835 item++;
836 if (op_entry.hash == hash)
837 {
838 selection = item;
839 break;
840 }
841 }
842
843 if (selection >= 0)
844 {
845 if (op_entry_read(fd_dat, selection, op_entry_sz))
846 {
847 /* param matches lang_id so we want to set the parameter */
848 if (op_entry.lang_id >= 0
849 && rb->strcmp(op_entry.param, rb->str(op_entry.lang_id)) == 0)
850 {
851 op_entry_add_path(NULL, op_entry.path, op_entry.param, false);
852 exit = true;
853 }
854 else
855 {
856 ret = op_entry_run();
857 if (ret == PLUGIN_GOTO_PLUGIN)
858 exit = true;
859 }
860 }
861 }
862 else
863 {
864 op_entry_read(fd_dat, selection, op_entry_sz);
865 if (op_entry_add_path(parameter, parameter, "\0", false) > 0)
866 {
867 selection = 0;
868 items++;
869 fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
870 if (!fd_dat)
871 exit = true;
872 }
873 }
874 }/* OP_EXT */
875 }
876
877 for (int i = items - 1; i > 0 && !exit; i--)
878 {
879 if (!op_entry_read(fd_dat, i, op_entry_sz))
880 items--;
881 }
882
883 if (items < 1 && !exit)
884 {
885 char* cur_filename = rb->plugin_get_current_filename();
886
887 if (op_entry_add_path(rb->str(LANG_ADD), cur_filename, "-add", true))
888 {
889 rb->close(fd_dat);
890 parameter = NULL;
891 goto reopen_datfile;
892 }
893 rb->close(fd_dat);
894 return PLUGIN_ERROR;
895 }
896
897
898
899 if (!exit)
900 {
901 synclist_set(MENU_ID_MAIN, selection, items, 1);
902 rb->gui_synclist_draw(&lists);
903
904 while (!exit && fd_dat >= 0)
905 {
906 action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
907
908 if (rb->gui_synclist_do_button(&lists, &action))
909 continue;
910 selection = rb->gui_synclist_get_sel_pos(&lists);
911 switch (action)
912 {
913 case ACTION_STD_CONTEXT:
914 ret = context_menu(selection);
915 if (ret == OP_PLUGIN_RESTART)
916 {
917 ret = PLUGIN_GOTO_PLUGIN;
918 exit = true;
919 break;
920 }
921 else if (ret != PLUGIN_GOTO_PLUGIN)
922 {
923 synclist_set(MENU_ID_MAIN, selection, items, 1);
924 rb->gui_synclist_draw(&lists);
925 break;
926 }
927 /* Inentional fallthrough */
928 case ACTION_STD_OK:
929 if (op_entry_read(fd_dat, selection, op_entry_sz))
930 {
931 ret = op_entry_run();
932 exit = true;
933 }
934 break;
935 case ACTION_STD_CANCEL:
936 case ACTION_STD_MENU:
937 {
938 selection = -2;
939 exit = true;
940 break;
941 }
942 }
943 }
944 op_entry_remove_empty();
945 }
946 rb->close(fd_dat);
947 if (ret != PLUGIN_OK)
948 return ret;
949 else
950 return PLUGIN_OK;
951}