diff options
author | Sebastian Leonhardt <sebastian.leonhardt@web.de> | 2017-09-15 23:43:41 +0200 |
---|---|---|
committer | Sebastian Leonhardt <sebastian.leonhardt@web.de> | 2019-08-11 22:26:49 +0200 |
commit | 5c701b02809ffeec2e9de50e89995dbb85cd7a62 (patch) | |
tree | 823dd1444789ef1ebff521cc7d6ca36e1c4f740a | |
parent | a9a891b47b828c20b5b3d3e29983e66d48419377 (diff) | |
download | rockbox-5c701b02809ffeec2e9de50e89995dbb85cd7a62.tar.gz rockbox-5c701b02809ffeec2e9de50e89995dbb85cd7a62.zip |
Add support for Windows shortcuts (*.lnk files)
Supports only relative links across the same volume.
Change-Id: I4f61bb9d5f2385d5b15d2b9d9a3f814a7ac85b54
-rw-r--r-- | apps/plugins/SOURCES | 1 | ||||
-rw-r--r-- | apps/plugins/viewers.config | 1 | ||||
-rw-r--r-- | apps/plugins/windows_lnk.c | 344 | ||||
-rw-r--r-- | manual/plugins/main.tex | 3 | ||||
-rw-r--r-- | manual/plugins/shortcuts.tex | 4 | ||||
-rw-r--r-- | manual/plugins/winshortcuts.tex | 15 |
6 files changed, 368 insertions, 0 deletions
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index 6551df71a9..9e153a3c96 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES | |||
@@ -34,6 +34,7 @@ flipit.c | |||
34 | shopper.c | 34 | shopper.c |
35 | resistor.c | 35 | resistor.c |
36 | otp.c | 36 | otp.c |
37 | windows_lnk.c | ||
37 | 38 | ||
38 | #ifdef USB_ENABLE_HID | 39 | #ifdef USB_ENABLE_HID |
39 | remote_control.c | 40 | remote_control.c |
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index c938eeb275..96228085c9 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config | |||
@@ -100,3 +100,4 @@ z6,viewers/frotz,- | |||
100 | z7,viewers/frotz,- | 100 | z7,viewers/frotz,- |
101 | z8,viewers/frotz,- | 101 | z8,viewers/frotz,- |
102 | shopper,viewers/shopper,1 | 102 | shopper,viewers/shopper,1 |
103 | lnk,viewers/windows_lnk,- | ||
diff --git a/apps/plugins/windows_lnk.c b/apps/plugins/windows_lnk.c new file mode 100644 index 0000000000..6ef1a6a3ac --- /dev/null +++ b/apps/plugins/windows_lnk.c | |||
@@ -0,0 +1,344 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2017 Sebastian Leonhardt | ||
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 | /** | ||
25 | * Follow Windows shortcuts (*.lnk files) in Rockbox. | ||
26 | * If the destination is a file, it will be selected in the file browser, | ||
27 | * a directory will be entered. | ||
28 | * For now, only relative links are supported. | ||
29 | */ | ||
30 | |||
31 | /* a selection of link flags */ | ||
32 | #define HAS_LINK_TARGET_ID_LIST 0x01 | ||
33 | #define HAS_LINK_INFO 0x02 | ||
34 | #define HAS_NAME 0x04 | ||
35 | #define HAS_RELATIVE_PATH 0x08 | ||
36 | #define HAS_WORKING_DIR 0x10 | ||
37 | #define HAS_ARGUMENTS 0x20 | ||
38 | #define HAS_ICON_LOCATION 0x40 | ||
39 | #define IS_UNICODE 0x80 | ||
40 | #define FORCE_NO_LINK_INFO 0x100 | ||
41 | /* a selection of file attributes flags */ | ||
42 | #define FILE_ATTRIBUTE_DIRECTORY 0x10 | ||
43 | #define FILE_ATTRIBUTE_NORMAL 0x80 | ||
44 | |||
45 | |||
46 | /** | ||
47 | * Read one byte from file | ||
48 | * \param fd file descriptor | ||
49 | * \param *a where the data should go | ||
50 | * \return false if an error occured, true on success | ||
51 | */ | ||
52 | static bool read_byte(const int fd, unsigned char *a) | ||
53 | { | ||
54 | if (!rb->read(fd, a, 1)) | ||
55 | return false; | ||
56 | return true; | ||
57 | } | ||
58 | |||
59 | /** | ||
60 | * Read 16-bit word from file, respecting windows endianness (little endian) | ||
61 | * \param fd file descriptor | ||
62 | * \param *a where the data should go | ||
63 | * \return false if an error occured, true on success | ||
64 | */ | ||
65 | static bool read_word(const int fd, int *a) | ||
66 | { | ||
67 | unsigned char a1,a2; | ||
68 | int r; | ||
69 | |||
70 | r = read_byte(fd, &a1); | ||
71 | if (!r) | ||
72 | return false; | ||
73 | r = read_byte(fd, &a2); | ||
74 | if (!r) | ||
75 | return false; | ||
76 | *a = (a2<<8) + a1; | ||
77 | return true; | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Read 32-bit word from file, respecting windows endianness (little endian) | ||
82 | * \param fd file descriptor | ||
83 | * \param *a where the data should go | ||
84 | * \return false if an error occured, true on success | ||
85 | */ | ||
86 | static bool read_lword(const int fd, uint32_t *a) | ||
87 | { | ||
88 | int a1,a2; | ||
89 | int r; | ||
90 | |||
91 | r = read_word(fd, &a1); | ||
92 | if (!r) | ||
93 | return false; | ||
94 | r = read_word(fd, &a2); | ||
95 | if (!r) | ||
96 | return false; | ||
97 | *a = (a2<<16) + a1; | ||
98 | return true; | ||
99 | } | ||
100 | |||
101 | |||
102 | /** | ||
103 | * Scan *.lnk file for relative link target | ||
104 | * \param fd file descriptor | ||
105 | * \param link_target the extracted link destination | ||
106 | * \param target_size available space for the extracted link (in bytes) | ||
107 | * \param link_flags the link flags are stored here | ||
108 | * \param file_atts file attributes are stored here | ||
109 | * \return returns false if extraction failed. | ||
110 | */ | ||
111 | static bool extract_link_destination(const int fd, | ||
112 | char *link_target, const int target_size, | ||
113 | uint32_t *link_flags, uint32_t *file_atts) | ||
114 | { | ||
115 | int r; | ||
116 | |||
117 | /* Read ShellLinkHeader */ | ||
118 | uint32_t size; | ||
119 | r = read_lword(fd, &size); | ||
120 | if (!r) return false; | ||
121 | if (size!=0x4c) { /* header size MUST be 76 bytes */ | ||
122 | DEBUGF("unexpected header size 0x%08lx (must be 0x0000004c)\n", size); | ||
123 | return false; | ||
124 | } | ||
125 | |||
126 | /* Skip LinkCLSID (class identifier) */ | ||
127 | rb->lseek(fd, 0x10, SEEK_CUR); | ||
128 | /* We need the LinkFlags and File attribute (to see if target is a directory) */ | ||
129 | r = read_lword(fd, link_flags); | ||
130 | if (!r) return false; | ||
131 | r = read_lword(fd, file_atts); | ||
132 | if (!r) return false; | ||
133 | rb->lseek(fd, size, SEEK_SET); /* Skip to end of header */ | ||
134 | |||
135 | /* For now we only support relative links, so we can exit right away | ||
136 | if no relative link structure is present */ | ||
137 | if (!(*link_flags & HAS_RELATIVE_PATH)) { | ||
138 | DEBUGF("Link doesn't have relative path information\n"); | ||
139 | return false; | ||
140 | } | ||
141 | |||
142 | /* Read (skip) LinkTargetIDList structure if present */ | ||
143 | if (*link_flags & HAS_LINK_TARGET_ID_LIST) { | ||
144 | int size; | ||
145 | if (!read_word(fd, &size)) | ||
146 | return false; | ||
147 | rb->lseek(fd, size, SEEK_CUR); | ||
148 | } | ||
149 | |||
150 | /* Read (skip) LinkInfo structure if present */ | ||
151 | if (*link_flags & HAS_LINK_INFO) { | ||
152 | uint32_t size; | ||
153 | r = read_lword(fd, &size); | ||
154 | if (!r) return false; | ||
155 | rb->lseek(fd, size-4, SEEK_CUR); | ||
156 | } | ||
157 | |||
158 | /* String Data section */ | ||
159 | |||
160 | /* Read (skip) NAME_STRING StringData structure if present */ | ||
161 | if (*link_flags & HAS_NAME) { | ||
162 | int ccount; | ||
163 | if (!read_word(fd, &ccount)) | ||
164 | return false; | ||
165 | if (*link_flags & IS_UNICODE) | ||
166 | rb->lseek(fd, ccount*2, SEEK_CUR); | ||
167 | else | ||
168 | rb->lseek(fd, ccount, SEEK_CUR); | ||
169 | } | ||
170 | |||
171 | /* Read RELATIVE_PATH StringData structure if present */ | ||
172 | /* This is finally the data we are searching for! */ | ||
173 | if (*link_flags & HAS_RELATIVE_PATH) { | ||
174 | int ccount; | ||
175 | r = read_word(fd, &ccount); | ||
176 | if (*link_flags & IS_UNICODE) { | ||
177 | unsigned char utf16[4], utf8[10]; | ||
178 | link_target[0] = '\0'; | ||
179 | for (int i=0; i<ccount; ++i) { | ||
180 | r = read_byte(fd, &utf16[0]); | ||
181 | if (!r) return false; | ||
182 | r = read_byte(fd, &utf16[1]); | ||
183 | if (!r) return false; | ||
184 | /* check for surrogate pair and read the second char */ | ||
185 | if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) { | ||
186 | r = read_byte(fd, &utf16[2]); | ||
187 | if (!r) return false; | ||
188 | r = read_byte(fd, &utf16[3]); | ||
189 | if (!r) return false; | ||
190 | ++i; | ||
191 | } | ||
192 | char *ptr = rb->utf16LEdecode(utf16, utf8, 1); | ||
193 | *ptr = '\0'; | ||
194 | rb->strlcat(link_target, utf8, target_size); | ||
195 | } | ||
196 | } | ||
197 | else { /* non-unicode */ | ||
198 | if (ccount >= target_size) { | ||
199 | DEBUGF("ERROR: link target filename exceeds size!"); | ||
200 | return false; | ||
201 | } | ||
202 | rb->read(fd, link_target, ccount); | ||
203 | link_target[ccount] = '\0'; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | /* convert from windows to unix subdir separators */ | ||
208 | for (int i=0; link_target[i] != '\0'; ++i) { | ||
209 | if (link_target[i]=='\\') | ||
210 | link_target[i] = '/'; | ||
211 | } | ||
212 | |||
213 | return true; | ||
214 | } | ||
215 | |||
216 | |||
217 | /** | ||
218 | * strip rightmost part of file/pathname to next '/', i.e. remove filename | ||
219 | * or last subdirectory. Leaves a trailing '/' character. | ||
220 | * \param pathname full path or filename | ||
221 | */ | ||
222 | static void strip_rightmost_part(char *pathname) | ||
223 | { | ||
224 | for (int i = rb->strlen(pathname)-2; i >= 0; --i) { | ||
225 | if (pathname[i] == '/') { | ||
226 | pathname[i+1] = '\0'; /* cut off */ | ||
227 | return; | ||
228 | } | ||
229 | } | ||
230 | pathname[0] = '\0'; | ||
231 | } | ||
232 | |||
233 | |||
234 | /** | ||
235 | * Combine link file's absolute path with relative link target to form | ||
236 | * (absolute) link destination | ||
237 | * \param abs_path full shortcut filename (including path) | ||
238 | * \param rel_path the extracted relative link target | ||
239 | * \param max_len maximum lengt of combined filename | ||
240 | */ | ||
241 | static void assemble_link_dest(char *const abs_path, char const *rel_path, | ||
242 | const size_t max_len) | ||
243 | { | ||
244 | strip_rightmost_part(abs_path); /* cut off link filename */ | ||
245 | |||
246 | for (;;) { | ||
247 | if (rb->strncmp(rel_path, "../", 3)==0) { | ||
248 | rel_path += 3; | ||
249 | strip_rightmost_part(abs_path); | ||
250 | } | ||
251 | else if (rb->strncmp(rel_path, "./", 2)==0) { | ||
252 | rel_path += 2; | ||
253 | } | ||
254 | else | ||
255 | break; | ||
256 | } | ||
257 | |||
258 | if (*rel_path=='/') | ||
259 | ++rel_path; /* avoid double '/' chars when concatenating */ | ||
260 | rb->strlcat(abs_path, rel_path, max_len); | ||
261 | } | ||
262 | |||
263 | |||
264 | /** | ||
265 | * Select the chosen file in the file browser. A directory (filename ending | ||
266 | * with '/') will be entered. | ||
267 | * \param link_target link target to be selected in the browser | ||
268 | * \return returns false if the target doesn't exist | ||
269 | */ | ||
270 | static bool goto_entry(char *link_target) | ||
271 | { | ||
272 | DEBUGF("Trying to go to '%s'...\n", link_target); | ||
273 | if (!rb->file_exists(link_target)) | ||
274 | return false; | ||
275 | |||
276 | /* Set the browsers dirfilter to the global setting. | ||
277 | * This is required in case the plugin was launched | ||
278 | * from the plugins browser, in which case the | ||
279 | * dirfilter is set to only display .rock files */ | ||
280 | rb->set_dirfilter(rb->global_settings->dirfilter); | ||
281 | |||
282 | /* Change directory to the entry selected by the user */ | ||
283 | rb->set_current_file(link_target); | ||
284 | return true; | ||
285 | } | ||
286 | |||
287 | |||
288 | enum plugin_status plugin_start(const void* void_parameter) | ||
289 | { | ||
290 | char *link_filename; | ||
291 | char extracted_link[MAX_PATH]; | ||
292 | char link_target[MAX_PATH]; | ||
293 | uint32_t lnk_flags; | ||
294 | uint32_t file_atts; | ||
295 | |||
296 | /* This is a viewer, so a parameter must have been specified */ | ||
297 | if (void_parameter == NULL) { | ||
298 | rb->splash(HZ*3, "No *.lnk file selected"); | ||
299 | return PLUGIN_OK; | ||
300 | } | ||
301 | |||
302 | link_filename = (char*)void_parameter; | ||
303 | DEBUGF("Shortcut filename: \"%s\"\n", link_filename); | ||
304 | |||
305 | int fd = rb->open(link_filename, O_RDONLY); | ||
306 | if (fd < 0) { | ||
307 | DEBUGF("Can't open link file\n"); | ||
308 | rb->splashf(HZ*3, "Can't open link file!"); | ||
309 | return PLUGIN_OK; | ||
310 | } | ||
311 | |||
312 | if (!extract_link_destination(fd, extracted_link, sizeof(extracted_link), | ||
313 | &lnk_flags, &file_atts)) { | ||
314 | rb->close(fd); | ||
315 | DEBUGF("Error in extract_link_destination()\n"); | ||
316 | rb->splashf(HZ*3, "Unsupported or erroneous link file format"); | ||
317 | return PLUGIN_OK; | ||
318 | } | ||
319 | rb->close(fd); | ||
320 | DEBUGF("Shortcut destination: \"%s\"\n", extracted_link); | ||
321 | |||
322 | rb->strcpy(link_target, link_filename); | ||
323 | assemble_link_dest(link_target, extracted_link, sizeof(link_target)); | ||
324 | DEBUGF("Link absolute path: \"%s\"\n", link_target); | ||
325 | |||
326 | /* if target is a directory, add '/' to the dir name, | ||
327 | so that the directory gets entered instead of just highlighted */ | ||
328 | if (file_atts & FILE_ATTRIBUTE_DIRECTORY) | ||
329 | if (link_target[rb->strlen(link_target)-1] != '/') | ||
330 | rb->strlcat(link_target, "/", sizeof(link_target)); | ||
331 | |||
332 | if (!goto_entry(link_target)) { | ||
333 | char *what; | ||
334 | if (file_atts & FILE_ATTRIBUTE_DIRECTORY) | ||
335 | what = "directory"; | ||
336 | else | ||
337 | what = "file"; | ||
338 | rb->splashf(HZ*3, "Can't find %s %s", what, link_target); | ||
339 | DEBUGF("Can't find %s %s", what, link_target); | ||
340 | return PLUGIN_OK; | ||
341 | } | ||
342 | |||
343 | return PLUGIN_OK; | ||
344 | } | ||
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index c07d106024..96326cbffe 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex | |||
@@ -165,6 +165,7 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} | |||
165 | {\textbf{Viewer Plugin}& \textbf{Associated filetype(s)} & \textbf{Context Menu only}}% | 165 | {\textbf{Viewer Plugin}& \textbf{Associated filetype(s)} & \textbf{Context Menu only}}% |
166 | {}{} | 166 | {}{} |
167 | Shortcuts & \fname{.link} & \\ | 167 | Shortcuts & \fname{.link} & \\ |
168 | MS Windows shortcuts & \fname{.lnk} & \\ | ||
168 | Chip-8 Emulator & \fname{.ch8} & \\ | 169 | Chip-8 Emulator & \fname{.ch8} & \\ |
169 | Frotz & \fname{.z1} to \fname{.z8} & \\ | 170 | Frotz & \fname{.z1} to \fname{.z8} & \\ |
170 | Image Viewer & \fname{.bmp, .jpg, .jpeg, .png\opt{lcd_color}{, .ppm}} & \\ | 171 | Image Viewer & \fname{.bmp, .jpg, .jpeg, .png\opt{lcd_color}{, .ppm}} & \\ |
@@ -200,6 +201,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} | |||
200 | 201 | ||
201 | {\input{plugins/shortcuts.tex}} | 202 | {\input{plugins/shortcuts.tex}} |
202 | 203 | ||
204 | {\input{plugins/winshortcuts.tex}} | ||
205 | |||
203 | \opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}} | 206 | \opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}} |
204 | 207 | ||
205 | \opt{lcd_bitmap}{\input{plugins/frotz.tex}} | 208 | \opt{lcd_bitmap}{\input{plugins/frotz.tex}} |
diff --git a/manual/plugins/shortcuts.tex b/manual/plugins/shortcuts.tex index 2cca5f773f..813bdeaf56 100644 --- a/manual/plugins/shortcuts.tex +++ b/manual/plugins/shortcuts.tex | |||
@@ -8,6 +8,10 @@ line containing the name of the file or the directory you want to quickly | |||
8 | jump to. All names should be full absolute names, i.e. they should start | 8 | jump to. All names should be full absolute names, i.e. they should start |
9 | with a \fname{/}. Directory names should also end with a \fname{/}. | 9 | with a \fname{/}. Directory names should also end with a \fname{/}. |
10 | 10 | ||
11 | \note{This plugin cannot read Microsoft Windows shortcuts (\fname{.lnk} | ||
12 | files). These are handled by a separate plugin; see | ||
13 | \reference{ref:Winshortcutsplugin}.} | ||
14 | |||
11 | \subsubsection{How to create \fname{.link} files} | 15 | \subsubsection{How to create \fname{.link} files} |
12 | 16 | ||
13 | You can use your favourite text editor to create a \fname{.link} file on the | 17 | You can use your favourite text editor to create a \fname{.link} file on the |
diff --git a/manual/plugins/winshortcuts.tex b/manual/plugins/winshortcuts.tex new file mode 100644 index 0000000000..a3a4063b6f --- /dev/null +++ b/manual/plugins/winshortcuts.tex | |||
@@ -0,0 +1,15 @@ | |||
1 | \subsection{Windows Shortcuts} | ||
2 | \label{ref:Winshortcutsplugin} | ||
3 | |||
4 | This plugin follows Microsoft Windows Explorer shortcuts (\fname{.lnk} files). | ||
5 | In Rockbox, these types of shortcuts will show up as \fname{.lnk} files. To | ||
6 | follow a shortcut, just ``play'' a \fname{.lnk} file from the file browser. | ||
7 | The plugin will navigate the file browser to the linked file (which | ||
8 | will be highlighted) or directory (which will be opened). Linked files will | ||
9 | not be automatically opened; you must do this manually. | ||
10 | |||
11 | Only relative links across the same volume are supported. | ||
12 | |||
13 | \note{You may like to use native Rockbox shortcuts instead. These can be | ||
14 | created from within Rockbox itself and have advanced capabilities. | ||
15 | See \reference{ref:Shortcutsplugin}.} | ||