Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign up
Fetching contributors…
| #!/bin/sh | |
| #--------------------------------------------- | |
| # xdg-open | |
| # | |
| # Utility script to open a URL in the registered default application. | |
| # | |
| # Refer to the usage() function below for usage. | |
| # | |
| # Copyright 2009-2010, Fathi Boudra <fabo@freedesktop.org> | |
| # Copyright 2009-2010, Rex Dieter <rdieter@fedoraproject.org> | |
| # Copyright 2006, Kevin Krammer <kevin.krammer@gmx.at> | |
| # Copyright 2006, Jeremy White <jwhite@codeweavers.com> | |
| # | |
| # 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() | |
| { | |
| 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 | |
| } | |
| #------------------------------------------------------------- | |
| # Exit script on successfully completing the desired operation | |
| exit_success() | |
| { | |
| if [ $# -gt 0 ]; then | |
| echo "$@" | |
| echo | |
| fi | |
| exit 0 | |
| } | |
| #----------------------------------------- | |
| # Exit script on malformed arguments, not enough arguments | |
| # or missing required option. | |
| # prints usage information | |
| exit_failure_syntax() | |
| { | |
| if [ $# -gt 0 ]; then | |
| echo "xdg-open: $@" >&2 | |
| echo "Try 'xdg-open --help' for more information." >&2 | |
| else | |
| usage | |
| echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info." | |
| fi | |
| exit 1 | |
| } | |
| #------------------------------------------------------------- | |
| # Exit script on missing file specified on command line | |
| exit_failure_file_missing() | |
| { | |
| 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 | |
| fi | |
| exit 5 | |
| } | |
| #------------------------------------------------------------ | |
| # Exit script on insufficient permission to write a specified file | |
| exit_failure_file_permission_write() | |
| { | |
| if [ $# -gt 0 ]; then | |
| echo "xdg-open: $@" >&2 | |
| fi | |
| exit 6 | |
| } | |
| 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'" | |
| fi | |
| } | |
| check_vendor_prefix() | |
| { | |
| 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 | |
| } | |
| 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 | |
| else | |
| DIR=`dirname "$1"` | |
| if [ ! -w "$DIR" ] || [ ! -x "$DIR" ]; then | |
| exit_failure_file_permission_write "no permission to create file '$1'" | |
| fi | |
| 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() | |
| { | |
| # 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; | |
| ;; | |
| esac | |
| 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 | |
| exit_failure_operation_failed | |
| fi | |
| } | |
| open_kde() | |
| { | |
| if kde-open -v 2>/dev/null 1>&2; then | |
| kde-open "$1" | |
| else | |
| if [ x"$KDE_SESSION_VERSION" = x"4" ]; then | |
| kfmclient openURL "$1" | |
| else | |
| kfmclient exec "$1" | |
| kfmclient_fix_exit_code $? | |
| fi | |
| fi | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| else | |
| exit_failure_operation_failed | |
| fi | |
| } | |
| open_gnome() | |
| { | |
| if gvfs-open --help 2>/dev/null 1>&2; then | |
| gvfs-open "$1" | |
| else | |
| gnome-open "$1" | |
| fi | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| else | |
| exit_failure_operation_failed | |
| fi | |
| } | |
| open_mate() | |
| { | |
| if gvfs-open --help 2>/dev/null 1>&2; then | |
| gvfs-open "$1" | |
| else | |
| mate-open "$1" | |
| fi | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| else | |
| exit_failure_operation_failed | |
| fi | |
| } | |
| open_xfce() | |
| { | |
| exo-open "$1" | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| else | |
| exit_failure_operation_failed | |
| fi | |
| } | |
| #----------------------------------------- | |
| # Recursively search .desktop file | |
| search_desktop_file() | |
| { | |
| local default="$1" | |
| local dir="$2" | |
| local arg="$3" | |
| local file="" | |
| # look for both vendor-app.desktop, vendor/app.desktop | |
| if [ -r "$dir/$default" ]; then | |
| file="$dir/$default" | |
| elif [ -r "$dir/`echo $default | sed -e 's|-|/|'`" ]; then | |
| file="$dir/`echo $default | sed -e 's|-|/|'`" | |
| fi | |
| if [ -r "$file" ] ; then | |
| command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | 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 | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| fi | |
| fi | |
| fi | |
| for d in $dir/*/; do | |
| [ -d "$d" ] && search_desktop_file "$default" "$d" "$arg" | |
| done | |
| } | |
| open_generic_xdg_mime() | |
| { | |
| filetype="$2" | |
| default=`xdg-mime query default "$filetype"` | |
| if [ -n "$default" ] ; then | |
| xdg_user_dir="$XDG_DATA_HOME" | |
| [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" | |
| xdg_system_dirs="$XDG_DATA_DIRS" | |
| [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/ | |
| DEBUG 3 "$xdg_user_dir:$xdg_system_dirs" | |
| for x in `echo "$xdg_user_dir:$xdg_system_dirs" | sed 's/:/ /g'`; do | |
| search_desktop_file "$default" "$x/applications/" "$1" | |
| done | |
| fi | |
| } | |
| open_generic_xdg_file_mime() | |
| { | |
| filetype=`xdg-mime query filetype "$1" | sed "s/;.*//"` | |
| open_generic_xdg_mime "$1" "$filetype" | |
| } | |
| open_generic_xdg_x_scheme_handler() | |
| { | |
| scheme="`echo $1 | sed -n 's/\(^[[:alnum:]+\.-]*\):.*$/\1/p'`" | |
| if [ -n $scheme ]; then | |
| filetype="x-scheme-handler/$scheme" | |
| open_generic_xdg_mime "$1" "$filetype" | |
| fi | |
| } | |
| open_generic() | |
| { | |
| # Paths or file:// URLs | |
| if (echo "$1" | grep -q '^file://' || | |
| ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:'); then | |
| local file="$1" | |
| # 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')")" | |
| fi | |
| check_input_file "$file" | |
| open_generic_xdg_file_mime "$file" | |
| if [ -f /etc/debian_version ] && | |
| 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 | |
| mimeopen -L -n "$file" | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| fi | |
| 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 [ x"$browser_with_arg" = x"$browser" ]; then | |
| eval '$browser $1'$xdg_redirect_output; | |
| else eval '$browser_with_arg'$xdg_redirect_output; | |
| fi | |
| if [ $? -eq 0 ]; then | |
| exit_success; | |
| fi | |
| fi | |
| done | |
| exit_failure_operation_impossible "no method available for opening '$1'" | |
| } | |
| 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://%%')" | |
| # handle relative paths | |
| if ! echo "$file" | grep -q '^/'; then | |
| file="$(pwd)/$file" | |
| fi | |
| pcmanfm "$file" | |
| else | |
| open_generic "$1" | |
| fi | |
| if [ $? -eq 0 ]; then | |
| exit_success | |
| else | |
| exit_failure_operation_failed | |
| fi | |
| } | |
| [ x"$1" != x"" ] || exit_failure_syntax | |
| url= | |
| while [ $# -gt 0 ] ; do | |
| parm="$1" | |
| shift | |
| case "$parm" in | |
| -*) | |
| exit_failure_syntax "unexpected option '$parm'" | |
| ;; | |
| *) | |
| if [ -n "$url" ] ; then | |
| exit_failure_syntax "unexpected argument '$parm'" | |
| fi | |
| url="$parm" | |
| ;; | |
| esac | |
| done | |
| if [ -z "${url}" ] ; then | |
| exit_failure_syntax "file or URL argument missing" | |
| fi | |
| detectDE | |
| if [ x"$DE" = x"" ]; then | |
| DE=generic | |
| 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 | |
| case "$DE" in | |
| kde) | |
| open_kde "$url" | |
| ;; | |
| gnome*) | |
| open_gnome "$url" | |
| ;; | |
| mate) | |
| open_mate "$url" | |
| ;; | |
| xfce) | |
| open_xfce "$url" | |
| ;; | |
| lxde) | |
| open_lxde "$url" | |
| ;; | |
| generic) | |
| open_generic "$url" | |
| ;; | |
| *) | |
| exit_failure_operation_impossible "no method available for opening '$url'" | |
| ;; | |
| esac |