summaryrefslogtreecommitdiff
path: root/src/xmenu.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/xmenu.c')
-rw-r--r--src/xmenu.c711
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, &copy);
+ 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 (&copy.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, 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
}