File Coverage

blib/lib/File/DesktopEntry.pm
Criterion Covered Total %
statement 266 279 95.3
branch 132 172 76.7
condition 39 61 63.9
subroutine 38 38 100.0
pod 17 17 100.0
total 492 567 86.7


line stmt bran cond sub pod time code
1             package File::DesktopEntry;
2              
3 7     7   845751 use strict;
  7         16  
  7         260  
4 7     7   31 use warnings;
  7         16  
  7         462  
5              
6 7     7   40 use vars qw/$AUTOLOAD/;
  7         19  
  7         453  
7 7     7   42 use Carp;
  7         38  
  7         684  
8 7     7   2975 use Encode;
  7         101880  
  7         960  
9 7     7   63 use File::Spec;
  7         11  
  7         378  
10 7     7   3106 use File::BaseDir 0.03 qw/data_files data_home/;
  7         8634  
  7         543  
11 7     7   3731 use URI::Escape;
  7         20127  
  7         42295  
12              
13             our $VERSION = '0.23';
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 430040 my ($class, $file) = @_;
92 19         69 my $self = bless {}, $class;
93 19 100       219 if (! defined $file) { # initialize new file
    100          
    100          
94 4         37 $self->set(Version => '1.0', Encoding => 'UTF-8');
95             }
96 4         20 elsif (ref $file) { $self->read($file) } # SCALAR
97 10         39 elsif ($file =~ /[\/\\\.]/) { $$self{file} = $file } # file
98             else {
99 1         4 $$self{file} = $class->lookup($file); # name
100 1         4 $$self{name} = $file;
101             }
102 19         101 return $self;
103             }
104              
105             sub AUTOLOAD {
106 20     20   773594 $AUTOLOAD =~ s/.*:://;
107 20 100       465 return if $AUTOLOAD eq 'DESTROY';
108 3 50       20 croak "No such method: File::DesktopEntry::$AUTOLOAD"
109             unless $AUTOLOAD =~ /^[A-Z][A-Za-z0-9-]+$/;
110 3         14 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 202242 my (undef, $name) = @_;
121 2         5 $name .= '.desktop';
122 2         10 my $file = data_files('applications', $name);
123 2 50 33     195 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         15 return $file;
129             }
130              
131             sub _parse_lang {
132             # lang might look like lang_COUNTRY.ENCODING@MODIFIER
133 18     18   2523 my $lang = shift;
134 18 100 66     156 return '' if !$lang or $lang eq 'C' or $lang eq 'POSIX';
      100        
135 7 50       43 $lang =~ m{^
136             ([^_@\.]+) # lang $1
137             (?: _ ([^@\.]+) )? # COUNTRY $2
138             (?: \. [^@]+ )? # ENCODING
139             (?: \@ (.+) )? # MODIFIER $3
140             $}x or return '';
141 7         28 my ($l, $c, $m) = ($1, $2, $3);
142 7 100 100     40 my @locale = (
    100          
    100          
143             $l,
144             ($m ? "$l\@$m" : ()),
145             ($c ? "$l\_$c" : ()),
146             (($m && $c) ? "$l\_$c\@$m" : ()) );
147 7         33 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         5 my $exec = $self->get('Exec');
167 1 50       5 croak "No Exec string defined for desktop entry" unless length $exec;
168 1         5 $exec =~ s/\%\%//g;
169 1         12 return $exec =~ /\%U/i;
170             }
171              
172             sub wants_list {
173 1     1 1 3 my $self = shift;
174 1         4 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         9 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 5241 my $pid = fork;
213 3 100       692 return $pid if $pid; # parent process
214 1         64 unshift @_, 'exec'; goto \&_run;
  1         116  
215             }
216              
217 5     5 1 62 sub system { unshift @_, 'system'; goto \&_run }
  5         40  
218              
219 1     1 1 2571 sub exec { unshift @_, 'exec'; goto \&_run }
  1         182  
