summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/plugins/SOURCES1
-rw-r--r--apps/plugins/viewers.config1
-rw-r--r--apps/plugins/windows_lnk.c344
-rw-r--r--manual/plugins/main.tex3
-rw-r--r--manual/plugins/shortcuts.tex4
-rw-r--r--manual/plugins/winshortcuts.tex15
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
34shopper.c 34shopper.c
35resistor.c 35resistor.c
36otp.c 36otp.c
37windows_lnk.c
37 38
38#ifdef USB_ENABLE_HID 39#ifdef USB_ENABLE_HID
39remote_control.c 40remote_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,-
100z7,viewers/frotz,- 100z7,viewers/frotz,-
101z8,viewers/frotz,- 101z8,viewers/frotz,-
102shopper,viewers/shopper,1 102shopper,viewers/shopper,1
103lnk,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 */
52static 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 */
65static 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 */
86static 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 */
111static 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*/
222static 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 */
241static 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 */
270static 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
288enum 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
8jump to. All names should be full absolute names, i.e. they should start 8jump to. All names should be full absolute names, i.e. they should start
9with a \fname{/}. Directory names should also end with a \fname{/}. 9with a \fname{/}. Directory names should also end with a \fname{/}.
10 10
11\note{This plugin cannot read Microsoft Windows shortcuts (\fname{.lnk}
12files). 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
13You can use your favourite text editor to create a \fname{.link} file on the 17You 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
4This plugin follows Microsoft Windows Explorer shortcuts (\fname{.lnk} files).
5In Rockbox, these types of shortcuts will show up as \fname{.lnk} files. To
6follow a shortcut, just ``play'' a \fname{.lnk} file from the file browser.
7The plugin will navigate the file browser to the linked file (which
8will be highlighted) or directory (which will be opened). Linked files will
9not be automatically opened; you must do this manually.
10
11Only 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}.}