1 /* rootmenu.c- user defined menu
3 * Window Maker window manager
5 * Copyright (c) 1997-2003 Alfredo K. Kojima
6 * Copyright (c) 1998-2003 Dan Pascu
7 * Copyright (c) 2014 Window Maker Team
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License along
20 * with this program; if not, write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
31 #include <sys/types.h>
40 #include <X11/Xutil.h>
41 #include <X11/Xatom.h>
43 #include "WindowMaker.h"
51 #include "workspace.h"
56 #include "xmodifier.h"
59 #include "switchmenu.h"
61 #include <WINGs/WUtil.h>
63 #define MAX_SHORTCUT_LENGTH 32
65 static WMenu
*readMenuPipe(WScreen
* scr
, char **file_name
);
66 static WMenu
*readPLMenuPipe(WScreen
* scr
, char **file_name
);
67 static WMenu
*readMenuFile(WScreen
*scr
, const char *file_name
);
68 static WMenu
*readMenuDirectory(WScreen
*scr
, const char *title
, char **file_name
, const char *command
);
69 static WMenu
*configureMenu(WScreen
*scr
, WMPropList
*definition
);
70 static void menu_parser_register_macros(WMenuParser parser
);
72 typedef struct Shortcut
{
73 struct Shortcut
*next
;
81 static Shortcut
*shortcutList
= NULL
;
87 * "Title" EXEC command_to_exec -params
89 * "Title" EXEC command_to_exec -params
91 * "Workspaces" WORKSPACE_MENU
92 * "Title" built_in_command
94 * "Quick Quit" EXIT QUICK
97 * Commands may be preceded by SHORTCUT key
101 * INFO_PANEL - shows the Info Panel
102 * LEGAL_PANEL - shows the Legal info panel
103 * SHUTDOWN [QUICK] - closes the X server [without confirmation]
104 * REFRESH - forces the desktop to be repainted
105 * EXIT [QUICK] - exit the window manager [without confirmation]
106 * EXEC <program> - execute an external program
107 * SHEXEC <command> - execute a shell command
108 * WORKSPACE_MENU - places the workspace submenu
110 * RESTART [<window manager>] - restarts the window manager
111 * SHOW_ALL - unhide all windows on workspace
112 * HIDE_OTHERS - hides all windows excep the focused one
113 * OPEN_MENU file - read menu data from file which must be a valid menu file.
114 * OPEN_MENU /some/dir [/some/other/dir ...] [WITH command -options]
115 * - read menu data from directory(ies) and
116 * eventually precede each with a command.
117 * OPEN_MENU | command
118 * - opens command and uses its stdout to construct and insert
119 * the resulting menu in current position. The output of
120 * command must be a valid menu description.
121 * The space between '|' and command is optional.
122 * || will do the same, but will not cache the contents.
123 * OPEN_PLMENU | command
124 * - opens command and uses its stdout which must be in proplist
125 * fromat to construct and insert the resulting menu in current
127 * The space between '|' and command is optional.
128 * || will do the same, but will not cache the contents.
129 * SAVE_SESSION - saves the current state of the desktop, which include
130 * all running applications, all their hints (geometry,
131 * position on screen, workspace they live on, the dock
132 * or clip from where they were launched, and
133 * if minimized, shaded or hidden. Also saves the current
134 * workspace the user is on. All will be restored on every
135 * start of windowmaker until another SAVE_SESSION or
136 * CLEAR_SESSION is used. If SaveSessionOnExit = Yes; in
137 * WindowMaker domain file, then saving is automatically
138 * done on every windowmaker exit, overwriting any
139 * SAVE_SESSION or CLEAR_SESSION (see below). Also save
141 * CLEAR_SESSION - clears any previous saved session. This will not have
142 * any effect if SaveSessionOnExit is True.
150 static void execCommand(WMenu
* menu
, WMenuEntry
* entry
)
154 cmdline
= ExpandOptions(menu
->frame
->screen_ptr
, (char *)entry
->clientdata
);
156 XGrabPointer(dpy
, menu
->frame
->screen_ptr
->root_win
, True
, 0,
157 GrabModeAsync
, GrabModeAsync
, None
, wPreferences
.cursor
[WCUR_WAIT
], CurrentTime
);
161 ExecuteShellCommand(menu
->frame
->screen_ptr
, cmdline
);
164 XUngrabPointer(dpy
, CurrentTime
);
168 static void exitCommand(WMenu
* menu
, WMenuEntry
* entry
)
170 static int inside
= 0;
173 /* prevent reentrant calls */
183 if ((long)entry
->clientdata
== M_QUICK
) {
186 int r
, oldSaveSessionFlag
;
188 oldSaveSessionFlag
= wPreferences
.save_session_on_exit
;
189 r
= wExitDialog(menu
->frame
->screen_ptr
, _("Exit"),
190 _("Exit window manager?"), _("Exit"), _("Cancel"), NULL
);
192 if (r
== WAPRDefault
) {
194 } else if (r
== WAPRAlternate
) {
195 /* Don't modify the "save session on exit" flag if the
196 * user canceled the operation. */
197 wPreferences
.save_session_on_exit
= oldSaveSessionFlag
;
200 if (result
== R_EXIT
)
201 Shutdown(WSExitMode
);
208 static void shutdownCommand(WMenu
* menu
, WMenuEntry
* entry
)
210 static int inside
= 0;
213 /* prevent reentrant calls */
223 if ((long)entry
->clientdata
== M_QUICK
)
226 int r
, oldSaveSessionFlag
;
228 oldSaveSessionFlag
= wPreferences
.save_session_on_exit
;
230 r
= wExitDialog(menu
->frame
->screen_ptr
,
232 _("Kill Window System session?\n"
233 "(all applications will be closed)"), _("Kill"), _("Cancel"), NULL
);
234 if (r
== WAPRDefault
) {
236 } else if (r
== WAPRAlternate
) {
237 /* Don't modify the "save session on exit" flag if the
238 * user canceled the operation. */
239 wPreferences
.save_session_on_exit
= oldSaveSessionFlag
;
243 if (result
!= R_CANCEL
) {
244 Shutdown(WSKillMode
);
252 static void restartCommand(WMenu
* menu
, WMenuEntry
* entry
)
254 /* Parameter not used, but tell the compiler that it is ok */
258 Shutdown(WSRestartPreparationMode
);
259 Restart((char *)entry
->clientdata
, False
);
263 static void refreshCommand(WMenu
* menu
, WMenuEntry
* entry
)
265 /* Parameter not used, but tell the compiler that it is ok */
268 wRefreshDesktop(menu
->frame
->screen_ptr
);
271 static void arrangeIconsCommand(WMenu
* menu
, WMenuEntry
* entry
)
273 /* Parameter not used, but tell the compiler that it is ok */
276 wArrangeIcons(menu
->frame
->screen_ptr
, True
);
279 static void showAllCommand(WMenu
* menu
, WMenuEntry
* entry
)
281 /* Parameter not used, but tell the compiler that it is ok */
284 wShowAllWindows(menu
->frame
->screen_ptr
);
287 static void hideOthersCommand(WMenu
* menu
, WMenuEntry
* entry
)
289 /* Parameter not used, but tell the compiler that it is ok */
292 wHideOtherApplications(menu
->frame
->screen_ptr
->focused_window
);
295 static void saveSessionCommand(WMenu
* menu
, WMenuEntry
* entry
)
297 /* Parameter not used, but tell the compiler that it is ok */
300 if (!wPreferences
.save_session_on_exit
)
301 wSessionSaveState(menu
->frame
->screen_ptr
);
303 wScreenSaveState(menu
->frame
->screen_ptr
);
306 static void clearSessionCommand(WMenu
* menu
, WMenuEntry
* entry
)
308 /* Parameter not used, but tell the compiler that it is ok */
311 wSessionClearState(menu
->frame
->screen_ptr
);
312 wScreenSaveState(menu
->frame
->screen_ptr
);
315 static void infoPanelCommand(WMenu
* menu
, WMenuEntry
* entry
)
317 /* Parameter not used, but tell the compiler that it is ok */
320 wShowInfoPanel(menu
->frame
->screen_ptr
);
323 static void legalPanelCommand(WMenu
* menu
, WMenuEntry
* entry
)
325 /* Parameter not used, but tell the compiler that it is ok */
328 wShowLegalPanel(menu
->frame
->screen_ptr
);
331 /********************************************************************/
333 static char *getLocalizedMenuFile(const char *menu
)
335 char *buffer
, *ptr
, *locale
;
338 if (!w_global
.locale
)
341 len
= strlen(menu
) + strlen(w_global
.locale
) + 8;
342 buffer
= wmalloc(len
);
344 /* try menu.locale_name */
345 snprintf(buffer
, len
, "%s.%s", menu
, w_global
.locale
);
346 if (access(buffer
, F_OK
) == 0)
349 /* position of locale in our buffer */
350 locale
= buffer
+ strlen(menu
) + 1;
352 /* check if it is in the form aa_bb.encoding and check for aa_bb */
353 ptr
= strchr(locale
, '.');
356 if (access(buffer
, F_OK
) == 0)
360 /* now check for aa */
361 ptr
= strchr(locale
, '_');
364 if (access(buffer
, F_OK
) == 0)
373 Bool
wRootMenuPerformShortcut(XEvent
* event
)
375 WScreen
*scr
= wScreenForRootWindow(event
->xkey
.root
);
380 /* ignore CapsLock */
381 modifiers
= event
->xkey
.state
& w_global
.shortcut
.modifiers_mask
;
383 for (ptr
= shortcutList
; ptr
!= NULL
; ptr
= ptr
->next
) {
384 if (ptr
->keycode
== 0 || ptr
->menu
->menu
->screen_ptr
!= scr
)
387 if (ptr
->keycode
== event
->xkey
.keycode
&& ptr
->modifier
== modifiers
) {
388 (*ptr
->entry
->callback
) (ptr
->menu
, ptr
->entry
);
396 void wRootMenuBindShortcuts(Window window
)
402 if (ptr
->modifier
!= AnyModifier
) {
403 XGrabKey(dpy
, ptr
->keycode
, ptr
->modifier
| LockMask
,
404 window
, True
, GrabModeAsync
, GrabModeAsync
);
406 wHackedGrabKey(ptr
->keycode
, ptr
->modifier
, window
, True
, GrabModeAsync
, GrabModeAsync
);
409 XGrabKey(dpy
, ptr
->keycode
, ptr
->modifier
, window
, True
, GrabModeAsync
, GrabModeAsync
);
414 static void rebindKeygrabs(WScreen
* scr
)
418 wwin
= scr
->focused_window
;
420 while (wwin
!= NULL
) {
421 XUngrabKey(dpy
, AnyKey
, AnyModifier
, wwin
->frame
->core
->window
);
423 if (!WFLAGP(wwin
, no_bind_keys
)) {
424 wWindowSetKeyGrabs(wwin
);
430 static void removeShortcutsForMenu(WMenu
* menu
)
433 Shortcut
*newList
= NULL
;
436 while (ptr
!= NULL
) {
438 if (ptr
->menu
== menu
) {
446 shortcutList
= newList
;
447 menu
->menu
->screen_ptr
->flags
.root_menu_changed_shortcuts
= 1;
450 static Bool
addShortcut(const char *file
, const char *shortcutDefinition
, WMenu
*menu
, WMenuEntry
*entry
)
455 char buf
[MAX_SHORTCUT_LENGTH
], *b
;
457 ptr
= wmalloc(sizeof(Shortcut
));
459 wstrlcpy(buf
, shortcutDefinition
, MAX_SHORTCUT_LENGTH
);
464 while ((k
= strchr(b
, '+')) != NULL
) {
468 mod
= wXModifierFromKey(b
);
470 wwarning(_("%s: invalid key modifier \"%s\""), file
, b
);
474 ptr
->modifier
|= mod
;
480 ksym
= XStringToKeysym(b
);
482 if (ksym
== NoSymbol
) {
483 wwarning(_("%s:invalid kbd shortcut specification \"%s\" for entry %s"),
484 file
, shortcutDefinition
, entry
->text
);
489 ptr
->keycode
= XKeysymToKeycode(dpy
, ksym
);
490 if (ptr
->keycode
== 0) {
491 wwarning(_("%s:invalid key in shortcut \"%s\" for entry %s"), file
,
492 shortcutDefinition
, entry
->text
);
500 ptr
->next
= shortcutList
;
503 menu
->menu
->screen_ptr
->flags
.root_menu_changed_shortcuts
= 1;
508 static char *next_token(char *line
, char **next
)
514 while (*line
== ' ' || *line
== '\t')
522 while (*tmp
!= 0 && *tmp
!= '"')
525 wwarning(_("%s: unmatched '\"' in menu file"), line
);
536 } while (*tmp
!= 0 && *tmp
!= ' ' && *tmp
!= '\t');
550 while (*tmp
== ' ' || *tmp
== '\t')
559 static void separateCommand(char *line
, char ***file
, char **command
)
561 char *token
, *tmp
= line
;
562 WMArray
*array
= WMCreateArray(4);
568 token
= next_token(tmp
, &tmp
);
570 if (strcmp(token
, "WITH") == 0) {
571 if (tmp
!= NULL
&& *tmp
!= 0)
572 *command
= wstrdup(tmp
);
574 wwarning(_("%s: missing command"), line
);
578 WMAddToArray(array
, token
);
580 } while (token
!= NULL
&& tmp
!= NULL
);
582 count
= WMGetArrayItemCount(array
);
584 *file
= wmalloc(sizeof(char *) * (count
+ 1));
585 (*file
)[count
] = NULL
;
586 for (i
= 0; i
< count
; i
++) {
587 (*file
)[i
] = WMGetFromArray(array
, i
);
593 static WMenu
*constructPLMenu(WScreen
*screen
, const char *path
)
595 WMPropList
*pl
= NULL
;
601 pl
= WMReadPropListFromFile(path
);
605 menu
= configureMenu(screen
, pl
);
607 WMReleasePropList(pl
);
612 menu
->on_destroy
= removeShortcutsForMenu
;
618 static void constructMenu(WMenu
* menu
, WMenuEntry
* entry
)
621 struct stat stat_buf
;
628 separateCommand((char *)entry
->clientdata
, &path
, &cmd
);
629 if (path
== NULL
|| *path
== NULL
|| **path
== 0) {
630 wwarning(_("invalid OPEN_MENU specification: %s"), (char *)entry
->clientdata
);
632 for (i
= 0; path
[i
] != NULL
; i
++)
641 if (path
[0][0] == '|') {
644 if (!menu
->cascades
[entry
->cascade
] || menu
->cascades
[entry
->cascade
]->timestamp
== 0) {
647 submenu
= readMenuPipe(menu
->frame
->screen_ptr
, path
);
649 if (submenu
!= NULL
) {
650 if (path
[0][1] == '|')
651 submenu
->timestamp
= 0;
653 submenu
->timestamp
= 1; /* there's no automatic reloading */
661 /* try interpreting path as a proplist file */
662 submenu
= constructPLMenu(menu
->frame
->screen_ptr
, path
[0]);
663 /* if unsuccessful, try it as an old-style file */
667 while (path
[i
] != NULL
) {
670 if (strcmp(path
[i
], "-noext") == 0) {
675 tmp
= wexpandpath(path
[i
]);
677 if (strstr(tmp
, "#usergnusteppath#") == tmp
)
678 tmp
= wstrconcat(wusergnusteppath(),
682 lpath
= getLocalizedMenuFile(tmp
);
691 if (stat(path
[i
], &stat_buf
) == 0) {
692 if (last
< stat_buf
.st_mtime
)
693 last
= stat_buf
.st_mtime
;
697 werror(_("%s:could not stat menu"), path
[i
]);
705 werror(_("%s:could not stat menu:%s"), "OPEN_MENU", (char *)entry
->clientdata
);
708 stat(path
[first
], &stat_buf
);
709 if (!menu
->cascades
[entry
->cascade
]
710 || menu
->cascades
[entry
->cascade
]->timestamp
< last
) {
712 if (S_ISDIR(stat_buf
.st_mode
)) {
714 submenu
= readMenuDirectory(menu
->frame
->screen_ptr
, entry
->text
, path
, cmd
);
716 submenu
->timestamp
= last
;
717 } else if (S_ISREG(stat_buf
.st_mode
)) {
721 wwarning(_("too many parameters in OPEN_MENU: %s"),
722 (char *)entry
->clientdata
);
724 submenu
= readMenuFile(menu
->frame
->screen_ptr
, path
[first
]);
726 submenu
->timestamp
= stat_buf
.st_mtime
;
737 wMenuEntryRemoveCascade(menu
, entry
);
738 wMenuEntrySetCascade(menu
, entry
, submenu
);
743 while (path
[i
] != NULL
)
750 static void constructPLMenuFromPipe(WMenu
* menu
, WMenuEntry
* entry
)
752 WMenu
*submenu
= NULL
;
757 separateCommand((char *)entry
->clientdata
, &path
, &cmd
);
758 if (path
== NULL
|| *path
== NULL
|| **path
== 0) {
759 wwarning(_("invalid OPEN_PLMENU specification: %s"),
760 (char *)entry
->clientdata
);
762 for (i
= 0; path
[i
] != NULL
; i
++)
771 if (path
[0][0] == '|') {
774 if (!menu
->cascades
[entry
->cascade
]
775 || menu
->cascades
[entry
->cascade
]->timestamp
== 0) {
777 submenu
= readPLMenuPipe(menu
->frame
->screen_ptr
, path
);
779 if (submenu
!= NULL
) {
780 if (path
[0][1] == '|')
781 submenu
->timestamp
= 0;
783 submenu
->timestamp
= 1; /* there's no automatic reloading */
789 wMenuEntryRemoveCascade(menu
, entry
);
790 wMenuEntrySetCascade(menu
, entry
, submenu
);
794 while (path
[i
] != NULL
)
802 static void cleanupWorkspaceMenu(WMenu
*menu
)
804 if (menu
->frame
->screen_ptr
->workspace_menu
== menu
)
805 menu
->frame
->screen_ptr
->workspace_menu
= NULL
;
808 static WMenuEntry
*addWorkspaceMenu(WScreen
*scr
, WMenu
*menu
, const char *title
)
813 if (scr
->flags
.added_workspace_menu
) {
815 ("There are more than one WORKSPACE_MENU commands in the applications menu. Only one is allowed."));
818 scr
->flags
.added_workspace_menu
= 1;
820 wsmenu
= wWorkspaceMenuMake(scr
, True
);
821 wsmenu
->on_destroy
= cleanupWorkspaceMenu
;
823 scr
->workspace_menu
= wsmenu
;
824 entry
= wMenuAddCallback(menu
, title
, NULL
, NULL
);
825 wMenuEntrySetCascade(menu
, entry
, wsmenu
);
827 wWorkspaceMenuUpdate(scr
, wsmenu
);
832 static void cleanupWindowsMenu(WMenu
* menu
)
834 if (menu
->frame
->screen_ptr
->switch_menu
== menu
)
835 menu
->frame
->screen_ptr
->switch_menu
= NULL
;
838 static WMenuEntry
*addWindowsMenu(WScreen
*scr
, WMenu
*menu
, const char *title
)
844 if (scr
->flags
.added_windows_menu
) {
846 ("There are more than one WINDOWS_MENU commands in the applications menu. Only one is allowed."));
849 scr
->flags
.added_windows_menu
= 1;
851 wwmenu
= wMenuCreate(scr
, _("Window List"), False
);
852 wwmenu
->on_destroy
= cleanupWindowsMenu
;
853 scr
->switch_menu
= wwmenu
;
854 wwin
= scr
->focused_window
;
856 UpdateSwitchMenu(scr
, wwin
, ACTION_ADD
);
860 entry
= wMenuAddCallback(menu
, title
, NULL
, NULL
);
861 wMenuEntrySetCascade(menu
, entry
, wwmenu
);
866 static WMenuEntry
*addMenuEntry(WMenu
*menu
, const char *title
, const char *shortcut
, const char *command
,
867 const char *params
, const char *file_name
)
870 WMenuEntry
*entry
= NULL
;
871 Bool shortcutOk
= False
;
875 scr
= menu
->frame
->screen_ptr
;
876 if (strcmp(command
, "OPEN_MENU") == 0) {
878 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name
, command
);
883 path
= wfindfile(DEF_CONFIG_PATHS
, params
);
885 path
= wstrdup(params
);
887 dummy
= wMenuCreate(scr
, title
, False
);
888 dummy
->on_destroy
= removeShortcutsForMenu
;
889 entry
= wMenuAddCallback(menu
, title
, constructMenu
, path
);
890 entry
->free_cdata
= wfree
;
891 wMenuEntrySetCascade(menu
, entry
, dummy
);
893 } else if (strcmp(command
, "OPEN_PLMENU") == 0) {
895 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name
, command
);
900 path
= wfindfile(DEF_CONFIG_PATHS
, params
);
902 path
= wstrdup(params
);
904 dummy
= wMenuCreate(scr
, title
, False
);
905 dummy
->on_destroy
= removeShortcutsForMenu
;
906 entry
= wMenuAddCallback(menu
, title
, constructPLMenuFromPipe
, path
);
907 entry
->free_cdata
= wfree
;
908 wMenuEntrySetCascade(menu
, entry
, dummy
);
910 } else if (strcmp(command
, "EXEC") == 0) {
912 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name
, command
);
914 entry
= wMenuAddCallback(menu
, title
, execCommand
, wstrconcat("exec ", params
));
915 entry
->free_cdata
= wfree
;
918 } else if (strcmp(command
, "SHEXEC") == 0) {
920 wwarning(_("%s:missing parameter for menu command \"%s\""), file_name
, command
);
922 entry
= wMenuAddCallback(menu
, title
, execCommand
, wstrdup(params
));
923 entry
->free_cdata
= wfree
;
926 } else if (strcmp(command
, "EXIT") == 0) {
928 if (params
&& strcmp(params
, "QUICK") == 0)
929 entry
= wMenuAddCallback(menu
, title
, exitCommand
, (void *)M_QUICK
);
931 entry
= wMenuAddCallback(menu
, title
, exitCommand
, NULL
);
934 } else if (strcmp(command
, "SHUTDOWN") == 0) {
936 if (params
&& strcmp(params
, "QUICK") == 0)
937 entry
= wMenuAddCallback(menu
, title
, shutdownCommand
, (void *)M_QUICK
);
939 entry
= wMenuAddCallback(menu
, title
, shutdownCommand
, NULL
);
942 } else if (strcmp(command
, "REFRESH") == 0) {
943 entry
= wMenuAddCallback(menu
, title
, refreshCommand
, NULL
);
946 } else if (strcmp(command
, "WORKSPACE_MENU") == 0) {
947 entry
= addWorkspaceMenu(scr
, menu
, title
);
950 } else if (strcmp(command
, "WINDOWS_MENU") == 0) {
951 entry
= addWindowsMenu(scr
, menu
, title
);
954 } else if (strcmp(command
, "ARRANGE_ICONS") == 0) {
955 entry
= wMenuAddCallback(menu
, title
, arrangeIconsCommand
, NULL
);
958 } else if (strcmp(command
, "HIDE_OTHERS") == 0) {
959 entry
= wMenuAddCallback(menu
, title
, hideOthersCommand
, NULL
);
962 } else if (strcmp(command
, "SHOW_ALL") == 0) {
963 entry
= wMenuAddCallback(menu
, title
, showAllCommand
, NULL
);
966 } else if (strcmp(command
, "RESTART") == 0) {
967 entry
= wMenuAddCallback(menu
, title
, restartCommand
, params
? wstrdup(params
) : NULL
);
968 entry
->free_cdata
= wfree
;
970 } else if (strcmp(command
, "SAVE_SESSION") == 0) {
971 entry
= wMenuAddCallback(menu
, title
, saveSessionCommand
, NULL
);
974 } else if (strcmp(command
, "CLEAR_SESSION") == 0) {
975 entry
= wMenuAddCallback(menu
, title
, clearSessionCommand
, NULL
);
977 } else if (strcmp(command
, "INFO_PANEL") == 0) {
978 entry
= wMenuAddCallback(menu
, title
, infoPanelCommand
, NULL
);
980 } else if (strcmp(command
, "LEGAL_PANEL") == 0) {
981 entry
= wMenuAddCallback(menu
, title
, legalPanelCommand
, NULL
);
984 wwarning(_("%s:unknown command \"%s\" in menu config."), file_name
, command
);
989 if (shortcut
&& entry
) {
991 wwarning(_("%s:can't add shortcut for entry \"%s\""), file_name
, title
);
993 if (addShortcut(file_name
, shortcut
, menu
, entry
)) {
995 entry
->rtext
= GetShortcutString(shortcut
);
997 entry->rtext = wstrdup(shortcut);
1006 /******************* Menu Configuration From File *******************/
1008 static void freeline(char *title
, char *command
, char *parameter
, char *shortcut
)
1016 static WMenu
*parseCascade(WScreen
* scr
, WMenu
* menu
, WMenuParser parser
)
1018 char *command
, *params
, *shortcut
, *title
;
1020 while (WMenuParserGetLine(parser
, &title
, &command
, ¶ms
, &shortcut
)) {
1022 if (command
== NULL
|| !command
[0]) {
1023 WMenuParserError(parser
, _("missing command in menu config") );
1024 freeline(title
, command
, params
, shortcut
);
1028 if (strcasecmp(command
, "MENU") == 0) {
1033 cascade
= wMenuCreate(scr
, M_(title
), False
);
1034 cascade
->on_destroy
= removeShortcutsForMenu
;
1035 if (!parseCascade(scr
, cascade
, parser
)) {
1036 wMenuDestroy(cascade
, True
);
1038 wMenuEntrySetCascade(menu
, wMenuAddCallback(menu
, M_(title
), NULL
, NULL
), cascade
);
1040 } else if (strcasecmp(command
, "END") == 0) {
1042 freeline(title
, command
, params
, shortcut
);
1046 addMenuEntry(menu
, M_(title
), shortcut
, command
, params
, WMenuParserGetFilename(parser
));
1048 freeline(title
, command
, params
, shortcut
);
1051 WMenuParserError(parser
, _("syntax error in menu file: END declaration missing") );
1057 static WMenu
*readMenu(WScreen
*scr
, const char *flat_file
, FILE *file
)
1061 char *title
, *command
, *params
, *shortcut
;
1063 parser
= WMenuParserCreate(flat_file
, file
, DEF_CONFIG_PATHS
);
1064 menu_parser_register_macros(parser
);
1066 while (WMenuParserGetLine(parser
, &title
, &command
, ¶ms
, &shortcut
)) {
1068 if (command
== NULL
|| !command
[0]) {
1069 WMenuParserError(parser
, _("missing command in menu config") );
1070 freeline(title
, command
, params
, shortcut
);
1073 if (strcasecmp(command
, "MENU") == 0) {
1074 menu
= wMenuCreate(scr
, M_(title
), True
);
1075 menu
->on_destroy
= removeShortcutsForMenu
;
1076 if (!parseCascade(scr
, menu
, parser
)) {
1077 wMenuDestroy(menu
, True
);
1080 freeline(title
, command
, params
, shortcut
);
1083 WMenuParserError(parser
, _("invalid menu, no menu title given") );
1084 freeline(title
, command
, params
, shortcut
);
1088 freeline(title
, command
, params
, shortcut
);
1091 WMenuParserDelete(parser
);
1095 static WMenu
*readMenuFile(WScreen
*scr
, const char *file_name
)
1100 file
= fopen(file_name
, "rb");
1102 werror(_("could not open menu file \"%s\": %s"), file_name
, strerror(errno
));
1105 menu
= readMenu(scr
, file_name
, file
);
1111 static inline int generate_command_from_list(char *buffer
, size_t buffer_size
, char **command_elements
)
1118 for (i
= 0; command_elements
[i
] != NULL
; i
++) {
1121 if (wr_idx
< buffer_size
- 1)
1122 buffer
[wr_idx
++] = ' ';
1124 for (rd
= command_elements
[i
]; *rd
!= '\0'; rd
++) {
1125 if (wr_idx
< buffer_size
- 1)
1126 buffer
[wr_idx
++] = *rd
;
1131 buffer
[wr_idx
] = '\0';
1135 /************ Menu Configuration From Pipe *************/
1136 static WMenu
*readPLMenuPipe(WScreen
* scr
, char **file_name
)
1138 WMPropList
*plist
= NULL
;
1141 char flat_file
[MAXLINE
];
1143 if (generate_command_from_list(flat_file
, sizeof(flat_file
), file_name
)) {
1144 werror(_("could not open menu file \"%s\": %s"),
1145 file_name
[0], _("pipe command for PropertyList is too long"));
1148 filename
= flat_file
+ (flat_file
[1] == '|' ? 2 : 1);
1150 plist
= WMReadPropListFromPipe(filename
);
1155 menu
= configureMenu(scr
, plist
);
1157 WMReleasePropList(plist
);
1162 menu
->on_destroy
= removeShortcutsForMenu
;
1166 static WMenu
*readMenuPipe(WScreen
* scr
, char **file_name
)
1171 char flat_file
[MAXLINE
];
1173 if (generate_command_from_list(flat_file
, sizeof(flat_file
), file_name
)) {
1174 werror(_("could not open menu file \"%s\": %s"),
1175 file_name
[0], _("pipe command is too long"));
1178 filename
= flat_file
+ (flat_file
[1] == '|' ? 2 : 1);
1181 * In case of memory problem, 'popen' will not set the errno, so we initialise it
1182 * to be able to display a meaningful message. For other problems, 'popen' will
1183 * properly set errno, so we'll still get a good message
1186 file
= popen(filename
, "r");
1188 werror(_("could not open menu file \"%s\": %s"), filename
, strerror(errno
));
1191 menu
= readMenu(scr
, flat_file
, file
);
1202 static int myCompare(const void *d1
, const void *d2
)
1204 dir_data
*p1
= *(dir_data
**) d1
;
1205 dir_data
*p2
= *(dir_data
**) d2
;
1207 return strcmp(p1
->name
, p2
->name
);
1210 /***** Preset some macro for file parser *****/
1211 static void menu_parser_register_macros(WMenuParser parser
)
1216 // Used to return CPP verion, now returns wmaker's version
1217 WMenuParserRegisterSimpleMacro(parser
, "__VERSION__", VERSION
);
1219 // All macros below were historically defined by WindowMaker
1220 visual
= DefaultVisual(dpy
, DefaultScreen(dpy
));
1221 snprintf(buf
, sizeof(buf
), "%d", visual
->class);
1222 WMenuParserRegisterSimpleMacro(parser
, "VISUAL", buf
);
1224 snprintf(buf
, sizeof(buf
), "%d", DefaultDepth(dpy
, DefaultScreen(dpy
)) );
1225 WMenuParserRegisterSimpleMacro(parser
, "DEPTH", buf
);
1227 snprintf(buf
, sizeof(buf
), "%d", WidthOfScreen(DefaultScreenOfDisplay(dpy
)) );
1228 WMenuParserRegisterSimpleMacro(parser
, "SCR_WIDTH", buf
);
1230 snprintf(buf
, sizeof(buf
), "%d", HeightOfScreen(DefaultScreenOfDisplay(dpy
)) );
1231 WMenuParserRegisterSimpleMacro(parser
, "SCR_HEIGHT", buf
);
1233 WMenuParserRegisterSimpleMacro(parser
, "DISPLAY", XDisplayName(DisplayString(dpy
)) );
1235 WMenuParserRegisterSimpleMacro(parser
, "WM_VERSION", "\"" VERSION
"\"");
1238 /************ Menu Configuration From Directory *************/
1240 static Bool
isFilePackage(const char *file
)
1244 /* check if the extension indicates this file is a
1245 * file package. For now, only recognize .themed */
1249 if (l
> 7 && strcmp(&(file
[l
- 7]), ".themed") == 0) {
1256 static WMenu
*readMenuDirectory(WScreen
*scr
, const char *title
, char **path
, const char *command
)
1259 struct dirent
*dentry
;
1260 struct stat stat_buf
;
1263 WMArray
*dirs
= NULL
, *files
= NULL
;
1264 WMArrayIterator iter
;
1265 int length
, i
, have_space
= 0;
1267 int stripExtension
= 0;
1269 dirs
= WMCreateArray(16);
1270 files
= WMCreateArray(16);
1273 while (path
[i
] != NULL
) {
1274 if (strcmp(path
[i
], "-noext") == 0) {
1280 dir
= opendir(path
[i
]);
1286 while ((dentry
= readdir(dir
))) {
1288 if (strcmp(dentry
->d_name
, ".") == 0 || strcmp(dentry
->d_name
, "..") == 0)
1291 if (dentry
->d_name
[0] == '.')
1294 buffer
= malloc(strlen(path
[i
]) + strlen(dentry
->d_name
) + 4);
1296 werror(_("out of memory while constructing directory menu %s"), path
[i
]);
1300 strcpy(buffer
, path
[i
]);
1301 strcat(buffer
, "/");
1302 strcat(buffer
, dentry
->d_name
);
1304 if (stat(buffer
, &stat_buf
) != 0) {
1305 werror(_("%s:could not stat file \"%s\" in menu directory"),
1306 path
[i
], dentry
->d_name
);
1308 Bool isFilePack
= False
;
1311 if (S_ISDIR(stat_buf
.st_mode
)
1312 && !(isFilePack
= isFilePackage(dentry
->d_name
))) {
1314 /* access always returns success for user root */
1315 if (access(buffer
, X_OK
) == 0) {
1316 /* Directory is accesible. Add to directory list */
1318 data
= (dir_data
*) wmalloc(sizeof(dir_data
));
1319 data
->name
= wstrdup(dentry
->d_name
);
1322 WMAddToArray(dirs
, data
);
1324 } else if (S_ISREG(stat_buf
.st_mode
) || isFilePack
) {
1325 /* Hack because access always returns X_OK success for user root */
1326 #define S_IXANY (S_IXUSR | S_IXGRP | S_IXOTH)
1327 if ((command
!= NULL
&& access(buffer
, R_OK
) == 0) ||
1328 (command
== NULL
&& access(buffer
, X_OK
) == 0 &&
1329 (stat_buf
.st_mode
& S_IXANY
))) {
1331 data
= (dir_data
*) wmalloc(sizeof(dir_data
));
1332 data
->name
= wstrdup(dentry
->d_name
);
1335 WMAddToArray(files
, data
);
1346 if (!WMGetArrayItemCount(dirs
) && !WMGetArrayItemCount(files
)) {
1352 WMSortArray(dirs
, myCompare
);
1353 WMSortArray(files
, myCompare
);
1355 menu
= wMenuCreate(scr
, M_(title
), False
);
1356 menu
->on_destroy
= removeShortcutsForMenu
;
1358 WM_ITERATE_ARRAY(dirs
, data
, iter
) {
1359 /* New directory. Use same OPEN_MENU command that was used
1360 * for the current directory. */
1361 length
= strlen(path
[data
->index
]) + strlen(data
->name
) + 6;
1365 length
+= strlen(command
) + 6;
1366 buffer
= malloc(length
);
1368 werror(_("out of memory while constructing directory menu %s"), path
[data
->index
]);
1374 strcat(buffer
, "-noext ");
1376 have_space
= strchr(path
[data
->index
], ' ') != NULL
|| strchr(data
->name
, ' ') != NULL
;
1379 strcat(buffer
, "\"");
1380 strcat(buffer
, path
[data
->index
]);
1382 strcat(buffer
, "/");
1383 strcat(buffer
, data
->name
);
1385 strcat(buffer
, "\"");
1387 strcat(buffer
, " WITH ");
1388 strcat(buffer
, command
);
1391 addMenuEntry(menu
, M_(data
->name
), NULL
, "OPEN_MENU", buffer
, path
[data
->index
]);
1398 WM_ITERATE_ARRAY(files
, data
, iter
) {
1399 /* executable: add as entry */
1400 length
= strlen(path
[data
->index
]) + strlen(data
->name
) + 6;
1402 length
+= strlen(command
);
1404 buffer
= malloc(length
);
1406 werror(_("out of memory while constructing directory menu %s"), path
[data
->index
]);
1410 have_space
= strchr(path
[data
->index
], ' ') != NULL
|| strchr(data
->name
, ' ') != NULL
;
1411 if (command
!= NULL
) {
1412 strcpy(buffer
, command
);
1413 strcat(buffer
, " ");
1415 strcat(buffer
, "\"");
1416 strcat(buffer
, path
[data
->index
]);
1421 strcat(buffer
, path
[data
->index
]);
1423 strcpy(buffer
, path
[data
->index
]);
1426 strcat(buffer
, "/");
1427 strcat(buffer
, data
->name
);
1429 strcat(buffer
, "\"");
1431 if (stripExtension
) {
1432 char *ptr
= strrchr(data
->name
, '.');
1433 if (ptr
&& ptr
!= data
->name
)
1436 addMenuEntry(menu
, M_(data
->name
), NULL
, "SHEXEC", buffer
, path
[data
->index
]);
1449 /************ Menu Configuration From WMRootMenu *************/
1451 static WMenu
*makeDefaultMenu(WScreen
* scr
)
1455 menu
= wMenuCreate(scr
, _("Commands"), True
);
1456 wMenuAddCallback(menu
, M_("XTerm"), execCommand
, "xterm");
1457 wMenuAddCallback(menu
, M_("rxvt"), execCommand
, "rxvt");
1458 wMenuAddCallback(menu
, _("Restart"), restartCommand
, NULL
);
1459 wMenuAddCallback(menu
, _("Exit..."), exitCommand
, NULL
);
1464 *----------------------------------------------------------------------
1466 * Reads root menu configuration from defaults database.
1468 *----------------------------------------------------------------------
1470 static WMenu
*configureMenu(WScreen
*scr
, WMPropList
*definition
)
1475 WMPropList
*title
, *command
, *params
;
1478 if (WMIsPLString(definition
)) {
1479 struct stat stat_buf
;
1481 Bool menu_is_default
= False
;
1483 /* menu definition is a string. Probably a path, so parse the file */
1485 tmp
= wexpandpath(WMGetFromPLString(definition
));
1487 path
= getLocalizedMenuFile(tmp
);
1490 path
= wfindfile(DEF_CONFIG_PATHS
, tmp
);
1493 path
= wfindfile(DEF_CONFIG_PATHS
, DEF_MENU_FILE
);
1494 menu_is_default
= True
;
1498 werror(_("could not find menu file \"%s\" referenced in WMRootMenu"), tmp
);
1503 if (stat(path
, &stat_buf
) < 0) {
1504 werror(_("could not access menu \"%s\" referenced in WMRootMenu"), path
);
1510 if (!scr
->root_menu
|| stat_buf
.st_mtime
> scr
->root_menu
->timestamp
1511 /* if the pointer in WMRootMenu has changed */
1512 || w_global
.domain
.root_menu
->timestamp
> scr
->root_menu
->timestamp
) {
1513 WMPropList
*menu_from_file
= NULL
;
1515 if (menu_is_default
) {
1517 ("using default menu file \"%s\" as the menu referenced in WMRootMenu could not be found "),
1521 menu_from_file
= WMReadPropListFromFile(path
);
1522 if (menu_from_file
== NULL
) { /* old style menu */
1523 menu
= readMenuFile(scr
, path
);
1525 menu
= configureMenu(scr
, menu_from_file
);
1526 WMReleasePropList(menu_from_file
);
1530 menu
->timestamp
= WMAX(stat_buf
.st_mtime
, w_global
.domain
.root_menu
->timestamp
);
1540 count
= WMGetPropListItemCount(definition
);
1544 elem
= WMGetFromPLArray(definition
, 0);
1545 if (!WMIsPLString(elem
)) {
1546 tmp
= WMGetPropListDescription(elem
, False
);
1547 wwarning(_("%s:format error in root menu configuration \"%s\""), "WMRootMenu", tmp
);
1551 mtitle
= WMGetFromPLString(elem
);
1553 menu
= wMenuCreate(scr
, M_(mtitle
), False
);
1554 menu
->on_destroy
= removeShortcutsForMenu
;
1556 for (i
= 1; i
< count
; i
++) {
1557 elem
= WMGetFromPLArray(definition
, i
);
1559 if (WMIsPLString(elem
)) {
1562 file
= WMGetFromPLString(elem
);
1566 if (!WMIsPLArray(elem
) || WMGetPropListItemCount(elem
) < 2)
1569 if (WMIsPLArray(WMGetFromPLArray(elem
, 1))) {
1574 submenu
= configureMenu(scr
, elem
);
1576 mentry
= wMenuAddCallback(menu
, submenu
->frame
->title
, NULL
, NULL
);
1577 wMenuEntrySetCascade(menu
, mentry
, submenu
);
1581 WMPropList
*shortcut
;
1584 title
= WMGetFromPLArray(elem
, idx
++);
1585 shortcut
= WMGetFromPLArray(elem
, idx
++);
1586 if (strcmp(WMGetFromPLString(shortcut
), "SHORTCUT") == 0) {
1587 shortcut
= WMGetFromPLArray(elem
, idx
++);
1588 command
= WMGetFromPLArray(elem
, idx
++);
1593 params
= WMGetFromPLArray(elem
, idx
++);
1595 if (!title
|| !command
)
1598 addMenuEntry(menu
, M_(WMGetFromPLString(title
)),
1599 shortcut
? WMGetFromPLString(shortcut
) : NULL
,
1600 WMGetFromPLString(command
),
1601 params
? WMGetFromPLString(params
) : NULL
, "WMRootMenu");
1606 tmp
= WMGetPropListDescription(elem
, False
);
1607 wwarning(_("%s:format error in root menu configuration \"%s\""), "WMRootMenu", tmp
);
1615 *----------------------------------------------------------------------
1617 * Opens the root menu, parsing the menu configuration from the
1618 * defaults database.
1619 * If the menu is already mapped and is not sticked to the
1620 * root window, it will be unmapped.
1623 * The menu may be remade.
1626 * Construction of OPEN_MENU entries are delayed to the moment the
1628 *----------------------------------------------------------------------
1630 void OpenRootMenu(WScreen
* scr
, int x
, int y
, int keyboard
)
1633 WMPropList
*definition
;
1635 static WMPropList *domain=NULL;
1638 domain = WMCreatePLString("WMRootMenu");
1642 scr
->flags
.root_menu_changed_shortcuts
= 0;
1643 scr
->flags
.added_workspace_menu
= 0;
1644 scr
->flags
.added_windows_menu
= 0;
1646 if (scr
->root_menu
&& scr
->root_menu
->flags
.mapped
) {
1647 menu
= scr
->root_menu
;
1648 if (!menu
->flags
.buttoned
) {
1651 wRaiseFrame(menu
->frame
->core
);
1654 wMenuMapAt(menu
, 0, 0, True
);
1656 wMenuMapCopyAt(menu
, x
- menu
->frame
->core
->width
/ 2, y
);
1661 definition
= w_global
.domain
.root_menu
->dictionary
;
1664 definition = PLGetDomain(domain);
1667 if (WMIsPLArray(definition
)) {
1668 if (!scr
->root_menu
|| w_global
.domain
.root_menu
->timestamp
> scr
->root_menu
->timestamp
) {
1669 menu
= configureMenu(scr
, definition
);
1671 menu
->timestamp
= w_global
.domain
.root_menu
->timestamp
;
1676 menu
= configureMenu(scr
, definition
);
1681 /* menu hasn't changed or could not be read */
1682 if (!scr
->root_menu
) {
1683 wMessageDialog(scr
, _("Error"),
1684 _("The applications menu could not be loaded. "
1685 "Look at the console output for a detailed "
1686 "description of the errors."), _("OK"), NULL
, NULL
);
1688 menu
= makeDefaultMenu(scr
);
1689 scr
->root_menu
= menu
;
1691 menu
= scr
->root_menu
;
1694 if (scr
->root_menu
) {
1695 wMenuDestroy(scr
->root_menu
, True
);
1697 scr
->root_menu
= menu
;
1702 if (keyboard
&& x
== 0 && y
== 0) {
1704 } else if (keyboard
&& x
== scr
->scr_width
/ 2 && y
== scr
->scr_height
/ 2) {
1705 newx
= x
- menu
->frame
->core
->width
/ 2;
1706 newy
= y
- menu
->frame
->core
->height
/ 2;
1708 newx
= x
- menu
->frame
->core
->width
/ 2;
1711 wMenuMapAt(menu
, newx
, newy
, keyboard
);
1714 if (scr
->flags
.root_menu_changed_shortcuts
)
1715 rebindKeygrabs(scr
);