obmenu-generator 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746
  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. # Openbox Menu Generator
  17. # A fast menu generator for the Openbox Window Manager, with support for icons.
  18. # Edited on 07 December 2014 by Bob Currey
  19. # added cmd line option -t "Menu Label Text" with default of "Applications"
  20. # Name: obmenu-generator
  21. # License: GPLv3
  22. # Created: 25 March 2011
  23. # Edited: 01 September 2023
  24. # https://github.com/trizen/obmenu-generator
  25. use 5.014;
  26. #use strict;
  27. #use warnings;
  28. use Linux::DesktopFiles;
  29. my $pkgname = 'obmenu-generator';
  30. my $version = '0.93';
  31. our ($CONFIG, $SCHEMA);
  32. my $output_h = \*STDOUT;
  33. my ($pipe, $static, $with_icons, $reload_config, $db_clean, $update_config, $reconf_openbox);
  34. my $home_dir =
  35. $ENV{HOME}
  36. || $ENV{LOGDIR}
  37. || (getpwuid($<))[7]
  38. || `echo -n ~`;
  39. my $xdg_config_home = $ENV{XDG_CONFIG_HOME} || "$home_dir/.config";
  40. my $xdg_cache_home = $ENV{XDG_CACHE_HOME} || "$home_dir/.cache";
  41. my $menu_id = "root-menu";
  42. my $menu_label_text = "Applications";
  43. my $config_dir = "$xdg_config_home/$pkgname";
  44. my $cache_dir = "$xdg_cache_home/$pkgname";
  45. my $schema_file = "$config_dir/schema.pl";
  46. my $config_file = "$config_dir/config.pl";
  47. my $openbox_conf = "$xdg_config_home/openbox";
  48. my $menufile = "$openbox_conf/menu.xml";
  49. my $cache_db = "$cache_dir/cache.db";
  50. my $icons_dir = "$cache_dir/icons";
  51. sub usage {
  52. print <<"HELP";
  53. usage: $0 [options]
  54. menu:
  55. -p : generate a dynamic menu (pipe)
  56. -s : generate a static menu
  57. -i : include icons
  58. -m <id> : menu id (default: 'root-menu')
  59. -t <label> : menu label text (default: 'Applications')
  60. misc:
  61. -u : update the config file
  62. -d : regenerate the cache file
  63. -c : reconfigure openbox automatically
  64. -R : reconfigure openbox and exit
  65. -S <file> : absolute path to the schema.pl file
  66. -C <file> : absolute path to the config.pl file
  67. -o <file> : absolute path to the menu.xml file
  68. info:
  69. -h : print this message and exit
  70. -v : print version and exit
  71. examples:
  72. $0 -p -i # dynamic menu with icons
  73. $0 -s -c # static menu without icons
  74. => Config file: $config_file
  75. => Schema file: $schema_file
  76. HELP
  77. exit 0;
  78. }
  79. my $config_help = <<"HELP";
  80. || FILTERING
  81. | skip_filename_re : Skip a .desktop file if its name matches the regex.
  82. Name is from the last slash to the end. (e.g.: name.desktop)
  83. Example: qr/^(?:gimp|xterm)\\b/, # skips 'gimp' and 'xterm'
  84. | skip_entry : Skip a desktop file if the value from a given key matches the regex.
  85. Example: [
  86. {key => 'Name', re => qr/(?:about|terminal)/i},
  87. {key => 'Exec', re => qr/^xterm/},
  88. {key => 'OnlyShowIn', re => qr/XFCE/},
  89. ],
  90. | substitutions : Substitute, by using a regex, in the values from the desktop files.
  91. Example: [
  92. {key => 'Exec', re => qr/xterm/, value => 'tilix', global => 1},
  93. ],
  94. || ICON SETTINGS
  95. | gtk_version : The version of the Gtk library used for resolving the icon paths. (default: 3)
  96. | gtk_rc_filename : Absolute path to the Gtk configuration file.
  97. | missing_icon : Use this icon for missing icons (default: gtk-missing-image)
  98. | icon_size : Preferred size for icons. (default: 48)
  99. | generic_fallback : Try to shorten icon name at '-' characters before looking at inherited themes. (default: 0)
  100. | force_icon_size : Always get the icon scaled to the requested size. (default: 0)
  101. || PATHS
  102. | desktop_files_paths : Absolute paths which contain .desktop files.
  103. Example: [
  104. '/usr/share/applications',
  105. "\$ENV{HOME}/.local/share/applications",
  106. glob("\$ENV{HOME}/.local/share/applications/wine/Programs/*"),
  107. ],
  108. || NOTES
  109. | Regular expressions:
  110. * use qr/.../ instead of '...'
  111. * use qr/.../i for case insensitive mode
  112. HELP
  113. sub remove_database {
  114. my ($db) = @_;
  115. foreach my $file ($db, "$db.dir", "$db.pag") {
  116. unlink($file) if (-e $file);
  117. }
  118. }
  119. if (@ARGV) {
  120. while (defined(my $arg = shift @ARGV)) {
  121. if ($arg eq '-i') {
  122. $with_icons = 1;
  123. }
  124. elsif ($arg eq '-p') {
  125. $pipe = 1;
  126. }
  127. elsif ($arg eq '-s') {
  128. $static = 1;
  129. }
  130. elsif ($arg eq '-d') {
  131. $db_clean = 1;
  132. remove_database($cache_db);
  133. }
  134. elsif ($arg eq '-u') {
  135. $update_config = 1;
  136. }
  137. elsif ($arg eq '-v') {
  138. print "$pkgname $version\n";
  139. exit 0;
  140. }
  141. elsif ($arg eq '-c') {
  142. $reconf_openbox = 1;
  143. }
  144. elsif ($arg eq '-R') {
  145. exec 'openbox', '--reconfigure';
  146. }
  147. elsif ($arg eq '-S') {
  148. $schema_file = shift(@ARGV) // die "$0: option '-S' requires an argument!\n";
  149. }
  150. elsif ($arg eq '-C') {
  151. $reload_config = 1;
  152. $config_file = shift(@ARGV) // die "$0: options '-C' requires an argument!\n";
  153. }
  154. elsif ($arg eq '-o') {
  155. $menufile = shift(@ARGV) // die "$0: option '-o' requires an argument!\n";
  156. }
  157. elsif ($arg eq '-m') {
  158. $menu_id = shift(@ARGV) // die "$0: option '-m' requires an argument!\n";
  159. }
  160. elsif ($arg eq '-t') {
  161. $menu_label_text = shift(@ARGV) // die "$0: option '-t' requires an argument!\n";
  162. }
  163. elsif ($arg eq '-h') {
  164. usage();
  165. }
  166. else {
  167. die "$0: option `$arg' is invalid!\n";
  168. }
  169. }
  170. }
  171. if (not -d $config_dir) {
  172. require File::Path;
  173. File::Path::make_path($config_dir)
  174. or die "$0: can't create configuration directory `$config_dir': $!\n";
  175. }
  176. if (not -d $cache_dir) {
  177. require File::Path;
  178. File::Path::make_path($cache_dir)
  179. or die "$0: can't create cache directory `$cache_dir': $!\n";
  180. }
  181. if ($with_icons and not -d $icons_dir) {
  182. remove_database($cache_db);
  183. require File::Path;
  184. File::Path::make_path($icons_dir)
  185. or warn "$0: can't create icon path `$icons_dir': $!\n";
  186. }
  187. my $config_documentation = <<"EOD";
  188. #!/usr/bin/perl
  189. # $pkgname - configuration file
  190. # This file will be updated automatically.
  191. # Any additional comment and/or indentation will be lost.
  192. =for comment
  193. $config_help
  194. =cut
  195. EOD
  196. my %CONFIG = (
  197. 'Linux::DesktopFiles' => {
  198. keep_unknown_categories => 1,
  199. unknown_category_key => 'other',
  200. skip_entry => undef,
  201. substitutions => undef,
  202. skip_filename_re => undef,
  203. terminalize => 1,
  204. terminalization_format => q{%s -e '%s'},
  205. #<<<
  206. desktop_files_paths => [
  207. '/usr/share/applications',
  208. '/usr/local/share/applications',
  209. '/usr/share/applications/kde4',
  210. "$home_dir/.local/share/applications",
  211. ],
  212. #>>>
  213. },
  214. terminal => 'xterm',
  215. editor => 'geany',
  216. missing_icon => 'gtk-missing-image',
  217. gtk_rc_filename => "$home_dir/.config/gtk-3.0/settings.ini",
  218. icon_size => 48,
  219. force_icon_size => 0,
  220. generic_fallback => 0,
  221. locale_support => 1,
  222. gtk_version => 3,
  223. VERSION => $version,
  224. );
  225. sub dump_configuration {
  226. require Data::Dump;
  227. open my $config_fh, '>', $config_file
  228. or die "Can't open file '${config_file}' for write: $!";
  229. my $dumped_config = q{our $CONFIG = } . Data::Dump::dump(\%CONFIG) . "\n";
  230. $dumped_config =~ s/\Q$home_dir\E/\$ENV{HOME}/g if ($home_dir eq $ENV{HOME});
  231. print $config_fh $config_documentation, $dumped_config;
  232. close $config_fh;
  233. }
  234. if (not -e $config_file) {
  235. dump_configuration();
  236. }
  237. if (not -e $schema_file) {
  238. if (-e (my $etc_schema_file = "/etc/xdg/$pkgname/schema.pl")) {
  239. require File::Copy;
  240. File::Copy::copy($etc_schema_file, $schema_file)
  241. or warn "$0: can't copy file `$etc_schema_file' to `$schema_file': $!\n";
  242. }
  243. else {
  244. die "$0: schema file `$schema_file' does not exists!\n";
  245. }
  246. }
  247. # Load the configuration files
  248. require $schema_file;
  249. require $config_file if $reload_config;
  250. # Remove invalid user-defined keys
  251. my @valid_keys = grep { exists $CONFIG{$_} } keys %$CONFIG;
  252. @CONFIG{@valid_keys} = @{$CONFIG}{@valid_keys};
  253. if ($CONFIG{VERSION} != $version) {
  254. $CONFIG{VERSION} = $version;
  255. dump_configuration();
  256. }
  257. #<<<
  258. my @desktop_files_paths = do {
  259. my %seen;
  260. grep { !$seen{$_}++ } (
  261. ($ENV{XDG_DATA_DIRS} ? split(/:/, $ENV{XDG_DATA_DIRS}) : ()),
  262. @{$CONFIG{'Linux::DesktopFiles'}{desktop_files_paths}},
  263. );
  264. };
  265. #>>>
  266. my $desk_obj = Linux::DesktopFiles->new(
  267. %{$CONFIG{'Linux::DesktopFiles'}},
  268. desktop_files_paths => \@desktop_files_paths,
  269. categories => [map { exists($_->{cat}) ? $_->{cat}[0] : () } @$SCHEMA],
  270. keys_to_keep => ['Name', 'Exec', 'Path',
  271. ($with_icons ? 'Icon' : ()),
  272. (
  273. ref($CONFIG{'Linux::DesktopFiles'}{skip_entry}) eq 'ARRAY'
  274. ? (map { $_->{key} } @{$CONFIG{'Linux::DesktopFiles'}{skip_entry}})
  275. : ()
  276. ),
  277. ],
  278. terminal => $CONFIG{terminal},
  279. case_insensitive_cats => 1,
  280. );
  281. if ($pipe or $static) {
  282. my $menu_backup = $menufile . '.bak';
  283. if (not -e $menu_backup and -e $menufile) {
  284. require File::Copy;
  285. File::Copy::copy($menufile, $menu_backup);
  286. }
  287. if ($static) {
  288. open $output_h, '>', $menufile
  289. or die "Can't open file '${menufile}' for write: $!";
  290. }
  291. elsif ($pipe) {
  292. if (not -d $openbox_conf) {
  293. require File::Path;
  294. File::Path::make_path($openbox_conf)
  295. or die "Can't create directory '${openbox_conf}': $!";
  296. }
  297. require Cwd;
  298. my $exec_name = Cwd::abs_path($0);
  299. if (not -x $exec_name) {
  300. $exec_name = "$^X $exec_name";
  301. }
  302. $with_icons && ($exec_name .= q{ -i});
  303. open my $fh, '>', $menufile
  304. or die "Can't open file '${menufile}' for write: $!";
  305. print $fh <<"PIPE_MENU_HEADER";
  306. <?xml version="1.0" encoding="utf-8"?>
  307. <openbox_menu xmlns="https://openbox.org/"
  308. xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  309. xsi:schemaLocation="https://openbox.org/">
  310. <menu id="$menu_id" label="$pkgname" execute="$exec_name" />
  311. </openbox_menu>
  312. PIPE_MENU_HEADER
  313. close $fh;
  314. print STDERR <<'EOT';
  315. :: A dynamic menu has been successfully generated!
  316. EOT
  317. exec 'openbox', '--reconfigure';
  318. }
  319. }
  320. my $generated_menu = $static
  321. ? <<"STATIC_MENU_HEADER"
  322. <?xml version="1.0" encoding="utf-8"?>
  323. <openbox_menu xmlns="https://openbox.org/"
  324. xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  325. xsi:schemaLocation="https://openbox.org/">
  326. <menu id="$menu_id" label="$menu_label_text">
  327. STATIC_MENU_HEADER
  328. : "<openbox_pipe_menu>\n";
  329. sub get_icon_path {
  330. my ($name) = @_;
  331. state $gtk = do {
  332. require Digest::MD5;
  333. ($CONFIG{gtk_version} == 3)
  334. ? do {
  335. eval "use Gtk3";
  336. 'Gtk3'->init;
  337. 'Gtk3';
  338. }
  339. : do {
  340. require Gtk2;
  341. 'Gtk2'->init;
  342. 'Gtk2';
  343. };
  344. };
  345. state $theme =
  346. ($gtk eq 'Gtk2')
  347. ? Gtk2::IconTheme->get_default
  348. : Gtk3::IconTheme::get_default();
  349. #<<<
  350. state $flags = "${gtk}::IconLookupFlags"->new(
  351. [
  352. ($CONFIG{force_icon_size} ? 'force-size' : ()),
  353. ($CONFIG{generic_fallback} ? 'generic-fallback' : ()),
  354. ]
  355. );
  356. #>>>
  357. foreach my $icon_name ($name, $CONFIG{missing_icon}) {
  358. #<<<
  359. my $pixbuf = eval {
  360. (substr($icon_name, 0, 1) eq '/')
  361. ? (substr($icon_name, -4) eq '.xpm')
  362. ? "${gtk}::Gdk::Pixbuf"->new_from_file($icon_name)->scale_simple($CONFIG{icon_size}, $CONFIG{icon_size}, 'hyper')
  363. : "${gtk}::Gdk::Pixbuf"->new_from_file_at_size($icon_name, $CONFIG{icon_size}, $CONFIG{icon_size})
  364. : $theme->load_icon($icon_name, $CONFIG{icon_size}, $flags);
  365. };
  366. #>>>
  367. if (defined($pixbuf)) {
  368. my $md5 = Digest::MD5::md5_hex($pixbuf->get_pixels);
  369. my $path = "$icons_dir/$md5.png";
  370. $pixbuf->save($path, 'png') if not -e $path;
  371. return $path;
  372. }
  373. }
  374. return '';
  375. }
  376. # Regenerate the cache db if the config or schema file has been modified
  377. if (!$db_clean and ((-M $config_file) < (-M $cache_db) or (-M _) > (-M $schema_file))) {
  378. print STDERR ":: Regenerating the cache DB...\n";
  379. remove_database($cache_db);
  380. $db_clean = 1;
  381. }
  382. eval { require GDBM_File } // eval { require DB_File };
  383. dbmopen(my %cache_db, $cache_db, 0777)
  384. or die "Can't create/access database <<$cache_db>>: $!";
  385. # Regenerate the icon db if the GTKRC file has been modified
  386. if ($with_icons) {
  387. my $gtkrc_mtime = (stat $CONFIG{gtk_rc_filename})[9];
  388. if ($db_clean) {
  389. $cache_db{__GTKRC_MTIME__} = $gtkrc_mtime;
  390. }
  391. else {
  392. my $old_mtime = exists($cache_db{__GTKRC_MTIME__}) ? $cache_db{__GTKRC_MTIME__} : -1;
  393. if ($old_mtime != $gtkrc_mtime) {
  394. print STDERR ":: Regenerating the cache DB...\n";
  395. dbmclose(%cache_db);
  396. remove_database($cache_db);
  397. dbmopen(%cache_db, $cache_db, 0777)
  398. or die "Can't create database <<$cache_db>>: $!";
  399. $cache_db{__GTKRC_MTIME__} = $gtkrc_mtime;
  400. }
  401. }
  402. }
  403. {
  404. my %fast_cache;
  405. sub check_icon {
  406. $fast_cache{$_[0] // return undef} //= do {
  407. exists($cache_db{$_[0]})
  408. ? $cache_db{$_[0]}
  409. : do { $cache_db{$_[0]} = get_icon_path($_[0]) }
  410. };
  411. }
  412. }
  413. sub begin_category {
  414. if ($with_icons and (my $icon_path = check_icon($_[1]))) {
  415. <<"MENU_WITH_ICON";
  416. <menu id="${\rand()}" icon="$icon_path" label="$_[0]">
  417. MENU_WITH_ICON
  418. }
  419. else {
  420. <<"MENU";
  421. <menu id="${\rand()}" label="$_[0]">
  422. MENU
  423. }
  424. }
  425. my %categories;
  426. foreach my $file ($desk_obj->get_desktop_files) {
  427. my %info = split("\0\1\0", (exists($cache_db{$file}) ? $cache_db{$file} : ''), -1);
  428. next if exists $info{__IGNORE__};
  429. my $mtime = (stat $file)[9];
  430. my $cache_ok = (%info and $info{__MTIME__} == $mtime);
  431. if ($with_icons and $cache_ok and not exists $info{Icon}) {
  432. $cache_ok = 0;
  433. }
  434. if (not $cache_ok) {
  435. my $entry = $desk_obj->parse_desktop_file($file) // do {
  436. $cache_db{$file} = join("\0\1\0", __IGNORE__ => 1);
  437. next;
  438. };
  439. #<<<
  440. %info = (
  441. Name => $entry->{Name} // next,
  442. Exec => $entry->{Exec} // next,
  443. Path => $entry->{Path} // '',
  444. (
  445. $with_icons
  446. ? (Icon => check_icon($entry->{Icon}) // '')
  447. : ()
  448. ),
  449. __CATEGORIES__ => join(';', @{$entry->{Categories}}),
  450. __MTIME__ => $mtime,
  451. );
  452. #>>>
  453. # Support for the Path key
  454. if ($info{Path} ne '') {
  455. require Encode;
  456. my $path = Encode::decode_utf8($info{Path});
  457. my $exec = Encode::decode_utf8($info{Exec});
  458. $exec = "$^X -e 'chdir(\$ARGV[0]) && exec(\$ARGV[1])' \Q$path\E \Q$exec\E";
  459. $info{Exec} = Encode::encode_utf8($exec);
  460. }
  461. eval {
  462. state $x = do {
  463. require Encode;
  464. require File::DesktopEntry;
  465. };
  466. $info{Name} = Encode::encode_utf8(File::DesktopEntry->new($file)->get('Name') // '');
  467. } if $CONFIG{locale_support};
  468. state $entities = {
  469. '&' => '&amp;',
  470. '"' => '&quot;',
  471. '_' => '__',
  472. '<' => '&lt;',
  473. '>' => '&gt;',
  474. };
  475. # Encode XML entities (if any)
  476. $info{Name} =~ tr/"&_<>//
  477. && $info{Name} =~ s/([&"_<>])/$entities->{$1}/g;
  478. $cache_db{$file} = join("\0\1\0", %info);
  479. }
  480. foreach my $category (split(/;/, $info{__CATEGORIES__})) {
  481. push @{$categories{$category}}, \%info;
  482. }
  483. }
  484. foreach my $schema (@$SCHEMA) {
  485. if (exists $schema->{cat}) {
  486. exists($categories{my $category = lc($schema->{cat}[0]) =~ tr/_a-z0-9/_/cr}) || next;
  487. $generated_menu .= begin_category($schema->{cat}[1], ($with_icons ? $schema->{cat}[2] : ())) . join(
  488. q{},
  489. map { $_->[1] }
  490. sort { $a->[0] cmp $b->[0] }
  491. map { [lc($_), $_] }
  492. map {
  493. ($with_icons and $_->{Icon})
  494. ? <<"ITEM_WITH_ICON"
  495. <item label="$_->{Name}" icon="$_->{Icon}"><action name="Execute"><command><![CDATA[$_->{Exec}]]></command></action></item>
  496. ITEM_WITH_ICON
  497. : <<"ITEM";
  498. <item label="$_->{Name}"><action name="Execute"><command><![CDATA[$_->{Exec}]]></command></action></item>
  499. ITEM
  500. } @{$categories{$category}}
  501. )
  502. . qq[ </menu>\n];
  503. }
  504. elsif (exists $schema->{item}) {
  505. my ($command, $label, $icon) = @{$schema->{item}};
  506. if ($with_icons and (my $icon_path = check_icon($icon))) {
  507. $generated_menu .= <<"ITEM_WITH_ICON";
  508. <item label="$label" icon="$icon_path"><action name="Execute"><command><![CDATA[$command]]></command></action></item>
  509. ITEM_WITH_ICON
  510. }
  511. else {
  512. $generated_menu .= <<"ITEM";
  513. <item label="$label"><action name="Execute"><command><![CDATA[$command]]></command></action></item>
  514. ITEM
  515. }
  516. }
  517. elsif (exists $schema->{sep}) {
  518. $generated_menu .=
  519. defined($schema->{sep})
  520. ? qq[ <separator label="$schema->{sep}"/>\n]
  521. : qq[ <separator/>\n];
  522. }
  523. elsif (exists $schema->{beg}) {
  524. $generated_menu .= begin_category(@{$schema->{beg}});
  525. }
  526. elsif (exists $schema->{begin_cat}) {
  527. $generated_menu .= begin_category(@{$schema->{begin_cat}});
  528. }
  529. elsif (exists $schema->{end}) {
  530. $generated_menu .= qq[ </menu>\n];
  531. }
  532. elsif (exists $schema->{end_cat}) {
  533. $generated_menu .= qq[ </menu>\n];
  534. }
  535. elsif (exists $schema->{exit}) {
  536. my ($label, $icon) = @{$schema->{exit}};
  537. if ($with_icons and (my $icon_path = check_icon($icon))) {
  538. $generated_menu .= <<"EXIT_WITH_ICON";
  539. <item label="$label" icon="$icon_path"><action name="Exit"/></item>
  540. EXIT_WITH_ICON
  541. }
  542. else {
  543. $generated_menu .= <<"EXIT";
  544. <item label="$label"><action name="Exit"/></item>
  545. EXIT
  546. }
  547. }
  548. elsif (exists $schema->{raw}) {
  549. $generated_menu .= qq[ $schema->{raw}\n];
  550. }
  551. elsif (exists $schema->{file}) {
  552. sysopen(my $fh, $schema->{file}, 0) or die "Can't open file <<$schema->{file}>>: $!";
  553. sysread($fh, $generated_menu, -s $schema->{file}, length($generated_menu));
  554. }
  555. elsif (exists $schema->{pipe}) {
  556. my ($command, $label, $icon) = @{$schema->{pipe}};
  557. if ($with_icons and (my $icon_path = check_icon($icon))) {
  558. $generated_menu .= <<"PIPE_WITH_ICON";
  559. <menu id="${\rand()}" label="$label" execute="$command" icon="$icon_path"/>
  560. PIPE_WITH_ICON
  561. }
  562. else {
  563. $generated_menu .= <<"PIPE";
  564. <menu id="${\rand()}" label="$label" execute="$command"/>
  565. PIPE
  566. }
  567. }
  568. elsif (exists $schema->{obgenmenu}) {
  569. my ($name, $icon) = ref($schema->{obgenmenu}) eq 'ARRAY' ? @{$schema->{obgenmenu}} : $schema->{obgenmenu};
  570. if ($with_icons and (my $icon_path = check_icon($icon))) {
  571. $generated_menu .= <<"MENU_WITH_ICON";
  572. <menu id="${\rand()}" label="$name" icon="$icon_path">
  573. MENU_WITH_ICON
  574. }
  575. else {
  576. $generated_menu .= <<"MENU";
  577. <menu id="${\rand()}" label="$name">
  578. MENU
  579. }
  580. $generated_menu .= ($with_icons ? <<"ITEMS_WITH_ICONS" : <<"ITEMS");
  581. <item label="Menu Schema" icon="${\check_icon('text-x-generic')}"><action name="Execute"><command><![CDATA[$CONFIG{editor} $schema_file]]></command></action></item>
  582. <item label="Menu Config" icon="${\check_icon('text-x-generic')}"><action name="Execute"><command><![CDATA[$CONFIG{editor} $config_file]]></command></action></item>
  583. <separator/>
  584. <item label="Generate a static menu" icon="${\check_icon('accessories-text-editor')}"><action name="Execute"><command><![CDATA[obmenu-generator -s -c]]></command></action></item>
  585. <item label="Generate a static menu with icons" icon="${\check_icon('accessories-text-editor')}"><action name="Execute"><command><![CDATA[obmenu-generator -s -i -c]]></command></action></item>
  586. <separator/>
  587. <item label="Generate a dynamic menu" icon="${\check_icon('accessories-text-editor')}"><action name="Execute"><command><![CDATA[obmenu-generator -p]]></command></action></item>
  588. <item label="Generate a dynamic menu with icons" icon="${\check_icon('accessories-text-editor')}"><action name="Execute"><command><![CDATA[obmenu-generator -p -i]]></command></action></item>
  589. <separator/>
  590. <menu id="${\rand()}" label="Openbox" icon="${\check_icon('openbox')}">
  591. <item label="Openbox Autostart" icon="${\check_icon('text-x-generic')}"><action name="Execute"><command><![CDATA[$CONFIG{editor} $openbox_conf/autostart]]></command></action></item>
  592. <item label="Openbox RC" icon="${\check_icon('text-x-generic')}"><action name="Execute"><command><![CDATA[$CONFIG{editor} $openbox_conf/rc.xml]]></command></action></item>
  593. <item label="Openbox Menu" icon="${\check_icon('text-x-generic')}"><action name="Execute"><command><![CDATA[$CONFIG{editor} $openbox_conf/menu.xml]]></command></action></item>
  594. <item label="Reconfigure" icon="${\check_icon('openbox')}"><action name="Reconfigure"/></item>
  595. </menu>
  596. <separator/>
  597. <item label="Refresh Icon Set" icon="${\check_icon('view-refresh')}"><action name="Execute"><command><![CDATA[obmenu-generator -d]]></command></action></item>
  598. </menu>
  599. ITEMS_WITH_ICONS
  600. <item label="Menu Schema"><action name="Execute"><command><![CDATA[$CONFIG{editor} $schema_file]]></command></action></item>
  601. <item label="Menu Config"><action name="Execute"><command><![CDATA[$CONFIG{editor} $config_file]]></command></action></item>
  602. <separator/>
  603. <item label="Generate a static menu"><action name="Execute"><command><![CDATA[obmenu-generator -s -c]]></command></action></item>
  604. <item label="Generate a static menu with icons"><action name="Execute"><command><![CDATA[obmenu-generator -s -i -c]]></command></action></item>
  605. <separator/>
  606. <item label="Generate a dynamic menu"><action name="Execute"><command><![CDATA[obmenu-generator -p]]></command></action></item>
  607. <item label="Generate a dynamic menu with icons"><action name="Execute"><command><![CDATA[obmenu-generator -p -i]]></command></action></item>
  608. <separator/>
  609. <menu id="${\rand()}" label="Openbox">
  610. <item label="Openbox Autostart"><action name="Execute"><command><![CDATA[$CONFIG{editor} $openbox_conf/autostart]]></command></action></item>
  611. <item label="Openbox RC"><action name="Execute"><command><![CDATA[$CONFIG{editor} $openbox_conf/rc.xml]]></command></action></item>
  612. <item label="Openbox Menu"><action name="Execute"><command><![CDATA[$CONFIG{editor} $openbox_conf/menu.xml]]></command></action></item>
  613. <item label="Reconfigure Openbox"><action name="Reconfigure" /></item>
  614. </menu>
  615. <separator/>
  616. <item label="Refresh Icon Set"><action name="Execute"><command><![CDATA[obmenu-generator -d]]></command></action></item>
  617. </menu>
  618. ITEMS
  619. }
  620. else {
  621. warn "$0: invalid key '", (keys %{$schema})[0], "' in schema file!\n";
  622. }
  623. }
  624. print $output_h $generated_menu, $static
  625. ? qq[ </menu>\n</openbox_menu>\n]
  626. : qq[</openbox_pipe_menu>\n];
  627. dump_configuration() if $update_config;
  628. if ($static) {
  629. print STDERR <<'EOT';
  630. :: A static menu has been successfully generated!
  631. EOT
  632. if ($reconf_openbox) {
  633. exec 'openbox', '--reconfigure';
  634. }
  635. }