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 manualpage. 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" -o -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" -a -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 [a-zA-Z]*-*) 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" -o ! -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 GNOME) 370 DE=gnome; 371 ;; 372 KDE) 373 DE=kde; 374 ;; 375 LXDE) 376 DE=lxde; 377 ;; 378 XFCE) 379 DE=xfce 380 esac 381 fi 382 383 if [ x"$DE" = x"" ]; then 384 # classic fallbacks 385 if [ x"$KDE_FULL_SESSION" = x"true" ]; then DE=kde; 386 elif [ x"$GNOME_DESKTOP_SESSION_ID" != x"" ]; then DE=gnome; 387 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; 388 elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce; 389 elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce 390 fi 391 fi 392 393 if [ x"$DE" = x"" ]; then 394 # fallback to checking $DESKTOP_SESSION 395 case "$DESKTOP_SESSION" in 396 gnome) 397 DE=gnome; 398 ;; 399 LXDE) 400 DE=lxde; 401 ;; 402 xfce|xfce4) 403 DE=xfce; 404 ;; 405 esac 406 fi 407 408 if [ x"$DE" = x"" ]; then 409 # fallback to uname output for other platforms 410 case "$(uname 2>/dev/null)" in 411 Darwin) 412 DE=darwin; 413 ;; 414 esac 415 fi 416 417 if [ x"$DE" = x"gnome" ]; then 418 # gnome-default-applications-properties is only available in GNOME 2.x 419 # but not in GNOME 3.x 420 which gnome-default-applications-properties > /dev/null 2>&1 || DE="gnome3" 421 fi 422} 423 424#---------------------------------------------------------------------------- 425# kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4 426# It also always returns 1 in KDE 3.4 and earlier 427# Simply return 0 in such case 428 429kfmclient_fix_exit_code() 430{ 431 version=`LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE'` 432 major=`echo $version | sed 's/KDE.*: \([0-9]\).*/\1/'` 433 minor=`echo $version | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/'` 434 release=`echo $version | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/'` 435 test "$major" -gt 3 && return $1 436 test "$minor" -gt 5 && return $1 437 test "$release" -gt 4 && return $1 438 return 0 439} 440 441# This handles backslashes but not quote marks. 442first_word() 443{ 444 read first rest 445 echo "$first" 446} 447 448last_word() 449{ 450 read first rest 451 echo "$rest" 452} 453 454open_darwin() 455{ 456 open "$1" 457 458 if [ $? -eq 0 ]; then 459 exit_success 460 else 461 exit_failure_operation_failed 462 fi 463} 464 465open_kde() 466{ 467 if kde-open -v 2>/dev/null 1>&2; then 468 kde-open "$1" 469 else 470 if [ x"$KDE_SESSION_VERSION" = x"4" ]; then 471 kfmclient openURL "$1" 472 else 473 kfmclient exec "$1" 474 kfmclient_fix_exit_code $? 475 fi 476 fi 477 478 if [ $? -eq 0 ]; then 479 exit_success 480 else 481 exit_failure_operation_failed 482 fi 483} 484 485open_gnome() 486{ 487 if gvfs-open --help 2>/dev/null 1>&2; then 488 gvfs-open "$1" 489 else 490 gnome-open "$1" 491 fi 492 493 if [ $? -eq 0 ]; then 494 exit_success 495 else 496 exit_failure_operation_failed 497 fi 498} 499 500open_xfce() 501{ 502 exo-open "$1" 503 504 if [ $? -eq 0 ]; then 505 exit_success 506 else 507 exit_failure_operation_failed 508 fi 509} 510 511open_generic_xdg_mime() 512{ 513 filetype=`xdg-mime query filetype "$1" | sed "s/;.*//"` 514 default=`xdg-mime query default "$filetype"` 515 if [ -n "$default" ] ; then 516 xdg_user_dir="$XDG_DATA_HOME" 517 [ -n "$xdg_user_dir" ] || xdg_user_dir="$HOME/.local/share" 518 519 xdg_system_dirs="$XDG_DATA_DIRS" 520 [ -n "$xdg_system_dirs" ] || xdg_system_dirs=/usr/local/share/:/usr/share/ 521 522 for x in `echo "$xdg_user_dir:$xdg_system_dirs" | sed 's/:/ /g'`; do 523 local file 524 # look for both vendor-app.desktop, vendor/app.desktop 525 if [ -r "$x/applications/$default" ]; then 526 file="$x/applications/$default" 527 elif [ -r "$x/applications/`echo $default | sed -e 's|-|/|'`" ]; then 528 file="$x/applications/`echo $default | sed -e 's|-|/|'`" 529 fi 530 531 if [ -r "$file" ] ; then 532 command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`" 533 command_exec=`which $command 2>/dev/null` 534 arguments="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | last_word`" 535 arguments_exec="`echo $arguments | sed -e 's*%[fFuU]*"'"$1"'"*g'`" 536 if [ -x "$command_exec" ] ; then 537 if echo $arguments | grep -iq '%[fFuU]' ; then 538 eval $command_exec $arguments_exec 539 else 540 eval $command_exec $arguments_exec "$1" 541 fi 542 543 if [ $? -eq 0 ]; then 544 exit_success 545 fi 546 fi 547 fi 548 done 549 fi 550} 551 552open_generic() 553{ 554 # Paths or file:// URLs 555 if (echo "$1" | grep -q '^file://' || 556 ! echo "$1" | egrep -q '^[a-zA-Z+\.\-]+:'); then 557 558 local file="$1" 559 560 # Decode URLs 561 if echo "$file" | grep -q '^file:///'; then 562 file=${file#file://} 563 file="$(printf "$(echo "$file" | sed -e 's@%\([a-f0-9A-F]\{2\}\)@\\x\1@g')")" 564 fi 565 check_input_file "$file" 566 567 open_generic_xdg_mime "$file" 568 569 if [ -f /etc/debian_version ] && 570 which run-mailcap 2>/dev/null 1>&2; then 571 run-mailcap --action=view "$file" 572 if [ $? -eq 0 ]; then 573 exit_success 574 fi 575 fi 576 577 if mimeopen -v 2>/dev/null 1>&2; then 578 mimeopen -L -n "$file" 579 if [ $? -eq 0 ]; then 580 exit_success 581 fi 582 fi 583 fi 584 585 IFS=":" 586 for browser in $BROWSER; do 587 if [ x"$browser" != x"" ]; then 588 589 browser_with_arg=`printf "$browser" "$1" 2>/dev/null` 590 if [ $? -ne 0 ]; then 591 browser_with_arg=$browser; 592 fi 593 594 if [ x"$browser_with_arg" = x"$browser" ]; then 595 "$browser" "$1"; 596 else eval '$browser_with_arg'$xdg_redirect_output; 597 fi 598 599 if [ $? -eq 0 ]; then 600 exit_success; 601 fi 602 fi 603 done 604 605 exit_failure_operation_impossible "no method available for opening '$1'" 606} 607 608open_lxde() 609{ 610 # pcmanfm only knows how to handle file:// urls and filepaths, it seems. 611 if (echo "$1" | grep -q '^file://' || 612 ! echo "$1" | egrep -q '^[a-zA-Z+\.\-]+:') 613 then 614 local file="$(echo "$1" | sed 's%^file://%%')" 615 616 # handle relative paths 617 if ! echo "$file" | grep -q '^/'; then 618 file="$(pwd)/$file" 619 fi 620 621 pcmanfm "$file" 622 623 else 624 open_generic "$1" 625 fi 626 627 if [ $? -eq 0 ]; then 628 exit_success 629 else 630 exit_failure_operation_failed 631 fi 632} 633 634[ x"$1" != x"" ] || exit_failure_syntax 635 636url= 637while [ $# -gt 0 ] ; do 638 parm="$1" 639 shift 640 641 case "$parm" in 642 -*) 643 exit_failure_syntax "unexpected option '$parm'" 644 ;; 645 646 *) 647 if [ -n "$url" ] ; then 648 exit_failure_syntax "unexpected argument '$parm'" 649 fi 650 url="$parm" 651 ;; 652 esac 653done 654 655if [ -z "${url}" ] ; then 656 exit_failure_syntax "file or URL argument missing" 657fi 658 659detectDE 660 661if [ x"$DE" = x"" ]; then 662 DE=generic 663fi 664 665# if BROWSER variable is not set, check some well known browsers instead 666if [ x"$BROWSER" = x"" ]; then 667 BROWSER=links2:elinks:links:lynx:w3m 668 if [ -n "$DISPLAY" ]; then 669 BROWSER=x-www-browser:firefox:seamonkey:mozilla:epiphany:konqueror:chromium-browser:google-chrome:$BROWSER 670 fi 671fi 672 673case "$DE" in 674 kde) 675 open_kde "$url" 676 ;; 677 678 gnome*) 679 open_gnome "$url" 680 ;; 681 682 xfce) 683 open_xfce "$url" 684 ;; 685 686 lxde) 687 open_lxde "$url" 688 ;; 689 690 generic) 691 open_generic "$url" 692 ;; 693 694 *) 695 exit_failure_operation_impossible "no method available for opening '$url'" 696 ;; 697esac 698