Don't use xdg-open when showing file in folder

This commit is contained in:
Roman Chistokhodov 2016-03-17 07:54:59 -04:00 committed by FreeSlave
parent d9af0b3b17
commit 73e4b1702a
1 changed files with 377 additions and 47 deletions

View File

@ -1245,67 +1245,397 @@ class SDLPlatform : Platform {
wait(pid); wait(pid);
return true; return true;
} else { } else {
import std.exception; import std.stdio : File;
import std.algorithm : map, filter, splitter, find, canFind, equal, findSplit;
import std.ascii : isAlpha;
import std.exception : collectException;
import std.file : isDir, isFile;
import std.path : buildPath, absolutePath, isAbsolute, dirName, baseName;
import std.process : environment, spawnProcess;
import std.range;
import std.string : toStringz;
import std.typecons : Tuple, tuple;
/* string toOpen = pathName;
* Use xdg-mime to get default application for inode/directory.
* If application is known to be able to select the file, start it directly.
* Otherwise just open directory with xdg-open.
*
* So this relies on external tools like xdg-mime and xdg-open.
* To make implementation free from external tools we need to implement
* reading of mimeapps.list to find appropriate .desktop file,
* find it on the file system, parse its Exec value and run.
* That's basically what xdg-open is doing.
*
* We still will need to keep track of file managers capable of selecting file on opening,
* since there's no freedesktop specification on this feature.
*/
static string[] getXdgOpen(string toOpen) static inout(char)[] doUnescape(inout(char)[] value, in Tuple!(char, char)[] pairs) nothrow pure {
auto toReturn = appender!(typeof(value))();
for (size_t i = 0; i < value.length; i++) {
if (value[i] == '\\') {
if (i < value.length - 1) {
char c = value[i+1];
auto t = pairs.find!"a[0] == b[0]"(tuple(c,c));
if (!t.empty) {
toReturn.put(t.front[1]);
i++;
continue;
}
}
}
toReturn.put(value[i]);
}
return toReturn.data;
}
static auto unescapeExecArgument(string arg) nothrow pure
{
static immutable Tuple!(char, char)[] pairs = [
tuple('s', ' '), tuple('n', '\n'), tuple('r', '\r'), tuple('t', '\t'),
tuple('"', '"'), tuple('\'', '\''), tuple('\\', '\\'), tuple('>', '>'),
tuple('<', '<'), tuple('~', '~'), tuple('|', '|'), tuple('&', '&'),
tuple(';', ';'), tuple('$', '$'), tuple('*', '*'), tuple('?', '?'),
tuple('#', '#'), tuple('(', '('), tuple(')', ')'), tuple('`', '`')
];
return doUnescape(arg, pairs);
}
static string unescapeQuotedArgument(string value) nothrow pure
{
static immutable Tuple!(char, char)[] pairs = [
tuple('`', '`'), tuple('$', '$'), tuple('"', '"'), tuple('\\', '\\')
];
return doUnescape(value, pairs);
}
static string[] unquoteExecString(string value) pure
{
import std.uni : isWhite;
string[] result;
size_t i;
while(i < value.length) {
if (isWhite(value[i])) {
i++;
} else if (value[i] == '"' || value[i] == '\'') {
char delimeter = value[i];
size_t start = ++i;
bool inQuotes = true;
bool wasSlash;
while(i < value.length) {
if (value[i] == '\\' && value.length > i+1 && value[i+1] == '\\') {
i+=2;
wasSlash = true;
continue;
}
if (value[i] == delimeter && (value[i-1] != '\\' || (value[i-1] == '\\' && wasSlash) )) {
inQuotes = false;
break;
}
wasSlash = false;
i++;
}
if (inQuotes) {
throw new Exception("Missing pair quote");
}
result ~= unescapeQuotedArgument(value[start..i]);
i++;
} else {
size_t start = i;
while(i < value.length && !isWhite(value[i])) {
i++;
}
result ~= value[start..i];
}
}
return result;
}
static string[] parseExecString(string execString) pure
{
return unquoteExecString(execString).map!(unescapeExecArgument).array;
}
static string[] expandExecArgs(in string[] execArgs, in string[] urls = null, string iconName = null, string name = null, string fileName = null) pure
{
string[] toReturn;
foreach(token; execArgs) {
if (token == "%f") {
if (urls.length) {
toReturn ~= urls.front;
}
} else if (token == "%F") {
toReturn ~= urls;
} else if (token == "%u") {
if (urls.length) {
toReturn ~= urls.front;
}
} else if (token == "%U") {
toReturn ~= urls;
} else if (token == "%i") {
if (iconName.length) {
toReturn ~= "--icon";
toReturn ~= iconName;
}
} else if (token == "%c") {
toReturn ~= name;
} else if (token == "%k") {
toReturn ~= fileName;
} else if (token == "%d" || token == "%D" || token == "%n" || token == "%N" || token == "%m" || token == "%v") {
continue;
} else {
if (token.length >= 2 && token[0] == '%') {
if (token[1] == '%') {
toReturn ~= token[1..$];
} else {
throw new Exception("Unknown field code: " ~ token);
}
} else {
toReturn ~= token;
}
}
}
return toReturn;
}
static bool isExecutable(string program) nothrow
{
import core.sys.posix.unistd;
return access(program.toStringz, X_OK) == 0;
}
static string findExecutable(string program, const(string)[] binPaths) nothrow
{
if (program.isAbsolute && isExecutable(program)) {
return program;
} else if (program.baseName == program) {
foreach(path; binPaths) {
auto candidate = buildPath(path, program);
if (isExecutable(candidate)) {
return candidate;
}
}
}
return null;
}
static string[] findFileManagerCommand(string app, const(string)[] appDirs, const(string)[] binPaths) nothrow
{
foreach(appDir; appDirs) {
bool fileExists;
auto appPath = buildPath(appDir, app);
collectException(appPath.isFile, fileExists);
if (!fileExists) {
//check if file in subdirectory exist. E.g. kde4-dolphin.desktop refers to kde4/dolphin.desktop
auto appSplitted = findSplit(app, "-");
if (appSplitted[1].length && appSplitted[2].length) {
appPath = buildPath(appDir, appSplitted[0], appSplitted[2]);
collectException(appPath.isFile, fileExists);
}
}
if (fileExists) {
try {
bool inDesktopEntry;
bool canOpenDirectory; //not used for now. Some file managers does not have MimeType in their .desktop file.
string exec;
string tryExec;
foreach(line; File(appPath).byLine()) {
if (exec.length || tryExec.length) {
break;
}
if (!line.length || line[0] == '#') {
continue;
} else if (line[0] == '[') {
if (line.equal("[Desktop Entry]")) {
inDesktopEntry = true;
} else {
if (inDesktopEntry) {
break;
}
inDesktopEntry = false;
}
} else if (line[0].isAlpha) {
if (inDesktopEntry) {
auto splitted = findSplit(line, "=");
if (splitted[1].length) {
auto key = splitted[0];
auto value = splitted[2];
if (key.equal("MimeType")) {
canOpenDirectory = value.splitter(';').canFind("inode/directory");
} else if (key.equal("Exec")) {
exec = value.idup;
} else if (key.equal("TryExec")) {
tryExec = value.idup;
}
}
}
} else {
//unexpected line content
break;
}
}
if (tryExec.length) {
auto program = findExecutable(tryExec, binPaths);
if (program.length) {
return [program];
}
}
if (exec.length) {
return expandExecArgs(parseExecString(exec));
}
} catch(Exception e) {
}
}
}
return null;
}
static void execShowInFileManager(string[] fileManagerArgs, string toOpen)
{
toOpen = toOpen.absolutePath();
switch(fileManagerArgs[0].baseName) {
//nautilus and nemo selects item if it's file
case "nautilus":
case "nemo":
fileManagerArgs ~= toOpen;
break;
//dolphin needs --select option
case "dolphin":
case "konqueror":
fileManagerArgs ~= ["--select", toOpen];
break;
default:
{ {
bool pathIsDir; bool pathIsDir;
collectException(toOpen.isDir, pathIsDir); collectException(toOpen.isDir, pathIsDir);
if (!pathIsDir) { if (!pathIsDir) {
toOpen = toOpen.dirName; fileManagerArgs ~= toOpen.dirName;
} else {
fileManagerArgs ~= toOpen;
} }
return ["xdg-open", toOpen]; }
break;
} }
string toOpen = normalized; File devNullOut;
try {
devNullOut = File("/dev/null", "wb");
} catch(Exception) {
devNullOut = std.stdio.stdout;
}
auto p = pipe(); File devNullErr;
spawnProcess(["xdg-mime", "query", "default", "inode/directory"], std.stdio.stdin, p.writeEnd); try {
auto fileManager = p.readEnd.readln().chomp(); devNullErr = File("/dev/null", "wb");
p.close(); } catch(Exception) {
devNullErr = std.stdio.stderr;
}
if (fileManager.length) { File devNullIn;
string[] arguments; try {
devNullIn = File("/dev/null", "rb");
} catch(Exception) {
devNullIn = std.stdio.stdin;
}
switch(fileManager) { spawnProcess(fileManagerArgs, devNullIn, devNullOut, devNullErr);
//nautilus and nemo selects item if it's file }
case "nautilus.desktop":
case "nautilus-classic.desktop": string configHome = environment.get("XDG_CONFIG_HOME", buildPath(environment.get("HOME"), ".config"));
arguments = ["nautilus", toOpen]; string appHome = environment.get("XDG_DATA_HOME", buildPath(environment.get("HOME"), ".local/share")).buildPath("applications");
break;
case "nemo.desktop": auto configDirs = environment.get("XDG_CONFIG_DIRS", "/etc/xdg").splitter(':').find!(p => p.length > 0);
arguments = ["nemo", toOpen]; auto appDirs = environment.get("XDG_DATA_DIRS", "/usr/local/share:/usr/share").splitter(':').find!(p => p.length > 0).map!(p => buildPath(p, "applications"));
break;
//dolphin needs --select option auto allAppDirs = chain(only(appHome), appDirs).array;
case "kde4-dolphin.desktop": auto binPaths = environment.get("PATH").splitter(':').find!(p => p.length > 0).array;
case "kde-dolphin.desktop":
arguments = ["dolphin", "--select", toOpen]; foreach(mimeappsList; chain(only(configHome), only(appHome), configDirs, appDirs).map!(p => buildPath(p, "mimeapps.list"))) {
break; try {
default: bool inDefaultApps;
arguments = getXdgOpen(toOpen); foreach(line; File(mimeappsList).byLine()) {
if (!line.length || line[0] == '#') {
continue;
} else if (line[0] == '[') {
if (line.equal("[Default Applications]")) {
inDefaultApps = true;
} else {
if (inDefaultApps) {
break; break;
} }
spawnProcess(arguments); inDefaultApps = false;
}
} else if (line[0].isAlpha) {
if (inDefaultApps) {
auto keyValue = findSplit(line, "=");
auto key = keyValue[0];
auto value = keyValue[2];
if (keyValue[1].length && key.equal("inode/directory") && value.length) {
auto app = value.idup;
auto fileManagerArgs = findFileManagerCommand(app, allAppDirs, binPaths);
if (fileManagerArgs.length) {
execShowInFileManager(fileManagerArgs, toOpen);
return true; return true;
} else { //xdg-mime returned non-zero. } else {
return false; break;
} }
} }
}
} else {
//unexpected line content
break;
}
}
} catch(Exception e) {
}
}
foreach(mimeinfoCache; allAppDirs.map!(p => buildPath(p, "mimeinfo.cache"))) {
try {
bool inMimeCache;
foreach(line; File(mimeinfoCache).byLine()) {
if (!line.length || line[0] == '#') {
continue;
} else if (line[0] == '[') {
if (line.equal("[MIME Cache]")) {
inMimeCache = true;
} else {
if (inMimeCache) {
break;
}
inMimeCache = false;
}
} else if (line[0].isAlpha) {
if (inMimeCache) {
auto keyValue = findSplit(line, "=");
auto key = keyValue[0];
auto value = keyValue[2];
if (keyValue[1].length && key.equal("inode/directory") && value.length) {
auto alternatives = value.splitter(';').filter!(p => p.length > 0);
foreach(alternative; alternatives) {
auto fileManagerArgs = findFileManagerCommand(alternative.idup, allAppDirs, binPaths);
if (fileManagerArgs.length) {
execShowInFileManager(fileManagerArgs, toOpen);
return true;
}
}
break;
}
}
} else {
//unexpected line content
break;
}
}
} catch(Exception e) {
}
}
Log.e("showInFileManager -- could not find application to open directory");
return false;
}
} catch (Exception e) { } catch (Exception e) {
Log.e("showInFileManager -- exception while trying to open file browser"); Log.e("showInFileManager -- exception while trying to open file browser");
} }