Update for 0.51.0
[wmaker-crm.git] / src / rootmenu.c
blob9aca896f1327f3a71f48e278e796920eb657edfd
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 #ifndef LITE
27 #include <assert.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <sys/wait.h>
33 #include <sys/types.h>
34 #include <string.h>
35 #include <ctype.h>
36 #include <time.h>
37 #include <dirent.h>
39 #include <X11/Xlib.h>
40 #include <X11/Xutil.h>
41 #include <X11/Xatom.h>
43 #include "WindowMaker.h"
44 #include "actions.h"
45 #include "menu.h"
46 #include "funcs.h"
47 #include "dialog.h"
48 #include "keybind.h"
49 #include "stacking.h"
50 #include "workspace.h"
51 #include "defaults.h"
52 #include "framewin.h"
53 #include "session.h"
54 #include "xmodifier.h"
55 #include <proplist.h>
57 #include "list.h"
60 extern char *Locale;
62 extern WDDomain *WDRootMenu;
64 extern Cursor wCursor[WCUR_LAST];
66 extern Time LastTimestamp;
68 extern WPreferences wPreferences;
70 extern int wScreenCount;
72 static WMenu *readMenuPipe(WScreen *scr, char **file_name);
73 static WMenu *readMenuFile(WScreen *scr, char *file_name);
74 static WMenu *readMenuDirectory(WScreen *scr, char *title, char **file_name,
75 char *command);
78 typedef struct Shortcut {
79 struct Shortcut *next;
81 int modifier;
82 KeyCode keycode;
83 WMenuEntry *entry;
84 WMenu *menu;
85 } Shortcut;
89 static Shortcut *shortcutList = NULL;
93 * Syntax:
94 * # main menu
95 * "Menu Name" MENU
96 * "Title" EXEC command_to_exec -params
97 * "Submenu" MENU
98 * "Title" EXEC command_to_exec -params
99 * "Submenu" END
100 * "Workspaces" WORKSPACE_MENU
101 * "Title" built_in_command
102 * "Quit" EXIT
103 * "Quick Quit" EXIT QUICK
104 * "Menu Name" END
106 * Commands may be preceded by SHORTCUT key
108 * Built-in commands:
110 * INFO_PANEL - shows the Info Panel
111 * LEGAL_PANEL - shows the Legal info panel
112 * SHUTDOWN [QUICK] - closes the X server [without confirmation]
113 * REFRESH - forces the desktop to be repainted
114 * EXIT [QUICK] - exit the window manager [without confirmation]
115 * EXEC <program> - execute an external program
116 * WORKSPACE_MENU - places the workspace submenu
117 * ARRANGE_ICONS
118 * RESTART [<window manager>] - restarts the window manager
119 * SHOW_ALL - unhide all windows on workspace
120 * HIDE_OTHERS - hides all windows excep the focused one
121 * OPEN_MENU file - read menu data from file which must be a valid menu file.
122 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
123 * - read menu data from directory(ies) and
124 * eventually precede each with a command.
125 * OPEN_MENU | command
126 * - opens command and uses its stdout to construct and insert
127 * the resulting menu in current position. The output of
128 * command must be a valid menu description.
129 * The space between '|' and command is optional.
130 * SAVE_SESSION - saves the current state of the desktop, which include
131 * all running applications, all their hints (geometry,
132 * position on screen, workspace they live on, the dock
133 * or clip from where they were launched, and
134 * if minimized, shaded or hidden. Also saves the current
135 * workspace the user is on. All will be restored on every
136 * start of windowmaker until another SAVE_SESSION or
137 * CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
138 * WindowMaker domain file, then saving is automatically
139 * done on every windowmaker exit, overwriting any
140 * SAVE_SESSION or CLEAR_SESSION (see below). Also save
141 * dock state now.
142 * CLEAR_SESSION - clears any previous saved session. This will not have
143 * any effect if SaveSessionOnExit is True.
147 #define MAX(a,b) ((a)>(b) ? (a) : (b))
150 #define M_QUICK 1
152 /* menu commands */
154 static void
155 execCommand(WMenu *menu, WMenuEntry *entry)
157 char *cmdline;
159 cmdline = ExpandOptions(menu->frame->screen_ptr, (char*)entry->clientdata);
161 XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
162 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT],
163 CurrentTime);
164 XSync(dpy, 0);
166 if (cmdline) {
167 ExecuteShellCommand(menu->frame->screen_ptr, cmdline);
168 free(cmdline);
170 XUngrabPointer(dpy, CurrentTime);
171 XSync(dpy, 0);
175 static void
176 exitCommand(WMenu *menu, WMenuEntry *entry)
178 static int inside = 0;
180 /* prevent reentrant calls */
181 if (inside)
182 return;
183 inside = 1;
185 if ((long)entry->clientdata==M_QUICK
186 || wMessageDialog(menu->frame->screen_ptr, _("Exit"),
187 _("Exit window manager?"),
188 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
189 #ifdef DEBUG
190 printf("Exiting WindowMaker.\n");
191 #endif
192 Shutdown(WSExitMode);
194 inside = 0;
198 static void
199 shutdownCommand(WMenu *menu, WMenuEntry *entry)
201 static int inside = 0;
202 int result;
204 /* prevent reentrant calls */
205 if (inside)
206 return;
207 inside = 1;
209 #define R_CANCEL 0
210 #define R_CLOSE 1
211 #define R_KILL 2
214 result = R_CANCEL;
215 if ((long)entry->clientdata==M_QUICK)
216 result = R_CLOSE;
217 else {
218 #ifdef XSMP_ENABLED
219 if (wSessionIsManaged()) {
220 int r;
222 r = wMessageDialog(menu->frame->screen_ptr,
223 _("Close X session"),
224 _("Close Window System session?\n"
225 "Kill might close applications with unsaved data."),
226 _("Close"), _("Kill"), _("Cancel"));
227 if (r==WAPRDefault)
228 result = R_CLOSE;
229 else if (r==WAPRAlternate)
230 result = R_KILL;
231 } else
232 #endif
234 int r;
236 r = wMessageDialog(menu->frame->screen_ptr,
237 _("Kill X session"),
238 _("Kill Window System session?\n"
239 "(all applications will be closed)"),
240 _("Kill"), _("Cancel"), NULL);
241 if (r==WAPRDefault)
242 result = R_KILL;
246 if (result!=R_CANCEL) {
247 #ifdef XSMP_ENABLED
248 if (result == R_CLOSE) {
249 Shutdown(WSLogoutMode);
250 } else
251 #endif /* XSMP_ENABLED */
253 Shutdown(WSKillMode);
256 #undef R_CLOSE
257 #undef R_CANCEL
258 #undef R_KILL
259 inside = 0;
263 static void
264 restartCommand(WMenu *menu, WMenuEntry *entry)
266 Shutdown(WSRestartPreparationMode);
267 Restart((char*)entry->clientdata);
271 static void
272 refreshCommand(WMenu *menu, WMenuEntry *entry)
274 wRefreshDesktop(menu->frame->screen_ptr);
278 static void
279 arrangeIconsCommand(WMenu *menu, WMenuEntry *entry)
281 wArrangeIcons(menu->frame->screen_ptr, True);
284 static void
285 showAllCommand(WMenu *menu, WMenuEntry *entry)
287 wShowAllWindows(menu->frame->screen_ptr);
290 static void
291 hideOthersCommand(WMenu *menu, WMenuEntry *entry)
293 wHideOtherApplications(menu->frame->screen_ptr->focused_window);
297 static void
298 saveSessionCommand(WMenu *menu, WMenuEntry *entry)
300 if (!wPreferences.save_session_on_exit)
301 wSessionSaveState(menu->frame->screen_ptr);
303 wScreenSaveState(menu->frame->screen_ptr);
307 static void
308 clearSessionCommand(WMenu *menu, WMenuEntry *entry)
310 wSessionClearState(menu->frame->screen_ptr);
314 static void
315 infoPanelCommand(WMenu *menu, WMenuEntry *entry)
317 wShowInfoPanel(menu->frame->screen_ptr);
321 static void
322 legalPanelCommand(WMenu *menu, WMenuEntry *entry)
324 wShowLegalPanel(menu->frame->screen_ptr);
329 /********************************************************************/
331 static void
332 raiseMenus(WMenu *menu)
334 int i;
336 if (menu->flags.mapped) {
337 wRaiseFrame(menu->frame->core);
339 for (i=0; i<menu->cascade_no; i++) {
340 if (menu->cascades[i])
341 raiseMenus(menu->cascades[i]);
347 Bool
348 wRootMenuPerformShortcut(XEvent *event)
350 Shortcut *ptr;
351 int modifiers;
352 int done = 0;
354 /* ignore CapsLock */
355 modifiers = event->xkey.state & ValidModMask;
357 for (ptr = shortcutList; ptr!=NULL; ptr = ptr->next) {
358 if (ptr->keycode==0)
359 continue;
361 if (ptr->keycode==event->xkey.keycode && (ptr->modifier==modifiers)) {
362 (*ptr->entry->callback)(ptr->menu, ptr->entry);
363 done = True;
366 return done;
370 void
371 wRootMenuBindShortcuts(Window window)
373 Shortcut *ptr;
375 ptr = shortcutList;
376 while (ptr) {
377 if (ptr->modifier!=AnyModifier) {
378 XGrabKey(dpy, ptr->keycode, ptr->modifier|LockMask,
379 window, True, GrabModeAsync, GrabModeAsync);
380 #ifdef NUMLOCK_HACK
381 wHackedGrabKey(ptr->keycode, ptr->modifier,
382 window, True, GrabModeAsync, GrabModeAsync);
383 #endif
385 XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True,
386 GrabModeAsync, GrabModeAsync);
387 ptr = ptr->next;
392 static void
393 rebindKeygrabs(WScreen *scr)
395 WWindow *wwin;
397 wwin = scr->focused_window;
399 while (wwin!=NULL) {
400 XUngrabKey(dpy, AnyKey, AnyModifier, wwin->frame->core->window);
402 if (!WFLAGP(wwin, no_bind_keys)) {
403 wWindowSetKeyGrabs(wwin);
405 wwin = wwin->prev;
410 static void
411 removeShortcutsForMenu(WMenu *menu)
413 Shortcut *ptr, *tmp;
414 Shortcut *newList = NULL;
416 ptr = shortcutList;
417 while (ptr!=NULL) {
418 tmp = ptr->next;
419 if (ptr->menu == menu) {
420 free(ptr);
421 } else {
422 ptr->next = newList;
423 newList = ptr;
425 ptr = tmp;
427 shortcutList = newList;
428 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
432 static Bool
433 addShortcut(char *file, char *shortcutDefinition, WMenu *menu,
434 WMenuEntry *entry)
436 Shortcut *ptr;
437 KeySym ksym;
438 char *k;
439 char buf[128], *b;
441 ptr = wmalloc(sizeof(Shortcut));
443 strcpy(buf, shortcutDefinition);
444 b = (char*)buf;
446 /* get modifiers */
447 ptr->modifier = 0;
448 while ((k = strchr(b, '+'))!=NULL) {
449 int mod;
451 *k = 0;
452 mod = wXModifierFromKey(b);
453 if (mod<0) {
454 wwarning(_("%s:invalid key modifier \"%s\""), file, b);
455 free(ptr);
456 return False;
458 ptr->modifier |= mod;
460 b = k+1;
463 /* get key */
464 ksym = XStringToKeysym(b);
466 if (ksym==NoSymbol) {
467 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
468 file, shortcutDefinition, entry->text);
469 free(ptr);
470 return False;
473 ptr->keycode = XKeysymToKeycode(dpy, ksym);
474 if (ptr->keycode==0) {
475 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
476 shortcutDefinition, entry->text);
477 free(ptr);
478 return False;
481 ptr->menu = menu;
482 ptr->entry = entry;
484 ptr->next = shortcutList;
485 shortcutList = ptr;
487 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
489 return True;
493 /*******************************/
495 static char*
496 cropline(char *line)
498 char *end;
500 if (strlen(line)==0)
501 return line;
503 end = &(line[strlen(line)])-1;
504 while (isspace(*line) && *line!=0) line++;
505 while (end>line && isspace(*end)) {
506 *end=0;
507 end--;
509 return line;
513 static char*
514 next_token(char *line, char **next)
516 char *tmp, c;
517 char *ret;
519 *next = NULL;
520 while (*line==' ' || *line=='\t') line++;
522 tmp = line;
524 if (*tmp=='"') {
525 tmp++; line++;
526 while (*tmp!=0 && *tmp!='"') tmp++;
527 if (*tmp!='"') {
528 wwarning(_("%s: unmatched '\"' in menu file"), line);
529 return NULL;
531 } else {
532 do {
533 if (*tmp=='\\')
534 tmp++;
536 if (*tmp!=0)
537 tmp++;
539 } while (*tmp!=0 && *tmp!=' ' && *tmp!='\t');
542 c = *tmp;
543 *tmp = 0;
544 ret = wstrdup(line);
545 *tmp = c;
547 if (c==0)
548 return ret;
549 else
550 tmp++;
552 /* skip blanks */
553 while (*tmp==' ' || *tmp=='\t') tmp++;
555 if (*tmp!=0)
556 *next = tmp;
558 return ret;
562 static void
563 separateCommand(char *line, char ***file, char **command)
565 char *token, *tmp = line;
566 LinkedList *list = NULL;
567 int count, i;
569 *file = NULL;
570 *command = NULL;
571 do {
572 token = next_token(tmp, &tmp);
573 if (token) {
574 if (strcmp(token, "WITH")==0) {
575 if (tmp!=NULL && *tmp!=0)
576 *command = wstrdup(tmp);
577 else
578 wwarning(_("%s: missing command"), line);
579 break;
581 list = list_cons(token, list);
583 } while (token!=NULL && tmp!=NULL);
585 count = list_length(list);
586 if (count>0) {
587 *file = wmalloc(sizeof(char*)*(count+1));
588 i = count;
589 (*file)[count] = NULL;
590 while (list!=NULL) {
591 (*file)[--i] = list->head;
592 list_remove_head(&list);
598 static void
599 constructMenu(WMenu *menu, WMenuEntry *entry)
601 WMenu *submenu;
602 struct stat stat_buf;
603 char **path;
604 char *cmd;
605 char *lpath = NULL;
606 int i, first=-1;
607 time_t last=0;
609 separateCommand((char*)entry->clientdata, &path, &cmd);
610 if (!path || *path==NULL || **path==0) {
611 wwarning(_("invalid OPEN_MENU specification: %s"),
612 (char*)entry->clientdata);
613 return;
616 if (path[0][0]=='|') {
617 /* pipe menu */
619 if (!menu->cascades[entry->cascade] ||
620 menu->cascades[entry->cascade]->timestamp == 0) {
621 /* parse pipe */
623 submenu = readMenuPipe(menu->frame->screen_ptr, path);
625 /* there's no automatic reloading */
626 if(submenu != NULL)
627 submenu->timestamp = 1;
628 } else {
629 submenu = NULL;
632 } else {
633 i=0;
634 while(path[i] != NULL) {
635 char *tmp;
636 Bool statted = False;
638 if (strcmp(path[i], "-noext")==0) {
639 i++;
640 continue;
643 tmp = wexpandpath(path[i]);
644 free(path[i]);
645 path[i] = tmp;
647 if (Locale) {
648 lpath = wmalloc(strlen(path[i])+32);
650 strcpy(lpath, path[i]);
651 strcat(lpath, ".");
652 strcat(lpath, Locale);
653 if (stat(lpath, &stat_buf)<0) {
654 int i;
655 i = strlen(Locale);
656 if (i>2) {
657 lpath[strlen(lpath)-(i-2)]=0;
658 if (stat(lpath, &stat_buf)==0) {
659 statted = True;
660 free(path[i]);
661 path[i] = lpath;
662 lpath = NULL;
665 } else {
666 statted = True;
667 free(path[i]);
668 path[i] = lpath;
669 lpath = NULL;
673 if (lpath) {
674 free(lpath);
675 lpath = NULL;
678 if (statted || stat(path[i], &stat_buf)==0) {
679 if (last < stat_buf.st_mtime)
680 last = stat_buf.st_mtime;
681 if (first<0)
682 first=i;
683 } else {
684 wsyserror(_("%s:could not stat menu"), path[i]);
685 /*goto finish;*/
688 i++;
691 if (first < 0) {
692 wsyserror(_("%s:could not stat menu:%s"), "OPEN_MENU",
693 (char*)entry->clientdata);
694 goto finish;
696 stat(path[first], &stat_buf);
697 if (!menu->cascades[entry->cascade]
698 || menu->cascades[entry->cascade]->timestamp < last) {
700 if (S_ISDIR(stat_buf.st_mode)) {
701 /* menu directory */
702 submenu = readMenuDirectory(menu->frame->screen_ptr,
703 entry->text, path, cmd);
704 if (submenu)
705 submenu->timestamp = last;
706 } else if (S_ISREG(stat_buf.st_mode)) {
707 /* menu file */
709 if (cmd || path[1])
710 wwarning(_("too many parameters in OPEN_MENU: %s"),
711 (char*)entry->clientdata);
713 submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
714 if (submenu)
715 submenu->timestamp = stat_buf.st_mtime;
716 } else {
717 submenu = NULL;
719 } else {
720 submenu = NULL;
724 if (submenu) {
725 wMenuEntryRemoveCascade(menu, entry);
726 wMenuEntrySetCascade(menu, entry, submenu);
729 finish:
730 i = 0;
731 while (path[i]!=NULL)
732 free(path[i++]);
733 free(path);
734 if (cmd)
735 free(cmd);
739 static WMenuEntry*
740 addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
742 WMenu *wsmenu;
743 WMenuEntry *entry;
745 if (scr->flags.added_workspace_menu) {
746 wwarning(_("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
747 return NULL;
748 } else {
749 scr->flags.added_workspace_menu = 1;
751 wsmenu = wWorkspaceMenuMake(scr, True);
752 scr->workspace_menu = wsmenu;
753 entry = wMenuAddCallback(menu, title, NULL, NULL);
754 wMenuEntrySetCascade(menu, entry, wsmenu);
756 wWorkspaceMenuUpdate(scr, wsmenu);
758 return entry;
762 static WMenuEntry*
763 addMenuEntry(WMenu *menu, char *title, char *shortcut, char *command,
764 char *params, char *file_name)
766 WScreen *scr;
767 WMenuEntry *entry = NULL;
768 Bool shortcutOk = False;
770 if (!menu)
771 return NULL;
772 scr = menu->frame->screen_ptr;
773 if (strcmp(command, "OPEN_MENU")==0) {
774 if (!params) {
775 wwarning(_("%s:missing parameter for menu command \"%s\""),
776 file_name, command);
777 } else {
778 WMenu *dummy;
779 char *path;
781 path = wfindfile(DEF_CONFIG_PATHS, params);
782 if (!path) {
783 path = wstrdup(params);
785 dummy = wMenuCreate(scr, title, False);
786 dummy->on_destroy = removeShortcutsForMenu;
787 entry = wMenuAddCallback(menu, title, constructMenu, path);
788 entry->free_cdata = free;
789 wMenuEntrySetCascade(menu, entry, dummy);
791 } else if (strcmp(command, "EXEC")==0) {
792 if (!params)
793 wwarning(_("%s:missing parameter for menu command \"%s\""),
794 file_name, command);
795 else {
796 entry = wMenuAddCallback(menu, title, execCommand, wstrdup(params));
797 entry->free_cdata = free;
798 shortcutOk = True;
800 } else if (strcmp(command, "EXIT")==0) {
802 if (params && strcmp(params, "QUICK")==0)
803 entry = wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
804 else
805 entry = wMenuAddCallback(menu, title, exitCommand, NULL);
807 shortcutOk = True;
808 } else if (strcmp(command, "SHUTDOWN")==0) {
810 if (params && strcmp(params, "QUICK")==0)
811 entry = wMenuAddCallback(menu, title, shutdownCommand,
812 (void*)M_QUICK);
813 else
814 entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
816 shortcutOk = True;
817 } else if (strcmp(command, "REFRESH")==0) {
818 entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
820 shortcutOk = True;
821 } else if (strcmp(command, "WORKSPACE_MENU")==0) {
822 entry = addWorkspaceMenu(scr, menu, title);
824 shortcutOk = True;
825 } else if (strcmp(command, "ARRANGE_ICONS")==0) {
826 entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
828 shortcutOk = True;
829 } else if (strcmp(command, "HIDE_OTHERS")==0) {
830 entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
832 shortcutOk = True;
833 } else if (strcmp(command, "SHOW_ALL")==0) {
834 entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
836 shortcutOk = True;
837 } else if (strcmp(command, "RESTART")==0) {
838 entry = wMenuAddCallback(menu, title, restartCommand,
839 params ? wstrdup(params) : NULL);
840 entry->free_cdata = free;
841 shortcutOk = True;
842 } else if (strcmp(command, "SAVE_SESSION")==0) {
843 entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
845 shortcutOk = True;
846 } else if (strcmp(command, "CLEAR_SESSION")==0) {
847 entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
848 shortcutOk = True;
849 } else if (strcmp(command, "INFO_PANEL")==0) {
850 entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
851 shortcutOk = True;
852 } else if (strcmp(command, "LEGAL_PANEL")==0) {
853 entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
854 shortcutOk = True;
855 } else {
856 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name,
857 command);
859 return NULL;
862 if (shortcut && entry) {
863 if (!shortcutOk) {
864 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name,
865 title);
866 } else {
867 if (addShortcut(file_name, shortcut, menu, entry)) {
869 entry->rtext = GetShortcutString(shortcut);
871 entry->rtext = wstrdup(shortcut);
877 return entry;
882 /******************* Menu Configuration From File *******************/
884 static void
885 separateline(char *line, char *title, char *command, char *parameter,
886 char *shortcut)
888 int l, i;
890 l = strlen(line);
892 *title = 0;
893 *command = 0;
894 *parameter = 0;
895 *shortcut = 0;
896 /* get the title */
897 while (isspace(*line) && (*line!=0)) line++;
898 if (*line=='"') {
899 line++;
900 i=0;
901 while (line[i]!='"' && (line[i]!=0)) i++;
902 if (line[i]!='"')
903 return;
904 } else {
905 i=0;
906 while (!isspace(line[i]) && (line[i]!=0)) i++;
908 strncpy(title, line, i);
909 title[i++]=0;
910 line+=i;
912 /* get the command or shortcut keyword */
913 while (isspace(*line) && (*line!=0)) line++;
914 if (*line==0)
915 return;
916 i=0;
917 while (!isspace(line[i]) && (line[i]!=0)) i++;
918 strncpy(command, line, i);
919 command[i++]=0;
920 line+=i;
922 if (strcmp(command, "SHORTCUT")==0) {
923 /* get the shortcut key */
924 while (isspace(*line) && (*line!=0)) line++;
925 if (*line=='"') {
926 line++;
927 i=0;
928 while (line[i]!='"' && (line[i]!=0)) i++;
929 if (line[i]!='"')
930 return;
931 } else {
932 i=0;
933 while (!isspace(line[i]) && (line[i]!=0)) i++;
935 strncpy(shortcut, line, i);
936 shortcut[i++]=0;
937 line+=i;
939 *command=0;
941 /* get the command */
942 while (isspace(*line) && (*line!=0)) line++;
943 if (*line==0)
944 return;
945 i=0;
946 while (!isspace(line[i]) && (line[i]!=0)) i++;
947 strncpy(command, line, i);
948 command[i++]=0;
949 line+=i;
952 /* get the parameters */
953 while (isspace(*line) && (*line!=0)) line++;
954 if (*line==0)
955 return;
957 if (*line=='"') {
958 line++;
959 l = 0;
960 while (line[l]!=0 && line[l]!='"') {
961 parameter[l] = line[l];
962 l++;
964 parameter[l] = 0;
965 return;
968 l = strlen(line);
969 while (isspace(line[l]) && (l>0)) l--;
970 strncpy(parameter, line, l);
971 parameter[l]=0;
975 static WMenu*
976 parseCascade(WScreen *scr, WMenu *menu, FILE *file, char *file_name)
978 char linebuf[MAXLINE];
979 char elinebuf[MAXLINE];
980 char title[MAXLINE];
981 char command[MAXLINE];
982 char shortcut[MAXLINE];
983 char params[MAXLINE];
984 char *line;
986 while (!IsEof(file)) {
987 int lsize, ok;
989 ok = 0;
990 fgets(linebuf, MAXLINE, file);
991 line = cropline(linebuf);
992 lsize = strlen(line);
993 do {
994 if (line[lsize-1]=='\\') {
995 char *line2;
996 int lsize2;
997 fgets(elinebuf, MAXLINE, file);
998 line2=cropline(elinebuf);
999 lsize2=strlen(line2);
1000 if (lsize2+lsize>MAXLINE) {
1001 wwarning(_("%s:maximal line size exceeded in menu config: %s"),
1002 file_name, line);
1003 ok=2;
1004 } else {
1005 line[lsize-1]=0;
1006 lsize+=lsize2-1;
1007 strcat(line, line2);
1009 } else {
1010 ok=1;
1012 } while (!ok && !IsEof(file));
1013 if (ok==2)
1014 continue;
1016 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1017 continue;
1020 separateline(line, title, command, params, shortcut);
1022 if (!command[0]) {
1023 wwarning(_("%s:missing command in menu config: %s"), file_name,
1024 line);
1025 goto error;
1028 if (strcasecmp(command, "MENU")==0) {
1029 WMenu *cascade;
1031 /* start submenu */
1033 cascade = wMenuCreate(scr, title, False);
1034 cascade->on_destroy = removeShortcutsForMenu;
1035 if (parseCascade(scr, cascade, file, file_name)==NULL) {
1036 wMenuDestroy(cascade, True);
1037 } else {
1038 wMenuEntrySetCascade(menu,
1039 wMenuAddCallback(menu, title, NULL, NULL),
1040 cascade);
1042 } else if (strcasecmp(command, "END")==0) {
1043 /* end of menu */
1044 return menu;
1046 } else {
1047 /* normal items */
1048 addMenuEntry(menu, title, shortcut[0] ? shortcut : NULL, command,
1049 params[0] ? params : NULL, file_name);
1053 wwarning(_("%s:syntax error in menu file:END declaration missing"),
1054 file_name);
1055 return menu;
1057 error:
1058 return menu;
1062 static WMenu*
1063 readMenuFile(WScreen *scr, char *file_name)
1065 WMenu *menu=NULL;
1066 FILE *file = NULL;
1067 char linebuf[MAXLINE];
1068 char title[MAXLINE];
1069 char shortcut[MAXLINE];
1070 char command[MAXLINE];
1071 char params[MAXLINE];
1072 char *line;
1073 #ifdef USECPP
1074 char *args;
1075 int cpp = 0;
1076 #endif
1078 #ifdef USECPP
1079 if (!wPreferences.flags.nocpp) {
1080 args = MakeCPPArgs(file_name);
1081 if (!args) {
1082 wwarning(_("could not make arguments for menu file preprocessor"));
1083 } else {
1084 sprintf(command, "%s %s %s", CPP_PATH, args, file_name);
1085 free(args);
1086 file = popen(command, "r");
1087 if (!file) {
1088 wsyserror(_("%s:could not open/preprocess menu file"),
1089 file_name);
1090 } else {
1091 cpp = 1;
1095 #endif /* USECPP */
1097 if (!file) {
1098 file = fopen(file_name, "r");
1099 if (!file) {
1100 wsyserror(_("%s:could not open menu file"), file_name);
1101 return NULL;
1105 while (!IsEof(file)) {
1106 if (!fgets(linebuf, MAXLINE, file))
1107 break;
1108 line = cropline(linebuf);
1109 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1110 continue;
1112 separateline(line, title, command, params, shortcut);
1114 if (!command[0]) {
1115 wwarning(_("%s:missing command in menu config: %s"), file_name,
1116 line);
1117 break;
1119 if (strcasecmp(command, "MENU")==0) {
1120 menu = wMenuCreate(scr, title, True);
1121 menu->on_destroy = removeShortcutsForMenu;
1122 if (!parseCascade(scr, menu, file, file_name)) {
1123 wMenuDestroy(menu, True);
1125 break;
1126 } else {
1127 wwarning(_("%s:invalid menu file. MENU command is missing"),
1128 file_name);
1129 break;
1133 #ifdef CPP
1134 if (cpp) {
1135 if (pclose(file)==-1) {
1136 wsyserror(_("error reading preprocessed menu data"));
1138 } else {
1139 fclose(file);
1141 #else
1142 fclose(file);
1143 #endif
1145 return menu;
1148 /************ Menu Configuration From Pipe *************/
1150 static WMenu*
1151 readMenuPipe(WScreen *scr, char **file_name)
1153 WMenu *menu=NULL;
1154 FILE *file = NULL;
1155 char linebuf[MAXLINE];
1156 char title[MAXLINE];
1157 char command[MAXLINE];
1158 char params[MAXLINE];
1159 char shortcut[MAXLINE];
1160 char *line;
1161 char * filename;
1162 char flat_file[MAXLINE];
1163 int i;
1164 #ifdef USECPP
1165 char *args;
1166 int cpp = 0;
1167 #endif
1169 flat_file[0] = '\0';
1171 for(i = 0 ; file_name[i] != NULL ; i++) {
1172 strcat(flat_file, file_name[i]);
1173 strcat(flat_file, " ");
1175 filename = flat_file+1;
1177 #ifdef USECPP
1178 if (!wPreferences.flags.nocpp) {
1179 args = MakeCPPArgs(filename);
1180 if (!args) {
1181 wwarning(_("could not make arguments for menu file preprocessor"));
1182 } else {
1183 sprintf(command, "%s | %s %s", filename, CPP_PATH, args);
1185 free(args);
1186 file = popen(command, "r");
1187 if (!file) {
1188 wsyserror(_("%s:could not open/preprocess menu file"), filename);
1189 } else {
1190 cpp = 1;
1195 #endif /* USECPP */
1197 if (!file) {
1198 file = popen(filename, "r");
1200 if (!file) {
1201 wsyserror(_("%s:could not open menu file"), filename);
1202 return NULL;
1206 while (!IsEof(file)) {
1207 if (!fgets(linebuf, MAXLINE, file))
1208 break;
1209 line = cropline(linebuf);
1210 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1211 continue;
1213 separateline(line, title, command, params, shortcut);
1215 if (!command[0]) {
1216 wwarning(_("%s:missing command in menu config: %s"), file_name,
1217 line);
1218 break;
1220 if (strcasecmp(command, "MENU")==0) {
1221 menu = wMenuCreate(scr, title, True);
1222 menu->on_destroy = removeShortcutsForMenu;
1223 if (!parseCascade(scr, menu, file, filename)) {
1224 wMenuDestroy(menu, True);
1226 break;
1227 } else {
1228 wwarning(_("%s:no title given for the root menu"), filename);
1229 break;
1233 pclose(file);
1235 return menu;
1240 typedef struct {
1241 char *name;
1242 int index;
1243 } dir_data;
1246 static int
1247 myCompare(dir_data *d1, dir_data *d2)
1249 return strcmp(d1->name, d2->name);
1253 /************ Menu Configuration From Directory *************/
1255 static Bool
1256 isFilePackage(char *file)
1258 int l;
1260 /* check if the extension indicates this file is a
1261 * file package. For now, only recognize .themed */
1263 l = strlen(file);
1265 if (l > 7 && strcmp(&(file[l-7]), ".themed")==0) {
1266 return True;
1267 } else {
1268 return False;
1273 static WMenu*
1274 readMenuDirectory(WScreen *scr, char *title, char **path, char *command)
1276 DIR *dir;
1277 struct dirent *dentry;
1278 struct stat stat_buf;
1279 WMenu *menu=NULL;
1280 char *buffer;
1281 LinkedList *dirs = NULL, *files = NULL;
1282 int length, i, have_space=0;
1283 dir_data *data;
1284 int stripExtension = 0;
1286 i=0;
1287 while (path[i]!=NULL) {
1288 if (strcmp(path[i], "-noext")==0) {
1289 stripExtension = 1;
1290 i++;
1291 continue;
1294 dir = opendir(path[i]);
1295 if (!dir) {
1296 i++;
1297 continue;
1300 while ((dentry = readdir(dir))) {
1302 if (strcmp(dentry->d_name, ".")==0 ||
1303 strcmp(dentry->d_name, "..")==0)
1304 continue;
1306 if (dentry->d_name[0] == '.')
1307 continue;
1309 buffer = wmalloc(strlen(path[i])+strlen(dentry->d_name)+4);
1310 if (!buffer) {
1311 wsyserror(_("out of memory while constructing directory menu %s"),
1312 path[i]);
1313 break;
1316 strcpy(buffer, path[i]);
1317 strcat(buffer, "/");
1318 strcat(buffer, dentry->d_name);
1320 if (stat(buffer, &stat_buf)!=0) {
1321 wsyserror(_("%s:could not stat file \"%s\" in menu directory"),
1322 path[i], dentry->d_name);
1323 } else {
1324 Bool isFilePack = False;
1326 data = NULL;
1327 if (S_ISDIR(stat_buf.st_mode)
1328 && !(isFilePack = isFilePackage(dentry->d_name))) {
1330 /* access always returns success for user root */
1331 if (access(buffer, X_OK)==0) {
1332 /* Directory is accesible. Add to directory list */
1334 data = (dir_data*) wmalloc(sizeof(dir_data));
1335 data->name = wstrdup(dentry->d_name);
1336 data->index = i;
1338 list_insert_sorted(data, &dirs, (int(*)())myCompare);
1340 } else if (S_ISREG(stat_buf.st_mode) || isFilePack) {
1341 /* Hack because access always returns X_OK success for user root */
1342 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1343 if ((command!=NULL && access(buffer, R_OK)==0) ||
1344 (command==NULL && access(buffer, X_OK)==0 &&
1345 (stat_buf.st_mode & S_IXANY))) {
1347 data = (dir_data*) wmalloc(sizeof(dir_data));
1348 data->name = wstrdup(dentry->d_name);
1349 data->index = i;
1351 list_insert_sorted(data, &files, (int(*)())myCompare);
1355 free(buffer);
1358 closedir(dir);
1359 i++;
1362 if (!dirs && !files)
1363 return NULL;
1365 menu = wMenuCreate(scr, title, False);
1366 menu->on_destroy = removeShortcutsForMenu;
1368 while (dirs != NULL) {
1369 /* New directory. Use same OPEN_MENU command that was used
1370 * for the current directory. */
1371 dir_data *d = (dir_data*)dirs->head;
1373 length = strlen(path[d->index])+strlen(d->name)+6;
1374 if (command)
1375 length += strlen(command) + 6;
1376 buffer = wmalloc(length);
1377 if (!buffer) {
1378 wsyserror(_("out of memory while constructing directory menu %s"),
1379 path[d->index]);
1380 break;
1383 have_space = strchr(path[d->index], ' ')!=NULL ||
1384 strchr(d->name, ' ')!=NULL;
1385 if (have_space) {
1386 buffer[0] = '"';
1387 buffer[1] = 0;
1388 strcat(buffer, path[d->index]);
1389 } else {
1390 strcpy(buffer, path[d->index]);
1392 strcat(buffer, "/");
1393 strcat(buffer, d->name);
1394 if (have_space)
1395 strcat(buffer, "\"");
1396 if (command) {
1397 strcat(buffer, " WITH ");
1398 strcat(buffer, command);
1401 addMenuEntry(menu, d->name, NULL, "OPEN_MENU", buffer, path[d->index]);
1403 free(buffer);
1404 if (dirs->head) {
1405 if (d->name)
1406 free(d->name);
1407 free(dirs->head);
1409 list_remove_head(&dirs);
1412 while (files != NULL) {
1413 /* executable: add as entry */
1414 dir_data *f = (dir_data*) files->head;;
1416 length = strlen(path[f->index])+strlen(f->name)+6;
1417 if (command)
1418 length += strlen(command);
1420 buffer = wmalloc(length);
1421 if (!buffer) {
1422 wsyserror(_("out of memory while constructing directory menu %s"),
1423 path[f->index]);
1424 break;
1427 have_space = strchr(path[f->index], ' ')!=NULL ||
1428 strchr(f->name, ' ')!=NULL;
1429 if (command!=NULL) {
1430 strcpy(buffer, command);
1431 strcat(buffer, " ");
1432 if (have_space)
1433 strcat(buffer, "\"");
1434 strcat(buffer, path[f->index]);
1435 } else {
1436 if (have_space) {
1437 buffer[0] = '"';
1438 buffer[1] = 0;
1439 strcat(buffer, path[f->index]);
1440 } else {
1441 strcpy(buffer, path[f->index]);
1444 strcat(buffer, "/");
1445 strcat(buffer, f->name);
1446 if (have_space)
1447 strcat(buffer, "\"");
1449 if (stripExtension) {
1450 char *ptr = strrchr(f->name, '.');
1451 if (ptr && ptr!=f->name)
1452 *ptr = 0;
1454 addMenuEntry(menu, f->name, NULL, "EXEC", buffer, path[f->index]);
1456 free(buffer);
1457 if (files->head) {
1458 if (f->name)
1459 free(f->name);
1460 free(files->head);
1462 list_remove_head(&files);
1465 return menu;
1469 /************ Menu Configuration From WMRootMenu *************/
1471 static WMenu*
1472 makeDefaultMenu(WScreen *scr)
1474 WMenu *menu=NULL;
1476 menu = wMenuCreate(scr, _("Commands"), True);
1477 wMenuAddCallback(menu, "XTerm", execCommand, "xterm");
1478 wMenuAddCallback(menu, "rxvt", execCommand, "rxvt");
1479 wMenuAddCallback(menu, _("Restart"), restartCommand, NULL);
1480 wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
1481 return menu;
1489 *----------------------------------------------------------------------
1490 * configureMenu--
1491 * Reads root menu configuration from defaults database.
1493 *----------------------------------------------------------------------
1495 static WMenu*
1496 configureMenu(WScreen *scr, proplist_t definition)
1498 WMenu *menu = NULL;
1499 proplist_t elem;
1500 int i, count;
1501 proplist_t title, command, params;
1502 char *tmp, *mtitle;
1505 if (PLIsString(definition)) {
1506 struct stat stat_buf;
1507 char *path = NULL;
1508 Bool menu_is_default = False;
1510 /* menu definition is a string. Probably a path, so parse the file */
1512 tmp = wexpandpath(PLGetString(definition));
1514 if (Locale) {
1515 path = wmalloc(strlen(tmp)+32);
1517 strcpy(path, tmp);
1518 strcat(path, ".");
1519 strcat(path, Locale);
1521 /* look for menu.xy */
1522 if (stat(path, &stat_buf)<0) {
1523 int i;
1524 i = strlen(Locale);
1525 if (i>2) {
1526 path[strlen(path)-(i-2)]=0;
1527 /* look for menu.xy_zw */
1528 if (stat(path, &stat_buf)<0) {
1529 free(path);
1530 /* If did not find any localized menus, try
1531 * only menu. This can also mean that
1532 * the path in WMRootMenu was already the
1533 * path for the localized menu (eg: menu = "menu.ab")
1535 path = NULL;
1537 } else {
1538 free(path);
1539 path = NULL;
1544 if (!path)
1545 path = wfindfile(DEF_CONFIG_PATHS, tmp);
1547 if (!path) {
1548 path = wfindfile(DEF_CONFIG_PATHS, DEF_MENU_FILE);
1549 menu_is_default = True;
1552 if (!path) {
1553 wsyserror(_("could not find menu file \"%s\" referenced in WMRootMenu"),
1554 tmp);
1555 free(tmp);
1556 return NULL;
1559 if (stat(path, &stat_buf)<0) {
1560 wsyserror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
1561 free(path);
1562 free(tmp);
1563 return NULL;
1566 if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
1567 /* if the pointer in WMRootMenu has changed */
1568 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1570 if (menu_is_default) {
1571 wwarning(_("using default menu file \"%s\" as the menu referenced in WMRootMenu could not be found "),
1572 path);
1575 menu = readMenuFile(scr, path);
1576 if (menu)
1577 menu->timestamp = MAX(stat_buf.st_mtime, WDRootMenu->timestamp);
1578 } else {
1579 menu = NULL;
1581 free(path);
1582 free(tmp);
1584 return menu;
1587 count = PLGetNumberOfElements(definition);
1588 if (count==0)
1589 return NULL;
1591 elem = PLGetArrayElement(definition, 0);
1592 if (!PLIsString(elem)) {
1593 tmp = PLGetDescription(elem);
1594 wwarning(_("%s:format error in root menu configuration \"%s\""),
1595 "WMRootMenu", tmp);
1596 free(tmp);
1597 return NULL;
1599 mtitle = PLGetString(elem);
1601 menu = wMenuCreate(scr, mtitle, False);
1602 menu->on_destroy = removeShortcutsForMenu;
1604 #ifdef GLOBAL_SUBMENU_FILE
1606 WMenu *submenu;
1607 WMenuEntry *mentry;
1609 submenu = readMenuFile(scr, GLOBAL_SUBMENU_FILE);
1611 if (submenu) {
1612 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL, NULL);
1613 wMenuEntrySetCascade(menu, mentry, submenu);
1616 #endif
1618 for (i=1; i<count; i++) {
1619 elem = PLGetArrayElement(definition, i);
1620 #if 0
1621 if (PLIsString(elem)) {
1622 char *file;
1624 file = PLGetString(elem);
1627 #endif
1628 if (!PLIsArray(elem) || PLGetNumberOfElements(elem) < 2)
1629 goto error;
1631 if (PLIsArray(PLGetArrayElement(elem,1))) {
1632 WMenu *submenu;
1633 WMenuEntry *mentry;
1635 /* submenu */
1636 submenu = configureMenu(scr, elem);
1637 if (submenu) {
1638 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL,
1639 NULL);
1640 wMenuEntrySetCascade(menu, mentry, submenu);
1642 } else {
1643 int idx = 0;
1644 char *shortcut;
1645 /* normal entry */
1647 title = PLGetArrayElement(elem, idx++);
1648 shortcut = PLGetArrayElement(elem, idx++);
1649 if (strcmp(PLGetString(shortcut), "SHORTCUT")==0) {
1650 shortcut = PLGetArrayElement(elem, idx++);
1651 command = PLGetArrayElement(elem, idx++);
1652 } else {
1653 command = shortcut;
1654 shortcut = NULL;
1656 params = PLGetArrayElement(elem, idx++);
1658 if (!title || !command)
1659 goto error;
1661 addMenuEntry(menu, PLGetString(title),
1662 shortcut ? PLGetString(shortcut) : NULL,
1663 PLGetString(command),
1664 params ? PLGetString(params) : NULL, "WMRootMenu");
1666 continue;
1668 error:
1669 tmp = PLGetDescription(elem);
1670 wwarning(_("%s:format error in root menu configuration \"%s\""),
1671 "WMRootMenu", tmp);
1672 free(tmp);
1675 return menu;
1686 *----------------------------------------------------------------------
1687 * OpenRootMenu--
1688 * Opens the root menu, parsing the menu configuration from the
1689 * defaults database.
1690 * If the menu is already mapped and is not sticked to the
1691 * root window, it will be unmapped.
1693 * Side effects:
1694 * The menu may be remade.
1696 * Notes:
1697 * Construction of OPEN_MENU entries are delayed to the moment the
1698 * user map's them.
1699 *----------------------------------------------------------------------
1701 void
1702 OpenRootMenu(WScreen *scr, int x, int y, int keyboard)
1704 WMenu *menu=NULL;
1705 proplist_t definition;
1707 static proplist_t domain=NULL;
1709 if (!domain) {
1710 domain = PLMakeString("WMRootMenu");
1714 scr->flags.root_menu_changed_shortcuts = 0;
1715 scr->flags.added_workspace_menu = 0;
1717 if (scr->root_menu && scr->root_menu->flags.mapped) {
1718 menu = scr->root_menu;
1719 if (!menu->flags.buttoned) {
1720 wMenuUnmap(menu);
1721 } else {
1722 wRaiseFrame(menu->frame->core);
1724 if (keyboard)
1725 wMenuMapAt(menu, 0, 0, True);
1726 else
1727 wMenuMapCopyAt(menu, x-menu->frame->core->width/2,
1728 y-menu->frame->top_width/2);
1730 return;
1734 definition = WDRootMenu->dictionary;
1737 definition = PLGetDomain(domain);
1739 if (definition) {
1740 if (PLIsArray(definition)) {
1741 if (!scr->root_menu
1742 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1743 menu = configureMenu(scr, definition);
1744 if (menu)
1745 menu->timestamp = WDRootMenu->timestamp;
1747 } else
1748 menu = NULL;
1749 } else {
1750 menu = configureMenu(scr, definition);
1754 if (!menu) {
1755 /* menu hasn't changed or could not be read */
1756 if (!scr->root_menu) {
1757 menu = makeDefaultMenu(scr);
1758 scr->root_menu = menu;
1760 menu = scr->root_menu;
1761 } else {
1762 /* new root menu */
1763 if (scr->root_menu)
1764 wMenuDestroy(scr->root_menu, True);
1765 scr->root_menu = menu;
1767 if (menu) {
1768 wMenuMapAt(menu, x-menu->frame->core->width/2, y-menu->frame->top_width/2,
1769 keyboard);
1772 if (scr->flags.root_menu_changed_shortcuts)
1773 rebindKeygrabs(scr);
1776 #endif /* !LITE */