diff options
Diffstat (limited to 'src/xmenu.c')
-rw-r--r-- | src/xmenu.c | 711 |
1 files changed, 625 insertions, 86 deletions
diff --git a/src/xmenu.c b/src/xmenu.c index 3935307519f..1452b3c6d12 100644 --- a/src/xmenu.c +++ b/src/xmenu.c @@ -1,8 +1,12 @@ /* X Communication module for terminals which understand the X protocol. -Copyright (C) 1986, 1988, 1993-1994, 1996, 1999-2017 Free Software +Copyright (C) 1986, 1988, 1993-1994, 1996, 1999-2022 Free Software Foundation, Inc. +Author: Jon Arnold + Roman Budzianowski + Robert Krawitz + This file is part of GNU Emacs. GNU Emacs is free software: you can redistribute it and/or modify @@ -20,9 +24,6 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ /* X pop-up deck-of-cards menu facility for GNU Emacs. * - * Written by Jon Arnold and Roman Budzianowski - * Mods and rewrite by Robert Krawitz - * */ /* Modified by Fred Pierresteguy on December 93 @@ -44,11 +45,17 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ #include "buffer.h" #include "coding.h" #include "sysselect.h" +#include "pdumper.h" #ifdef MSDOS #include "msdos.h" #endif +#ifdef HAVE_XINPUT2 +#include <math.h> +#include <X11/extensions/XInput2.h> +#endif + #ifdef HAVE_X_WINDOWS /* This may include sys/types.h, and that somehow loses if this is not done before the other system files. */ @@ -103,7 +110,11 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ /* Flag which when set indicates a dialog or menu has been posted by Xt on behalf of one of the widget sets. */ +#ifndef HAVE_XINPUT2 static int popup_activated_flag; +#else +int popup_activated_flag; +#endif #ifdef USE_X_TOOLKIT @@ -142,7 +153,7 @@ x_menu_set_in_use (bool in_use) { Lisp_Object frames, frame; - menu_items_inuse = in_use ? Qt : Qnil; + menu_items_inuse = in_use; popup_activated_flag = in_use; #ifdef USE_X_TOOLKIT if (popup_activated_flag) @@ -173,8 +184,8 @@ x_menu_wait_for_event (void *data) instead of the small ifdefs below. */ while ( -#ifdef USE_X_TOOLKIT - ! XtAppPending (Xt_app_con) +#if defined USE_X_TOOLKIT + ! (data ? XPending (data) : XtAppPending (Xt_app_con)) #elif defined USE_GTK ! gtk_events_pending () #else @@ -187,6 +198,10 @@ x_menu_wait_for_event (void *data) struct x_display_info *dpyinfo; int n = 0; + /* ISTM that if timer_check is okay, this should be too, since + both can run random Lisp. */ + x_handle_pending_selection_requests (); + FD_ZERO (&read_fds); for (dpyinfo = x_display_list; dpyinfo; dpyinfo = dpyinfo->next) { @@ -211,6 +226,80 @@ x_menu_wait_for_event (void *data) #endif } } + +#if !defined USE_GTK && !defined USE_X_TOOLKIT && defined HAVE_XINPUT2 +static void +x_menu_translate_generic_event (XEvent *event) +{ + struct x_display_info *dpyinfo; + struct xi_device_t *device; + XEvent copy; + XIDeviceEvent *xev; + + dpyinfo = x_display_info_for_display (event->xgeneric.display); + + if (event->xgeneric.extension == dpyinfo->xi2_opcode) + { + eassert (!event->xcookie.data); + + switch (event->xcookie.evtype) + { + case XI_ButtonPress: + case XI_ButtonRelease: + + if (!XGetEventData (dpyinfo->display, &event->xcookie)) + break; + + xev = (XIDeviceEvent *) event->xcookie.data; + copy.xbutton.type = (event->xcookie.evtype == XI_ButtonPress + ? ButtonPress : ButtonRelease); + copy.xbutton.serial = xev->serial; + copy.xbutton.send_event = xev->send_event; + copy.xbutton.display = dpyinfo->display; + copy.xbutton.window = xev->event; + copy.xbutton.root = xev->root; + copy.xbutton.subwindow = xev->child; + copy.xbutton.time = xev->time; + copy.xbutton.x = lrint (xev->event_x); + copy.xbutton.y = lrint (xev->event_y); + copy.xbutton.x_root = lrint (xev->root_x); + copy.xbutton.y_root = lrint (xev->root_y); + copy.xbutton.state = xi_convert_event_state (xev); + copy.xbutton.button = xev->detail; + copy.xbutton.same_screen = True; + + device = xi_device_from_id (dpyinfo, xev->deviceid); + + /* I don't know the repercussions of changing + device->grab on XI_ButtonPress events, so be safe and + only do what is necessary to prevent the grab from + being left invalid as XMenuActivate swallows + events. */ + if (device && xev->evtype == XI_ButtonRelease) + device->grab &= ~(1 << xev->detail); + + XPutBackEvent (dpyinfo->display, ©); + XFreeEventData (dpyinfo->display, &event->xcookie); + + break; + + case XI_HierarchyChanged: + case XI_DeviceChanged: + /* These events must always be handled. */ + x_dispatch_event (event, dpyinfo->display); + break; + } + } +} +#endif + +#if !defined USE_X_TOOLKIT && !defined USE_GTK +static void +x_menu_expose_event (XEvent *event) +{ + x_dispatch_event (event, event->xexpose.display); +} +#endif #endif /* ! MSDOS */ @@ -230,18 +319,25 @@ popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo, LWLIB_ID id, bool do_timers) { XEvent event; + XEvent copy; +#ifdef HAVE_XINPUT2 + bool cookie_claimed_p = false; + XIDeviceEvent *xev; + struct xi_device_t *device; +#endif while (popup_activated_flag) { if (initial_event) { - event = *initial_event; + copy = event = *initial_event; initial_event = 0; } else { if (do_timers) x_menu_wait_for_event (0); XtAppNextEvent (Xt_app_con, &event); + copy = event; } /* Make sure we don't consider buttons grabbed after menu goes. @@ -261,6 +357,7 @@ popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo, so Motif thinks this is the case. */ event.xbutton.state = 0; #endif + copy = event; } /* Pop down on C-g and Escape. */ else if (event.type == KeyPress @@ -271,28 +368,117 @@ popup_get_selection (XEvent *initial_event, struct x_display_info *dpyinfo, if ((keysym == XK_g && (event.xkey.state & ControlMask) != 0) || keysym == XK_Escape) /* Any escape, ignore modifiers. */ popup_activated_flag = 0; + + copy = event; } +#ifdef HAVE_XINPUT2 + else if (event.type == GenericEvent + && dpyinfo->supports_xi2 + && event.xgeneric.display == dpyinfo->display + && event.xgeneric.extension == dpyinfo->xi2_opcode) + { + if (!event.xcookie.data + && XGetEventData (dpyinfo->display, &event.xcookie)) + cookie_claimed_p = true; + + if (event.xcookie.data) + { + switch (event.xgeneric.evtype) + { + case XI_ButtonRelease: + { + xev = (XIDeviceEvent *) event.xcookie.data; + device = xi_device_from_id (dpyinfo, xev->deviceid); + + dpyinfo->grabbed &= ~(1 << xev->detail); + device->grab &= ~(1 << xev->detail); + + copy.xbutton.type = ButtonRelease; + copy.xbutton.serial = xev->serial; + copy.xbutton.send_event = xev->send_event; + copy.xbutton.display = dpyinfo->display; + copy.xbutton.window = xev->event; + copy.xbutton.root = xev->root; + copy.xbutton.subwindow = xev->child; + copy.xbutton.time = xev->time; + copy.xbutton.x = lrint (xev->event_x); + copy.xbutton.y = lrint (xev->event_y); + copy.xbutton.x_root = lrint (xev->root_x); + copy.xbutton.y_root = lrint (xev->root_y); + copy.xbutton.state = xi_convert_event_state (xev); + copy.xbutton.button = xev->detail; + copy.xbutton.same_screen = True; + +#ifdef USE_MOTIF /* Pretending that the event came from a + Btn1Down seems the only way to convince Motif to + activate its callbacks; setting the XmNmenuPost + isn't working. --marcus@sysc.pdx.edu. */ + copy.xbutton.button = 1; + /* Motif only pops down menus when no Ctrl, Alt or Mod + key is pressed and the button is released. So reset key state + so Motif thinks this is the case. */ + copy.xbutton.state = 0; +#endif - x_dispatch_event (&event, event.xany.display); + break; + } + case XI_KeyPress: + { + KeySym keysym; + + xev = (XIDeviceEvent *) event.xcookie.data; + + copy.xkey.type = KeyPress; + copy.xkey.serial = xev->serial; + copy.xkey.send_event = xev->send_event; + copy.xkey.display = dpyinfo->display; + copy.xkey.window = xev->event; + copy.xkey.root = xev->root; + copy.xkey.subwindow = xev->child; + copy.xkey.time = xev->time; + copy.xkey.x = lrint (xev->event_x); + copy.xkey.y = lrint (xev->event_y); + copy.xkey.x_root = lrint (xev->root_x); + copy.xkey.y_root = lrint (xev->root_y); + copy.xkey.state = xi_convert_event_state (xev); + copy.xkey.keycode = xev->detail; + copy.xkey.same_screen = True; + + keysym = XLookupKeysym (©.xkey, 0); + + if ((keysym == XK_g + && (copy.xkey.state & ControlMask) != 0) + || keysym == XK_Escape) /* Any escape, ignore modifiers. */ + popup_activated_flag = 0; + + break; + } + } + } + } + + if (cookie_claimed_p) + XFreeEventData (dpyinfo->display, &event.xcookie); +#endif + + x_dispatch_event (©, copy.xany.display); } } DEFUN ("x-menu-bar-open-internal", Fx_menu_bar_open_internal, Sx_menu_bar_open_internal, 0, 1, "i", - doc: /* Start key navigation of the menu bar in FRAME. -This initially opens the first menu bar item and you can then navigate with the -arrow keys, select a menu entry with the return key or cancel with the -escape key. If FRAME has no menu bar this function does nothing. - -If FRAME is nil or not given, use the selected frame. */) + doc: /* SKIP: real doc in USE_GTK definition in xmenu.c. */) (Lisp_Object frame) { XEvent ev; struct frame *f = decode_window_system_frame (frame); +#if defined USE_X_TOOLKIT && defined HAVE_XINPUT2 + struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); +#endif Widget menubar; block_input (); if (FRAME_EXTERNAL_MENU_BAR (f)) - set_frame_menubar (f, false, true); + set_frame_menubar (f, true); menubar = FRAME_X_OUTPUT (f)->menubar_widget; if (menubar) @@ -300,12 +486,44 @@ If FRAME is nil or not given, use the selected frame. */) Window child; bool error_p = false; +#if defined USE_X_TOOLKIT && defined HAVE_XINPUT2 + /* Clear the XI2 grab so Motif or lwlib can set a core grab. + Otherwise some versions of Motif will emit a warning and hang, + and lwlib will fail to destroy the menu window. */ + + if (dpyinfo->supports_xi2 + && xi_frame_selected_for (f, XI_ButtonPress)) + { + for (int i = 0; i < dpyinfo->num_devices; ++i) + { + /* The keyboard grab matters too, in this specific + case. */ +#ifndef USE_LUCID + if (dpyinfo->devices[i].grab) +#endif + { + XIUngrabDevice (dpyinfo->display, + dpyinfo->devices[i].device_id, + CurrentTime); + dpyinfo->devices[i].grab = 0; + } + } + } +#endif + x_catch_errors (FRAME_X_DISPLAY (f)); memset (&ev, 0, sizeof ev); ev.xbutton.display = FRAME_X_DISPLAY (f); ev.xbutton.window = XtWindow (menubar); ev.xbutton.root = FRAME_DISPLAY_INFO (f)->root_window; +#ifndef HAVE_XINPUT2 ev.xbutton.time = XtLastTimestampProcessed (FRAME_X_DISPLAY (f)); +#else + ev.xbutton.time = ((dpyinfo->supports_xi2 + && xi_frame_selected_for (f, XI_KeyPress)) + ? dpyinfo->last_user_time + : XtLastTimestampProcessed (dpyinfo->display)); +#endif ev.xbutton.button = Button1; ev.xbutton.x = ev.xbutton.y = FRAME_MENUBAR_HEIGHT (f) / 2; ev.xbutton.same_screen = True; @@ -371,7 +589,7 @@ If FRAME is nil or not given, use the selected frame. */) f = decode_window_system_frame (frame); if (FRAME_EXTERNAL_MENU_BAR (f)) - set_frame_menubar (f, false, true); + set_frame_menubar (f, true); menubar = FRAME_X_OUTPUT (f)->menubar_widget; if (menubar) @@ -436,14 +654,35 @@ x_activate_menubar (struct frame *f) return; #endif - set_frame_menubar (f, false, true); + set_frame_menubar (f, true); block_input (); popup_activated_flag = 1; #ifdef USE_GTK XPutBackEvent (f->output_data.x->display_info->display, f->output_data.x->saved_menu_event); #else - XtDispatchEvent (f->output_data.x->saved_menu_event); +#if defined USE_X_TOOLKIT && defined HAVE_XINPUT2 + struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + /* Clear the XI2 grab so Motif or lwlib can set a core grab. + Otherwise some versions of Motif will emit a warning and hang, + and lwlib will fail to destroy the menu window. */ + + if (dpyinfo->supports_xi2 + && xi_frame_selected_for (f, XI_ButtonPress)) + { + for (int i = 0; i < dpyinfo->num_devices; ++i) + { + if (dpyinfo->devices[i].grab) + XIUngrabDevice (dpyinfo->display, + dpyinfo->devices[i].device_id, + CurrentTime); + } + } +#endif + /* The cascade button might have been deleted, so don't activate the + popup if it no widget was found to dispatch to. */ + popup_activated_flag + = XtDispatchEvent (f->output_data.x->saved_menu_event); #endif unblock_input (); @@ -644,7 +883,7 @@ update_frame_menubar (struct frame *f) lw_refigure_widget (x->column_widget, True); /* Force the pane widget to resize itself. */ - adjust_frame_size (f, -1, -1, 2, false, Qupdate_frame_menubar); + adjust_frame_size (f, -1, -1, 2, false, Qmenu_bar_lines); unblock_input (); #endif /* USE_GTK */ } @@ -680,12 +919,10 @@ apply_systemfont_to_menu (struct frame *f, Widget w) #endif -/* Set the contents of the menubar widgets of frame F. - The argument FIRST_TIME is currently ignored; - it is set the first time this is called, from initialize_frame_menubar. */ +/* Set the contents of the menubar widgets of frame F. */ void -set_frame_menubar (struct frame *f, bool first_time, bool deep_p) +set_frame_menubar (struct frame *f, bool deep_p) { xt_or_gtk_widget menubar_widget, old_widget; #ifdef USE_X_TOOLKIT @@ -726,7 +963,7 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) struct buffer *prev = current_buffer; Lisp_Object buffer; - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); int previous_menu_items_used = f->menu_bar_items_used; Lisp_Object *previous_items = alloca (previous_menu_items_used * sizeof *previous_items); @@ -757,8 +994,6 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) /* If it has changed current-menubar from previous value, really recompute the menubar from the value. */ - if (! NILP (Vlucid_menu_bar_dirty_flag)) - call0 (Qrecompute_lucid_menubar); safe_run_hooks (Qmenu_bar_update_hook); fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); @@ -766,7 +1001,7 @@ set_frame_menubar (struct frame *f, bool first_time, bool deep_p) /* Save the frame's previous menu bar contents data. */ if (previous_menu_items_used) - memcpy (previous_items, XVECTOR (f->menu_bar_vector)->contents, + memcpy (previous_items, xvector_contents (f->menu_bar_vector), previous_menu_items_used * word_size); /* Fill in menu_items with the current menu bar contents. @@ -1032,7 +1267,7 @@ initialize_frame_menubar (struct frame *f) /* This function is called before the first chance to redisplay the frame. It has to be, so the frame will have the right size. */ fset_menu_bar_items (f, menu_bar_items (FRAME_MENU_BAR_ITEMS (f))); - set_frame_menubar (f, true, true); + set_frame_menubar (f, true); } @@ -1049,6 +1284,7 @@ free_frame_menubar (struct frame *f) /* Motif automatically shrinks the frame in lw_destroy_all_widgets. If we want to preserve the old height, calculate it now so we can restore it below. */ + int old_width = FRAME_TEXT_WIDTH (f); int old_height = FRAME_TEXT_HEIGHT (f) + FRAME_MENUBAR_HEIGHT (f); #endif @@ -1082,26 +1318,43 @@ free_frame_menubar (struct frame *f) lw_destroy_all_widgets ((LWLIB_ID) f->output_data.x->id); f->output_data.x->menubar_widget = NULL; + /* When double-buffering is enabled and the frame shall not be + resized either because resizing is inhibited or the frame is + fullheight, some (usually harmless) display artifacts like a + doubled mode line may show up. Sometimes the configuration + gets messed up in a more serious fashion though and you may + have to resize the frame to get it back in a normal state. */ if (f->output_data.x->widget) { #ifdef USE_MOTIF XtVaGetValues (f->output_data.x->widget, XtNx, &x1, XtNy, &y1, NULL); if (x1 == 0 && y1 == 0) XtVaSetValues (f->output_data.x->widget, XtNx, x0, XtNy, y0, NULL); - if (frame_inhibit_resize (f, false, Qmenu_bar_lines)) - adjust_frame_size (f, -1, old_height, 1, false, Qfree_frame_menubar_1); + /* When resizing is inhibited and a normal Motif frame is not + fullheight, we have to explicitly request its old sizes + here since otherwise turning off the menu bar will shrink + the frame but turning them on again will not resize it + back. For a fullheight frame we let the window manager + deal with this problem. */ + if (frame_inhibit_resize (f, false, Qmenu_bar_lines) + && !EQ (get_frame_param (f, Qfullscreen), Qfullheight)) + adjust_frame_size (f, old_width, old_height, 1, false, + Qmenu_bar_lines); else - adjust_frame_size (f, -1, -1, 2, false, Qfree_frame_menubar_1); + adjust_frame_size (f, -1, -1, 2, false, Qmenu_bar_lines); #else - adjust_frame_size (f, -1, -1, 2, false, Qfree_frame_menubar_1); + adjust_frame_size (f, -1, -1, 2, false, Qmenu_bar_lines); #endif /* USE_MOTIF */ } else { #ifdef USE_MOTIF if (WINDOWP (FRAME_ROOT_WINDOW (f)) - && frame_inhibit_resize (f, false, Qmenu_bar_lines)) - adjust_frame_size (f, -1, old_height, 1, false, Qfree_frame_menubar_2); + /* See comment above. */ + && frame_inhibit_resize (f, false, Qmenu_bar_lines) + && !EQ (get_frame_param (f, Qfullscreen), Qfullheight)) + adjust_frame_size (f, old_width, old_height, 1, false, + Qmenu_bar_lines); #endif } @@ -1162,26 +1415,32 @@ menu_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer GtkRequisition req; int max_x = -1; int max_y = -1; +#ifdef HAVE_GTK3 + int scale; +#endif Lisp_Object frame, workarea; XSETFRAME (frame, data->f); +#ifdef HAVE_GTK3 + scale = xg_get_scale (data->f); +#endif /* TODO: Get the monitor workarea directly without calculating other items in x-display-monitor-attributes-list. */ workarea = call3 (Qframe_monitor_workarea, Qnil, - make_number (data->x), - make_number (data->y)); + make_fixnum (data->x), + make_fixnum (data->y)); if (CONSP (workarea)) { int min_x, min_y; - min_x = XINT (XCAR (workarea)); - min_y = XINT (Fnth (make_number (1), workarea)); - max_x = min_x + XINT (Fnth (make_number (2), workarea)); - max_y = min_y + XINT (Fnth (make_number (3), workarea)); + min_x = XFIXNUM (XCAR (workarea)); + min_y = XFIXNUM (Fnth (make_fixnum (1), workarea)); + max_x = min_x + XFIXNUM (Fnth (make_fixnum (2), workarea)); + max_y = min_y + XFIXNUM (Fnth (make_fixnum (3), workarea)); } if (max_x < 0 || max_y < 0) @@ -1192,11 +1451,20 @@ menu_position_func (GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer max_y = x_display_pixel_height (dpyinfo); } + /* frame-monitor-workarea and {x,y}_display_pixel_width/height all + return device pixels, but GTK wants scaled pixels. The positions + passed in via data were already scaled for us. */ +#ifdef HAVE_GTK3 + max_x /= scale; + max_y /= scale; +#endif *x = data->x; *y = data->y; /* Check if there is room for the menu. If not, adjust x/y so that - the menu is fully visible. */ + the menu is fully visible. gtk_widget_get_preferred_size returns + scaled pixels, so there is no need to apply the scaling + factor. */ gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &req); if (data->x + req.width > max_x) *x -= data->x + req.width - max_x; @@ -1233,7 +1501,7 @@ create_and_show_popup_menu (struct frame *f, widget_value *first_wv, GtkWidget *menu; GtkMenuPositionFunc pos_func = 0; /* Pop up at pointer. */ struct next_popup_x_y popup_x_y; - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); bool use_pos_func = ! for_click; #ifdef HAVE_GTK3 @@ -1293,6 +1561,26 @@ create_and_show_popup_menu (struct frame *f, widget_value *first_wv, if (i == 5) i = 0; } +#if !defined HAVE_GTK3 && defined HAVE_XINPUT2 + if (FRAME_DISPLAY_INFO (f)->supports_xi2 + && xi_frame_selected_for (f, XI_ButtonPress)) + { + for (int i = 0; i < FRAME_DISPLAY_INFO (f)->num_devices; ++i) + { + if (FRAME_DISPLAY_INFO (f)->devices[i].grab) + { + FRAME_DISPLAY_INFO (f)->devices[i].grab = 0; + + XIUngrabDevice (FRAME_X_DISPLAY (f), + FRAME_DISPLAY_INFO (f)->devices[i].device_id, + CurrentTime); + } + } + } +#endif + + DEFER_SELECTIONS; + /* Display the menu. */ gtk_widget_show_all (menu); @@ -1335,6 +1623,84 @@ popup_selection_callback (Widget widget, LWLIB_ID id, XtPointer client_data) menu_item_selection = client_data; } + +#ifdef HAVE_XINPUT2 +static void +prepare_for_entry_into_toolkit_menu (struct frame *f) +{ + XIEventMask mask; + ptrdiff_t l = XIMaskLen (XI_LASTEVENT); + unsigned char *m; + Lisp_Object tail, frame; + struct x_display_info *dpyinfo; + + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (!dpyinfo->supports_xi2) + return; + + mask.mask = m = alloca (l); + memset (m, 0, l); + mask.mask_len = l; + + mask.deviceid = XIAllMasterDevices; + + XISetMask (m, XI_Motion); + XISetMask (m, XI_Enter); + XISetMask (m, XI_Leave); + + FOR_EACH_FRAME (tail, frame) + { + f = XFRAME (frame); + + if (FRAME_X_P (f) + && FRAME_DISPLAY_INFO (f) == dpyinfo + && !FRAME_TOOLTIP_P (f)) + XISelectEvents (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), + &mask, 1); + } +} + +static void +leave_toolkit_menu (void *data) +{ + XIEventMask mask; + ptrdiff_t l = XIMaskLen (XI_LASTEVENT); + unsigned char *m; + Lisp_Object tail, frame; + struct x_display_info *dpyinfo; + struct frame *f; + + dpyinfo = FRAME_DISPLAY_INFO ((struct frame *) data); + + if (!dpyinfo->supports_xi2) + return; + + mask.mask = m = alloca (l); + memset (m, 0, l); + mask.mask_len = l; + + mask.deviceid = XIAllMasterDevices; + + XISetMask (m, XI_ButtonPress); + XISetMask (m, XI_ButtonRelease); + XISetMask (m, XI_Motion); + XISetMask (m, XI_Enter); + XISetMask (m, XI_Leave); + + FOR_EACH_FRAME (tail, frame) + { + f = XFRAME (frame); + + if (FRAME_X_P (f) + && FRAME_DISPLAY_INFO (f) == dpyinfo + && !FRAME_TOOLTIP_P (f)) + XISelectEvents (FRAME_X_DISPLAY (f), FRAME_X_WINDOW (f), + &mask, 1); + } +} +#endif + /* ID is the LWLIB ID of the dialog box. */ static void @@ -1346,6 +1712,23 @@ pop_down_menu (int id) popup_activated_flag = 0; } +#if defined HAVE_XINPUT2 && defined USE_MOTIF +static Bool +server_timestamp_predicate (Display *display, + XEvent *xevent, + XPointer arg) +{ + XID *args = (XID *) arg; + + if (xevent->type == PropertyNotify + && xevent->xproperty.window == args[0] + && xevent->xproperty.atom == args[1]) + return True; + + return False; +} +#endif + /* Pop up the menu for frame F defined by FIRST_WV at X/Y and loop until the menu pops down. menu_item_selection will be set to the selection. */ @@ -1361,6 +1744,10 @@ create_and_show_popup_menu (struct frame *f, widget_value *first_wv, LWLIB_ID menu_id; Widget menu; Window dummy_window; +#if defined HAVE_XINPUT2 && defined USE_MOTIF + XEvent property_dummy; + Atom property_atom; +#endif eassert (FRAME_X_P (f)); @@ -1414,15 +1801,81 @@ create_and_show_popup_menu (struct frame *f, widget_value *first_wv, XtSetArg (av[ac], (char *) XtNgeometry, 0); ac++; XtSetValues (menu, av, ac); +#ifdef HAVE_XINPUT2 + struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + + /* Clear the XI2 grab, and if any XI2 grab was set, place a core + grab on the frame's edit widget. */ + if (dpyinfo->supports_xi2) + XGrabServer (dpyinfo->display); + + if (dpyinfo->supports_xi2 + && xi_frame_selected_for (f, XI_ButtonPress)) + { + for (int i = 0; i < dpyinfo->num_devices; ++i) + { + if (dpyinfo->devices[i].grab) + { + dpyinfo->devices[i].grab = 0; + + XIUngrabDevice (dpyinfo->display, + dpyinfo->devices[i].device_id, + CurrentTime); + } + } + } + +#ifdef USE_MOTIF + if (dpyinfo->supports_xi2) + { + /* Dispatch a PropertyNotify to Xt with the current server time. + Motif tries to set a grab with the timestamp of the last event + processed by Xt, but Xt doesn't consider GenericEvents, so the + timestamp is always less than the last grab time. */ + + property_atom = dpyinfo->Xatom_EMACS_SERVER_TIME_PROP; + + XChangeProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), + property_atom, XA_ATOM, 32, + PropModeReplace, (unsigned char *) &property_atom, 1); + + XIfEvent (dpyinfo->display, &property_dummy, server_timestamp_predicate, + (XPointer) &(XID[]) {FRAME_OUTER_WINDOW (f), property_atom}); + + XtDispatchEvent (&property_dummy); + } +#endif +#endif + +#ifdef HAVE_XINPUT2 + prepare_for_entry_into_toolkit_menu (f); + +#ifdef USE_LUCID + if (dpyinfo->supports_xi2) + x_mouse_leave (dpyinfo); +#endif +#endif /* Display the menu. */ lw_popup_menu (menu, &dummy); + +#ifdef HAVE_XINPUT2 + if (dpyinfo->supports_xi2) + XUngrabServer (dpyinfo->display); +#endif + popup_activated_flag = 1; + x_activate_timeout_atimer (); { - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); + + DEFER_SELECTIONS; record_unwind_protect_int (pop_down_menu, (int) menu_id); +#ifdef HAVE_XINPUT2 + record_unwind_protect_ptr (leave_toolkit_menu, f); +#endif /* Process events that apply to the menu. */ popup_get_selection (0, FRAME_DISPLAY_INFO (f), menu_id, true); @@ -1445,13 +1898,19 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, { int i; widget_value *wv, *save_wv = 0, *first_wv = 0, *prev_wv = 0; - widget_value **submenu_stack - = alloca (menu_items_used * sizeof *submenu_stack); - Lisp_Object *subprefix_stack - = alloca (menu_items_used * sizeof *subprefix_stack); + widget_value **submenu_stack; + Lisp_Object *subprefix_stack; int submenu_depth = 0; + specpdl_ref specpdl_count; + + USE_SAFE_ALLOCA; - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + submenu_stack = SAFE_ALLOCA (menu_items_used + * sizeof *submenu_stack); + subprefix_stack = SAFE_ALLOCA (menu_items_used + * sizeof *subprefix_stack); + + specpdl_count = SPECPDL_INDEX (); eassert (FRAME_X_P (f)); @@ -1460,6 +1919,7 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, if (menu_items_used <= MENU_ITEMS_PANE_LENGTH) { *error_name = "Empty menu"; + SAFE_FREE (); return Qnil; } @@ -1476,7 +1936,7 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, i = 0; while (i < menu_items_used) { - if (EQ (AREF (menu_items, i), Qnil)) + if (NILP (AREF (menu_items, i))) { submenu_stack[submenu_depth++] = save_wv; save_wv = prev_wv; @@ -1575,6 +2035,14 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, STRINGP (help) ? help : Qnil); if (prev_wv) prev_wv->next = wv; + else if (!save_wv) + { + /* This emacs_abort call pacifies gcc 11.2.1 when Emacs + is configured with --enable-gcc-warnings. FIXME: If + save_wv can be null, do something better; otherwise, + explain why save_wv cannot be null. */ + emacs_abort (); + } else save_wv->contents = wv; if (!NILP (descrip)) @@ -1645,7 +2113,7 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, i = 0; while (i < menu_items_used) { - if (EQ (AREF (menu_items, i), Qnil)) + if (NILP (AREF (menu_items, i))) { subprefix_stack[submenu_depth++] = prefix; prefix = entry; @@ -1684,6 +2152,8 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, entry = Fcons (subprefix_stack[j], entry); } unblock_input (); + + SAFE_FREE (); return entry; } i += MENU_ITEMS_ITEM_LENGTH; @@ -1698,6 +2168,8 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, } unblock_input (); + + SAFE_FREE (); return Qnil; } @@ -1730,7 +2202,9 @@ create_and_show_dialog (struct frame *f, widget_value *first_wv) if (menu) { - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); + + DEFER_SELECTIONS; record_unwind_protect_ptr (pop_down_menu, menu); /* Display the menu. */ @@ -1785,7 +2259,9 @@ create_and_show_dialog (struct frame *f, widget_value *first_wv) /* Process events that apply to the dialog box. Also handle timers. */ { - ptrdiff_t count = SPECPDL_INDEX (); + specpdl_ref count = SPECPDL_INDEX (); + + DEFER_SELECTIONS; /* xdialog_show_unwind is responsible for popping the dialog box down. */ @@ -1817,7 +2293,7 @@ x_dialog_show (struct frame *f, Lisp_Object title, /* Whether we've seen the boundary between left-hand elts and right-hand. */ bool boundary_seen = false; - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); eassert (FRAME_X_P (f)); @@ -1969,7 +2445,7 @@ xw_popup_dialog (struct frame *f, Lisp_Object header, Lisp_Object contents) Lisp_Object title; const char *error_name; Lisp_Object selection; - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); check_window_system (f); @@ -2032,16 +2508,27 @@ menu_help_callback (char const *help_string, int pane, int item) pane_name = first_item[MENU_ITEMS_ITEM_NAME]; /* (menu-item MENU-NAME PANE-NUMBER) */ - menu_object = list3 (Qmenu_item, pane_name, make_number (pane)); + menu_object = list3 (Qmenu_item, pane_name, make_fixnum (pane)); show_help_echo (help_string ? build_string (help_string) : Qnil, - Qnil, menu_object, make_number (item)); + Qnil, menu_object, make_fixnum (item)); } +struct pop_down_menu +{ + struct frame *frame; + XMenu *menu; +}; + static void -pop_down_menu (Lisp_Object arg) +pop_down_menu (void *arg) { - struct frame *f = XSAVE_POINTER (arg, 0); - XMenu *menu = XSAVE_POINTER (arg, 1); + struct pop_down_menu *data = arg; + struct frame *f = data->frame; + XMenu *menu = data->menu; +#ifdef HAVE_XINPUT2 + int i; + struct xi_device_t *device; +#endif block_input (); #ifndef MSDOS @@ -2061,6 +2548,17 @@ pop_down_menu (Lisp_Object arg) results, and it is a pain to ask which are actually held now. */ FRAME_DISPLAY_INFO (f)->grabbed = 0; +#ifdef HAVE_XINPUT2 + /* Likewise for XI grabs when the mouse is released on top of the + menu itself. */ + + for (i = 0; i < FRAME_DISPLAY_INFO (f)->num_devices; ++i) + { + device = &FRAME_DISPLAY_INFO (f)->devices[i]; + device->grab = 0; + } +#endif + #endif /* HAVE_X_WINDOWS */ unblock_input (); @@ -2071,6 +2569,9 @@ Lisp_Object x_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object title, const char **error_name) { +#ifdef HAVE_X_WINDOWS + Window dummy_window; +#endif Window root; XMenu *menu; int pane, selidx, lpane, status; @@ -2083,7 +2584,7 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, int maxwidth; int dummy_int; unsigned int dummy_uint; - ptrdiff_t specpdl_count = SPECPDL_INDEX (); + specpdl_ref specpdl_count = SPECPDL_INDEX (); eassert (FRAME_X_P (f) || FRAME_MSDOS_P (f)); @@ -2119,20 +2620,22 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, inhibit_garbage_collection (); #ifdef HAVE_X_WINDOWS - { - /* Adjust coordinates to relative to the outer (window manager) window. */ - int left_off, top_off; + XTranslateCoordinates (FRAME_X_DISPLAY (f), - x_real_pos_and_offsets (f, &left_off, NULL, &top_off, NULL, - NULL, NULL, NULL, NULL, NULL); + /* From-window, to-window. */ + FRAME_X_WINDOW (f), + FRAME_DISPLAY_INFO (f)->root_window, - x += left_off; - y += top_off; - } -#endif /* HAVE_X_WINDOWS */ + /* From-position, to-position. */ + x, y, &x, &y, + /* Child of win. */ + &dummy_window); +#else + /* MSDOS without X support. */ x += f->left_pos; y += f->top_pos; +#endif /* Create all the necessary panes and their items. */ maxwidth = maxlines = lines = i = 0; @@ -2240,18 +2743,18 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, y = max (y, 1); XMenuLocate (FRAME_X_DISPLAY (f), menu, 0, 0, x, y, &ulx, &uly, &width, &height); - if (ulx+width > dispwidth) + if (ulx + width > dispwidth) { x -= (ulx + width) - dispwidth; ulx = dispwidth - width; } - if (uly+height > dispheight) + if (uly + height > dispheight) { y -= (uly + height) - dispheight; uly = dispheight - height; } #ifndef HAVE_X_WINDOWS - if (FRAME_HAS_MINIBUF_P (f) && uly+height > dispheight - 1) + if (FRAME_HAS_MINIBUF_P (f) && uly + height > dispheight - 1) { /* Move the menu away of the echo area, to avoid overwriting the menu with help echo messages or vice versa. */ @@ -2275,8 +2778,8 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, /* If position was not given by a mouse click, adjust so upper left corner of the menu as a whole ends up at given coordinates. This is what x-popup-menu says in its documentation. */ - x += width/2; - y += 1.5*height/(maxlines+2); + x += width / 2; + y += 1.5 * height/ (maxlines + 2); } XMenuSetAEQ (menu, true); @@ -2284,14 +2787,41 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, pane = selidx = 0; #ifndef MSDOS + DEFER_SELECTIONS; + XMenuActivateSetWaitFunction (x_menu_wait_for_event, FRAME_X_DISPLAY (f)); + /* When the input extension is in use, the owner_events grab will + report extension events on frames, which the XMenu library does + not normally understand. */ +#ifdef HAVE_XINPUT2 + XMenuActivateSetTranslateFunction (x_menu_translate_generic_event); +#endif + XMenuActivateSetExposeFunction (x_menu_expose_event); #endif - record_unwind_protect (pop_down_menu, make_save_ptr_ptr (f, menu)); + record_unwind_protect_ptr (pop_down_menu, + &(struct pop_down_menu) {f, menu}); /* Help display under X won't work because XMenuActivate contains a loop that doesn't give Emacs a chance to process it. */ menu_help_frame = f; + +#ifdef HAVE_XINPUT2 + struct x_display_info *dpyinfo = FRAME_DISPLAY_INFO (f); + /* Clear the XI2 grab so a core grab can be set. */ + + if (dpyinfo->supports_xi2 + && xi_frame_selected_for (f, XI_ButtonPress)) + { + for (int i = 0; i < dpyinfo->num_devices; ++i) + { + if (dpyinfo->devices[i].grab) + XIUngrabDevice (dpyinfo->display, dpyinfo->devices[i].device_id, + CurrentTime); + } + } +#endif + status = XMenuActivate (FRAME_X_DISPLAY (f), menu, &pane, &selidx, x, y, ButtonReleaseMask, &datap, menu_help_callback); @@ -2356,8 +2886,7 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, return_entry: unblock_input (); - SAFE_FREE (); - return unbind_to (specpdl_count, entry); + return SAFE_FREE_UNBIND_TO (specpdl_count, entry); } #endif /* not USE_X_TOOLKIT */ @@ -2376,21 +2905,19 @@ popup_activated (void) /* The following is used by delayed window autoselection. */ DEFUN ("menu-or-popup-active-p", Fmenu_or_popup_active_p, Smenu_or_popup_active_p, 0, 0, 0, - doc: /* Return t if a menu or popup dialog is active. */) + doc: /* Return t if a menu or popup dialog is active. +\(On MS Windows, this refers to the selected frame.) */) (void) { return (popup_activated ()) ? Qt : Qnil; } + +static void syms_of_xmenu_for_pdumper (void); + void syms_of_xmenu (void) { -#ifdef USE_X_TOOLKIT - enum { WIDGET_ID_TICK_START = 1 << 16 }; - widget_id_tick = WIDGET_ID_TICK_START; - next_menubar_widget_id = 1; -#endif - DEFSYM (Qdebug_on_next_call, "debug-on-next-call"); defsubr (&Smenu_or_popup_active_p); @@ -2401,6 +2928,18 @@ syms_of_xmenu (void) #if defined (USE_GTK) || defined (USE_X_TOOLKIT) defsubr (&Sx_menu_bar_open_internal); Ffset (intern_c_string ("accelerate-menu"), - intern_c_string (Sx_menu_bar_open_internal.symbol_name)); + intern_c_string (Sx_menu_bar_open_internal.s.symbol_name)); +#endif + + pdumper_do_now_and_after_load (syms_of_xmenu_for_pdumper); +} + +static void +syms_of_xmenu_for_pdumper (void) +{ +#ifdef USE_X_TOOLKIT + enum { WIDGET_ID_TICK_START = 1 << 16 }; + widget_id_tick = WIDGET_ID_TICK_START; + next_menubar_widget_id = 1; #endif } |