summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/plugin.c3
-rw-r--r--apps/plugin.h5
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES4
-rw-r--r--apps/plugins/multiboot_select.c346
-rw-r--r--apps/plugins/viewers.config2
6 files changed, 359 insertions, 2 deletions
diff --git a/apps/plugin.c b/apps/plugin.c
index e0766e47bf..f2964a9c45 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -810,6 +810,9 @@ static const struct plugin_api rockbox_api = {
810 queue_remove_from_head, 810 queue_remove_from_head,
811 core_set_keyremap, 811 core_set_keyremap,
812 plugin_reserve_buffer, 812 plugin_reserve_buffer,
813 path_strip_volume,
814 sys_poweroff,
815 sys_reboot,
813}; 816};
814 817
815static int plugin_buffer_handle; 818static int plugin_buffer_handle;
diff --git a/apps/plugin.h b/apps/plugin.h
index 2e0ace2e48..1c3c59c6cc 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -156,7 +156,7 @@ int plugin_open(const char *plugin, const char *parameter);
156#define PLUGIN_MAGIC 0x526F634B /* RocK */ 156#define PLUGIN_MAGIC 0x526F634B /* RocK */
157 157
158/* increase this every time the api struct changes */ 158/* increase this every time the api struct changes */
159#define PLUGIN_API_VERSION 249 159#define PLUGIN_API_VERSION 250
160 160
161/* update this to latest version if a change to the api struct breaks 161/* update this to latest version if a change to the api struct breaks
162 backwards compatibility (and please take the opportunity to sort in any 162 backwards compatibility (and please take the opportunity to sort in any
@@ -937,6 +937,9 @@ struct plugin_api {
937 void (*queue_remove_from_head)(struct event_queue *q, long id); 937 void (*queue_remove_from_head)(struct event_queue *q, long id);
938 int (*core_set_keyremap)(struct button_mapping* core_keymap, int count); 938 int (*core_set_keyremap)(struct button_mapping* core_keymap, int count);
939 size_t (*plugin_reserve_buffer)(size_t buffer_size); 939 size_t (*plugin_reserve_buffer)(size_t buffer_size);
940 int (*path_strip_volume)(const char *name, const char **nameptr, bool greedy);
941 void (*sys_poweroff)(void);
942 void (*sys_reboot)(void);
940}; 943};
941 944
942/* plugin header */ 945/* plugin header */
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 3444dde9fa..332dd1bde4 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -69,6 +69,7 @@ minesweeper,games
69mosaique,demos 69mosaique,demos
70mp3_encoder,apps 70mp3_encoder,apps
71mpegplayer,viewers 71mpegplayer,viewers
72multiboot_select,apps
72nim,games 73nim,games
73open_plugins,viewers 74open_plugins,viewers
74oscilloscope,demos 75oscilloscope,demos
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index 3e0a65da57..fce974f338 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -158,6 +158,10 @@ wormlet.c
158announce_status.c 158announce_status.c
159#endif 159#endif
160 160
161#ifdef HAVE_MULTIBOOT
162multiboot_select.c
163#endif
164
161 165
162/* Plugins needing the grayscale lib on low-depth LCDs */ 166/* Plugins needing the grayscale lib on low-depth LCDs */
163fire.c 167fire.c
diff --git a/apps/plugins/multiboot_select.c b/apps/plugins/multiboot_select.c
new file mode 100644
index 0000000000..63babbb2c1
--- /dev/null
+++ b/apps/plugins/multiboot_select.c
@@ -0,0 +1,346 @@
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 int r = rb->snprintf(tmpbuf, sizeof(tmpbuf), "/<%d>/%s/%s/%s",
159 vol, ent->d_name, bootdir, BOOTFILE);
160 if(r < 0 || (size_t)r >= sizeof(tmpbuf))
161 continue;
162
163 if(check_firmware(tmpbuf)) {
164 rb->snprintf(roots[nroots], MAX_PATH, "/<%d>/%s",
165 vol, ent->d_name);
166 nroots += 1;
167
168 /* quit if we hit the maximum */
169 if(nroots == MAX_ROOTS) {
170 vol = NUM_VOLUMES;
171 break;
172 }
173 }
174 }
175
176 rb->closedir(dir);
177 }
178
179 rb->qsort(roots, nroots, MAX_PATH, root_compare);
180 return nroots;
181}
182
183static const char* picker_get_name_cb(int selected, void* data,
184 char* buffer, size_t buffer_len)
185{
186 (void)data;
187 (void)buffer;
188 (void)buffer_len;
189 return roots[selected];
190}
191
192static int picker_action_cb(int action, struct gui_synclist* lists)
193{
194 (void)lists;
195 if(action == ACTION_STD_OK)
196 action = ACTION_STD_CANCEL;
197 return action;
198}
199
200static bool show_picker_menu(int* selection)
201{
202 struct simplelist_info info;
203 rb->simplelist_info_init(&info, "Select new root", nroots, NULL);
204 info.selection = *selection;
205 info.get_name = picker_get_name_cb;
206 info.action_callback = picker_action_cb;
207 if(rb->simplelist_show_list(&info))
208 return true;
209
210 if(0 <= info.selection && info.selection < nroots)
211 *selection = info.selection;
212
213 return false;
214}
215
216enum {
217 MB_SELECT_ROOT,
218 MB_CLEAR_REDIRECT,
219 MB_CURRENT_ROOT,
220 MB_SAVE_AND_EXIT,
221 MB_SAVE_AND_REBOOT,
222 MB_EXIT,
223 MB_NUM_ITEMS,
224};
225
226static const char* menu_get_name_cb(int selected, void* data,
227 char* buffer, size_t buffer_len)
228{
229 (void)data;
230
231 switch(selected) {
232 case MB_SELECT_ROOT:
233 return "Select root";
234
235 case MB_CLEAR_REDIRECT:
236 return "Clear redirect";
237
238 case MB_CURRENT_ROOT:
239 if(cur_root[0]) {
240 rb->snprintf(buffer, buffer_len, "Using root: %s", cur_root);
241 return buffer;
242 }
243
244 return "Using default root";
245
246 case MB_SAVE_AND_EXIT:
247 return "Save and Exit";
248
249 case MB_SAVE_AND_REBOOT:
250 return "Save and Reboot";
251
252 case MB_EXIT:
253 default:
254 return "Exit";
255 }
256}
257
258static int menu_action_cb(int action, struct gui_synclist* lists)
259{
260 int selected = rb->gui_synclist_get_sel_pos(lists);
261
262 if(action != ACTION_STD_OK)
263 return action;
264
265 switch(selected) {
266 case MB_SELECT_ROOT: {
267 if(find_roots() <= 0) {
268 rb->splashf(3*HZ, "No roots found");
269 break;
270 }
271
272 int root_sel = nroots-1;
273 for(; root_sel > 0; --root_sel)
274 if(!rb->strcmp(roots[root_sel], cur_root))
275 break;
276
277 if(show_picker_menu(&root_sel))
278 return SYS_USB_CONNECTED;
279
280 rb->strcpy(cur_root, roots[root_sel]);
281 action = ACTION_REDRAW;
282 } break;
283
284 case MB_CLEAR_REDIRECT:
285 if(cur_root[0]) {
286 memset(cur_root, 0, sizeof(cur_root));
287 rb->splashf(HZ, "Cleared redirect");
288 }
289
290 action = ACTION_REDRAW;
291 break;
292
293 case MB_SAVE_AND_REBOOT:
294 case MB_SAVE_AND_EXIT: {
295 int ret;
296 if(cur_root[0])
297 ret = write_redirect(cur_root);
298 else
299 ret = clear_redirect(-1);
300
301 if(ret < 0)
302 rb->splashf(3*HZ, "Couldn't save settings");
303 else
304 rb->splashf(HZ, "Settings saved");
305
306 action = ACTION_STD_CANCEL;
307 plugin_status = (ret < 0) ? PLUGIN_ERROR : PLUGIN_OK;
308
309 if(ret >= 0 && selected == MB_SAVE_AND_REBOOT)
310 rb->sys_reboot();
311 } break;
312
313 case MB_EXIT:
314 return ACTION_STD_CANCEL;
315
316 default:
317 action = ACTION_REDRAW;
318 break;
319 }
320
321 return action;
322}
323
324static bool show_menu(void)
325{
326 struct simplelist_info info;
327 rb->simplelist_info_init(&info, "Multiboot Settings", MB_NUM_ITEMS, NULL);
328 info.get_name = menu_get_name_cb;
329 info.action_callback = menu_action_cb;
330 return rb->simplelist_show_list(&info);
331}
332
333enum plugin_status plugin_start(const void* param)
334{
335 (void)param;
336
337 /* load the current root */
338 const char* myroot = read_redirect();
339 rb->strcpy(cur_root, myroot);
340
341 /* display the menu */
342 if(show_menu())
343 return PLUGIN_USB_CONNECTED;
344 else
345 return plugin_status;
346}
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config
index 8aa0ac370a..8d266966d0 100644
--- a/apps/plugins/viewers.config
+++ b/apps/plugins/viewers.config
@@ -105,4 +105,4 @@ shopper,viewers/shopper,1
105lnk,viewers/windows_lnk,- 105lnk,viewers/windows_lnk,-
106#ifdef HAVE_TAGCACHE 106#ifdef HAVE_TAGCACHE
107*,demos/pictureflow,- 107*,demos/pictureflow,-
108#endif \ No newline at end of file 108#endif