#!/usr/bin/perl # Copyright (C) 2011-2012 Trizen . # # 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 3 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, see . # Fluxbox Menu Generator # A simple menu generator for the Fluxbox Window Manager # Should be installed in $PATH before the first execution! # License: GPLv3 # Created on: 01 August 2010 # Latest edit on: 03 March 2012 # Website: http://trizen.googlecode.com # use strict; # use warnings 'all'; my $pkgname = 'fbmenugen'; my $version = '0.7.2'; my ($generate_menu, $icons, $reconfigure, $stdout_config); my (@desktop_files, %icons_db, %categories_icons); our ($CONFIG, $SCHEMA); my $home_dir = $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($<))[7] || `echo -n ~`; my $xdg_config_home = $ENV{XDG_CONFIG_HOME} || $home_dir . '/.config'; my $config_dir = "$xdg_config_home/$pkgname"; my $config_file = "$config_dir/configuration.pl"; my $icons_db = "$config_dir/icons.db"; my $menufile = "$home_dir/.fluxbox/menu"; foreach my $arg (@ARGV) { if ($arg eq '-i') { $icons = 1; $generate_menu = 1; } elsif ($arg eq '-g') { $generate_menu = 1; } elsif ($arg eq '-r') { $reconfigure = 1; } elsif ($arg eq '-d') { unlink $icons_db; } elsif ($arg eq '-C') { $reconfigure = 1; $stdout_config = 1; } elsif ($arg eq '-v') { print "$pkgname $version\n"; exit; } elsif ($arg eq '-h') { usage(); } } unless (-e $config_dir) { require File::Path; File::Path::make_path($config_dir) or die "Unable to create $config_dir: $!\n"; } if (not -e $config_file or $reconfigure) { my $fh = *STDOUT; unless ($stdout_config) { open $fh, '>', $config_file or die "Open failed: $!"; } print {$fh} <<'CONFIG_FILE'; #!/usr/bin/perl # fbmenugen config file # # SCHEMA supports the following keys: item, cat, config, exit, fluxbox, regenerate, raw, sep # # Posible values for each of this types are: # For 'item': [COMMAND, NAME, ICON] # For 'sep': undef - horizontal separator # For 'cat': Any of the posible categories. 'cat => [CATEGORY, NAME, IMAGE]' - image is optional # For 'raw': [COMMAND, NAME, ICON] # Example: {raw => ' [exec] (Geeqie) {geeqie -r } '}, # NOTE: # * Keys and values are case sensitive. Keep all keys lowercase. # * ICON can be a either a direct path to a icon or a valid icon name # * Category names are case insensitive. (ex: X-XFCE and x_xfce are equivalent) # For regular expressions # * is better to use qr/REGEX/ instead of 'REGEX' my %items = ( terminal => 'xterm', editor => "xterm -e $ENV{EDITOR}", file_manager => 'pcmanfm', web_browser => 'firefox', instant_messaging => 'pidgin', run_command => 'gmrun', lock_command => 'xscreensaver-command -lock', ); our $CONFIG = { # Menu title title => 'Fluxbox', # Example: [ "$ENV{'HOME'}/.local/share/applications", '/my/path' ] desktop_files_paths => ['/usr/share/applications'], # File where to look for icon theme (default: ~/.gtkrc-2.0) gtk_rc_file => undef, # When 'Terminal=true' open_in_terminal => "$items{terminal} -e %s", # Editor command open_in_editor => "$items{editor}", # Ignore desktop files if their filenames match a regex ignore_file_name_re => undef, # Ignore applications if their names match a regex ignore_app_name_re => undef, # Ignore applications if their commands match a regex ignore_app_command_re => undef, # Ignore desktop files if their content match a regex ignore_file_content_re => undef, # Remove from every command something matched by a regex (/g) command_rem_re => undef, # Look in this directories first (when generating icons.db) dirs_first_to_look => [], # Look in this directories as a second icon theme (when generating icons.db) dirs_middle_to_look => [], # Look in this directories, as a backup plan (when generating icons.db) dirs_last_to_look => [], }; our $SCHEMA = [ # COMMAND LABEL ICON {item => [$items{file_manager}, 'File Manager', 'file-manager']}, {item => [$items{terminal}, 'Terminal', 'terminal']}, {item => [$items{editor}, 'Editor', 'text-editor']}, {item => [$items{web_browser}, 'Web Browser', 'web-browser']}, {item => [$items{run_command}, 'Run command', 'system-run']}, {item => [$items{instant_messaging}, 'Instant messaging', 'system-users']}, #{item => [$items{lock_command}, 'Lock', 'lock']}, {sep => undef}, # NAME LABEL ICON {cat => ['utility', 'Accessories', 'applications-utilities']}, {cat => ['development', 'Development', 'applications-development']}, {cat => ['education', 'Education', 'applications-science']}, {cat => ['game', 'Games', 'applications-games']}, {cat => ['graphics', 'Graphics', 'applications-graphics']}, {cat => ['audiovideo', 'Multimedia', 'applications-multimedia']}, {cat => ['network', 'Network', 'applications-internet']}, {cat => ['office', 'Office', 'applications-office']}, {cat => ['settings', 'Settings', 'applications-accessories']}, {cat => ['system', 'System', 'applications-system']}, #{cat => ['qt', 'QT Applications', 'qtlogo']}, #{cat => ['gtk', 'GTK Applications', 'gnome-applications']}, #{cat => ['x_xfce', 'XFCE Applications', 'applications-other']}, #{cat => ['gnome', 'GNOME Applications', 'gnome-applications']}, #{cat => ['consoleonly', 'CLI Applications', 'applications-utilities']}, # LABEL ICON {config => ['Configure menu', 'preferences-desktop']}, {fluxbox => ['Fluxbox menu', 'package_settings']}, {sep => undef}, {regenerate => ['Regenerate', 'gtk-refresh']}, {exit => ['Exit', 'exit']}, ]; CONFIG_FILE close $fh; exit 0; } sub usage { print <<"HELP"; usage: $0 [options]\n options : -g : (re)generate a simple menu -i : (re)generate a menu with icons -d : (re)generate icons.db (with -i) -r : (re)write the configuration file -C : print configuration to STDOUT ** FBMenu : $menufile ** Config : $config_file\n HELP exit 0; } usage() unless $generate_menu; sub push_more { opendir(my $dir_h, $_[0]) or return; foreach my $file (readdir $dir_h) { push @desktop_files, "$_[0]/$file" if substr($file, -8) eq '.desktop'; } closedir $dir_h; } do $config_file; my $date = localtime time; my $generated_menu = <<"HEADER"; # Fluxbox Right-Click Menu - Config file # Generated on $date # Generated with $pkgname (Fluxbox Menu Generator) v$version\n [begin] ($CONFIG->{title}) [encoding] {UTF-8} HEADER foreach my $dir (@{$CONFIG->{desktop_files_paths}}) { opendir(my $dir_h, $dir) or next; foreach my $file (readdir $dir_h) { push @desktop_files, "$dir/$file" and next if substr($file, -8) eq '.desktop'; if ($file eq q{.} or $file eq q{..}) { next } push_more("$dir/$file") if -d "$dir/$file"; } closedir $dir_h; } unless (-e "$menufile.backup") { if (-e $menufile) { print "\n* Creating backup of menu at: $menufile.backup\n"; rename $menufile, "$menufile.backup" or warn "[!] Unable to rename menu to menu.backup: $!\n"; } else { warn "\n[!] Unable to find: $menufile\n"; } } if ($icons) { # Categories: name => icon (%categories_icons) = ( 'utility' => 'applications-accessories', 'development' => 'applications-development', 'education' => 'applications-science', 'game' => 'applications-games', 'graphics' => 'applications-graphics', 'audiovideo' => 'applications-multimedia', 'network' => 'applications-internet', 'office' => 'applications-office', 'settings' => 'preferences-system', 'system' => 'applications-system', 'config' => 'preferences-desktop' ); # Load/Create icon database require GDBM_File; GDBM_File->import; tie %icons_db, 'GDBM_File', $icons_db, &GDBM_WRCREAT, 0640; } print "\n* Generating menu...\n"; sub store_icons { return if substr($File::Find::name, -4, 1) ne q{.}; return if substr($_, -4, 4, '') eq '.svg'; return if exists $icons_db{$_}; $icons_db{$_} = $File::Find::name; } sub get_icon { my ($icon_name) = @_; if (length $icon_name) { $icon_name = unpack('A*', $icon_name) if substr($icon_name, -1) eq q{ }; if (chr ord $icon_name eq '/') { return $icon_name if -f $icon_name; } else { $icon_name =~ s/\.\w{3}$//; } } else { return ''; } my $icon = $icons_db{$icon_name}; unless (defined $icon) { my $gtk_rc_file = $CONFIG->{gtk_rc_file} // "$home_dir/.gtkrc-2.0"; my $icon_theme; if (-r $gtk_rc_file) { if (sysopen my $gtk_rc_fh, $gtk_rc_file, 0) { my $gtk_rc_content; sysread $gtk_rc_fh, $gtk_rc_content, -s $gtk_rc_file; if ($gtk_rc_content =~ /^\s*gtk-icon-theme-name\s*=\s*["']?([^'"\r\n]+)/im) { $icon_theme = $1; } close $gtk_rc_fh; } } my $personal_theme; require File::Find; # Look in this directories first if (ref $CONFIG->{dirs_first_to_look} eq 'ARRAY' and (my @dirs = grep { -d } @{$CONFIG->{dirs_first_to_look}})) { File::Find::find(\&store_icons, @dirs); } if (defined $icon_theme) { if (-d "/usr/share/icons/$icon_theme/") { $personal_theme = "/usr/share/icons/$icon_theme/"; } elsif (-d "$home_dir/.icons/$icon_theme/") { $personal_theme = "$home_dir/.icons/$icon_theme/"; } elsif (-d "$home_dir/.local/share/icons/$icon_theme/") { $personal_theme = "$home_dir/.local/share/icons/$icon_theme/"; } if (defined $personal_theme) { File::Find::find(\&store_icons, $personal_theme); } # Look in this directories as a second icon theme if (ref $CONFIG->{dirs_middle_to_look} eq 'ARRAY' and (my @dirs = grep { -d } @{$CONFIG->{dirs_middle_to_look}})) { File::Find::find(\&store_icons, @dirs); } File::Find::find( \&store_icons, grep { -d } ( '/usr/share/pixmaps/' => '/usr/share/icons/hicolor/', "$home_dir/.local/share/icons" => "$home_dir/.icons/" ) ); if (-d '/usr/share/icons/gnome/' and not $icon_theme =~ /^gnome$/i) { File::Find::find(\&store_icons, '/usr/share/icons/gnome/'); } } else { File::Find::find( \&store_icons, grep { -d } ( '/usr/share/pixmaps/' => "$home_dir/.local/share/icons", "$home_dir/.icons/" => '/usr/share/icons/' ) ); } # Look in this directories last if (ref $CONFIG->{dirs_last_to_look} eq 'ARRAY' and (my @dirs = grep { -d } @{$CONFIG->{dirs_last_to_look}})) { File::Find::find(\&store_icons, @dirs); } } unless (defined $icon) { $icon = $icons_db{$icon_name}; unless (defined $icon) { $icons_db{$icon_name} = ''; $icon = ''; } } $icon; # return icon } sub add_item { my ($command, $name, $icon) = @_; if ($icons) { $icon = get_icon($icon) unless -f $icon; } else { $icon = ''; } $name =~ s/\)/\\)/g; $command =~ s/\}/\\}/g; $generated_menu .= " [exec] ($name) {$command} <$icon>\n"; return 1; } sub push_app { my ($array_ref, $cat_id, $cat_name, $icon) = @_; if ($icons) { $icon = defined($icon) ? get_icon($icon) : get_icon($categories_icons{$cat_id}); } else { $icon = ''; } $generated_menu .= qq[\n[submenu] ($cat_name) <$icon>\n] . join( q{}, sort { lc $a cmp lc $b } map { $_->[2] //= $icon; qq[ [exec] ($_->[0]) {$_->[1]} <$_->[2]>\n]; } @{$array_ref} ); $generated_menu .= "[end]\n"; return 1; } my (@config_menu) = ( [q[Edit fluxbox's init file], "$CONFIG->{open_in_editor} $ENV{'HOME'}/.fluxbox/init"], [q[Edit fluxbox's keys file], "$CONFIG->{open_in_editor} $ENV{'HOME'}/.fluxbox/keys"], ["Edit ${pkgname}'s menu", "$CONFIG->{open_in_editor} $menufile"], ["Edit ${pkgname}'s config", "$CONFIG->{open_in_editor} $config_file"], ); my %categories; foreach my $category (@$SCHEMA) { next unless exists $category->{cat}; my $cat = lc $category->{cat}[0]; $cat =~ tr/_a-z/_/c; $categories{$cat} = (); $category->{cat}[0] = $cat; } my $desktop_fh; foreach my $desktop_file (@desktop_files) { if (defined $CONFIG->{ignore_file_name_re}) { next if substr($desktop_file, rindex($desktop_file, '/') + 1) =~ /$CONFIG->{ignore_file_name_re}/o; } my $file; sysopen $desktop_fh, $desktop_file, 0; sysread $desktop_fh, $file, -s $desktop_file; next if index($file, "\nNoDisplay=true") != -1; if (defined $CONFIG->{ignore_file_content_re}) { next if $file =~ /$CONFIG->{ignore_file_content_re}/o; } foreach my $category ( split( /;/, do { my $i = index($file, "\nCategories="); next if $i == -1; lc substr($file, $i + 12, index($file, "\n", $i + 12) - $i - 12); } ) ) { $category =~ tr/_a-z/_/c; next unless exists $categories{$category}; my $i = index($file, "\nExec="); my $execname = substr($file, $i + 6, index($file, "\n", $i + 6) - $i - 6); $execname =~ s/ +%.*// if index($execname, '%') != -1; if (defined $CONFIG->{ignore_app_command_re}) { next if $execname =~ /$CONFIG->{ignore_app_command_re}/o; } if (defined $CONFIG->{command_rem_re}) { $execname =~ s/$CONFIG->{command_rem_re}//go; } if (index($file, "\nTerminal=true") != -1 or index($file, "\nTerminal=1") != -1) { $execname = sprintf($CONFIG->{open_in_terminal}, $execname); } $i = index($file, "\nName="); my $name = substr($file, $i + 6, index($file, "\n", $i + 6) - $i - 6); if (defined $CONFIG->{ignore_app_name_re}) { next if $name =~ /$CONFIG->{ignore_app_name_re}/o; } if ($name =~ tr/)}//) { $name =~ s/\)/\\)/g; $name =~ s/\}/\\}/g; } push @{$categories{$category}}, [ $name, $execname, $icons ? do { $i = index($file, "\nIcon="); get_icon(substr $file, $i + 6, index($file, "\n", $i + 6) - $i - 6); } : undef ]; } } close $desktop_fh; foreach my $schema (@$SCHEMA) { if (exists $schema->{cat}) { next unless defined $categories{$schema->{cat}[0]}; next unless @{$categories{$schema->{cat}[0]}}; push_app(\@{$categories{$schema->{cat}[0]}}, $schema->{cat}[0], $schema->{cat}[1], $schema->{cat}[2]); @{$categories{$schema->{cat}[0]}} = (); } elsif (exists $schema->{item}) { add_item(@{$schema->{item}}); } elsif (exists $schema->{config}) { my ($label, $icon) = ref $schema->{config} eq 'ARRAY' ? @{$schema->{config}} : $schema->{config}; push_app(\@config_menu, 'config', $label, $icon); } elsif (exists $schema->{exit}) { my ($label, $icon) = ref $schema->{exit} eq 'ARRAY' ? @{$schema->{exit}} : $schema->{exit}; $generated_menu .= $icons ? defined $icon ? "[exit] ($label) <" . get_icon($icon) . ">\n" : "[exit] ($label) <$icons_db{exit}>\n" : "[exit] ($label)\n"; } elsif (exists $schema->{sep}) { $generated_menu .= "[separator]\n"; } elsif (exists $schema->{regenerate}) { require Cwd; my $regenerate_exec = join(q{ }, $^X, Cwd::abs_path($0), $icons ? '-i' : '-g'); my ($label, $icon) = ref $schema->{regenerate} eq 'ARRAY' ? @{$schema->{regenerate}} : $schema->{regenerate}; $generated_menu .= $icons ? defined $icon ? "[exec] ($label) {$regenerate_exec} <" . get_icon($icon) . ">\n" : "[exec] ($label) {$regenerate_exec} <$icons_db{'gtk-refresh'}>\n" : "[exec] ($label) {$regenerate_exec}\n"; } elsif (exists $schema->{raw}) { $generated_menu .= "$schema->{raw}\n"; } elsif (exists $schema->{fluxbox}) { my ($label, $icon) = ref $schema->{fluxbox} eq 'ARRAY' ? @{$schema->{fluxbox}} : $schema->{fluxbox}; $generated_menu .= $icons ? defined $icon ? "[submenu] ($label) <" . get_icon($icon) . ">\n" : "[submenu] ($label) <$icons_db{'package_settings'}>\n" : "[submenu] ($label)\n"; $generated_menu .= <<"FOOTER"; [config] (Configure) [submenu] (System Styles) {Choose a style...} [stylesdir] (/usr/share/fluxbox/styles) [end] [submenu] (User Styles) {Choose a style...} [stylesdir] (~/.fluxbox/styles) [end] [workspaces] (Workspace List) [submenu] (Tools) [exec] (Screenshot - JPG) {import screenshot.jpg && display -resize 50% screenshot.jpg} [exec] (Screenshot - PNG) {import screenshot.png && display -resize 50% screenshot.png} [exec] (Run) {fbrun} [exec] (Regen Menu) {fluxbox-generate_menu} [end] [submenu] (Window Managers) [restart] (openbox) {openbox} [end] [commanddialog] (Fluxbox Command) [reconfig] (Reload config) [restart] (Restart) [exec] (About) {(fluxbox -v; fluxbox -info | sed 1d) | xmessage -file - -center} [separator] [exit] (Exit) [end] FOOTER } } $generated_menu .= "\n[endencoding]\n[end]\n"; open my $menu_fh, '>', $menufile or die "\nCannot open $menufile for write: $!\n"; print {$menu_fh} $generated_menu; close $menu_fh; print "\n* Menu successfully generated !\n\n"; exit 0;