diff options
-rw-r--r-- | apps/plugin.c | 3 | ||||
-rw-r--r-- | apps/plugin.h | 5 | ||||
-rw-r--r-- | apps/plugins/CATEGORIES | 1 | ||||
-rw-r--r-- | apps/plugins/SOURCES | 4 | ||||
-rw-r--r-- | apps/plugins/multiboot_select.c | 346 | ||||
-rw-r--r-- | apps/plugins/viewers.config | 2 |
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 | ||
815 | static int plugin_buffer_handle; | 818 | static 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 | |||
69 | mosaique,demos | 69 | mosaique,demos |
70 | mp3_encoder,apps | 70 | mp3_encoder,apps |
71 | mpegplayer,viewers | 71 | mpegplayer,viewers |
72 | multiboot_select,apps | ||
72 | nim,games | 73 | nim,games |
73 | open_plugins,viewers | 74 | open_plugins,viewers |
74 | oscilloscope,demos | 75 | oscilloscope,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 | |||
158 | announce_status.c | 158 | announce_status.c |
159 | #endif | 159 | #endif |
160 | 160 | ||
161 | #ifdef HAVE_MULTIBOOT | ||
162 | multiboot_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 */ |
163 | fire.c | 167 | fire.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 | |||
27 | static enum plugin_status plugin_status = PLUGIN_OK; | ||
28 | static char tmpbuf[MAX_PATH+1]; | ||
29 | static char tmpbuf2[MAX_PATH+1]; | ||
30 | static char cur_root[MAX_PATH]; | ||
31 | static char roots[MAX_ROOTS][MAX_PATH]; | ||
32 | static int nroots; | ||
33 | |||
34 | /* Read a redirect file and return the path */ | ||
35 | static 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() */ | ||
58 | static 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' */ | ||
75 | static 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 */ | ||
91 | static 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 */ | ||
126 | static bool check_firmware(const char* path) | ||
127 | { | ||
128 | return rb->file_exists(path); | ||
129 | } | ||
130 | |||
131 | static 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. */ | ||
142 | static 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 | |||
183 | static 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 | |||
192 | static 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 | |||
200 | static 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 | |||
216 | enum { | ||
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 | |||
226 | static 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 | |||
258 | static 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 | |||
324 | static 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 | |||
333 | enum 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 | |||
105 | lnk,viewers/windows_lnk,- | 105 | lnk,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 |