1#!/bin/sh
2#---------------------------------------------
3#   xdg-desktop-icon
4#
5#   Utility script to install desktop items on a Linux desktop.
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
36usage()
37{
38cat << _USAGE
39xdg-desktop-icon - command line tool for (un)installing icons to the desktop
40
41Synopsis
42
43xdg-desktop-icon install [--novendor] FILE
44
45xdg-desktop-icon uninstall FILE
46
47xdg-desktop-icon { --help | --manual | --version }
48
49_USAGE
50}
51
52manualpage()
53{
54cat << _MANUALPAGE
55Name
56
57xdg-desktop-icon - command line tool for (un)installing icons to the desktop
58
59Synopsis
60
61xdg-desktop-icon install [--novendor] FILE
62
63xdg-desktop-icon uninstall FILE
64
65xdg-desktop-icon { --help | --manual | --version }
66
67Description
68
69The xdg-desktop-icon program can be used to install an application launcher or
70other file on the desktop of the current user.
71
72An application launcher is represented by a *.desktop file. Desktop files are
73defined by the freedesktop.org Desktop Entry Specification. The most important
74aspects of *.desktop files are summarized below.
75
76Commands
77
78install
79    Installs FILE to the desktop of the current user. FILE can be a *.desktop
80    file or any other type of file.
81uninstall
82    Removes FILE from the desktop of the current user.
83
84Options
85
86--novendor
87
88    Normally, xdg-desktop-icon checks to ensure that a *.desktop file to be
89    installed has a vendor prefix. This option can be used to disable that
90    check.
91
92    A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated
93    with a dash ("-"). Companies and organizations are encouraged to use a word
94    or phrase, preferably the organizations name, for which they hold a
95    trademark as their vendor prefix. The purpose of the vendor prefix is to
96    prevent name conflicts.
97
98--help
99    Show command synopsis.
100--manual
101    Show this manualpage.
102--version
103    Show the xdg-utils version information.
104
105Desktop Files
106
107An application launcher can be added to the desktop by installing a *.desktop
108file. A *.desktop file consists of a [Desktop Entry] header followed by several
109Key=Value lines.
110
111A *.desktop file can provide a name and description for an application in
112several different languages. This is done by adding a language code as used by
113LC_MESSAGES in square brackets behind the Key. This way one can specify
114different values for the same Key depending on the currently selected language.
115
116The following keys are often used:
117
118Value=1.0
119    This is a mandatory field to indicate that the *.desktop file follows the
120    1.0 version of the specification.
121Type=Application
122    This is a mandatory field that indicates that the *.desktop file describes
123    an application launcher.
124Name=Application Name
125    The name of the application. For example Mozilla
126GenericName=Generic Name
127    A generic description of the application. For example Web Browser
128Comment=Comment
129    Optional field to specify a tooltip for the application. For example Visit
130    websites on the Internet
131Icon=Icon File
132    The icon to use for the application. This can either be an absolute path to
133    an image file or an icon-name. If an icon-name is provided an image lookup
134    by name is done in the user's current icon theme. The xdg-icon-resource
135    command can be used to install image files into icon themes. The advantage
136    of using an icon-name instead of an absolute path is that with an icon-name
137    the application icon can be provided in several different sizes as well as
138    in several differently themed styles.
139Exec=Command Line
140    The command line to start the application. If the application can open
141    files the %f placeholder should be specified. When a file is dropped on the
142    application launcher the %f is replaced with the file path of the dropped
143    file. If multiple files can be specified on the command line the %F
144    placeholder should be used instead of %f. If the application is able to
145    open URLs in addition to local files then %u or %U can be used instead of
146    %f or %F.
147
148For a complete oveview of the *.desktop file format please visit http://
149www.freedesktop.org/wiki/Standards/desktop-entry-spec
150
151Environment Variables
152
153xdg-desktop-icon honours the following environment variables:
154
155XDG_UTILS_DEBUG_LEVEL
156    Setting this environment variable to a non-zero numerical value makes
157    xdg-desktop-icon do more verbose reporting on stderr. Setting a higher
158    value increases the verbosity.
159
160Exit Codes
161
162An exit code of 0 indicates success while a non-zero exit code indicates
163failure. The following failure codes can be returned:
164
1651
166    Error in command line syntax.
1672
168    One of the files passed on the command line did not exist.
1693
170    A required tool could not be found.
1714
172    The action failed.
1735
174    No permission to read one of the files passed on the command line.
175
176See Also
177
178xdg-icon-resource(1)
179
180Examples
181
182The company ShinyThings Inc. has developed an application named "WebMirror" and
183would like to add a launcher for for on the desktop. The company will use
184"shinythings" as its vendor id. In order to add the application to the desktop
185there needs to be a .desktop file for the application:
186
187shinythings-webmirror.desktop:
188
189  [Desktop Entry]
190  Encoding=UTF-8
191  Type=Application
192
193  Exec=webmirror
194  Icon=shinythings-webmirror
195
196  Name=WebMirror
197  Name[nl]=WebSpiegel
198
199Now the xdg-desktop-icon tool can be used to add the webmirror.desktop file to
200the desktop:
201
202xdg-desktop-icon install ./shinythings-webmirror.desktop
203
204To add a README file to the desktop as well, the following command can be used:
205
206xdg-desktop-icon install ./shinythings-README
207
208_MANUALPAGE
209}
210
211#@xdg-utils-common@
212
213#----------------------------------------------------------------------------
214#   Common utility functions included in all XDG wrapper scripts
215#----------------------------------------------------------------------------
216
217DEBUG()
218{
219  [ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && return 0;
220  [ ${XDG_UTILS_DEBUG_LEVEL} -lt $1 ] && return 0;
221  shift
222  echo "$@" >&2
223}
224
225# This handles backslashes but not quote marks.
226first_word()
227{
228    read first rest
229    echo "$first"
230}
231
232#-------------------------------------------------------------
233# map a binary to a .desktop file
234binary_to_desktop_file()
235{
236    search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
237    binary="`which "$1"`"
238    binary="`readlink -f "$binary"`"
239    base="`basename "$binary"`"
240    IFS=:
241    for dir in $search; do
242        unset IFS
243        [ "$dir" ] || continue
244        [ -d "$dir/applications" -o -d "$dir/applnk" ] || continue
245        for file in "$dir"/applications/*.desktop "$dir"/applications/*/*.desktop "$dir"/applnk/*.desktop "$dir"/applnk/*/*.desktop; do
246            [ -r "$file" ] || continue
247            # Check to make sure it's worth the processing.
248            grep -q "^Exec.*$base" "$file" || continue
249            # Make sure it's a visible desktop file (e.g. not "preferred-web-browser.desktop").
250            grep -Eq "^(NoDisplay|Hidden)=true" "$file" && continue
251            command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`"
252            command="`which "$command"`"
253            if [ x"`readlink -f "$command"`" = x"$binary" ]; then
254                # Fix any double slashes that got added path composition
255                echo "$file" | sed -e 's,//*,/,g'
256                return
257            fi
258        done
259    done
260}
261
262#-------------------------------------------------------------
263# map a .desktop file to a binary
264## FIXME: handle vendor dir case
265desktop_file_to_binary()
266{
267    search="${XDG_DATA_HOME:-$HOME/.local/share}:${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
268    desktop="`basename "$1"`"
269    IFS=:
270    for dir in $search; do
271        unset IFS
272        [ "$dir" -a -d "$dir/applications" ] || continue
273        file="$dir/applications/$desktop"
274        [ -r "$file" ] || continue
275        # Remove any arguments (%F, %f, %U, %u, etc.).
276        command="`grep -E "^Exec(\[[^]=]*])?=" "$file" | cut -d= -f 2- | first_word`"
277        command="`which "$command"`"
278        readlink -f "$command"
279        return
280    done
281}
282
283#-------------------------------------------------------------
284# Exit script on successfully completing the desired operation
285
286exit_success()
287{
288    if [ $# -gt 0 ]; then
289        echo "$@"
290        echo
291    fi
292
293    exit 0
294}
295
296
297#-----------------------------------------
298# Exit script on malformed arguments, not enough arguments
299# or missing required option.
300# prints usage information
301
302exit_failure_syntax()
303{
304    if [ $# -gt 0 ]; then
305        echo "xdg-desktop-icon: $@" >&2
306        echo "Try 'xdg-desktop-icon --help' for more information." >&2
307    else
308        usage
309        echo "Use 'man xdg-desktop-icon' or 'xdg-desktop-icon --manual' for additional info."
310    fi
311
312    exit 1
313}
314
315#-------------------------------------------------------------
316# Exit script on missing file specified on command line
317
318exit_failure_file_missing()
319{
320    if [ $# -gt 0 ]; then
321        echo "xdg-desktop-icon: $@" >&2
322    fi
323
324    exit 2
325}
326
327#-------------------------------------------------------------
328# Exit script on failure to locate necessary tool applications
329
330exit_failure_operation_impossible()
331{
332    if [ $# -gt 0 ]; then
333        echo "xdg-desktop-icon: $@" >&2
334    fi
335
336    exit 3
337}
338
339#-------------------------------------------------------------
340# Exit script on failure returned by a tool application
341
342exit_failure_operation_failed()
343{
344    if [ $# -gt 0 ]; then
345        echo "xdg-desktop-icon: $@" >&2
346    fi
347
348    exit 4
349}
350
351#------------------------------------------------------------
352# Exit script on insufficient permission to read a specified file
353
354exit_failure_file_permission_read()
355{
356    if [ $# -gt 0 ]; then
357        echo "xdg-desktop-icon: $@" >&2
358    fi
359
360    exit 5
361}
362
363#------------------------------------------------------------
364# Exit script on insufficient permission to write a specified file
365
366exit_failure_file_permission_write()
367{
368    if [ $# -gt 0 ]; then
369        echo "xdg-desktop-icon: $@" >&2
370    fi
371
372    exit 6
373}
374
375check_input_file()
376{
377    if [ ! -e "$1" ]; then
378        exit_failure_file_missing "file '$1' does not exist"
379    fi
380    if [ ! -r "$1" ]; then
381        exit_failure_file_permission_read "no permission to read file '$1'"
382    fi
383}
384
385check_vendor_prefix()
386{
387    file_label="$2"
388    [ -n "$file_label" ] || file_label="filename"
389    file=`basename "$1"`
390    case "$file" in
391       [a-zA-Z]*-*)
392         return
393         ;;
394    esac
395
396    echo "xdg-desktop-icon: $file_label '$file' does not have a proper vendor prefix" >&2
397    echo 'A vendor prefix consists of alpha characters ([a-zA-Z]) and is terminated' >&2
398    echo 'with a dash ("-"). An example '"$file_label"' is '"'example-$file'" >&2
399    echo "Use --novendor to override or 'xdg-desktop-icon --manual' for additional info." >&2
400    exit 1
401}
402
403check_output_file()
404{
405    # if the file exists, check if it is writeable
406    # if it does not exists, check if we are allowed to write on the directory
407    if [ -e "$1" ]; then
408        if [ ! -w "$1" ]; then
409            exit_failure_file_permission_write "no permission to write to file '$1'"
410        fi
411    else
412        DIR=`dirname "$1"`
413        if [ ! -w "$DIR" -o ! -x "$DIR" ]; then
414            exit_failure_file_permission_write "no permission to create file '$1'"
415        fi
416    fi
417}
418
419#----------------------------------------
420# Checks for shared commands, e.g. --help
421
422check_common_commands()
423{
424    while [ $# -gt 0 ] ; do
425        parm="$1"
426        shift
427
428        case "$parm" in
429            --help)
430            usage
431            echo "Use 'man xdg-desktop-icon' or 'xdg-desktop-icon --manual' for additional info."
432            exit_success
433            ;;
434
435            --manual)
436            manualpage
437            exit_success
438            ;;
439
440            --version)
441            echo "xdg-desktop-icon 1.1.0 rc1"
442            exit_success
443            ;;
444        esac
445    done
446}
447
448check_common_commands "$@"
449
450[ -z "${XDG_UTILS_DEBUG_LEVEL}" ] && unset XDG_UTILS_DEBUG_LEVEL;
451if [ ${XDG_UTILS_DEBUG_LEVEL-0} -lt 1 ]; then
452    # Be silent
453    xdg_redirect_output=" > /dev/null 2> /dev/null"
454else
455    # All output to stderr
456    xdg_redirect_output=" >&2"
457fi
458
459#--------------------------------------
460# Checks for known desktop environments
461# set variable DE to the desktop environments name, lowercase
462
463detectDE()
464{
465    # see https://bugs.freedesktop.org/show_bug.cgi?id=34164
466    unset GREP_OPTIONS
467
468    if [ -n "${XDG_CURRENT_DESKTOP}" ]; then
469      case "${XDG_CURRENT_DESKTOP}" in
470         GNOME)
471           DE=gnome;
472           ;;
473         KDE)
474           DE=kde;
475           ;;
476         LXDE)
477           DE=lxde;
478           ;;
479         XFCE)
480           DE=xfce
481      esac
482    fi
483
484    if [ x"$DE" = x"" ]; then
485      # classic fallbacks
486      if [ x"$KDE_FULL_SESSION" = x"true" ]; then DE=kde;
487      elif [ x"$GNOME_DESKTOP_SESSION_ID" != x"" ]; then DE=gnome;
488      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;
489      elif xprop -root _DT_SAVE_MODE 2> /dev/null | grep ' = \"xfce4\"$' >/dev/null 2>&1; then DE=xfce;
490      elif xprop -root 2> /dev/null | grep -i '^xfce_desktop_window' >/dev/null 2>&1; then DE=xfce
491      fi
492    fi
493
494    if [ x"$DE" = x"" ]; then
495      # fallback to checking $DESKTOP_SESSION
496      case "$DESKTOP_SESSION" in
497         gnome)
498           DE=gnome;
499           ;;
500         LXDE)
501           DE=lxde;
502           ;;
503         xfce|xfce4)
504           DE=xfce;
505           ;;
506      esac
507    fi
508
509    if [ x"$DE" = x"" ]; then
510      # fallback to uname output for other platforms
511      case "$(uname 2>/dev/null)" in
512        Darwin)
513          DE=darwin;
514          ;;
515      esac
516    fi
517
518    if [ x"$DE" = x"gnome" ]; then
519      # gnome-default-applications-properties is only available in GNOME 2.x
520      # but not in GNOME 3.x
521      which gnome-default-applications-properties > /dev/null 2>&1  || DE="gnome3"
522    fi
523}
524
525#----------------------------------------------------------------------------
526# kfmclient exec/openURL can give bogus exit value in KDE <= 3.5.4
527# It also always returns 1 in KDE 3.4 and earlier
528# Simply return 0 in such case
529
530kfmclient_fix_exit_code()
531{
532    version=`LC_ALL=C.UTF-8 kde-config --version 2>/dev/null | grep '^KDE'`
533    major=`echo $version | sed 's/KDE.*: \([0-9]\).*/\1/'`
534    minor=`echo $version | sed 's/KDE.*: [0-9]*\.\([0-9]\).*/\1/'`
535    release=`echo $version | sed 's/KDE.*: [0-9]*\.[0-9]*\.\([0-9]\).*/\1/'`
536    test "$major" -gt 3 && return $1
537    test "$minor" -gt 5 && return $1
538    test "$release" -gt 4 && return $1
539    return 0
540}
541
542[ x"$1" != x"" ] || exit_failure_syntax
543
544action=
545desktop_file=
546
547case $1 in
548  install)
549    action=install
550    ;;
551
552  uninstall)
553    action=uninstall
554    ;;
555
556  *)
557    exit_failure_syntax "unknown command '$1'"
558    ;;
559esac
560
561shift
562
563vendor=true
564while [ $# -gt 0 ] ; do
565    parm=$1
566    shift
567
568    case $parm in
569      --novendor)
570        vendor=false
571        ;;
572
573      -*)
574        exit_failure_syntax "unexpected option '$parm'"
575        ;;
576
577      *)
578        if [ -n "$desktop_file" ] ; then
579            exit_failure_syntax "unexpected argument '$parm'"
580        fi
581        if [ "$action" = "install" ] ; then
582            check_input_file "$parm"
583        fi
584        desktop_file=$parm
585        ;;
586    esac
587done
588
589# Shouldn't happen
590if [ -z "$action" ] ; then
591    exit_failure_syntax "command argument missing"
592fi
593
594if [ -z "$desktop_file" ] ; then
595    exit_failure_syntax "FILE argument missing"
596fi
597
598filetype=
599case "$desktop_file" in
600  *.desktop)
601     filetype=desktop
602     if [ "$vendor" = "true" -a "$action" = "install" ] ; then
603        check_vendor_prefix "$desktop_file"
604     fi
605     ;;
606  *)
607     filetype=other
608     ;;
609esac
610
611my_umask=077
612desktop_dir="$HOME/Desktop"
613if xdg-user-dir 2>/dev/null 1>&2; then
614  desktop_dir=`xdg-user-dir DESKTOP`
615fi
616desktop_dir_kde=`kde${KDE_SESSION_VERSION}-config --userpath desktop 2> /dev/null`
617if gconftool-2 -g /apps/nautilus/preferences/desktop_is_home_dir 2> /dev/null | grep true > /dev/null; then
618    desktop_dir_gnome="$HOME"
619    # Don't create $HOME/Desktop if it doesn't exist
620    [ -w "$desktop_dir" ] || desktop_dir=
621fi
622if [ -n "$desktop_dir_kde" ]; then
623    if [ ! -d "$desktop_dir_kde" ]; then
624        save_umask=`umask`
625        umask $my_umask
626        mkdir -p "$desktop_dir_kde"
627        umask $save_umask
628    fi
629    # Is the KDE desktop dir != $HOME/Desktop ?
630    if [ "x`readlink -f "$desktop_dir"`" != "x`readlink -f "$desktop_dir_kde"`" ]; then
631        # If so, don't create $HOME/Desktop if it doesn't exist
632        [ -w "$desktop_dir" ] || desktop_dir=
633    else
634        desktop_dir_kde=
635    fi
636fi
637
638basefile=`basename "$desktop_file"`
639
640DEBUG 1 "$action $desktop_file in $desktop_dir $desktop_dir_kde $desktop_dir_gnome"
641
642case $action in
643    install)
644        save_umask=`umask`
645        umask $my_umask
646
647        for x in "$desktop_dir" "$desktop_dir_kde" "$desktop_dir_gnome" ; do
648            if [ -n "$x" ]; then
649                mkdir -p "$x"
650                eval 'cp "$desktop_file" "$x/$basefile"'$xdg_redirect_output
651                chmod u+x "$x/$basefile"
652            fi
653        done
654
655        umask $save_umask
656        ;;
657
658    uninstall)
659        for x in "$desktop_dir" "$desktop_dir_kde" "$desktop_dir_gnome" ; do
660            if [ -n "$x" ]; then
661                rm -f "$x/$basefile"
662            fi
663        done
664
665        ;;
666esac
667
668exit_success
669
670
671