220              
221             sub _run {
222 7     7   95 my $call = shift;
223 7         93 my $self = shift;
224              
225 7 50       310 croak "Desktop entry is not an Application"
226             unless $self->get('Type') eq 'Application';
227              
228 7         96 my @exec = $self->parse_Exec(@_);
229              
230 7         40 my $t = $self->get('Terminal');
231 7 50 33     29 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         17 my $cwd;
237 7 100       24 if (my $path = $self->get('Path')) {
238 3         97 require Cwd;
239 3         110 $cwd = Cwd::getcwd();
240 3 50       132 chdir $path or croak "Could not change to dir: $path";
241 3         130 $ENV{PWD} = $path;
242 3 50       28 warn "Running from directory: $path\n" if $VERBOSE;
243             }
244              
245 7 50       50 warn "Running: "._quote(@exec)."\n" if $VERBOSE;
246              
247 7 100       28 if ($call eq 'exec') { CORE::exec {$exec[0]} @exec; exit 1 }
  2         16  
  2         0  
  0         0  
248 5         10 else { CORE::system {$exec[0]} @exec }
  5         2187489  
249 5 0 33     404 warn "Error: $!\n" if $VERBOSE and $?;
250              
251 5 100       200 if (defined $cwd) {
252 2 50       230 chdir $cwd or croak "Could not change back to dir: $cwd";
253 2         166 $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 3196 my ($self, @argv) = @_;
294 23         71 my @format = _split( $self->get('Exec') );
295              
296             # Check format
297 23         54 my $seen = 0;
298 23         52 for (@format) {
299 62         96 my $s = $_; # copy;
300 62         158 $s =~ s/\%\%//g;
301 62         156 $seen += ($s =~ /\%[fFuUdD]/);
302              
303 62 50 66     339 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       175 die "Exec key for '".$self->get('Name')."' contains " .
308             "unknown field code '$1'\n"
309             if $s =~ /(\%[^fFuUdDnNickvm])/;
310              
311 61 100 100     184 croak "Application '".$self->get('Name')."' ".
312             "takes only one argument"
313             if @argv > 1 and $s =~ /\%[fud]/;
314              
315 60 50 33     197 warn "Exec key for '".$self->get('Name')."' contains " .
316             "deprecated field codes\n"
317             if $VERBOSE and $s =~ /%([nNvm])/;
318             }
319 21 100       71 if ($seen == 0) { push @format, '%F' }
  9 50       55  
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         47 my @exec;
328              
329 21         70 for (@format) {
330 67 100       219 if (/^\%([FUD])$/) {
    100          
331 17 100       100 push @exec,
    100          
332             ($1 eq 'F') ? _paths(@argv) :
333             ($1 eq 'U') ? _uris(@argv) : _dirs(@argv) ;
334             }
335             elsif ($_ eq '%i') {
336 1         5 my $icon = $self->get('Icon');
337 1 50       6 push @exec, '--icon', $icon if defined($icon);
338             }
339             else { # expand with word ( e.g. --input=%f )
340 49         67 my $bad;
341 49         117 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       82 ($1 eq 'k') ? $$self{file} : '' ;
    100          
    100          
    100          
    100          
    100          
348             /eg;
349              
350 49         135 push @exec, $_;
351             }
352             }
353              
354 21 50 66     190 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       198 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   152 my $string = shift;
368 46         255 my @args;
369 46         316 while ($string =~ /\S/) {
370 126 100       372 if ($string =~ /^(['"])/) {
371 33         179 my $q = $1;
372 33         4053 $string =~ s/^($q(\\.|[^$q])*$q)//s;
373 33 50       216 push @args, $1 if defined $1;
374             }
375 126         528 $string =~ s/(\S*)\s*//; # also fallback for above regex
376 126 50       641 push @args, $1 if defined $1;
377             }
378 46         346 @args = grep length($_), @args;
379 46         108 for (@args) {
380 127 100       540 if (/^(["'])(.*)\1$/s) {
381 32         93 $_ = $2;
382 32         204 s/\\(["`\$\\])/$1/g; # remove backslashes
383             }
384             }
385 46         255 return @args;
386             }
387              
388             sub _quote {
389             # Turn a list of words in a properly quoted Exec key
390 32     32   104 my @words = @_; # copy;
391             return join ' ', map {
392 32 100       100 if (/([\s"'`\\<>~\|\&;\$\*\?#\(\)])/) { # reserved chars
  91         629  
393 19         204 s/(["`\$\\])/\\$1/g; # add backslashes
394 19         54 $_ = qq/"$_"/; # add quotes
395             }
396 91         346 $_;
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   10346 $_ = _uri_to_path($_) if s#^file:(?://localhost/+|/|///+)(?!/)#/#i;
  19         137  
407 19         229 s#^file://(?!/)#smb://#i;
408 19         73 $_;
409             } @_;
410             }
411              
412             sub _dirs {
413             # Like _paths, but makes the path a directory
414             map {
415 2 100   2   7 if (-d $_) { $_ }
  3         258  
  2         12  
416             else {
417 1         23 my ($vol, $dirs, undef) = File::Spec->splitpath($_);
418 1         19 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   10 m#^\w+://# ? $_ : 'file://'._path_to_uri($_);
  6         47  
427             } @_;
428             }
429              
430             sub _uri_to_path {
431 7     7   69 my $x = Encode::encode('utf8', $_);
432 7         197 $x = uri_unescape($x);
433 7         110 return Encode::decode('utf8', $x);
434             }
435              
436             sub _path_to_uri {
437 3     3   74 my $path = File::Spec->rel2abs(shift);
438 3         50 my ($volume, $directories, $file) = File::Spec->splitpath($path);
439 3         9 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       11 if ($volume) {
445 0         0 $uri .= '/' . $volume;
446             }
447 3         32 $uri .= join '/', map { uri_escape_utf8($_) } File::Spec->splitdir($directories . $file);
  9         214  
448 3         88 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 1878 my ($self, $group, $key) =
475             (@_ == 2) ? ($_[0], '', $_[1]) : (@_) ;
476 115         217 my $locale = $_locale;
477 115 100       449 if ($key =~ /^(.*?)\[(.*?)\]$/) {
478 6         15 $key = $1;
479 6         14 $locale = _parse_lang($2);
480             }
481              
482 115         352 my @lang = split /\|/, $locale;
483              
484             # Get values that match locale from group
485 115 100       331 $self->read() unless $$self{groups};
486 115         300 my $i = $self->_group($group);
487 115 100       280 return undef unless defined $i;
488 114   100     598 my $lang = join('|', map quotemeta($_), @lang) || 'C';
489 114         11853 my %matches = ( $$self{groups}[$i] =~
490             /^(\Q$key\E\[(?:$lang)\]|\Q$key\E)[^\S\n]*=[^\S\n]*(.*?)\s*$/gm );
491 114 100       673 return undef unless keys %matches;
492              
493             # Find preferred value
494 98         291 my @keys = (map($key."[$_]", @lang), $key);
495 98         398 my ($value) = grep defined($_), @matches{@keys};
496              
497             # Parse string (replace \n, \t, etc.)
498 98 50       304 $value =~ s/\\(.)/$Chr{$1}||$1/eg;
  28         219  
499 98         862 return $value;
500             }
501              
502             sub _group { # returns index for a group name
503 151     151   369 my ($self, $group, $dont_die) = @_;
504 151   100     976 $group ||= 'Desktop Entry';
505 151 50       4924 croak "Group name contains invalid characters: $group"
506             if $group =~ /[\[\]\r\n]/;
507 151         243 for my $i (0 .. $#{$$self{groups}}) {
  151         574  
508 158 100       1776 return $i if $$self{groups}[$i] =~ /^\[\Q$group\E\]/;
509             }
510 7         24 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 2031338 my $self = shift;
536 34 100       304 my ($group, @data) = ($#_ % 2) ? (undef, @_) : (@_) ;
537              
538 34 100 100     350 $self->read() unless $$self{groups} or ! $$self{file};
539 34         160 my $i = $self->_group($group);
540 34 100       135 unless (defined $i) {
541 6   100     38 $group ||= 'Desktop Entry';
542 6         11 push @{$$self{groups}}, "[$group]\n";
  6         24  
543 6         8 $i = $#{$$self{groups}};
  6         20  
544             }
545              
546 34         145 while (@data) {
547 48         228 my ($k, $v) = splice(@data, 0, 2);
548 48         171 $k =~ s/\[(C|POSIX)\]$//; # remove default locale
549 48         448 my ($word) = ($k =~ /^(.*?)(\[.*?\])?$/);
550             # separate key and locale
551 48 50       231 croak "BUG: Key missing: $k" unless length $word;
552 48 50       167 carp "Key contains invalid characters: $k"
553             if $word =~ /[^A-Za-z0-9-]/;
554 48 100 100     346 $v = _quote( _split($v) ) if ! $group and $k eq 'Exec';
555             # Exec key needs extra quoting
556 48         312 $v =~ s/([\\\n\r\t])/\\$Esc{$1}/g; # add escapes
557 48 100       1320 $$self{groups}[$i] =~ s/^\Q$k\E=.*$/$k=$v/m and next;
558 25         149 $$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 24 $_[0]->read() unless $_[0]{groups};
570 3 50       9 return '' unless $_[0]{groups};
571 3         4 s/\n?$/\n/ for @{$_[0]{groups}}; # just to be sure
  3         31  
572 3         5 return join "\n", @{$_[0]{groups}};
  3         19  
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 31 my ($self, $file) = @_;
593 14   66     68 $file ||= $$self{file};
594 14 50       65 croak "DesktopEntry has no filename to read from" unless length $file;
595              
596 14         20 my $fh;
597 14 100       43 unless (ref $file) {
598 10 50       625 open $fh, "<$file" or croak "Could not open file: $file";
599             }
600             else {
601 4 50       65 open $fh, '<', $file or croak "Could not open SCALAR ref !?";
602             }
603 14         102 binmode $fh, ':utf8';
604 14         61 $self->read_fh($fh);
605 14         261 close $fh;
606             }
607              
608             sub read_fh {
609 14     14 1 34 my ($self, $fh) = @_;
610 14         47 $$self{groups} = [];
611              
612             # Read groups
613 14         27 my $group = '';
614 14         434 while (my $l = <$fh>) {
615 437         1657 $l =~ s/\r?\n$/\n/; # DOS to Unix conversion
616 437 100       1207 if ($l =~ /^\[(.*?)\]\s*$/) {
617 32 100       76 push @{$$self{groups}}, $group
  18         49  
618             if length $group;
619 32         54 $group = '';
620             }
621 437         1359 $group .= $l;
622             }
623 14         26 push @{$$self{groups}}, $group;
  14         43  
624 14         24 s/\n\n$/\n/ for @{$$self{groups}}; # remove last empty line
  14         163  
625              
626             # Some checks
627 14         33 for (qw/Name Type/) {
628 28 50       142 carp "Required key missing in Desktop Entry: $_"
629             unless defined $self->get($_);
630             }
631 14         40 my $enc = $self->get('Encoding');
632 14 50 66     78 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 5 my $self = shift;
652 1   33     4 my $file = shift || $$self{file};
653 1 50       7 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       26 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     38 if (! $file or ! -w $file) and defined $$self{name};
      33        
668 1 50       3 croak "No file given for writing Desktop Entry" unless length $file;
669              
670             # Write file
671 1         2 s/\n?$/\n/ for @{$$self{groups}}; # just to be sure
  1         11  
672 1 50       124 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         17  
675 1         50 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         2 $parts[-1] .= '.desktop';
683 1         9 my $dir = data_home('applications', @parts[0 .. $#parts-1]);
684 1 50       114 unless (-d $dir) { # create dir if it doesn't exist
685 1         12 require File::Path;
686 1         282 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 1027 sub new_from_file { $_[0]->new($_[1]) }
715              
716 2     2 1 146979 sub new_from_data { $_[0]->new(\$_[1]) }
717              
718             sub get_value {
719 2     2 1 10 my ($self, $key, $group, $locale) = @_;
720 2   50     27 $locale ||= 'C';
721 2         4 $key .= "[$locale]";
722 2 100       8 $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 7     7   107 no warnings;
  7         15  
  7         4818  
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__