File Coverage

blib/lib/File/DesktopEntry.pm
Criterion Covered Total %
statement 269 282 95.3
branch 131 172 76.1
condition 39 61 63.9
subroutine 39 39 100.0
pod 17 17 100.0
total 495 571 86.6


line stmt bran cond sub pod time code
1             package File::DesktopEntry;
2              
3 6     6   47332 use strict;
  6         12  
  6         158  
4 6     6   35 use warnings;
  6         7  
  6         192  
5              
6 6     6   33 use vars qw/$AUTOLOAD/;
  6         12  
  6         312  
7 6     6   30 use Carp;
  6         9  
  6         425  
8 6     6   5669 use Encode;
  6         70022  
  6         515  
9 6     6   43 use File::Spec;
  6         17  
  6         192  
10 6     6   4452 use File::BaseDir 0.03 qw/data_files data_home/;
  6         8107  
  6         441  
11 6     6   4343 use URI::Escape;
  6         11539  
  6         30239  
12              
13             our $VERSION = '0.22';
14             our $VERBOSE = 0;
15              
16             if ($^O eq 'MSWin32') {
17             eval q/use Win32::Process/;
18             die $@ if $@;
19             }
20              
21             =head1 NAME
22              
23             File::DesktopEntry - Object to handle .desktop files
24              
25             =head1 SYNOPSIS
26              
27             use File::DesktopEntry;
28              
29             my $entry = File::DesktopEntry->new('firefox');
30              
31             print "Using ".$entry->Name." to open http://perl.org\n";
32             $entry->run('http://perl.org');
33              
34             =head1 DESCRIPTION
35              
36             This module is designed to work with F<.desktop> files. The format of these files
37             is specified by the freedesktop "Desktop Entry" specification. This module can
38             parse these files but also knows how to run the applications defined by these
39             files.
40              
41             For this module version 1.0 of the specification was used.
42              
43             This module was written to support L.
44              
45             Please remember: case is significant for the names of Desktop Entry keys.
46              
47             =head1 VARIABLES
48              
49             You can set the global variable C<$File::DesktopEntry::VERBOSE>. If set the
50             module prints a warning every time a command gets executed.
51              
52             The global variable C<$File::DesktopEntry::LOCALE> tells you what the default
53             locale being used is. However, changing it will not change the default locale.
54              
55             =head1 AUTOLOAD
56              
57             All methods that start with a capital are autoloaded as C where
58             key is the autoloaded method name.
59              
60             =head1 METHODS
61              
62             =over 4
63              
64             =item C
65              
66             =item C
67              
68             =item C
69              
70             Constructor. FILE, NAME or TEXT are optional arguments.
71              
72             When a name is given (a string without 'C', 'C<\>' or 'C<.>') a lookup is
73             done using File::BaseDir. If the file found in this lookup is not writable or
74             if no file was found, the XDG_DATA_HOME path will be used when writing.
75              
76             =cut
77              
78             our $LOCALE = 'C';
79              
80             # POSIX setlocale(LC_MESSAGES) not supported on all platforms
81             # so we do it ourselves ...
82             # string might look like lang_COUNTRY.ENCODING@MODIFIER
83             for (qw/LC_ALL LC_MESSAGES LANGUAGE LANG/) {
84             next unless $ENV{$_};
85             $LOCALE = $ENV{$_};
86             last;
87             }
88             our $_locale = _parse_lang($LOCALE);
89              
90             sub new {
91 19     19 1 4754 my ($class, $file) = @_;
92 19         45 my $self = bless {}, $class;
93 19 100       100 if (! defined $file) { # initialize new file
    100          
    100          
94 4         23 $self->set(Version => '1.0', Encoding => 'UTF-8');
95             }
96 4         15 elsif (ref $file) { $self->read($file) } # SCALAR
97 10         29 elsif ($file =~ /[\/\\\.]/) { $$self{file} = $file } # file
98             else {
99 1         3 $$self{file} = $class->lookup($file); # name
100 1         4 $$self{name} = $file;
101             }
102 19         50 return $self;
103             }
104              
105             sub AUTOLOAD {
106 3     3   16 $AUTOLOAD =~ s/.*:://;
107 3 50       9 return if $AUTOLOAD eq 'DESTROY';
108 3 50       15 croak "No such method: File::DesktopEntry::$AUTOLOAD"
109             unless $AUTOLOAD =~ /^[A-Z][A-Za-z0-9-]+$/;
110 3         9 return $_[0]->get($AUTOLOAD);
111             }
112              
113             =item C
114              
115             Returns a filename for a desktop entry with desktop file id NAME.
116              
117             =cut
118              
119             sub lookup {
120 2     2 1 46 my (undef, $name) = @_;
121 2         3 $name .= '.desktop';
122 2         9 my $file = data_files('applications', $name);
123 2 50 33     120 if (! $file and $name =~ /-/) {
124             # name contains "-" and was not found
125 0         0 my @name = split /-/, $name;
126 0         0 $file = data_files('applications', @name);
127             }
128 2         10 return $file;
129             }
130              
131             sub _parse_lang {
132             # lang might look like lang_COUNTRY.ENCODING@MODIFIER
133 17     17   1974 my $lang = shift;
134 17 100 66     149 return '' if !$lang or $lang eq 'C' or $lang eq 'POSIX';
      100        
135 7 50       40 $lang =~ m{^
136             ([^_@\.]+) # lang $1
137             (?: _ ([^@\.]+) )? # COUNTRY $2
138             (?: \. [^@]+ )? # ENCODING
139             (?: \@ (.+) )? # MODIFIER $3
140             $}x or return '';
141 7         21 my ($l, $c, $m) = ($1, $2, $3);
142 7 100 100     46 my @locale = (
    100          
    100          
143             $l,
144             ($m ? "$l\@$m" : ()),
145             ($c ? "$l\_$c" : ()),
146             (($m && $c) ? "$l\_$c\@$m" : ()) );
147 7         34 return join '|', reverse @locale;
148             }
149              
150             =item C
151              
152             Returns true if the Exec string for this desktop entry specifies that the
153             application uses URIs instead of paths. This can be used to determine
154             whether an application uses a VFS library.
155              
156             =item C
157              
158             Returns true if the Exec string for this desktop entry specifies that the
159             application can handle multiple arguments at once.
160              
161             =cut
162              
163              
164             sub wants_uris {
165 1     1 1 3 my $self = shift;
166 1         3 my $exec = $self->get('Exec');
167 1 50       4 croak "No Exec string defined for desktop entry" unless length $exec;
168 1         3 $exec =~ s/\%\%//g;
169 1         8 return $exec =~ /\%U/i;
170             }
171              
172             sub wants_list {
173 1     1 1 2 my $self = shift;
174 1         3 my $exec = $self->get('Exec');
175 1 50       5 croak "No Exec string defined for desktop entry" unless length $exec;
176 1         4 $exec =~ s/\%\%//g;
177 1         7 return $exec !~ /\%[fud]/; # we default to %F if no /\%[FUD]/i is found
178             }
179              
180             =item C
181              
182             Forks and runs the application specified in this Desktop Entry
183             with arguments FILES as a background process. Returns the pid.
184              
185             The child process fails when this is not a Desktop Entry of type Application
186             or if the Exec key is missing or invalid.
187              
188             If the desktop entry specifies that the program needs to be executed in a
189             terminal the $TERMINAL environment variable is used. If this variable is not
190             set C is used as default.
191              
192             (On Windows this method returns a L object.)
193              
194             =item C
195              
196             Like C but using the C system call.
197             It only return after the application has ended.
198              
199             =item C
200              
201             Like C but using the C system call. This method
202             is expected not to return but to replace the current process with the
203             application you try to run.
204              
205             On Windows this method doesn't always work the way you want it to
206             due to the C emulation on this platform. Try using C or
207             C instead.
208              
209             =cut
210              
211             sub run {
212 3     3 1 4875 my $pid = fork;
213 3 100       412 return $pid if $pid; # parent process
214 1         69 unshift @_, 'exec'; goto \&_run;
  1         97  
215             }
216              
217 5     5 1 51 sub system { unshift @_, 'system'; goto \&_run }
  5         48  
