fbmenugen 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. #!/usr/bin/perl
  2. # Copyright (C) 2010-2023 Daniel "Trizen" Șuteu <echo dHJpemVuQHByb3Rvbm1haWwuY29tCg== | base64 -d>.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. # Fluxbox Menu Generator
  17. # A simple menu generator for the Fluxbox Window Manager
  18. # Should be installed in $PATH before the first execution!
  19. # Name: fbmenugen
  20. # License: GPLv3
  21. # Created on: 01 August 2010
  22. # Latest edit on: 22 September 2023
  23. # https://github.com/trizen/fbmenugen
  24. use 5.014;
  25. use File::Spec;
  26. use Linux::DesktopFiles 0.25;
  27. my $pkgname = 'fbmenugen';
  28. my $version = '0.88';
  29. my ($with_icons, $db_clean, $create_menu, $pipe, $reload_config, $update_config);
  30. our ($CONFIG, $SCHEMA);
  31. my $home_dir =
  32. $ENV{HOME}
  33. || $ENV{LOGDIR}
  34. || (getpwuid($<))[7]
  35. || `echo -n ~`;
  36. my $xdg_config_home = $ENV{XDG_CONFIG_HOME} || "$home_dir/.config";
  37. my $xdg_cache_home = $ENV{XDG_CACHE_HOME} || "$home_dir/.cache";
  38. my $config_dir = "$xdg_config_home/$pkgname";
  39. my $schema_file = "$config_dir/schema.pl";
  40. my $config_file = "$config_dir/config.pl";
  41. my $cache_dir = "$xdg_cache_home/$pkgname";
  42. my $fluxbox_dir = "$home_dir/.fluxbox";
  43. my $menu_file = "$fluxbox_dir/menu";
  44. my $cache_db = "$cache_dir/cache.db";
  45. my $icons_dir = "$cache_dir/icons";
  46. sub usage {
  47. print <<"HELP";
  48. usage: $0 [options]
  49. menu:
  50. -g : generate a new menu
  51. -i : include icons
  52. -p : pipe menu (prints to STDOUT)
  53. misc:
  54. -u : update the config file
  55. -d : regenerate the cache file
  56. -S <file> : absolute path to the schema.pl file
  57. -C <file> : absolute path to the config.pl file
  58. -o <file> : menu file (default: ~/.fluxbox/menu)
  59. info:
  60. -h : print this message and exit
  61. -v : print version and exit
  62. example:
  63. $0 -g -i # generates a menu with icons
  64. => Config file: $config_file
  65. => Schema file: $schema_file
  66. HELP
  67. exit 0;
  68. }
  69. my $config_help = <<"HELP";
  70. || FILTERING
  71. | skip_filename_re : Skip a .desktop file if its name matches the regex.
  72. Name is from the last slash to the end. (e.g.: name.desktop)
  73. Example: qr/^(?:gimp|xterm)\\b/, # skips 'gimp' and 'xterm'
  74. | skip_entry : Skip a desktop file if the value from a given key matches the regex.
  75. Example: [
  76. {key => 'Name', re => qr/(?:about|terminal)/i},
  77. {key => 'Exec', re => qr/^xterm/},
  78. {key => 'OnlyShowIn', re => qr/XFCE/},
  79. ],
  80. | substitutions : Substitute, by using a regex, in the values from the desktop files.
  81. Example: [
  82. {key => 'Exec', re => qr/xterm/, value => 'tilix', global => 1},
  83. ],
  84. || ICON SETTINGS
  85. | gtk_version : The version of the Gtk library used for resolving the icon paths. (default: 3)
  86. | gtk_rc_filename : Absolute path to the Gtk configuration file.
  87. | missing_icon : Use this icon for missing icons (default: gtk-missing-image)
  88. | icon_size : Preferred size for icons. (default: 32)
  89. | generic_fallback : Try to shorten icon name at '-' characters before looking at inherited themes. (default: 0)
  90. | force_icon_size : Always get the icon scaled to the requested size. (default: 0)
  91. || PATHS
  92. | desktop_files_paths : Absolute paths which contain .desktop files.
  93. Example: [
  94. '/usr/share/applications',
  95. "\$ENV{HOME}/.local/share/applications",
  96. glob("\$ENV{HOME}/.local/share/applications/wine/Programs/*"),
  97. ],
  98. || NOTES
  99. | Regular expressions:
  100. * use qr/.../ instead of '...'
  101. * use qr/.../i for case insensitive mode
  102. HELP
  103. sub remove_database {
  104. my ($db) = @_;
  105. foreach my $file ($db, "$db.dir", "$db.pag") {
  106. unlink($file) if (-e $file);
  107. }
  108. }
  109. if (@ARGV) {
  110. while (defined(my $arg = shift @ARGV)) {
  111. if ($arg eq '-i') {
  112. $with_icons = 1;
  113. $create_menu = 1;
  114. }
  115. elsif ($arg eq '-g') {
  116. $create_menu = 1;
  117. }
  118. elsif ($arg eq '-p') {
  119. $pipe = 1;
  120. }
  121. elsif ($arg eq '-d') {
  122. $db_clean = 1;
  123. print STDERR ":: Regenerating the cache DB...\n";
  124. remove_database($cache_db);
  125. }
  126. elsif ($arg eq '-u') {
  127. $update_config = 1;
  128. }
  129. elsif ($arg eq '-v') {
  130. print "$pkgname $version\n";
  131. exit 0;
  132. }
  133. elsif ($arg eq '-o') {
  134. $menu_file = shift(@ARGV) // die "$0: option '-o' requires an argument!\n";
  135. }
  136. elsif ($arg eq '-S') {
  137. $schema_file = shift(@ARGV) // die "$0: option '-S' requires an argument!\n";
  138. }
  139. elsif ($arg eq '-C') {
  140. $reload_config = 1;
  141. $config_file = shift(@ARGV) // die "$0: options '-C' requires an argument!\n";
  142. }
  143. elsif ($arg eq '-h') {
  144. usage();
  145. }
  146. else {
  147. die "$0: option `$arg' is invalid!\n";
  148. }
  149. }
  150. }
  151. if (not -d $config_dir) {
  152. require File::Path;
  153. File::Path::make_path($config_dir)
  154. or die "$0: can't create configuration directory `$config_dir': $!\n";
  155. }
  156. if (not -d $cache_dir) {
  157. require File::Path;
  158. File::Path::make_path($cache_dir)
  159. or die "$0: can't create cache directory `$cache_dir': $!\n";
  160. }
  161. if ($with_icons and not -d $icons_dir) {
  162. remove_database($cache_db);
  163. require File::Path;
  164. File::Path::make_path($icons_dir)
  165. or warn "$0: can't create icon path `$icons_dir': $!\n";
  166. }
  167. my $config_documentation = <<"EOD";
  168. #!/usr/bin/perl
  169. # $pkgname - configuration file
  170. # This file will be updated automatically.
  171. # Any additional comment and/or indentation will be lost.
  172. =for comment
  173. $config_help
  174. =cut
  175. EOD
  176. my %CONFIG = (
  177. 'Linux::DesktopFiles' => {
  178. keep_unknown_categories => 1,
  179. unknown_category_key => 'other',
  180. skip_entry => undef,
  181. substitutions => undef,
  182. skip_filename_re => undef,
  183. terminalize => 1,
  184. terminalization_format => q{%s -e '%s'},
  185. #<<<
  186. desktop_files_paths => [
  187. '/usr/share/applications',
  188. '/usr/local/share/applications',
  189. '/usr/share/applications/kde4',
  190. "$home_dir/.local/share/applications",
  191. ],
  192. #>>>
  193. },
  194. menu_title => 'Fluxbox',
  195. terminal => 'xterm',
  196. editor => 'geany',
  197. missing_icon => 'gtk-missing-image',
  198. gtk_rc_filename => "$home_dir/.config/gtk-3.0/settings.ini",
  199. icon_size => 32,
  200. force_icon_size => 0,
  201. generic_fallback => 0,
  202. locale_support => 1,
  203. gtk_version => 3,
  204. VERSION => $version,
  205. );
  206. sub dump_configuration {
  207. require Data::Dump;
  208. open my $config_fh, '>', $config_file
  209. or die "Can't open file '${config_file}' for write: $!";
  210. my $dumped_config = q{our $CONFIG = } . Data::Dump::dump(\%CONFIG) . "\n";
  211. $dumped_config =~ s/\Q$home_dir\E/\$ENV{HOME}/g if ($home_dir eq $ENV{HOME});
  212. print $config_fh $config_documentation, $dumped_config;
  213. close $config_fh;
  214. }
  215. if (not -e $config_file) {
  216. dump_configuration();
  217. }
  218. if (not -e $schema_file) {
  219. if (-e (my $etc_schema_file = "/etc/xdg/$pkgname/schema.pl")) {
  220. require File::Copy;
  221. File::Copy::copy($etc_schema_file, $schema_file)
  222. or warn "$0: can't copy file `$etc_schema_file' to `$schema_file': $!\n";
  223. }
  224. else {
  225. die "$0: schema file `$schema_file' does not exists!\n";
  226. }
  227. }
  228. foreach my $file (\$schema_file, \$config_file) {
  229. if (not File::Spec->file_name_is_absolute($$file)) {
  230. $$file = File::Spec->rel2abs($$file);
  231. }
  232. }
  233. # Load the configuration files
  234. require $schema_file;
  235. require $config_file if $reload_config;
  236. # Remove invalid user-defined keys
  237. my @valid_keys = grep { exists $CONFIG{$_} } keys %$CONFIG;
  238. @CONFIG{@valid_keys} = @{$CONFIG}{@valid_keys};
  239. if ($CONFIG{VERSION} != $version) {
  240. $CONFIG{VERSION} = $version;
  241. dump_configuration();
  242. }
  243. #<<<
  244. my @desktop_files_paths = do {
  245. my %seen;
  246. grep { !$seen{$_}++ } (
  247. ($ENV{XDG_DATA_DIRS} ? split(/:/, $ENV{XDG_DATA_DIRS}) : ()),
  248. @{$CONFIG{'Linux::DesktopFiles'}{desktop_files_paths}},
  249. );
  250. };
  251. #>>>
  252. my $desk_obj = Linux::DesktopFiles->new(
  253. %{$CONFIG{'Linux::DesktopFiles'}},
  254. desktop_files_paths => \@desktop_files_paths,
  255. categories => [map { exists($_->{cat}) ? $_->{cat}[0] : () } @$SCHEMA],
  256. keys_to_keep => ['Name', 'Exec', 'Path',
  257. ($with_icons ? 'Icon' : ()),
  258. (
  259. ref($CONFIG{'Linux::DesktopFiles'}{skip_entry}) eq 'ARRAY'
  260. ? (map { $_->{key} } @{$CONFIG{'Linux::DesktopFiles'}{skip_entry}})
  261. : ()
  262. ),
  263. ],
  264. terminal => $CONFIG{terminal},
  265. case_insensitive_cats => 1,
  266. );
  267. my $generated_menu = <<"HEADER";
  268. #
  269. ## Menu generated with $pkgname v$version
  270. #
  271. [begin] ($CONFIG{menu_title})
  272. [encoding] {UTF-8}
  273. HEADER
  274. {
  275. my $menu_backup = $menu_file . '.bak';
  276. if (not -e $menu_backup and -e $menu_file) {
  277. require File::Copy;
  278. File::Copy::cp($menu_file, $menu_backup);
  279. }
  280. }
  281. sub get_icon_path {
  282. my ($name) = @_;
  283. state $gtk = do {
  284. require Digest::MD5;
  285. ($CONFIG{gtk_version} == 3)
  286. ? do {
  287. eval "use Gtk3";
  288. 'Gtk3'->init;
  289. 'Gtk3';
  290. }
  291. : do {
  292. require Gtk2;
  293. 'Gtk2'->init;
  294. 'Gtk2';
  295. };
  296. };
  297. state $theme =
  298. ($gtk eq 'Gtk2')
  299. ? Gtk2::IconTheme->get_default
  300. : Gtk3::IconTheme::get_default();
  301. #<<<
  302. state $flags = "${gtk}::IconLookupFlags"->new(
  303. [
  304. ($CONFIG{force_icon_size} ? 'force-size' : ()),
  305. ($CONFIG{generic_fallback} ? 'generic-fallback' : ()),
  306. ]
  307. );
  308. #>>>
  309. foreach my $icon_name ($name, $CONFIG{missing_icon}) {
  310. #<<<
  311. my $pixbuf = eval {
  312. (substr($icon_name, 0, 1) eq '/')
  313. ? (substr($icon_name, -4) eq '.xpm')
  314. ? "${gtk}::Gdk::Pixbuf"->new_from_file($icon_name)->scale_simple($CONFIG{icon_size}, $CONFIG{icon_size}, 'hyper')
  315. : "${gtk}::Gdk::Pixbuf"->new_from_file_at_size($icon_name, $CONFIG{icon_size}, $CONFIG{icon_size})
  316. : $theme->load_icon($icon_name, $CONFIG{icon_size}, $flags);
  317. };
  318. #>>>
  319. if (defined($pixbuf)) {
  320. my $md5 = Digest::MD5::md5_hex($pixbuf->get_pixels);
  321. my $path = "$icons_dir/$md5.png";
  322. $pixbuf->save($path, 'png') if not -e $path;
  323. return $path;
  324. }
  325. }
  326. return '';
  327. }
  328. # Regenerate the cache db if the config or schema file has been modified
  329. if (!$db_clean and ((-M $config_file) < (-M $cache_db) or (-M _) > (-M $schema_file))) {
  330. print STDERR ":: Regenerating the cache DB...\n";
  331. remove_database($cache_db);
  332. $db_clean = 1;
  333. }
  334. eval { require GDBM_File } // eval { require DB_File };
  335. dbmopen(my %cache_db, $cache_db, 0777)
  336. or die "Can't create/access database <<$cache_db>>: $!";
  337. # Regenerate the icon db if the GTKRC file has been modified
  338. if ($with_icons) {
  339. my $gtkrc_mtime = (stat $CONFIG{gtk_rc_filename})[9];
  340. if ($db_clean) {
  341. $cache_db{__GTKRC_MTIME__} = $gtkrc_mtime;
  342. }
  343. else {
  344. my $old_mtime = exists($cache_db{__GTKRC_MTIME__}) ? $cache_db{__GTKRC_MTIME__} : -1;
  345. if ($old_mtime != $gtkrc_mtime) {
  346. print STDERR ":: Regenerating the cache DB...\n";
  347. dbmclose(%cache_db);
  348. remove_database($cache_db);
  349. dbmopen(%cache_db, $cache_db, 0777)
  350. or die "Can't create database <<$cache_db>>: $!";
  351. $cache_db{__GTKRC_MTIME__} = $gtkrc_mtime;
  352. }
  353. }
  354. }
  355. {
  356. my %fast_cache;
  357. sub check_icon {
  358. $fast_cache{$_[0] // return undef} //= do {
  359. exists($cache_db{$_[0]})
  360. ? $cache_db{$_[0]}
  361. : do { $cache_db{$_[0]} = get_icon_path($_[0]) }
  362. };
  363. }
  364. }
  365. sub prepare_item {
  366. my $command = shift() =~ s/\}/\\}/gr;
  367. my $name = shift() =~ s/\)/\\)/gr;
  368. my $icon = shift() || $CONFIG{missing_icon};
  369. if ($with_icons and (my $icon_path = check_icon($icon))) {
  370. return <<"ITEM_WITH_ICON";
  371. [exec] ($name) {$command} <$icon_path>
  372. ITEM_WITH_ICON
  373. }
  374. <<"ITEM";
  375. [exec] ($name) {$command}
  376. ITEM
  377. }
  378. sub begin_category {
  379. my ($name, $icon) = @_;
  380. if ($with_icons and (my $icon_path = check_icon($icon))) {
  381. return <<"MENU_WITH_ICON";
  382. [submenu] ($name) <$icon_path>
  383. MENU_WITH_ICON
  384. }
  385. <<"MENU";
  386. [submenu] ($name)
  387. MENU
  388. }
  389. my %categories;
  390. foreach my $file ($desk_obj->get_desktop_files) {
  391. my %info = split("\0\1\0", (exists($cache_db{$file}) ? $cache_db{$file} : ''), -1);
  392. next if exists $info{__IGNORE__};
  393. my $mtime = (stat $file)[9];
  394. my $cache_ok = (%info and $info{__MTIME__} == $mtime);
  395. if ($with_icons and $cache_ok and not exists $info{Icon}) {
  396. $cache_ok = 0;
  397. }
  398. if (not $cache_ok) {
  399. my $entry = $desk_obj->parse_desktop_file($file) // do {
  400. $cache_db{$file} = join("\0\1\0", __IGNORE__ => 1);
  401. next;
  402. };
  403. #<<<
  404. %info = (
  405. Name => $entry->{Name} // next,
  406. Exec => $entry->{Exec} // next,
  407. Path => $entry->{Path} // '',
  408. (
  409. $with_icons
  410. ? (Icon => $entry->{Icon})
  411. : ()
  412. ),
  413. __CATEGORIES__ => join(';', @{$entry->{Categories}}),
  414. __MTIME__ => $mtime,
  415. );
  416. #>>>
  417. # Support for the Path key
  418. if ($info{Path} ne '') {
  419. require Encode;
  420. my $path = Encode::decode_utf8($info{Path});
  421. my $exec = Encode::decode_utf8($info{Exec});
  422. $exec = "$^X -e 'chdir(\$ARGV[0]) && exec(\$ARGV[1])' \Q$path\E \Q$exec\E";
  423. $info{Exec} = Encode::encode_utf8($exec);
  424. }
  425. eval {
  426. state $x = do {
  427. require Encode;
  428. require File::DesktopEntry;
  429. };
  430. $info{Name} = Encode::encode_utf8(File::DesktopEntry->new($file)->get('Name') // '');
  431. } if $CONFIG{locale_support};
  432. $cache_db{$file} = join("\0\1\0", %info);
  433. }
  434. foreach my $category (split(/;/, $info{__CATEGORIES__})) {
  435. push @{$categories{$category}}, \%info;
  436. }
  437. }
  438. foreach my $schema (@$SCHEMA) {
  439. if (exists $schema->{cat}) {
  440. exists($categories{my $category = lc($schema->{cat}[0]) =~ tr/_a-z0-9/_/cr}) || next;
  441. $generated_menu .= begin_category($schema->{cat}[1], ($with_icons ? $schema->{cat}[2] : ()))
  442. . join(q{},
  443. map { $_->[1] }
  444. sort { $a->[0] cmp $b->[0] }
  445. map { [lc($_), $_] }
  446. map { prepare_item($_->{Exec}, $_->{Name}, $with_icons ? $_->{Icon} : ()) } @{$categories{$category}})
  447. . "[end]\n";
  448. }
  449. elsif (exists $schema->{item}) {
  450. $generated_menu .= prepare_item(@{$schema->{item}});
  451. }
  452. elsif (exists $schema->{sep}) {
  453. $generated_menu .= "[separator]\n";
  454. }
  455. elsif (exists $schema->{beg}) {
  456. $generated_menu .= begin_category(@{$schema->{beg}});
  457. }
  458. elsif (exists $schema->{begin_cat}) {
  459. $generated_menu .= begin_category(@{$schema->{begin_cat}});
  460. }
  461. elsif (exists $schema->{end}) {
  462. $generated_menu .= "[end]\n";
  463. }
  464. elsif (exists $schema->{end_cat}) {
  465. $generated_menu .= "[end]\n";
  466. }
  467. elsif (exists $schema->{raw}) {
  468. $generated_menu .= "$schema->{raw}\n";
  469. }
  470. elsif (exists $schema->{fbmenugen}) {
  471. $generated_menu .= begin_category(@{$schema->{fbmenugen}});
  472. require Cwd;
  473. foreach my $item (
  474. [join(' ', $CONFIG{editor}, quotemeta(Cwd::abs_path($menu_file))), 'Menu file'],
  475. [join(' ', $CONFIG{editor}, quotemeta(Cwd::abs_path($config_file))), 'Config file'],
  476. [join(' ', $CONFIG{editor}, quotemeta(Cwd::abs_path($schema_file))), 'Schema file'],
  477. ) {
  478. $generated_menu .= prepare_item(@$item, $schema->{fbmenugen}[1]);
  479. }
  480. $generated_menu .= "[end]\n";
  481. }
  482. elsif (exists $schema->{exit}) {
  483. my ($name, $icon) = @{$schema->{exit}};
  484. if ($with_icons and (my $icon_path = check_icon($icon))) {
  485. $generated_menu .= <<EXIT_WITH_ICON;
  486. [exit] ($name) <$icon_path>
  487. EXIT_WITH_ICON
  488. }
  489. else {
  490. $generated_menu .= <<EXIT;
  491. [exit] ($name)
  492. EXIT
  493. }
  494. }
  495. elsif (exists $schema->{regenerate}) {
  496. require Cwd;
  497. my $regenerate_exec = join(
  498. q{ }, $^X, quotemeta(Cwd::abs_path($0)), ($with_icons ? '-i' : ()),
  499. '-S' => quotemeta(Cwd::abs_path($schema_file)),
  500. '-C' => quotemeta(Cwd::abs_path($config_file)),
  501. '-o' => quotemeta(Cwd::abs_path($menu_file)),
  502. );
  503. my ($label, $icon) = @{$schema->{regenerate}};
  504. $generated_menu .= prepare_item($regenerate_exec, $label, $icon);
  505. }
  506. elsif (exists $schema->{fluxbox}) {
  507. my ($label, $icon) =
  508. ref $schema->{fluxbox} eq 'ARRAY'
  509. ? @{$schema->{fluxbox}}
  510. : $schema->{fluxbox};
  511. $generated_menu .= begin_category(@{$schema->{fluxbox}}) . <<"FOOTER";
  512. [config] (Configure)
  513. [submenu] (System Styles) {Choose a style...}
  514. [stylesdir] (/usr/share/fluxbox/styles)
  515. [end]
  516. [submenu] (User Styles) {Choose a style...}
  517. [stylesdir] (~/.fluxbox/styles)
  518. [end]
  519. [workspaces] (Workspace List)
  520. [submenu] (Tools)
  521. [exec] (Screenshot - JPG) {import screenshot.jpg && display -resize 50% screenshot.jpg}
  522. [exec] (Screenshot - PNG) {import screenshot.png && display -resize 50% screenshot.png}
  523. [exec] (Run) {fbrun}
  524. [exec] (Regen Menu) {fluxbox-generate_menu}
  525. [end]
  526. [commanddialog] (Fluxbox Command)
  527. [reconfig] (Reload config)
  528. [restart] (Restart)
  529. [exec] (About) {(fluxbox -v; fluxbox -info | sed 1d) | xmessage -file - -center}
  530. [separator]
  531. [exit] (Exit)
  532. [end]
  533. FOOTER
  534. }
  535. }
  536. $generated_menu .= "\n[endencoding]\n[end]\n";
  537. if ($create_menu) {
  538. my $out_fh = $pipe ? \*STDOUT : do {
  539. open my $fh, '>', $menu_file
  540. or die "Can't open '${menu_file}' for write: $!";
  541. $fh;
  542. };
  543. print $out_fh $generated_menu;
  544. if (!$pipe) {
  545. print STDERR ":: A new menu has been successfully generated!\n";
  546. }
  547. }
  548. else {
  549. print STDERR "[!] To generate a new menu, please specify option `-g`.\n";
  550. }
  551. dump_configuration() if $update_config;
  552. dbmclose(%cache_db);