#!/usr/bin/env perl
# vim:ts=4:sw=4:expandtab
#
-# © 2012-2013 Michael Stapelberg
+# © 2012 Michael Stapelberg
#
# No dependencies except for perl ≥ v5.10
# reads in a whole file
sub slurp {
my ($filename) = @_;
- open(my $fh, '<', $filename) or die "$!";
+ my $fh;
+ if (!open($fh, '<', $filename)) {
+ warn "Could not open $filename: $!";
+ return undef;
+ }
local $/;
my $result;
eval {
'dmenu=s' => \$dmenu_cmd,
'entry-type=s' => \@entry_types,
'version' => sub {
- say "dmenu-desktop 1.4 © 2012-2013 Michael Stapelberg";
+ say "dmenu-desktop 1.5 © 2012 Michael Stapelberg";
exit 0;
},
'help' => sub {
die "Could not parse command line options" unless $result;
# Filter entry types and set default type(s) if none selected
-my @valid_types = ('name', 'command', 'filename');
-@entry_types = grep { $_ ~~ @valid_types } @entry_types;
+my $valid_types = {
+ name => 1,
+ command => 1,
+ filename => 1,
+};
+@entry_types = grep { exists($valid_types->{$_}) } @entry_types;
@entry_types = ('name', 'command') unless @entry_types;
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
$names{$key} = $value;
} elsif ($key eq 'Exec' ||
$key eq 'TryExec' ||
+ $key eq 'Path' ||
$key eq 'Type') {
$apps{$base}->{$key} = $value;
} elsif ($key eq 'NoDisplay' ||
}
}
- if ('name' ~~ @entry_types) {
+ if ((scalar grep { $_ eq 'name' } @entry_types) > 0) {
if (exists($choices{$name})) {
# There are two .desktop files which contain the same “Name” value.
# I’m not sure if that is allowed to happen, but we disambiguate the
}
$choices{$name} = $app;
+ next;
}
- if ('command' ~~ @entry_types) {
- my ($command) = split(' ', $apps{$app}->{Exec});
+ if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
+ my $command = $apps{$app}->{Exec};
+
+ # Handle escape sequences (should be done for all string values, but does
+ # matter here).
+ my %escapes = (
+ '\\s' => ' ',
+ '\\n' => '\n',
+ '\\t' => '\t',
+ '\\r' => '\r',
+ '\\\\' => '\\',
+ );
+ $command =~ s/(\\[sntr\\])/$escapes{$1}/go;
+
+ # Extract executable
+ if ($command =~ m/^\s*([^\s\"]+)(?:\s|$)/) {
+ # No quotes
+ $command = $1;
+ } elsif ($command =~ m/^\s*\"([^\"\\]*(?:\\.[^\"\\]*)*)\"(?:\s|$)/) {
+ # Quoted, remove quotes and fix escaped characters
+ $command = $1;
+ $command =~ s/\\([\"\`\$\\])/$1/g;
+ } else {
+ # Invalid quotes, fallback to whitespace
+ ($command) = split(' ', $command);
+ }
# Don’t add “geany” if “Geany” is already present.
my @keys = map { lc } keys %choices;
- next if lc(basename($command)) ~~ @keys;
-
- $choices{basename($command)} = $app;
+ if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) {
+ $choices{basename($command)} = $app;
+ }
+ next;
}
- if ('filename' ~~ @entry_types) {
+ if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
my $filename = basename($app, '.desktop');
# Don’t add “geany” if “Geany” is already present.
my @keys = map { lc } keys %choices;
- next if lc($filename) ~~ @keys;
+ next if (scalar grep { $_ eq lc($filename) } @keys) > 0;
$choices{$filename} = $app;
}
# };
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
-# ┃ Run dmenu to ask the user for her choice ┃
+# ┃ Run dmenu to ask the user for their choice ┃
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
# open2 will just make dmenu’s STDERR go to our own STDERR.
$choice = quote($choice);
$location = quote($location);
+$name = quote($name);
# Remove deprecated field codes, as the spec dictates.
$exec =~ s/%[dDnNvm]//g;
# Literal % characters are represented as %%.
$exec =~ s/%%/%/g;
+if (exists($app->{Path}) && $app->{Path} ne '') {
+ $exec = 'cd ' . quote($app->{Path}) . ' && ' . $exec;
+}
+
my $nosn = '';
my $cmd;
if (exists($app->{Terminal}) && $app->{Terminal}) {
=head1 VERSION
-Version 1.4
+Version 1.5
=head1 AUTHOR