This update includes the 0.20.3pre3 code
[wmaker-crm.git] / src / rootmenu.c
blob57b8dcebbe35f0ff4893d1ef3311b3b3d9ed9d49
1 /* rootmenu.c- user defined menu
2 *
3 * Window Maker window manager
4 *
5 * Copyright (c) 1997, 1998 Alfredo K. Kojima
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20 * USA.
23 #include "wconfig.h"
25 #include <assert.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
30 #include <sys/wait.h>
31 #include <sys/types.h>
32 #include <string.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 "dialog.h"
46 #include "keybind.h"
47 #include "stacking.h"
48 #include "workspace.h"
49 #include "defaults.h"
50 #include "framewin.h"
51 #include "session.h"
52 #include "xmodifier.h"
53 #include <proplist.h>
55 #include "list.h"
58 extern char *Locale;
60 extern WDDomain *WDRootMenu;
62 extern wCursor[WCUR_LAST];
64 extern Time LastTimestamp;
66 extern WPreferences wPreferences;
68 extern int wScreenCount;
70 static WMenu *readMenuPipe(WScreen *scr, char **file_name);
71 static WMenu *readMenuFile(WScreen *scr, char *file_name);
72 static WMenu *readMenuDirectory(WScreen *scr, char *title, char **file_name,
73 char *command);
76 typedef struct Shortcut {
77 struct Shortcut *next;
79 int modifier;
80 KeyCode keycode;
81 WMenuEntry *entry;
82 WMenu *menu;
83 } Shortcut;
87 static Shortcut *shortcutList = NULL;
91 * Syntax:
92 * # main menu
93 * "Menu Name" MENU
94 * "Title" EXEC command_to_exec -params
95 * "Submenu" MENU
96 * "Title" EXEC command_to_exec -params
97 * "Submenu" END
98 * "Workspaces" WORKSPACE_MENU
99 * "Title" built_in_command
100 * "Quit" EXIT
101 * "Quick Quit" EXIT QUICK
102 * "Menu Name" END
104 * Commands may be preceded by SHORTCUT key
106 * Built-in commands:
108 * INFO_PANEL - shows the Info Panel
109 * LEGAL_PANEL - shows the Legal info panel
110 * SHUTDOWN [QUICK] - closes the X server [without confirmation]
111 * REFRESH - forces the desktop to be repainted
112 * EXIT [QUICK] - exit the window manager [without confirmation]
113 * EXEC <program> - execute an external program
114 * WORKSPACE_MENU - places the workspace submenu
115 * ARRANGE_ICONS
116 * RESTART [<window manager>] - restarts the window manager
117 * SHOW_ALL - unhide all windows on workspace
118 * HIDE_OTHERS - hides all windows excep the focused one
119 * OPEN_MENU file - read menu data from file which must be a valid menu file.
120 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
121 * - read menu data from directory(ies) and
122 * eventually precede each with a command.
123 * OPEN_MENU | command
124 * - opens command and uses its stdout to construct and insert
125 * the resulting menu in current position. The output of
126 * command must be a valid menu description.
127 * The space between '|' and command is optional.
128 * SAVE_SESSION - saves the current state of the desktop, which include
129 * all running applications, all their hints (geometry,
130 * position on screen, workspace they live on, the dock
131 * or clip from where they were launched, and
132 * if minimized, shaded or hidden. Also saves the current
133 * workspace the user is on. All will be restored on every
134 * start of windowmaker until another SAVE_SESSION or
135 * CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
136 * WindowMaker domain file, then saving is automatically
137 * done on every windowmaker exit, overwriting any
138 * SAVE_SESSION or CLEAR_SESSION (see below).
139 * CLEAR_SESSION - clears any previous saved session. This will not have
140 * any effect if SaveSessionOnExit is True.
144 #define MAX(a,b) ((a)>(b) ? (a) : (b))
147 #define M_QUICK 1
149 /* menu commands */
151 static void
152 execCommand(WMenu *menu, WMenuEntry *entry)
154 char *cmdline;
155 static char *shell = NULL;
158 * This have a problem: if the shell is tcsh (not sure about others)
159 * and ~/.tcshrc have /bin/stty erase ^H somewhere on it, the shell
160 * will block and the command will not be executed.
161 if (!shell) {
162 shell = getenv("SHELL");
163 if (!shell)
164 shell = "/bin/sh";
167 shell = "/bin/sh";
169 cmdline = ExpandOptions(menu->frame->screen_ptr, (char*)entry->clientdata);
171 XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
172 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT],
173 CurrentTime);
174 XSync(dpy, 0);
176 if (cmdline) {
177 /* We should not use ParseCommand() here, because it does not handle
178 * correctly '~/' expansion and '>' redirection.
179 * While we can do '~/' expansion ourselves, we can do nothing about
180 * '>' redirection.
181 * So we better let /bin/sh to do that for us. Dan.
182 * Ok. -Alfredo
184 if (fork()==0) {
186 SetupEnvironment(menu->frame->screen_ptr);
188 CloseDescriptors();
190 #ifdef HAVE_SETPGID
191 setpgid(0, 0);
192 #endif
193 execl(shell, shell, "-c", cmdline, NULL);
194 wsyserror("could not exec %s -c %s\n", shell, cmdline);
195 Exit(-1);
197 free(cmdline);
199 XUngrabPointer(dpy, CurrentTime);
200 XSync(dpy, 0);
204 static void
205 exitCommand(WMenu *menu, WMenuEntry *entry)
207 static int inside = 0;
209 /* prevent reentrant calls */
210 if (inside)
211 return;
212 inside = 1;
214 if ((int)entry->clientdata==M_QUICK
215 || wMessageDialog(menu->frame->screen_ptr, _("Exit"),
216 _("Exit window manager?"),
217 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
218 int i;
219 #ifdef DEBUG
220 printf("Exiting WindowMaker.\n");
221 #endif
222 for (i=0; i<wScreenCount; i++) {
223 WScreen *scr;
225 scr = wScreenWithNumber(i);
226 if (scr)
227 wScreenSaveState(scr);
230 RestoreDesktop(NULL);
232 ExecExitScript();
233 Exit(0);
235 inside = 0;
239 static void
240 shutdownCommand(WMenu *menu, WMenuEntry *entry)
242 static int inside = 0;
243 int result;
244 int i;
246 /* prevent reentrant calls */
247 if (inside)
248 return;
249 inside = 1;
251 #define R_CANCEL 0
252 #define R_CLOSE 1
253 #define R_KILL 2
256 result = R_CANCEL;
257 if ((int)entry->clientdata==M_QUICK)
258 result = R_CLOSE;
259 else {
260 #ifdef R6SM
261 if (wSessionIsManaged()) {
262 int r;
264 r = wMessageDialog(menu->frame->screen_ptr,
265 _("Close X session"),
266 _("Close Window System session?\n"
267 "Kill might close applications with unsaved data."),
268 _("Close"), _("Kill"), _("Cancel"));
269 if (r==WAPRDefault)
270 result = R_CLOSE;
271 else if (r==WAPRAlternate)
272 result = R_KILL;
273 } else
274 #endif
276 int r;
278 r = wMessageDialog(menu->frame->screen_ptr,
279 _("Kill X session"),
280 _("Kill Window System session?\n"
281 "(all applications will be closed)"),
282 _("Kill"), _("Cancel"), NULL);
283 if (r==WAPRDefault)
284 result = R_KILL;
288 if (result!=R_CANCEL) {
289 #ifdef R6SM
290 if (result == R_CLOSE) {
291 wSessionRequestShutdown();
292 } else
293 #endif /* R6SM */
295 for (i=0; i<wScreenCount; i++) {
296 WScreen *scr;
298 scr = wScreenWithNumber(i);
299 if (scr)
300 wScreenSaveState(scr);
302 WipeDesktop(NULL);
304 ExecExitScript();
305 Exit(0);
308 #undef R_CLOSE
309 #undef R_CANCEL
310 #undef R_KILL
311 inside = 0;
315 static void
316 restartCommand(WMenu *menu, WMenuEntry *entry)
318 int i;
320 for (i=0; i<wScreenCount; i++) {
321 WScreen *scr;
323 scr = wScreenWithNumber(i);
324 if (scr)
325 wScreenSaveState(scr);
327 RestoreDesktop(NULL);
328 Restart((char*)entry->clientdata);
332 static void
333 refreshCommand(WMenu *menu, WMenuEntry *entry)
335 wRefreshDesktop(menu->frame->screen_ptr);
339 static void
340 arrangeIconsCommand(WMenu *menu, WMenuEntry *entry)
342 wArrangeIcons(menu->frame->screen_ptr, True);
345 static void
346 showAllCommand(WMenu *menu, WMenuEntry *entry)
348 wShowAllWindows(menu->frame->screen_ptr);
351 static void
352 hideOthersCommand(WMenu *menu, WMenuEntry *entry)
354 wHideOtherApplications(menu->frame->screen_ptr->focused_window);
358 static void
359 saveSessionCommand(WMenu *menu, WMenuEntry *entry)
361 wSessionSaveState(menu->frame->screen_ptr);
365 static void
366 clearSessionCommand(WMenu *menu, WMenuEntry *entry)
368 wSessionClearState(menu->frame->screen_ptr);
372 static void
373 infoPanelCommand(WMenu *menu, WMenuEntry *entry)
375 wShowInfoPanel(menu->frame->screen_ptr);
379 static void
380 legalPanelCommand(WMenu *menu, WMenuEntry *entry)
382 wShowLegalPanel(menu->frame->screen_ptr);
387 /********************************************************************/
389 static void
390 raiseMenus(WMenu *menu)
392 int i;
394 if (menu->flags.mapped) {
395 wRaiseFrame(menu->frame->core);
397 for (i=0; i<menu->cascade_no; i++) {
398 if (menu->cascades[i])
399 raiseMenus(menu->cascades[i]);
405 Bool
406 wRootMenuPerformShortcut(XEvent *event)
408 Shortcut *ptr;
409 int modifiers;
410 int done = 0;
412 /* ignore CapsLock */
413 modifiers = event->xkey.state & ValidModMask;
415 for (ptr = shortcutList; ptr!=NULL; ptr = ptr->next) {
416 if (ptr->keycode==0)
417 continue;
419 if (ptr->keycode==event->xkey.keycode && (ptr->modifier==modifiers)) {
420 (*ptr->entry->callback)(ptr->menu, ptr->entry);
421 done = True;
424 return done;
428 void
429 wRootMenuBindShortcuts(Window window)
431 Shortcut *ptr;
433 ptr = shortcutList;
434 while (ptr) {
435 if (ptr->modifier!=AnyModifier) {
436 XGrabKey(dpy, ptr->keycode, ptr->modifier|LockMask,
437 window, True, GrabModeAsync, GrabModeAsync);
438 #ifdef NUMLOCK_HACK
439 wHackedGrabKey(ptr->keycode, ptr->modifier,
440 window, True, GrabModeAsync, GrabModeAsync);
441 #endif
443 XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True,
444 GrabModeAsync, GrabModeAsync);
445 ptr = ptr->next;
450 static void
451 rebindKeygrabs(WScreen *scr)
453 WWindow *wwin;
455 wwin = scr->focused_window;
457 while (wwin!=NULL) {
458 XUngrabKey(dpy, AnyKey, AnyModifier, wwin->frame->core->window);
460 if (!wwin->window_flags.no_bind_keys) {
461 wWindowSetKeyGrabs(wwin);
463 wwin = wwin->prev;
468 static void
469 removeShortcutsForMenu(WMenu *menu)
471 Shortcut *ptr, *tmp;
472 Shortcut *newList = NULL;
474 ptr = shortcutList;
475 while (ptr!=NULL) {
476 tmp = ptr->next;
477 if (ptr->menu == menu) {
478 free(ptr);
479 } else {
480 ptr->next = newList;
481 newList = ptr;
483 ptr = tmp;
485 shortcutList = newList;
486 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
490 static Bool
491 addShortcut(char *file, char *shortcutDefinition, WMenu *menu,
492 WMenuEntry *entry)
494 Shortcut *ptr;
495 KeySym ksym;
496 char *k;
497 char buf[128], *b;
499 ptr = wmalloc(sizeof(Shortcut));
501 strcpy(buf, shortcutDefinition);
502 b = (char*)buf;
504 /* get modifiers */
505 ptr->modifier = 0;
506 while ((k = strchr(b, '+'))!=NULL) {
507 int mod;
509 *k = 0;
510 mod = wXModifierFromKey(b);
511 if (mod<0) {
512 wwarning(_("%s:invalid key modifier \"%s\""), file,
513 shortcutDefinition);
514 free(ptr);
515 return False;
517 ptr->modifier |= mod;
519 b = k+1;
522 /* get key */
523 ksym = XStringToKeysym(b);
525 if (ksym==NoSymbol) {
526 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
527 file, shortcutDefinition, entry->text);
528 free(ptr);
529 return False;
532 ptr->keycode = XKeysymToKeycode(dpy, ksym);
533 if (ptr->keycode==0) {
534 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
535 shortcutDefinition, entry->text);
536 free(ptr);
537 return False;
540 ptr->menu = menu;
541 ptr->entry = entry;
543 ptr->next = shortcutList;
544 shortcutList = ptr;
546 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
548 return True;
552 /*******************************/
554 static char*
555 cropline(char *line)
557 char *end;
559 if (strlen(line)==0)
560 return line;
562 end = &(line[strlen(line)])-1;
563 while (isspace(*line) && *line!=0) line++;
564 while (end>line && isspace(*end)) {
565 *end=0;
566 end--;
568 return line;
572 static char*
573 next_token(char *line, char **next)
575 char *tmp, c;
576 char *ret;
578 *next = NULL;
579 while (*line==' ' || *line=='\t') line++;
581 tmp = line;
583 if (*tmp=='"') {
584 tmp++; line++;
585 while (*tmp!=0 && *tmp!='"') tmp++;
586 if (*tmp!='"') {
587 wwarning(_("%s: unmatched '\"' in menu file"), line);
588 return NULL;
590 } else {
591 do {
592 if (*tmp=='\\')
593 tmp++;
595 if (*tmp!=0)
596 tmp++;
598 } while (*tmp!=0 && *tmp!=' ' && *tmp!='\t');
601 c = *tmp;
602 *tmp = 0;
603 ret = wstrdup(line);
604 *tmp = c;
606 if (c==0)
607 return ret;
608 else
609 tmp++;
611 /* skip blanks */
612 while (*tmp==' ' || *tmp=='\t') tmp++;
614 if (*tmp!=0)
615 *next = tmp;
617 return ret;
621 static void
622 separateCommand(char *line, char ***file, char **command)
624 char *token, *tmp = line;
625 LinkedList *list = NULL;
626 int count, i;
628 *file = NULL;
629 *command = NULL;
630 do {
631 token = next_token(tmp, &tmp);
632 if (token) {
633 if (strcmp(token, "WITH")==0) {
634 if (tmp!=NULL && *tmp!=0)
635 *command = wstrdup(tmp);
636 else
637 wwarning(_("%s: missing command"), line);
638 break;
640 list = list_cons(token, list);
642 } while (token!=NULL && tmp!=NULL);
644 count = list_length(list);
645 if (count>0) {
646 *file = wmalloc(sizeof(char*)*(count+1));
647 i = count;
648 (*file)[count] = NULL;
649 while (list!=NULL) {
650 (*file)[--i] = list->head;
651 list_remove_head(&list);
657 static void
658 constructMenu(WMenu *menu, WMenuEntry *entry)
660 WMenu *submenu;
661 struct stat stat_buf;
662 char **path;
663 char *cmd;
664 char *lpath = NULL;
665 int i, first=-1;
666 time_t last=0;
668 separateCommand((char*)entry->clientdata, &path, &cmd);
669 if (!path || *path==NULL || **path==0) {
670 wwarning(_("invalid OPEN_MENU specification: %s"),
671 (char*)entry->clientdata);
672 return;
675 if (path[0][0]=='|') {
676 /* pipe menu */
678 if (!menu->cascades[entry->cascade] ||
679 menu->cascades[entry->cascade]->timestamp == 0) {
680 /* parse pipe */
682 submenu = readMenuPipe(menu->frame->screen_ptr, path);
684 /* there's no automatic reloading */
685 if(submenu != NULL)
686 submenu->timestamp = 1;
687 } else {
688 submenu = NULL;
691 } else {
692 i=0;
693 while(path[i] != NULL) {
694 char *tmp;
696 tmp = wexpandpath(path[i]);
697 free(path[i]);
698 path[i] = tmp;
700 if (Locale) {
701 lpath = wmalloc(strlen(path[i])+32);
703 strcpy(lpath, path[i]);
704 strcat(lpath, ".");
705 strcat(lpath, Locale);
706 if (stat(lpath, &stat_buf)<0) {
707 int i;
708 i = strlen(Locale);
709 if (i>2) {
710 lpath[strlen(lpath)-(i-2)]=0;
711 if (stat(lpath, &stat_buf)==0) {
712 free(path[i]);
713 path[i] = lpath;
714 lpath = NULL;
717 } else {
718 free(path[i]);
719 path[i] = lpath;
720 lpath = NULL;
724 if (lpath) {
725 free(lpath);
726 lpath = NULL;
729 if (stat(path[i], &stat_buf)==0) {
730 if (last < stat_buf.st_mtime)
731 last = stat_buf.st_mtime;
732 if (first<0)
733 first=i;
734 } else {
735 wsyserror(_("%s:could not stat menu"), path[i]);
736 /*goto finish;*/
739 i++;
742 if (first < 0) {
743 wsyserror(_("%s:could not stat menu :%s"), "OPEN_MENU",
744 (char*)entry->clientdata);
745 goto finish;
747 stat(path[first], &stat_buf);
748 if (!menu->cascades[entry->cascade]
749 || menu->cascades[entry->cascade]->timestamp < last) {
751 if (S_ISDIR(stat_buf.st_mode)) {
752 /* menu directory */
753 submenu = readMenuDirectory(menu->frame->screen_ptr,
754 entry->text, &path[first], cmd);
755 if (submenu)
756 submenu->timestamp = last;
757 } else if (S_ISREG(stat_buf.st_mode)) {
758 /* menu file */
760 if (cmd || path[1])
761 wwarning(_("too many parameters in OPEN_MENU: %s"),
762 (char*)entry->clientdata);
764 submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
765 if (submenu)
766 submenu->timestamp = stat_buf.st_mtime;
767 } else {
768 submenu = NULL;
770 } else {
771 submenu = NULL;
775 if (submenu) {
776 wMenuEntryRemoveCascade(menu, entry);
777 wMenuEntrySetCascade(menu, entry, submenu);
780 finish:
781 i = 0;
782 while (path[i]!=NULL)
783 free(path[i++]);
784 free(path);
785 if (cmd)
786 free(cmd);
790 static WMenuEntry*
791 addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
793 WMenu *wsmenu;
794 WMenuEntry *entry;
796 if (scr->flags.added_workspace_menu) {
797 wwarning(_("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
798 return NULL;
799 } else {
800 scr->flags.added_workspace_menu = 1;
802 wsmenu = wWorkspaceMenuMake(scr, True);
803 scr->workspace_menu = wsmenu;
804 entry=wMenuAddCallback(menu, title, NULL, NULL);
805 wMenuEntrySetCascade(menu, entry, wsmenu);
807 wWorkspaceMenuUpdate(scr, wsmenu);
809 return entry;
813 static WMenuEntry*
814 addMenuEntry(WMenu *menu, char *title, char *shortcut, char *command,
815 char *params, char *file_name)
817 WScreen *scr;
818 WMenuEntry *entry = NULL;
819 Bool shortcutOk = False;
821 if (!menu)
822 return NULL;
823 scr = menu->frame->screen_ptr;
824 if (strcmp(command, "OPEN_MENU")==0) {
825 if (!params) {
826 wwarning(_("%s:missing parameter for menu command \"%s\""),
827 file_name, command);
828 } else {
829 WMenu *dummy;
830 char *path;
832 path = wfindfile(DEF_CONFIG_PATHS, params);
833 if (!path) {
834 path = wstrdup(params);
836 dummy = wMenuCreate(scr, title, False);
837 dummy->on_destroy = removeShortcutsForMenu;
838 entry = wMenuAddCallback(menu, title, constructMenu, path);
839 entry->free_cdata = free;
840 wMenuEntrySetCascade(menu, entry, dummy);
842 } else if (strcmp(command, "EXEC")==0) {
843 if (!params)
844 wwarning(_("%s:missing parameter for menu command \"%s\""),
845 file_name, command);
846 else {
847 entry = wMenuAddCallback(menu, title, execCommand, wstrdup(params));
848 entry->free_cdata = free;
849 shortcutOk = True;
851 } else if (strcmp(command, "EXIT")==0) {
853 if (params && strcmp(params, "QUICK")==0)
854 entry = wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
855 else
856 entry = wMenuAddCallback(menu, title, exitCommand, NULL);
858 shortcutOk = True;
859 } else if (strcmp(command, "SHUTDOWN")==0) {
861 if (params && strcmp(params, "QUICK")==0)
862 entry = wMenuAddCallback(menu, title, shutdownCommand,
863 (void*)M_QUICK);
864 else
865 entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
867 shortcutOk = True;
868 } else if (strcmp(command, "REFRESH")==0) {
869 entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
871 shortcutOk = True;
872 } else if (strcmp(command, "WORKSPACE_MENU")==0) {
873 entry = addWorkspaceMenu(scr, menu, title);
875 shortcutOk = True;
876 } else if (strcmp(command, "ARRANGE_ICONS")==0) {
877 entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
879 shortcutOk = True;
880 } else if (strcmp(command, "HIDE_OTHERS")==0) {
881 entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
883 shortcutOk = True;
884 } else if (strcmp(command, "SHOW_ALL")==0) {
885 entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
887 shortcutOk = True;
888 } else if (strcmp(command, "RESTART")==0) {
889 entry = wMenuAddCallback(menu, title, restartCommand,
890 params ? wstrdup(params) : NULL);
891 entry->free_cdata = free;
892 shortcutOk = True;
893 } else if (strcmp(command, "SAVE_SESSION")==0) {
894 entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
896 shortcutOk = True;
897 } else if (strcmp(command, "CLEAR_SESSION")==0) {
898 entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
899 shortcutOk = True;
900 } else if (strcmp(command, "INFO_PANEL")==0) {
901 entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
902 shortcutOk = True;
903 } else if (strcmp(command, "LEGAL_PANEL")==0) {
904 entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
905 shortcutOk = True;
906 } else {
907 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name,
908 command);
910 return NULL;
913 if (shortcut && entry) {
914 if (!shortcutOk) {
915 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name,
916 title);
917 } else {
918 if (addShortcut(file_name, shortcut, menu, entry)) {
920 entry->rtext = GetShortcutString(shortcut);
922 entry->rtext = wstrdup(shortcut);
928 return entry;
933 /******************* Menu Configuration From File *******************/
935 static void
936 separateline(char *line, char *title, char *command, char *parameter,
937 char *shortcut)
939 int l, i;
941 l = strlen(line);
943 *title = 0;
944 *command = 0;
945 *parameter = 0;
946 *shortcut = 0;
947 /* get the title */
948 while (isspace(*line) && (*line!=0)) line++;
949 if (*line=='"') {
950 line++;
951 i=0;
952 while (line[i]!='"' && (line[i]!=0)) i++;
953 if (line[i]!='"')
954 return;
955 } else {
956 i=0;
957 while (!isspace(line[i]) && (line[i]!=0)) i++;
959 strncpy(title, line, i);
960 title[i++]=0;
961 line+=i;
963 /* get the command or shortcut keyword */
964 while (isspace(*line) && (*line!=0)) line++;
965 if (*line==0)
966 return;
967 i=0;
968 while (!isspace(line[i]) && (line[i]!=0)) i++;
969 strncpy(command, line, i);
970 command[i++]=0;
971 line+=i;
973 if (strcmp(command, "SHORTCUT")==0) {
974 /* get the shortcut key */
975 while (isspace(*line) && (*line!=0)) line++;
976 if (*line=='"') {
977 line++;
978 i=0;
979 while (line[i]!='"' && (line[i]!=0)) i++;
980 if (line[i]!='"')
981 return;
982 } else {
983 i=0;
984 while (!isspace(line[i]) && (line[i]!=0)) i++;
986 strncpy(shortcut, line, i);
987 shortcut[i++]=0;
988 line+=i;
990 *command=0;
992 /* get the command */
993 while (isspace(*line) && (*line!=0)) line++;
994 if (*line==0)
995 return;
996 i=0;
997 while (!isspace(line[i]) && (line[i]!=0)) i++;
998 strncpy(command, line, i);
999 command[i++]=0;
1000 line+=i;
1003 /* get the parameters */
1004 while (isspace(*line) && (*line!=0)) line++;
1005 if (*line==0)
1006 return;
1008 if (*line=='"') {
1009 line++;
1010 l = 0;
1011 while (line[l]!=0 && line[l]!='"') {
1012 parameter[l] = line[l];
1013 l++;
1015 parameter[l] = 0;
1016 return;
1019 l = strlen(line);
1020 while (isspace(line[l]) && (l>0)) l--;
1021 strncpy(parameter, line, l);
1022 parameter[l]=0;
1026 static WMenu*
1027 parseCascade(WScreen *scr, WMenu *menu, FILE *file, char *file_name)
1029 char linebuf[MAXLINE];
1030 char elinebuf[MAXLINE];
1031 char title[MAXLINE];
1032 char command[MAXLINE];
1033 char shortcut[MAXLINE];
1034 char params[MAXLINE];
1035 char *line;
1037 while (!IsEof(file)) {
1038 int lsize, ok;
1040 ok = 0;
1041 fgets(linebuf, MAXLINE, file);
1042 line = cropline(linebuf);
1043 lsize = strlen(line);
1044 do {
1045 if (line[lsize-1]=='\\') {
1046 char *line2;
1047 int lsize2;
1048 fgets(elinebuf, MAXLINE, file);
1049 line2=cropline(elinebuf);
1050 lsize2=strlen(line2);
1051 if (lsize2+lsize>MAXLINE) {
1052 wwarning(_("%s:maximal line size exceeded in menu config: %s"),
1053 file_name, line);
1054 ok=2;
1055 } else {
1056 line[lsize-1]=0;
1057 lsize+=lsize2-1;
1058 strcat(line, line2);
1060 } else {
1061 ok=1;
1063 } while (!ok && !IsEof(file));
1064 if (ok==2)
1065 continue;
1067 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1068 continue;
1071 separateline(line, title, command, params, shortcut);
1073 if (!command[0]) {
1074 wwarning(_("%s:missing command in menu config: %s"), file_name,
1075 line);
1076 goto error;
1079 if (strcasecmp(command, "MENU")==0) {
1080 WMenu *cascade;
1082 /* start submenu */
1084 cascade = wMenuCreate(scr, title, False);
1085 cascade->on_destroy = removeShortcutsForMenu;
1086 if (parseCascade(scr, cascade, file, file_name)==NULL) {
1087 wMenuDestroy(cascade, True);
1088 } else {
1089 wMenuEntrySetCascade(menu,
1090 wMenuAddCallback(menu, title, NULL, NULL),
1091 cascade);
1093 } else if (strcasecmp(command, "END")==0) {
1094 /* end of menu */
1095 return menu;
1097 } else {
1098 /* normal items */
1099 addMenuEntry(menu, title, shortcut[0] ? shortcut : NULL, command,
1100 params[0] ? params : NULL, file_name);
1104 wwarning(_("%s:syntax error in menu file:END declaration missing"),
1105 file_name);
1106 return menu;
1108 error:
1109 return menu;
1113 static WMenu*
1114 readMenuFile(WScreen *scr, char *file_name)
1116 WMenu *menu=NULL;
1117 FILE *file = NULL;
1118 char linebuf[MAXLINE];
1119 char title[MAXLINE];
1120 char shortcut[MAXLINE];
1121 char command[MAXLINE];
1122 char params[MAXLINE];
1123 char *line;
1124 #ifdef USECPP
1125 char *args;
1126 int cpp = 0;
1127 #endif
1129 #ifdef USECPP
1130 if (!wPreferences.flags.nocpp) {
1131 args = MakeCPPArgs(file_name);
1132 if (!args) {
1133 wwarning(_("could not make arguments for menu file preprocessor"));
1134 } else {
1135 sprintf(command, "%s %s %s", CPP_PATH, args, file_name);
1136 free(args);
1137 file = popen(command, "r");
1138 if (!file) {
1139 wsyserror(_("%s:could not open/preprocess menu file"),
1140 file_name);
1141 } else {
1142 cpp = 1;
1146 #endif /* USECPP */
1148 if (!file) {
1149 file = fopen(file_name, "r");
1150 if (!file) {
1151 wsyserror(_("%s:could not open menu file"), file_name);
1152 return NULL;
1156 while (!IsEof(file)) {
1157 if (!fgets(linebuf, MAXLINE, file))
1158 break;
1159 line = cropline(linebuf);
1160 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1161 continue;
1163 separateline(line, title, command, params, shortcut);
1165 if (!command[0]) {
1166 wwarning(_("%s:missing command in menu config: %s"), file_name,
1167 line);
1168 break;
1170 if (strcasecmp(command, "MENU")==0) {
1171 menu = wMenuCreate(scr, title, True);
1172 menu->on_destroy = removeShortcutsForMenu;
1173 if (!parseCascade(scr, menu, file, file_name)) {
1174 wMenuDestroy(menu, True);
1176 break;
1177 } else {
1178 wwarning(_("%s:invalid menu file. MENU command is missing"),
1179 file_name);
1180 break;
1184 #ifdef CPP
1185 if (cpp) {
1186 if (pclose(file)==-1) {
1187 wsyserror(_("error reading preprocessed menu data"));
1189 } else {
1190 fclose(file);
1192 #else
1193 fclose(file);
1194 #endif
1196 return menu;
1199 /************ Menu Configuration From Pipe *************/
1201 static WMenu*
1202 readMenuPipe(WScreen *scr, char **file_name)
1204 WMenu *menu=NULL;
1205 FILE *file = NULL;
1206 char linebuf[MAXLINE];
1207 char title[MAXLINE];
1208 char command[MAXLINE];
1209 char params[MAXLINE];
1210 char shortcut[MAXLINE];
1211 char *line;
1212 char * filename;
1213 char flat_file[MAXLINE];
1214 int i;
1215 #ifdef USECPP
1216 char *args;
1217 int cpp = 0;
1218 #endif
1220 flat_file[0] = '\0';
1222 for(i = 0 ; file_name[i] != NULL ; i++) {
1223 strcat(flat_file, file_name[i]);
1224 strcat(flat_file, " ");
1226 filename = flat_file+1;
1228 #ifdef USECPP
1229 if (!wPreferences.flags.nocpp) {
1230 args = MakeCPPArgs(filename);
1231 if (!args) {
1232 wwarning(_("could not make arguments for menu file preprocessor"));
1233 } else {
1234 sprintf(command, "%s | %s %s", filename, CPP_PATH, args);
1236 free(args);
1237 file = popen(command, "r");
1238 if (!file) {
1239 wsyserror(_("%s:could not open/preprocess menu file"), filename);
1240 } else {
1241 cpp = 1;
1246 #endif /* USECPP */
1248 if (!file) {
1249 file = popen(filename, "r");
1251 if (!file) {
1252 wsyserror(_("%s:could not open menu file"), filename);
1253 return NULL;
1257 while (!IsEof(file)) {
1258 if (!fgets(linebuf, MAXLINE, file))
1259 break;
1260 line = cropline(linebuf);
1261 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1262 continue;
1264 separateline(line, title, command, params, shortcut);
1266 if (!command[0]) {
1267 wwarning(_("%s:missing command in menu config: %s"), file_name,
1268 line);
1269 break;
1271 if (strcasecmp(command, "MENU")==0) {
1272 menu = wMenuCreate(scr, title, True);
1273 menu->on_destroy = removeShortcutsForMenu;
1274 if (!parseCascade(scr, menu, file, filename)) {
1275 wMenuDestroy(menu, True);
1277 break;
1278 } else {
1279 wwarning(_("%s:no title given for the root menu"), filename);
1280 break;
1284 pclose(file);
1286 return menu;
1291 typedef struct {
1292 char *name;
1293 int index;
1294 } dir_data;
1297 static int
1298 myCompare(dir_data *d1, dir_data *d2)
1300 return strcmp(d1->name, d2->name);
1304 /************ Menu Configuration From Directory *************/
1305 static WMenu*
1306 readMenuDirectory(WScreen *scr, char *title, char **path, char *command)
1308 DIR *dir;
1309 struct dirent *dentry;
1310 struct stat stat_buf;
1311 WMenu *menu=NULL;
1312 char *buffer;
1313 LinkedList *dirs = NULL, *files = NULL;
1314 int length, i, have_space=0;
1315 dir_data *data;
1317 i=0;
1318 while (path[i]!=NULL) {
1319 dir = opendir(path[i]);
1320 if (!dir) {
1321 i++;
1322 continue;
1325 while ((dentry = readdir(dir))) {
1327 if (strcmp(dentry->d_name, ".")==0 ||
1328 strcmp(dentry->d_name, "..")==0)
1329 continue;
1331 if (dentry->d_name[0] == '.')
1332 continue;
1334 buffer = wmalloc(strlen(path[i])+strlen(dentry->d_name)+4);
1335 if (!buffer) {
1336 wsyserror(_("out of memory while constructing directory menu %s"),
1337 path[i]);
1338 break;
1341 strcpy(buffer, path[i]);
1342 strcat(buffer, "/");
1343 strcat(buffer, dentry->d_name);
1345 if (stat(buffer, &stat_buf)!=0) {
1346 wsyserror(_("%s:could not stat file \"%s\" in menu directory"),
1347 path[i], dentry->d_name);
1348 } else {
1349 data = (dir_data*) wmalloc(sizeof(dir_data));
1350 data->name = wstrdup(dentry->d_name);
1351 data->index = i;
1352 if (S_ISDIR(stat_buf.st_mode)) {
1353 /* access always returns success for user root */
1354 if (access(buffer, X_OK)==0) {
1355 /* Directory is accesible. Add to directory list */
1356 list_insert_sorted(data, &dirs, (int(*)())myCompare);
1357 data = NULL;
1359 } else if (S_ISREG(stat_buf.st_mode)) {
1360 /* Hack because access always returns X_OK success for user root */
1361 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1362 if ((command!=NULL && access(buffer, R_OK)==0) ||
1363 (command==NULL && access(buffer, X_OK)==0 &&
1364 (stat_buf.st_mode & S_IXANY))) {
1365 list_insert_sorted(data, &files, (int(*)())myCompare);
1366 data = NULL;
1369 if (data!=NULL) {
1370 if (data->name)
1371 free(data->name);
1372 free(data);
1373 data = NULL;
1376 free(buffer);
1379 closedir(dir);
1380 i++;
1383 if (!dirs && !files)
1384 return NULL;
1386 menu = wMenuCreate(scr, title, False);
1387 menu->on_destroy = removeShortcutsForMenu;
1389 while (dirs != NULL) {
1390 /* New directory. Use same OPEN_MENU command that was used
1391 * for the current directory. */
1392 dir_data *d = (dir_data*)dirs->head;
1394 length = strlen(path[d->index])+strlen(d->name)+6;
1395 if (command)
1396 length += strlen(command) + 6;
1397 buffer = wmalloc(length);
1398 if (!buffer) {
1399 wsyserror(_("out of memory while constructing directory menu %s"),
1400 path[d->index]);
1401 break;
1404 have_space = strchr(path[d->index], ' ')!=NULL ||
1405 strchr(d->name, ' ')!=NULL;
1406 if (have_space) {
1407 buffer[0] = '"';
1408 buffer[1] = 0;
1409 strcat(buffer, path[d->index]);
1410 } else {
1411 strcpy(buffer, path[d->index]);
1413 strcat(buffer, "/");
1414 strcat(buffer, d->name);
1415 if (have_space)
1416 strcat(buffer, "\"");
1417 if (command) {
1418 strcat(buffer, " WITH ");
1419 strcat(buffer, command);
1422 addMenuEntry(menu, d->name, NULL, "OPEN_MENU", buffer, path[d->index]);
1424 free(buffer);
1425 if (dirs->head) {
1426 if (d->name)
1427 free(d->name);
1428 free(dirs->head);
1430 list_remove_head(&dirs);
1433 while (files != NULL) {
1434 /* executable: add as entry */
1435 dir_data *f = (dir_data*) files->head;;
1437 length = strlen(path[f->index])+strlen(f->name)+6;
1438 if (command)
1439 length += strlen(command);
1441 buffer = wmalloc(length);
1442 if (!buffer) {
1443 wsyserror(_("out of memory while constructing directory menu %s"),
1444 path[f->index]);
1445 break;
1448 have_space = strchr(path[f->index], ' ')!=NULL ||
1449 strchr(f->name, ' ')!=NULL;
1450 if (command!=NULL) {
1451 strcpy(buffer, command);
1452 strcat(buffer, " ");
1453 if (have_space)
1454 strcat(buffer, "\"");
1455 strcat(buffer, path[f->index]);
1456 } else {
1457 if (have_space) {
1458 buffer[0] = '"';
1459 buffer[1] = 0;
1460 strcat(buffer, path[f->index]);
1461 } else {
1462 strcpy(buffer, path[f->index]);
1465 strcat(buffer, "/");
1466 strcat(buffer, f->name);
1467 if (have_space)
1468 strcat(buffer, "\"");
1470 addMenuEntry(menu, f->name, NULL, "EXEC", buffer, path[f->index]);
1472 free(buffer);
1473 if (files->head) {
1474 if (f->name)
1475 free(f->name);
1476 free(files->head);
1478 list_remove_head(&files);
1481 return menu;
1485 /************ Menu Configuration From WMRootMenu *************/
1487 static WMenu*
1488 makeDefaultMenu(WScreen *scr)
1490 WMenu *menu=NULL;
1492 menu = wMenuCreate(scr, _("Commands"), True);
1493 wMenuAddCallback(menu, "XTerm", execCommand, "xterm");
1494 wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
1495 return menu;
1503 *----------------------------------------------------------------------
1504 * configureMenu--
1505 * Reads root menu configuration from defaults database.
1507 *----------------------------------------------------------------------
1509 static WMenu*
1510 configureMenu(WScreen *scr, proplist_t definition)
1512 WMenu *menu = NULL;
1513 proplist_t elem;
1514 int i, count;
1515 proplist_t title, command, params;
1516 char *tmp, *mtitle;
1519 if (PLIsString(definition)) {
1520 struct stat stat_buf;
1521 char *path = NULL;
1522 Bool menu_is_default = False;
1524 /* menu definition is a string. Probably a path, so parse the file */
1526 tmp = wexpandpath(PLGetString(definition));
1528 if (Locale) {
1529 path = wmalloc(strlen(tmp)+32);
1531 strcpy(path, tmp);
1532 strcat(path, ".");
1533 strcat(path, Locale);
1535 /* look for menu.xy */
1536 if (stat(path, &stat_buf)<0) {
1537 int i;
1538 i = strlen(Locale);
1539 if (i>2) {
1540 path[strlen(path)-(i-2)]=0;
1541 /* look for menu.xy_zw */
1542 if (stat(path, &stat_buf)<0) {
1543 free(path);
1544 /* If did not find any localized menus, try
1545 * only menu. This can also mean that
1546 * the path in WMRootMenu was already the
1547 * path for the localized menu (eg: menu = "menu.ab")
1549 path = NULL;
1551 } else {
1552 free(path);
1553 path = NULL;
1558 if (!path)
1559 path = wfindfile(DEF_CONFIG_PATHS, tmp);
1561 if (!path) {
1562 path = wfindfile(DEF_CONFIG_PATHS, DEF_MENU_FILE);
1563 menu_is_default = True;
1566 if (!path) {
1567 wsyserror(_("could not find menu file \"%s\" referenced in WMRootMenu"),
1568 tmp);
1569 free(tmp);
1570 return NULL;
1573 if (stat(path, &stat_buf)<0) {
1574 wsyserror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
1575 free(path);
1576 free(tmp);
1577 return NULL;
1580 if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
1581 /* if the pointer in WMRootMenu has changed */
1582 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1584 if (menu_is_default) {
1585 wwarning(_("using default menu file \"%s\" as the menu referenced in WMRootMenu could not be found "),
1586 path);
1589 menu = readMenuFile(scr, path);
1590 if (menu)
1591 menu->timestamp = MAX(stat_buf.st_mtime, WDRootMenu->timestamp);
1592 } else {
1593 menu = NULL;
1595 free(path);
1596 free(tmp);
1598 return menu;
1601 count = PLGetNumberOfElements(definition);
1602 if (count==0)
1603 return NULL;
1605 elem = PLGetArrayElement(definition, 0);
1606 if (!PLIsString(elem)) {
1607 tmp = PLGetDescription(elem);
1608 wwarning(_("%s:format error in root menu configuration \"%s\""),
1609 "WMRootMenu", tmp);
1610 free(tmp);
1611 return NULL;
1613 mtitle = PLGetString(elem);
1615 menu = wMenuCreate(scr, mtitle, False);
1616 menu->on_destroy = removeShortcutsForMenu;
1618 for (i=1; i<count; i++) {
1619 elem = PLGetArrayElement(definition, i);
1621 if (!PLIsArray(elem) || PLGetNumberOfElements(elem) < 2)
1622 goto error;
1624 if (PLIsArray(PLGetArrayElement(elem,1))) {
1625 WMenu *submenu;
1626 WMenuEntry *mentry;
1628 /* submenu */
1629 submenu = configureMenu(scr, elem);
1630 if (submenu) {
1631 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL,
1632 NULL);
1633 wMenuEntrySetCascade(menu, mentry, submenu);
1635 } else {
1636 int idx = 0;
1637 char *shortcut;
1638 /* normal entry */
1640 title = PLGetArrayElement(elem, idx++);
1641 shortcut = PLGetArrayElement(elem, idx++);
1642 if (strcmp(PLGetString(shortcut), "SHORTCUT")==0) {
1643 shortcut = PLGetArrayElement(elem, idx++);
1644 command = PLGetArrayElement(elem, idx++);
1645 } else {
1646 command = shortcut;
1647 shortcut = NULL;
1649 params = PLGetArrayElement(elem, idx++);
1651 if (!title || !command)
1652 goto error;
1654 addMenuEntry(menu, PLGetString(title),
1655 shortcut ? PLGetString(shortcut) : NULL,
1656 PLGetString(command),
1657 params ? PLGetString(params) : NULL, "WMRootMenu");
1659 continue;
1661 error:
1662 tmp = PLGetDescription(elem);
1663 wwarning(_("%s:format error in root menu configuration \"%s\""),
1664 "WMRootMenu", tmp);
1665 free(tmp);
1668 return menu;
1679 *----------------------------------------------------------------------
1680 * OpenRootMenu--
1681 * Opens the root menu, parsing the menu configuration from the
1682 * defaults database.
1683 * If the menu is already mapped and is not sticked to the
1684 * root window, it will be unmapped.
1686 * Side effects:
1687 * The menu may be remade.
1689 * Notes:
1690 * Construction of OPEN_MENU entries are delayed to the moment the
1691 * user map's them.
1692 *----------------------------------------------------------------------
1694 void
1695 OpenRootMenu(WScreen *scr, int x, int y, int keyboard)
1697 WMenu *menu=NULL;
1698 proplist_t definition;
1700 static proplist_t domain=NULL;
1702 if (!domain) {
1703 domain = PLMakeString("WMRootMenu");
1707 scr->flags.root_menu_changed_shortcuts = 0;
1708 scr->flags.added_workspace_menu = 0;
1710 if (scr->root_menu && scr->root_menu->flags.mapped) {
1711 menu = scr->root_menu;
1712 if (!menu->flags.buttoned) {
1713 wMenuUnmap(menu);
1714 } else {
1715 wRaiseFrame(menu->frame->core);
1717 if (keyboard)
1718 wMenuMapAt(menu, 0, 0, True);
1719 else
1720 wMenuMapCopyAt(menu, x-menu->frame->core->width/2,
1721 y-menu->frame->top_width/2);
1723 return;
1727 definition = WDRootMenu->dictionary;
1730 definition = PLGetDomain(domain);
1732 if (definition) {
1733 if (PLIsArray(definition)) {
1734 if (!scr->root_menu
1735 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1736 menu = configureMenu(scr, definition);
1737 if (menu)
1738 menu->timestamp = WDRootMenu->timestamp;
1740 } else
1741 menu = NULL;
1742 } else {
1743 menu = configureMenu(scr, definition);
1747 if (!menu) {
1748 /* menu hasn't changed or could not be read */
1749 if (!scr->root_menu) {
1750 menu = makeDefaultMenu(scr);
1751 scr->root_menu = menu;
1753 menu = scr->root_menu;
1754 } else {
1755 /* new root menu */
1756 if (scr->root_menu)
1757 wMenuDestroy(scr->root_menu, True);
1758 scr->root_menu = menu;
1760 if (menu) {
1761 wMenuMapAt(menu, x-menu->frame->core->width/2, y-menu->frame->top_width/2,
1762 keyboard);
1765 if (scr->flags.root_menu_changed_shortcuts)
1766 rebindKeygrabs(scr);