Fixed crashing bug in WPrefs.app's appereance section.
[wmaker-crm.git] / src / rootmenu.c
blob4a4cb69f6df8d3bac2d6f68c9eca423db37b99e0
1 /* rootmenu.c- user defined menu
2 *
3 * Window Maker window manager
4 *
5 * Copyright (c) 1997, 1998 Alfredo K. Kojima
6 * Copyright (c) 1998 Dan Pascu
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
21 * USA.
24 #include "wconfig.h"
26 #ifndef LITE
28 #include <assert.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <sys/stat.h>
33 #include <sys/wait.h>
34 #include <sys/types.h>
35 #include <string.h>
36 #include <ctype.h>
37 #include <time.h>
38 #include <dirent.h>
40 #include <X11/Xlib.h>
41 #include <X11/Xutil.h>
42 #include <X11/Xatom.h>
44 #include "WindowMaker.h"
45 #include "actions.h"
46 #include "menu.h"
47 #include "funcs.h"
48 #include "dialog.h"
49 #include "keybind.h"
50 #include "stacking.h"
51 #include "workspace.h"
52 #include "defaults.h"
53 #include "framewin.h"
54 #include "session.h"
55 #include "xmodifier.h"
56 #include <proplist.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 * SHEXEC <command> - execute a shell command
117 * WORKSPACE_MENU - places the workspace submenu
118 * ARRANGE_ICONS
119 * RESTART [<window manager>] - restarts the window manager
120 * SHOW_ALL - unhide all windows on workspace
121 * HIDE_OTHERS - hides all windows excep the focused one
122 * OPEN_MENU file - read menu data from file which must be a valid menu file.
123 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
124 * - read menu data from directory(ies) and
125 * eventually precede each with a command.
126 * OPEN_MENU | command
127 * - opens command and uses its stdout to construct and insert
128 * the resulting menu in current position. The output of
129 * command must be a valid menu description.
130 * The space between '|' and command is optional.
131 * SAVE_SESSION - saves the current state of the desktop, which include
132 * all running applications, all their hints (geometry,
133 * position on screen, workspace they live on, the dock
134 * or clip from where they were launched, and
135 * if minimized, shaded or hidden. Also saves the current
136 * workspace the user is on. All will be restored on every
137 * start of windowmaker until another SAVE_SESSION or
138 * CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
139 * WindowMaker domain file, then saving is automatically
140 * done on every windowmaker exit, overwriting any
141 * SAVE_SESSION or CLEAR_SESSION (see below). Also save
142 * dock state now.
143 * CLEAR_SESSION - clears any previous saved session. This will not have
144 * any effect if SaveSessionOnExit is True.
148 #define MAX(a,b) ((a)>(b) ? (a) : (b))
151 #define M_QUICK 1
153 /* menu commands */
155 static void
156 execCommand(WMenu *menu, WMenuEntry *entry)
158 char *cmdline;
160 cmdline = ExpandOptions(menu->frame->screen_ptr, (char*)entry->clientdata);
162 XGrabPointer(dpy, menu->frame->screen_ptr->root_win, True, 0,
163 GrabModeAsync, GrabModeAsync, None, wCursor[WCUR_WAIT],
164 CurrentTime);
165 XSync(dpy, 0);
167 if (cmdline) {
168 ExecuteShellCommand(menu->frame->screen_ptr, cmdline);
169 free(cmdline);
171 XUngrabPointer(dpy, CurrentTime);
172 XSync(dpy, 0);
176 static void
177 exitCommand(WMenu *menu, WMenuEntry *entry)
179 static int inside = 0;
181 /* prevent reentrant calls */
182 if (inside)
183 return;
184 inside = 1;
186 if ((long)entry->clientdata==M_QUICK
187 || wMessageDialog(menu->frame->screen_ptr, _("Exit"),
188 _("Exit window manager?"),
189 _("Exit"), _("Cancel"), NULL)==WAPRDefault) {
190 #ifdef DEBUG
191 printf("Exiting WindowMaker.\n");
192 #endif
193 Shutdown(WSExitMode);
195 inside = 0;
199 static void
200 shutdownCommand(WMenu *menu, WMenuEntry *entry)
202 static int inside = 0;
203 int result;
205 /* prevent reentrant calls */
206 if (inside)
207 return;
208 inside = 1;
210 #define R_CANCEL 0
211 #define R_CLOSE 1
212 #define R_KILL 2
215 result = R_CANCEL;
216 if ((long)entry->clientdata==M_QUICK)
217 result = R_CLOSE;
218 else {
219 #ifdef XSMP_ENABLED
220 if (wSessionIsManaged()) {
221 int r;
223 r = wMessageDialog(menu->frame->screen_ptr,
224 _("Close X session"),
225 _("Close Window System session?\n"
226 "Kill might close applications with unsaved data."),
227 _("Close"), _("Kill"), _("Cancel"));
228 if (r==WAPRDefault)
229 result = R_CLOSE;
230 else if (r==WAPRAlternate)
231 result = R_KILL;
232 } else
233 #endif
235 int r;
237 r = wMessageDialog(menu->frame->screen_ptr,
238 _("Kill X session"),
239 _("Kill Window System session?\n"
240 "(all applications will be closed)"),
241 _("Kill"), _("Cancel"), NULL);
242 if (r==WAPRDefault)
243 result = R_KILL;
247 if (result!=R_CANCEL) {
248 #ifdef XSMP_ENABLED
249 if (result == R_CLOSE) {
250 Shutdown(WSLogoutMode);
251 } else
252 #endif /* XSMP_ENABLED */
254 Shutdown(WSKillMode);
257 #undef R_CLOSE
258 #undef R_CANCEL
259 #undef R_KILL
260 inside = 0;
264 static void
265 restartCommand(WMenu *menu, WMenuEntry *entry)
267 Shutdown(WSRestartPreparationMode);
268 Restart((char*)entry->clientdata, False);
269 Restart(NULL, True);
273 static void
274 refreshCommand(WMenu *menu, WMenuEntry *entry)
276 wRefreshDesktop(menu->frame->screen_ptr);
280 static void
281 arrangeIconsCommand(WMenu *menu, WMenuEntry *entry)
283 wArrangeIcons(menu->frame->screen_ptr, True);
286 static void
287 showAllCommand(WMenu *menu, WMenuEntry *entry)
289 wShowAllWindows(menu->frame->screen_ptr);
292 static void
293 hideOthersCommand(WMenu *menu, WMenuEntry *entry)
295 wHideOtherApplications(menu->frame->screen_ptr->focused_window);
299 static void
300 saveSessionCommand(WMenu *menu, WMenuEntry *entry)
302 if (!wPreferences.save_session_on_exit)
303 wSessionSaveState(menu->frame->screen_ptr);
305 wScreenSaveState(menu->frame->screen_ptr);
309 static void
310 clearSessionCommand(WMenu *menu, WMenuEntry *entry)
312 wSessionClearState(menu->frame->screen_ptr);
316 static void
317 infoPanelCommand(WMenu *menu, WMenuEntry *entry)
319 wShowInfoPanel(menu->frame->screen_ptr);
323 static void
324 legalPanelCommand(WMenu *menu, WMenuEntry *entry)
326 wShowLegalPanel(menu->frame->screen_ptr);
330 /********************************************************************/
333 static char*
334 getLocalizedMenuFile(char *menu)
336 char *buffer;
337 char *ptr;
339 if (!Locale)
340 return NULL;
342 buffer = wmalloc(strlen(menu)+32);
344 /* try menu.locale_name */
345 sprintf(buffer, "%s.%s", menu, Locale);
346 if (access(buffer, F_OK)==0) {
347 return buffer;
349 /* check if it is in the form aa_bb.encoding and check for aa_bb */
350 ptr = strchr(Locale, '.');
351 if (ptr) {
352 *ptr = 0;
353 if (access(buffer, F_OK)==0) {
354 return buffer;
357 /* now check for aa */
358 ptr = strchr(buffer, '_');
359 if (ptr) {
360 *ptr = 0;
361 if (access(buffer, F_OK)==0) {
362 return buffer;
366 return NULL;
370 static void
371 raiseMenus(WMenu *menu)
373 int i;
375 if (menu->flags.mapped) {
376 wRaiseFrame(menu->frame->core);
378 for (i=0; i<menu->cascade_no; i++) {
379 if (menu->cascades[i])
380 raiseMenus(menu->cascades[i]);
386 Bool
387 wRootMenuPerformShortcut(XEvent *event)
389 Shortcut *ptr;
390 int modifiers;
391 int done = 0;
393 /* ignore CapsLock */
394 modifiers = event->xkey.state & ValidModMask;
396 for (ptr = shortcutList; ptr!=NULL; ptr = ptr->next) {
397 if (ptr->keycode==0)
398 continue;
400 if (ptr->keycode==event->xkey.keycode && (ptr->modifier==modifiers)) {
401 (*ptr->entry->callback)(ptr->menu, ptr->entry);
402 done = True;
405 return done;
409 void
410 wRootMenuBindShortcuts(Window window)
412 Shortcut *ptr;
414 ptr = shortcutList;
415 while (ptr) {
416 if (ptr->modifier!=AnyModifier) {
417 XGrabKey(dpy, ptr->keycode, ptr->modifier|LockMask,
418 window, True, GrabModeAsync, GrabModeAsync);
419 #ifdef NUMLOCK_HACK
420 wHackedGrabKey(ptr->keycode, ptr->modifier,
421 window, True, GrabModeAsync, GrabModeAsync);
422 #endif
424 XGrabKey(dpy, ptr->keycode, ptr->modifier, window, True,
425 GrabModeAsync, GrabModeAsync);
426 ptr = ptr->next;
431 static void
432 rebindKeygrabs(WScreen *scr)
434 WWindow *wwin;
436 wwin = scr->focused_window;
438 while (wwin!=NULL) {
439 XUngrabKey(dpy, AnyKey, AnyModifier, wwin->frame->core->window);
441 if (!WFLAGP(wwin, no_bind_keys)) {
442 wWindowSetKeyGrabs(wwin);
444 wwin = wwin->prev;
449 static void
450 removeShortcutsForMenu(WMenu *menu)
452 Shortcut *ptr, *tmp;
453 Shortcut *newList = NULL;
455 ptr = shortcutList;
456 while (ptr!=NULL) {
457 tmp = ptr->next;
458 if (ptr->menu == menu) {
459 free(ptr);
460 } else {
461 ptr->next = newList;
462 newList = ptr;
464 ptr = tmp;
466 shortcutList = newList;
467 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
471 static Bool
472 addShortcut(char *file, char *shortcutDefinition, WMenu *menu,
473 WMenuEntry *entry)
475 Shortcut *ptr;
476 KeySym ksym;
477 char *k;
478 char buf[128], *b;
480 ptr = wmalloc(sizeof(Shortcut));
482 strcpy(buf, shortcutDefinition);
483 b = (char*)buf;
485 /* get modifiers */
486 ptr->modifier = 0;
487 while ((k = strchr(b, '+'))!=NULL) {
488 int mod;
490 *k = 0;
491 mod = wXModifierFromKey(b);
492 if (mod<0) {
493 wwarning(_("%s:invalid key modifier \"%s\""), file, b);
494 free(ptr);
495 return False;
497 ptr->modifier |= mod;
499 b = k+1;
502 /* get key */
503 ksym = XStringToKeysym(b);
505 if (ksym==NoSymbol) {
506 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
507 file, shortcutDefinition, entry->text);
508 free(ptr);
509 return False;
512 ptr->keycode = XKeysymToKeycode(dpy, ksym);
513 if (ptr->keycode==0) {
514 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file,
515 shortcutDefinition, entry->text);
516 free(ptr);
517 return False;
520 ptr->menu = menu;
521 ptr->entry = entry;
523 ptr->next = shortcutList;
524 shortcutList = ptr;
526 menu->menu->screen_ptr->flags.root_menu_changed_shortcuts = 1;
528 return True;
532 /*******************************/
534 static char*
535 cropline(char *line)
537 char *end;
539 if (strlen(line)==0)
540 return line;
542 end = &(line[strlen(line)])-1;
543 while (isspace(*line) && *line!=0) line++;
544 while (end>line && isspace(*end)) {
545 *end=0;
546 end--;
548 return line;
552 static char*
553 next_token(char *line, char **next)
555 char *tmp, c;
556 char *ret;
558 *next = NULL;
559 while (*line==' ' || *line=='\t') line++;
561 tmp = line;
563 if (*tmp=='"') {
564 tmp++; line++;
565 while (*tmp!=0 && *tmp!='"') tmp++;
566 if (*tmp!='"') {
567 wwarning(_("%s: unmatched '\"' in menu file"), line);
568 return NULL;
570 } else {
571 do {
572 if (*tmp=='\\')
573 tmp++;
575 if (*tmp!=0)
576 tmp++;
578 } while (*tmp!=0 && *tmp!=' ' && *tmp!='\t');
581 c = *tmp;
582 *tmp = 0;
583 ret = wstrdup(line);
584 *tmp = c;
586 if (c==0)
587 return ret;
588 else
589 tmp++;
591 /* skip blanks */
592 while (*tmp==' ' || *tmp=='\t') tmp++;
594 if (*tmp!=0)
595 *next = tmp;
597 return ret;
601 static void
602 separateCommand(char *line, char ***file, char **command)
604 char *token, *tmp = line;
605 WMBag *bag = WMCreateBag(4);
606 int count, i;
608 *file = NULL;
609 *command = NULL;
610 do {
611 token = next_token(tmp, &tmp);
612 if (token) {
613 if (strcmp(token, "WITH")==0) {
614 if (tmp!=NULL && *tmp!=0)
615 *command = wstrdup(tmp);
616 else
617 wwarning(_("%s: missing command"), line);
618 break;
620 WMPutInBag(bag, token);
622 } while (token!=NULL && tmp!=NULL);
624 count = WMGetBagItemCount(bag);
625 if (count>0) {
626 *file = wmalloc(sizeof(char*)*(count+1));
627 (*file)[count] = NULL;
628 for (i = 0; i < count; i++) {
629 (*file)[i] = WMGetFromBag(bag, i);
632 WMFreeBag(bag);
636 static void
637 constructMenu(WMenu *menu, WMenuEntry *entry)
639 WMenu *submenu;
640 struct stat stat_buf;
641 char **path;
642 char *cmd;
643 char *lpath = NULL;
644 int i, first=-1;
645 time_t last=0;
647 separateCommand((char*)entry->clientdata, &path, &cmd);
648 if (!path || *path==NULL || **path==0) {
649 wwarning(_("invalid OPEN_MENU specification: %s"),
650 (char*)entry->clientdata);
651 return;
654 if (path[0][0]=='|') {
655 /* pipe menu */
657 if (!menu->cascades[entry->cascade] ||
658 menu->cascades[entry->cascade]->timestamp == 0) {
659 /* parse pipe */
661 submenu = readMenuPipe(menu->frame->screen_ptr, path);
663 /* there's no automatic reloading */
664 if(submenu != NULL)
665 submenu->timestamp = 1;
666 } else {
667 submenu = NULL;
670 } else {
671 i=0;
672 while(path[i] != NULL) {
673 char *tmp;
675 if (strcmp(path[i], "-noext")==0) {
676 i++;
677 continue;
680 tmp = wexpandpath(path[i]);
681 free(path[i]);
682 lpath = getLocalizedMenuFile(tmp);
683 if (lpath) {
684 free(tmp);
685 path[i] = lpath;
686 lpath = NULL;
687 } else {
688 path[i] = tmp;
691 if (stat(path[i], &stat_buf)==0) {
692 if (last < stat_buf.st_mtime)
693 last = stat_buf.st_mtime;
694 if (first<0)
695 first=i;
696 } else {
697 wsyserror(_("%s:could not stat menu"), path[i]);
698 /*goto finish;*/
701 i++;
704 if (first < 0) {
705 wsyserror(_("%s:could not stat menu:%s"), "OPEN_MENU",
706 (char*)entry->clientdata);
707 goto finish;
709 stat(path[first], &stat_buf);
710 if (!menu->cascades[entry->cascade]
711 || menu->cascades[entry->cascade]->timestamp < last) {
713 if (S_ISDIR(stat_buf.st_mode)) {
714 /* menu directory */
715 submenu = readMenuDirectory(menu->frame->screen_ptr,
716 entry->text, path, cmd);
717 if (submenu)
718 submenu->timestamp = last;
719 } else if (S_ISREG(stat_buf.st_mode)) {
720 /* menu file */
722 if (cmd || path[1])
723 wwarning(_("too many parameters in OPEN_MENU: %s"),
724 (char*)entry->clientdata);
726 submenu = readMenuFile(menu->frame->screen_ptr, path[first]);
727 if (submenu)
728 submenu->timestamp = stat_buf.st_mtime;
729 } else {
730 submenu = NULL;
732 } else {
733 submenu = NULL;
737 if (submenu) {
738 wMenuEntryRemoveCascade(menu, entry);
739 wMenuEntrySetCascade(menu, entry, submenu);
742 finish:
743 i = 0;
744 while (path[i]!=NULL)
745 free(path[i++]);
746 free(path);
747 if (cmd)
748 free(cmd);
752 static WMenuEntry*
753 addWorkspaceMenu(WScreen *scr, WMenu *menu, char *title)
755 WMenu *wsmenu;
756 WMenuEntry *entry;
758 if (scr->flags.added_workspace_menu) {
759 wwarning(_("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
760 return NULL;
761 } else {
762 scr->flags.added_workspace_menu = 1;
764 wsmenu = wWorkspaceMenuMake(scr, True);
765 scr->workspace_menu = wsmenu;
766 entry = wMenuAddCallback(menu, title, NULL, NULL);
767 wMenuEntrySetCascade(menu, entry, wsmenu);
769 wWorkspaceMenuUpdate(scr, wsmenu);
771 return entry;
775 static WMenuEntry*
776 addMenuEntry(WMenu *menu, char *title, char *shortcut, char *command,
777 char *params, char *file_name)
779 WScreen *scr;
780 WMenuEntry *entry = NULL;
781 Bool shortcutOk = False;
783 if (!menu)
784 return NULL;
785 scr = menu->frame->screen_ptr;
786 if (strcmp(command, "OPEN_MENU")==0) {
787 if (!params) {
788 wwarning(_("%s:missing parameter for menu command \"%s\""),
789 file_name, command);
790 } else {
791 WMenu *dummy;
792 char *path;
794 path = wfindfile(DEF_CONFIG_PATHS, params);
795 if (!path) {
796 path = wstrdup(params);
798 dummy = wMenuCreate(scr, title, False);
799 dummy->on_destroy = removeShortcutsForMenu;
800 entry = wMenuAddCallback(menu, title, constructMenu, path);
801 entry->free_cdata = free;
802 wMenuEntrySetCascade(menu, entry, dummy);
804 } else if (strcmp(command, "EXEC")==0) {
805 if (!params)
806 wwarning(_("%s:missing parameter for menu command \"%s\""),
807 file_name, command);
808 else {
809 entry = wMenuAddCallback(menu, title, execCommand,
810 wstrappend("exec ", params));
811 entry->free_cdata = free;
812 shortcutOk = True;
814 } else if (strcmp(command, "SHEXEC")==0) {
815 if (!params)
816 wwarning(_("%s:missing parameter for menu command \"%s\""),
817 file_name, command);
818 else {
819 entry = wMenuAddCallback(menu, title, execCommand,
820 wstrdup(params));
821 entry->free_cdata = free;
822 shortcutOk = True;
824 } else if (strcmp(command, "EXIT")==0) {
826 if (params && strcmp(params, "QUICK")==0)
827 entry = wMenuAddCallback(menu, title, exitCommand, (void*)M_QUICK);
828 else
829 entry = wMenuAddCallback(menu, title, exitCommand, NULL);
831 shortcutOk = True;
832 } else if (strcmp(command, "SHUTDOWN")==0) {
834 if (params && strcmp(params, "QUICK")==0)
835 entry = wMenuAddCallback(menu, title, shutdownCommand,
836 (void*)M_QUICK);
837 else
838 entry = wMenuAddCallback(menu, title, shutdownCommand, NULL);
840 shortcutOk = True;
841 } else if (strcmp(command, "REFRESH")==0) {
842 entry = wMenuAddCallback(menu, title, refreshCommand, NULL);
844 shortcutOk = True;
845 } else if (strcmp(command, "WORKSPACE_MENU")==0) {
846 entry = addWorkspaceMenu(scr, menu, title);
848 shortcutOk = True;
849 } else if (strcmp(command, "ARRANGE_ICONS")==0) {
850 entry = wMenuAddCallback(menu, title, arrangeIconsCommand, NULL);
852 shortcutOk = True;
853 } else if (strcmp(command, "HIDE_OTHERS")==0) {
854 entry = wMenuAddCallback(menu, title, hideOthersCommand, NULL);
856 shortcutOk = True;
857 } else if (strcmp(command, "SHOW_ALL")==0) {
858 entry = wMenuAddCallback(menu, title, showAllCommand, NULL);
860 shortcutOk = True;
861 } else if (strcmp(command, "RESTART")==0) {
862 entry = wMenuAddCallback(menu, title, restartCommand,
863 params ? wstrdup(params) : NULL);
864 entry->free_cdata = free;
865 shortcutOk = True;
866 } else if (strcmp(command, "SAVE_SESSION")==0) {
867 entry = wMenuAddCallback(menu, title, saveSessionCommand, NULL);
869 shortcutOk = True;
870 } else if (strcmp(command, "CLEAR_SESSION")==0) {
871 entry = wMenuAddCallback(menu, title, clearSessionCommand, NULL);
872 shortcutOk = True;
873 } else if (strcmp(command, "INFO_PANEL")==0) {
874 entry = wMenuAddCallback(menu, title, infoPanelCommand, NULL);
875 shortcutOk = True;
876 } else if (strcmp(command, "LEGAL_PANEL")==0) {
877 entry = wMenuAddCallback(menu, title, legalPanelCommand, NULL);
878 shortcutOk = True;
879 } else {
880 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name,
881 command);
883 return NULL;
886 if (shortcut && entry) {
887 if (!shortcutOk) {
888 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name,
889 title);
890 } else {
891 if (addShortcut(file_name, shortcut, menu, entry)) {
893 entry->rtext = GetShortcutString(shortcut);
895 entry->rtext = wstrdup(shortcut);
901 return entry;
906 /******************* Menu Configuration From File *******************/
908 static void
909 separateline(char *line, char *title, char *command, char *parameter,
910 char *shortcut)
912 int l, i;
914 l = strlen(line);
916 *title = 0;
917 *command = 0;
918 *parameter = 0;
919 *shortcut = 0;
920 /* get the title */
921 while (isspace(*line) && (*line!=0)) line++;
922 if (*line=='"') {
923 line++;
924 i=0;
925 while (line[i]!='"' && (line[i]!=0)) i++;
926 if (line[i]!='"')
927 return;
928 } else {
929 i=0;
930 while (!isspace(line[i]) && (line[i]!=0)) i++;
932 strncpy(title, line, i);
933 title[i++]=0;
934 line+=i;
936 /* get the command or shortcut keyword */
937 while (isspace(*line) && (*line!=0)) line++;
938 if (*line==0)
939 return;
940 i=0;
941 while (!isspace(line[i]) && (line[i]!=0)) i++;
942 strncpy(command, line, i);
943 command[i++]=0;
944 line+=i;
946 if (strcmp(command, "SHORTCUT")==0) {
947 /* get the shortcut key */
948 while (isspace(*line) && (*line!=0)) line++;
949 if (*line=='"') {
950 line++;
951 i=0;
952 while (line[i]!='"' && (line[i]!=0)) i++;
953 if (line[i]!='"')
954 return;
955 } else {
956 i=0;
957 while (!isspace(line[i]) && (line[i]!=0)) i++;
959 strncpy(shortcut, line, i);
960 shortcut[i++]=0;
961 line+=i;
963 *command=0;
965 /* get the command */
966 while (isspace(*line) && (*line!=0)) line++;
967 if (*line==0)
968 return;
969 i=0;
970 while (!isspace(line[i]) && (line[i]!=0)) i++;
971 strncpy(command, line, i);
972 command[i++]=0;
973 line+=i;
976 /* get the parameters */
977 while (isspace(*line) && (*line!=0)) line++;
978 if (*line==0)
979 return;
981 if (*line=='"') {
982 line++;
983 l = 0;
984 while (line[l]!=0 && line[l]!='"') {
985 parameter[l] = line[l];
986 l++;
988 parameter[l] = 0;
989 return;
992 l = strlen(line);
993 while (isspace(line[l]) && (l>0)) l--;
994 strncpy(parameter, line, l);
995 parameter[l]=0;
999 static WMenu*
1000 parseCascade(WScreen *scr, WMenu *menu, FILE *file, char *file_name)
1002 char linebuf[MAXLINE];
1003 char elinebuf[MAXLINE];
1004 char title[MAXLINE];
1005 char command[MAXLINE];
1006 char shortcut[MAXLINE];
1007 char params[MAXLINE];
1008 char *line;
1010 while (!IsEof(file)) {
1011 int lsize, ok;
1013 ok = 0;
1014 fgets(linebuf, MAXLINE, file);
1015 line = cropline(linebuf);
1016 lsize = strlen(line);
1017 do {
1018 if (line[lsize-1]=='\\') {
1019 char *line2;
1020 int lsize2;
1021 fgets(elinebuf, MAXLINE, file);
1022 line2=cropline(elinebuf);
1023 lsize2=strlen(line2);
1024 if (lsize2+lsize>MAXLINE) {
1025 wwarning(_("%s:maximal line size exceeded in menu config: %s"),
1026 file_name, line);
1027 ok=2;
1028 } else {
1029 line[lsize-1]=0;
1030 lsize+=lsize2-1;
1031 strcat(line, line2);
1033 } else {
1034 ok=1;
1036 } while (!ok && !IsEof(file));
1037 if (ok==2)
1038 continue;
1040 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1041 continue;
1044 separateline(line, title, command, params, shortcut);
1046 if (!command[0]) {
1047 wwarning(_("%s:missing command in menu config: %s"), file_name,
1048 line);
1049 goto error;
1052 if (strcasecmp(command, "MENU")==0) {
1053 WMenu *cascade;
1055 /* start submenu */
1057 cascade = wMenuCreate(scr, title, False);
1058 cascade->on_destroy = removeShortcutsForMenu;
1059 if (parseCascade(scr, cascade, file, file_name)==NULL) {
1060 wMenuDestroy(cascade, True);
1061 } else {
1062 wMenuEntrySetCascade(menu,
1063 wMenuAddCallback(menu, title, NULL, NULL),
1064 cascade);
1066 } else if (strcasecmp(command, "END")==0) {
1067 /* end of menu */
1068 return menu;
1070 } else {
1071 /* normal items */
1072 addMenuEntry(menu, title, shortcut[0] ? shortcut : NULL, command,
1073 params[0] ? params : NULL, file_name);
1077 wwarning(_("%s:syntax error in menu file:END declaration missing"),
1078 file_name);
1079 return menu;
1081 error:
1082 return menu;
1086 static WMenu*
1087 readMenuFile(WScreen *scr, char *file_name)
1089 WMenu *menu=NULL;
1090 FILE *file = NULL;
1091 char linebuf[MAXLINE];
1092 char title[MAXLINE];
1093 char shortcut[MAXLINE];
1094 char command[MAXLINE];
1095 char params[MAXLINE];
1096 char *line;
1097 #ifdef USECPP
1098 char *args;
1099 int cpp = 0;
1100 #endif
1102 #ifdef USECPP
1103 if (!wPreferences.flags.nocpp) {
1104 args = MakeCPPArgs(file_name);
1105 if (!args) {
1106 wwarning(_("could not make arguments for menu file preprocessor"));
1107 } else {
1108 sprintf(command, "%s %s %s", CPP_PATH, args, file_name);
1109 free(args);
1110 file = popen(command, "r");
1111 if (!file) {
1112 wsyserror(_("%s:could not open/preprocess menu file"),
1113 file_name);
1114 } else {
1115 cpp = 1;
1119 #endif /* USECPP */
1121 if (!file) {
1122 file = fopen(file_name, "r");
1123 if (!file) {
1124 wsyserror(_("%s:could not open menu file"), file_name);
1125 return NULL;
1129 while (!IsEof(file)) {
1130 if (!fgets(linebuf, MAXLINE, file))
1131 break;
1132 line = cropline(linebuf);
1133 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1134 continue;
1136 separateline(line, title, command, params, shortcut);
1138 if (!command[0]) {
1139 wwarning(_("%s:missing command in menu config: %s"), file_name,
1140 line);
1141 break;
1143 if (strcasecmp(command, "MENU")==0) {
1144 menu = wMenuCreate(scr, title, True);
1145 menu->on_destroy = removeShortcutsForMenu;
1146 if (!parseCascade(scr, menu, file, file_name)) {
1147 wMenuDestroy(menu, True);
1149 break;
1150 } else {
1151 wwarning(_("%s:invalid menu file. MENU command is missing"),
1152 file_name);
1153 break;
1157 #ifdef CPP
1158 if (cpp) {
1159 if (pclose(file)==-1) {
1160 wsyserror(_("error reading preprocessed menu data"));
1162 } else {
1163 fclose(file);
1165 #else
1166 fclose(file);
1167 #endif
1169 return menu;
1172 /************ Menu Configuration From Pipe *************/
1174 static WMenu*
1175 readMenuPipe(WScreen *scr, char **file_name)
1177 WMenu *menu=NULL;
1178 FILE *file = NULL;
1179 char linebuf[MAXLINE];
1180 char title[MAXLINE];
1181 char command[MAXLINE];
1182 char params[MAXLINE];
1183 char shortcut[MAXLINE];
1184 char *line;
1185 char * filename;
1186 char flat_file[MAXLINE];
1187 int i;
1188 #ifdef USECPP
1189 char *args;
1190 int cpp = 0;
1191 #endif
1193 flat_file[0] = '\0';
1195 for(i = 0 ; file_name[i] != NULL ; i++) {
1196 strcat(flat_file, file_name[i]);
1197 strcat(flat_file, " ");
1199 filename = flat_file+1;
1201 #ifdef USECPP
1202 if (!wPreferences.flags.nocpp) {
1203 args = MakeCPPArgs(filename);
1204 if (!args) {
1205 wwarning(_("could not make arguments for menu file preprocessor"));
1206 } else {
1207 sprintf(command, "%s | %s %s", filename, CPP_PATH, args);
1209 free(args);
1210 file = popen(command, "r");
1211 if (!file) {
1212 wsyserror(_("%s:could not open/preprocess menu file"), filename);
1213 } else {
1214 cpp = 1;
1219 #endif /* USECPP */
1221 if (!file) {
1222 file = popen(filename, "r");
1224 if (!file) {
1225 wsyserror(_("%s:could not open menu file"), filename);
1226 return NULL;
1230 while (!IsEof(file)) {
1231 if (!fgets(linebuf, MAXLINE, file))
1232 break;
1233 line = cropline(linebuf);
1234 if (line[0]==0 || line[0]=='#' || (line[0]=='/' && line[1]=='/'))
1235 continue;
1237 separateline(line, title, command, params, shortcut);
1239 if (!command[0]) {
1240 wwarning(_("%s:missing command in menu config: %s"), file_name,
1241 line);
1242 break;
1244 if (strcasecmp(command, "MENU")==0) {
1245 menu = wMenuCreate(scr, title, True);
1246 menu->on_destroy = removeShortcutsForMenu;
1247 if (!parseCascade(scr, menu, file, filename)) {
1248 wMenuDestroy(menu, True);
1250 break;
1251 } else {
1252 wwarning(_("%s:no title given for the root menu"), filename);
1253 break;
1257 pclose(file);
1259 return menu;
1264 typedef struct {
1265 char *name;
1266 int index;
1267 } dir_data;
1270 static int
1271 myCompare(const void *d1, const void *d2)
1273 dir_data *p1 = *(dir_data**)d1;
1274 dir_data *p2 = *(dir_data**)d2;
1276 return strcmp(p1->name, p2->name);
1280 /************ Menu Configuration From Directory *************/
1282 static Bool
1283 isFilePackage(char *file)
1285 int l;
1287 /* check if the extension indicates this file is a
1288 * file package. For now, only recognize .themed */
1290 l = strlen(file);
1292 if (l > 7 && strcmp(&(file[l-7]), ".themed")==0) {
1293 return True;
1294 } else {
1295 return False;
1300 static WMenu*
1301 readMenuDirectory(WScreen *scr, char *title, char **path, char *command)
1303 DIR *dir;
1304 struct dirent *dentry;
1305 struct stat stat_buf;
1306 WMenu *menu=NULL;
1307 char *buffer;
1308 WMBag *dirs = NULL, *files = NULL;
1309 int length, i, have_space=0;
1310 dir_data *data;
1311 int stripExtension = 0;
1314 dirs = WMCreateBag(16);
1315 files = WMCreateBag(16);
1317 i=0;
1318 while (path[i]!=NULL) {
1319 if (strcmp(path[i], "-noext")==0) {
1320 stripExtension = 1;
1321 i++;
1322 continue;
1325 dir = opendir(path[i]);
1326 if (!dir) {
1327 i++;
1328 continue;
1331 while ((dentry = readdir(dir))) {
1333 if (strcmp(dentry->d_name, ".")==0 ||
1334 strcmp(dentry->d_name, "..")==0)
1335 continue;
1337 if (dentry->d_name[0] == '.')
1338 continue;
1340 buffer = malloc(strlen(path[i])+strlen(dentry->d_name)+4);
1341 if (!buffer) {
1342 wsyserror(_("out of memory while constructing directory menu %s"),
1343 path[i]);
1344 break;
1347 strcpy(buffer, path[i]);
1348 strcat(buffer, "/");
1349 strcat(buffer, dentry->d_name);
1351 if (stat(buffer, &stat_buf)!=0) {
1352 wsyserror(_("%s:could not stat file \"%s\" in menu directory"),
1353 path[i], dentry->d_name);
1354 } else {
1355 Bool isFilePack = False;
1357 data = NULL;
1358 if (S_ISDIR(stat_buf.st_mode)
1359 && !(isFilePack = isFilePackage(dentry->d_name))) {
1361 /* access always returns success for user root */
1362 if (access(buffer, X_OK)==0) {
1363 /* Directory is accesible. Add to directory list */
1365 data = (dir_data*) wmalloc(sizeof(dir_data));
1366 data->name = wstrdup(dentry->d_name);
1367 data->index = i;
1369 WMPutInBag(dirs, data);
1371 } else if (S_ISREG(stat_buf.st_mode) || isFilePack) {
1372 /* Hack because access always returns X_OK success for user root */
1373 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1374 if ((command!=NULL && access(buffer, R_OK)==0) ||
1375 (command==NULL && access(buffer, X_OK)==0 &&
1376 (stat_buf.st_mode & S_IXANY))) {
1378 data = (dir_data*) wmalloc(sizeof(dir_data));
1379 data->name = wstrdup(dentry->d_name);
1380 data->index = i;
1382 WMPutInBag(files, data);
1386 free(buffer);
1389 closedir(dir);
1390 i++;
1393 if (!WMGetBagItemCount(dirs) && !WMGetBagItemCount(files)) {
1394 WMFreeBag(dirs);
1395 WMFreeBag(files);
1396 return NULL;
1399 WMSortBag(dirs, myCompare);
1400 WMSortBag(files, myCompare);
1402 menu = wMenuCreate(scr, title, False);
1403 menu->on_destroy = removeShortcutsForMenu;
1405 for (i = 0; i < WMGetBagItemCount(dirs); i++) {
1406 /* New directory. Use same OPEN_MENU command that was used
1407 * for the current directory. */
1408 dir_data *d = (dir_data*)WMGetFromBag(dirs, i);
1410 length = strlen(path[d->index])+strlen(d->name)+6;
1411 if (command)
1412 length += strlen(command) + 6;
1413 buffer = malloc(length);
1414 if (!buffer) {
1415 wsyserror(_("out of memory while constructing directory menu %s"),
1416 path[d->index]);
1417 break;
1420 have_space = strchr(path[d->index], ' ')!=NULL ||
1421 strchr(d->name, ' ')!=NULL;
1422 if (have_space) {
1423 buffer[0] = '"';
1424 buffer[1] = 0;
1425 strcat(buffer, path[d->index]);
1426 } else {
1427 strcpy(buffer, path[d->index]);
1429 strcat(buffer, "/");
1430 strcat(buffer, d->name);
1431 if (have_space)
1432 strcat(buffer, "\"");
1433 if (command) {
1434 strcat(buffer, " WITH ");
1435 strcat(buffer, command);
1438 addMenuEntry(menu, d->name, NULL, "OPEN_MENU", buffer, path[d->index]);
1440 free(buffer);
1441 if (d->name)
1442 free(d->name);
1443 free(d);
1446 for (i = 0; i < WMGetBagItemCount(files); i++) {
1447 /* executable: add as entry */
1448 dir_data *f = (dir_data*)WMGetFromBag(files, i);
1450 length = strlen(path[f->index])+strlen(f->name)+6;
1451 if (command)
1452 length += strlen(command);
1454 buffer = wmalloc(length);
1455 if (!buffer) {
1456 wsyserror(_("out of memory while constructing directory menu %s"),
1457 path[f->index]);
1458 break;
1461 have_space = strchr(path[f->index], ' ')!=NULL ||
1462 strchr(f->name, ' ')!=NULL;
1463 if (command!=NULL) {
1464 strcpy(buffer, command);
1465 strcat(buffer, " ");
1466 if (have_space)
1467 strcat(buffer, "\"");
1468 strcat(buffer, path[f->index]);
1469 } else {
1470 if (have_space) {
1471 buffer[0] = '"';
1472 buffer[1] = 0;
1473 strcat(buffer, path[f->index]);
1474 } else {
1475 strcpy(buffer, path[f->index]);
1478 strcat(buffer, "/");
1479 strcat(buffer, f->name);
1480 if (have_space)
1481 strcat(buffer, "\"");
1483 if (stripExtension) {
1484 char *ptr = strrchr(f->name, '.');
1485 if (ptr && ptr!=f->name)
1486 *ptr = 0;
1488 addMenuEntry(menu, f->name, NULL, "SHEXEC", buffer, path[f->index]);
1490 free(buffer);
1491 if (f->name)
1492 free(f->name);
1493 free(f);
1496 WMFreeBag(files);
1497 WMFreeBag(dirs);
1499 return menu;
1503 /************ Menu Configuration From WMRootMenu *************/
1505 static WMenu*
1506 makeDefaultMenu(WScreen *scr)
1508 WMenu *menu=NULL;
1510 menu = wMenuCreate(scr, _("Commands"), True);
1511 wMenuAddCallback(menu, "XTerm", execCommand, "xterm");
1512 wMenuAddCallback(menu, "rxvt", execCommand, "rxvt");
1513 wMenuAddCallback(menu, _("Restart"), restartCommand, NULL);
1514 wMenuAddCallback(menu, _("Exit..."), exitCommand, NULL);
1515 return menu;
1523 *----------------------------------------------------------------------
1524 * configureMenu--
1525 * Reads root menu configuration from defaults database.
1527 *----------------------------------------------------------------------
1529 static WMenu*
1530 configureMenu(WScreen *scr, proplist_t definition)
1532 WMenu *menu = NULL;
1533 proplist_t elem;
1534 int i, count;
1535 proplist_t title, command, params;
1536 char *tmp, *mtitle;
1539 if (PLIsString(definition)) {
1540 struct stat stat_buf;
1541 char *path = NULL;
1542 Bool menu_is_default = False;
1544 /* menu definition is a string. Probably a path, so parse the file */
1546 tmp = wexpandpath(PLGetString(definition));
1548 path = getLocalizedMenuFile(tmp);
1550 if (!path)
1551 path = wfindfile(DEF_CONFIG_PATHS, tmp);
1553 if (!path) {
1554 path = wfindfile(DEF_CONFIG_PATHS, DEF_MENU_FILE);
1555 menu_is_default = True;
1558 if (!path) {
1559 wsyserror(_("could not find menu file \"%s\" referenced in WMRootMenu"),
1560 tmp);
1561 free(tmp);
1562 return NULL;
1565 if (stat(path, &stat_buf)<0) {
1566 wsyserror(_("could not access menu \"%s\" referenced in WMRootMenu"), path);
1567 free(path);
1568 free(tmp);
1569 return NULL;
1572 if (!scr->root_menu || stat_buf.st_mtime > scr->root_menu->timestamp
1573 /* if the pointer in WMRootMenu has changed */
1574 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1576 if (menu_is_default) {
1577 wwarning(_("using default menu file \"%s\" as the menu referenced in WMRootMenu could not be found "),
1578 path);
1581 menu = readMenuFile(scr, path);
1582 if (menu)
1583 menu->timestamp = MAX(stat_buf.st_mtime, WDRootMenu->timestamp);
1584 } else {
1585 menu = NULL;
1587 free(path);
1588 free(tmp);
1590 return menu;
1593 count = PLGetNumberOfElements(definition);
1594 if (count==0)
1595 return NULL;
1597 elem = PLGetArrayElement(definition, 0);
1598 if (!PLIsString(elem)) {
1599 tmp = PLGetDescription(elem);
1600 wwarning(_("%s:format error in root menu configuration \"%s\""),
1601 "WMRootMenu", tmp);
1602 free(tmp);
1603 return NULL;
1605 mtitle = PLGetString(elem);
1607 menu = wMenuCreate(scr, mtitle, False);
1608 menu->on_destroy = removeShortcutsForMenu;
1610 #ifdef GLOBAL_SUBMENU_FILE
1612 WMenu *submenu;
1613 WMenuEntry *mentry;
1615 submenu = readMenuFile(scr, GLOBAL_SUBMENU_FILE);
1617 if (submenu) {
1618 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL, NULL);
1619 wMenuEntrySetCascade(menu, mentry, submenu);
1622 #endif
1624 for (i=1; i<count; i++) {
1625 elem = PLGetArrayElement(definition, i);
1626 #if 0
1627 if (PLIsString(elem)) {
1628 char *file;
1630 file = PLGetString(elem);
1633 #endif
1634 if (!PLIsArray(elem) || PLGetNumberOfElements(elem) < 2)
1635 goto error;
1637 if (PLIsArray(PLGetArrayElement(elem,1))) {
1638 WMenu *submenu;
1639 WMenuEntry *mentry;
1641 /* submenu */
1642 submenu = configureMenu(scr, elem);
1643 if (submenu) {
1644 mentry = wMenuAddCallback(menu, submenu->frame->title, NULL,
1645 NULL);
1646 wMenuEntrySetCascade(menu, mentry, submenu);
1648 } else {
1649 int idx = 0;
1650 char *shortcut;
1651 /* normal entry */
1653 title = PLGetArrayElement(elem, idx++);
1654 shortcut = PLGetArrayElement(elem, idx++);
1655 if (strcmp(PLGetString(shortcut), "SHORTCUT")==0) {
1656 shortcut = PLGetArrayElement(elem, idx++);
1657 command = PLGetArrayElement(elem, idx++);
1658 } else {
1659 command = shortcut;
1660 shortcut = NULL;
1662 params = PLGetArrayElement(elem, idx++);
1664 if (!title || !command)
1665 goto error;
1667 addMenuEntry(menu, PLGetString(title),
1668 shortcut ? PLGetString(shortcut) : NULL,
1669 PLGetString(command),
1670 params ? PLGetString(params) : NULL, "WMRootMenu");
1672 continue;
1674 error:
1675 tmp = PLGetDescription(elem);
1676 wwarning(_("%s:format error in root menu configuration \"%s\""),
1677 "WMRootMenu", tmp);
1678 free(tmp);
1681 return menu;
1692 *----------------------------------------------------------------------
1693 * OpenRootMenu--
1694 * Opens the root menu, parsing the menu configuration from the
1695 * defaults database.
1696 * If the menu is already mapped and is not sticked to the
1697 * root window, it will be unmapped.
1699 * Side effects:
1700 * The menu may be remade.
1702 * Notes:
1703 * Construction of OPEN_MENU entries are delayed to the moment the
1704 * user map's them.
1705 *----------------------------------------------------------------------
1707 void
1708 OpenRootMenu(WScreen *scr, int x, int y, int keyboard)
1710 WMenu *menu=NULL;
1711 proplist_t definition;
1713 static proplist_t domain=NULL;
1715 if (!domain) {
1716 domain = PLMakeString("WMRootMenu");
1720 scr->flags.root_menu_changed_shortcuts = 0;
1721 scr->flags.added_workspace_menu = 0;
1723 if (scr->root_menu && scr->root_menu->flags.mapped) {
1724 menu = scr->root_menu;
1725 if (!menu->flags.buttoned) {
1726 wMenuUnmap(menu);
1727 } else {
1728 wRaiseFrame(menu->frame->core);
1730 if (keyboard)
1731 wMenuMapAt(menu, 0, 0, True);
1732 else
1733 wMenuMapCopyAt(menu, x-menu->frame->core->width/2, y);
1735 return;
1739 definition = WDRootMenu->dictionary;
1742 definition = PLGetDomain(domain);
1744 if (definition) {
1745 if (PLIsArray(definition)) {
1746 if (!scr->root_menu
1747 || WDRootMenu->timestamp > scr->root_menu->timestamp) {
1748 menu = configureMenu(scr, definition);
1749 if (menu)
1750 menu->timestamp = WDRootMenu->timestamp;
1752 } else
1753 menu = NULL;
1754 } else {
1755 menu = configureMenu(scr, definition);
1759 if (!menu) {
1760 /* menu hasn't changed or could not be read */
1761 if (!scr->root_menu) {
1762 wMessageDialog(scr, _("Error"),
1763 _("The applications menu could not be loaded. "
1764 "Look at the console output for a detailed "
1765 "description of the errors."),
1766 _("OK"), NULL, NULL);
1768 menu = makeDefaultMenu(scr);
1769 scr->root_menu = menu;
1771 menu = scr->root_menu;
1772 } else {
1773 /* new root menu */
1774 if (scr->root_menu)
1775 wMenuDestroy(scr->root_menu, True);
1776 scr->root_menu = menu;
1778 if (menu) {
1779 wMenuMapAt(menu, x-menu->frame->core->width/2, y, keyboard);
1782 if (scr->flags.root_menu_changed_shortcuts)
1783 rebindKeygrabs(scr);
1786 #endif /* !LITE */