Package: lintian Version: 1.23.32 Priority: wishlist Tags: patch Following the recent discussion at debian-devel related to .desktop files I've noticed that we lack some lintian checks for common issues in desktop files (mispelled categories, wrong key value pairs, etc...)
Even though we don't have a policy for Desktop files, since most Desktop environments (KDE, GNOME) are using them it might be sane to have a check for desktop files just like we have for menu files. I've written a first attempt at such check, heavily based on the current menu-format method and reusing some of its code. It is capable of detecting wrong Categories and invalid Type values and should be useful to detect .desktop files which need to be fixed. Notice that it could be improved to do much more tests, I'm not sure I'll handle them myself but maybe this will help others scratch their itch.. Please consider its inclusion into the lintian lab. Thanks, Javier
# desktop format -- lintian check script -*- perl -*- # Copyright (C) 2007 by Javier Fernandez-Sanguino # based on the menu lintian check written by # Copyright (C) 1998 by Joey Hess # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, you can find it on the World Wide # Web at http://www.gnu.org/copyleft/gpl.html, or write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. # # # This lintian check aims at implement some standard check for Desktop files # which are currently not covered by Debian Policy (only menu entries are) # # This is a barebones module, it should be extended to check for some more things # from FreeDesktop's standard: # # - Look for Deprecated Items # - Check field codes in the Exec key # - Check fields with locales # - Check Group headers # - .... # package Lintian::desktop_format; use strict; use Tags; use Util; use File::Basename; # Authorative source of desktop keys: # http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html # # This is a list of all keys that should be in every desktop entry. my @req_keys=qw(Type Name); # This is a list of all known keys. my @known_keys=qw( Type Name Version GenericName NoDisplay Comment Icon Hidden OnlyShowIn NotShowIn TryExec Exec Path Terminal MimeType Categories StartupNotify StartupWMClass URL ); my @deprecated_keys=qw( Encoding MiniIcon TerminalOptions Protocols Extensions BinaryPattern MapNotify SwallowTitle SwallowExec SortOrder FilePattern ); # Authorative source of desktop categories # http://standards.freedesktop.org/menu-spec/menu-spec-1.0.html#category-registry # This is a list of all valid section on the root menu. my @root_categories=qw(AudioVideo Audio Video Development Education Game Graphics Network Office Settings System Utility); # This is a list of all additional categories my @categories=qw( Building Debugger IDE GUIDesigner Profiling RevisionControl Translation Calendar ContactManagement Database Dictionary Chart Email Finance FlowChart PDA ProjectManagement Presentation Spreadsheet WordProcessor 2DGraphics VectorGraphics RasterGraphics 3DGraphics Scanning OCR Photography Publishing Viewer TextTools DesktopSettings HardwareSettings Printing PackageManager Dialup InstantMessaging Chat IRCClient FileTransfer HamRadio News P2P RemoteAccess Telephony TelephonyTools VideoConference WebBrowser WebDevelopment Midi Mixer Sequencer Tuner TV AudioVideoEditing Player Recorder DiscBurning ActionGame AdventureGame ArcadeGame BoardGame BlocksGame CardGame KidsGame LogicGame RolePlaying Simulation SportsGame StrategyGame Art Construction Music Languages Science ArtificialIntelligence Astronomy Biology Chemistry ComputerScience DataVisualization Economy Electricity Geography Geology Geoscience History ImageProcessing Literature Math NumericalAnalysis MedicalSoftware Physics Robotics Sports ParallelComputing Amusement Archiving Compression Electronics Emulator Engineering FileTools FileManager TerminalEmulator Filesystem Monitor Security Accessibility Calculator Clock TextEditor Documentation Core KDE GNOME GTK Qt Motif Java ConsoleOnly ); # Path in which to search for binaries referenced in menu entries. my @path = qw(/usr/local/bin/ /usr/bin/ /bin/ /usr/X11R6/bin/ /usr/games/); my %known_keys_hash; my %deprecated_keys_hash; my %root_categories_hash; my %categories_hash; my %all_files = (); my %all_links = (); # ----------------------------------- sub run { my $pkg = shift; my $type = shift; # Things worth hashing. foreach my $tag (@known_keys) { $known_keys_hash{$tag}=1; } foreach my $tag (@deprecated_keys) { $deprecated_keys_hash{$tag}=1; } foreach my $section (@root_categories) { $root_categories_hash{$section}=1; } foreach my $section (@categories) { $categories_hash{$section}=1; } # read package contents open(IN,"index") or fail("cannot open index file index: $!"); while (<IN>) { chomp; my ($perm,$owner,$size,$date,$time,$file) = split(' ', $_, 6); $file =~ s,^(\./),,; add_file_link_info ($file); $file =~ s/ link to .*//; $file =~ s/ -> .*//; my $operm = perm2oct($perm); if ($perm =~ m,^-,o) { # file checks # desktop file? if ($file =~ m/\.desktop$/o) { # correct permissions? if ($perm =~ m,x,o) { tag "executable-desktop-file", sprintf("$file %04o",$operm); } # TODO: This should provide the VerifyFile($file, $file); } } } close IN; } # ----------------------------------- # Pass this a line of a menu file, it sanitizes it and # verifies that it is correct. sub VerifyFile { my ( $desktopfile, $fullname ) = @_; my %vals; open (DESKTOP, "unpacked/$fullname") or fail("cannot open desktop file $fullname: $!"); my $linecount = 0; while ( my $line = <DESKTOP> ) { chomp $line; $linecount++; next if ( $line =~ m/^\s*\#/ or $line = ~ m/^\s*$/); # Tag = Value # TODO: We do not check for properly formatted localised values for keys # but might be worth checking if they are properly formatted (not their value) if ( $line =~ /^(.*?)\s*=\s*(.*?)$/ ) { my $tag = $1; my $value = $2; if (exists $vals{$tag}) { tag "duplicated-key-in-desktop-entry", "$fullname $1:$linecount"; } $vals{$tag} = $value; } } close DESKTOP; # Now validate the data in the desktop file. # Test for important keys. foreach my $tag (@req_keys) { unless ( exists($vals{$tag}) && defined($vals{$tag}) ) { tag "desktop-entry-missing-required-key", "$tag $fullname:$linecount"; } } # Make sure all keys are known and no deprecated keys are being used foreach my $tag (keys %vals) { if ( $deprecated_keys_hash{$tag} ) { tag "desktop-entry-contains-deprecated-key", "$tag $fullname:$linecount"; } elsif (! $known_keys_hash{$tag} && $tag !~ /^X-/ ) { # TODO: Will produce false positives for keys reserved by KDE # which do not use X-KDE (see Standard) tag "desktop-entry-contains-unknown-key", "$tag $fullname:$linecount"; } } # TODO: Check for deprecated keys: Encoding, MiniIcon, TerminalOptions, Protocols, # Extensions, BinaryPattern, MapNotify, SwallowTitle, SwallowExec, SortOrder # FilePattern, if (exists($vals{'Exec'})) { VerifyExec( $fullname, $linecount, $vals{'Exec'}); } if (exists($vals{'Icon'})) { VerifyIcon( $fullname, $linecount, $vals{'Icon'}, 32); } # Check the category tag if (exists($vals{'Categories'})) { # Is the main category there? my $ccats = 0; foreach my $cat (keys %root_categories_hash) { $ccats++ if $vals{'Categories'} =~ /$cat;?/; } if ( $ccats == 0 ) { tag "desktop-entry-lacks-main-category", "$fullname:$linecount"; } elsif ( $ccats > 1 ) { tag "desktop-entry-multiple-main-category", "$fullname:$linecount"; } foreach my $cat (split(';',$vals{'Categories'})) { if (not $categories_hash{$cat}) { tag "desktop-entry-invalid-category", "$cat $fullname:$linecount"; } } } # TODO: # - Check if the values correspond to the allowed type for a given key (boolean, # integer...) } sub VerifyCmd { # Verify if commands are installed with the package. # TODO: Should check quoting and the check special field codes in Exec my ( $fullname, $linecount, $exec) = @_; # Read the file index: my %file_index; open(FILE_INDEX,"index") or fail("cannot open index file index: $!"); while(<FILE_INDEX>) { $file_index{(split /\s+/, $_)[5]} = 1; } close FILE_INDEX; # This routine handles su wrappers. The option parsing here is ugly and dead-simple, # but it's hopefully good enough for what will show up in desktop files. # su-to-root and sux require -c options, kdesu optionally allows one, and # gksu has the command at the end of its arguments. my @com = split(' ',$exec); my $cmd; if ($com[0] eq "/usr/sbin/su-to-root") { tag "su-to-root-with-usr-sbin", "$fullname:$linecount"; } elsif ($com[0] =~ m,^(?:/usr/bin/)?(su-to-root|gksu|kdesu|sux)$,) { my $wrapper = $1; shift @com; while (@com) { unless ($com[0]) { shift @com; next; } if ($com[0] eq '-c') { $cmd = $com[1]; last; } elsif ($com[0] =~ /^-[Dfmupi]|^--(user|description|message)/) { shift @com; shift @com; } elsif ($com[0] =~ /^-/) { shift @com; } else { last; } } if (!$cmd && $wrapper =~ /^(gk|kde)su$/) { $cmd = $com[0]; } elsif (!$cmd) { tag "su-wrapper-without--c", "$fullname:$linecount $wrapper"; } } else { $cmd = $com[0]; } tag "desktop-command-not-in-package", "$fullname:$linecount $cmd" if ($cmd && !($file_index{".$cmd"} || grep {$file_index{".".$_.$cmd}} @path)); } # TODO: Shouldn't this allow PNG files too? sub VerifyIcon { my ( $fullname, $linecount, $icon, $size) = @_; local *IN; if ($icon eq 'none') { tag "desktop-entry-uses-icon-none", "$fullname:$linecount"; return; } if (not ($icon =~ m/\.xpm$/i)) { tag "desktop-icon-not-in-xpm-format", "$icon"; return; } # Try the explicit location, and if that fails, try the standard path. my $iconfile = "unpacked/$icon"; if (! -f $iconfile) { $iconfile = "unpacked/usr/share/pixmaps/$icon"; } if (! open IN, $iconfile) { tag "desktop-icon-missing", "$icon"; return; } my $parse = "XPM header"; my $line; do { defined ($line = <IN>) or goto parse_error; } until ($line =~ /\/\*\s*XPM\s*\*\//); $parse = "size line"; do { defined ($line = <IN>) or goto parse_error; } until ($line =~ /"\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*([0-9]+)\s*"/); my $width = $1 + 0; my $height = $2 + 0; my $numcolours = $3 + 0; my $cpp = $4 + 0; if ($width > $size || $height > $size) { tag "desktop-icon-too-big", "$icon: ${width}x${height} > ${size}x${size}"; } close IN or die; return; parse_error: close IN or die; tag "desktop-icon-cannot-be-parsed", "$icon: looking for $parse"; return; } 1; # ----------------------------------- # Add file and link to %all_files and %all_links. Note that both files and # links have to include a leading /. sub add_file_link_info { my $file = shift; my $link = undef; $file = "/" . $file if (not $file =~ m%^/%); # make file absolute $file =~ s%/+%/%g; # remove duplicated `/' ($file, $link) = split(/ -> /, $file); $all_files{$file} = 1; if (defined $link) { if (not $link =~ m,^/,) { # not absolute link $link = "/" . $link; # make sure link starts with '/' $link =~ s,/+\./+,/,g; # remove all /./ parts my $dcount = 1; while ($link =~ s,^/+\.\./+,/,) { #\ count & remove $dcount++; #/ any leading /../ parts } my $f = $file; while ($dcount--) { #\ remove last $dcount $f =~ s,/[^/]*$,,; #/ path components from $file } $link = $f . $link; # now we should have absolute link } $all_links{$file} = $link unless ($link eq $file); } } 1; # vim: syntax=perl ts=8 sw=4
Check-Script: desktop-format Author: Javier Fernandez-Sanguino Pen~a <[EMAIL PROTECTED]> Abbrev: dkf Standards-Version: 3.6.1 Type: binary Unpack-Level: 1 Info: This script validates the format of desktop files. Tag: unparsable-desktop-entry Type: error Info: An entry of the desktop file cannot be parsed as a series of key=value pairs. Tag: duplicated-key-in-desktop-entry Type: warning Info: The desktop entry contains two instances of the same key. This is just a waste of space, as only one will be used. Tag: desktop-entry-missing-required-key Type: error Info: The desktop entry has a line that is missing a required key. See: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html#recognized-keys Tag: desktop-entry-contains-unknown-key Type: warning Info: The desktop entry has a line that has a key in it that is not one of the standard keys defined in the FreeDesktop specification and this key does not conform to the preferred method for introducing changes (via the 'X-' prefix). It's more likely the key's name is misspelled. See: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html#recognized-keys Tag: desktop-entry-contains-deprecated-key Type: warning Info: The desktop entry has a line that has a key in it that has been deprecated in the FreeDesktop specification. See: http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html#deprecated-items Tag: desktop-icon-not-in-xpm-format Type: error Info: Icons in the Debian desktop system should be in XPM format. Ref: desktop manual 3.7 Tag: desktop-icon-missing Type: warning Info: This icon file couldn't be found. If the path to the icon in the desktop file is an absolute path, make sure that icon exists at that path in the package. If the path is relative or a simple filename, make sure the icon is installed in <tt>/usr/share/pixmaps</tt>, the default location. . If the icon is in a package this package depends on, add a lintian override for this warning. lintian cannot check icons in other packages. Ref: desktop manual 3.7 Tag: desktop-icon-too-big Type: error Info: Icons in the Debian desktop system should be at most 32x32 pixels (icon16x16 icons should of course be at most 16x16 pixels) Ref: desktop manual 3.7 Tag: desktop-icon-cannot-be-parsed Type: warning Info: The icon file could not be parsed. Perhaps this means a bad XPM file, or perhaps it means the lintian parsing needs to be improved. If the window managers and standard tools accept the file then probably it's the latter; please file a bug on lintian then. Tag: desktop-command-not-in-package Type: warning Info: The desktop entry specifies a command which is not available in the package. In most cases this is a typo or after you moved a binary around, but forgot to update the desktop file.

