Initial revision
[wmaker-crm.git] / src / rootmenu.c
blobb01bb51bfa2c5054f76f272a0583c16c6a4317b3
1 /* rootmenu.c- user defined menu
2 *
3 * WindowMaker 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 #if 0
155 char *path, *chr;
156 #endif
157 cmdline = ExpandOptions(menu->frame->screen_ptr, (char*)entry->clientdata);
158 #if 0
159 path = wstrdup(cmdline);
160 chr = strchr(path, ' ');
161 if (chr)
162 *chr = 0;
164 if (access(path, X_OK)!=0) {
165 path = realloc(path, strlen(cmdline)+128);
166 sprintf(path, _("Program \"%s\" not found or cannot be executed."),
167 cmdline);
168 wMessageDialog(menu->frame->screen_ptr, _("Error"), path,
169 _("OK"), NULL, NULL);
170 free(path);
171 free(cmdline);
172 return;
175 free(path);
176 #endif
177 XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
178 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT],
179 CurrentTime);
180 XSync(dpy, 0);
182 if (cmdline) {
183 /* We should not use ParseCommand() here, because it does not handle
184 * correctly '~/' expansion and '>' redirection.
185 * While we can do '~/' expansion ourselves, we can do nothing about
186 * '>' redirection.
187 * So we better let /bin/sh to do that for us. -Dan
188 * Ok. -Alfredo
190 if (fork()==0) {
191 SetupEnvironment(menu->frame->screen_ptr);
192 close(ConnectionNumber(dpy));
193 #ifdef HAVE_SETPGID
194 setpgid(0, 0);
195 #endif
196 execl("/bin/sh", "/bin/sh", "-c", cmdline, NULL);
197 exit(-1);
199 free(cmdline);
201 XUngrabPointer(dpy, CurrentTime);
202 XSync(dpy, 0);
206 static void
207 exitCommand(WMenu *menu, WMenuEntry *entry)
209 /* prevent reentrant calls */
210 wMenuSetEnabled(menu, entry->order, False);
211 if ((int)entry->clientdata==M_QUICK
212 || wMessageDialog(menu->frame->screen_ptr, _("Exit"),
213 _("Exit window manager?"),
214 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
215 #ifdef DEBUG
216 printf("Exiting WindowMaker.\n");
217 #endif
219 wScreenSaveState(menu->frame->screen_ptr);
221 RestoreDesktop(menu->frame->screen_ptr);
223 ExecExitScript();
224 exit(0);
226 wMenuSetEnabled(menu, entry->order, True);
230 static void
231 shutdownCommand(WMenu *menu, WMenuEntry *entry)
233 /* prevent reentrant calls */
234 wMenuSetEnabled(menu, entry->order, False);
235 if ((int)entry->clientdata==M_QUICK
236 || wMessageDialog(menu->frame->screen_ptr, _("Close X session"),
237 _("Close Window System session?\n(all applications will be closed)"),
238 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
239 /* printf(_("Exiting...\n"));*/
241 wScreenSaveState(menu->frame->screen_ptr);
243 WipeDesktop(menu->frame->screen_ptr);
245 ExecExitScript();
246 exit(0);
248 wMenuSetEnabled(menu, entry->order, True);
252 static void
253 restartCommand(WMenu *menu, WMenuEntry *entry)
255 wScreenSaveState(menu->frame->screen_ptr);
257 RestoreDesktop(menu->frame->screen_ptr);
258 Restart((char*)entry->clientdata);
262 static void
263 refreshCommand(WMenu *menu, WMenuEntry *entry)
265 wRefreshDesktop(menu->frame->screen_ptr);
269 static void
270 arrangeIconsCommand(WMenu *menu, WMenuEntry *entry)
272 wArrangeIcons(menu->frame->screen_ptr, True);
275 static void
276 showAllCommand(WMenu *menu, WMenuEntry *entry)
278 wShowAllWindows(menu->frame->screen_ptr);
281 static void
282 hideOthersCommand(WMenu *menu, WMenuEntry *entry)
284 wHideOtherApplications(menu->frame->screen_ptr->focused_window);
288 static void
289 saveSessionCommand(WMenu *menu, WMenuEntry *entry)
291 wSessionSaveState(menu->frame->screen_ptr);
295 static void
296 clearSessionCommand(WMenu *menu, WMenuEntry *entry)
298 wSessionClearState(menu->frame->screen_ptr);
302 static void
303 infoPanelCommand(WMenu *menu, WMenuEntry *entry)
305 wShowInfoPanel(menu->frame->screen_ptr);
309 static void
310 legalPanelCommand(WMenu *menu, WMenuEntry *entry)
312 wShowLegalPanel(menu->frame->screen_ptr);
317 /********************************************************************/
319 static void
320 raiseMenus(WMenu *menu)
322 int i;
324 if (menu->flags.mapped) {
325 wRaiseFrame(menu->frame->core);
327 for (i=0; i<menu->cascade_no; i++) {
328 if (menu->cascades[i])
329 raiseMenus(menu->cascades[i]);
335 Bool
336 wRootMenuPerformShortcut(XEvent *event)
338 Shortcut *ptr;
339 int modifiers;
340 int done = 0;
342 /* ignore CapsLock */
343 modifiers = event->xkey.state & ValidModMask;
345 for (ptr = shortcutList; ptr!=NULL; ptr = ptr->next) {
346 if (ptr->keycode==0)
347 continue;
349 if (ptr->keycode==event->xkey.keycode && (ptr->modifier==modifiers)) {
350 (*ptr->entry->callback)(ptr->menu, ptr->entry);
351 done = True;
354 return done;
358 void
359 wRootMenuBindShortcuts(Window window)
361 Shortcut *ptr;
363 ptr = shortcutList;
364 while (ptr) {
365 if (ptr->modifier!=AnyModifier) {
366 XGrabKey(dpy, ptr->keycode, ptr->modifier|LockMask,
367 window, True, GrabModeAsync, GrabModeAsync);
368 #ifdef NUMLOCK_HACK
369 wHackedGrabKey(ptr->keycode, ptr->modifier,
370 window, True, GrabModeAsync, GrabModeAsync);
371 #endif
373 XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True,
374 GrabModeAsync, GrabModeAsync);
375 ptr = ptr->next;
380 static void
381 rebindKeygrabs(WScreen *scr)
383 WWindow *wwin;
385 wwin = scr->focused_window;
387 while (wwin!=NULL) {
388 XUngrabKey(dpy, AnyKey, AnyModifier, wwin->frame->core->window);
390 if (!wwin->window_flags.no_bind_keys) {
391 wWindowSetKeyGrabs(wwin);
393 wwin = wwin->prev;
398 static void
399 removeShortcutsForMenu(WMenu *menu)
401 Shortcut *ptr, *tmp;
402 Shortcut *newList = NULL;
404 ptr = shortcutList;
405 while (ptr!=NULL) {
406 tmp = ptr->next;
407 if (ptr->menu == menu) {
408 free(ptr);
409 } else {
410 ptr->next = newList;
411 newList = ptr;
413 ptr = tmp;
415 shortcutList = newList;
416 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
420 static Bool
421 addShortcut(char *file, char *shortcutDefinition, WMenu *menu,
422 WMenuEntry *entry)
424 Shortcut *ptr;
425 KeySym ksym;
426 char *k;
427 char buf[128], *b;
429 ptr = wmalloc(sizeof(Shortcut));
431 strcpy(buf, shortcutDefinition);
432 b = (char*)buf;
434 /* get modifiers */
435 ptr->modifier = 0;
436 while ((k = strchr(b, '+'))!=NULL) {
437 int mod;
439 *k = 0;
440 mod = wXModifierFromKey(b);
441 if (mod<0) {
442 wwarning(_("%s:invalid key modifier \"%s\""), file,
443 shortcutDefinition);
444 free(ptr);
445 return False;
447 ptr->modifier |= mod;
449 b = k+1;
452 /* get key */
453 ksym = XStringToKeysym(b);
455 if (ksym==NoSymbol) {
456 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
457 file, shortcutDefinition, entry->text);
458 free(ptr);
459 return False;
462 ptr->keycode = XKeysymToKeycode(dpy, ksym);
463 if (ptr->keycode==0) {
464 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
465 shortcutDefinition, entry->text);
466 free(ptr);
467 return False;
470 ptr->menu = menu;
471 ptr->entry = entry;
473 ptr->next = shortcutList;
474 shortcutList = ptr;
476 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
478 return True;
482 /*******************************/
484 static char*
485 cropline(char *line)
487 char *end;
489 if (strlen(line)==0)
490 return line;
492 end = &(line[strlen(line)])-1;
493 while (isspace(*line) && *line!=0) line++;
494 while (end>line && isspace(*end)) {
495 *end=0;
496 end--;
498 return line;
502 static char*
503 next_token(char *line, char **next)
505 char *tmp, c;
506 char *ret;
508 *next = NULL;
509 while (*line==' ' || *line=='\t') line++;
511 tmp = line;
513 if (*tmp=='"') {
514 tmp++; line++;
515 while (*tmp!=0 && *tmp!='"') tmp++;
516 if (*tmp!='"') {
517 wwarning(_("%s: unmatched '\"' in menu file"), line);
518 return NULL;
520 } else {
521 do {
522 if (*tmp=='\\')
523 tmp++;
525 if (*tmp!=0)
526 tmp++;
528 } while (*tmp!=0 && *tmp!=' ' && *tmp!='\t');
531 c = *tmp;
532 *tmp = 0;
533 ret = wstrdup(line);
534 *tmp = c;
536 if (c==0)
537 return ret;
538 else
539 tmp++;
541 /* skip blanks */
542 while (*tmp==' ' || *tmp=='\t') tmp++;
544 if (*tmp!=0)
545 *next = tmp;
547 return ret;
551 static void
552 separateCommand(char *line, char ***file, char **command)
554 char *token, *tmp = line;
555 LinkedList *list = NULL;
556 int count, i;
558 *file = NULL;
559 *command = NULL;
560 do {
561 token = next_token(tmp, &tmp);
562 if (token) {
563 if (strcmp(token, "WITH")==0) {
564 if (tmp!=NULL && *tmp!=0)
565 *command = wstrdup(tmp);
566 else
567 wwarning(_("%s: missing command"), line);
568 break;
570 list = list_cons(token, list);
572 } while (token!=NULL && tmp!=NULL);
574 count = list_length(list);
575 if (count>0) {
576 *file = wmalloc(sizeof(char*)*(count+1));
577 i = count;
578 (*file)[count] = NULL;
579 while (list!=NULL) {
580 (*file)[--i] = list->head;
581 list_remove_head(&list);
587 static void
588 constructMenu(WMenu *menu, WMenuEntry *entry)
590 WMenu *submenu;
591 struct stat stat_buf;
592 char **path;
593 char *cmd;
594 char *lpath = NULL;
595 int i, first=-1;
596 time_t last=0;
598 separateCommand((char*)entry->clientdata, &path, &cmd);
599 if (!path || *path==NULL || **path==0) {
600 wwarning(_("invalid OPEN_MENU specification: %s"),
601 (char*)entry->clientdata);
602 return;
605 if (path[0][0]=='|') {
606 /* pipe menu */
608 if (!menu->cascades[entry->cascade] ||
609 menu->cascades[entry->cascade]->timestamp == 0) {
610 /* parse pipe */
612 submenu = readMenuPipe(menu->frame->screen_ptr, path);
614 /* there's no automatic reloading */
615 if(submenu != NULL)
616 submenu->timestamp = 1;
617 } else {
618 submenu = NULL;
621 } else {
622 i=0;
623 while(path[i] != NULL) {
624 char *tmp;
626 tmp = wexpandpath(path[i]);
627 free(path[i]);
628 path[i] = tmp;
630 if (Locale) {
631 lpath = wmalloc(strlen(path[i])+32);
633 strcpy(lpath, path[i]);
634 strcat(lpath, ".");
635 strcat(lpath, Locale);
636 if (stat(lpath, &stat_buf)<0) {
637 int i;
638 i = strlen(Locale);
639 if (i>2) {
640 lpath[strlen(lpath)-(i-2)]=0;
641 if (stat(lpath, &stat_buf)==0) {
642 free(path[i]);
643 path[i] = lpath;
644 lpath = NULL;
647 } else {
648 free(path[i]);
649 path[i] = lpath;
650 lpath = NULL;
654 if (lpath) {
655 free(lpath);
656 lpath = NULL;
659 if (stat(path[i], &stat_buf)==0) {
660 if (last < stat_buf.st_mtime)
661 last = stat_buf.st_mtime;
662 if (first<0)
663 first=i;
664 } else {
665 wsyserror(_("%s:could not stat menu"), path[i]);
666 /*goto finish;*/
669 i++;
672 if (first < 0) {
673 wsyserror(_("%s:could not stat menu :%s"), "OPEN_MENU",
674 (char*)entry->clientdata);
675 goto finish;
677 stat(path[first], &stat_buf);
678 if (!menu->cascades[entry->cascade]
679 || menu->cascades[entry->cascade]->timestamp < last) {
681 if (S_ISDIR(stat_buf.st_mode)) {
682 /* menu directory */
683 submenu = readMenuDirectory(menu->frame->screen_ptr,
684 entry->text, &path[first], cmd);
685 if (submenu)
686 submenu->timestamp = last;
687 } else if (S_ISREG(stat_buf.st_mode)) {
688 /* menu file */
690 if (cmd || path[1])
691 wwarning(_("too many parameters in OPEN_MENU: %s"),
692 (char*)entry->clientdata);
694 submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
695 if (submenu)
696 submenu->timestamp = stat_buf.st_mtime;
697 } else {
698 submenu = NULL;
700 } else {
701 submenu = NULL;
705 if (submenu) {
706 wMenuEntryRemoveCascade(menu, entry);
707 wMenuEntrySetCascade(menu, entry, submenu);
710 finish:
711 i = 0;
712 while (path[i]!=NULL)
713 free(path[i++]);
714 free(path);
715 if (cmd)
716 free(cmd);
720 static WMenuEntry*
721 addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
723 WMenu *wsmenu;
724 WMenuEntry *entry;
726 if (scr->flags.added_workspace_menu) {
727 wwarning(_("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
728 return NULL;
729 } else {
730 scr->flags.added_workspace_menu = 1;
732 wsmenu = wWorkspaceMenuMake(scr, True);
733 scr->workspace_menu = wsmenu;
734 entry=wMenuAddCallback(menu, title, NULL, NULL);
735 wMenuEntrySetCascade(menu, entry, wsmenu);
737 wWorkspaceMenuUpdate(scr, wsmenu);
739 return entry;
743 static WMenuEntry*
744 addMenuEntry(WMenu *menu, char *title, char *shortcut, char *command,
745 char *params, char *file_name)
747 WScreen *scr;
748 WMenuEntry *entry = NULL;
749 Bool shortcutOk = False;
751 if (!menu)
752 return NULL;
753 scr = menu->frame->screen_ptr;
754 if (strcmp(command, "OPEN_MENU")==0) {
755 if (!params) {
756 wwarning(_("%s:missing parameter for menu command \"%s\""),
757 file_name, command);
758 } else {
759 WMenu *dummy;
760 char *path;
762 path = wfindfile(DEF_CONFIG_PATHS, params);
763 if (!path) {
764 path = wstrdup(params);
766 dummy = wMenuCreate(scr, title, False);
767 dummy->on_destroy = removeShortcutsForMenu;
768 entry = wMenuAddCallback(menu, title, constructMenu, path);
769 entry->free_cdata = free;
770 wMenuEntrySetCascade(menu, entry, dummy);
772 } else if (strcmp(command, "EXEC")==0) {
773 if (!params)
774 wwarning(_("%s:missing parameter for menu command \"%s\""),
775 file_name, command);
776 else {
777 entry = wMenuAddCallback(menu, title, execCommand, wstrdup(params));
778 entry->free_cdata = free;
779 shortcutOk = True;
781 } else if (strcmp(command, "EXIT")==0) {
783 if (params && strcmp(params, "QUICK")==0)
784 entry = wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
785 else
786 entry = wMenuAddCallback(menu, title, exitCommand, NULL);
788 shortcutOk = True;
789 } else if (strcmp(command, "SHUTDOWN")==0) {
791 if (params && strcmp(params, "QUICK")==0)
792 entry = wMenuAddCallback(menu, title, shutdownCommand,
793 (void*)M_QUICK);
794 else
795 entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
797 shortcutOk = True;
798 } else if (strcmp(command, "REFRESH")==0) {
799 entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
801 shortcutOk = True;
802 } else if (strcmp(command, "WORKSPACE_MENU")==0) {
803 entry = addWorkspaceMenu(scr, menu, title);
805 shortcutOk = True;
806 } else if (strcmp(command, "ARRANGE_ICONS")==0) {
807 entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
809 shortcutOk = True;
810 } else if (strcmp(command, "HIDE_OTHERS")==0) {
811 entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
813 shortcutOk = True;
814 } else if (strcmp(command, "SHOW_ALL")==0) {
815 entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
817 shortcutOk = True;
818 } else if (strcmp(command, "RESTART")==0) {
819 entry = wMenuAddCallback(menu, title, restartCommand,
820 params ? wstrdup(params) : NULL);
821 entry->free_cdata = free;
822 shortcutOk = True;
823 } else if (strcmp(command, "SAVE_SESSION")==0) {
824 entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
826 shortcutOk = True;
827 } else if (strcmp(command, "CLEAR_SESSION")==0) {
828 entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
829 shortcutOk = True;
830 } else if (strcmp(command, "INFO_PANEL")==0) {
831 entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
832 shortcutOk = True;
833 } else if (strcmp(command, "LEGAL_PANEL")==0) {
834 entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
835 shortcutOk = True;
836 } else {
837 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name,
838 command);
840 return NULL;
843 if (shortcut && entry) {
844 if (!shortcutOk) {
845 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name,
846 title);
847 } else {
848 if (addShortcut(file_name, shortcut, menu, entry)) {
850 entry->rtext = GetShortcutString(shortcut);
852 entry->rtext = wstrdup(shortcut);
858 return entry;
863 /******************* Menu Configuration From File *******************/
865 static void
866 separateline(char *line, char *title, char *command, char *parameter,
867 char *shortcut)
869 int l, i;
871 l = strlen(line);
873 *title = 0;
874 *command = 0;
875 *parameter = 0;
876 *shortcut = 0;
877 /* get the title */
878 while (isspace(*line) && (*line!=0)) line++;
879 if (*line=='"') {
880 line++;
881 i=0;
882 while (line[i]!='"' && (line[i]!=0)) i++;
883 if (line[i]!='"')
884 return;
885 } else {
886 i=0;
887 while (!isspace(line[i]) && (line[i]!=0)) i++;
889 strncpy(title, line, i);
890 title[i++]=0;
891 line+=i;
893 /* get the command or shortcut keyword */
894 while (isspace(*line) && (*line!=0)) line++;
895 if (*line==0)
896 return;
897 i=0;
898 while (!isspace(line[i]) && (line[i]!=0)) i++;
899 strncpy(command, line, i);
900 command[i++]=0;
901 line+=i;
903 if (strcmp(command, "SHORTCUT")==0) {
904 /* get the shortcut key */
905 while (isspace(*line) && (*line!=0)) line++;
906 if (*line=='"') {
907 line++;
908 i=0;
909 while (line[i]!='"' && (line[i]!=0)) i++;
910 if (line[i]!='"')
911 return;
912 } else {
913 i=0;
914 while (!isspace(line[i]) && (line[i]!=0)) i++;
916 strncpy(shortcut, line, i);
917 shortcut[i++]=0;
918 line+=i;
920 *command=0;
922 /* get the command */
923 while (isspace(*line) && (*line!=0)) line++;
924 if (*line==0)
925 return;
926 i=0;
927 while (!isspace(line[i]) && (line[i]!=0)) i++;
928 strncpy(command, line, i);
929 command[i++]=0;
930 line+=i;
933 /* get the parameters */
934 while (isspace(*line) && (*line!=0)) line++;
935 if (*line==0)
936 return;
938 if (*line=='"') {
939 line++;
940 l = 0;
941 while (line[l]!=0 && line[l]!='"') {
942 parameter[l] = line[l];
943 l++;
945 parameter[l] = 0;
946 return;
949 l = strlen(line);
950 while (isspace(line[l]) && (l>0)) l--;
951 strncpy(parameter, line, l);
952 parameter[l]=0;
956 static WMenu*
957 parseCascade(WScreen *scr, WMenu *menu, FILE *file, char *file_name)
959 char linebuf[MAXLINE];
960 char elinebuf[MAXLINE];
961 char title[MAXLINE];
962 char command[MAXLINE];
963 char shortcut[MAXLINE];
964 char params[MAXLINE];
965 char *line;
967 while (!IsEof(file)) {
968 int lsize, ok;
970 ok = 0;
971 fgets(linebuf, MAXLINE, file);
972 line = cropline(linebuf);
973 lsize = strlen(line);
974 do {
975 if (line[lsize-1]=='\\') {
976 char *line2;
977 int lsize2;
978 fgets(elinebuf, MAXLINE, file);
979 line2=cropline(elinebuf);
980 lsize2=strlen(line2);
981 if (lsize2+lsize>MAXLINE) {
982 wwarning(_("%s:maximal line size exceeded in menu config: %s"),
983 file_name, line);
984 ok=2;
985 } else {
986 line[lsize-1]=0;
987 lsize+=lsize2-1;
988 strcat(line, line2);
990 } else {
991 ok=1;
993 } while (!ok && !IsEof(file));
994 if (ok==2)
995 continue;
997 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
998 continue;
1001 separateline(line, title, command, params, shortcut);
1003 if (!command[0]) {
1004 wwarning(_("%s:missing command in menu config: %s"), file_name,
1005 line);
1006 goto error;
1009 if (strcasecmp(command, "MENU")==0) {
1010 WMenu *cascade;
1012 /* start submenu */
1014 cascade = wMenuCreate(scr, title, False);
1015 cascade->on_destroy = removeShortcutsForMenu;
1016 if (parseCascade(scr, cascade, file, file_name)==NULL) {
1017 wMenuDestroy(cascade, True);
1018 } else {
1019 wMenuEntrySetCascade(menu,
1020 wMenuAddCallback(menu, title, NULL, NULL),
1021 cascade);
1023 } else if (strcasecmp(command, "END")==0) {
1024 /* end of menu */
1025 return menu;
1027 } else {
1028 /* normal items */
1029 addMenuEntry(menu, title, shortcut[0] ? shortcut : NULL, command,
1030 params[0] ? params : NULL, file_name);
1034 wwarning(_("%s:syntax error in menu file:END declaration missing"),
1035 file_name);
1036 return menu;
1038 error:
1039 return menu;
1043 static WMenu*
1044 readMenuFile(WScreen *scr, char *file_name)
1046 WMenu *menu=NULL;
1047 FILE *file = NULL;
1048 char linebuf[MAXLINE];
1049 char title[MAXLINE];
1050 char shortcut[MAXLINE];
1051 char command[MAXLINE];
1052 char params[MAXLINE];
1053 char *line;
1054 #ifdef USECPP
1055 char *args;
1056 int cpp = 0;
1057 #endif
1059 #ifdef USECPP
1060 if (!wPreferences.flags.nocpp) {
1061 args = MakeCPPArgs(file_name);
1062 if (!args) {
1063 wwarning(_("could not make arguments for menu file preprocessor"));
1064 } else {
1065 sprintf(command, "%s %s %s", CPP_PATH, args, file_name);
1066 free(args);
1067 file = popen(command, "r");
1068 if (!file) {
1069 wsyserror(_("%s:could not open/preprocess menu file"),
1070 file_name);
1071 } else {
1072 cpp = 1;
1076 #endif /* USECPP */
1078 if (!file) {
1079 file = fopen(file_name, "r");
1080 if (!file) {
1081 wsyserror(_("%s:could not open menu file"), file_name);
1082 return NULL;
1086 while (!IsEof(file)) {
1087 if (!fgets(linebuf, MAXLINE, file))
1088 break;
1089 line = cropline(linebuf);
1090 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1091 continue;
1093 separateline(line, title, command, params, shortcut);
1095 if (!command[0]) {
1096 wwarning(_("%s:missing command in menu config: %s"), file_name,
1097 line);
1098 break;
1100 if (strcasecmp(command, "MENU")==0) {
1101 menu = wMenuCreate(scr, title, True);
1102 menu->on_destroy = removeShortcutsForMenu;
1103 if (!parseCascade(scr, menu, file, file_name)) {
1104 wMenuDestroy(menu, True);
1106 break;
1107 } else {
1108 wwarning(_("%s:invalid menu file. MENU command is missing"),
1109 file_name);
1110 break;
1114 #ifdef CPP
1115 if (cpp) {
1116 if (pclose(file)==-1) {
1117 wsyserror(_("error reading preprocessed menu data"));
1119 } else {
1120 fclose(file);
1122 #else
1123 fclose(file);
1124 #endif
1126 return menu;
1129 /************ Menu Configuration From Pipe *************/
1131 static WMenu*
1132 readMenuPipe(WScreen *scr, char **file_name)
1134 WMenu *menu=NULL;
1135 FILE *file = NULL;
1136 char linebuf[MAXLINE];
1137 char title[MAXLINE];
1138 char command[MAXLINE];
1139 char params[MAXLINE];
1140 char shortcut[MAXLINE];
1141 char *line;
1142 char * filename;
1143 char flat_file[MAXLINE];
1144 int i;
1145 #ifdef USECPP
1146 char *args;
1147 int cpp = 0;
1148 #endif
1150 flat_file[0] = '\0';
1152 for(i = 0 ; file_name[i] != NULL ; i++) {
1153 strcat(flat_file, file_name[i]);
1154 strcat(flat_file, " ");
1156 filename = flat_file+1;
1158 #ifdef USECPP
1159 if (!wPreferences.flags.nocpp) {
1160 args = MakeCPPArgs(filename);
1161 if (!args) {
1162 wwarning(_("could not make arguments for menu file preprocessor"));
1163 } else {
1164 sprintf(command, "%s | %s %s", filename, CPP_PATH, args);
1166 free(args);
1167 file = popen(command, "r");
1168 if (!file) {
1169 wsyserror(_("%s:could not open/preprocess menu file"), filename);
1170 } else {
1171 cpp = 1;
1176 #endif /* USECPP */
1178 if (!file) {
1179 file = popen(filename, "r");
1181 if (!file) {
1182 wsyserror(_("%s:could not open menu file"), filename);
1183 return NULL;
1187 while (!IsEof(file)) {
1188 if (!fgets(linebuf, MAXLINE, file))
1189 break;
1190 line = cropline(linebuf);
1191 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1192 continue;
1194 separateline(line, title, command, params, shortcut);
1196 if (!command[0]) {
1197 wwarning(_("%s:missing command in menu config: %s"), file_name,
1198 line);
1199 break;
1201 if (strcasecmp(command, "MENU")==0) {
1202 menu = wMenuCreate(scr, title, True);
1203 menu->on_destroy = removeShortcutsForMenu;
1204 if (!parseCascade(scr, menu, file, filename)) {
1205 wMenuDestroy(menu, True);
1207 break;
1208 } else {
1209 wwarning(_("%s:no title given for the root menu"), filename);
1210 break;
1214 pclose(file);
1216 return menu;
1221 typedef struct {
1222 char *name;
1223 int index;
1224 } dir_data;
1227 static int
1228 myCompare(dir_data *d1, dir_data *d2)
1230 return strcmp(d1->name, d2->name);
1234 /************ Menu Configuration From Directory *************/
1235 static WMenu*
1236 readMenuDirectory(WScreen *scr, char *title, char **path, char *command)
1238 DIR *dir;
1239 struct dirent *dentry;
1240 struct stat stat_buf;
1241 WMenu *menu=NULL;
1242 char *buffer;
1243 LinkedList *dirs = NULL, *files = NULL;
1244 int length, i, have_space=0;
1245 dir_data *data;
1247 i=0;
1248 while (path[i]!=NULL) {
1249 dir = opendir(path[i]);
1250 if (!dir) {
1251 i++;
1252 continue;
1255 while ((dentry = readdir(dir))) {
1257 if (strcmp(dentry->d_name, ".")==0 ||
1258 strcmp(dentry->d_name, "..")==0)
1259 continue;
1261 buffer = wmalloc(strlen(path[i])+strlen(dentry->d_name)+4);
1262 if (!buffer) {
1263 wsyserror(_("out of memory while constructing directory menu %s"),
1264 path[i]);
1265 break;
1268 strcpy(buffer, path[i]);
1269 strcat(buffer, "/");
1270 strcat(buffer, dentry->d_name);
1272 if (stat(buffer, &stat_buf)!=0) {
1273 wsyserror(_("%s:could not stat file \"%s\" in menu directory"),
1274 path[i], dentry->d_name);
1275 } else {
1276 data = (dir_data*) wmalloc(sizeof(dir_data));
1277 data->name = wstrdup(dentry->d_name);
1278 data->index = i;
1279 if (S_ISDIR(stat_buf.st_mode)) {
1280 /* access always returns success for user root */
1281 if (access(buffer, X_OK)==0) {
1282 /* Directory is accesible. Add to directory list */
1283 list_insert_sorted(data, &dirs, (int(*)())myCompare);
1284 data = NULL;
1286 } else if (S_ISREG(stat_buf.st_mode)) {
1287 /* Hack because access always returns X_OK success for user root */
1288 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1289 if ((command!=NULL && access(buffer, R_OK)==0) ||
1290 (command==NULL && access(buffer, X_OK)==0 &&
1291 (stat_buf.st_mode & S_IXANY))) {
1292 list_insert_sorted(data, &files, (int(*)())myCompare);
1293 data = NULL;
1296 if (data!=NULL) {
1297 if (data->name)
1298 free(data->name);
1299 free(data);
1300 data = NULL;
1303 free(buffer);
1306 closedir(dir);
1307 i++;
1310 if (!dirs && !files)
1311 return NULL;
1313 menu = wMenuCreate(scr, title, False);
1314 menu->on_destroy = removeShortcutsForMenu;
1316 while (dirs != NULL) {
1317 /* New directory. Use same OPEN_MENU command that was used
1318 * for the current directory. */
1319 dir_data *d = (dir_data*)dirs->head;
1321 length = strlen(path[d->index])+strlen(d->name)+6;
1322 if (command)
1323 length += strlen(command) + 6;
1324 buffer = wmalloc(length);
1325 if (!buffer) {
1326 wsyserror(_("out of memory while constructing directory menu %s"),
1327 path[d->index]);
1328 break;
1331 have_space = strchr(path[d->index], ' ')!=NULL ||
1332 strchr(d->name, ' ')!=NULL;
1333 if (have_space) {
1334 buffer[0] = '"';
1335 buffer[1] = 0;
1336 strcat(buffer, path[d->index]);
1337 } else {
1338 strcpy(buffer, path[d->index]);
1340 strcat(buffer, "/");
1341 strcat(buffer, d->name);
1342 if (have_space)
1343 strcat(buffer, "\"");
1344 if (command) {
1345 strcat(buffer, " WITH ");
1346 strcat(buffer, command);
1349 addMenuEntry(menu, d->name, NULL, "OPEN_MENU", buffer, path[d->index]);
1351 free(buffer);
1352 if (dirs->head) {
1353 if (d->name)
1354 free(d->name);
1355 free(dirs->head);
1357 list_remove_head(&dirs);
1360 while (files != NULL) {
1361 /* executable: add as entry */
1362 dir_data *f = (dir_data*) files->head;;
1364 length = strlen(path[f->index])+strlen(f->name)+6;
1365 if (command)
1366 length += strlen(command);
1368 buffer = wmalloc(length);
1369 if (!buffer) {
1370 wsyserror(_("out of memory while constructing directory menu %s"),
1371 path[f->index]);
1372 break;
1375 have_space = strchr(path[f->index], ' ')!=NULL ||
1376 strchr(f->name, ' ')!=NULL;
1377 if (command!=NULL) {
1378 strcpy(buffer, command);
1379 strcat(buffer, " ");
1380 if (have_space)
1381 strcat(buffer, "\"");
1382 strcat(buffer, path[f->index]);
1383 } else {
1384 if (have_space) {
1385 buffer[0] = '"';
1386 buffer[1] = 0;
1387 strcat(buffer, path[f->index]);
1388 } else {
1389 strcpy(buffer, path[f->index]);
1392 strcat(buffer, "/");
1393 strcat(buffer, f->name);
1394 if (have_space)
1395 strcat(buffer, "\"");
1397 addMenuEntry(menu, f->name, NULL, "EXEC", buffer, path[f->index]);
1399 free(buffer);
1400 if (files->head) {
1401 if (f->name)
1402 free(f->name);
1403 free(files->head);
1405 list_remove_head(&files);
1408 return menu;
1412 /************ Menu Configuration From WMRootMenu *************/
1414 static WMenu*
1415 makeDefaultMenu(WScreen *scr)
1417 WMenu *menu=NULL;
1419 menu = wMenuCreate(scr, _("Commands"), True);
1420 wMenuAddCallback(menu, "XTerm", execCommand, "xterm");
1421 wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
1422 return menu;
1430 *----------------------------------------------------------------------
1431 * configureMenu--
1432 * Reads root menu configuration from defaults database.
1434 *----------------------------------------------------------------------
1436 static WMenu*
1437 configureMenu(WScreen *scr, proplist_t definition)
1439 WMenu *menu = NULL;
1440 proplist_t elem;
1441 int i, count;
1442 proplist_t title, command, params;
1443 char *tmp, *mtitle;
1446 if (PLIsString(definition)) {
1447 struct stat stat_buf;
1448 char *path = NULL;
1450 /* menu definition is a string. Probably a path, so parse the file */
1452 tmp = wexpandpath(PLGetString(definition));
1454 if (Locale) {
1455 path = wmalloc(strlen(tmp)+32);
1457 strcpy(path, tmp);
1458 strcat(path, ".");
1459 strcat(path, Locale);
1461 /* look for menu.xy */
1462 if (stat(path, &stat_buf)<0) {
1463 int i;
1464 i = strlen(Locale);
1465 if (i>2) {
1466 path[strlen(path)-(i-2)]=0;
1467 /* look for menu.xy_zw */
1468 if (stat(path, &stat_buf)<0) {
1469 free(path);
1470 /* If did not find any localized menus, try
1471 * only menu. This can also mean that
1472 * the path in WMRootMenu was already the
1473 * path for the localized menu (eg: menu = "menu.ab")
1475 path = NULL;
1477 } else {
1478 free(path);
1479 path = NULL;
1484 if (!path)
1485 path = wfindfile(DEF_CONFIG_PATHS, tmp);
1487 if (!path) {
1488 wsyserror(_("could not find menu file \"%s\" referenced in WMRootMenu"),
1489 tmp);
1490 free(tmp);
1491 return NULL;
1494 if (stat(path, &stat_buf)<0) {
1495 wsyserror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
1496 free(path);
1497 free(tmp);
1498 return NULL;
1501 if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
1502 /* if the pointer in WMRootMenu has changed */
1503 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1504 menu = readMenuFile(scr, path);
1505 if (menu)
1506 menu->timestamp = MAX(stat_buf.st_mtime, WDRootMenu->timestamp);
1507 } else {
1508 menu = NULL;
1510 free(path);
1511 free(tmp);
1513 return menu;
1516 count = PLGetNumberOfElements(definition);
1517 if (count==0)
1518 return NULL;
1520 elem = PLGetArrayElement(definition, 0);
1521 if (!PLIsString(elem)) {
1522 tmp = PLGetDescription(elem);
1523 wwarning(_("%s:format error in root menu configuration \"%s\""),
1524 "WMRootMenu", tmp);
1525 free(tmp);
1526 return NULL;
1528 mtitle = PLGetString(elem);
1530 menu = wMenuCreate(scr, mtitle, False);
1531 menu->on_destroy = removeShortcutsForMenu;
1533 for (i=1; i<count; i++) {
1534 elem = PLGetArrayElement(definition, i);
1536 if (!PLIsArray(elem) || PLGetNumberOfElements(elem) < 2)
1537 goto error;
1539 if (PLIsArray(PLGetArrayElement(elem,1))) {
1540 WMenu *submenu;
1541 WMenuEntry *mentry;
1543 /* submenu */
1544 submenu = configureMenu(scr, elem);
1545 if (submenu) {
1546 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL,
1547 NULL);
1548 wMenuEntrySetCascade(menu, mentry, submenu);
1550 } else {
1551 int idx = 0;
1552 char *shortcut;
1553 /* normal entry */
1555 title = PLGetArrayElement(elem, idx++);
1556 shortcut = PLGetArrayElement(elem, idx++);
1557 if (strcmp(PLGetString(shortcut), "SHORTCUT")==0) {
1558 shortcut = PLGetArrayElement(elem, idx++);
1559 command = PLGetArrayElement(elem, idx++);
1560 } else {
1561 command = shortcut;
1562 shortcut = NULL;
1564 params = PLGetArrayElement(elem, idx++);
1566 if (!title || !command)
1567 goto error;
1569 addMenuEntry(menu, PLGetString(title),
1570 shortcut ? PLGetString(shortcut) : NULL,
1571 PLGetString(command),
1572 params ? PLGetString(params) : NULL, "WMRootMenu");
1574 continue;
1576 error:
1577 tmp = PLGetDescription(elem);
1578 wwarning(_("%s:format error in root menu configuration \"%s\""),
1579 "WMRootMenu", tmp);
1580 free(tmp);
1583 return menu;
1594 *----------------------------------------------------------------------
1595 * OpenRootMenu--
1596 * Opens the root menu, parsing the menu configuration from the
1597 * defaults database.
1598 * If the menu is already mapped and is not sticked to the
1599 * root window, it will be unmapped.
1601 * Side effects:
1602 * The menu may be remade.
1604 * Notes:
1605 * Construction of OPEN_MENU entries are delayed to the moment the
1606 * user map's them.
1607 *----------------------------------------------------------------------
1609 void
1610 OpenRootMenu(WScreen *scr, int x, int y, int keyboard)
1612 WMenu *menu=NULL;
1613 proplist_t definition;
1615 static proplist_t domain=NULL;
1617 if (!domain) {
1618 domain = PLMakeString("WMRootMenu");
1622 scr->flags.root_menu_changed_shortcuts = 0;
1623 scr->flags.added_workspace_menu = 0;
1625 if (scr->root_menu && scr->root_menu->flags.mapped) {
1626 menu = scr->root_menu;
1627 if (!menu->flags.buttoned) {
1628 wMenuUnmap(menu);
1629 } else {
1630 wRaiseFrame(menu->frame->core);
1632 if (keyboard)
1633 wMenuMapAt(menu, 0, 0, True);
1634 else
1635 wMenuMapCopyAt(menu, x-menu->frame->core->width/2,
1636 y-menu->frame->top_width/2);
1638 return;
1642 definition = WDRootMenu->dictionary;
1645 definition = PLGetDomain(domain);
1647 if (definition) {
1648 if (PLIsArray(definition)) {
1649 if (!scr->root_menu
1650 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1651 menu = configureMenu(scr, definition);
1652 if (menu)
1653 menu->timestamp = WDRootMenu->timestamp;
1654 } else
1655 menu = NULL;
1656 } else {
1657 menu = configureMenu(scr, definition);
1661 if (!menu) {
1662 /* menu hasn't changed or could not be read */
1663 if (!scr->root_menu) {
1664 menu = makeDefaultMenu(scr);
1665 scr->root_menu = menu;
1667 menu = scr->root_menu;
1668 } else {
1669 /* new root menu */
1670 if (scr->root_menu)
1671 wMenuDestroy(scr->root_menu, True);
1672 scr->root_menu = menu;
1674 if (menu) {
1675 wMenuMapAt(menu, x-menu->frame->core->width/2, y-menu->frame->top_width/2,
1676 keyboard);
1679 if (scr->flags.root_menu_changed_shortcuts)
1680 rebindKeygrabs(scr);