diff --git a/README.md b/README.md index 19c3ab2..1a2c2e7 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ This should start command line application in terminal emulator (will be detecte Additional application actions are supported too: dub examples/util.d exec /usr/share/applications/steam.desktop --action=Settings + dub examples/util.d exec /usr/share/applications/qpdfview.desktop --action NonUniqueInstance /path/to/pdf/file Running of multiple application instances if it does not support handling multiple urls: @@ -132,7 +133,7 @@ Open a link with preferred application: dub examples/util.d open /usr/share/desktop-base/debian-homepage.desktop -Looks up the .desktop file type and executes it if it's an application or opens a link if it's a link. +Look up the .desktop file type and executes it if it's an application or opens a link if it's a link. dub examples/util.d start /path/to/file.desktop @@ -144,7 +145,7 @@ Read basic information about desktop file: dub examples/util.d read /usr/share/applications/kde4/kate.desktop -When passing base name of desktop file instead of path it's treated like desktop file id and desktop file is searched in system applications paths. +When passing base name of desktop file instead of path it's treated as a desktop file id and desktop file is searched in system applications paths. dub examples/util.d exec python2.7.desktop dub examples/util.d exec kde4-kate.desktop @@ -188,7 +189,7 @@ Uses the alternative way of starting desktop file. Instead of constructing Deskt dub examples/fire.d python2.7.desktop dub examples/fire.d geany.desktop dub.json -Running of multiple application instances if it does not support handling multiple urls: +Running multiple application instances if it does not support handling multiple urls: dub examples/fire.d leafpad.desktop dub.json README.md diff --git a/examples/util.d b/examples/util.d index caab03d..01cc2d1 100644 --- a/examples/util.d +++ b/examples/util.d @@ -27,7 +27,7 @@ void main(string[] args) string action; string[] appPaths; getopt(args, - "action", "Action to run", &action, + "action", "Desktop Action to run (only with 'exec' command)", &action, "appPath", "Path of applications directory", &appPaths ); @@ -95,9 +95,12 @@ void main(string[] args) if (action.length) { auto desktopAction = df.action(action); if (desktopAction is null) { - stderr.writefln("No such action %s", action); + stderr.writefln("No such action (%s)", action); } else { - desktopAction.start(); + string[] urls = args[3..$]; + string[] actionArgs = desktopAction.expandExecValue(urls, locale); + writefln("Exec: %(%s %)", actionArgs); + desktopAction.start(urls, locale); } } else { string[] urls = args[3..$]; diff --git a/source/desktopfile/file.d b/source/desktopfile/file.d index 7ad771a..832eefd 100644 --- a/source/desktopfile/file.d +++ b/source/desktopfile/file.d @@ -76,21 +76,44 @@ public: } /** - * Start this action. + * Expand "Exec" value into the array of command line arguments to use to start the action. + * It applies unquoting and unescaping. + * See_Also: $(D execValue), $(D desktopfile.utils.expandExecArgs), $(D start) + */ + @safe string[] expandExecValue(in string[] urls = null, string locale = null) const + { + return expandExecArgs(unquoteExec(execValue()), urls, localizedIconName(locale), localizedDisplayName(locale)); + } + + /** + * Start this action with provided urls. * Throws: * $(B ProcessException) on failure to start the process. * $(D desktopfile.utils.DesktopExecException) if exec string is invalid. - * See_Also: $(D execValue) + * See_Also: $(D execValue), $(D desktopfile.utils.spawnApplication) */ - @safe void start(string locale = null) const { + @safe void start(in string[] urls, string locale = null) const + { auto unquotedArgs = unquoteExec(execValue()); SpawnParams params; + params.urls = urls; params.iconName = localizedIconName(locale); params.displayName = localizedDisplayName(locale); return spawnApplication(unquotedArgs, params); } + + /// ditto, but using a single url + @safe void start(string url, string locale) const + { + return start([url], locale); + } + + /// ditto, but without any urls. + @safe void start(string locale = null) const { + return start(string[].init, locale); + } } /** @@ -722,6 +745,7 @@ public: Key=Value Actions=Action1; [Desktop Action Action1] +Name=Action1 Name Key=Value`; alias DesktopFile.DesktopReadOptions DesktopReadOptions; @@ -852,13 +876,13 @@ public: } /** - * Constructs DesktopFile with "Desktop Entry" group and Version set to 1.0 + * Constructs DesktopFile with "Desktop Entry" group and Version set to 1.1 */ @safe this() { super(); _desktopEntry = new DesktopEntry(); insertGroup(_desktopEntry); - _desktopEntry.setEscapedValue("Version", "1.0"); + _desktopEntry.setEscapedValue("Version", "1.1"); } /// @@ -866,7 +890,7 @@ public: { auto df = new DesktopFile(); assert(df.desktopEntry()); - assert(df.desktopEntry().escapedValue("Version") == "1.0"); + assert(df.desktopEntry().escapedValue("Version") == "1.1"); assert(df.categories().empty); assert(df.type() == DesktopFile.Type.Unknown); } @@ -919,7 +943,7 @@ public: static if (isFreedesktop) { /** - * See $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/ape.html, Desktop File ID) + * See $(LINK2 https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s02.html#desktop-file-id, Desktop File ID) * Returns: Desktop file ID or empty string if file does not have an ID. * Note: This function retrieves applications paths each time it's called and therefore can impact performance. To avoid this issue use overload with argument. * See_Also: $(D desktopfile.paths.applicationsPaths), $(D desktopfile.utils.desktopId) @@ -941,7 +965,7 @@ public: } /** - * See $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/ape.html, Desktop File ID) + * See $(LINK2 https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s02.html#desktop-file-id, Desktop File ID) * Params: * appPaths = range of base application paths to check if this file belongs to one of them. * Returns: Desktop file ID or empty string if file does not have an ID. @@ -1077,7 +1101,7 @@ Type=Directory`; } /** - * Get $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s10.html, additional application action) by name. + * Get $(LINK2 https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s11.html, additional application action) by name. * Returns: $(D DesktopAction) with given action name or null if not found or found section does not have a name. * See_Also: $(D actions), $(D byAction) */ @@ -1144,14 +1168,14 @@ Icon[ru]=folder_ru`; * If the program should be run in terminal it tries to find system defined terminal emulator to run in. * Params: * urls = urls application will start with. - * locale = locale that may be needed to be placed in urls if Exec value has %c code. - * terminalCommand = preferable terminal emulator command. If not set then terminal is determined via getTerminalCommand. + * locale = locale that may be needed to be placed in params if Exec value has %c code. + * terminalCommand = preferable terminal emulator command. If not set then terminal is determined via $(D desktopfile.utils.getTerminalCommand). * Note: * This function does not check if the type of desktop file is Application. It relies only on "Exec" value. * Throws: - * ProcessException on failure to start the process. + * $(B ProcessException) on failure to start the process. * $(D desktopfile.utils.DesktopExecException) if exec string is invalid. - * See_Also: $(D desktopfile.utils.getTerminalCommand), $(D start), $(D expandExecValue) + * See_Also: $(D desktopfile.utils.spawnApplication), $(D desktopfile.utils.getTerminalCommand), $(D start), $(D expandExecValue) */ @trusted void startApplication(in string[] urls = null, string locale = null, lazy const(string)[] terminalCommand = getTerminalCommand) const { @@ -1201,8 +1225,8 @@ Icon[ru]=folder_ru`; * Note: * This function does not check if the type of desktop file is Link. It relies only on "URL" value. * Throws: - * ProcessException on failure to start the process. - * Exception if desktop file does not define URL or it's empty. + * $(B ProcessException) on failure to start the process. + * $(B Exception) if desktop file does not define URL or it's empty. * See_Also: $(D start) */ @trusted void startLink() const { @@ -1221,8 +1245,10 @@ Icon[ru]=folder_ru`; /** * Starts application or open link depending on desktop entry type. * Throws: - * ProcessException on failure to start the process. - * Exception if type is $(D DesktopEntry.Type.Unknown) or $(D DesktopEntry.Type.Directory). + * $(B ProcessException) on failure to start the process. + * $(D desktopfile.utils.DesktopExecException) if type is $(D DesktopEntry.Type.Application) and the exec string is invalid. + * $(B Exception) if type is $(D DesktopEntry.Type.Unknown) or $(D DesktopEntry.Type.Directory), + * or if type is $(D DesktopEntry.Type.Link), but no url provided. * See_Also: $(D startApplication), $(D startLink) */ @trusted void start() const @@ -1339,6 +1365,10 @@ Name=Notspecified Action`; tuple(desktopAction.displayName(), desktopAction.localizedDisplayName("ru"), desktopAction.iconName(), desktopAction.execValue())), [tuple("Open directory", "Открыть папку", "open", "doublecmd %u"), tuple("Settings", "Настройки", "edit", "doublecmd settings")])); + DesktopAction desktopAction = df.action("OpenDirectory"); + assert(desktopAction !is null); + assert(desktopAction.expandExecValue(["path/to/file"]) == ["doublecmd", "path/to/file"]); + assert(df.action("NotPresented") is null); assert(df.action("Notspecified") is null); assert(df.action("X-NoName") is null); diff --git a/source/desktopfile/utils.d b/source/desktopfile/utils.d index 8c903e7..f37da76 100644 --- a/source/desktopfile/utils.d +++ b/source/desktopfile/utils.d @@ -89,11 +89,11 @@ class DesktopExecException : Exception } /** - * Parameters for spawnApplication. + * Parameters for $(D spawnApplication). */ struct SpawnParams { - /// Urls of file paths to open + /// Urls or file paths to open const(string)[] urls; /// Icon to use in place of %i field code. @@ -212,7 +212,7 @@ private @trusted string escapeQuotedArgument(string value) pure { * Note: * Although Desktop Entry Specification says that arguments must be quoted by double quote, for compatibility reasons this implementation also recognizes single quotes. * See_Also: - * $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html, specification) + * $(LINK2 https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html, specification) */ @trusted auto unquoteExec(string unescapedValue) pure { @@ -850,8 +850,6 @@ struct FireOptions bool allowMultipleInstances = true; } -deprecated("Use FireOptions") alias FireOptions ShootOptions; - package bool readDesktopEntryValues(IniLikeReader)(IniLikeReader reader, string locale, string fileName, out string iconName, out string name, out string execValue, out string url, @@ -927,7 +925,7 @@ unittest * $(B ProcessException) on failure to start the process. * $(D DesktopExecException) if exec string is invalid. * $(B Exception) on other errors. - * See_Also: $(D FireOptions) + * See_Also: $(D FireOptions), $(D spawnApplication), $(D getTerminalCommand) */ void fireDesktopFile(IniLikeReader)(IniLikeReader reader, string fileName = null, FireOptions options = FireOptions.init) { @@ -1057,10 +1055,8 @@ unittest fireDesktopFile(iniLikeFileReader(fileName), fileName, options); } -deprecated("Use fireDesktopFile") alias fireDesktopFile shootDesktopFile; - /** - * See $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/ape.html, Desktop File ID) + * See $(LINK2 https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s02.html#desktop-file-id, Desktop File ID) * Params: * fileName = Desktop file. * appsPaths = Range of base application paths. @@ -1117,7 +1113,7 @@ unittest static if (isFreedesktop) { /** - * See $(LINK2 http://standards.freedesktop.org/desktop-entry-spec/latest/ape.html, Desktop File ID) + * See $(LINK2 https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s02.html#desktop-file-id, Desktop File ID) * Returns: Desktop file ID or empty string if file does not have an ID. * Params: * fileName = Desktop file. @@ -1191,7 +1187,7 @@ static if (isFreedesktop) * * This is not actually part of Desktop File Specification but many desktop envrionments have this concept. * The trusted .desktop file is a file the current user has executable access on or the owner of which is root. - * This function should be applicable only to desktop files of $(D DesktopEntry.Type.Application) type. + * This function should be applicable only to desktop files of $(D desktopfile.file.DesktopEntry.Type.Application) type. * Note: Always returns true on non-posix systems. */ @trusted bool isTrusted(string appFileName) nothrow