diff options
Diffstat (limited to 'apps/shortcuts.c')
-rw-r--r-- | apps/shortcuts.c | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/apps/shortcuts.c b/apps/shortcuts.c new file mode 100644 index 0000000000..3992068807 --- /dev/null +++ b/apps/shortcuts.c | |||
@@ -0,0 +1,421 @@ | |||
1 | /*************************************************************************** | ||
2 | * | ||
3 | * __________ __ ___. | ||
4 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
5 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
6 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
7 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
8 | * \/ \/ \/ \/ \/ | ||
9 | * $Id$ | ||
10 | * | ||
11 | * Copyright (C) 2011 Jonathan Gordon | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or | ||
14 | * modify it under the terms of the GNU General Public License | ||
15 | * as published by the Free Software Foundation; either version 2 | ||
16 | * of the License, or (at your option) any later version. | ||
17 | * | ||
18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
19 | * KIND, either express or implied. | ||
20 | * | ||
21 | ****************************************************************************/ | ||
22 | |||
23 | #include <stdbool.h> | ||
24 | #include <stdlib.h> | ||
25 | #include "config.h" | ||
26 | #include "system.h" | ||
27 | #include "action.h" | ||
28 | #include "ata_idle_notify.h" | ||
29 | #include "debug_menu.h" | ||
30 | #include "core_alloc.h" | ||
31 | #include "list.h" | ||
32 | #include "settings.h" | ||
33 | #include "settings_list.h" | ||
34 | #include "lang.h" | ||
35 | #include "menu.h" | ||
36 | #include "misc.h" | ||
37 | #include "tree.h" | ||
38 | #include "splash.h" | ||
39 | #include "filefuncs.h" | ||
40 | #include "filetypes.h" | ||
41 | #include "shortcuts.h" | ||
42 | #include "onplay.h" | ||
43 | |||
44 | |||
45 | |||
46 | #define MAX_SHORTCUT_NAME 32 | ||
47 | #define SHORTCUTS_FILENAME ROCKBOX_DIR "/shortcuts.txt" | ||
48 | char *type_strings[SHORTCUT_TYPE_COUNT] = { | ||
49 | [SHORTCUT_SETTING] = "setting", | ||
50 | [SHORTCUT_FILE] = "file", | ||
51 | [SHORTCUT_DEBUGITEM] = "debug", | ||
52 | [SHORTCUT_BROWSER] = "browse", | ||
53 | [SHORTCUT_PLAYLISTMENU] = "playlist menu", | ||
54 | [SHORTCUT_SEPARATOR] = "separator", | ||
55 | }; | ||
56 | |||
57 | |||
58 | struct shortcut { | ||
59 | enum shortcut_type type; | ||
60 | char name[MAX_SHORTCUT_NAME]; | ||
61 | int icon; | ||
62 | union { | ||
63 | char path[MAX_PATH]; | ||
64 | const struct settings_list *setting; | ||
65 | } u; | ||
66 | }; | ||
67 | #define SHORTCUTS_PER_HANDLE 32 | ||
68 | struct shortcut_handle { | ||
69 | struct shortcut shortcuts[SHORTCUTS_PER_HANDLE]; | ||
70 | int next_handle; | ||
71 | }; | ||
72 | static int first_handle = 0; | ||
73 | static int shortcut_count = 0; | ||
74 | |||
75 | static void reset_shortcuts(void) | ||
76 | { | ||
77 | int current_handle = first_handle; | ||
78 | struct shortcut_handle *h = NULL; | ||
79 | while (current_handle > 0) | ||
80 | { | ||
81 | int next; | ||
82 | h = core_get_data(current_handle); | ||
83 | next = h->next_handle; | ||
84 | core_free(current_handle); | ||
85 | current_handle = next; | ||
86 | } | ||
87 | first_handle = 0; | ||
88 | shortcut_count = 0; | ||
89 | } | ||
90 | |||
91 | static struct shortcut* get_shortcut(int index) | ||
92 | { | ||
93 | int handle_count, handle_index; | ||
94 | int current_handle = first_handle; | ||
95 | struct shortcut_handle *h = NULL; | ||
96 | |||
97 | if (first_handle == 0) | ||
98 | { | ||
99 | first_handle = core_alloc("shortcuts_head", sizeof(struct shortcut_handle)); | ||
100 | if (first_handle <= 0) | ||
101 | return NULL; | ||
102 | h = core_get_data(first_handle); | ||
103 | h->next_handle = 0; | ||
104 | current_handle = first_handle; | ||
105 | } | ||
106 | |||
107 | handle_count = index/SHORTCUTS_PER_HANDLE + 1; | ||
108 | handle_index = index%SHORTCUTS_PER_HANDLE; | ||
109 | do { | ||
110 | h = core_get_data(current_handle); | ||
111 | current_handle = h->next_handle; | ||
112 | handle_count--; | ||
113 | } while (handle_count > 0 && current_handle > 0); | ||
114 | if (handle_count > 0 && handle_index == 0) | ||
115 | { | ||
116 | char buf[32]; | ||
117 | snprintf(buf, sizeof buf, "shortcuts_%d", index/SHORTCUTS_PER_HANDLE); | ||
118 | h->next_handle = core_alloc(buf, sizeof(struct shortcut_handle)); | ||
119 | if (h->next_handle <= 0) | ||
120 | return NULL; | ||
121 | h = core_get_data(h->next_handle); | ||
122 | h->next_handle = 0; | ||
123 | } | ||
124 | return &h->shortcuts[handle_index]; | ||
125 | } | ||
126 | |||
127 | bool verify_shortcut(struct shortcut* sc) | ||
128 | { | ||
129 | switch (sc->type) | ||
130 | { | ||
131 | case SHORTCUT_UNDEFINED: | ||
132 | return false; | ||
133 | case SHORTCUT_BROWSER: | ||
134 | case SHORTCUT_FILE: | ||
135 | case SHORTCUT_PLAYLISTMENU: | ||
136 | if (sc->u.path[0] == '\0') | ||
137 | return false; | ||
138 | break; | ||
139 | case SHORTCUT_SETTING: | ||
140 | return sc->u.setting != NULL; | ||
141 | case SHORTCUT_DEBUGITEM: | ||
142 | case SHORTCUT_SEPARATOR: | ||
143 | default: | ||
144 | break; | ||
145 | } | ||
146 | return true; | ||
147 | } | ||
148 | |||
149 | static void init_shortcut(struct shortcut* sc) | ||
150 | { | ||
151 | sc->type = SHORTCUT_UNDEFINED; | ||
152 | sc->name[0] = '\0'; | ||
153 | sc->u.path[0] = '\0'; | ||
154 | sc->icon = Icon_NOICON; | ||
155 | } | ||
156 | static int first_idx_to_writeback = -1; | ||
157 | void shortcuts_ata_idle_callback(void* data) | ||
158 | { | ||
159 | (void)data; | ||
160 | int fd; | ||
161 | char buf[MAX_PATH]; | ||
162 | int current_idx = first_idx_to_writeback; | ||
163 | if (first_idx_to_writeback < 0) | ||
164 | return; | ||
165 | fd = open(SHORTCUTS_FILENAME, O_APPEND|O_RDWR|O_CREAT, 0644); | ||
166 | if (fd < 0) | ||
167 | return; | ||
168 | while (current_idx < shortcut_count) | ||
169 | { | ||
170 | struct shortcut* sc = get_shortcut(current_idx++); | ||
171 | char *type; | ||
172 | int len; | ||
173 | if (!sc) | ||
174 | break; | ||
175 | type = type_strings[sc->type]; | ||
176 | len = snprintf(buf, MAX_PATH, "[shortcut]\ntype: %s\ndata: ", type); | ||
177 | write(fd, buf, len); | ||
178 | if (sc->type == SHORTCUT_SETTING) | ||
179 | write(fd, sc->u.setting->cfg_name, strlen(sc->u.setting->cfg_name)); | ||
180 | else | ||
181 | write(fd, sc->u.path, strlen(sc->u.path)); | ||
182 | write(fd, "\n\n", 2); | ||
183 | } | ||
184 | close(fd); | ||
185 | if (first_idx_to_writeback == 0) | ||
186 | { | ||
187 | /* reload all shortcuts because we appended to the shortcuts file which | ||
188 | * has not been read yet. | ||
189 | */ | ||
190 | reset_shortcuts(); | ||
191 | shortcuts_init(); | ||
192 | } | ||
193 | first_idx_to_writeback = -1; | ||
194 | } | ||
195 | void shortcuts_add(enum shortcut_type type, char* value) | ||
196 | { | ||
197 | struct shortcut* sc = get_shortcut(shortcut_count++); | ||
198 | if (!sc) | ||
199 | return; | ||
200 | init_shortcut(sc); | ||
201 | sc->type = type; | ||
202 | if (type == SHORTCUT_SETTING) | ||
203 | sc->u.setting = (void*)value; | ||
204 | else | ||
205 | strlcpy(sc->u.path, value, MAX_PATH); | ||
206 | if (first_idx_to_writeback < 0) | ||
207 | first_idx_to_writeback = shortcut_count - 1; | ||
208 | register_storage_idle_func(shortcuts_ata_idle_callback); | ||
209 | } | ||
210 | |||
211 | |||
212 | int readline_cb(int n, char *buf, void *parameters) | ||
213 | { | ||
214 | (void)n; | ||
215 | (void)parameters; | ||
216 | struct shortcut **param = (struct shortcut**)parameters; | ||
217 | struct shortcut* sc = *param; | ||
218 | char *name, *value; | ||
219 | |||
220 | if (!strcasecmp(skip_whitespace(buf), "[shortcut]")) | ||
221 | { | ||
222 | if (sc && verify_shortcut(sc)) | ||
223 | shortcut_count++; | ||
224 | sc = get_shortcut(shortcut_count); | ||
225 | if (!sc) | ||
226 | return 1; | ||
227 | init_shortcut(sc); | ||
228 | *param = sc; | ||
229 | } | ||
230 | else if (sc && settings_parseline(buf, &name, &value)) | ||
231 | { | ||
232 | if (!strcmp(name, "type")) | ||
233 | { | ||
234 | int t = 0; | ||
235 | for (t=0; t<SHORTCUT_TYPE_COUNT && sc->type == SHORTCUT_UNDEFINED; t++) | ||
236 | if (!strcmp(value, type_strings[t])) | ||
237 | sc->type = t; | ||
238 | } | ||
239 | else if (!strcmp(name, "name")) | ||
240 | { | ||
241 | strlcpy(sc->name, value, MAX_SHORTCUT_NAME); | ||
242 | } | ||
243 | else if (!strcmp(name, "data")) | ||
244 | { | ||
245 | switch (sc->type) | ||
246 | { | ||
247 | case SHORTCUT_UNDEFINED: | ||
248 | case SHORTCUT_TYPE_COUNT: | ||
249 | *param = NULL; | ||
250 | break; | ||
251 | case SHORTCUT_BROWSER: | ||
252 | case SHORTCUT_FILE: | ||
253 | case SHORTCUT_DEBUGITEM: | ||
254 | case SHORTCUT_PLAYLISTMENU: | ||
255 | strlcpy(sc->u.path, value, MAX_PATH); | ||
256 | break; | ||
257 | case SHORTCUT_SETTING: | ||
258 | sc->u.setting = find_setting_by_cfgname(value, NULL); | ||
259 | break; | ||
260 | case SHORTCUT_SEPARATOR: | ||
261 | break; | ||
262 | } | ||
263 | } | ||
264 | else if (!strcmp(name, "icon")) | ||
265 | { | ||
266 | if (!strcmp(value, "filetype") && sc->type != SHORTCUT_SETTING && sc->u.path[0]) | ||
267 | { | ||
268 | sc->icon = filetype_get_icon(filetype_get_attr(sc->u.path)); | ||
269 | } | ||
270 | else | ||
271 | { | ||
272 | sc->icon = atoi(value); | ||
273 | } | ||
274 | } | ||
275 | } | ||
276 | return 0; | ||
277 | } | ||
278 | void shortcuts_init(void) | ||
279 | { | ||
280 | int fd; | ||
281 | char buf[512]; | ||
282 | struct shortcut *param = NULL; | ||
283 | struct shortcut_handle *h; | ||
284 | shortcut_count = 0; | ||
285 | fd = open_utf8(SHORTCUTS_FILENAME, O_RDONLY); | ||
286 | if (fd < 0) | ||
287 | return; | ||
288 | first_handle = core_alloc("shortcuts_head", sizeof(struct shortcut_handle)); | ||
289 | if (first_handle <= 0) | ||
290 | return; | ||
291 | h = core_get_data(first_handle); | ||
292 | h->next_handle = 0; | ||
293 | fast_readline(fd, buf, sizeof buf, ¶m, readline_cb); | ||
294 | close(fd); | ||
295 | if (param && verify_shortcut(param)) | ||
296 | shortcut_count++; | ||
297 | } | ||
298 | |||
299 | const char * shortcut_menu_get_name(int selected_item, void * data, | ||
300 | char * buffer, size_t buffer_len) | ||
301 | { | ||
302 | (void)data; | ||
303 | (void)buffer; | ||
304 | (void)buffer_len; | ||
305 | struct shortcut *sc = get_shortcut(selected_item); | ||
306 | if (!sc) | ||
307 | return ""; | ||
308 | if (sc->type == SHORTCUT_SETTING) | ||
309 | return sc->name[0] ? sc->name : P2STR(ID2P(sc->u.setting->lang_id)); | ||
310 | else if (sc->type == SHORTCUT_SEPARATOR) | ||
311 | return sc->name; | ||
312 | return sc->name[0] ? sc->name : sc->u.path; | ||
313 | } | ||
314 | |||
315 | int shortcut_menu_get_action(int action, struct gui_synclist *lists) | ||
316 | { | ||
317 | (void)lists; | ||
318 | if (action == ACTION_STD_OK) | ||
319 | return ACTION_STD_CANCEL; | ||
320 | return action; | ||
321 | } | ||
322 | enum themable_icons shortcut_menu_get_icon(int selected_item, void * data) | ||
323 | { | ||
324 | (void)data; | ||
325 | struct shortcut *sc = get_shortcut(selected_item); | ||
326 | if (!sc) | ||
327 | return Icon_NOICON; | ||
328 | if (sc->icon == Icon_NOICON) | ||
329 | { | ||
330 | switch (sc->type) | ||
331 | { | ||
332 | case SHORTCUT_FILE: | ||
333 | return filetype_get_icon(filetype_get_attr(sc->u.path)); | ||
334 | case SHORTCUT_BROWSER: | ||
335 | return Icon_Folder; | ||
336 | case SHORTCUT_SETTING: | ||
337 | return Icon_Menu_setting; | ||
338 | case SHORTCUT_DEBUGITEM: | ||
339 | return Icon_Menu_functioncall; | ||
340 | case SHORTCUT_PLAYLISTMENU: | ||
341 | return Icon_Playlist; | ||
342 | default: | ||
343 | break; | ||
344 | } | ||
345 | } | ||
346 | return sc->icon; | ||
347 | } | ||
348 | |||
349 | int do_shortcut_menu(void *ignored) | ||
350 | { | ||
351 | (void)ignored; | ||
352 | struct simplelist_info list; | ||
353 | struct shortcut *sc; | ||
354 | int done = GO_TO_PREVIOUS; | ||
355 | if (first_handle == 0) | ||
356 | shortcuts_init(); | ||
357 | simplelist_info_init(&list, P2STR(ID2P(LANG_SHORTCUTS)), shortcut_count, NULL); | ||
358 | list.get_name = shortcut_menu_get_name; | ||
359 | list.action_callback = shortcut_menu_get_action; | ||
360 | list.get_icon = shortcut_menu_get_icon; | ||
361 | list.title_icon = Icon_Bookmark; | ||
362 | |||
363 | push_current_activity(ACTIVITY_SHORTCUTSMENU); | ||
364 | |||
365 | while (done == GO_TO_PREVIOUS) | ||
366 | { | ||
367 | if (simplelist_show_list(&list)) | ||
368 | break; /* some error happened?! */ | ||
369 | if (list.selection == -1) | ||
370 | break; | ||
371 | else | ||
372 | { | ||
373 | sc = get_shortcut(list.selection); | ||
374 | if (!sc) | ||
375 | continue; | ||
376 | switch (sc->type) | ||
377 | { | ||
378 | case SHORTCUT_PLAYLISTMENU: | ||
379 | if (!file_exists(sc->u.path)) | ||
380 | { | ||
381 | splash(HZ, ID2P(LANG_NO_FILES)); | ||
382 | break; | ||
383 | } | ||
384 | else | ||
385 | { | ||
386 | onplay_show_playlist_menu(sc->u.path); | ||
387 | } | ||
388 | break; | ||
389 | case SHORTCUT_FILE: | ||
390 | if (!file_exists(sc->u.path)) | ||
391 | { | ||
392 | splash(HZ, ID2P(LANG_NO_FILES)); | ||
393 | break; | ||
394 | } | ||
395 | /* else fall through */ | ||
396 | case SHORTCUT_BROWSER: | ||
397 | { | ||
398 | struct browse_context browse; | ||
399 | browse_context_init(&browse, global_settings.dirfilter, 0, | ||
400 | NULL, NOICON, sc->u.path, NULL); | ||
401 | if (sc->type == SHORTCUT_FILE) | ||
402 | browse.flags |= BROWSE_RUNFILE; | ||
403 | done = rockbox_browse(&browse); | ||
404 | } | ||
405 | break; | ||
406 | case SHORTCUT_SETTING: | ||
407 | do_setting_screen(sc->u.setting, | ||
408 | sc->name[0] ? sc->name : P2STR(ID2P(sc->u.setting->lang_id)),NULL); | ||
409 | break; | ||
410 | case SHORTCUT_DEBUGITEM: | ||
411 | run_debug_screen(sc->u.path); | ||
412 | break; | ||
413 | case SHORTCUT_UNDEFINED: | ||
414 | default: | ||
415 | break; | ||
416 | } | ||
417 | } | ||
418 | } | ||
419 | pop_current_activity(); | ||
420 | return done; | ||
421 | } | ||