218              
219 1     1 1 1824 sub exec { unshift @_, 'exec'; goto \&_run }
  1         80  
220              
221             sub _run {
222 7     7   62 my $call = shift;
223 7         36 my $self = shift;
224              
225 7 50       131 croak "Desktop entry is not an Application"
226             unless $self->get('Type') eq 'Application';
227              
228 7         81 my @exec = $self->parse_Exec(@_);
229              
230 7         40 my $t = $self->get('Terminal');
231 7 50 33     37 if ($t and $t eq 'true') {
232 0   0     0 my $term = $ENV{TERMINAL} || 'xterm -e';
233 0         0 unshift @exec, _split($term);
234             }
235              
236 7         11 my $cwd;
237 7 100       28 if (my $path = $self->get('Path')) {
238 3         100 require Cwd;
239 3         67 $cwd = Cwd::getcwd();
240 3 50       138 chdir $path or croak "Could not change to dir: $path";
241 3         80 $ENV{PWD} = $path;
242 3 50       21 warn "Running from directory: $path\n" if $VERBOSE;
243             }
244              
245 7 50       45 warn "Running: "._quote(@exec)."\n" if $VERBOSE;
246              
247 7 100       25 if ($call eq 'exec') { CORE::exec {$exec[0]} @exec; exit 1 }
  2         26  
  2         0  
  0         0  
248 5         10 else { CORE::system {$exec[0]} @exec }
  5         34463  
249 5 0 33     215 warn "Error: $!\n" if $VERBOSE and $?;
250              
251 5 100       252 if (defined $cwd) {
252 2 50       306 chdir $cwd or croak "Could not change back to dir: $cwd";
253 2         148 $ENV{PWD} = $cwd;
254             }
255             }
256              
257             =item C
258              
259             Expands the Exec format in this desktop entry with. Returns a properly quoted
260             string in scalar context or a list of words in list context. Dies when the
261             Exec key is invalid.
262              
263             It supports the following fields:
264              
265             %f single file
266             %F multiple files
267             %u single url
268             %U multiple urls
269             %i Icon field prefixed by --icon
270             %c Name field, possibly translated
271             %k location of this .desktop file
272             %% literal '%'
273              
274             If necessary this method tries to convert between paths and URLs but this
275             is not perfect.
276              
277             Fields that are deprecated, but (still) supported by this module:
278              
279             %d single directory
280             %D multiple directories
281              
282             The fields C<%n>, C<%N>, C<%v> and C<%m> are deprecated and will cause a
283             warning if C<$VERBOSE> is used. Any other unknown fields will cause an error.
284              
285             The fields C<%F>, C<%U>, C<%D> and C<%i> can only occur as separate words
286             because they expand to multiple arguments.
287              
288             Also see L.
289              
290             =cut
291              
292             sub parse_Exec {
293 23     23 1 1466 my ($self, @argv) = @_;
294 23         76 my @format = _split( $self->get('Exec') );
295              
296             # Check format
297 23         52 my $seen = 0;
298 23         44 for (@format) {
299 62         102 my $s = $_; # copy;
300 62         126 $s =~ s/\%\%//g;
301 62         140 $seen += ($s =~ /\%[fFuUdD]/);
302              
303 62 50 66     369 die "Exec key for '".$self->get('Name')."' contains " .
304             "'\%F\', '\%U' or '\%D' at the wrong place\n"
305             if $s !~ /^\%[FUD]$/ and $s =~ /\%[FUD]/;
306              
307 62 100       170 die "Exec key for '".$self->get('Name')."' contains " .
308             "unknown field code '$1'\n"
309             if $s =~ /(\%[^fFuUdDnNickvm])/;
310              
311 61 100 100     236 croak "Application '".$self->get('Name')."' ".
312             "takes only one argument"
313             if @argv > 1 and $s =~ /\%[fud]/;
314              
315 60 50 33     216 warn "Exec key for '".$self->get('Name')."' contains " .
316             "deprecated field codes\n"
317             if $VERBOSE and $s =~ /%([nNvm])/;
318             }
319 21 100       65 if ($seen == 0) { push @format, '%F' }
  9 50       25  
320             elsif ($seen > 1) {
321             # not allowed according to the spec
322 0         0 warn "Exec key for '".$self->get('Name')."' contains " .
323             "multiple fields for files or uris.\n"
324             }
325              
326             # Expand format
327 21         34 my @exec;
328              
329 21         45 for (@format) {
330 67 100       259 if (/^\%([FUD])$/) {
    100          
331 17 100       152 push @exec,
    100          
332             ($1 eq 'F') ? _paths(@argv) :
333             ($1 eq 'U') ? _uris(@argv) : _dirs(@argv) ;
334             }
335             elsif ($_ eq '%i') {
336 1         3 my $icon = $self->get('Icon');
337 1 50       5 push @exec, '--icon', $icon if defined($icon);
338             }
339             else { # expand with word ( e.g. --input=%f )
340 49         64 my $bad;
341 49         102 s/\%(.)/
342             ($1 eq '%') ? '%' :
343             ($1 eq 'f') ? (_paths(@argv))[0] :
344             ($1 eq 'u') ? (_uris(@argv) )[0] :
345             ($1 eq 'd') ? (_dirs(@argv) )[0] :
346             ($1 eq 'c') ? $self->get('Name') :
347 11 100       64 ($1 eq 'k') ? $$self{file} : '' ;
    100          
    100          
    100          
    100          
    100          
348             /eg;
349              
350 49         147 push @exec, $_;
351             }
352             }
353              
354 21 50 66     196 if (wantarray and $^O eq 'MSWin32') {
355             # Win32 requires different quoting *sigh*
356 0         0 for (grep /"/, @exec) {
357 0         0 s#"#\\"#g;
358 0         0 $_ = qq#"$_"#;
359             }
360             }
361 21 100       157 return wantarray ? (@exec) : _quote(@exec);
362             }
363              
364             sub _split {
365             # Reverse quoting and break string in words.
366             # It allows single quotes to be used, which the spec doesn't.
367 46     46   99 my $string = shift;
368 46         68 my @args;
369 46         278 while ($string =~ /\S/) {
370 126 100       500 if ($string =~ /^(['"])/) {
371 33         167 my $q = $1;
372 33         1865 $string =~ s/^($q(\\.|[^$q])*$q)//s;
373 33 50       226 push @args, $1 if defined $1;
374             }
375 126         454 $string =~ s/(\S*)\s*//; # also fallback for above regex
376 126 50       808 push @args, $1 if defined $1;
377             }
378 46         295 @args = grep length($_), @args;
379 46         116 for (@args) {
380 127 100       531 if (/^(["'])(.*)\1$/s) {
381 32         142 $_ = $2;
382 32         149 s/\\(["`\$\\])/$1/g; # remove backslashes
383             }
384             }
385 46         246 return @args;
386             }
387              
388             sub _quote {
389             # Turn a list of words in a properly quoted Exec key
390 32     32   89 my @words = @_; # copy;
391             return join ' ', map {
392 32 100       106 if (/([\s"'`\\<>~\|\&;\$\*\?#\(\)])/) { # reserved chars
  91         297  
393 19         233 s/(["`\$\\])/\\$1/g; # add backslashes
394 19         85 $_ = qq/"$_"/; # add quotes
395             }
396 91         320 $_;
397             } grep defined($_), @words;
398             }
399              
400             sub _paths {
401             # Check if we need to convert file:// uris to paths
402             # support file:/path file://localhost/path and file:///path
403             # A path like file://host/path is replace by smb://host/path
404             # which the app probably can't open
405             map {
406 21 100   21   1588 $_ = _uri_to_path($_) if s#^file:(?://localhost/+|/|///+)(?!/)#/#i;
  19         87  
407 19         235 s#^file://(?!/)#smb://#i;
408 19         60 $_;
409             } @_;
410             }
411              
412             sub _dirs {
413             # Like _paths, but makes the path a directory
414             map {
415 2 100   2   6 if (-d $_) { $_ }
  3         211  
  2         8  
416             else {
417 1         13 my ($vol, $dirs, undef) = File::Spec->splitpath($_);
418 1         14 File::Spec->catpath($vol, $dirs, '');
419             }
420             } _paths(@_);
421             }
422              
423             sub _uris {
424             # Convert paths to file:// uris
425             map {
426 4 100   4   9 m#^\w+://# ? $_ : 'file://'._path_to_uri($_);
  6         34  
427             } @_;
428             }
429              
430             sub _uri_to_path {
431 7     7   32 my $x = Encode::encode('utf8', $_);
432 7         207 $x = uri_unescape($x);
433 7         76 return Encode::decode('utf8', $x);
434             }
435              
436             sub _path_to_uri {
437 3     3   68 my $path = File::Spec->rel2abs(shift);
438 3         43 my ($volume, $directories, $file) = File::Spec->splitpath($path);
439 3         7 my $uri = '';
440              
441             # actually, on Windows, File URIs look like this:
442             # file:///C:/Program%20Files/MyApp/app.exe
443             # ref: https://blogs.msdn.microsoft.com/ie/2006/12/06/file-uris-in-windows/
444 3 50       12 if ($volume) {
445 0         0 $uri .= '/' . $volume;
446             }
447 3         29 $uri .= join '/', map { uri_escape_utf8($_) } File::Spec->splitdir($directories . $file);
  9         130  
448 3         72 return $uri;
449             }
450              
451             =item C
452              
453             =item C
454              
455             Get a value for KEY from GROUP. If GROUP is not specified 'Desktop Entry' is
456             used. All values are treated as string, so e.g. booleans will be returned as
457             the literal strings "true" and "false".
458              
459             When KEY does not contain a language code you get the translation in the
460             current locale if available or a sensible default. The request a specific
461             language you can add the language part. E.g. C<< $entry->get('Name[nl_NL]') >>
462             can return either the value of the 'Name[nl_NL]', the 'Name[nl]' or the 'Name'
463             key in the Desktop Entry file. Exact language parsing order can be found in the
464             spec. To force you get the untranslated key use either 'Name[C]' or
465             'Name[POSIX]'.
466              
467             =cut
468              
469             # used for (un-)escaping strings
470             my %Chr = (s => ' ', n => "\n", r => "\r", t => "\t", '\\' => '\\');
471             my %Esc = reverse %Chr;
472              
473             sub get {
474 115 100   115 1 1165 my ($self, $group, $key) =
475             (@_ == 2) ? ($_[0], '', $_[1]) : (@_) ;
476 115         204 my $locale = $_locale;
477 115 100       422 if ($key =~ /^(.*?)\[(.*?)\]$/) {
478 6         15 $key = $1;
479 6         13 $locale = _parse_lang($2);
480             }
481              
482 115         296 my @lang = split /\|/, $locale;
483              
484             # Get values that match locale from group
485 115 100       334 $self->read() unless $$self{groups};
486 115         270 my $i = $self->_group($group);
487 115 100       314 return undef unless defined $i;
488 114   100     615 my $lang = join('|', map quotemeta($_), @lang) || 'C';
489 114         6891 my %matches = ( $$self{groups}[$i] =~
490             /^(\Q$key\E\[(?:$lang)\]|\Q$key\E)[^\S\n]*=[^\S\n]*(.*?)\s*$/gm );
491 114 100       494 return undef unless keys %matches;
492              
493             # Find preferred value
494 98         240 my @keys = (map($key."[$_]", @lang), $key);
495 98         402 my ($value) = grep defined($_), @matches{@keys};
496              
497             # Parse string (replace \n, \t, etc.)
498 98 50       233 $value =~ s/\\(.)/$Chr{$1}||$1/eg;
  28         209  
499 98         817 return $value;
500             }
501              
502             sub _group { # returns index for a group name
503 151     151   276 my ($self, $group, $dont_die) = @_;
504 151   100     816 $group ||= 'Desktop Entry';
505 151 50       683 croak "Group name contains invalid characters: $group"
506             if $group =~ /[\[\]\r\n]/;
507 151         208 for my $i (0 .. $#{$$self{groups}}) {
  151         602  
508 158 100       1732 return $i if $$self{groups}[$i] =~ /^\[\Q$group\E\]/;
509             }
510 7         21 return undef;
511             }
512              
513             =item C VALUE, ...)>
514              
515             =item C VALUE, ...)>
516              
517             Set values for one or more keys. If GROUP is not given "Desktop Entry" is used.
518             All values are treated as strings, backslashes, newlines and tabs are escaped.
519             To set a boolean key you need to use the literal strings "true" and "false".
520              
521             Unlike the C call languages are not handled automatically for C.
522             KEY should include the language part if you want to set a translation.
523             E.g. C<< $entry->set("Name[nl_NL]" => "Tekst Verwerker") >> will set a Dutch
524             translation for the Name key. Using either "Name[C]" or "Name[POSIX]" will
525             be equivalent with not giving a language argument.
526              
527             When setting the Exec key without specifying a group it will be parsed
528             and quoted correctly as required by the spec. You can use quoted arguments
529             to include whitespace in a argument, escaping whitespace does not work.
530             To circumvent this quoting explicitly give the group name 'Desktop Entry'.
531              
532             =cut
533              
534             sub set {
535 34     34 1 813945 my $self = shift;
536 34 100       270 my ($group, @data) = ($#_ % 2) ? (undef, @_) : (@_) ;
537              
538 34 100 100     223 $self->read() unless $$self{groups} or ! $$self{file};
539 34         151 my $i = $self->_group($group);
540 34 100       117 unless (defined $i) {
541 6   100     29 $group ||= 'Desktop Entry';
542 6         14 push @{$$self{groups}}, "[$group]\n";
  6         28  
543 6         49 $i = $#{$$self{groups}};
  6         18  
544             }
545              
546 34         126 while (@data) {
547 48         149 my ($k, $v) = splice(@data, 0, 2);
548 48         146 $k =~ s/\[(C|POSIX)\]$//; # remove default locale
549 48         414 my ($word) = ($k =~ /^(.*?)(\[.*?\])?$/);
550             # separate key and locale
551 48 50       144 croak "BUG: Key missing: $k" unless length $word;
552 48 50       158 carp "Key contains invalid characters: $k"
553             if $word =~ /[^A-Za-z0-9-]/;
554 48 100 100     349 $v = _quote( _split($v) ) if ! $group and $k eq 'Exec';
555             # Exec key needs extra quoting
556 48         286 $v =~ s/([\\\n\r\t])/\\$Esc{$1}/g; # add escapes
557 48 100       940 $$self{groups}[$i] =~ s/^\Q$k\E=.*$/$k=$v/m and next;
558 25         168 $$self{groups}[$i] .= "$k=$v\n";
559             }
560             }
561              
562             =item C
563              
564             Returns the (modified) text of the file.
565              
566             =cut
567              
568             sub text {
569 3 100   3 1 26 $_[0]->read() unless $_[0]{groups};
570 3 50       10 return '' unless $_[0]{groups};
571 3         5 s/\n?$/\n/ for @{$_[0]{groups}}; # just to be sure
  3         35  
572 3         6 return join "\n", @{$_[0]{groups}};
  3         107  
573             }
574              
575             =item C
576              
577             =item C
578              
579             Read Desktop Entry data from file or memory buffer.
580             Without argument defaults to file given at constructor.
581              
582             If you gave a file, text buffer or name to the constructor this method will
583             be called automatically.
584              
585             =item C
586              
587             Read Desktop Entry data from filehandle or IO object.
588              
589             =cut
590              
591             sub read {
592 14     14 1 37 my ($self, $file) = @_;
593 14   66     58 $file ||= $$self{file};
594 14 50       36 croak "DesktopEntry has no filename to read from" unless length $file;
595              
596 14         18 my $fh;
597 14 100       29 unless (ref $file) {
598 10 50       392 open $fh, "<$file" or croak "Could not open file: $file";
599             }
600             else {
601 3 50   3   23 open $fh, '<', $file or croak "Could not open SCALAR ref !?";
  3         5  
  3         21  
  4         114  
602             }
603 14         3806 binmode $fh, ':utf8';
604 14         39 $self->read_fh($fh);
605 14         123 close $fh;
606             }
607              
608             sub read_fh {
609 14     14 1 26 my ($self, $fh) = @_;
610 14         49 $$self{groups} = [];
611              
612             # Read groups
613 14         37 my $group = '';
614 14         181 while (my $l = <$fh>) {
615 437         1534 $l =~ s/\r?\n$/\n/; # DOS to Unix conversion
616 437 100       1200 if ($l =~ /^\[(.*?)\]\s*$/) {
617 32 100       98 push @{$$self{groups}}, $group
  18         46  
618             if length $group;
619 32         55 $group = '';
620             }
621 437         1471 $group .= $l;
622             }
623 14         20 push @{$$self{groups}}, $group;
  14         52  
624 14         19 s/\n\n$/\n/ for @{$$self{groups}}; # remove last empty line
  14         168  
625              
626             # Some checks
627 14         29 for (qw/Name Type/) {
628 28 50       75 carp "Required key missing in Desktop Entry: $_"
629             unless defined $self->get($_);
630             }
631 14         37 my $enc = $self->get('Encoding');
632 14 50 66     77 carp "Desktop Entry uses unsupported encoding: $enc"
633             if $enc and $enc ne 'UTF-8';
634             }
635              
636             =item C
637              
638             Write the Desktop Entry data to FILE. Without arguments it writes to
639             the filename given to the constructor if any.
640              
641             The keys Name and Type are required. Type can be either C,
642             C or C. For an application set the optional key C. For
643             a link set the C key.
644              
645             =cut
646              
647             # Officially we should check lines end with LF - this is \n on Unix
648             # but on Windows \n is CR LF, which breaks the spec
649              
650             sub write {
651 1     1 1 7 my $self = shift;
652 1   33     5 my $file = shift || $$self{file};
653 1 50       5 unless ($$self{groups}) {
654 0 0       0 if ($$self{file}) { $self->read() }
  0         0  
655 0         0 else { croak "Can not write empty Desktop Entry file" }
656             }
657              
658             # Check keys
659 1         3 for (qw/Name Type/) {
660 2 50       7 croak "Can not write a desktop file without a $_ field"
661             unless defined $self->get($_);
662             }
663 1         4 $self->set(Version => '1.0', Encoding => 'UTF-8');
664              
665             # Check file writable
666             $file = $self->_data_home_file
667 1 50 33     31 if (! $file or ! -w $file) and defined $$self{name};
      33        
668 1 50       8 croak "No file given for writing Desktop Entry" unless length $file;
669              
670             # Write file
671 1         3 s/\n?$/\n/ for @{$$self{groups}}; # just to be sure
  1         11  
672 1 50       86 open OUT, ">$file" or die "Could not write file: $file\n";
673 1 50       9 binmode OUT, ':utf8' unless $] < 5.008;
674 1         2 print OUT join "\n", @{$$self{groups}};
  1         21  
675 1         49 close OUT;
676             }
677              
678             sub _data_home_file {
679             # create new file name in XDG_DATA_HOME from name
680 1     1   2 my $self = shift;
681 1         4 my @parts = split /-/, $$self{name};
682 1         3 $parts[-1] .= '.desktop';
683 1         9 my $dir = data_home('applications', @parts[0 .. $#parts-1]);
684 1 50       61 unless (-d $dir) { # create dir if it doesn't exist
685 1         9 require File::Path;
686 1         264 File::Path::mkpath($dir);
687             }
688 1         7 return data_home('applications', @parts);
689             }
690              
691             =back
692              
693             =head2 Backwards Compatibility
694              
695             Methods supported for backwards compatibility with 0.02.
696              
697             =over 4
698              
699             =item C
700              
701             Alias for C.
702              
703             =item C
704              
705             Alias for C.
706              
707             =item C
708              
709             Identical to C.
710             LANG defaults to 'C', GROUP is optional.
711              
712             =cut
713              
714 1     1 1 397 sub new_from_file { $_[0]->new($_[1]) }
715              
716 2     2 1 24 sub new_from_data { $_[0]->new(\$_[1]) }
717              
718             sub get_value {
719 2     2 1 5 my ($self, $key, $group, $locale) = @_;
720 2   50     13 $locale ||= 'C';
721 2         5 $key .= "[$locale]";
722 2 100       10 $group ? $self->get($group, $key) : $self->get($key);
723             }
724              
725             =back
726              
727             =head1 NON-UNIX PLATFORMS
728              
729             This module has a few bits of code to make it work on Windows. It handles
730             C uri a bit different and it uses L. On other
731             platforms your mileage may vary.
732              
733             Please note that the specification is targeting Unix platforms only and
734             will only have limited relevance on other platforms. Any platform-dependent
735             behavior in this module should be considered an extension of the spec.
736              
737             =cut
738              
739             if ($^O eq 'MSWin32') {
740             # Re-define some modules - I assume this block gets optimized away by the
741             # interpreter when not running on windows.
742 6     6   56 no warnings;
  6         11  
  6         6522  
743              
744             # Wrap _paths() to remove first '/'
745             # As a special case translate SMB file:// uris
746             my $_paths = \&_paths;
747             *_paths = sub {
748             my @paths = map {
749             s#^file:////(?!/)#smb://#;
750             $_;
751             } @_;
752             map {
753             s#^/+([a-z]:/)#$1#i;
754             $_;
755             } &$_paths(@paths);
756             };
757              
758             # Wrap _uris() to remove '\' in path
759             my $_uris = \&_uris;
760             *_uris = sub {
761             map {
762             s#\\#/#g;
763             $_;
764             } &$_uris(@_);
765             };
766              
767             # Using Win32::Process because fork is not native on win32
768             # Effect is that closing an application spawned with fork
769             # can kill the parent process as well when using Gtk2
770             *run = sub {
771             my ($self, @files) = @_;
772              
773             my $cmd = eval { $self->parse_Exec(@files) };
774             warn $@ if $@; # run should not die
775              
776             my $bin = (_split($cmd))[0];
777             unless (-f $bin) { # we need the real binary path
778             my ($b) = grep {-f $_}
779             map File::Spec->catfile($_, $bin),
780             split /[:;]/, $ENV{PATH} ;
781             if (-f $b) { $bin = $b }
782             else {
783             warn "Could not find application: $bin\n";
784             return;
785             }
786             }
787              
788             my $dir = $self->get('Path') || '.';
789              
790             if ($VERBOSE) {
791             warn "Running from directory: $dir" unless $dir eq '.';
792             warn "Running: $cmd\n";
793             }
794             my $obj;
795             eval {
796             Win32::Process::Create(
797             $obj, $bin, $cmd, 0, &NORMAL_PRIORITY_CLASS, $dir );
798             };
799             warn $@ if $@;
800             return $obj;
801             };
802              
803             }
804              
805             1;
806              
807             __END__