1 /* Copyright Massachusetts Institute of Technology 1985 */
2
3 /*
4
5 Copyright 1985, 1986, 1987 by the Massachusetts Institute of Technology
6
7 Permission to use, copy, modify, and distribute this
8 software and its documentation for any purpose and without
9 fee is hereby granted, provided that the above copyright
10 notice appear in all copies and that both that copyright
11 notice and this permission notice appear in supporting
12 documentation, and that the name of M.I.T. not be used in
13 advertising or publicity pertaining to distribution of the
14 software without specific, written prior permission.
15 M.I.T. makes no representations about the suitability of
16 this software for any purpose. It is provided "as is"
17 without express or implied warranty.
18
19 */
20
21
22
23 /*
24 Copyright (C) 2001-2023 Free Software Foundation, Inc.
25
26 This program is free software: you can redistribute it and/or modify
27 it under the terms of the GNU General Public License as published by
28 the Free Software Foundation, either version 3 of the License, or (at
29 your option) any later version.
30
31 This program is distributed in the hope that it will be useful,
32 but WITHOUT ANY WARRANTY; without even the implied warranty of
33 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 GNU General Public License for more details.
35
36 You should have received a copy of the GNU General Public License
37 along with this program. If not, see <https://www.gnu.org/licenses/>. */
38
39 /*
40 * XMenu: MIT Project Athena, X Window system menu package
41 *
42 * XMenuActivate - Maps a given menu to the display and activates
43 * the menu for user selection. The user is allowed to
44 * specify which pane and selection will be current,
45 * the X and Y location of the menu (relative to the
46 * parent window) and the mouse button event mask that
47 * will be used to identify a selection request.
48 *
49 * A menu selection is shown to be current by placing
50 * a highlight box around the selection as the mouse
51 * cursor enters its active region. Inactive selections
52 * will not be highlighted. As the mouse cursor moved
53 * from one menu pane to another menu pane the pane being
54 * entered is raised and made current and the pane being
55 * left is lowered.
56 *
57 * Anytime XMenuActivate returns, the p_num and
58 * s_num are left at their last known values (i.e.,
59 * the last known current pane and selection indices).
60 * The following are the defined return states:
61 *
62 * 1) If at any time an error occurs the data
63 * pointer is left untouched and XM_FAILURE
64 * is returned.
65 *
66 * 2) When a selection request is received (i.e.,
67 * when the specified mouse event occurs) the
68 * data pointer will be set to the data
69 * associated with the particular selection
70 * current at the time of the selection request
71 * and XM_SUCCESS is returned.
72 *
73 * 3) If no selection was current at the time a
74 * selection request is made the data pointer
75 * will be left untouched and XM_NO_SELECT will
76 * be returned.
77 *
78 * 4) If the selection that was current at the time
79 * a selection request is made is not an active
80 * selection the data pointer will be left
81 * untouched and XM_IA_SELECT will be returned.
82 *
83 * Since X processes events in an asynchronous manner
84 * it is likely that XMenuActivate will encounter
85 * a "foreign event" while it is executing. Foreign
86 * events are handled in one of three ways:
87 *
88 * 1) The event is discarded. This is the default
89 * mode and requires no action on the part of the
90 * application.
91 *
92 * 2) The application has identified an asynchronous
93 * event handler that will be called and the
94 * foreign event handed off to it. Note:
95 * AEQ mode disables this mode temporarily.
96 *
97 * 3) The application has enabled asynchronous event
98 * queuing mode. In this mode all foreign events
99 * will be queued up until XMenuActivate
100 * terminates; at which time they will be
101 * returned to the X event queue. As long as
102 * AEQ mode is enabled any asynchronous event
103 * handler as temporarily disabled.
104 *
105 * Any events encountered while taking down the menu
106 * (i.e., exposure events from occluded windows) will
107 * automatically be returned to the X event queue after
108 * XMenuActivate has cleaned the queue of any of its own
109 * events that are no longer needed.
110 *
111 * Author: Tony Della Fera, DEC
112 * March 12, 1986
113 *
114 */
115
116 #include "XMenuInt.h"
117 #include <X11/keysym.h>
118
119 /* For debug, set this to 0 to not grab the keyboard on menu popup */
120 int x_menu_grab_keyboard = 1;
121
122 static Wait_func wait_func;
123 static void* wait_data;
124 static Translate_func translate_func = NULL;
125
126 void
127 XMenuActivateSetWaitFunction (Wait_func func, void *data)
128 {
129 wait_func = func;
130 wait_data = data;
131 }
132
133 void
134 XMenuActivateSetTranslateFunction (Translate_func func)
135 {
136 translate_func = func;
137 }
138
139 int
140 XMenuActivate(
141 register Display *display, /* Display to put menu on. */
142 register XMenu *menu, /* Menu to activate. */
143 int *p_num, /* Pane number selected. */
144 int *s_num, /* Selection number selected. */
145 int x_pos, /* X coordinate of menu position. */
146 int y_pos, /* Y coordinate of menu position. */
147 unsigned int event_mask, /* Mouse button event mask. */
148 char **data, /* Pointer to return data value. */
149 void (*help_callback) (char const *, int, int)) /* Help callback. */
150 {
151 int status; /* X routine call status. */
152 int orig_x; /* Upper left menu origin X coord. */
153 int orig_y; /* Upper left menu origin Y coord. */
154 int ret_val; /* Return value. */
155
156 register XMPane *p_ptr; /* Current XMPane. */
157 register XMPane *event_xmp; /* Event XMPane pointer. */
158 register XMPane *cur_p; /* Current pane. */
159 register XMSelect *cur_s; /* Current selection. */
160 XMWindow *event_xmw; /* Event XMWindow pointer. */
161 XEvent event; /* X input event. */
162 XEvent peek_event; /* X input peek ahead event. */
163
164 Bool selection = False; /* Selection has been made. */
165 Bool forward = True; /* Moving forward in the pane list. */
166
167 Window root, child;
168 int root_x, root_y, win_x, win_y;
169 unsigned int mask;
170 KeySym keysym;
171
172 /*
173 * Define and allocate a foreign event queue to hold events
174 * that don't belong to XMenu. These events are later restored
175 * to the X event queue.
176 */
177 typedef struct _xmeventque {
178 XEvent event;
179 struct _xmeventque *next;
180 } XMEventQue;
181
182 XMEventQue *feq = NULL; /* Foreign event queue. */
183 XMEventQue *feq_tmp; /* Foreign event queue temporary. */
184
185 /*
186 * If there are no panes in the menu then return failure
187 * because the menu is not initialized.
188 */
189 if (menu->p_count == 0) {
190 _XMErrorCode = XME_NOT_INIT;
191 return(XM_FAILURE);
192 }
193
194 /*
195 * Find the desired current pane.
196 */
197 cur_p = _XMGetPanePtr(menu, *p_num);
198 if (cur_p == NULL) {
199 return(XM_FAILURE);
200 }
201 cur_p->activated = cur_p->active;
202
203 /*
204 * Find the desired current selection.
205 * If the current selection index is out of range a null current selection
206 * will be assumed and the cursor will be placed in the current pane
207 * header.
208 */
209 cur_s = _XMGetSelectionPtr(cur_p, *s_num);
210
211 /*
212 * Compute origin of menu so that cursor is in
213 * Correct pane and selection.
214 */
215 _XMTransToOrigin(display,
216 menu,
217 cur_p, cur_s,
218 x_pos, y_pos,
219 &orig_x, &orig_y);
220 menu->x_pos = orig_x; /* Store X and Y coords of menu. */
221 menu->y_pos = orig_y;
222
223 if (XMenuRecompute(display, menu) == XM_FAILURE) {
224 return(XM_FAILURE);
225 }
226
227 /*
228 * Flush the window creation queue.
229 * This batches all window creates since lazy evaluation
230 * is more efficient than individual evaluation.
231 * This routine also does an XFlush().
232 */
233 if (_XMWinQueFlush(display, menu, cur_p, cur_s) == _FAILURE) {
234 return(XM_FAILURE);
235 }
236
237 /*
238 * Make sure windows are in correct order (in case we were passed
239 * an already created menu in incorrect order.)
240 */
241 for(p_ptr = menu->p_list->next; p_ptr != cur_p; p_ptr = p_ptr->next)
242 XRaiseWindow(display, p_ptr->window);
243 for(p_ptr = menu->p_list->prev; p_ptr != cur_p->prev; p_ptr = p_ptr->prev)
244 XRaiseWindow(display, p_ptr->window);
245
246 /*
247 * Make sure all selection windows are mapped.
248 */
249 for (
250 p_ptr = menu->p_list->next;
251 p_ptr != menu->p_list;
252 p_ptr = p_ptr->next
253 ){
254 XMapSubwindows(display, p_ptr->window);
255 }
256
257 /*
258 * Synchronize the X buffers and the event queue.
259 * From here on, all events in the queue that don't belong to
260 * XMenu are sent back to the application via an application
261 * provided event handler or discarded if the application has
262 * not provided an event handler.
263 */
264 XSync(display, 0);
265
266 /*
267 * Grab the mouse for menu input.
268 */
269
270 status = XGrabPointer(
271 display,
272 menu->parent,
273 True,
274 event_mask,
275 GrabModeAsync,
276 GrabModeAsync,
277 None,
278 menu->mouse_cursor,
279 CurrentTime
280 );
281 if (status == Success && x_menu_grab_keyboard)
282 {
283 status = XGrabKeyboard (display,
284 menu->parent,
285 False,
286 GrabModeAsync,
287 GrabModeAsync,
288 CurrentTime);
289 if (status != Success)
290 XUngrabPointer(display, CurrentTime);
291 }
292
293 if (status == _X_FAILURE) {
294 _XMErrorCode = XME_GRAB_MOUSE;
295 return(XM_FAILURE);
296 }
297
298 /*
299 * Map the menu panes.
300 */
301 XMapWindow(display, cur_p->window);
302 for (p_ptr = menu->p_list->next;
303 p_ptr != cur_p;
304 p_ptr = p_ptr->next)
305 XMapWindow(display, p_ptr->window);
306 for (p_ptr = cur_p->next;
307 p_ptr != menu->p_list;
308 p_ptr = p_ptr->next)
309 XMapWindow(display, p_ptr->window);
310
311 XRaiseWindow(display, cur_p->window); /* Make sure current */
312 /* pane is on top. */
313
314 cur_s = NULL; /* Clear current selection. */
315
316 /*
317 * Begin event processing loop.
318 */
319 while (1) {
320 if (wait_func) (*wait_func) (wait_data);
321 XNextEvent(display, &event); /* Get next event. */
322 switch (event.type) { /* Dispatch on the event type. */
323 case Expose:
324 event_xmp = (XMPane *)XLookUpAssoc(display,
325 menu->assoc_tab,
326 event.xexpose.window);
327 if (event_xmp == NULL) {
328 /*
329 * If AEQ mode is enabled then queue the event.
330 */
331 if (menu->aeq) {
332 feq_tmp = (XMEventQue *)malloc(sizeof(XMEventQue));
333 if (feq_tmp == NULL) {
334 _XMErrorCode = XME_CALLOC;
335 return(XM_FAILURE);
336 }
337 feq_tmp->event = event;
338 feq_tmp->next = feq;
339 feq = feq_tmp;
340 }
341 else if (_XMEventHandler) (*_XMEventHandler)(&event);
342 break;
343 }
344 if (event_xmp->activated) {
345 XSetWindowBackground(display,
346 event_xmp->window,
347 menu->bkgnd_color);
348 }
349 else {
350 XSetWindowBackgroundPixmap(display,
351 event_xmp->window,
352 menu->inact_pixmap);
353 }
354 _XMRefreshPane(display, menu, event_xmp);
355 break;
356 case EnterNotify:
357 /*
358 * First wait a small period of time, and see
359 * if another EnterNotify event follows hard on the
360 * heels of this one. i.e., the user is simply
361 * "passing through". If so, ignore this one.
362 */
363
364 event_xmw = (XMWindow *)XLookUpAssoc(display,
365 menu->assoc_tab,
366 event.xcrossing.window);
367 if (event_xmw == NULL) break;
368 if (event_xmw->type == SELECTION) {
369 /*
370 * We have entered a selection.
371 */
372 /* if (XPending(display) == 0) usleep(150000); */
373 if (XPending(display) != 0) {
374 XPeekEvent(display, &peek_event);
375 if(peek_event.type == LeaveNotify) {
376 break;
377 }
378 }
379 cur_s = (XMSelect *)event_xmw;
380 help_callback (cur_s->help_string,
381 cur_p->serial, cur_s->serial);
382
383 /*
384 * If the pane we are in is active and the
385 * selection entered is active then activate
386 * the selection.
387 */
388 if (cur_p->active && cur_s->active > 0) {
389 cur_s->activated = 1;
390 _XMRefreshSelection(display, menu, cur_s);
391 }
392 }
393 else {
394 /*
395 * We have entered a pane.
396 */
397 /* if (XPending(display) == 0) usleep(150000); */
398 if (XPending(display) != 0) {
399 XPeekEvent(display, &peek_event);
400 if (peek_event.type == EnterNotify) break;
401 }
402 XQueryPointer(display,
403 menu->parent,
404 &root, &child,
405 &root_x, &root_y,
406 &win_x, &win_y,
407 &mask);
408 event_xmp = (XMPane *)XLookUpAssoc(display,
409 menu->assoc_tab,
410 child);
411 if (event_xmp == NULL) break;
412 if (event_xmp == cur_p) break;
413 if (event_xmp->serial > cur_p->serial) forward = True;
414 else forward = False;
415 p_ptr = cur_p;
416 while (p_ptr != event_xmp) {
417 if (forward) p_ptr = p_ptr->next;
418 else p_ptr = p_ptr->prev;
419 XRaiseWindow(display, p_ptr->window);
420 }
421 if (cur_p->activated) {
422 cur_p->activated = False;
423 XSetWindowBackgroundPixmap(display,
424 cur_p->window,
425 menu->inact_pixmap);
426 _XMRefreshPane(display, menu, cur_p);
427 }
428 if (event_xmp->active) event_xmp->activated = True;
429 #if 1
430 /*
431 * i suspect the we don't get an EXPOSE event when backing
432 * store is enabled; the menu windows content is probably
433 * not drawn in when it should be in that case.
434 * in that case, this is probably an ugly fix!
435 * i hope someone more familiar with this code would
436 * take it from here. -- caveh@eng.sun.com.
437 */
438 XSetWindowBackground(display,
439 event_xmp->window,
440 menu->bkgnd_color);
441 _XMRefreshPane(display, menu, event_xmp);
442 #endif
443 cur_p = event_xmp;
444 }
445 break;
446 case LeaveNotify:
447 event_xmw = (XMWindow *)XLookUpAssoc(
448 display,
449 menu->assoc_tab,
450 event.xcrossing.window
451 );
452 if (event_xmw == NULL) break;
453 if(cur_s == NULL) break;
454
455 /*
456 * If the current selection was activated then
457 * deactivate it.
458 */
459 /* Emacs specific, HELP_STRING cannot be validly NULL
460 * in the real XMenu library. */
461 help_callback (NULL, cur_p->serial, cur_s->serial);
462 if (cur_s->activated) {
463 cur_s->activated = False;
464 _XMRefreshSelection(display, menu, cur_s);
465 }
466 cur_s = NULL;
467 break;
468
469 case ButtonPress:
470 case ButtonRelease:
471 *p_num = cur_p->serial;
472 /*
473 * Check to see if there is a current selection.
474 */
475 if (cur_s != NULL) {
476 /*
477 * Set the selection number to the current selection.
478 */
479 *s_num = cur_s->serial;
480 /*
481 * If the current selection was activated then
482 * we have a valid selection otherwise we have
483 * an inactive selection.
484 */
485 if (cur_s->activated) {
486 *data = cur_s->data;
487 ret_val = XM_SUCCESS;
488 }
489 else {
490 ret_val = XM_IA_SELECT;
491 }
492 }
493 else {
494 /*
495 * No selection was current.
496 */
497 ret_val = XM_NO_SELECT;
498 }
499 selection = True;
500 break;
501 case KeyPress:
502 case KeyRelease:
503 keysym = XLookupKeysym (&event.xkey, 0);
504
505 /* Pop down on C-g and Escape. */
506 if ((keysym == XK_g && (event.xkey.state & ControlMask) != 0)
507 || keysym == XK_Escape) /* Any escape, ignore modifiers. */
508 {
509 ret_val = XM_NO_SELECT;
510 selection = True;
511 }
512 break;
513 default:
514 /*
515 * If AEQ mode is enabled then queue the event.
516 */
517 if (menu->aeq) {
518 feq_tmp = (XMEventQue *)malloc(sizeof(XMEventQue));
519 if (feq_tmp == NULL) {
520 _XMErrorCode = XME_CALLOC;
521 return(XM_FAILURE);
522 }
523 feq_tmp->event = event;
524 feq_tmp->next = feq;
525 feq = feq_tmp;
526 }
527 else if (_XMEventHandler) (*_XMEventHandler)(&event);
528 break;
529 #ifdef HAVE_XINPUT2
530 case GenericEvent:
531 if (translate_func)
532 translate_func (&event);
533 #endif
534 }
535 /*
536 * If a selection has been made, break out of the event loop.
537 */
538 if (selection == True) break;
539 }
540
541 /*
542 * Unmap the menu.
543 */
544 for ( p_ptr = menu->p_list->next;
545 p_ptr != menu->p_list;
546 p_ptr = p_ptr->next)
547 {
548 XUnmapWindow(display, p_ptr->window);
549 }
550
551 /*
552 * Ungrab the mouse.
553 */
554 XUngrabPointer(display, CurrentTime);
555 XUngrabKeyboard(display, CurrentTime);
556
557 /*
558 * Restore bits under where the menu was if we managed
559 * to save them and free the pixmap.
560 */
561
562 /*
563 * If there is a current selection deactivate it.
564 */
565 if (cur_s != NULL) cur_s->activated = 0;
566
567 /*
568 * Deactivate the current pane.
569 */
570 cur_p->activated = 0;
571 XSetWindowBackgroundPixmap(display, cur_p->window, menu->inact_pixmap);
572
573 /*
574 * Synchronize the X buffers and the X event queue.
575 */
576 XSync(display, 0);
577
578 /*
579 * Dispatch any events remaining on the queue.
580 */
581 while (QLength(display)) {
582 /*
583 * Fetch the next event.
584 */
585 XNextEvent(display, &event);
586
587 /*
588 * Discard any events left on the queue that belong to XMenu.
589 * All others are held and then returned to the event queue.
590 */
591 switch (event.type) {
592 case Expose:
593 case EnterNotify:
594 case LeaveNotify:
595 case ButtonPress:
596 case ButtonRelease:
597 /*
598 * Does this event belong to one of XMenu's windows?
599 * If so, discard it and process the next event.
600 * If not fall through and treat it as a foreign event.
601 */
602 event_xmp = (XMPane *)XLookUpAssoc(
603 display,
604 menu->assoc_tab,
605 event.xbutton.window
606 );
607 if (event_xmp != NULL) continue;
608 FALLTHROUGH;
609 default:
610 /*
611 * This is a foreign event.
612 * Queue it for later return to the X event queue.
613 */
614 feq_tmp = (XMEventQue *)malloc(sizeof(XMEventQue));
615 if (feq_tmp == NULL) {
616 _XMErrorCode = XME_CALLOC;
617 return(XM_FAILURE);
618 }
619 feq_tmp->event = event;
620 feq_tmp->next = feq;
621 feq = feq_tmp;
622 }
623 }
624 /*
625 * Return any foreign events that were queued to the X event queue.
626 */
627 while (feq != NULL) {
628 feq_tmp = feq;
629 XPutBackEvent(display, &feq_tmp->event);
630 feq = feq_tmp->next;
631 free((char *)feq_tmp);
632 }
633
634 wait_func = 0;
635
636 /*
637 * Return successfully.
638 */
639 _XMErrorCode = XME_NO_ERROR;
640 return(ret_val);
641
642 }