diff --git a/.gitignore b/.gitignore index ce233d3..99e7d2e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ lib-cov *.pid *.gz *.sw* +*.bak pids logs diff --git a/.npmignore b/.npmignore index 5b8e2b5..a7fd76d 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,2 @@ -test -*.sw* +test +*.sw* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f927862 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,26 @@ +language: node_js + +branches: + only: + - master + - /^greenkeeper-.*$/ + - /^greenkeeper/.*$/ + +addons: + firefox: "latest" + +node_js: + - 12 + - 13 + - 14 + - 15 + - lts/* + - node + +os: + - windows + - linux + - osx + +env: + - NODE_ENV=testing diff --git a/LICENSE b/LICENSE index 42719e0..58b5cd2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,22 +1,22 @@ -Copyright (c) 2012 Jay Jordan - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2012 Jay Jordan + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 9768db9..81e6058 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,17 @@ # open +[![Inline docs](http://inch-ci.org/github/HansHammel/node-open.svg?branch=master)](http://inch-ci.org/github/HansHammel/node-open) +[![star this repo](http://githubbadges.com/star.svg?user=HansHammel&repo=node-open&style=flat&color=fff&background=007ec6)](https://github.com/HansHammel/node-open) +[![fork this repo](http://githubbadges.com/fork.svg?user=HansHammel&repo=node-open&style=flat&color=fff&background=007ec6)](https://github.com/HansHammel/node-open/fork) +[![david dependency](https://img.shields.io/david/HansHammel/node-open.svg)](https://david-dm.org/HansHammel/node-open) +[![david devDependency](https://img.shields.io/david/dev/HansHammel/node-open.svg)](https://david-dm.org/HansHammel/node-open) +[![david optionalDependency](https://img.shields.io/david/optional/HansHammel/node-open.svg)](https://david-dm.org/HansHammel/node-open) +[![david peerDependency](https://img.shields.io/david/peer/HansHammel/node-open.svg)](https://david-dm.org/HansHammel/node-open) +[![Known Vulnerabilities](https://snyk.io/test/github/HansHammel/node-open/badge.svg)](https://snyk.io/test/github/HansHammel/node-open) + Open a file or url in the user's preferred application. -# Usage +# Node usage ```javascript var open = require("open"); @@ -16,6 +25,33 @@ file or URL. open("http://www.google.com", "firefox"); ``` +# NPM script usage + +```javascript +{ ... + "scripts: { + ... + "browser": "node-open http://www.google.com", + "firefox": "node-open http://www.google.com firefox", + ... + }, + ... +} + +With development profile + +```javascript +open("http://www.google.com", "firefox", "-P development"); +``` + +Getting Error from program (null if success) + +```javascript +open("http://www.google.com", "firefox", "-P development", function(Error){ + console.log(Error); +}); +``` + # Installation npm install open diff --git a/bin/open b/bin/open new file mode 100644 index 0000000..5758d89 --- /dev/null +++ b/bin/open @@ -0,0 +1,5 @@ +#! /usr/bin/env node +var open = require('../lib/open'); +var url = process.argv[2]; +var browser = process.argv[3]; +open(url, browser); diff --git a/lib/open.js b/lib/open.js index ce4e9aa..2b1ddc1 100644 --- a/lib/open.js +++ b/lib/open.js @@ -1,63 +1,91 @@ -var exec = require('child_process').exec - , path = require('path') - ; - - -/** - * open a file or uri using the default application for the file type. - * - * @return {ChildProcess} - the child process object. - * @param {string} target - the file/uri to open. - * @param {string} appName - (optional) the application to be used to open the - * file (for example, "chrome", "firefox") - * @param {function(Error)} callback - called with null on success, or - * an error object that contains a property 'code' with the exit - * code of the process. - */ - -module.exports = open; - -function open(target, appName, callback) { - var opener; - - if (typeof(appName) === 'function') { - callback = appName; - appName = null; - } - - switch (process.platform) { - case 'darwin': - if (appName) { - opener = 'open -a "' + escape(appName) + '"'; - } else { - opener = 'open'; - } - break; - case 'win32': - // if the first parameter to start is quoted, it uses that as the title - // so we pass a blank title so we can quote the file we are opening - if (appName) { - opener = 'start "" "' + escape(appName) + '"'; - } else { - opener = 'start ""'; - } - break; - default: - if (appName) { - opener = escape(appName); - } else { - // use Portlands xdg-open everywhere else - opener = path.join(__dirname, '../vendor/xdg-open'); - } - break; - } - - if (process.env.SUDO_USER) { - opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener; - } - return exec(opener + ' "' + escape(target) + '"', callback); -} - -function escape(s) { - return s.replace(/"/g, '\\\"'); -} +var exec = require('child_process').exec + , path = require('path') + whereis = function(filename){ + var pathSep = process.platform === 'win32' ? ';' : ':'; + + var directories = process.env.PATH.split(pathSep); + for (var i = 0; i < directories.length; i++) { + var path = directories[i] + '/' + filename; + if (fs.existsSync(path)) { + return path; + } + } + return null; +}; + + +/** + * open a file or uri using the default application for the file type. + * + * @return {ChildProcess} - the child process object. + * @param {string} target - the file/uri to open. + * @param {string} appName - (optional) the application to be used to open the + * file (for example, "chrome", "firefox") + * @param {function(error, stdout, stderr)} callback - called with null on success, or + * an error object that contains a property 'code' with the exit + * code of the process. + */ + +module.exports = open; + +function open(target, appName, args, callback) { + var opener; + + if (typeof(appName) === 'function') { + callback = appName; + appName = null; + } + + if (typeof(args) === 'function') { + callback = args; + args = null; + } + + switch (process.platform) { + case 'darwin': + if (appName) { + opener = 'open -a "' + escape(appName) + '"'; + if (args) { + opener += ' --args '; + } + } else { + opener = 'open'; + } + break; + case 'win32': + // if the first parameter to start is quoted, it uses that as the title + // so we pass a blank title so we can quote the file we are opening + if (appName) { + opener = 'start "" "' + escape(appName) + '"'; + } else { + opener = 'start ""'; + } + break; + default: + if (appName) { + opener = escape(appName); + } else if (whereis('xdg-open')) { + // use default xdg-open if exist + opener = 'xdg-open'; + } else { + // use Portlands xdg-open everywhere else + opener = path.join(__dirname, '../vendor/xdg-open'); + } + break; + } + + if (process.env.SUDO_USER) { + opener = 'sudo -u ' + process.env.SUDO_USER + ' ' + opener; + } + + if (args) { + opener = opener + ' ' + args; + } + + return exec(opener + ' "' + escape(target) + '"', { "shell": true},callback); + +} + +function escape(s) { + return s.replace(/"/g, '\\\"'); +} diff --git a/package.json b/package.json index 46ba0fa..f604533 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,14 @@ { "name": "open", - "version": "0.0.5", + "version": "0.1.1", "description": "open a file or url in the user's preferred application", - "keywords": ["start", "open", "browser", "editor", "default"], + "keywords": [ + "start", + "open", + "browser", + "editor", + "default" + ], "homepage": "https://github.com/jjrdn/node-open", "author": "J Jordan ", "license": "MIT", @@ -19,13 +25,17 @@ "engines": { "node": ">= 0.6.0" }, - "dependencies": {}, + "dependencies": { + }, "devDependencies": { "mocha": "*" }, "optionalDependencies": {}, "main": "lib/open.js", + "bin": { + "node-open": "bin/open" + }, "scripts": { - "test": "node_modules/mocha/bin/mocha" + "test": "mocha" } } diff --git a/test/open.js b/test/open.js index 8b0470d..23ba3ca 100644 --- a/test/open.js +++ b/test/open.js @@ -2,6 +2,7 @@ var path = require('path'); var open = require('../'); +var os = require("os"); // NOTE: this is not really an automated test suite. // It does not check that the applications are actually opened. This needs @@ -17,39 +18,84 @@ var open = require('../'); // process exits. Because of this, the callback parameter is not documented // in the readme at this time. describe('open', function () { + this.timeout(20000); + function pathTo(asset) { return path.join(__dirname, 'support', asset); } it('should open html file in default browser', function (done) { - open(pathTo('asset.html'), done); + var p = open(pathTo('asset.html'), done); + process.on('exit', function () { + p.kill(); + }); }); it('should open https uris in default browser', function (done) { - open('https://github.com/jjrdn/node-open', done); + var p = open('https://github.com/pwnall/node-open', done); + process.on('exit', function () { + p.kill(); + }); }); //it('should open image file in default image viewer', function (done) { - //open(pathTo('asset.jpg'), done); + //var p = open(pathTo('asset.jpg'), done); + //process.on('exit', function () { + // p.kill(); + //}); //}); it('should open txt file in default text editor', function (done) { - open(pathTo('asset.txt'), function (error) { + var p = open(pathTo('asset.txt'), function (error) { console.log('yep editor is open'); done(); }); + process.on('exit', function () { + p.kill(); + }); }); it('should open files with spaces', function (done) { - open(pathTo('with space.html'), done); + var p = open(pathTo('with space.html'), done); + process.on('exit', function () { + p.kill(); + }); }); - it('should open files with quotes', function (done) { - open(pathTo('with"quote.html'), done); - }); - - it('should open files in the specified application', function (done) { - open(pathTo('with space.html'), 'firefox', done); - }); + if (os.platform() == "win32") { + + xit('should open files with quotes', function (done) { + var p = open(pathTo('with\'quote.html'), done); + process.on('exit', function () { + p.kill(); + }); + }); + + xit('should open files in the specified application', function (done) { + var p = open(pathTo('with space.html'), 'firefox', done); + //kill the process if timeout is exceeded to kill all "commandnot found" messages on windows + process.on('exit', function () { + p.kill(); + }); + }); + + } else { + + it('should open files with quotes', function (done) { + var p = open(pathTo('with\'quote.html'), done); + process.on('exit', function () { + p.kill(); + }); + }); + + it('should open files in the specified application', function (done) { + var p = open(pathTo('with space.html'), 'firefox', done); + process.on('exit', function () { + p.kill(); + }); + }); + + } + }); diff --git "a/test/support/with\"quote.html" "b/test/support/with\"quote.html" deleted file mode 100644 index bc49057..0000000 --- "a/test/support/with\"quote.html" +++ /dev/null @@ -1,5 +0,0 @@ - - -

open.js test asset

-

If this is your default browser, it worked. You can close this.

- diff --git a/test/support/with'quote.html b/test/support/with'quote.html new file mode 100644 index 0000000..18c5f7a --- /dev/null +++ b/test/support/with'quote.html @@ -0,0 +1,5 @@ + + +

open.js test asset

+

If this page is opened in Firefox, the test might have worked. If you don't have Firefox configured as your default browser, it definitely worked! You can close this.

+ diff --git a/vendor/xdg-open b/vendor/xdg-open old mode 100755 new mode 100644 index 13caac1..0fbd63d --- a/vendor/xdg-open +++ b/vendor/xdg-open @@ -13,458 +13,128 @@ # # LICENSE: # -# Permission is hereby granted, free of charge, to any person obtaining a -# copy of this software and associated documentation files (the "Software"), -# to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, -# and/or sell copies of the Software, and to permit persons to whom the -# Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR -# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, -# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. -# #--------------------------------------------- manualpage() { cat << _MANUALPAGE -Name - - xdg-open -- opens a file or URL in the user's preferred - application - -Synopsis - - xdg-open { file | URL } - - xdg-open { --help | --manual | --version } - -Description - - xdg-open opens a file or URL in the user's preferred - application. If a URL is provided the URL will be opened in the - user's preferred web browser. If a file is provided the file - will be opened in the preferred application for files of that - type. xdg-open supports file, ftp, http and https URLs. - - xdg-open is for use inside a desktop session only. It is not - recommended to use xdg-open as root. - -Options - - --help - Show command synopsis. - - --manual - Show this manual page. - - --version - Show the xdg-utils version information. - -Exit Codes - - An exit code of 0 indicates success while a non-zero exit code - indicates failure. The following failure codes can be returned: - - 1 - Error in command line syntax. - - 2 - One of the files passed on the command line did not - exist. - - 3 - A required tool could not be found. - - 4 - The action failed. - -Examples - -xdg-open 'http://www.freedesktop.org/' - - Opens the freedesktop.org website in the user's default - browser. - -xdg-open /tmp/foobar.png - - Opens the PNG image file /tmp/foobar.png in the user's default - image viewing application. _MANUALPAGE } usage() { cat << _USAGE - xdg-open -- opens a file or URL in the user's preferred - application - -Synopsis - - xdg-open { file | URL } - - xdg-open { --help | --manual | --version } - _USAGE } #@xdg-utils-common@ -#---------------------------------------------------------------------------- -# Common utility functions included in all XDG wrapper scripts -#---------------------------------------------------------------------------- - -DEBUG() -{ - [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && return 0; - [ ${XDG_UTILS_DEBUG_LEVEL} -lt $1 ] && return 0; - shift - echo "$@" >&2 -} - # This handles backslashes but not quote marks. -first_word() +last_word() { read first rest - echo "$first" -} - -#------------------------------------------------------------- -# map a binary to a .desktop file -binary_to_desktop_file() -{ - search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" - binary="`which "$1"`" - binary="`readlink -f "$binary"`" - base="`basename "$binary"`" - IFS=: - for dir in $search; do - unset IFS - [ "$dir" ] || continue - [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue - for file in "$dir"/applications/*.desktop "$dir"/applications/*/*.desktop "$dir"/applnk/*.desktop "$dir"/applnk/*/*.desktop; do - [ -r "$file" ] || continue - # Check to make sure it's worth the processing. - grep -q "^Exec.*$base" "$file" || continue - # Make sure it's a visible desktop file (e.g. not "preferred-web-browser.desktop"). - grep -Eq "^(NoDisplay|Hidden)=true" "$file" && continue - command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`" - command="`which "$command"`" - if [ x"`readlink -f "$command"`" = x"$binary" ]; then - # Fix any double slashes that got added path composition - echo "$file" | sed -e 's,//*,/,g' - return - fi - done - done -} - -#------------------------------------------------------------- -# map a .desktop file to a binary -## FIXME: handle vendor dir case -desktop_file_to_binary() -{ - search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" - desktop="`basename "$1"`" - IFS=: - for dir in $search; do - unset IFS - [ "$dir" ] && [ -d "$dir/applications" ] || continue - file="$dir/applications/$desktop" - [ -r "$file" ] || continue - # Remove any arguments (%F, %f, %U, %u, etc.). - command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`" - command="`which "$command"`" - readlink -f "$command" - return - done + echo "$rest" } -#------------------------------------------------------------- -# Exit script on successfully completing the desired operation - -exit_success() +# Get the value of a key in a desktop file's Desktop Entry group. +# Example: Use get_key foo.desktop Exec +# to get the values of the Exec= key for the Desktop Entry group. +get_key() { - if [ $# -gt 0 ]; then - echo "$@" - echo - fi + local file="${1}" + local key="${2}" + local desktop_entry="" - exit 0 + IFS_="${IFS}" + IFS="" + while read line + do + case "$line" in + "[Desktop Entry]") + desktop_entry="y" + ;; + # Reset match flag for other groups + "["*) + desktop_entry="" + ;; + "${key}="*) + # Only match Desktop Entry group + if [ -n "${desktop_entry}" ] + then + echo "${line}" | cut -d= -f 2- + fi + esac + done < "${file}" + IFS="${IFS_}" } - -#----------------------------------------- -# Exit script on malformed arguments, not enough arguments -# or missing required option. -# prints usage information - -exit_failure_syntax() +# Returns true if argument is a file:// URL or path +is_file_url_or_path() { - if [ $# -gt 0 ]; then - echo "xdg-open: $@" >&2 - echo "Try 'xdg-open --help' for more information." >&2 + if echo "$1" | grep -q '^file://' \ + || ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:'; then + return 0 else - usage - echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info." + return 1 fi - - exit 1 } -#------------------------------------------------------------- -# Exit script on missing file specified on command line - -exit_failure_file_missing() +# If argument is a file URL, convert it to a (percent-decoded) path. +# If not, leave it as it is. +file_url_to_path() { - if [ $# -gt 0 ]; then - echo "xdg-open: $@" >&2 - fi - - exit 2 -} - -#------------------------------------------------------------- -# Exit script on failure to locate necessary tool applications - -exit_failure_operation_impossible() -{ - if [ $# -gt 0 ]; then - echo "xdg-open: $@" >&2 - fi - - exit 3 -} - -#------------------------------------------------------------- -# Exit script on failure returned by a tool application - -exit_failure_operation_failed() -{ - if [ $# -gt 0 ]; then - echo "xdg-open: $@" >&2 - fi - - exit 4 -} - -#------------------------------------------------------------ -# Exit script on insufficient permission to read a specified file - -exit_failure_file_permission_read() -{ - if [ $# -gt 0 ]; then - echo "xdg-open: $@" >&2 + local file="$1" + if echo "$file" | grep -q '^file:///'; then + file=${file#file://} + file=${file%%#*} + file=$(echo "$file" | sed -r 's/\?.*$//') + local printf=printf + if [ -x /usr/bin/printf ]; then + printf=/usr/bin/printf + fi + file=$($printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')") fi - - exit 5 + echo "$file" } -#------------------------------------------------------------ -# Exit script on insufficient permission to write a specified file - -exit_failure_file_permission_write() +open_cygwin() { - if [ $# -gt 0 ]; then - echo "xdg-open: $@" >&2 - fi - - exit 6 -} + cygstart "$1" -check_input_file() -{ - if [ ! -e "$1" ]; then - exit_failure_file_missing "file '$1' does not exist" - fi - if [ ! -r "$1" ]; then - exit_failure_file_permission_read "no permission to read file '$1'" + if [ $? -eq 0 ]; then + exit_success + else + exit_failure_operation_failed fi } -check_vendor_prefix() +open_darwin() { - file_label="$2" - [ -n "$file_label" ] || file_label="filename" - file=`basename "$1"` - case "$file" in - [a-zA-Z]*-*) - return - ;; - esac - - echo "xdg-open: $file_label '$file' does not have a proper vendor prefix" >&2 - echo 'A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated' >&2 - echo 'with a dash ("-"). An example '"$file_label"' is '"'example-$file'" >&2 - echo "Use --novendor to override or 'xdg-open --manual' for additional info." >&2 - exit 1 -} + open "$1" -check_output_file() -{ - # if the file exists, check if it is writeable - # if it does not exists, check if we are allowed to write on the directory - if [ -e "$1" ]; then - if [ ! -w "$1" ]; then - exit_failure_file_permission_write "no permission to write to file '$1'" - fi + if [ $? -eq 0 ]; then + exit_success else - DIR=`dirname "$1"` - if [ ! -w "$DIR" ] || [ ! -x "$DIR" ]; then - exit_failure_file_permission_write "no permission to create file '$1'" - fi + exit_failure_operation_failed fi } -#---------------------------------------- -# Checks for shared commands, e.g. --help - -check_common_commands() -{ - while [ $# -gt 0 ] ; do - parm="$1" - shift - - case "$parm" in - --help) - usage - echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info." - exit_success - ;; - - --manual) - manualpage - exit_success - ;; - - --version) - echo "xdg-open 1.1.0 rc1" - exit_success - ;; - esac - done -} - -check_common_commands "$@" - -[ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && unset XDG_UTILS_DEBUG_LEVEL; -if [ ${XDG_UTILS_DEBUG_LEVEL-0} -lt 1 ]; then - # Be silent - xdg_redirect_output=" > /dev/null 2> /dev/null" -else - # All output to stderr - xdg_redirect_output=" >&2" -fi - -#-------------------------------------- -# Checks for known desktop environments -# set variable DE to the desktop environments name, lowercase - -detectDE() +open_kde() { - # see https://bugs.freedesktop.org/show_bug.cgi?id=34164 - unset GREP_OPTIONS - - if [ -n "${XDG_CURRENT_DESKTOP}" ]; then - case "${XDG_CURRENT_DESKTOP}" in - GNOME) - DE=gnome; - ;; - KDE) - DE=kde; - ;; - LXDE) - DE=lxde; - ;; - XFCE) - DE=xfce - esac - fi - - if [ x"$DE" = x"" ]; then - # classic fallbacks - if [ x"$KDE_FULL_SESSION" = x"true" ]; then DE=kde; - elif [ x"$GNOME_DESKTOP_SESSION_ID" != x"" ]; then DE=gnome; - elif [ x"$MATE_DESKTOP_SESSION_ID" != x"" ]; then DE=mate; - elif `dbus-send --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.GetNameOwner string:org.gnome.SessionManager > /dev/null 2>&1` ; then DE=gnome; - elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce; - elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce - fi - fi - - if [ x"$DE" = x"" ]; then - # fallback to checking $DESKTOP_SESSION - case "$DESKTOP_SESSION" in - gnome) - DE=gnome; - ;; - LXDE|Lubuntu) - DE=lxde; - ;; - xfce|xfce4|'Xfce Session') - DE=xfce; - ;; - esac - fi - - if [ x"$DE" = x"" ]; then - # fallback to uname output for other platforms - case "$(uname 2>/dev/null)" in - Darwin) - DE=darwin; - ;; + if [ -n "${KDE_SESSION_VERSION}" ]; then + case "${KDE_SESSION_VERSION}" in + 4) + kde-open "$1" + ;; + 5) + kde-open${KDE_SESSION_VERSION} "$1" + ;; esac + else + kfmclient exec "$1" + kfmclient_fix_exit_code $? fi - if [ x"$DE" = x"gnome" ]; then - # gnome-default-applications-properties is only available in GNOME 2.x - # but not in GNOME 3.x - which gnome-default-applications-properties > /dev/null 2>&1 || DE="gnome3" - fi -} - -#---------------------------------------------------------------------------- -# kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4 -# It also always returns 1 in KDE 3.4 and earlier -# Simply return 0 in such case - -kfmclient_fix_exit_code() -{ - version=`LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE'` - major=`echo $version | sed 's/KDE.*: \([0-9]\).*/\1/'` - minor=`echo $version | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/'` - release=`echo $version | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/'` - test "$major" -gt 3 && return $1 - test "$minor" -gt 5 && return $1 - test "$release" -gt 4 && return $1 - return 0 -} - -# This handles backslashes but not quote marks. -first_word() -{ - read first rest - echo "$first" -} - -last_word() -{ - read first rest - echo "$rest" -} - -open_darwin() -{ - open "$1" - if [ $? -eq 0 ]; then exit_success else @@ -472,17 +142,12 @@ open_darwin() fi } -open_kde() +open_gnome3() { - if kde-open -v 2>/dev/null 1>&2; then - kde-open "$1" + if gvfs-open --help >/dev/null 2>&1; then + gvfs-open "$1" else - if [ x"$KDE_SESSION_VERSION" = x"4" ]; then - kfmclient openURL "$1" - else - kfmclient exec "$1" - kfmclient_fix_exit_code $? - fi + open_generic "$1" fi if [ $? -eq 0 ]; then @@ -494,10 +159,12 @@ open_kde() open_gnome() { - if gvfs-open --help 2>/dev/null 1>&2; then + if gvfs-open --help >/dev/null 2>&1; then gvfs-open "$1" - else + elif gnome-open --help >/dev/null 2>&1; then gnome-open "$1" + else + open_generic "$1" fi if [ $? -eq 0 ]; then @@ -533,6 +200,17 @@ open_xfce() fi } +open_enlightenment() +{ + enlightenment_open "$1" + + if [ $? -eq 0 ]; then + exit_success + else + exit_failure_operation_failed + fi +} + #----------------------------------------- # Recursively search .desktop file @@ -540,7 +218,7 @@ search_desktop_file() { local default="$1" local dir="$2" - local arg="$3" + local target="$3" local file="" # look for both vendor-app.desktop, vendor/app.desktop @@ -551,29 +229,56 @@ search_desktop_file() fi if [ -r "$file" ] ; then - command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`" + command="$(get_key "${file}" "Exec" | first_word)" command_exec=`which $command 2>/dev/null` - arguments="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | last_word`" - arg_one="`echo $arg | sed 's/&/\\\\&/g'`" - arguments_exec="`echo $arguments | sed -e 's*%[fFuU]*"'"$arg_one"'"*g'`" - - if [ -x "$command_exec" ] ; then - if echo $arguments | grep -iq '%[fFuU]' ; then - echo START $command_exec $arguments_exec - eval $command_exec $arguments_exec - else - echo START $command_exec $arguments_exec "$arg" - eval $command_exec $arguments_exec "$arg" - fi + icon="$(get_key "${file}" "Icon")" + # FIXME: Actually LC_MESSAGES should be used as described in + # http://standards.freedesktop.org/desktop-entry-spec/latest/ar01s04.html + localised_name="$(get_key "${file}" "Name")" + set -- $(get_key "${file}" "Exec" | last_word) + # We need to replace any occurrence of "%f", "%F" and + # the like by the target file. We examine each + # argument and append the modified argument to the + # end then shift. + local args=$# + local replaced=0 + while [ $args -gt 0 ]; do + case $1 in + %[c]) + replaced=1 + arg="${localised_name}" + shift + set -- "$@" "$arg" + ;; + %[fFuU]) + replaced=1 + arg="$target" + shift + set -- "$@" "$arg" + ;; + %[i]) + replaced=1 + shift + set -- "$@" "--icon" "$icon" + ;; + *) + arg="$1" + shift + set -- "$@" "$arg" + ;; + esac + args=$(( $args - 1 )) + done + [ $replaced -eq 1 ] || set -- "$@" "$target" + "$command_exec" "$@" - if [ $? -eq 0 ]; then - exit_success - fi + if [ $? -eq 0 ]; then + exit_success fi fi for d in $dir/*/; do - [ -d "$d" ] && search_desktop_file "$default" "$d" "$arg" + [ -d "$d" ] && search_desktop_file "$default" "$d" "$target" done } @@ -611,32 +316,51 @@ open_generic_xdg_x_scheme_handler() fi } -open_generic() +open_envvar() { - # Paths or file:// URLs - if (echo "$1" | grep -q '^file://' || - ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:'); then + local oldifs="$IFS" + local browser browser_with_arg - local file="$1" + IFS=":" + for browser in $BROWSER; do + IFS="$oldifs" - # Decode URLs - if echo "$file" | grep -q '^file:///'; then - file=${file#file://} - file="$(printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')")" + if [ -z "$browser" ]; then + continue fi + + if echo "$browser" | grep -q %s; then + $(printf "$browser" "$1") + else + $browser "$1" + fi + + if [ $? -eq 0 ]; then + exit_success + fi + done +} + +open_generic() +{ + if is_file_url_or_path "$1"; then + local file="$(file_url_to_path "$1")" + check_input_file "$file" - open_generic_xdg_file_mime "$file" + if [ -n "$DISPLAY" ]; then + filetype=`xdg-mime query filetype "$file" | sed "s/;.*//"` + open_generic_xdg_mime "$file" "$filetype" + fi - if [ -f /etc/debian_version ] && - which run-mailcap 2>/dev/null 1>&2; then + if which run-mailcap 2>/dev/null 1>&2; then run-mailcap --action=view "$file" if [ $? -eq 0 ]; then exit_success fi fi - if mimeopen -v 2>/dev/null 1>&2; then + if [ -n "$DISPLAY" ] && mimeopen -v 2>/dev/null 1>&2; then mimeopen -L -n "$file" if [ $? -eq 0 ]; then exit_success @@ -644,27 +368,23 @@ open_generic() fi fi - open_generic_xdg_x_scheme_handler "$1" - - IFS=":" - for browser in $BROWSER; do - if [ x"$browser" != x"" ]; then - - browser_with_arg=`printf "$browser" "$1" 2>/dev/null` - if [ $? -ne 0 ]; then - browser_with_arg=$browser; - fi + if [ -n "$BROWSER" ]; then + open_envvar "$1" + fi - if [ x"$browser_with_arg" = x"$browser" ]; then - eval '$browser $1'$xdg_redirect_output; - else eval '$browser_with_arg'$xdg_redirect_output; - fi + if [ -n "$DISPLAY" ]; then + open_generic_xdg_x_scheme_handler "$1" + fi - if [ $? -eq 0 ]; then - exit_success; - fi + # if BROWSER variable is not set, check some well known browsers instead + if [ x"$BROWSER" = x"" ]; then + BROWSER=www-browser:links2:elinks:links:lynx:w3m + if [ -n "$DISPLAY" ]; then + BROWSER=x-www-browser:firefox:iceweasel:seamonkey:mozilla:epiphany:konqueror:chromium-browser:google-chrome:$BROWSER fi - done + fi + + open_envvar "$1" exit_failure_operation_impossible "no method available for opening '$1'" } @@ -672,18 +392,15 @@ open_generic() open_lxde() { # pcmanfm only knows how to handle file:// urls and filepaths, it seems. - if (echo "$1" | grep -q '^file://' || - ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:') - then - local file="$(echo "$1" | sed 's%^file://%%')" + if is_file_url_or_path "$1"; then + local file="$(file_url_to_path "$1")" # handle relative paths - if ! echo "$file" | grep -q '^/'; then + if ! echo "$file" | grep -q ^/; then file="$(pwd)/$file" fi pcmanfm "$file" - else open_generic "$1" fi @@ -728,20 +445,26 @@ fi DEBUG 2 "Selected DE $DE" -# if BROWSER variable is not set, check some well known browsers instead -if [ x"$BROWSER" = x"" ]; then - BROWSER=links2:elinks:links:lynx:w3m - if [ -n "$DISPLAY" ]; then - BROWSER=x-www-browser:firefox:seamonkey:mozilla:epiphany:konqueror:chromium-browser:google-chrome:$BROWSER - fi -fi +# sanitize BROWSER (avoid caling ourselves in particular) +case "${BROWSER}" in + *:"xdg-open"|"xdg-open":*) + BROWSER=$(echo $BROWSER | sed -e 's|:xdg-open||g' -e 's|xdg-open:||g') + ;; + "xdg-open") + BROWSER= + ;; +esac case "$DE" in kde) open_kde "$url" ;; - gnome*) + gnome3|cinnamon) + open_gnome3 "$url" + ;; + + gnome) open_gnome "$url" ;; @@ -757,6 +480,18 @@ case "$DE" in open_lxde "$url" ;; + enlightenment) + open_enlightenment "$url" + ;; + + cygwin) + open_cygwin "$url" + ;; + + darwin) + open_darwin "$url" + ;; + generic) open_generic "$url" ;;