summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/lang/english.lang98
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/open_plugins.c823
-rw-r--r--apps/plugins/plugins.make5
-rw-r--r--apps/plugins/viewers.config2
-rwxr-xr-xtools/buildzip.pl6
7 files changed, 936 insertions, 0 deletions
diff --git a/apps/lang/english.lang b/apps/lang/english.lang
index 8af6e55c5e..fc5a37c569 100644
--- a/apps/lang/english.lang
+++ b/apps/lang/english.lang
@@ -16206,3 +16206,101 @@
16206 *: "Parameter" 16206 *: "Parameter"
16207 </voice> 16207 </voice>
16208</phrase> 16208</phrase>
16209<phrase>
16210 id: LANG_NAME
16211 desc:
16212 user: core
16213 <source>
16214 *: "Name"
16215 </source>
16216 <dest>
16217 *: "Name"
16218 </dest>
16219 <voice>
16220 *: "Name"
16221 </voice>
16222</phrase>
16223<phrase>
16224 id: LANG_ADD
16225 desc:
16226 user: core
16227 <source>
16228 *: "Add"
16229 </source>
16230 <dest>
16231 *: "Add"
16232 </dest>
16233 <voice>
16234 *: "Add"
16235 </voice>
16236</phrase>
16237<phrase>
16238 id: LANG_BACK
16239 desc:
16240 user: core
16241 <source>
16242 *: "Back"
16243 </source>
16244 <dest>
16245 *: "Back"
16246 </dest>
16247 <voice>
16248 *: "Back"
16249 </voice>
16250</phrase>
16251<phrase>
16252 id: LANG_EDIT
16253 desc:
16254 user: core
16255 <source>
16256 *: "Edit"
16257 </source>
16258 <dest>
16259 *: "Edit"
16260 </dest>
16261 <voice>
16262 *: "Edit"
16263 </voice>
16264</phrase>
16265<phrase>
16266 id: LANG_RUN
16267 desc:
16268 user: core
16269 <source>
16270 *: "Run"
16271 </source>
16272 <dest>
16273 *: "Run"
16274 </dest>
16275 <voice>
16276 *: "Run"
16277 </voice>
16278</phrase>
16279<phrase>
16280 id: LANG_EXPORT
16281 desc:
16282 user: core
16283 <source>
16284 *: "Export"
16285 </source>
16286 <dest>
16287 *: "Export"
16288 </dest>
16289 <voice>
16290 *: "Export"
16291 </voice>
16292</phrase>
16293<phrase>
16294 id: LANG_BROWSE
16295 desc:
16296 user: core
16297 <source>
16298 *: "Browse"
16299 </source>
16300 <dest>
16301 *: "Browse"
16302 </dest>
16303 <voice>
16304 *: "Browse"
16305 </voice>
16306</phrase>
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 07bc4df847..d3093689f9 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -67,6 +67,7 @@ mosaique,demos
67mp3_encoder,apps 67mp3_encoder,apps
68mpegplayer,viewers 68mpegplayer,viewers
69nim,games 69nim,games
70open_plugins,viewers
70oscilloscope,demos 71oscilloscope,demos
71otp,apps 72otp,apps
72pacbox,games 73pacbox,games
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 46a3c61537..408dc6bc67 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -137,6 +137,7 @@ chopper.c
137demystify.c 137demystify.c
138jewels.c 138jewels.c
139minesweeper.c 139minesweeper.c
140open_plugins.c
140oscilloscope.c 141oscilloscope.c
141pegbox.c 142pegbox.c
142periodic_table.c 143periodic_table.c
diff --git a/apps/plugins/open_plugins.c b/apps/plugins/open_plugins.c
new file mode 100644
index 0000000000..f06a790d02
--- /dev/null
+++ b/apps/plugins/open_plugins.c
@@ -0,0 +1,823 @@
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 OP_EXT "opx"
40
41#define OP_PLUGIN_RESTART (PLUGIN_GOTO_PLUGIN | 0x8000)
42
43#define MENU_ID_MAIN "0"
44#define MENU_ID_EDIT "1"
45
46static int fd_dat;
47static struct gui_synclist lists;
48struct open_plugin_entry_t op_entry;
49const off_t op_entry_sz = sizeof(struct open_plugin_entry_t);
50
51/* we only need the names for the first menu so don't bother reading paths yet */
52const off_t op_name_sz = OPEN_PLUGIN_NAMESZ + (op_entry.name - (char*)&op_entry);
53
54static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter);
55
56static bool _yesno_pop(const char* text)
57{
58 const char *lines[]={text};
59 const struct text_message message={lines, 1};
60 bool ret = (rb->gui_syncyesno_run(&message,NULL,NULL)== YESNO_YES);
61 FOR_NB_SCREENS(i)
62 rb->screens[i]->clear_viewport();
63 return ret;
64}
65
66static size_t pathbasename(const char *name, const char **nameptr)
67{
68 const char *p = name;
69 const char *q = p;
70 const char *r = q;
71
72 while (*(p = GOBBLE_PATH_SEPCH(p)))
73 {
74 q = p;
75 p = GOBBLE_PATH_COMP(++p);
76 r = p;
77 }
78
79 if (r == name && p > name)
80 q = p, r = q--; /* root - return last slash */
81 /* else path is an empty string */
82
83 *nameptr = q;
84 return r - q;
85}
86
87static bool op_entry_read(int fd, int selected_item, off_t data_sz)
88{
89 rb->memset(&op_entry, 0, op_entry_sz);
90 op_entry.lang_id = -1;
91 return ((selected_item >= 0) &&
92 (rb->lseek(fd, selected_item * op_entry_sz, SEEK_SET) >= 0) &&
93 (rb->read(fd, &op_entry, data_sz) == data_sz));
94}
95
96static bool op_entry_read_name(int fd, int selected_item)
97{
98 return op_entry_read(fd, selected_item, op_name_sz);
99}
100
101static void op_entry_export(int selection)
102{
103 int len;
104 int fd = -1;
105 char filename [MAX_PATH + 1];
106
107 if (!op_entry_read(fd_dat, selection, op_entry_sz))
108 goto failure;
109
110 rb->snprintf(filename, MAX_PATH, "%s/%s", PLUGIN_APPS_DIR, op_entry.name);
111
112 if( !rb->kbd_input( filename, MAX_PATH, NULL ) )
113 {
114 len = rb->strlen(filename);
115 if(len > 4 && filename[len] != PATH_SEPCH &&
116 rb->strcasecmp(&((filename)[len-4]), "." OP_EXT) != 0)
117 {
118 rb->strcat(filename, "." OP_EXT);
119 }
120
121 fd = rb->open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
122
123 if (fd >= 0 && rb->write(fd, &op_entry, op_entry_sz) == op_entry_sz)
124 {
125 rb->close(fd);
126 rb->splashf( 1*HZ, "File Saved (%s)", filename );
127 return;
128 }
129 rb->close(fd);
130 }
131
132failure:
133 rb->splashf( 2*HZ, "Save Failed (%s)", filename );
134
135}
136
137static void op_entry_set_name(void)
138{
139 char tmp_buf[OPEN_PLUGIN_NAMESZ+1];
140 rb->strlcpy(tmp_buf, op_entry.name, OPEN_PLUGIN_NAMESZ);
141 if (rb->kbd_input(tmp_buf, OPEN_PLUGIN_NAMESZ, NULL) >= 0)
142 rb->strlcpy(op_entry.name, tmp_buf, OPEN_PLUGIN_NAMESZ);
143}
144
145static int op_entry_set_path(void)
146{
147 int ret = 0;
148 struct browse_context browse;
149 char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
150
151 if (op_entry.path[0] == '\0')
152 rb->strcpy(op_entry.path, PLUGIN_DIR"/");
153
154 rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, "",
155 Icon_Plugin, op_entry.path, NULL);
156
157 browse.buf = tmp_buf;
158 browse.bufsize = OPEN_PLUGIN_BUFSZ;
159
160 if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
161 {
162 ret = rb->strlcpy(op_entry.path, tmp_buf, OPEN_PLUGIN_BUFSZ);
163 if (ret > OPEN_PLUGIN_BUFSZ)
164 ret = 0;
165 }
166 return ret;
167}
168
169static int op_entry_set_param_path(void)
170{
171 int ret = 0;
172 struct browse_context browse;
173 char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
174
175 if (op_entry.param[0] == '\0')
176 rb->strcpy(tmp_buf, "/");
177 else
178 rb->strcpy(tmp_buf, op_entry.param);
179
180 rb->browse_context_init(&browse, SHOW_ALL, BROWSE_SELECTONLY, "",
181 Icon_Plugin, tmp_buf, NULL);
182
183 browse.buf = tmp_buf;
184 browse.bufsize = OPEN_PLUGIN_BUFSZ;
185
186 if (rb->rockbox_browse(&browse) == GO_TO_PREVIOUS)
187 {
188 ret = rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ);
189 if (ret > OPEN_PLUGIN_BUFSZ)
190 ret = 0;
191 }
192 return ret;
193}
194
195static void op_entry_set_param(void)
196{
197 if (_yesno_pop(ID2P(LANG_BROWSE)))
198 op_entry_set_param_path();
199
200 char tmp_buf[OPEN_PLUGIN_BUFSZ+1];
201 rb->strlcpy(tmp_buf, op_entry.param, OPEN_PLUGIN_BUFSZ);
202 if (rb->kbd_input(tmp_buf, OPEN_PLUGIN_BUFSZ, NULL) >= 0)
203 rb->strlcpy(op_entry.param, tmp_buf, OPEN_PLUGIN_BUFSZ);
204}
205
206static int op_et_exclude_hash(struct open_plugin_entry_t *op_entry, int item, void *data)
207{
208 (void)item;
209
210 if (op_entry->hash == 0 || op_entry->name[0] == '\0')
211 return 0;
212
213 if (data)
214 {
215 uint32_t *hash = data;
216 if (op_entry->hash != *hash)
217 return 1;
218 }
219 return 0;
220}
221
222static int op_et_exclude_builtin(struct open_plugin_entry_t *op_entry, int item, void *data)
223{
224 (void)item;
225 (void)data;
226
227 if (op_entry->lang_id >= 0)
228 return 0;
229 else if(op_entry->hash == 0 || op_entry->name[0] == '\0')
230 return 0;
231
232 return 1;
233}
234
235static int op_et_exclude_user(struct open_plugin_entry_t *op_entry, int item, void *data)
236{
237 (void)item;
238 (void)data;
239
240 if (op_entry->lang_id < 0)
241 return 0;
242 else if (op_entry->hash == 0 || op_entry->name[0] == '\0')
243 return 0;
244
245 return 1;
246}
247
248static int op_entry_transfer(int fd, int fd_tmp,
249 int(*compfn)(struct open_plugin_entry_t*, int, void*),
250 void *data)
251{
252 int entries = -1;
253 if (fd_tmp && fd && rb->lseek(fd, 0, SEEK_SET) == 0)
254 {
255 entries = 0;
256 while (rb->read(fd, &op_entry, op_entry_sz) == op_entry_sz)
257 {
258 if (compfn && compfn(&op_entry, entries, data) > 0)
259 {
260 rb->write(fd_tmp, &op_entry, op_entry_sz);
261 entries++;
262 }
263 }
264 }
265 return entries + 1;
266}
267
268static uint32_t op_entry_add_path(const char *key, const char *plugin, const char *parameter)
269{
270 int len;
271 uint32_t hash;
272 char *pos = "";;
273 int fd_tmp = -1;
274
275 if (key)
276 {
277 open_plugin_get_hash(key, &hash);
278 op_entry.hash = hash;
279 }
280 else if (op_entry.lang_id < 0 && plugin)
281 {
282 /* need to keep the old hash so we can remove the old entry */
283 hash = op_entry.hash;
284 open_plugin_get_hash(plugin, &op_entry.hash);
285 }
286 else
287 hash = op_entry.hash;
288
289 if (plugin)
290 {
291 /* name */
292 if (pathbasename(plugin, (const char **)&pos) == 0)
293 pos = "\0";
294 if (op_entry.name[0] == '\0' || op_entry.lang_id >= 0)
295 rb->strlcpy(op_entry.name, pos, OPEN_PLUGIN_NAMESZ);
296
297 len = rb->strlen(pos);
298 if(len > 5 && rb->strcasecmp(&(pos[len-5]), "." ROCK_EXT) == 0)
299 {
300 fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
301 if (fd_tmp < 0)
302 return 0;
303
304 /* path */
305 if (plugin != op_entry.path)
306 rb->strlcpy(op_entry.path, plugin, OPEN_PLUGIN_BUFSZ);
307
308 if(parameter)
309 {
310 if (parameter[0] == '\0' &&
311 _yesno_pop(ID2P(LANG_PARAMETER)))
312 {
313 op_entry_set_param();
314 }
315 else
316 rb->strlcpy(op_entry.param, parameter, OPEN_PLUGIN_BUFSZ);
317 }
318
319 rb->write(fd_tmp, &op_entry, op_entry_sz); /* add new entry first */
320 }
321 else
322 {
323 if (op_entry.lang_id != LANG_SHORTCUTS)
324 rb->splashf(HZ / 2, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), pos);
325 return 0;
326 }
327 }
328
329 if (op_entry_transfer(fd_dat, fd_tmp, op_et_exclude_hash, &hash) > 0)
330 {
331 rb->close(fd_tmp);
332 rb->close(fd_dat);
333 rb->remove(OPEN_PLUGIN_DAT);
334 rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
335 }
336 else
337 {
338 rb->close(fd_tmp);
339 rb->remove(OPEN_PLUGIN_DAT ".tmp");
340 hash = 0;
341 }
342
343 return hash;
344}
345
346void op_entry_browse_add(int selection)
347{
348 char* key;
349 op_entry_read(fd_dat, selection, op_entry_sz);
350 if (op_entry_set_path() > 0)
351 {
352 if (op_entry.lang_id >= 0)
353 key = rb->str(op_entry.lang_id);
354 else
355 key = op_entry.path;
356
357 op_entry_add_path(key, op_entry.path, NULL);
358 }
359}
360
361static void op_entry_remove(int selection)
362{
363
364 int entries = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz;
365 int32_t hash = 0;
366 int lang_id = -1;
367
368 if (entries > 0 && _yesno_pop(ID2P(LANG_REMOVE)))
369 {
370 op_entry_read(fd_dat, selection, op_entry_sz);
371 if (rb->lseek(fd_dat, selection * op_entry_sz, SEEK_SET) >= 0)
372 {
373 if (op_entry.lang_id >= 0)
374 {
375 lang_id = op_entry.lang_id;
376 hash = op_entry.hash;
377 }
378 rb->memset(&op_entry, 0, op_entry_sz);
379 op_entry.lang_id = lang_id;
380 op_entry.hash = hash;
381 rb->write(fd_dat, &op_entry, op_entry_sz);
382 }
383 }
384}
385
386static void op_entry_remove_empty(void)
387{
388 bool resave = false;
389 if (fd_dat && rb->lseek(fd_dat, 0, SEEK_SET) == 0)
390 {
391 while (resave == false &&
392 rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz)
393 {
394 if (op_entry.hash == 0)
395 resave = true;
396 }
397 }
398
399 if (resave)
400 {
401 int fd_tmp = rb->open(OPEN_PLUGIN_DAT ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666);
402 if (fd_tmp < 0)
403 return;
404
405 if ((op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_builtin, NULL)
406 + op_entry_transfer(fd_dat, fd_tmp, &op_et_exclude_user, NULL)) > 0)
407 {
408 rb->close(fd_tmp);
409 rb->close(fd_dat);
410 rb->remove(OPEN_PLUGIN_DAT);
411 rb->rename(OPEN_PLUGIN_DAT ".tmp", OPEN_PLUGIN_DAT);
412 }
413 else
414 {
415 rb->close(fd_tmp);
416 rb->remove(OPEN_PLUGIN_DAT ".tmp");
417 }
418 }
419
420}
421
422static int op_entry_run(void)
423{
424 int ret = PLUGIN_ERROR;
425 char* path;
426 char* param;
427 if (op_entry.hash != 0 && op_entry.path[0] != '\0')
428 {
429 rb->splash(1, ID2P(LANG_OPEN_PLUGIN));
430 path = op_entry.path;
431 param = op_entry.param;
432 if (param[0] == '\0')
433 param = NULL;
434
435 ret = rb->plugin_open(path, param);
436 }
437 return ret;
438}
439
440static const char* list_get_name_cb(int selected_item, void* data,
441 char* buf, size_t buf_len)
442{
443 /*TODO memoize names so we don't keep reading the disk when not necessary */
444 if (data == &MENU_ID_MAIN)
445 {
446 if (op_entry_read_name(fd_dat, selected_item))
447 {
448 if (op_entry.lang_id >= 0)
449 rb->snprintf(buf, buf_len, "%s [%s] ",
450 rb->str(op_entry.lang_id), op_entry.name);
451 else if (rb->strlcpy(buf, op_entry.name, buf_len) >= buf_len)
452 rb->strcpy(&buf[buf_len-10], " ...");
453 }
454 else
455 return "?";
456 }
457 else /* op_entry should already be loaded */
458 {
459 switch(selected_item)
460 {
461 case 0:
462 return ID2P(LANG_NAME);
463 case 1:
464 if (op_entry.lang_id >= 0)
465 rb->snprintf(buf, buf_len, "%s [%s] ",
466 rb->str(op_entry.lang_id), op_entry.name);
467 else if (rb->strlcpy(buf, op_entry.name, buf_len) >= buf_len)
468 rb->strcpy(&buf[buf_len-10], " ...");
469 break;
470 case 2:
471 return ID2P(LANG_DISPLAY_FULL_PATH);
472 case 3:
473 if (rb->strlcpy(buf, op_entry.path, buf_len) >= buf_len)
474 rb->strcpy(&buf[buf_len-10], " ...");
475 break;
476 case 4:
477 return ID2P(LANG_PARAMETER);
478 case 5:
479 if (op_entry.param[0] == '\0')
480 return "[NULL]";
481 else if (rb->strlcpy(buf, op_entry.param, buf_len) >= buf_len)
482 rb->strcpy(&buf[buf_len-10], " ...");
483 break;
484 case 6:
485 return "";
486 case 7:
487 return ID2P(LANG_BACK);
488 default:
489 return "?";
490 }
491 }
492
493 return buf;
494}
495
496static int list_voice_cb(int list_index, void* data)
497{
498 if (data == &MENU_ID_MAIN)
499 {
500 if (op_entry_read_name(fd_dat, list_index))
501 {
502 if (op_entry.lang_id >= 0)
503 {
504 rb->talk_id(op_entry.lang_id, false);
505 rb->talk_id(VOICE_PAUSE, true);
506 rb->talk_force_enqueue_next();
507 }
508 return rb->talk_spell(op_entry.name, false);
509 }
510 }
511 else
512 {
513 switch(list_index)
514 {
515 case 0:
516 rb->talk_id(LANG_NAME, false);
517 rb->talk_id(VOICE_PAUSE, true);
518
519 if (op_entry.lang_id >= 0)
520 {
521 rb->talk_id(op_entry.lang_id, true);
522 rb->talk_id(VOICE_PAUSE, true);
523 rb->talk_force_enqueue_next();
524 }
525 return rb->talk_spell(op_entry.name, false);
526 case 2:
527 return rb->talk_id(LANG_DISPLAY_FULL_PATH, false);
528 case 4:
529 return rb->talk_id(LANG_PARAMETER, false);
530 case 6:
531 return rb->talk_id(LANG_BACK, false);
532 default:
533 return 0;
534 }
535 }
536
537 return 0;
538}
539
540static void synclist_set(char* menu_id, int selection, int items, int sel_size)
541{
542 if (selection < 0)
543 selection = 0;
544
545 rb->gui_synclist_init(&lists,list_get_name_cb,
546 menu_id, false, sel_size, NULL);
547
548 rb->gui_synclist_set_icon_callback(&lists,NULL);
549 rb->gui_synclist_set_voice_callback(&lists, list_voice_cb);
550 rb->gui_synclist_set_nb_items(&lists,items);
551 rb->gui_synclist_limit_scroll(&lists,true);
552 rb->gui_synclist_select_item(&lists, selection);
553 list_voice_cb(selection, menu_id);
554}
555
556static int context_menu_cb(int action,
557 const struct menu_item_ex *this_item,
558 struct gui_synclist *this_list)
559{
560 (void)this_item;
561
562 int selection = rb->gui_synclist_get_sel_pos(this_list);
563
564 if(action == ACTION_ENTER_MENUITEM)
565 {
566 if (selection == 0 &&
567 op_entry.lang_id >= 0 && op_entry.lang_id != LANG_OPEN_PLUGIN)
568 {
569 rb->gui_synclist_set_title(this_list,
570 rb->str(op_entry.lang_id), 0);
571 }
572 }
573 else if ((action == ACTION_STD_OK))
574 {
575 /*Run, Edit, Remove, Export, Blank, Import, Add, Back*/
576 switch(selection)
577 {
578 case 0:case 1:case 2:case 3:case 5:
579 return ACTION_STD_OK;
580 case 4: /*blank*/
581 break;
582 default:
583 return ACTION_STD_CANCEL;
584 }
585 rb->gui_synclist_draw(this_list); /* redraw */
586 return 0;
587 }
588
589 return action;
590}
591
592static void edit_menu(int selection)
593{
594 int selected_item;
595 bool exit = false;
596 int action = 0;
597
598 if (!op_entry_read(fd_dat, selection, op_entry_sz))
599 return;
600
601 uint32_t crc = rb->crc_32(&op_entry, op_entry_sz, 0xffffffff);
602
603 synclist_set(MENU_ID_EDIT, 2, 8, 2);
604 rb->gui_synclist_draw(&lists);
605
606 while (!exit)
607 {
608 action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
609
610 if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD))
611 continue;
612 selected_item = rb->gui_synclist_get_sel_pos(&lists);
613 switch (action)
614 {
615 case ACTION_STD_OK:
616 if (selected_item == 0)
617 op_entry_set_name();
618 else if (selected_item == 2)
619 op_entry_set_path();
620 else if (selected_item == 4)
621 op_entry_set_param();
622 else
623 exit = true;
624
625 rb->gui_synclist_draw(&lists);
626 break;
627 case ACTION_STD_CANCEL:
628 exit = true;
629 break;
630 }
631 }
632
633 if (crc != rb->crc_32(&op_entry, op_entry_sz, 0xffffffff) &&
634 _yesno_pop(ID2P(LANG_SAVE)) == true)
635 {
636 char *param = op_entry.param;
637 if (param[0] == '\0')
638 param = NULL;
639
640 op_entry_add_path(NULL, op_entry.path, param);
641 fd_dat = rb->open(OPEN_PLUGIN_DAT, O_RDWR, 0666);
642 }
643}
644
645static int context_menu(int selection)
646{
647 int selected_item;
648 if (op_entry_read(fd_dat, selection, op_entry_sz))
649 {
650 MENUITEM_STRINGLIST(menu, op_entry.name, context_menu_cb,
651 ID2P(LANG_RUN), ID2P(LANG_EDIT), ID2P(LANG_REMOVE), ID2P(LANG_EXPORT),
652 ID2P(VOICE_BLANK), ID2P(LANG_ADD), ID2P(LANG_BACK));
653
654 selected_item = rb->do_menu(&menu, 0, NULL, false);
655 switch (selected_item)
656 {
657 case 0: /*run*/
658 return PLUGIN_GOTO_PLUGIN;
659 case 1: /*edit*/
660 edit_menu(selection);
661 break;
662 case 2: /*remove*/
663 op_entry_remove(selection);
664 break;
665 case 3: /*export*/
666 op_entry_export(selection);
667 break;
668 case 4: /*blank*/
669 break;
670 case 5: /*add*/
671 op_entry_browse_add(-1);
672 rb->plugin_open(rb->plugin_get_current_filename(), "\0");
673 return OP_PLUGIN_RESTART;
674 default:
675 break;
676
677 }
678 return PLUGIN_OK;
679 }
680 return PLUGIN_ERROR;
681}
682
683enum plugin_status plugin_start(const void* parameter)
684{
685 int ret = PLUGIN_OK;
686 uint32_t hash = 0;
687 int item = -1;
688 int selection = -1;
689 int action;
690 int items;
691 int len;
692 int fd_opx;
693 off_t filesize;
694 char *path;
695 bool exit = false;
696
697 const int creat_flags = O_RDWR | O_CREAT;
698 fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
699 if (!fd_dat)
700 exit = true;
701
702 items = rb->lseek(fd_dat, 0, SEEK_END) / op_entry_sz;
703 if (items == 0 && !parameter)
704 {
705 rb->plugin_open(rb->plugin_get_current_filename(), NULL);
706 rb->close(fd_dat);
707 return PLUGIN_GOTO_PLUGIN;
708 }
709
710 if (parameter)
711 {
712 path = (char*)parameter;
713 len = rb->strlen(path);
714 if(len > 4 && rb->strcasecmp(&((path)[len-4]), "." OP_EXT) == 0)
715 {
716 fd_opx = rb->open(path, O_RDONLY, 0666);
717 if (fd_opx)
718 {
719 filesize = rb->filesize(fd_opx);
720 if (filesize == op_entry_sz)
721 {
722
723 if (op_entry_read(fd_opx, 0, op_entry_sz))
724 {
725 exit = true;
726 ret = op_entry_run();
727 }
728 else
729 rb->splashf(HZ, rb->str(LANG_READ_FAILED), path);
730 }
731 else if (filesize != 0)
732 rb->splashf(2 * HZ, rb->str(LANG_OPEN_PLUGIN_NOT_A_PLUGIN), path);
733
734 rb->close(fd_opx);
735 }
736 }
737 else
738 {
739 open_plugin_get_hash(parameter, &hash);
740 rb->lseek(fd_dat, 0, SEEK_SET);
741 while (rb->read(fd_dat, &op_entry, op_entry_sz) == op_entry_sz)
742 {
743 item++;
744 if (op_entry.hash == hash)
745 {
746 selection = item;
747 break;
748 }
749 }
750
751 if (selection >= 0)
752 {
753 if (op_entry_read(fd_dat, selection, op_entry_sz))
754 {
755 if (op_entry_run() == PLUGIN_GOTO_PLUGIN)
756 return PLUGIN_GOTO_PLUGIN;
757 }
758 }
759 else
760 {
761 op_entry_read(fd_dat, selection, op_entry_sz);
762 op_entry_add_path(parameter, parameter, "\0");
763 selection = 0;
764 items++;
765 fd_dat = rb->open(OPEN_PLUGIN_DAT, creat_flags, 0666);
766 if (!fd_dat)
767 exit = true;
768 }
769 }/* OP_EXT */
770
771 }
772
773 if (!exit)
774 {
775 synclist_set(MENU_ID_MAIN, selection, items, 1);
776 rb->gui_synclist_draw(&lists);
777
778 while (!exit)
779 {
780 action = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
781
782 if (rb->gui_synclist_do_button(&lists,&action,LIST_WRAP_UNLESS_HELD))
783 continue;
784 selection = rb->gui_synclist_get_sel_pos(&lists);
785 switch (action)
786 {
787 case ACTION_STD_CONTEXT:
788 ret = context_menu(selection);
789 if (ret == OP_PLUGIN_RESTART)
790 {
791 ret = PLUGIN_GOTO_PLUGIN;
792 exit = true;
793 break;
794 }
795 else if (ret != PLUGIN_GOTO_PLUGIN)
796 {
797 synclist_set(MENU_ID_MAIN, selection, items, 1);
798 rb->gui_synclist_draw(&lists);
799 break;
800 }
801 case ACTION_STD_OK:
802 if (op_entry_read(fd_dat, selection, op_entry_sz))
803 {
804 ret = op_entry_run();
805 exit = true;
806 }
807 break;
808 case ACTION_STD_CANCEL:
809 {
810 selection = -2;
811 exit = true;
812 break;
813 }
814 }
815 }
816 op_entry_remove_empty();
817 }
818 rb->close(fd_dat);
819 if (ret != PLUGIN_OK)
820 return ret;
821 else
822 return PLUGIN_OK;
823}
diff --git a/apps/plugins/plugins.make b/apps/plugins/plugins.make
index d395c00e82..71eaeb52c0 100644
--- a/apps/plugins/plugins.make
+++ b/apps/plugins/plugins.make
@@ -86,6 +86,11 @@ $(OVERLAYREF_LDS): $(PLUGIN_LDS)
86$(BUILDDIR)/credits.raw credits.raw: $(DOCSDIR)/CREDITS 86$(BUILDDIR)/credits.raw credits.raw: $(DOCSDIR)/CREDITS
87 $(call PRINTS,Create credits.raw)perl $(APPSDIR)/plugins/credits.pl < $< > $(BUILDDIR)/$(@F) 87 $(call PRINTS,Create credits.raw)perl $(APPSDIR)/plugins/credits.pl < $< > $(BUILDDIR)/$(@F)
88 88
89$(BUILDDIR)/apps/plugins/open_plugins.opx:
90 $(call PRINTS,MK open_plugins.opx) touch $< $(BUILDDIR)/apps/plugins/open_plugins.opx
91
92$(BUILDDIR)/apps/plugins/open_plugins.rock: $(BUILDDIR)/apps/plugins/open_plugins.opx
93
89# special dependencies 94# special dependencies
90$(BUILDDIR)/apps/plugins/wav2wv.rock: $(RBCODEC_BLD)/codecs/libwavpack.a $(PLUGIN_LIBS) 95$(BUILDDIR)/apps/plugins/wav2wv.rock: $(RBCODEC_BLD)/codecs/libwavpack.a $(PLUGIN_LIBS)
91 96
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index 84f096a409..2bf052bb20 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -77,6 +77,8 @@ mpv,viewers/mpegplayer,4
77m2v,viewers/mpegplayer,4 77m2v,viewers/mpegplayer,4
78iriver,viewers/iriver_flash,3 78iriver,viewers/iriver_flash,3
79tap,viewers/zxbox,12 79tap,viewers/zxbox,12
80opx,viewers/open_plugins,-
81rock,viewers/open_plugins,-
80sna,viewers/zxbox,12 82sna,viewers/zxbox,12
81tzx,viewers/zxbox,12 83tzx,viewers/zxbox,12
82z80,viewers/zxbox,12 84z80,viewers/zxbox,12
diff --git a/tools/buildzip.pl b/tools/buildzip.pl
index ce225139ef..4b15771333 100755
--- a/tools/buildzip.pl
+++ b/tools/buildzip.pl
@@ -518,6 +518,12 @@ sub buildzip {
518 copy("$ROOT/apps/tagnavi.config", "$temp_dir/"); 518 copy("$ROOT/apps/tagnavi.config", "$temp_dir/");
519 copy("$ROOT/apps/plugins/disktidy.config", "$temp_dir/rocks/apps/"); 519 copy("$ROOT/apps/plugins/disktidy.config", "$temp_dir/rocks/apps/");
520 520
521 if(-e "$temp_dir/rocks/viewers/open_plugins.rock") {
522 my $cwd = getcwd();
523 copy("$cwd/apps/plugins/open_plugins.opx", "$temp_dir/rocks/apps/open_plugins.opx") or
524 print STDERR "Copy failed: $cwd/apps/plugins/open_plugins.opx $!\n";
525 }
526
521 if($bitmap) { 527 if($bitmap) {
522 copy("$ROOT/apps/plugins/sokoban.levels", "$temp_dir/rocks/games/sokoban.levels"); # sokoban levels 528 copy("$ROOT/apps/plugins/sokoban.levels", "$temp_dir/rocks/games/sokoban.levels"); # sokoban levels
523 copy("$ROOT/apps/plugins/snake2.levels", "$temp_dir/rocks/games/snake2.levels"); # snake2 levels 529 copy("$ROOT/apps/plugins/snake2.levels", "$temp_dir/rocks/games/snake2.levels"); # snake2 levels