UNPKG

19.6 kBPlain TextView Raw
1#!/bin/sh
2#---------------------------------------------
3# xdg-open
4#
5# Utility script to open a URL in the registered default application.
6#
7# Refer to the usage() function below for usage.
8#
9# Copyright 2009-2010, Fathi Boudra <fabo@freedesktop.org>
10# Copyright 2009-2010, Rex Dieter <rdieter@fedoraproject.org>
11# Copyright 2006, Kevin Krammer <kevin.krammer@gmx.at>
12# Copyright 2006, Jeremy White <jwhite@codeweavers.com>
13#
14# LICENSE:
15#
16# Permission is hereby granted, free of charge, to any person obtaining a
17# copy of this software and associated documentation files (the "Software"),
18# to deal in the Software without restriction, including without limitation
19# the rights to use, copy, modify, merge, publish, distribute, sublicense,
20# and/or sell copies of the Software, and to permit persons to whom the
21# Software is furnished to do so, subject to the following conditions:
22#
23# The above copyright notice and this permission notice shall be included
24# in all copies or substantial portions of the Software.
25#
26# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
27# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
30# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
31# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
32# OTHER DEALINGS IN THE SOFTWARE.
33#
34#---------------------------------------------
35
36manualpage()
37{
38cat << _MANUALPAGE
39Name
40
41xdg-open - opens a file or URL in the user's preferred application
42
43Synopsis
44
45xdg-open { file | URL }
46
47xdg-open { --help | --manual | --version }
48
49Description
50
51xdg-open opens a file or URL in the user's preferred application. If a URL is
52provided the URL will be opened in the user's preferred web browser. If a file
53is provided the file will be opened in the preferred application for files of
54that type. xdg-open supports file, ftp, http and https URLs.
55
56xdg-open is for use inside a desktop session only. It is not recommended to use
57xdg-open as root.
58
59Options
60
61--help
62 Show command synopsis.
63--manual
64 Show this manual page.
65--version
66 Show the xdg-utils version information.
67
68Exit Codes
69
70An exit code of 0 indicates success while a non-zero exit code indicates
71failure. The following failure codes can be returned:
72
731
74 Error in command line syntax.
752
76 One of the files passed on the command line did not exist.
773
78 A required tool could not be found.
794
80 The action failed.
81
82Examples
83
84xdg-open 'http://www.freedesktop.org/'
85
86Opens the freedesktop.org website in the user's default browser.
87
88xdg-open /tmp/foobar.png
89
90Opens the PNG image file /tmp/foobar.png in the user's default image viewing
91application.
92
93_MANUALPAGE
94}
95
96usage()
97{
98cat << _USAGE
99xdg-open - opens a file or URL in the user's preferred application
100
101Synopsis
102
103xdg-open { file | URL }
104
105xdg-open { --help | --manual | --version }
106
107_USAGE
108}
109
110#@xdg-utils-common@
111
112#----------------------------------------------------------------------------
113# Common utility functions included in all XDG wrapper scripts
114#----------------------------------------------------------------------------
115
116DEBUG()
117{
118 [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && return 0;
119 [ ${XDG_UTILS_DEBUG_LEVEL} -lt $1 ] && return 0;
120 shift
121 echo "$@" >&2
122}
123
124# This handles backslashes but not quote marks.
125first_word()
126{
127 read first rest
128 echo "$first"
129}
130
131#-------------------------------------------------------------
132# map a binary to a .desktop file
133binary_to_desktop_file()
134{
135 search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
136 binary="`which "$1"`"
137 binary="`readlink -f "$binary"`"
138 base="`basename "$binary"`"
139 IFS=:
140 for dir in $search; do
141 unset IFS
142 [ "$dir" ] || continue
143 [ -d "$dir/applications" ] || [ -d "$dir/applnk" ] || continue
144 for file in "$dir"/applications/*.desktop "$dir"/applications/*/*.desktop "$dir"/applnk/*.desktop "$dir"/applnk/*/*.desktop; do
145 [ -r "$file" ] || continue
146 # Check to make sure it's worth the processing.
147 grep -q "^Exec.*$base" "$file" || continue
148 # Make sure it's a visible desktop file (e.g. not "preferred-web-browser.desktop").
149 grep -Eq "^(NoDisplay|Hidden)=true" "$file" && continue
150 command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`"
151 command="`which "$command"`"
152 if [ x"`readlink -f "$command"`" = x"$binary" ]; then
153 # Fix any double slashes that got added path composition
154 echo "$file" | sed -e 's,//*,/,g'
155 return
156 fi
157 done
158 done
159}
160
161#-------------------------------------------------------------
162# map a .desktop file to a binary
163## FIXME: handle vendor dir case
164desktop_file_to_binary()
165{
166 search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
167 desktop="`basename "$1"`"
168 IFS=:
169 for dir in $search; do
170 unset IFS
171 [ "$dir" ] && [ -d "$dir/applications" ] || continue
172 file="$dir/applications/$desktop"
173 [ -r "$file" ] || continue
174 # Remove any arguments (%F, %f, %U, %u, etc.).
175 command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`"
176 command="`which "$command"`"
177 readlink -f "$command"
178 return
179 done
180}
181
182#-------------------------------------------------------------
183# Exit script on successfully completing the desired operation
184
185exit_success()
186{
187 if [ $# -gt 0 ]; then
188 echo "$@"
189 echo
190 fi
191
192 exit 0
193}
194
195
196#-----------------------------------------
197# Exit script on malformed arguments, not enough arguments
198# or missing required option.
199# prints usage information
200
201exit_failure_syntax()
202{
203 if [ $# -gt 0 ]; then
204 echo "xdg-open: $@" >&2
205 echo "Try 'xdg-open --help' for more information." >&2
206 else
207 usage
208 echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info."
209 fi
210
211 exit 1
212}
213
214#-------------------------------------------------------------
215# Exit script on missing file specified on command line
216
217exit_failure_file_missing()
218{
219 if [ $# -gt 0 ]; then
220 echo "xdg-open: $@" >&2
221 fi
222
223 exit 2
224}
225
226#-------------------------------------------------------------
227# Exit script on failure to locate necessary tool applications
228
229exit_failure_operation_impossible()
230{
231 if [ $# -gt 0 ]; then
232 echo "xdg-open: $@" >&2
233 fi
234
235 exit 3
236}
237
238#-------------------------------------------------------------
239# Exit script on failure returned by a tool application
240
241exit_failure_operation_failed()
242{
243 if [ $# -gt 0 ]; then
244 echo "xdg-open: $@" >&2
245 fi
246
247 exit 4
248}
249
250#------------------------------------------------------------
251# Exit script on insufficient permission to read a specified file
252
253exit_failure_file_permission_read()
254{
255 if [ $# -gt 0 ]; then
256 echo "xdg-open: $@" >&2
257 fi
258
259 exit 5
260}
261
262#------------------------------------------------------------
263# Exit script on insufficient permission to write a specified file
264
265exit_failure_file_permission_write()
266{
267 if [ $# -gt 0 ]; then
268 echo "xdg-open: $@" >&2
269 fi
270
271 exit 6
272}
273
274check_input_file()
275{
276 if [ ! -e "$1" ]; then
277 exit_failure_file_missing "file '$1' does not exist"
278 fi
279 if [ ! -r "$1" ]; then
280 exit_failure_file_permission_read "no permission to read file '$1'"
281 fi
282}
283
284check_vendor_prefix()
285{
286 file_label="$2"
287 [ -n "$file_label" ] || file_label="filename"
288 file=`basename "$1"`
289 case "$file" in
290 [[:alpha:]]*-*)
291 return
292 ;;
293 esac
294
295 echo "xdg-open: $file_label '$file' does not have a proper vendor prefix" >&2
296 echo 'A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated' >&2
297 echo 'with a dash ("-"). An example '"$file_label"' is '"'example-$file'" >&2
298 echo "Use --novendor to override or 'xdg-open --manual' for additional info." >&2
299 exit 1
300}
301
302check_output_file()
303{
304 # if the file exists, check if it is writeable
305 # if it does not exists, check if we are allowed to write on the directory
306 if [ -e "$1" ]; then
307 if [ ! -w "$1" ]; then
308 exit_failure_file_permission_write "no permission to write to file '$1'"
309 fi
310 else
311 DIR=`dirname "$1"`
312 if [ ! -w "$DIR" ] || [ ! -x "$DIR" ]; then
313 exit_failure_file_permission_write "no permission to create file '$1'"
314 fi
315 fi
316}
317
318#----------------------------------------
319# Checks for shared commands, e.g. --help
320
321check_common_commands()
322{
323 while [ $# -gt 0 ] ; do
324 parm="$1"
325 shift
326
327 case "$parm" in
328 --help)
329 usage
330 echo "Use 'man xdg-open' or 'xdg-open --manual' for additional info."
331 exit_success
332 ;;
333
334 --manual)
335 manualpage
336 exit_success
337 ;;
338
339 --version)
340 echo "xdg-open 1.1.0 rc1"
341 exit_success
342 ;;
343 esac
344 done
345}
346
347check_common_commands "$@"
348
349[ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && unset XDG_UTILS_DEBUG_LEVEL;
350if [ ${XDG_UTILS_DEBUG_LEVEL-0} -lt 1 ]; then
351 # Be silent
352 xdg_redirect_output=" > /dev/null 2> /dev/null"
353else
354 # All output to stderr
355 xdg_redirect_output=" >&2"
356fi
357
358#--------------------------------------
359# Checks for known desktop environments
360# set variable DE to the desktop environments name, lowercase
361
362detectDE()
363{
364 # see https://bugs.freedesktop.org/show_bug.cgi?id=34164
365 unset GREP_OPTIONS
366
367 if [ -n "${XDG_CURRENT_DESKTOP}" ]; then
368 case "${XDG_CURRENT_DESKTOP}" in
369 ENLIGHTENMENT)
370 DE=enlightenment;
371 ;;
372 GNOME)
373 DE=gnome;
374 ;;
375 KDE)
376 DE=kde;
377 ;;
378 LXDE)
379 DE=lxde;
380 ;;
381 MATE)
382 DE=mate;
383 ;;
384 XFCE)
385 DE=xfce
386 ;;
387 esac
388 fi
389
390 if [ x"$DE" = x"" ]; then
391 # classic fallbacks
392 if [ x"$KDE_FULL_SESSION" = x"true" ]; then DE=kde;
393 elif [ x"$GNOME_DESKTOP_SESSION_ID" != x"" ]; then DE=gnome;
394 elif [ x"$MATE_DESKTOP_SESSION_ID" != x"" ]; then DE=mate;
395 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;
396 elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce;
397 elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce
398 elif echo $DESKTOP | grep -q '^Enlightenment'; then DE=enlightenment;
399 fi
400 fi
401
402 if [ x"$DE" = x"" ]; then
403 # fallback to checking $DESKTOP_SESSION
404 case "$DESKTOP_SESSION" in
405 gnome)
406 DE=gnome;
407 ;;
408 LXDE|Lubuntu)
409 DE=lxde;
410 ;;
411 MATE)
412 DE=mate;
413 ;;
414 xfce|xfce4|'Xfce Session')
415 DE=xfce;
416 ;;
417 esac
418 fi
419
420 if [ x"$DE" = x"" ]; then
421 # fallback to uname output for other platforms
422 case "$(uname 2>/dev/null)" in
423 Darwin)
424 DE=darwin;
425 ;;
426 esac
427 fi
428
429 if [ x"$DE" = x"gnome" ]; then
430 # gnome-default-applications-properties is only available in GNOME 2.x
431 # but not in GNOME 3.x
432 which gnome-default-applications-properties > /dev/null 2>&1 || DE="gnome3"
433 fi
434}
435
436#----------------------------------------------------------------------------
437# kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4
438# It also always returns 1 in KDE 3.4 and earlier
439# Simply return 0 in such case
440
441kfmclient_fix_exit_code()
442{
443 version=`LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE'`
444 major=`echo $version | sed 's/KDE.*: \([0-9]\).*/\1/'`
445 minor=`echo $version | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/'`
446 release=`echo $version | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/'`
447 test "$major" -gt 3 && return $1
448 test "$minor" -gt 5 && return $1
449 test "$release" -gt 4 && return $1
450 return 0
451}
452
453# This handles backslashes but not quote marks.
454first_word()
455{
456 read first rest
457 echo "$first"
458}
459
460last_word()
461{
462 read first rest
463 echo "$rest"
464}
465
466open_darwin()
467{
468 open "$1"
469
470 if [ $? -eq 0 ]; then
471 exit_success
472 else
473 exit_failure_operation_failed
474 fi
475}
476
477open_kde()
478{
479 if kde-open -v 2>/dev/null 1>&2; then
480 kde-open "$1"
481 else
482 if [ x"$KDE_SESSION_VERSION" = x"4" ]; then
483 kfmclient openURL "$1"
484 else
485 kfmclient exec "$1"
486 kfmclient_fix_exit_code $?
487 fi
488 fi
489
490 if [ $? -eq 0 ]; then
491 exit_success
492 else
493 exit_failure_operation_failed
494 fi
495}
496
497open_gnome()
498{
499 if gvfs-open --help 2>/dev/null 1>&2; then
500 gvfs-open "$1"
501 else
502 gnome-open "$1"
503 fi
504
505 if [ $? -eq 0 ]; then
506 exit_success
507 else
508 exit_failure_operation_failed
509 fi
510}
511
512open_mate()
513{
514 if gvfs-open --help 2>/dev/null 1>&2; then
515 gvfs-open "$1"
516 else
517 mate-open "$1"
518 fi
519
520 if [ $? -eq 0 ]; then
521 exit_success
522 else
523 exit_failure_operation_failed
524 fi
525}
526
527open_xfce()
528{
529 exo-open "$1"
530
531 if [ $? -eq 0 ]; then
532 exit_success
533 else
534 exit_failure_operation_failed
535 fi
536}
537
538open_enlightenment()
539{
540 enlightenment_open "$1"
541
542 if [ $? -eq 0 ]; then
543 exit_success
544 else
545 exit_failure_operation_failed
546 fi
547}
548
549#-----------------------------------------
550# Recursively search .desktop file
551
552search_desktop_file()
553{
554 local default="$1"
555 local dir="$2"
556 local arg="$3"
557
558 local file=""
559 # look for both vendor-app.desktop, vendor/app.desktop
560 if [ -r "$dir/$default" ]; then
561 file="$dir/$default"
562 elif [ -r "$dir/`echo $default | sed -e 's|-|/|'`" ]; then
563 file="$dir/`echo $default | sed -e 's|-|/|'`"
564 fi
565
566 if [ -r "$file" ] ; then
567 command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`"
568 command_exec=`which $command 2>/dev/null`
569 arguments="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | last_word`"
570 arg_one="`echo "$arg" | sed 's/[&*\\]/\\\\&/g'`"
571 arguments_exec="`echo "$arguments" | sed -e 's*%[fFuU]*"'"$arg_one"'"*g'`"
572
573 if [ -x "$command_exec" ] ; then
574 if echo "$arguments" | grep -iq '%[fFuU]' ; then
575 echo START "$command_exec" "$arguments_exec"
576 eval "$command_exec" "$arguments_exec"
577 else
578 echo START "$command_exec" "$arguments_exec" "$arg"
579 eval "$command_exec" "$arguments_exec" "$arg"
580 fi
581
582 if [ $? -eq 0 ]; then
583 exit_success
584 fi
585 fi
586 fi
587
588 for d in $dir/*/; do
589 [ -d "$d" ] && search_desktop_file "$default" "$d" "$arg"
590 done
591}
592
593
594open_generic_xdg_mime()
595{
596 filetype="$2"
597 default=`xdg-mime query default "$filetype"`
598 if [ -n "$default" ] ; then
599 xdg_user_dir="$XDG_DATA_HOME"
600 [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share"
601
602 xdg_system_dirs="$XDG_DATA_DIRS"
603 [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/
604
605DEBUG 3 "$xdg_user_dir:$xdg_system_dirs"
606 for x in `echo "$xdg_user_dir:$xdg_system_dirs" | sed 's/:/ /g'`; do
607 search_desktop_file "$default" "$x/applications/" "$1"
608 done
609 fi
610}
611
612open_generic_xdg_file_mime()
613{
614 filetype=`xdg-mime query filetype "$1" | sed "s/;.*//"`
615 open_generic_xdg_mime "$1" "$filetype"
616}
617
618open_generic_xdg_x_scheme_handler()
619{
620 scheme="`echo $1 | sed -n 's/\(^[[:alnum:]+\.-]*\):.*$/\1/p'`"
621 if [ -n $scheme ]; then
622 filetype="x-scheme-handler/$scheme"
623 open_generic_xdg_mime "$1" "$filetype"
624 fi
625}
626
627open_generic()
628{
629 # Paths or file:// URLs
630 if (echo "$1" | grep -q '^file://' ||
631 ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:'); then
632
633 local file="$1"
634
635 # Decode URLs
636 if echo "$file" | grep -q '^file:///'; then
637 file=${file#file://}
638 file="$(printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')")"
639 fi
640 check_input_file "$file"
641
642 open_generic_xdg_file_mime "$file"
643
644 if which run-mailcap 2>/dev/null 1>&2; then
645 run-mailcap --action=view "$file"
646 if [ $? -eq 0 ]; then
647 exit_success
648 fi
649 fi
650
651 if mimeopen -v 2>/dev/null 1>&2; then
652 mimeopen -L -n "$file"
653 if [ $? -eq 0 ]; then
654 exit_success
655 fi
656 fi
657 fi
658
659 open_generic_xdg_x_scheme_handler "$1"
660
661 IFS=":"
662 for browser in $BROWSER; do
663 if [ x"$browser" != x"" ]; then
664
665 browser_with_arg=`printf "$browser" "$1" 2>/dev/null`
666 if [ $? -ne 0 ]; then
667 browser_with_arg=$browser;
668 fi
669
670 if [ x"$browser_with_arg" = x"$browser" ]; then
671 eval '$browser "$1"'$xdg_redirect_output;
672 else eval '$browser_with_arg'$xdg_redirect_output;
673 fi
674
675 if [ $? -eq 0 ]; then
676 exit_success;
677 fi
678 fi
679 done
680
681 exit_failure_operation_impossible "no method available for opening '$1'"
682}
683
684open_lxde()
685{
686 # pcmanfm only knows how to handle file:// urls and filepaths, it seems.
687 if (echo "$1" | grep -q '^file://' ||
688 ! echo "$1" | egrep -q '^[[:alpha:]+\.\-]+:')
689 then
690 local file="$(echo "$1" | sed 's%^file://%%')"
691
692 # handle relative paths
693 if ! echo "$file" | grep -q '^/'; then
694 file="$(pwd)/$file"
695 fi
696
697 pcmanfm "$file"
698
699 else
700 open_generic "$1"
701 fi
702
703 if [ $? -eq 0 ]; then
704 exit_success
705 else
706 exit_failure_operation_failed
707 fi
708}
709
710[ x"$1" != x"" ] || exit_failure_syntax
711
712url=
713while [ $# -gt 0 ] ; do
714 parm="$1"
715 shift
716
717 case "$parm" in
718 -*)
719 exit_failure_syntax "unexpected option '$parm'"
720 ;;
721
722 *)
723 if [ -n "$url" ] ; then
724 exit_failure_syntax "unexpected argument '$parm'"
725 fi
726 url="$parm"
727 ;;
728 esac
729done
730
731if [ -z "${url}" ] ; then
732 exit_failure_syntax "file or URL argument missing"
733fi
734
735detectDE
736
737if [ x"$DE" = x"" ]; then
738 DE=generic
739fi
740
741DEBUG 2 "Selected DE $DE"
742
743# sanitize BROWSER (avoid caling ourselves in particular)
744case "${BROWSER}" in
745 *:"xdg-open"|"xdg-open":*)
746 BROWSER=$(echo $BROWSER | sed -e 's|:xdg-open||g' -e 's|xdg-open:||g')
747 ;;
748 "xdg-open")
749 BROWSER=
750 ;;
751esac
752
753# if BROWSER variable is not set, check some well known browsers instead
754if [ x"$BROWSER" = x"" ]; then
755 BROWSER=links2:elinks:links:lynx:w3m
756 if [ -n "$DISPLAY" ]; then
757 BROWSER=x-www-browser:firefox:seamonkey:mozilla:epiphany:konqueror:chromium-browser:google-chrome:$BROWSER
758 fi
759fi
760
761case "$DE" in
762 kde)
763 open_kde "$url"
764 ;;
765
766 gnome*)
767 open_gnome "$url"
768 ;;
769
770 mate)
771 open_mate "$url"
772 ;;
773
774 xfce)
775 open_xfce "$url"
776 ;;
777
778 lxde)
779 open_lxde "$url"
780 ;;
781
782 enlightenment)
783 open_enlightenment "$url"
784 ;;
785
786 generic)
787 open_generic "$url"
788 ;;
789
790 *)
791 exit_failure_operation_impossible "no method available for opening '$url'"
792 ;;
793esac