forked from len0rd/rockbox
Add support for Windows shortcuts (*.lnk files)
Supports only relative links across the same volume. Change-Id: I4f61bb9d5f2385d5b15d2b9d9a3f814a7ac85b54
This commit is contained in:
parent
a9a891b47b
commit
5c701b0280
6 changed files with 368 additions and 0 deletions
|
@ -34,6 +34,7 @@ flipit.c
|
||||||
shopper.c
|
shopper.c
|
||||||
resistor.c
|
resistor.c
|
||||||
otp.c
|
otp.c
|
||||||
|
windows_lnk.c
|
||||||
|
|
||||||
#ifdef USB_ENABLE_HID
|
#ifdef USB_ENABLE_HID
|
||||||
remote_control.c
|
remote_control.c
|
||||||
|
|
|
@ -100,3 +100,4 @@ z6,viewers/frotz,-
|
||||||
z7,viewers/frotz,-
|
z7,viewers/frotz,-
|
||||||
z8,viewers/frotz,-
|
z8,viewers/frotz,-
|
||||||
shopper,viewers/shopper,1
|
shopper,viewers/shopper,1
|
||||||
|
lnk,viewers/windows_lnk,-
|
||||||
|
|
344
apps/plugins/windows_lnk.c
Normal file
344
apps/plugins/windows_lnk.c
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
/***************************************************************************
|
||||||
|
* __________ __ ___.
|
||||||
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
* \/ \/ \/ \/ \/
|
||||||
|
* $Id$
|
||||||
|
*
|
||||||
|
* Copyright (C) 2017 Sebastian Leonhardt
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||||
|
* KIND, either express or implied.
|
||||||
|
*
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "plugin.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Follow Windows shortcuts (*.lnk files) in Rockbox.
|
||||||
|
* If the destination is a file, it will be selected in the file browser,
|
||||||
|
* a directory will be entered.
|
||||||
|
* For now, only relative links are supported.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* a selection of link flags */
|
||||||
|
#define HAS_LINK_TARGET_ID_LIST 0x01
|
||||||
|
#define HAS_LINK_INFO 0x02
|
||||||
|
#define HAS_NAME 0x04
|
||||||
|
#define HAS_RELATIVE_PATH 0x08
|
||||||
|
#define HAS_WORKING_DIR 0x10
|
||||||
|
#define HAS_ARGUMENTS 0x20
|
||||||
|
#define HAS_ICON_LOCATION 0x40
|
||||||
|
#define IS_UNICODE 0x80
|
||||||
|
#define FORCE_NO_LINK_INFO 0x100
|
||||||
|
/* a selection of file attributes flags */
|
||||||
|
#define FILE_ATTRIBUTE_DIRECTORY 0x10
|
||||||
|
#define FILE_ATTRIBUTE_NORMAL 0x80
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read one byte from file
|
||||||
|
* \param fd file descriptor
|
||||||
|
* \param *a where the data should go
|
||||||
|
* \return false if an error occured, true on success
|
||||||
|
*/
|
||||||
|
static bool read_byte(const int fd, unsigned char *a)
|
||||||
|
{
|
||||||
|
if (!rb->read(fd, a, 1))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read 16-bit word from file, respecting windows endianness (little endian)
|
||||||
|
* \param fd file descriptor
|
||||||
|
* \param *a where the data should go
|
||||||
|
* \return false if an error occured, true on success
|
||||||
|
*/
|
||||||
|
static bool read_word(const int fd, int *a)
|
||||||
|
{
|
||||||
|
unsigned char a1,a2;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = read_byte(fd, &a1);
|
||||||
|
if (!r)
|
||||||
|
return false;
|
||||||
|
r = read_byte(fd, &a2);
|
||||||
|
if (!r)
|
||||||
|
return false;
|
||||||
|
*a = (a2<<8) + a1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read 32-bit word from file, respecting windows endianness (little endian)
|
||||||
|
* \param fd file descriptor
|
||||||
|
* \param *a where the data should go
|
||||||
|
* \return false if an error occured, true on success
|
||||||
|
*/
|
||||||
|
static bool read_lword(const int fd, uint32_t *a)
|
||||||
|
{
|
||||||
|
int a1,a2;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = read_word(fd, &a1);
|
||||||
|
if (!r)
|
||||||
|
return false;
|
||||||
|
r = read_word(fd, &a2);
|
||||||
|
if (!r)
|
||||||
|
return false;
|
||||||
|
*a = (a2<<16) + a1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan *.lnk file for relative link target
|
||||||
|
* \param fd file descriptor
|
||||||
|
* \param link_target the extracted link destination
|
||||||
|
* \param target_size available space for the extracted link (in bytes)
|
||||||
|
* \param link_flags the link flags are stored here
|
||||||
|
* \param file_atts file attributes are stored here
|
||||||
|
* \return returns false if extraction failed.
|
||||||
|
*/
|
||||||
|
static bool extract_link_destination(const int fd,
|
||||||
|
char *link_target, const int target_size,
|
||||||
|
uint32_t *link_flags, uint32_t *file_atts)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* Read ShellLinkHeader */
|
||||||
|
uint32_t size;
|
||||||
|
r = read_lword(fd, &size);
|
||||||
|
if (!r) return false;
|
||||||
|
if (size!=0x4c) { /* header size MUST be 76 bytes */
|
||||||
|
DEBUGF("unexpected header size 0x%08lx (must be 0x0000004c)\n", size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip LinkCLSID (class identifier) */
|
||||||
|
rb->lseek(fd, 0x10, SEEK_CUR);
|
||||||
|
/* We need the LinkFlags and File attribute (to see if target is a directory) */
|
||||||
|
r = read_lword(fd, link_flags);
|
||||||
|
if (!r) return false;
|
||||||
|
r = read_lword(fd, file_atts);
|
||||||
|
if (!r) return false;
|
||||||
|
rb->lseek(fd, size, SEEK_SET); /* Skip to end of header */
|
||||||
|
|
||||||
|
/* For now we only support relative links, so we can exit right away
|
||||||
|
if no relative link structure is present */
|
||||||
|
if (!(*link_flags & HAS_RELATIVE_PATH)) {
|
||||||
|
DEBUGF("Link doesn't have relative path information\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read (skip) LinkTargetIDList structure if present */
|
||||||
|
if (*link_flags & HAS_LINK_TARGET_ID_LIST) {
|
||||||
|
int size;
|
||||||
|
if (!read_word(fd, &size))
|
||||||
|
return false;
|
||||||
|
rb->lseek(fd, size, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read (skip) LinkInfo structure if present */
|
||||||
|
if (*link_flags & HAS_LINK_INFO) {
|
||||||
|
uint32_t size;
|
||||||
|
r = read_lword(fd, &size);
|
||||||
|
if (!r) return false;
|
||||||
|
rb->lseek(fd, size-4, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* String Data section */
|
||||||
|
|
||||||
|
/* Read (skip) NAME_STRING StringData structure if present */
|
||||||
|
if (*link_flags & HAS_NAME) {
|
||||||
|
int ccount;
|
||||||
|
if (!read_word(fd, &ccount))
|
||||||
|
return false;
|
||||||
|
if (*link_flags & IS_UNICODE)
|
||||||
|
rb->lseek(fd, ccount*2, SEEK_CUR);
|
||||||
|
else
|
||||||
|
rb->lseek(fd, ccount, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read RELATIVE_PATH StringData structure if present */
|
||||||
|
/* This is finally the data we are searching for! */
|
||||||
|
if (*link_flags & HAS_RELATIVE_PATH) {
|
||||||
|
int ccount;
|
||||||
|
r = read_word(fd, &ccount);
|
||||||
|
if (*link_flags & IS_UNICODE) {
|
||||||
|
unsigned char utf16[4], utf8[10];
|
||||||
|
link_target[0] = '\0';
|
||||||
|
for (int i=0; i<ccount; ++i) {
|
||||||
|
r = read_byte(fd, &utf16[0]);
|
||||||
|
if (!r) return false;
|
||||||
|
r = read_byte(fd, &utf16[1]);
|
||||||
|
if (!r) return false;
|
||||||
|
/* check for surrogate pair and read the second char */
|
||||||
|
if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) {
|
||||||
|
r = read_byte(fd, &utf16[2]);
|
||||||
|
if (!r) return false;
|
||||||
|
r = read_byte(fd, &utf16[3]);
|
||||||
|
if (!r) return false;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
char *ptr = rb->utf16LEdecode(utf16, utf8, 1);
|
||||||
|
*ptr = '\0';
|
||||||
|
rb->strlcat(link_target, utf8, target_size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { /* non-unicode */
|
||||||
|
if (ccount >= target_size) {
|
||||||
|
DEBUGF("ERROR: link target filename exceeds size!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rb->read(fd, link_target, ccount);
|
||||||
|
link_target[ccount] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* convert from windows to unix subdir separators */
|
||||||
|
for (int i=0; link_target[i] != '\0'; ++i) {
|
||||||
|
if (link_target[i]=='\\')
|
||||||
|
link_target[i] = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* strip rightmost part of file/pathname to next '/', i.e. remove filename
|
||||||
|
* or last subdirectory. Leaves a trailing '/' character.
|
||||||
|
* \param pathname full path or filename
|
||||||
|
*/
|
||||||
|
static void strip_rightmost_part(char *pathname)
|
||||||
|
{
|
||||||
|
for (int i = rb->strlen(pathname)-2; i >= 0; --i) {
|
||||||
|
if (pathname[i] == '/') {
|
||||||
|
pathname[i+1] = '\0'; /* cut off */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pathname[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combine link file's absolute path with relative link target to form
|
||||||
|
* (absolute) link destination
|
||||||
|
* \param abs_path full shortcut filename (including path)
|
||||||
|
* \param rel_path the extracted relative link target
|
||||||
|
* \param max_len maximum lengt of combined filename
|
||||||
|
*/
|
||||||
|
static void assemble_link_dest(char *const abs_path, char const *rel_path,
|
||||||
|
const size_t max_len)
|
||||||
|
{
|
||||||
|
strip_rightmost_part(abs_path); /* cut off link filename */
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (rb->strncmp(rel_path, "../", 3)==0) {
|
||||||
|
rel_path += 3;
|
||||||
|
strip_rightmost_part(abs_path);
|
||||||
|
}
|
||||||
|
else if (rb->strncmp(rel_path, "./", 2)==0) {
|
||||||
|
rel_path += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*rel_path=='/')
|
||||||
|
++rel_path; /* avoid double '/' chars when concatenating */
|
||||||
|
rb->strlcat(abs_path, rel_path, max_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select the chosen file in the file browser. A directory (filename ending
|
||||||
|
* with '/') will be entered.
|
||||||
|
* \param link_target link target to be selected in the browser
|
||||||
|
* \return returns false if the target doesn't exist
|
||||||
|
*/
|
||||||
|
static bool goto_entry(char *link_target)
|
||||||
|
{
|
||||||
|
DEBUGF("Trying to go to '%s'...\n", link_target);
|
||||||
|
if (!rb->file_exists(link_target))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* Set the browsers dirfilter to the global setting.
|
||||||
|
* This is required in case the plugin was launched
|
||||||
|
* from the plugins browser, in which case the
|
||||||
|
* dirfilter is set to only display .rock files */
|
||||||
|
rb->set_dirfilter(rb->global_settings->dirfilter);
|
||||||
|
|
||||||
|
/* Change directory to the entry selected by the user */
|
||||||
|
rb->set_current_file(link_target);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum plugin_status plugin_start(const void* void_parameter)
|
||||||
|
{
|
||||||
|
char *link_filename;
|
||||||
|
char extracted_link[MAX_PATH];
|
||||||
|
char link_target[MAX_PATH];
|
||||||
|
uint32_t lnk_flags;
|
||||||
|
uint32_t file_atts;
|
||||||
|
|
||||||
|
/* This is a viewer, so a parameter must have been specified */
|
||||||
|
if (void_parameter == NULL) {
|
||||||
|
rb->splash(HZ*3, "No *.lnk file selected");
|
||||||
|
return PLUGIN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
link_filename = (char*)void_parameter;
|
||||||
|
DEBUGF("Shortcut filename: \"%s\"\n", link_filename);
|
||||||
|
|
||||||
|
int fd = rb->open(link_filename, O_RDONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
DEBUGF("Can't open link file\n");
|
||||||
|
rb->splashf(HZ*3, "Can't open link file!");
|
||||||
|
return PLUGIN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!extract_link_destination(fd, extracted_link, sizeof(extracted_link),
|
||||||
|
&lnk_flags, &file_atts)) {
|
||||||
|
rb->close(fd);
|
||||||
|
DEBUGF("Error in extract_link_destination()\n");
|
||||||
|
rb->splashf(HZ*3, "Unsupported or erroneous link file format");
|
||||||
|
return PLUGIN_OK;
|
||||||
|
}
|
||||||
|
rb->close(fd);
|
||||||
|
DEBUGF("Shortcut destination: \"%s\"\n", extracted_link);
|
||||||
|
|
||||||
|
rb->strcpy(link_target, link_filename);
|
||||||
|
assemble_link_dest(link_target, extracted_link, sizeof(link_target));
|
||||||
|
DEBUGF("Link absolute path: \"%s\"\n", link_target);
|
||||||
|
|
||||||
|
/* if target is a directory, add '/' to the dir name,
|
||||||
|
so that the directory gets entered instead of just highlighted */
|
||||||
|
if (file_atts & FILE_ATTRIBUTE_DIRECTORY)
|
||||||
|
if (link_target[rb->strlen(link_target)-1] != '/')
|
||||||
|
rb->strlcat(link_target, "/", sizeof(link_target));
|
||||||
|
|
||||||
|
if (!goto_entry(link_target)) {
|
||||||
|
char *what;
|
||||||
|
if (file_atts & FILE_ATTRIBUTE_DIRECTORY)
|
||||||
|
what = "directory";
|
||||||
|
else
|
||||||
|
what = "file";
|
||||||
|
rb->splashf(HZ*3, "Can't find %s %s", what, link_target);
|
||||||
|
DEBUGF("Can't find %s %s", what, link_target);
|
||||||
|
return PLUGIN_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PLUGIN_OK;
|
||||||
|
}
|
|
@ -165,6 +165,7 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).}
|
||||||
{\textbf{Viewer Plugin}& \textbf{Associated filetype(s)} & \textbf{Context Menu only}}%
|
{\textbf{Viewer Plugin}& \textbf{Associated filetype(s)} & \textbf{Context Menu only}}%
|
||||||
{}{}
|
{}{}
|
||||||
Shortcuts & \fname{.link} & \\
|
Shortcuts & \fname{.link} & \\
|
||||||
|
MS Windows shortcuts & \fname{.lnk} & \\
|
||||||
Chip-8 Emulator & \fname{.ch8} & \\
|
Chip-8 Emulator & \fname{.ch8} & \\
|
||||||
Frotz & \fname{.z1} to \fname{.z8} & \\
|
Frotz & \fname{.z1} to \fname{.z8} & \\
|
||||||
Image Viewer & \fname{.bmp, .jpg, .jpeg, .png\opt{lcd_color}{, .ppm}} & \\
|
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}).}
|
||||||
|
|
||||||
{\input{plugins/shortcuts.tex}}
|
{\input{plugins/shortcuts.tex}}
|
||||||
|
|
||||||
|
{\input{plugins/winshortcuts.tex}}
|
||||||
|
|
||||||
\opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}}
|
\opt{lcd_bitmap}{\input{plugins/chip8emulator.tex}}
|
||||||
|
|
||||||
\opt{lcd_bitmap}{\input{plugins/frotz.tex}}
|
\opt{lcd_bitmap}{\input{plugins/frotz.tex}}
|
||||||
|
|
|
@ -8,6 +8,10 @@ line containing the name of the file or the directory you want to quickly
|
||||||
jump to. All names should be full absolute names, i.e. they should start
|
jump to. All names should be full absolute names, i.e. they should start
|
||||||
with a \fname{/}. Directory names should also end with a \fname{/}.
|
with a \fname{/}. Directory names should also end with a \fname{/}.
|
||||||
|
|
||||||
|
\note{This plugin cannot read Microsoft Windows shortcuts (\fname{.lnk}
|
||||||
|
files). These are handled by a separate plugin; see
|
||||||
|
\reference{ref:Winshortcutsplugin}.}
|
||||||
|
|
||||||
\subsubsection{How to create \fname{.link} files}
|
\subsubsection{How to create \fname{.link} files}
|
||||||
|
|
||||||
You can use your favourite text editor to create a \fname{.link} file on the
|
You can use your favourite text editor to create a \fname{.link} file on the
|
||||||
|
|
15
manual/plugins/winshortcuts.tex
Normal file
15
manual/plugins/winshortcuts.tex
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
\subsection{Windows Shortcuts}
|
||||||
|
\label{ref:Winshortcutsplugin}
|
||||||
|
|
||||||
|
This plugin follows Microsoft Windows Explorer shortcuts (\fname{.lnk} files).
|
||||||
|
In Rockbox, these types of shortcuts will show up as \fname{.lnk} files. To
|
||||||
|
follow a shortcut, just ``play'' a \fname{.lnk} file from the file browser.
|
||||||
|
The plugin will navigate the file browser to the linked file (which
|
||||||
|
will be highlighted) or directory (which will be opened). Linked files will
|
||||||
|
not be automatically opened; you must do this manually.
|
||||||
|
|
||||||
|
Only relative links across the same volume are supported.
|
||||||
|
|
||||||
|
\note{You may like to use native Rockbox shortcuts instead. These can be
|
||||||
|
created from within Rockbox itself and have advanced capabilities.
|
||||||
|
See \reference{ref:Shortcutsplugin}.}
|
Loading…
Add table
Add a link
Reference in a new issue