# 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 {
}
}
-my $entry_type = 'both';
+my @entry_types;
my $dmenu_cmd = 'dmenu -i';
my $result = GetOptions(
'dmenu=s' => \$dmenu_cmd,
- 'entry-type=s' => \$entry_type,
+ 'entry-type=s' => \@entry_types,
'version' => sub {
- say "dmenu-desktop 1.3 © 2012 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 => 1,
+ command => 1,
+ filename => 1,
+};
+@entry_types = grep { exists($valid_types->{$_}) } @entry_types;
+@entry_types = ('name', 'command') unless @entry_types;
+
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
# ┃ Convert LC_MESSAGES into an ordered list of suffixes to search for in the ┃
# ┃ .desktop files (e.g. “Name[de_DE@euro]” for LC_MESSAGES=de_DE.UTF-8@euro ┃
$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 ($entry_type eq 'name' || $entry_type eq 'both') {
+ 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;
}
- if ($entry_type eq 'command' || $entry_type eq 'both') {
+ if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
my ($command) = split(' ', $apps{$app}->{Exec});
# Don’t add “geany” if “Geany” is already present.
my @keys = map { lc } keys %choices;
- next if lc(basename($command)) ~~ @keys;
+ next if (scalar grep { $_ eq lc(basename($command)) } @keys) > 0;
$choices{basename($command)} = $app;
}
+
+ 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 (scalar grep { $_ eq lc($filename) } @keys) > 0;
+
+ $choices{$filename} = $app;
+ }
}
# %choices now looks like this:
# };
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
-# ┃ 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.
my ($dmenu_out, $dmenu_in);
-my $pid = open2($dmenu_out, $dmenu_in, $dmenu_cmd);
+my $pid = eval {
+ open2($dmenu_out, $dmenu_in, $dmenu_cmd);
+} or do {
+ print STDERR "$@";
+ say STDERR "Running dmenu failed. Is dmenu installed at all? Try running dmenu -v";
+ exit 1;
+};
+
binmode $dmenu_in, ':utf8';
binmode $dmenu_out, ':utf8';
last;
}
if (!defined($app)) {
- die "Invalid input: “$choice” does not match any application.";
+ warn "Invalid input: “$choice” does not match any application. Trying to execute nevertheless.";
+ $app->{Name} = '';
+ $app->{Exec} = $choice;
+ # We assume that the app is old and does not support startup
+ # notifications because it doesn’t ship a desktop file.
+ $app->{StartupNotify} = 0;
+ $app->{_Location} = '';
}
}
$choice = quote($choice);
$location = quote($location);
+$name = quote($name);
# Remove deprecated field codes, as the spec dictates.
$exec =~ s/%[dDnNvm]//g;
# XXX: Icons are not implemented. Is the complexity (looking up the path if
# only a name is given) actually worth it?
#$exec =~ s/%i/--icon $icon/g;
+$exec =~ s/%i//g;
# location of .desktop file
$exec =~ s/%k/$location/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 SYNOPSIS
- i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=both]
+ i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
=head1 DESCRIPTION
=item B<--entry-type=type>
-Display the (localized) "Name" (type = name) or the command (type = command) or
-both (type = both) in dmenu.
+Display the (localized) "Name" (type = name), the command (type = command) or
+the (*.desktop) filename (type = filename) in dmenu. This option can be
+specified multiple times.
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
-command) and both (type = both).
+command), and "libreoffice-writer" (type = filename).
=back
=head1 VERSION
-Version 1.3
+Version 1.5
=head1 AUTHOR