New shutdown.h file
[wmaker-crm.git] / src / rootmenu.c
blob0b263cd9c8da93a73f8d3a10154215e3446c8c47
1 /* rootmenu.c- user defined menu
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
6 * Copyright (c) 1998-2003 Dan Pascu
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "wconfig.h"
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29 #include <sys/wait.h>
30 #include <sys/types.h>
31 #include <string.h>
32 #include <strings.h>
33 #include <ctype.h>
34 #include <time.h>
35 #include <dirent.h>
37 #include <X11/Xlib.h>
38 #include <X11/Xutil.h>
39 #include <X11/Xatom.h>
41 #include "WindowMaker.h"
42 #include "actions.h"
43 #include "menu.h"
44 #include "funcs.h"
45 #include "main.h"
46 #include "dialog.h"
47 #include "keybind.h"
48 #include "stacking.h"
49 #include "workspace.h"
50 #include "defaults.h"
51 #include "framewin.h"
52 #include "session.h"
53 #include "shutdown.h"
54 #include "xmodifier.h"
56 #include <WINGs/WUtil.h>
58 #define MAX_SHORTCUT_LENGTH 32
60 extern char *Locale;
61 extern WDDomain *WDRootMenu;
62 extern Cursor wCursor[WCUR_LAST];
63 extern WPreferences wPreferences;
65 static WMenu *readMenuPipe(WScreen * scr, char **file_name);
66 static WMenu *readMenuFile(WScreen * scr, char *file_name);
67 static WMenu *readMenuDirectory(WScreen * scr, char *title, char **file_name, char *command);
68 static WMenu *configureMenu(WScreen * scr, WMPropList * definition, Bool includeGlobals);
69 static void menu_parser_register_macros(WMenuParser parser);
71 typedef struct Shortcut {
72 struct Shortcut *next;
74 int modifier;
75 KeyCode keycode;
76 WMenuEntry *entry;
77 WMenu *menu;
78 } Shortcut;
80 static Shortcut *shortcutList = NULL;
83 * Syntax:
84 * # main menu
85 * "Menu Name" MENU
86 * "Title" EXEC command_to_exec -params
87 * "Submenu" MENU
88 * "Title" EXEC command_to_exec -params
89 * "Submenu" END
90 * "Workspaces" WORKSPACE_MENU
91 * "Title" built_in_command
92 * "Quit" EXIT
93 * "Quick Quit" EXIT QUICK
94 * "Menu Name" END
96 * Commands may be preceded by SHORTCUT key
98 * Built-in commands:
100 * INFO_PANEL - shows the Info Panel
101 * LEGAL_PANEL - shows the Legal info panel
102 * SHUTDOWN [QUICK] - closes the X server [without confirmation]
103 * REFRESH - forces the desktop to be repainted
104 * EXIT [QUICK] - exit the window manager [without confirmation]
105 * EXEC <program> - execute an external program
106 * SHEXEC <command> - execute a shell command
107 * WORKSPACE_MENU - places the workspace submenu
108 * ARRANGE_ICONS
109 * RESTART [<window manager>] - restarts the window manager
110 * SHOW_ALL - unhide all windows on workspace
111 * HIDE_OTHERS - hides all windows excep the focused one
112 * OPEN_MENU file - read menu data from file which must be a valid menu file.
113 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
114 * - read menu data from directory(ies) and
115 * eventually precede each with a command.
116 * OPEN_MENU | command
117 * - opens command and uses its stdout to construct and insert
118 * the resulting menu in current position. The output of
119 * command must be a valid menu description.
120 * The space between '|' and command is optional.
121 * || will do the same, but will not cache the contents.
122 * SAVE_SESSION - saves the current state of the desktop, which include
123 * all running applications, all their hints (geometry,
124 * position on screen, workspace they live on, the dock
125 * or clip from where they were launched, and
126 * if minimized, shaded or hidden. Also saves the current
127 * workspace the user is on. All will be restored on every
128 * start of windowmaker until another SAVE_SESSION or
129 * CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
130 * WindowMaker domain file, then saving is automatically
131 * done on every windowmaker exit, overwriting any
132 * SAVE_SESSION or CLEAR_SESSION (see below). Also save
133 * dock state now.
134 * CLEAR_SESSION - clears any previous saved session. This will not have
135 * any effect if SaveSessionOnExit is True.
139 #define M_QUICK 1
141 /* menu commands */
143 static void execCommand(WMenu * menu, WMenuEntry * entry)
145 char *cmdline;
147 cmdline = ExpandOptions(menu->frame->screen_ptr, (char *)entry->clientdata);
149 XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
150 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT], CurrentTime);
151 XSync(dpy, 0);
153 if (cmdline) {
154 ExecuteShellCommand(menu->frame->screen_ptr, cmdline);
155 wfree(cmdline);
157 XUngrabPointer(dpy, CurrentTime);
158 XSync(dpy, 0);
161 static void exitCommand(WMenu * menu, WMenuEntry * entry)
163 static int inside = 0;
164 int result;
166 /* prevent reentrant calls */
167 if (inside)
168 return;
169 inside = 1;
171 #define R_CANCEL 0
172 #define R_EXIT 1
174 result = R_CANCEL;
176 if ((long)entry->clientdata == M_QUICK) {
177 result = R_EXIT;
178 } else {
179 int r, oldSaveSessionFlag;
181 oldSaveSessionFlag = wPreferences.save_session_on_exit;
182 r = wExitDialog(menu->frame->screen_ptr, _("Exit"),
183 _("Exit window manager?"), _("Exit"), _("Cancel"), NULL);
185 if (r == WAPRDefault) {
186 result = R_EXIT;
187 } else if (r == WAPRAlternate) {
188 /* Don't modify the "save session on exit" flag if the
189 * user canceled the operation. */
190 wPreferences.save_session_on_exit = oldSaveSessionFlag;
193 if (result == R_EXIT)
194 Shutdown(WSExitMode);
196 #undef R_EXIT
197 #undef R_CANCEL
198 inside = 0;
201 static void shutdownCommand(WMenu * menu, WMenuEntry * entry)
203 static int inside = 0;
204 int result;
206 /* prevent reentrant calls */
207 if (inside)
208 return;
209 inside = 1;
211 #define R_CANCEL 0
212 #define R_CLOSE 1
213 #define R_KILL 2
215 result = R_CANCEL;
216 if ((long)entry->clientdata == M_QUICK)
217 result = R_CLOSE;
218 else {
219 int r, oldSaveSessionFlag;
221 oldSaveSessionFlag = wPreferences.save_session_on_exit;
223 r = wExitDialog(menu->frame->screen_ptr,
224 _("Kill X session"),
225 _("Kill Window System session?\n"
226 "(all applications will be closed)"), _("Kill"), _("Cancel"), NULL);
227 if (r == WAPRDefault) {
228 result = R_KILL;
229 } else if (r == WAPRAlternate) {
230 /* Don't modify the "save session on exit" flag if the
231 * user canceled the operation. */
232 wPreferences.save_session_on_exit = oldSaveSessionFlag;
236 if (result != R_CANCEL) {
237 Shutdown(WSKillMode);
239 #undef R_CLOSE
240 #undef R_CANCEL
241 #undef R_KILL
242 inside = 0;
245 static void restartCommand(WMenu * menu, WMenuEntry * entry)
247 Shutdown(WSRestartPreparationMode);
248 Restart((char *)entry->clientdata, False);
249 Restart(NULL, True);
252 static void refreshCommand(WMenu * menu, WMenuEntry * entry)
254 wRefreshDesktop(menu->frame->screen_ptr);
257 static void arrangeIconsCommand(WMenu * menu, WMenuEntry * entry)
259 wArrangeIcons(menu->frame->screen_ptr, True);
262 static void showAllCommand(WMenu * menu, WMenuEntry * entry)
264 wShowAllWindows(menu->frame->screen_ptr);
267 static void hideOthersCommand(WMenu * menu, WMenuEntry * entry)
269 wHideOtherApplications(menu->frame->screen_ptr->focused_window);
272 static void saveSessionCommand(WMenu * menu, WMenuEntry * entry)
274 if (!wPreferences.save_session_on_exit)
275 wSessionSaveState(menu->frame->screen_ptr);
277 wScreenSaveState(menu->frame->screen_ptr);
280 static void clearSessionCommand(WMenu * menu, WMenuEntry * entry)
282 wSessionClearState(menu->frame->screen_ptr);
283 wScreenSaveState(menu->frame->screen_ptr);
286 static void infoPanelCommand(WMenu * menu, WMenuEntry * entry)
288 wShowInfoPanel(menu->frame->screen_ptr);
291 static void legalPanelCommand(WMenu * menu, WMenuEntry * entry)
293 wShowLegalPanel(menu->frame->screen_ptr);
296 /********************************************************************/
298 static char * getLocalizedMenuFile(char *menu)
300 char *buffer, *ptr, *locale;
301 int len;
303 if (!Locale)
304 return NULL;
306 len = strlen(menu) + strlen(Locale) + 8;
307 buffer = wmalloc(len);
309 /* try menu.locale_name */
310 snprintf(buffer, len, "%s.%s", menu, Locale);
311 if (access(buffer, F_OK) == 0)
312 return buffer;
314 /* position of locale in our buffer */
315 locale = buffer + strlen(menu) + 1;
317 /* check if it is in the form aa_bb.encoding and check for aa_bb */
318 ptr = strchr(locale, '.');
319 if (ptr) {
320 *ptr = 0;
321 if (access(buffer, F_OK) == 0)
322 return buffer;
325 /* now check for aa */
326 ptr = strchr(locale, '_');
327 if (ptr) {
328 *ptr = 0;
329 if (access(buffer, F_OK) == 0)
330 return buffer;
333 wfree(buffer);
335 return NULL;
338 Bool wRootMenuPerformShortcut(XEvent * event)
340 WScreen *scr = wScreenForRootWindow(event->xkey.root);
341 Shortcut *ptr;
342 int modifiers;
343 int done = 0;
345 /* ignore CapsLock */
346 modifiers = event->xkey.state & ValidModMask;
348 for (ptr = shortcutList; ptr != NULL; ptr = ptr->next) {
349 if (ptr->keycode == 0 || ptr->menu->menu->screen_ptr != scr)
350 continue;
352 if (ptr->keycode == event->xkey.keycode && ptr->modifier == modifiers) {
353 (*ptr->entry->callback) (ptr->menu, ptr->entry);
354 done = True;
358 return done;
361 void wRootMenuBindShortcuts(Window window)
363 Shortcut *ptr;
365 ptr = shortcutList;
366 while (ptr) {
367 if (ptr->modifier != AnyModifier) {
368 XGrabKey(dpy, ptr->keycode, ptr->modifier | LockMask,
369 window, True, GrabModeAsync, GrabModeAsync);
370 #ifdef NUMLOCK_HACK
371 wHackedGrabKey(ptr->keycode, ptr->modifier, window, True, GrabModeAsync, GrabModeAsync);
372 #endif
374 XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True, GrabModeAsync, GrabModeAsync);
375 ptr = ptr->next;
379 static void rebindKeygrabs(WScreen * scr)
381 WWindow *wwin;
383 wwin = scr->focused_window;
385 while (wwin != NULL) {
386 XUngrabKey(dpy, AnyKey, AnyModifier, wwin->frame->core->window);
388 if (!WFLAGP(wwin, no_bind_keys)) {
389 wWindowSetKeyGrabs(wwin);
391 wwin = wwin->prev;
395 static void removeShortcutsForMenu(WMenu * menu)
397 Shortcut *ptr, *tmp;
398 Shortcut *newList = NULL;
400 ptr = shortcutList;
401 while (ptr != NULL) {
402 tmp = ptr->next;
403 if (ptr->menu == menu) {
404 wfree(ptr);
405 } else {
406 ptr->next = newList;
407 newList = ptr;
409 ptr = tmp;
411 shortcutList = newList;
412 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
415 static Bool addShortcut(const char *file, char *shortcutDefinition, WMenu * menu, WMenuEntry * entry)
417 Shortcut *ptr;
418 KeySym ksym;
419 char *k;
420 char buf[MAX_SHORTCUT_LENGTH], *b;
422 ptr = wmalloc(sizeof(Shortcut));
424 wstrlcpy(buf, shortcutDefinition, MAX_SHORTCUT_LENGTH);
425 b = (char *)buf;
427 /* get modifiers */
428 ptr->modifier = 0;
429 while ((k = strchr(b, '+')) != NULL) {
430 int mod;
432 *k = 0;
433 mod = wXModifierFromKey(b);
434 if (mod < 0) {
435 wwarning(_("%s: invalid key modifier \"%s\""), file, b);
436 wfree(ptr);
437 return False;
439 ptr->modifier |= mod;
441 b = k + 1;
444 /* get key */
445 ksym = XStringToKeysym(b);
447 if (ksym == NoSymbol) {
448 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
449 file, shortcutDefinition, entry->text);
450 wfree(ptr);
451 return False;
454 ptr->keycode = XKeysymToKeycode(dpy, ksym);
455 if (ptr->keycode == 0) {
456 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
457 shortcutDefinition, entry->text);
458 wfree(ptr);
459 return False;
462 ptr->menu = menu;
463 ptr->entry = entry;
465 ptr->next = shortcutList;
466 shortcutList = ptr;
468 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
470 return True;
473 static char *next_token(char *line, char **next)
475 char *tmp, c;
476 char *ret;
478 *next = NULL;
479 while (*line == ' ' || *line == '\t')
480 line++;
482 tmp = line;
484 if (*tmp == '"') {
485 tmp++;
486 line++;
487 while (*tmp != 0 && *tmp != '"')
488 tmp++;
489 if (*tmp != '"') {
490 wwarning(_("%s: unmatched '\"' in menu file"), line);
491 return NULL;
493 } else {
494 do {
495 if (*tmp == '\\')
496 tmp++;
498 if (*tmp != 0)
499 tmp++;
501 } while (*tmp != 0 && *tmp != ' ' && *tmp != '\t');
504 c = *tmp;
505 *tmp = 0;
506 ret = wstrdup(line);
507 *tmp = c;
509 if (c == 0)
510 return ret;
511 else
512 tmp++;
514 /* skip blanks */
515 while (*tmp == ' ' || *tmp == '\t')
516 tmp++;
518 if (*tmp != 0)
519 *next = tmp;
521 return ret;
524 static void separateCommand(char *line, char ***file, char **command)
526 char *token, *tmp = line;
527 WMArray *array = WMCreateArray(4);
528 int count, i;
530 *file = NULL;
531 *command = NULL;
532 do {
533 token = next_token(tmp, &tmp);
534 if (token) {
535 if (strcmp(token, "WITH") == 0) {
536 if (tmp != NULL && *tmp != 0)
537 *command = wstrdup(tmp);
538 else
539 wwarning(_("%s: missing command"), line);
540 break;
542 WMAddToArray(array, token);
544 } while (token != NULL && tmp != NULL);
546 count = WMGetArrayItemCount(array);
547 if (count > 0) {
548 *file = wmalloc(sizeof(char *) * (count + 1));
549 (*file)[count] = NULL;
550 for (i = 0; i < count; i++) {
551 (*file)[i] = WMGetFromArray(array, i);
554 WMFreeArray(array);
557 static WMenu *constructPLMenu(WScreen *screen, char *path)
559 WMPropList *pl = NULL;
560 WMenu *menu = NULL;
562 if (!path)
563 return NULL;
565 pl = WMReadPropListFromFile(path);
566 if (!pl)
567 return NULL;
569 menu = configureMenu(screen, pl, False);
570 if (!menu)
571 return NULL;
573 menu->on_destroy = removeShortcutsForMenu;
574 return menu;
577 static void constructMenu(WMenu * menu, WMenuEntry * entry)
579 WMenu *submenu;
580 struct stat stat_buf;
581 char **path;
582 char *cmd;
583 char *lpath = NULL;
584 int i, first = -1;
585 time_t last = 0;
587 separateCommand((char *)entry->clientdata, &path, &cmd);
588 if (path == NULL || *path == NULL || **path == 0) {
589 wwarning(_("invalid OPEN_MENU specification: %s"), (char *)entry->clientdata);
590 if (cmd)
591 wfree(cmd);
592 return;
595 if (path[0][0] == '|') {
596 /* pipe menu */
598 if (!menu->cascades[entry->cascade] || menu->cascades[entry->cascade]->timestamp == 0) {
599 /* parse pipe */
601 submenu = readMenuPipe(menu->frame->screen_ptr, path);
603 if (submenu != NULL) {
604 if (path[0][1] == '|')
605 submenu->timestamp = 0;
606 else
607 submenu->timestamp = 1; /* there's no automatic reloading */
609 } else {
610 submenu = NULL;
613 } else {
615 /* try interpreting path as a proplist file */
616 submenu = constructPLMenu(menu->frame->screen_ptr, path[0]);
617 /* if unsuccessful, try it as an old-style file */
618 if (!submenu) {
620 i = 0;
621 while (path[i] != NULL) {
622 char *tmp;
624 if (strcmp(path[i], "-noext") == 0) {
625 i++;
626 continue;
629 tmp = wexpandpath(path[i]);
630 wfree(path[i]);
631 lpath = getLocalizedMenuFile(tmp);
632 if (lpath) {
633 wfree(tmp);
634 path[i] = lpath;
635 lpath = NULL;
636 } else {
637 path[i] = tmp;
640 if (stat(path[i], &stat_buf) == 0) {
641 if (last < stat_buf.st_mtime)
642 last = stat_buf.st_mtime;
643 if (first < 0)
644 first = i;
645 } else {
646 werror(_("%s:could not stat menu"), path[i]);
647 /*goto finish; */
650 i++;
653 if (first < 0) {
654 werror(_("%s:could not stat menu:%s"), "OPEN_MENU", (char *)entry->clientdata);
655 goto finish;
657 stat(path[first], &stat_buf);
658 if (!menu->cascades[entry->cascade]
659 || menu->cascades[entry->cascade]->timestamp < last) {
661 if (S_ISDIR(stat_buf.st_mode)) {
662 /* menu directory */
663 submenu = readMenuDirectory(menu->frame->screen_ptr, entry->text, path, cmd);
664 if (submenu)
665 submenu->timestamp = last;
666 } else if (S_ISREG(stat_buf.st_mode)) {
667 /* menu file */
669 if (cmd || path[1])
670 wwarning(_("too many parameters in OPEN_MENU: %s"),
671 (char *)entry->clientdata);
673 submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
674 if (submenu)
675 submenu->timestamp = stat_buf.st_mtime;
676 } else {
677 submenu = NULL;
679 } else {
680 submenu = NULL;
685 if (submenu) {
686 wMenuEntryRemoveCascade(menu, entry);
687 wMenuEntrySetCascade(menu, entry, submenu);
690 finish:
691 i = 0;
692 while (path[i] != NULL)
693 wfree(path[i++]);
694 wfree(path);
695 if (cmd)
696 wfree(cmd);
699 static void cleanupWorkspaceMenu(WMenu * menu)
701 if (menu->frame->screen_ptr->workspace_menu == menu)
702 menu->frame->screen_ptr->workspace_menu = NULL;
705 static WMenuEntry *addWorkspaceMenu(WScreen * scr, WMenu * menu, char *title)
707 WMenu *wsmenu;
708 WMenuEntry *entry;
710 if (scr->flags.added_workspace_menu) {
711 wwarning(_
712 ("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
713 return NULL;
714 } else {
715 scr->flags.added_workspace_menu = 1;
717 wsmenu = wWorkspaceMenuMake(scr, True);
718 wsmenu->on_destroy = cleanupWorkspaceMenu;
720 scr->workspace_menu = wsmenu;
721 entry = wMenuAddCallback(menu, title, NULL, NULL);
722 wMenuEntrySetCascade(menu, entry, wsmenu);
724 wWorkspaceMenuUpdate(scr, wsmenu);
726 return entry;
729 static void cleanupWindowsMenu(WMenu * menu)
731 if (menu->frame->screen_ptr->switch_menu == menu)
732 menu->frame->screen_ptr->switch_menu = NULL;
735 static WMenuEntry *addWindowsMenu(WScreen * scr, WMenu * menu, char *title)
737 WMenu *wwmenu;
738 WWindow *wwin;
739 WMenuEntry *entry;
741 if (scr->flags.added_windows_menu) {
742 wwarning(_
743 ("There are more than one WINDOWS_MENU commands in the applications menu. Only one is allowed."));
744 return NULL;
745 } else {
746 scr->flags.added_windows_menu = 1;
748 wwmenu = wMenuCreate(scr, _("Window List"), False);
749 wwmenu->on_destroy = cleanupWindowsMenu;
750 scr->switch_menu = wwmenu;
751 wwin = scr->focused_window;
752 while (wwin) {
753 UpdateSwitchMenu(scr, wwin, ACTION_ADD);
755 wwin = wwin->prev;
757 entry = wMenuAddCallback(menu, title, NULL, NULL);
758 wMenuEntrySetCascade(menu, entry, wwmenu);
760 return entry;
763 static WMenuEntry *addMenuEntry(WMenu * menu, char *title, char *shortcut, char *command,
764 char *params, const char *file_name)
766 WScreen *scr;
767 WMenuEntry *entry = NULL;
768 Bool shortcutOk = False;
770 if (!menu)
771 return NULL;
772 scr = menu->frame->screen_ptr;
773 if (strcmp(command, "OPEN_MENU") == 0) {
774 if (!params) {
775 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name, command);
776 } else {
777 WMenu *dummy;
778 char *path;
780 path = wfindfile(DEF_CONFIG_PATHS, params);
781 if (!path) {
782 path = wstrdup(params);
784 dummy = wMenuCreate(scr, title, False);
785 dummy->on_destroy = removeShortcutsForMenu;
786 entry = wMenuAddCallback(menu, title, constructMenu, path);
787 entry->free_cdata = wfree;
788 wMenuEntrySetCascade(menu, entry, dummy);
790 } else if (strcmp(command, "EXEC") == 0) {
791 if (!params)
792 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name, command);
793 else {
794 entry = wMenuAddCallback(menu, title, execCommand, wstrconcat("exec ", params));
795 entry->free_cdata = wfree;
796 shortcutOk = True;
798 } else if (strcmp(command, "SHEXEC") == 0) {
799 if (!params)
800 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name, command);
801 else {
802 entry = wMenuAddCallback(menu, title, execCommand, wstrdup(params));
803 entry->free_cdata = wfree;
804 shortcutOk = True;
806 } else if (strcmp(command, "EXIT") == 0) {
808 if (params && strcmp(params, "QUICK") == 0)
809 entry = wMenuAddCallback(menu, title, exitCommand, (void *)M_QUICK);
810 else
811 entry = wMenuAddCallback(menu, title, exitCommand, NULL);
813 shortcutOk = True;
814 } else if (strcmp(command, "SHUTDOWN") == 0) {
816 if (params && strcmp(params, "QUICK") == 0)
817 entry = wMenuAddCallback(menu, title, shutdownCommand, (void *)M_QUICK);
818 else
819 entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
821 shortcutOk = True;
822 } else if (strcmp(command, "REFRESH") == 0) {
823 entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
825 shortcutOk = True;
826 } else if (strcmp(command, "WORKSPACE_MENU") == 0) {
827 entry = addWorkspaceMenu(scr, menu, title);
829 shortcutOk = True;
830 } else if (strcmp(command, "WINDOWS_MENU") == 0) {
831 entry = addWindowsMenu(scr, menu, title);
833 shortcutOk = True;
834 } else if (strcmp(command, "ARRANGE_ICONS") == 0) {
835 entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
837 shortcutOk = True;
838 } else if (strcmp(command, "HIDE_OTHERS") == 0) {
839 entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
841 shortcutOk = True;
842 } else if (strcmp(command, "SHOW_ALL") == 0) {
843 entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
845 shortcutOk = True;
846 } else if (strcmp(command, "RESTART") == 0) {
847 entry = wMenuAddCallback(menu, title, restartCommand, params ? wstrdup(params) : NULL);
848 entry->free_cdata = wfree;
849 shortcutOk = True;
850 } else if (strcmp(command, "SAVE_SESSION") == 0) {
851 entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
853 shortcutOk = True;
854 } else if (strcmp(command, "CLEAR_SESSION") == 0) {
855 entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
856 shortcutOk = True;
857 } else if (strcmp(command, "INFO_PANEL") == 0) {
858 entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
859 shortcutOk = True;
860 } else if (strcmp(command, "LEGAL_PANEL") == 0) {
861 entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
862 shortcutOk = True;
863 } else {
864 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name, command);
866 return NULL;
869 if (shortcut && entry) {
870 if (!shortcutOk) {
871 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name, title);
872 } else {
873 if (addShortcut(file_name, shortcut, menu, entry)) {
875 entry->rtext = GetShortcutString(shortcut);
877 entry->rtext = wstrdup(shortcut);
883 return entry;
886 /******************* Menu Configuration From File *******************/
888 static void freeline(char *title, char *command, char *parameter, char *shortcut)
890 wfree(title);
891 wfree(command);
892 wfree(parameter);
893 wfree(shortcut);
896 static WMenu *parseCascade(WScreen * scr, WMenu * menu, WMenuParser parser)
898 char *command, *params, *shortcut, *title;
900 while (WMenuParserGetLine(parser, &title, &command, &params, &shortcut)) {
902 if (command == NULL || !command[0]) {
903 WMenuParserError(parser, _("missing command in menu config") );
904 freeline(title, command, params, shortcut);
905 goto error;
908 if (strcasecmp(command, "MENU") == 0) {
909 WMenu *cascade;
911 /* start submenu */
913 cascade = wMenuCreate(scr, M_(title), False);
914 cascade->on_destroy = removeShortcutsForMenu;
915 if (!parseCascade(scr, cascade, parser)) {
916 wMenuDestroy(cascade, True);
917 } else {
918 wMenuEntrySetCascade(menu, wMenuAddCallback(menu, M_(title), NULL, NULL), cascade);
920 } else if (strcasecmp(command, "END") == 0) {
921 /* end of menu */
922 freeline(title, command, params, shortcut);
923 return menu;
924 } else {
925 /* normal items */
926 addMenuEntry(menu, M_(title), shortcut, command, params, WMenuParserGetFilename(parser));
928 freeline(title, command, params, shortcut);
931 WMenuParserError(parser, _("syntax error in menu file: END declaration missing") );
933 error:
934 return NULL;
937 static WMenu *readMenuFile(WScreen * scr, char *file_name)
939 WMenu *menu = NULL;
940 FILE *file = NULL;
941 WMenuParser parser;
942 char *command, *params, *shortcut, *title;
944 file = fopen(file_name, "rb");
945 if (!file) {
946 werror(_("%s:could not open menu file"), file_name);
947 return NULL;
949 parser = WMenuParserCreate(file_name, file, DEF_CONFIG_PATHS);
950 menu_parser_register_macros(parser);
952 while (WMenuParserGetLine(parser, &title, &command, &params, &shortcut)) {
954 if (command == NULL || !command[0]) {
955 WMenuParserError(parser, _("missing command in menu config") );
956 freeline(title, command, params, shortcut);
957 break;
959 if (strcasecmp(command, "MENU") == 0) {
960 menu = wMenuCreate(scr, M_(title), True);
961 menu->on_destroy = removeShortcutsForMenu;
962 if (!parseCascade(scr, menu, parser)) {
963 wMenuDestroy(menu, True);
964 menu = NULL;
966 freeline(title, command, params, shortcut);
967 break;
968 } else {
969 WMenuParserError(parser, _("invalid menu file, MENU command is missing") );
970 freeline(title, command, params, shortcut);
971 break;
973 freeline(title, command, params, shortcut);
976 WMenuParserDelete(parser);
977 fclose(file);
979 return menu;
982 /************ Menu Configuration From Pipe *************/
984 static WMenu *readMenuPipe(WScreen * scr, char **file_name)
986 WMenu *menu = NULL;
987 FILE *file = NULL;
988 WMenuParser parser;
989 char *command, *params, *shortcut, *title;
990 char *filename;
991 char flat_file[MAXLINE];
992 int i;
994 flat_file[0] = '\0';
996 for (i = 0; file_name[i] != NULL; i++) {
997 strcat(flat_file, file_name[i]);
998 strcat(flat_file, " ");
1000 filename = flat_file + (flat_file[1] == '|' ? 2 : 1);
1002 file = popen(filename, "r");
1003 if (!file) {
1004 werror(_("%s:could not open menu file"), filename);
1005 return NULL;
1007 parser = WMenuParserCreate(flat_file, file, DEF_CONFIG_PATHS);
1008 menu_parser_register_macros(parser);
1010 while (WMenuParserGetLine(parser, &title, &command, &params, &shortcut)) {
1012 if (command == NULL || !command[0]) {
1013 WMenuParserError(parser, _("missing command in menu config") );
1014 freeline(title, command, params, shortcut);
1015 break;
1017 if (strcasecmp(command, "MENU") == 0) {
1018 menu = wMenuCreate(scr, M_(title), True);
1019 menu->on_destroy = removeShortcutsForMenu;
1020 if (!parseCascade(scr, menu, parser)) {
1021 wMenuDestroy(menu, True);
1022 menu = NULL;
1024 freeline(title, command, params, shortcut);
1025 break;
1026 } else {
1027 WMenuParserError(parser, _("no title given for the root menu") );
1028 freeline(title, command, params, shortcut);
1029 break;
1032 freeline(title, command, params, shortcut);
1035 WMenuParserDelete(parser);
1036 pclose(file);
1038 return menu;
1041 typedef struct {
1042 char *name;
1043 int index;
1044 } dir_data;
1046 static int myCompare(const void *d1, const void *d2)
1048 dir_data *p1 = *(dir_data **) d1;
1049 dir_data *p2 = *(dir_data **) d2;
1051 return strcmp(p1->name, p2->name);
1054 /***** Preset some macro for file parser *****/
1055 static void menu_parser_register_macros(WMenuParser parser)
1057 Visual *visual;
1058 char buf[32];
1060 // Used to return CPP verion, now returns wmaker's version
1061 WMenuParserRegisterSimpleMacro(parser, "__VERSION__", VERSION);
1063 // All macros below were historically defined by WindowMaker
1064 visual = DefaultVisual(dpy, DefaultScreen(dpy));
1065 snprintf(buf, sizeof(buf), "%d", visual->class);
1066 WMenuParserRegisterSimpleMacro(parser, "VISUAL", buf);
1068 snprintf(buf, sizeof(buf), "%d", DefaultDepth(dpy, DefaultScreen(dpy)) );
1069 WMenuParserRegisterSimpleMacro(parser, "DEPTH", buf);
1071 snprintf(buf, sizeof(buf), "%d", WidthOfScreen(DefaultScreenOfDisplay(dpy)) );
1072 WMenuParserRegisterSimpleMacro(parser, "SCR_WIDTH", buf);
1074 snprintf(buf, sizeof(buf), "%d", HeightOfScreen(DefaultScreenOfDisplay(dpy)) );
1075 WMenuParserRegisterSimpleMacro(parser, "SCR_HEIGHT", buf);
1077 WMenuParserRegisterSimpleMacro(parser, "DISPLAY", XDisplayName(DisplayString(dpy)) );
1079 WMenuParserRegisterSimpleMacro(parser, "WM_VERSION", "\"" VERSION "\"");
1082 /************ Menu Configuration From Directory *************/
1084 static Bool isFilePackage(char *file)
1086 int l;
1088 /* check if the extension indicates this file is a
1089 * file package. For now, only recognize .themed */
1091 l = strlen(file);
1093 if (l > 7 && strcmp(&(file[l - 7]), ".themed") == 0) {
1094 return True;
1095 } else {
1096 return False;
1100 static WMenu *readMenuDirectory(WScreen * scr, char *title, char **path, char *command)
1102 DIR *dir;
1103 struct dirent *dentry;
1104 struct stat stat_buf;
1105 WMenu *menu = NULL;
1106 char *buffer;
1107 WMArray *dirs = NULL, *files = NULL;
1108 WMArrayIterator iter;
1109 int length, i, have_space = 0;
1110 dir_data *data;
1111 int stripExtension = 0;
1113 dirs = WMCreateArray(16);
1114 files = WMCreateArray(16);
1116 i = 0;
1117 while (path[i] != NULL) {
1118 if (strcmp(path[i], "-noext") == 0) {
1119 stripExtension = 1;
1120 i++;
1121 continue;
1124 dir = opendir(path[i]);
1125 if (!dir) {
1126 i++;
1127 continue;
1130 while ((dentry = readdir(dir))) {
1132 if (strcmp(dentry->d_name, ".") == 0 || strcmp(dentry->d_name, "..") == 0)
1133 continue;
1135 if (dentry->d_name[0] == '.')
1136 continue;
1138 buffer = malloc(strlen(path[i]) + strlen(dentry->d_name) + 4);
1139 if (!buffer) {
1140 werror(_("out of memory while constructing directory menu %s"), path[i]);
1141 break;
1144 strcpy(buffer, path[i]);
1145 strcat(buffer, "/");
1146 strcat(buffer, dentry->d_name);
1148 if (stat(buffer, &stat_buf) != 0) {
1149 werror(_("%s:could not stat file \"%s\" in menu directory"),
1150 path[i], dentry->d_name);
1151 } else {
1152 Bool isFilePack = False;
1154 data = NULL;
1155 if (S_ISDIR(stat_buf.st_mode)
1156 && !(isFilePack = isFilePackage(dentry->d_name))) {
1158 /* access always returns success for user root */
1159 if (access(buffer, X_OK) == 0) {
1160 /* Directory is accesible. Add to directory list */
1162 data = (dir_data *) wmalloc(sizeof(dir_data));
1163 data->name = wstrdup(dentry->d_name);
1164 data->index = i;
1166 WMAddToArray(dirs, data);
1168 } else if (S_ISREG(stat_buf.st_mode) || isFilePack) {
1169 /* Hack because access always returns X_OK success for user root */
1170 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1171 if ((command != NULL && access(buffer, R_OK) == 0) ||
1172 (command == NULL && access(buffer, X_OK) == 0 &&
1173 (stat_buf.st_mode & S_IXANY))) {
1175 data = (dir_data *) wmalloc(sizeof(dir_data));
1176 data->name = wstrdup(dentry->d_name);
1177 data->index = i;
1179 WMAddToArray(files, data);
1183 free(buffer);
1186 closedir(dir);
1187 i++;
1190 if (!WMGetArrayItemCount(dirs) && !WMGetArrayItemCount(files)) {
1191 WMFreeArray(dirs);
1192 WMFreeArray(files);
1193 return NULL;
1196 WMSortArray(dirs, myCompare);
1197 WMSortArray(files, myCompare);
1199 menu = wMenuCreate(scr, M_(title), False);
1200 menu->on_destroy = removeShortcutsForMenu;
1202 WM_ITERATE_ARRAY(dirs, data, iter) {
1203 /* New directory. Use same OPEN_MENU command that was used
1204 * for the current directory. */
1205 length = strlen(path[data->index]) + strlen(data->name) + 6;
1206 if (stripExtension)
1207 length += 7;
1208 if (command)
1209 length += strlen(command) + 6;
1210 buffer = malloc(length);
1211 if (!buffer) {
1212 werror(_("out of memory while constructing directory menu %s"), path[data->index]);
1213 break;
1216 buffer[0] = '\0';
1217 if (stripExtension)
1218 strcat(buffer, "-noext ");
1220 have_space = strchr(path[data->index], ' ') != NULL || strchr(data->name, ' ') != NULL;
1222 if (have_space)
1223 strcat(buffer, "\"");
1224 strcat(buffer, path[data->index]);
1226 strcat(buffer, "/");
1227 strcat(buffer, data->name);
1228 if (have_space)
1229 strcat(buffer, "\"");
1230 if (command) {
1231 strcat(buffer, " WITH ");
1232 strcat(buffer, command);
1235 addMenuEntry(menu, M_(data->name), NULL, "OPEN_MENU", buffer, path[data->index]);
1237 wfree(buffer);
1238 if (data->name)
1239 wfree(data->name);
1240 wfree(data);
1243 WM_ITERATE_ARRAY(files, data, iter) {
1244 /* executable: add as entry */
1245 length = strlen(path[data->index]) + strlen(data->name) + 6;
1246 if (command)
1247 length += strlen(command);
1249 buffer = malloc(length);
1250 if (!buffer) {
1251 werror(_("out of memory while constructing directory menu %s"), path[data->index]);
1252 break;
1255 have_space = strchr(path[data->index], ' ') != NULL || strchr(data->name, ' ') != NULL;
1256 if (command != NULL) {
1257 strcpy(buffer, command);
1258 strcat(buffer, " ");
1259 if (have_space)
1260 strcat(buffer, "\"");
1261 strcat(buffer, path[data->index]);
1262 } else {
1263 if (have_space) {
1264 buffer[0] = '"';
1265 buffer[1] = 0;
1266 strcat(buffer, path[data->index]);
1267 } else {
1268 strcpy(buffer, path[data->index]);
1271 strcat(buffer, "/");
1272 strcat(buffer, data->name);
1273 if (have_space)
1274 strcat(buffer, "\"");
1276 if (stripExtension) {
1277 char *ptr = strrchr(data->name, '.');
1278 if (ptr && ptr != data->name)
1279 *ptr = 0;
1281 addMenuEntry(menu, M_(data->name), NULL, "SHEXEC", buffer, path[data->index]);
1283 wfree(buffer);
1284 if (data->name)
1285 wfree(data->name);
1286 wfree(data);
1289 WMFreeArray(files);
1290 WMFreeArray(dirs);
1292 return menu;
1295 /************ Menu Configuration From WMRootMenu *************/
1297 static WMenu *makeDefaultMenu(WScreen * scr)
1299 WMenu *menu = NULL;
1301 menu = wMenuCreate(scr, _("Commands"), True);
1302 wMenuAddCallback(menu, M_("XTerm"), execCommand, "xterm");
1303 wMenuAddCallback(menu, M_("rxvt"), execCommand, "rxvt");
1304 wMenuAddCallback(menu, _("Restart"), restartCommand, NULL);
1305 wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
1306 return menu;
1310 *----------------------------------------------------------------------
1311 * configureMenu--
1312 * Reads root menu configuration from defaults database.
1314 *----------------------------------------------------------------------
1316 static WMenu *configureMenu(WScreen * scr, WMPropList * definition, Bool includeGlobals)
1318 WMenu *menu = NULL;
1319 WMPropList *elem;
1320 int i, count;
1321 WMPropList *title, *command, *params;
1322 char *tmp, *mtitle;
1324 if (WMIsPLString(definition)) {
1325 struct stat stat_buf;
1326 char *path = NULL;
1327 Bool menu_is_default = False;
1329 /* menu definition is a string. Probably a path, so parse the file */
1331 tmp = wexpandpath(WMGetFromPLString(definition));
1333 path = getLocalizedMenuFile(tmp);
1335 if (!path)
1336 path = wfindfile(DEF_CONFIG_PATHS, tmp);
1338 if (!path) {
1339 path = wfindfile(DEF_CONFIG_PATHS, DEF_MENU_FILE);
1340 menu_is_default = True;
1343 if (!path) {
1344 werror(_("could not find menu file \"%s\" referenced in WMRootMenu"), tmp);
1345 wfree(tmp);
1346 return NULL;
1349 if (stat(path, &stat_buf) < 0) {
1350 werror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
1351 wfree(path);
1352 wfree(tmp);
1353 return NULL;
1356 if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
1357 /* if the pointer in WMRootMenu has changed */
1358 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1360 if (menu_is_default) {
1361 wwarning(_
1362 ("using default menu file \"%s\" as the menu referenced in WMRootMenu could not be found "),
1363 path);
1366 menu = readMenuFile(scr, path);
1367 if (menu)
1368 menu->timestamp = WMAX(stat_buf.st_mtime, WDRootMenu->timestamp);
1369 } else {
1370 menu = NULL;
1372 wfree(path);
1373 wfree(tmp);
1375 return menu;
1378 count = WMGetPropListItemCount(definition);
1379 if (count == 0)
1380 return NULL;
1382 elem = WMGetFromPLArray(definition, 0);
1383 if (!WMIsPLString(elem)) {
1384 tmp = WMGetPropListDescription(elem, False);
1385 wwarning(_("%s:format error in root menu configuration \"%s\""), "WMRootMenu", tmp);
1386 wfree(tmp);
1387 return NULL;
1389 mtitle = WMGetFromPLString(elem);
1391 menu = wMenuCreate(scr, M_(mtitle), False);
1392 menu->on_destroy = removeShortcutsForMenu;
1394 #ifdef GLOBAL_SUBMENU_FILE
1395 if (includeGlobals) {
1396 WMenu *submenu;
1397 WMenuEntry *mentry;
1399 submenu = readMenuFile(scr, GLOBAL_SUBMENU_FILE);
1401 if (submenu) {
1402 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL, NULL);
1403 wMenuEntrySetCascade(menu, mentry, submenu);
1406 #endif
1408 for (i = 1; i < count; i++) {
1409 elem = WMGetFromPLArray(definition, i);
1410 #if 0
1411 if (WMIsPLString(elem)) {
1412 char *file;
1414 file = WMGetFromPLString(elem);
1417 #endif
1418 if (!WMIsPLArray(elem) || WMGetPropListItemCount(elem) < 2)
1419 goto error;
1421 if (WMIsPLArray(WMGetFromPLArray(elem, 1))) {
1422 WMenu *submenu;
1423 WMenuEntry *mentry;
1425 /* submenu */
1426 submenu = configureMenu(scr, elem, True);
1427 if (submenu) {
1428 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL, NULL);
1429 wMenuEntrySetCascade(menu, mentry, submenu);
1431 } else {
1432 int idx = 0;
1433 WMPropList *shortcut;
1434 /* normal entry */
1436 title = WMGetFromPLArray(elem, idx++);
1437 shortcut = WMGetFromPLArray(elem, idx++);
1438 if (strcmp(WMGetFromPLString(shortcut), "SHORTCUT") == 0) {
1439 shortcut = WMGetFromPLArray(elem, idx++);
1440 command = WMGetFromPLArray(elem, idx++);
1441 } else {
1442 command = shortcut;
1443 shortcut = NULL;
1445 params = WMGetFromPLArray(elem, idx++);
1447 if (!title || !command)
1448 goto error;
1450 addMenuEntry(menu, M_(WMGetFromPLString(title)),
1451 shortcut ? WMGetFromPLString(shortcut) : NULL,
1452 WMGetFromPLString(command),
1453 params ? WMGetFromPLString(params) : NULL, "WMRootMenu");
1455 continue;
1457 error:
1458 tmp = WMGetPropListDescription(elem, False);
1459 wwarning(_("%s:format error in root menu configuration \"%s\""), "WMRootMenu", tmp);
1460 wfree(tmp);
1463 return menu;
1467 *----------------------------------------------------------------------
1468 * OpenRootMenu--
1469 * Opens the root menu, parsing the menu configuration from the
1470 * defaults database.
1471 * If the menu is already mapped and is not sticked to the
1472 * root window, it will be unmapped.
1474 * Side effects:
1475 * The menu may be remade.
1477 * Notes:
1478 * Construction of OPEN_MENU entries are delayed to the moment the
1479 * user map's them.
1480 *----------------------------------------------------------------------
1482 void OpenRootMenu(WScreen * scr, int x, int y, int keyboard)
1484 WMenu *menu = NULL;
1485 WMPropList *definition;
1487 static WMPropList *domain=NULL;
1489 if (!domain) {
1490 domain = WMCreatePLString("WMRootMenu");
1494 scr->flags.root_menu_changed_shortcuts = 0;
1495 scr->flags.added_workspace_menu = 0;
1496 scr->flags.added_windows_menu = 0;
1498 if (scr->root_menu && scr->root_menu->flags.mapped) {
1499 menu = scr->root_menu;
1500 if (!menu->flags.buttoned) {
1501 wMenuUnmap(menu);
1502 } else {
1503 wRaiseFrame(menu->frame->core);
1505 if (keyboard)
1506 wMenuMapAt(menu, 0, 0, True);
1507 else
1508 wMenuMapCopyAt(menu, x - menu->frame->core->width / 2, y);
1510 return;
1513 definition = WDRootMenu->dictionary;
1516 definition = PLGetDomain(domain);
1518 if (definition) {
1519 if (WMIsPLArray(definition)) {
1520 if (!scr->root_menu || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1521 menu = configureMenu(scr, definition, True);
1522 if (menu)
1523 menu->timestamp = WDRootMenu->timestamp;
1525 } else
1526 menu = NULL;
1527 } else {
1528 menu = configureMenu(scr, definition, True);
1532 if (!menu) {
1533 /* menu hasn't changed or could not be read */
1534 if (!scr->root_menu) {
1535 wMessageDialog(scr, _("Error"),
1536 _("The applications menu could not be loaded. "
1537 "Look at the console output for a detailed "
1538 "description of the errors."), _("OK"), NULL, NULL);
1540 menu = makeDefaultMenu(scr);
1541 scr->root_menu = menu;
1543 menu = scr->root_menu;
1544 } else {
1545 /* new root menu */
1546 if (scr->root_menu) {
1547 wMenuDestroy(scr->root_menu, True);
1549 scr->root_menu = menu;
1551 if (menu) {
1552 int newx, newy;
1554 if (keyboard && x == 0 && y == 0) {
1555 newx = newy = 0;
1556 } else if (keyboard && x == scr->scr_width / 2 && y == scr->scr_height / 2) {
1557 newx = x - menu->frame->core->width / 2;
1558 newy = y - menu->frame->core->height / 2;
1559 } else {
1560 newx = x - menu->frame->core->width / 2;
1561 newy = y;
1563 wMenuMapAt(menu, newx, newy, keyboard);
1566 if (scr->flags.root_menu_changed_shortcuts)
1567 rebindKeygrabs(scr);