A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd

Add support for Windows shortcuts (*.lnk files)

Supports only relative links across the same volume.

Change-Id: I4f61bb9d5f2385d5b15d2b9d9a3f814a7ac85b54

+368
+1
apps/plugins/SOURCES
··· 34 34 shopper.c 35 35 resistor.c 36 36 otp.c 37 + windows_lnk.c 37 38 38 39 #ifdef USB_ENABLE_HID 39 40 remote_control.c
+1
apps/plugins/viewers.config
··· 100 100 z7,viewers/frotz,- 101 101 z8,viewers/frotz,- 102 102 shopper,viewers/shopper,1 103 + lnk,viewers/windows_lnk,-
+344
apps/plugins/windows_lnk.c
··· 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 + }
+3
manual/plugins/main.tex
··· 165 165 {\textbf{Viewer Plugin}& \textbf{Associated filetype(s)} & \textbf{Context Menu only}}% 166 166 {}{} 167 167 Shortcuts & \fname{.link} & \\ 168 + MS Windows shortcuts & \fname{.lnk} & \\ 168 169 Chip-8 Emulator & \fname{.ch8} & \\ 169 170 Frotz & \fname{.z1} to \fname{.z8} & \\ 170 171 Image Viewer & \fname{.bmp, .jpg, .jpeg, .png\opt{lcd_color}{, .ppm}} & \\ ··· 199 200 \end{table} 200 201 201 202 {\input{plugins/shortcuts.tex}} 203 + 204 + {\input{plugins/winshortcuts.tex}} 202 205 203 206 \opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}} 204 207
+4
manual/plugins/shortcuts.tex
··· 8 8 jump to. All names should be full absolute names, i.e. they should start 9 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 15 \subsubsection{How to create \fname{.link} files} 12 16 13 17 You can use your favourite text editor to create a \fname{.link} file on the
+15
manual/plugins/winshortcuts.tex
··· 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}.}