Updating to version 0.20.2
[wmaker-crm.git] / src / rootmenu.c
blobd2aecdd451c30eb1e8176432671624a2d9c7f4dd
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;
69 static WMenu *readMenuPipe(WScreen *scr, char **file_name);
70 static WMenu *readMenuFile(WScreen *scr, char *file_name);
71 static WMenu *readMenuDirectory(WScreen *scr, char *title, char **file_name,
72 char *command);
75 typedef struct Shortcut {
76 struct Shortcut *next;
78 int modifier;
79 KeyCode keycode;
80 WMenuEntry *entry;
81 WMenu *menu;
82 } Shortcut;
86 static Shortcut *shortcutList = NULL;
90 * Syntax:
91 * # main menu
92 * "Menu Name" MENU
93 * "Title" EXEC command_to_exec -params
94 * "Submenu" MENU
95 * "Title" EXEC command_to_exec -params
96 * "Submenu" END
97 * "Workspaces" WORKSPACE_MENU
98 * "Title" built_in_command
99 * "Quit" EXIT
100 * "Quick Quit" EXIT QUICK
101 * "Menu Name" END
103 * Commands may be preceded by SHORTCUT key
105 * Built-in commands:
107 * INFO_PANEL - shows the Info Panel
108 * LEGAL_PANEL - shows the Legal info panel
109 * SHUTDOWN [QUICK] - closes the X server [without confirmation]
110 * REFRESH - forces the desktop to be repainted
111 * EXIT [QUICK] - exit the window manager [without confirmation]
112 * EXEC <program> - execute an external program
113 * WORKSPACE_MENU - places the workspace submenu
114 * ARRANGE_ICONS
115 * RESTART [<window manager>] - restarts the window manager
116 * SHOW_ALL - unhide all windows on workspace
117 * HIDE_OTHERS - hides all windows excep the focused one
118 * OPEN_MENU file - read menu data from file which must be a valid menu file.
119 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
120 * - read menu data from directory(ies) and
121 * eventually precede each with a command.
122 * OPEN_MENU | command
123 * - opens command and uses its stdout to construct and insert
124 * the resulting menu in current position. The output of
125 * command must be a valid menu description.
126 * The space between '|' and command is optional.
127 * SAVE_SESSION - saves the current state of the desktop, which include
128 * all running applications, all their hints (geometry,
129 * position on screen, workspace they live on, the dock
130 * or clip from where they were launched, and
131 * if minimized, shaded or hidden. Also saves the current
132 * workspace the user is on. All will be restored on every
133 * start of windowmaker until another SAVE_SESSION or
134 * CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
135 * WindowMaker domain file, then saving is automatically
136 * done on every windowmaker exit, overwriting any
137 * SAVE_SESSION or CLEAR_SESSION (see below).
138 * CLEAR_SESSION - clears any previous saved session. This will not have
139 * any effect if SaveSessionOnExit is True.
143 #define MAX(a,b) ((a)>(b) ? (a) : (b))
146 #define M_QUICK 1
148 /* menu commands */
150 static void
151 execCommand(WMenu *menu, WMenuEntry *entry)
153 char *cmdline;
154 static char *shell = NULL;
156 if (!shell) {
157 shell = getenv("SHELL");
158 if (!shell)
159 shell = "/bin/sh";
162 cmdline = ExpandOptions(menu->frame->screen_ptr, (char*)entry->clientdata);
164 XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
165 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT],
166 CurrentTime);
167 XSync(dpy, 0);
169 if (cmdline) {
170 /* We should not use ParseCommand() here, because it does not handle
171 * correctly '~/' expansion and '>' redirection.
172 * While we can do '~/' expansion ourselves, we can do nothing about
173 * '>' redirection.
174 * So we better let /bin/sh to do that for us. Dan.
175 * Ok. -Alfredo
177 if (fork()==0) {
178 SetupEnvironment(menu->frame->screen_ptr);
179 close(ConnectionNumber(dpy));
180 #ifdef HAVE_SETPGID
181 setpgid(0, 0);
182 #endif
183 execl(shell, shell, "-c", cmdline, NULL);
184 wsyserror("could not exec %s -c %s\n", shell, cmdline);
185 exit(-1);
187 free(cmdline);
189 XUngrabPointer(dpy, CurrentTime);
190 XSync(dpy, 0);
194 static void
195 exitCommand(WMenu *menu, WMenuEntry *entry)
197 static int inside = 0;
199 /* prevent reentrant calls */
200 if (inside)
201 return;
202 inside = 1;
204 if ((int)entry->clientdata==M_QUICK
205 || wMessageDialog(menu->frame->screen_ptr, _("Exit"),
206 _("Exit window manager?"),
207 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
208 #ifdef DEBUG
209 printf("Exiting WindowMaker.\n");
210 #endif
212 wScreenSaveState(menu->frame->screen_ptr);
214 RestoreDesktop(menu->frame->screen_ptr);
216 ExecExitScript();
217 exit(0);
219 inside = 0;
223 static void
224 shutdownCommand(WMenu *menu, WMenuEntry *entry)
226 static int inside = 0;
228 /* prevent reentrant calls */
229 if (inside)
230 return;
231 inside = 1;
233 if ((int)entry->clientdata==M_QUICK
234 || wMessageDialog(menu->frame->screen_ptr, _("Close X session"),
235 _("Close Window System session?\n(all applications will be closed)"),
236 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
237 /* printf(_("Exiting...\n"));*/
239 wScreenSaveState(menu->frame->screen_ptr);
241 WipeDesktop(menu->frame->screen_ptr);
243 ExecExitScript();
244 exit(0);
246 inside = 0;
250 static void
251 restartCommand(WMenu *menu, WMenuEntry *entry)
253 wScreenSaveState(menu->frame->screen_ptr);
255 RestoreDesktop(menu->frame->screen_ptr);
256 Restart((char*)entry->clientdata);
260 static void
261 refreshCommand(WMenu *menu, WMenuEntry *entry)
263 wRefreshDesktop(menu->frame->screen_ptr);
267 static void
268 arrangeIconsCommand(WMenu *menu, WMenuEntry *entry)
270 wArrangeIcons(menu->frame->screen_ptr, True);
273 static void
274 showAllCommand(WMenu *menu, WMenuEntry *entry)
276 wShowAllWindows(menu->frame->screen_ptr);
279 static void
280 hideOthersCommand(WMenu *menu, WMenuEntry *entry)
282 wHideOtherApplications(menu->frame->screen_ptr->focused_window);
286 static void
287 saveSessionCommand(WMenu *menu, WMenuEntry *entry)
289 wSessionSaveState(menu->frame->screen_ptr);
293 static void
294 clearSessionCommand(WMenu *menu, WMenuEntry *entry)
296 wSessionClearState(menu->frame->screen_ptr);
300 static void
301 infoPanelCommand(WMenu *menu, WMenuEntry *entry)
303 wShowInfoPanel(menu->frame->screen_ptr);
307 static void
308 legalPanelCommand(WMenu *menu, WMenuEntry *entry)
310 wShowLegalPanel(menu->frame->screen_ptr);
315 /********************************************************************/
317 static void
318 raiseMenus(WMenu *menu)
320 int i;
322 if (menu->flags.mapped) {
323 wRaiseFrame(menu->frame->core);
325 for (i=0; i<menu->cascade_no; i++) {
326 if (menu->cascades[i])
327 raiseMenus(menu->cascades[i]);
333 Bool
334 wRootMenuPerformShortcut(XEvent *event)
336 Shortcut *ptr;
337 int modifiers;
338 int done = 0;
340 /* ignore CapsLock */
341 modifiers = event->xkey.state & ValidModMask;
343 for (ptr = shortcutList; ptr!=NULL; ptr = ptr->next) {
344 if (ptr->keycode==0)
345 continue;
347 if (ptr->keycode==event->xkey.keycode && (ptr->modifier==modifiers)) {
348 (*ptr->entry->callback)(ptr->menu, ptr->entry);
349 done = True;
352 return done;
356 void
357 wRootMenuBindShortcuts(Window window)
359 Shortcut *ptr;
361 ptr = shortcutList;
362 while (ptr) {
363 if (ptr->modifier!=AnyModifier) {
364 XGrabKey(dpy, ptr->keycode, ptr->modifier|LockMask,
365 window, True, GrabModeAsync, GrabModeAsync);
366 #ifdef NUMLOCK_HACK
367 wHackedGrabKey(ptr->keycode, ptr->modifier,
368 window, True, GrabModeAsync, GrabModeAsync);
369 #endif
371 XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True,
372 GrabModeAsync, GrabModeAsync);
373 ptr = ptr->next;
378 static void
379 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 (!wwin->window_flags.no_bind_keys) {
389 wWindowSetKeyGrabs(wwin);
391 wwin = wwin->prev;
396 static void
397 removeShortcutsForMenu(WMenu *menu)
399 Shortcut *ptr, *tmp;
400 Shortcut *newList = NULL;
402 ptr = shortcutList;
403 while (ptr!=NULL) {
404 tmp = ptr->next;
405 if (ptr->menu == menu) {
406 free(ptr);
407 } else {
408 ptr->next = newList;
409 newList = ptr;
411 ptr = tmp;
413 shortcutList = newList;
414 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
418 static Bool
419 addShortcut(char *file, char *shortcutDefinition, WMenu *menu,
420 WMenuEntry *entry)
422 Shortcut *ptr;
423 KeySym ksym;
424 char *k;
425 char buf[128], *b;
427 ptr = wmalloc(sizeof(Shortcut));
429 strcpy(buf, shortcutDefinition);
430 b = (char*)buf;
432 /* get modifiers */
433 ptr->modifier = 0;
434 while ((k = strchr(b, '+'))!=NULL) {
435 int mod;
437 *k = 0;
438 mod = wXModifierFromKey(b);
439 if (mod<0) {
440 wwarning(_("%s:invalid key modifier \"%s\""), file,
441 shortcutDefinition);
442 free(ptr);
443 return False;
445 ptr->modifier |= mod;
447 b = k+1;
450 /* get key */
451 ksym = XStringToKeysym(b);
453 if (ksym==NoSymbol) {
454 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
455 file, shortcutDefinition, entry->text);
456 free(ptr);
457 return False;
460 ptr->keycode = XKeysymToKeycode(dpy, ksym);
461 if (ptr->keycode==0) {
462 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
463 shortcutDefinition, entry->text);
464 free(ptr);
465 return False;
468 ptr->menu = menu;
469 ptr->entry = entry;
471 ptr->next = shortcutList;
472 shortcutList = ptr;
474 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
476 return True;
480 /*******************************/
482 static char*
483 cropline(char *line)
485 char *end;
487 if (strlen(line)==0)
488 return line;
490 end = &(line[strlen(line)])-1;
491 while (isspace(*line) && *line!=0) line++;
492 while (end>line && isspace(*end)) {
493 *end=0;
494 end--;
496 return line;
500 static char*
501 next_token(char *line, char **next)
503 char *tmp, c;
504 char *ret;
506 *next = NULL;
507 while (*line==' ' || *line=='\t') line++;
509 tmp = line;
511 if (*tmp=='"') {
512 tmp++; line++;
513 while (*tmp!=0 && *tmp!='"') tmp++;
514 if (*tmp!='"') {
515 wwarning(_("%s: unmatched '\"' in menu file"), line);
516 return NULL;
518 } else {
519 do {
520 if (*tmp=='\\')
521 tmp++;
523 if (*tmp!=0)
524 tmp++;
526 } while (*tmp!=0 && *tmp!=' ' && *tmp!='\t');
529 c = *tmp;
530 *tmp = 0;
531 ret = wstrdup(line);
532 *tmp = c;
534 if (c==0)
535 return ret;
536 else
537 tmp++;
539 /* skip blanks */
540 while (*tmp==' ' || *tmp=='\t') tmp++;
542 if (*tmp!=0)
543 *next = tmp;
545 return ret;
549 static void
550 separateCommand(char *line, char ***file, char **command)
552 char *token, *tmp = line;
553 LinkedList *list = NULL;
554 int count, i;
556 *file = NULL;
557 *command = NULL;
558 do {
559 token = next_token(tmp, &tmp);
560 if (token) {
561 if (strcmp(token, "WITH")==0) {
562 if (tmp!=NULL && *tmp!=0)
563 *command = wstrdup(tmp);
564 else
565 wwarning(_("%s: missing command"), line);
566 break;
568 list = list_cons(token, list);
570 } while (token!=NULL && tmp!=NULL);
572 count = list_length(list);
573 if (count>0) {
574 *file = wmalloc(sizeof(char*)*(count+1));
575 i = count;
576 (*file)[count] = NULL;
577 while (list!=NULL) {
578 (*file)[--i] = list->head;
579 list_remove_head(&list);
585 static void
586 constructMenu(WMenu *menu, WMenuEntry *entry)
588 WMenu *submenu;
589 struct stat stat_buf;
590 char **path;
591 char *cmd;
592 char *lpath = NULL;
593 int i, first=-1;
594 time_t last=0;
596 separateCommand((char*)entry->clientdata, &path, &cmd);
597 if (!path || *path==NULL || **path==0) {
598 wwarning(_("invalid OPEN_MENU specification: %s"),
599 (char*)entry->clientdata);
600 return;
603 if (path[0][0]=='|') {
604 /* pipe menu */
606 if (!menu->cascades[entry->cascade] ||
607 menu->cascades[entry->cascade]->timestamp == 0) {
608 /* parse pipe */
610 submenu = readMenuPipe(menu->frame->screen_ptr, path);
612 /* there's no automatic reloading */
613 if(submenu != NULL)
614 submenu->timestamp = 1;
615 } else {
616 submenu = NULL;
619 } else {
620 i=0;
621 while(path[i] != NULL) {
622 char *tmp;
624 tmp = wexpandpath(path[i]);
625 free(path[i]);
626 path[i] = tmp;
628 if (Locale) {
629 lpath = wmalloc(strlen(path[i])+32);
631 strcpy(lpath, path[i]);
632 strcat(lpath, ".");
633 strcat(lpath, Locale);
634 if (stat(lpath, &stat_buf)<0) {
635 int i;
636 i = strlen(Locale);
637 if (i>2) {
638 lpath[strlen(lpath)-(i-2)]=0;
639 if (stat(lpath, &stat_buf)==0) {
640 free(path[i]);
641 path[i] = lpath;
642 lpath = NULL;
645 } else {
646 free(path[i]);
647 path[i] = lpath;
648 lpath = NULL;
652 if (lpath) {
653 free(lpath);
654 lpath = NULL;
657 if (stat(path[i], &stat_buf)==0) {
658 if (last < stat_buf.st_mtime)
659 last = stat_buf.st_mtime;
660 if (first<0)
661 first=i;
662 } else {
663 wsyserror(_("%s:could not stat menu"), path[i]);
664 /*goto finish;*/
667 i++;
670 if (first < 0) {
671 wsyserror(_("%s:could not stat menu :%s"), "OPEN_MENU",
672 (char*)entry->clientdata);
673 goto finish;
675 stat(path[first], &stat_buf);
676 if (!menu->cascades[entry->cascade]
677 || menu->cascades[entry->cascade]->timestamp < last) {
679 if (S_ISDIR(stat_buf.st_mode)) {
680 /* menu directory */
681 submenu = readMenuDirectory(menu->frame->screen_ptr,
682 entry->text, &path[first], cmd);
683 if (submenu)
684 submenu->timestamp = last;
685 } else if (S_ISREG(stat_buf.st_mode)) {
686 /* menu file */
688 if (cmd || path[1])
689 wwarning(_("too many parameters in OPEN_MENU: %s"),
690 (char*)entry->clientdata);
692 submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
693 if (submenu)
694 submenu->timestamp = stat_buf.st_mtime;
695 } else {
696 submenu = NULL;
698 } else {
699 submenu = NULL;
703 if (submenu) {
704 wMenuEntryRemoveCascade(menu, entry);
705 wMenuEntrySetCascade(menu, entry, submenu);
708 finish:
709 i = 0;
710 while (path[i]!=NULL)
711 free(path[i++]);
712 free(path);
713 if (cmd)
714 free(cmd);
718 static WMenuEntry*
719 addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
721 WMenu *wsmenu;
722 WMenuEntry *entry;
724 if (scr->flags.added_workspace_menu) {
725 wwarning(_("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
726 return NULL;
727 } else {
728 scr->flags.added_workspace_menu = 1;
730 wsmenu = wWorkspaceMenuMake(scr, True);
731 scr->workspace_menu = wsmenu;
732 entry=wMenuAddCallback(menu, title, NULL, NULL);
733 wMenuEntrySetCascade(menu, entry, wsmenu);
735 wWorkspaceMenuUpdate(scr, wsmenu);
737 return entry;
741 static WMenuEntry*
742 addMenuEntry(WMenu *menu, char *title, char *shortcut, char *command,
743 char *params, char *file_name)
745 WScreen *scr;
746 WMenuEntry *entry = NULL;
747 Bool shortcutOk = False;
749 if (!menu)
750 return NULL;
751 scr = menu->frame->screen_ptr;
752 if (strcmp(command, "OPEN_MENU")==0) {
753 if (!params) {
754 wwarning(_("%s:missing parameter for menu command \"%s\""),
755 file_name, command);
756 } else {
757 WMenu *dummy;
758 char *path;
760 path = wfindfile(DEF_CONFIG_PATHS, params);
761 if (!path) {
762 path = wstrdup(params);
764 dummy = wMenuCreate(scr, title, False);
765 dummy->on_destroy = removeShortcutsForMenu;
766 entry = wMenuAddCallback(menu, title, constructMenu, path);
767 entry->free_cdata = free;
768 wMenuEntrySetCascade(menu, entry, dummy);
770 } else if (strcmp(command, "EXEC")==0) {
771 if (!params)
772 wwarning(_("%s:missing parameter for menu command \"%s\""),
773 file_name, command);
774 else {
775 entry = wMenuAddCallback(menu, title, execCommand, wstrdup(params));
776 entry->free_cdata = free;
777 shortcutOk = True;
779 } else if (strcmp(command, "EXIT")==0) {
781 if (params && strcmp(params, "QUICK")==0)
782 entry = wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
783 else
784 entry = wMenuAddCallback(menu, title, exitCommand, NULL);
786 shortcutOk = True;
787 } else if (strcmp(command, "SHUTDOWN")==0) {
789 if (params && strcmp(params, "QUICK")==0)
790 entry = wMenuAddCallback(menu, title, shutdownCommand,
791 (void*)M_QUICK);
792 else
793 entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
795 shortcutOk = True;
796 } else if (strcmp(command, "REFRESH")==0) {
797 entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
799 shortcutOk = True;
800 } else if (strcmp(command, "WORKSPACE_MENU")==0) {
801 entry = addWorkspaceMenu(scr, menu, title);
803 shortcutOk = True;
804 } else if (strcmp(command, "ARRANGE_ICONS")==0) {
805 entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
807 shortcutOk = True;
808 } else if (strcmp(command, "HIDE_OTHERS")==0) {
809 entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
811 shortcutOk = True;
812 } else if (strcmp(command, "SHOW_ALL")==0) {
813 entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
815 shortcutOk = True;
816 } else if (strcmp(command, "RESTART")==0) {
817 entry = wMenuAddCallback(menu, title, restartCommand,
818 params ? wstrdup(params) : NULL);
819 entry->free_cdata = free;
820 shortcutOk = True;
821 } else if (strcmp(command, "SAVE_SESSION")==0) {
822 entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
824 shortcutOk = True;
825 } else if (strcmp(command, "CLEAR_SESSION")==0) {
826 entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
827 shortcutOk = True;
828 } else if (strcmp(command, "INFO_PANEL")==0) {
829 entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
830 shortcutOk = True;
831 } else if (strcmp(command, "LEGAL_PANEL")==0) {
832 entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
833 shortcutOk = True;
834 } else {
835 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name,
836 command);
838 return NULL;
841 if (shortcut && entry) {
842 if (!shortcutOk) {
843 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name,
844 title);
845 } else {
846 if (addShortcut(file_name, shortcut, menu, entry)) {
848 entry->rtext = GetShortcutString(shortcut);
850 entry->rtext = wstrdup(shortcut);
856 return entry;
861 /******************* Menu Configuration From File *******************/
863 static void
864 separateline(char *line, char *title, char *command, char *parameter,
865 char *shortcut)
867 int l, i;
869 l = strlen(line);
871 *title = 0;
872 *command = 0;
873 *parameter = 0;
874 *shortcut = 0;
875 /* get the title */
876 while (isspace(*line) && (*line!=0)) line++;
877 if (*line=='"') {
878 line++;
879 i=0;
880 while (line[i]!='"' && (line[i]!=0)) i++;
881 if (line[i]!='"')
882 return;
883 } else {
884 i=0;
885 while (!isspace(line[i]) && (line[i]!=0)) i++;
887 strncpy(title, line, i);
888 title[i++]=0;
889 line+=i;
891 /* get the command or shortcut keyword */
892 while (isspace(*line) && (*line!=0)) line++;
893 if (*line==0)
894 return;
895 i=0;
896 while (!isspace(line[i]) && (line[i]!=0)) i++;
897 strncpy(command, line, i);
898 command[i++]=0;
899 line+=i;
901 if (strcmp(command, "SHORTCUT")==0) {
902 /* get the shortcut key */
903 while (isspace(*line) && (*line!=0)) line++;
904 if (*line=='"') {
905 line++;
906 i=0;
907 while (line[i]!='"' && (line[i]!=0)) i++;
908 if (line[i]!='"')
909 return;
910 } else {
911 i=0;
912 while (!isspace(line[i]) && (line[i]!=0)) i++;
914 strncpy(shortcut, line, i);
915 shortcut[i++]=0;
916 line+=i;
918 *command=0;
920 /* get the command */
921 while (isspace(*line) && (*line!=0)) line++;
922 if (*line==0)
923 return;
924 i=0;
925 while (!isspace(line[i]) && (line[i]!=0)) i++;
926 strncpy(command, line, i);
927 command[i++]=0;
928 line+=i;
931 /* get the parameters */
932 while (isspace(*line) && (*line!=0)) line++;
933 if (*line==0)
934 return;
936 if (*line=='"') {
937 line++;
938 l = 0;
939 while (line[l]!=0 && line[l]!='"') {
940 parameter[l] = line[l];
941 l++;
943 parameter[l] = 0;
944 return;
947 l = strlen(line);
948 while (isspace(line[l]) && (l>0)) l--;
949 strncpy(parameter, line, l);
950 parameter[l]=0;
954 static WMenu*
955 parseCascade(WScreen *scr, WMenu *menu, FILE *file, char *file_name)
957 char linebuf[MAXLINE];
958 char elinebuf[MAXLINE];
959 char title[MAXLINE];
960 char command[MAXLINE];
961 char shortcut[MAXLINE];
962 char params[MAXLINE];
963 char *line;
965 while (!IsEof(file)) {
966 int lsize, ok;
968 ok = 0;
969 fgets(linebuf, MAXLINE, file);
970 line = cropline(linebuf);
971 lsize = strlen(line);
972 do {
973 if (line[lsize-1]=='\\') {
974 char *line2;
975 int lsize2;
976 fgets(elinebuf, MAXLINE, file);
977 line2=cropline(elinebuf);
978 lsize2=strlen(line2);
979 if (lsize2+lsize>MAXLINE) {
980 wwarning(_("%s:maximal line size exceeded in menu config: %s"),
981 file_name, line);
982 ok=2;
983 } else {
984 line[lsize-1]=0;
985 lsize+=lsize2-1;
986 strcat(line, line2);
988 } else {
989 ok=1;
991 } while (!ok && !IsEof(file));
992 if (ok==2)
993 continue;
995 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
996 continue;
999 separateline(line, title, command, params, shortcut);
1001 if (!command[0]) {
1002 wwarning(_("%s:missing command in menu config: %s"), file_name,
1003 line);
1004 goto error;
1007 if (strcasecmp(command, "MENU")==0) {
1008 WMenu *cascade;
1010 /* start submenu */
1012 cascade = wMenuCreate(scr, title, False);
1013 cascade->on_destroy = removeShortcutsForMenu;
1014 if (parseCascade(scr, cascade, file, file_name)==NULL) {
1015 wMenuDestroy(cascade, True);
1016 } else {
1017 wMenuEntrySetCascade(menu,
1018 wMenuAddCallback(menu, title, NULL, NULL),
1019 cascade);
1021 } else if (strcasecmp(command, "END")==0) {
1022 /* end of menu */
1023 return menu;
1025 } else {
1026 /* normal items */
1027 addMenuEntry(menu, title, shortcut[0] ? shortcut : NULL, command,
1028 params[0] ? params : NULL, file_name);
1032 wwarning(_("%s:syntax error in menu file:END declaration missing"),
1033 file_name);
1034 return menu;
1036 error:
1037 return menu;
1041 static WMenu*
1042 readMenuFile(WScreen *scr, char *file_name)
1044 WMenu *menu=NULL;
1045 FILE *file = NULL;
1046 char linebuf[MAXLINE];
1047 char title[MAXLINE];
1048 char shortcut[MAXLINE];
1049 char command[MAXLINE];
1050 char params[MAXLINE];
1051 char *line;
1052 #ifdef USECPP
1053 char *args;
1054 int cpp = 0;
1055 #endif
1057 #ifdef USECPP
1058 if (!wPreferences.flags.nocpp) {
1059 args = MakeCPPArgs(file_name);
1060 if (!args) {
1061 wwarning(_("could not make arguments for menu file preprocessor"));
1062 } else {
1063 sprintf(command, "%s %s %s", CPP_PATH, args, file_name);
1064 free(args);
1065 file = popen(command, "r");
1066 if (!file) {
1067 wsyserror(_("%s:could not open/preprocess menu file"),
1068 file_name);
1069 } else {
1070 cpp = 1;
1074 #endif /* USECPP */
1076 if (!file) {
1077 file = fopen(file_name, "r");
1078 if (!file) {
1079 wsyserror(_("%s:could not open menu file"), file_name);
1080 return NULL;
1084 while (!IsEof(file)) {
1085 if (!fgets(linebuf, MAXLINE, file))
1086 break;
1087 line = cropline(linebuf);
1088 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1089 continue;
1091 separateline(line, title, command, params, shortcut);
1093 if (!command[0]) {
1094 wwarning(_("%s:missing command in menu config: %s"), file_name,
1095 line);
1096 break;
1098 if (strcasecmp(command, "MENU")==0) {
1099 menu = wMenuCreate(scr, title, True);
1100 menu->on_destroy = removeShortcutsForMenu;
1101 if (!parseCascade(scr, menu, file, file_name)) {
1102 wMenuDestroy(menu, True);
1104 break;
1105 } else {
1106 wwarning(_("%s:invalid menu file. MENU command is missing"),
1107 file_name);
1108 break;
1112 #ifdef CPP
1113 if (cpp) {
1114 if (pclose(file)==-1) {
1115 wsyserror(_("error reading preprocessed menu data"));
1117 } else {
1118 fclose(file);
1120 #else
1121 fclose(file);
1122 #endif
1124 return menu;
1127 /************ Menu Configuration From Pipe *************/
1129 static WMenu*
1130 readMenuPipe(WScreen *scr, char **file_name)
1132 WMenu *menu=NULL;
1133 FILE *file = NULL;
1134 char linebuf[MAXLINE];
1135 char title[MAXLINE];
1136 char command[MAXLINE];
1137 char params[MAXLINE];
1138 char shortcut[MAXLINE];
1139 char *line;
1140 char * filename;
1141 char flat_file[MAXLINE];
1142 int i;
1143 #ifdef USECPP
1144 char *args;
1145 int cpp = 0;
1146 #endif
1148 flat_file[0] = '\0';
1150 for(i = 0 ; file_name[i] != NULL ; i++) {
1151 strcat(flat_file, file_name[i]);
1152 strcat(flat_file, " ");
1154 filename = flat_file+1;
1156 #ifdef USECPP
1157 if (!wPreferences.flags.nocpp) {
1158 args = MakeCPPArgs(filename);
1159 if (!args) {
1160 wwarning(_("could not make arguments for menu file preprocessor"));
1161 } else {
1162 sprintf(command, "%s | %s %s", filename, CPP_PATH, args);
1164 free(args);
1165 file = popen(command, "r");
1166 if (!file) {
1167 wsyserror(_("%s:could not open/preprocess menu file"), filename);
1168 } else {
1169 cpp = 1;
1174 #endif /* USECPP */
1176 if (!file) {
1177 file = popen(filename, "r");
1179 if (!file) {
1180 wsyserror(_("%s:could not open menu file"), filename);
1181 return NULL;
1185 while (!IsEof(file)) {
1186 if (!fgets(linebuf, MAXLINE, file))
1187 break;
1188 line = cropline(linebuf);
1189 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1190 continue;
1192 separateline(line, title, command, params, shortcut);
1194 if (!command[0]) {
1195 wwarning(_("%s:missing command in menu config: %s"), file_name,
1196 line);
1197 break;
1199 if (strcasecmp(command, "MENU")==0) {
1200 menu = wMenuCreate(scr, title, True);
1201 menu->on_destroy = removeShortcutsForMenu;
1202 if (!parseCascade(scr, menu, file, filename)) {
1203 wMenuDestroy(menu, True);
1205 break;
1206 } else {
1207 wwarning(_("%s:no title given for the root menu"), filename);
1208 break;
1212 pclose(file);
1214 return menu;
1219 typedef struct {
1220 char *name;
1221 int index;
1222 } dir_data;
1225 static int
1226 myCompare(dir_data *d1, dir_data *d2)
1228 return strcmp(d1->name, d2->name);
1232 /************ Menu Configuration From Directory *************/
1233 static WMenu*
1234 readMenuDirectory(WScreen *scr, char *title, char **path, char *command)
1236 DIR *dir;
1237 struct dirent *dentry;
1238 struct stat stat_buf;
1239 WMenu *menu=NULL;
1240 char *buffer;
1241 LinkedList *dirs = NULL, *files = NULL;
1242 int length, i, have_space=0;
1243 dir_data *data;
1245 i=0;
1246 while (path[i]!=NULL) {
1247 dir = opendir(path[i]);
1248 if (!dir) {
1249 i++;
1250 continue;
1253 while ((dentry = readdir(dir))) {
1255 if (strcmp(dentry->d_name, ".")==0 ||
1256 strcmp(dentry->d_name, "..")==0)
1257 continue;
1259 buffer = wmalloc(strlen(path[i])+strlen(dentry->d_name)+4);
1260 if (!buffer) {
1261 wsyserror(_("out of memory while constructing directory menu %s"),
1262 path[i]);
1263 break;
1266 strcpy(buffer, path[i]);
1267 strcat(buffer, "/");
1268 strcat(buffer, dentry->d_name);
1270 if (stat(buffer, &stat_buf)!=0) {
1271 wsyserror(_("%s:could not stat file \"%s\" in menu directory"),
1272 path[i], dentry->d_name);
1273 } else {
1274 data = (dir_data*) wmalloc(sizeof(dir_data));
1275 data->name = wstrdup(dentry->d_name);
1276 data->index = i;
1277 if (S_ISDIR(stat_buf.st_mode)) {
1278 /* access always returns success for user root */
1279 if (access(buffer, X_OK)==0) {
1280 /* Directory is accesible. Add to directory list */
1281 list_insert_sorted(data, &dirs, (int(*)())myCompare);
1282 data = NULL;
1284 } else if (S_ISREG(stat_buf.st_mode)) {
1285 /* Hack because access always returns X_OK success for user root */
1286 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1287 if ((command!=NULL && access(buffer, R_OK)==0) ||
1288 (command==NULL && access(buffer, X_OK)==0 &&
1289 (stat_buf.st_mode & S_IXANY))) {
1290 list_insert_sorted(data, &files, (int(*)())myCompare);
1291 data = NULL;
1294 if (data!=NULL) {
1295 if (data->name)
1296 free(data->name);
1297 free(data);
1298 data = NULL;
1301 free(buffer);
1304 closedir(dir);
1305 i++;
1308 if (!dirs && !files)
1309 return NULL;
1311 menu = wMenuCreate(scr, title, False);
1312 menu->on_destroy = removeShortcutsForMenu;
1314 while (dirs != NULL) {
1315 /* New directory. Use same OPEN_MENU command that was used
1316 * for the current directory. */
1317 dir_data *d = (dir_data*)dirs->head;
1319 length = strlen(path[d->index])+strlen(d->name)+6;
1320 if (command)
1321 length += strlen(command) + 6;
1322 buffer = wmalloc(length);
1323 if (!buffer) {
1324 wsyserror(_("out of memory while constructing directory menu %s"),
1325 path[d->index]);
1326 break;
1329 have_space = strchr(path[d->index], ' ')!=NULL ||
1330 strchr(d->name, ' ')!=NULL;
1331 if (have_space) {
1332 buffer[0] = '"';
1333 buffer[1] = 0;
1334 strcat(buffer, path[d->index]);
1335 } else {
1336 strcpy(buffer, path[d->index]);
1338 strcat(buffer, "/");
1339 strcat(buffer, d->name);
1340 if (have_space)
1341 strcat(buffer, "\"");
1342 if (command) {
1343 strcat(buffer, " WITH ");
1344 strcat(buffer, command);
1347 addMenuEntry(menu, d->name, NULL, "OPEN_MENU", buffer, path[d->index]);
1349 free(buffer);
1350 if (dirs->head) {
1351 if (d->name)
1352 free(d->name);
1353 free(dirs->head);
1355 list_remove_head(&dirs);
1358 while (files != NULL) {
1359 /* executable: add as entry */
1360 dir_data *f = (dir_data*) files->head;;
1362 length = strlen(path[f->index])+strlen(f->name)+6;
1363 if (command)
1364 length += strlen(command);
1366 buffer = wmalloc(length);
1367 if (!buffer) {
1368 wsyserror(_("out of memory while constructing directory menu %s"),
1369 path[f->index]);
1370 break;
1373 have_space = strchr(path[f->index], ' ')!=NULL ||
1374 strchr(f->name, ' ')!=NULL;
1375 if (command!=NULL) {
1376 strcpy(buffer, command);
1377 strcat(buffer, " ");
1378 if (have_space)
1379 strcat(buffer, "\"");
1380 strcat(buffer, path[f->index]);
1381 } else {
1382 if (have_space) {
1383 buffer[0] = '"';
1384 buffer[1] = 0;
1385 strcat(buffer, path[f->index]);
1386 } else {
1387 strcpy(buffer, path[f->index]);
1390 strcat(buffer, "/");
1391 strcat(buffer, f->name);
1392 if (have_space)
1393 strcat(buffer, "\"");
1395 addMenuEntry(menu, f->name, NULL, "EXEC", buffer, path[f->index]);
1397 free(buffer);
1398 if (files->head) {
1399 if (f->name)
1400 free(f->name);
1401 free(files->head);
1403 list_remove_head(&files);
1406 return menu;
1410 /************ Menu Configuration From WMRootMenu *************/
1412 static WMenu*
1413 makeDefaultMenu(WScreen *scr)
1415 WMenu *menu=NULL;
1417 menu = wMenuCreate(scr, _("Commands"), True);
1418 wMenuAddCallback(menu, "XTerm", execCommand, "xterm");
1419 wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
1420 return menu;
1428 *----------------------------------------------------------------------
1429 * configureMenu--
1430 * Reads root menu configuration from defaults database.
1432 *----------------------------------------------------------------------
1434 static WMenu*
1435 configureMenu(WScreen *scr, proplist_t definition)
1437 WMenu *menu = NULL;
1438 proplist_t elem;
1439 int i, count;
1440 proplist_t title, command, params;
1441 char *tmp, *mtitle;
1444 if (PLIsString(definition)) {
1445 struct stat stat_buf;
1446 char *path = NULL;
1448 /* menu definition is a string. Probably a path, so parse the file */
1450 tmp = wexpandpath(PLGetString(definition));
1452 if (Locale) {
1453 path = wmalloc(strlen(tmp)+32);
1455 strcpy(path, tmp);
1456 strcat(path, ".");
1457 strcat(path, Locale);
1459 /* look for menu.xy */
1460 if (stat(path, &stat_buf)<0) {
1461 int i;
1462 i = strlen(Locale);
1463 if (i>2) {
1464 path[strlen(path)-(i-2)]=0;
1465 /* look for menu.xy_zw */
1466 if (stat(path, &stat_buf)<0) {
1467 free(path);
1468 /* If did not find any localized menus, try
1469 * only menu. This can also mean that
1470 * the path in WMRootMenu was already the
1471 * path for the localized menu (eg: menu = "menu.ab")
1473 path = NULL;
1475 } else {
1476 free(path);
1477 path = NULL;
1482 if (!path)
1483 path = wfindfile(DEF_CONFIG_PATHS, tmp);
1485 if (!path) {
1486 wsyserror(_("could not find menu file \"%s\" referenced in WMRootMenu"),
1487 tmp);
1488 free(tmp);
1489 return NULL;
1492 if (stat(path, &stat_buf)<0) {
1493 wsyserror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
1494 free(path);
1495 free(tmp);
1496 return NULL;
1499 if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
1500 /* if the pointer in WMRootMenu has changed */
1501 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1502 menu = readMenuFile(scr, path);
1503 if (menu)
1504 menu->timestamp = MAX(stat_buf.st_mtime, WDRootMenu->timestamp);
1505 } else {
1506 menu = NULL;
1508 free(path);
1509 free(tmp);
1511 return menu;
1514 count = PLGetNumberOfElements(definition);
1515 if (count==0)
1516 return NULL;
1518 elem = PLGetArrayElement(definition, 0);
1519 if (!PLIsString(elem)) {
1520 tmp = PLGetDescription(elem);
1521 wwarning(_("%s:format error in root menu configuration \"%s\""),
1522 "WMRootMenu", tmp);
1523 free(tmp);
1524 return NULL;
1526 mtitle = PLGetString(elem);
1528 menu = wMenuCreate(scr, mtitle, False);
1529 menu->on_destroy = removeShortcutsForMenu;
1531 for (i=1; i<count; i++) {
1532 elem = PLGetArrayElement(definition, i);
1534 if (!PLIsArray(elem) || PLGetNumberOfElements(elem) < 2)
1535 goto error;
1537 if (PLIsArray(PLGetArrayElement(elem,1))) {
1538 WMenu *submenu;
1539 WMenuEntry *mentry;
1541 /* submenu */
1542 submenu = configureMenu(scr, elem);
1543 if (submenu) {
1544 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL,
1545 NULL);
1546 wMenuEntrySetCascade(menu, mentry, submenu);
1548 } else {
1549 int idx = 0;
1550 char *shortcut;
1551 /* normal entry */
1553 title = PLGetArrayElement(elem, idx++);
1554 shortcut = PLGetArrayElement(elem, idx++);
1555 if (strcmp(PLGetString(shortcut), "SHORTCUT")==0) {
1556 shortcut = PLGetArrayElement(elem, idx++);
1557 command = PLGetArrayElement(elem, idx++);
1558 } else {
1559 command = shortcut;
1560 shortcut = NULL;
1562 params = PLGetArrayElement(elem, idx++);
1564 if (!title || !command)
1565 goto error;
1567 addMenuEntry(menu, PLGetString(title),
1568 shortcut ? PLGetString(shortcut) : NULL,
1569 PLGetString(command),
1570 params ? PLGetString(params) : NULL, "WMRootMenu");
1572 continue;
1574 error:
1575 tmp = PLGetDescription(elem);
1576 wwarning(_("%s:format error in root menu configuration \"%s\""),
1577 "WMRootMenu", tmp);
1578 free(tmp);
1581 return menu;
1592 *----------------------------------------------------------------------
1593 * OpenRootMenu--
1594 * Opens the root menu, parsing the menu configuration from the
1595 * defaults database.
1596 * If the menu is already mapped and is not sticked to the
1597 * root window, it will be unmapped.
1599 * Side effects:
1600 * The menu may be remade.
1602 * Notes:
1603 * Construction of OPEN_MENU entries are delayed to the moment the
1604 * user map's them.
1605 *----------------------------------------------------------------------
1607 void
1608 OpenRootMenu(WScreen *scr, int x, int y, int keyboard)
1610 WMenu *menu=NULL;
1611 proplist_t definition;
1613 static proplist_t domain=NULL;
1615 if (!domain) {
1616 domain = PLMakeString("WMRootMenu");
1620 scr->flags.root_menu_changed_shortcuts = 0;
1621 scr->flags.added_workspace_menu = 0;
1623 if (scr->root_menu && scr->root_menu->flags.mapped) {
1624 menu = scr->root_menu;
1625 if (!menu->flags.buttoned) {
1626 wMenuUnmap(menu);
1627 } else {
1628 wRaiseFrame(menu->frame->core);
1630 if (keyboard)
1631 wMenuMapAt(menu, 0, 0, True);
1632 else
1633 wMenuMapCopyAt(menu, x-menu->frame->core->width/2,
1634 y-menu->frame->top_width/2);
1636 return;
1640 definition = WDRootMenu->dictionary;
1643 definition = PLGetDomain(domain);
1645 if (definition) {
1646 if (PLIsArray(definition)) {
1647 if (!scr->root_menu
1648 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1649 menu = configureMenu(scr, definition);
1650 if (menu)
1651 menu->timestamp = WDRootMenu->timestamp;
1652 } else
1653 menu = NULL;
1654 } else {
1655 menu = configureMenu(scr, definition);
1659 if (!menu) {
1660 /* menu hasn't changed or could not be read */
1661 if (!scr->root_menu) {
1662 menu = makeDefaultMenu(scr);
1663 scr->root_menu = menu;
1665 menu = scr->root_menu;
1666 } else {
1667 /* new root menu */
1668 if (scr->root_menu)
1669 wMenuDestroy(scr->root_menu, True);
1670 scr->root_menu = menu;
1672 if (menu) {
1673 wMenuMapAt(menu, x-menu->frame->core->width/2, y-menu->frame->top_width/2,
1674 keyboard);
1677 if (scr->flags.root_menu_changed_shortcuts)
1678 rebindKeygrabs(scr);