patch 9.2.0537: GTK4: mouse popup menu does not show up at mouse pointer
Commit:
https://github.com/vim/vim/commit/b748f4b2f01ecb30140a1979c8a78a59a1a23629
Author: Yasuhiro Matsumoto <
matt...@gmail.com>
Date: Mon May 25 17:08:59 2026 +0000
patch 9.2.0537: GTK4: mouse popup menu does not show up at mouse pointer
Problem: GTK4: mouse popup menu does not show up at mouse pointer,
Drawing area blanks while popover is open
(lilydjwg, after v9.2.0501)
Solution: Query the pointer position, make the popover parented to the
surrounding GtkOverlay and build it from buttons instead of a
GMenu model(Yasuhiro Matsumoto)
fixes: #20255
closes: #20260
Co-Authored-by: Claude <
nor...@anthropic.com>
Signed-off-by: Yasuhiro Matsumoto <
matt...@gmail.com>
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c
index 9cbffb3c9..343b4f62d 100644
--- a/src/gui_gtk4.c
+++ b/src/gui_gtk4.c
@@ -2195,12 +2195,58 @@ gui_mch_set_foreground(void)
gtk_window_present(GTK_WINDOW(gui.mainwin));
}
+ static int
+query_pointer_pos(int *x, int *y)
+{
+ GtkNative *native;
+ GdkSurface *surface;
+ GdkDisplay *display;
+ GdkSeat *seat;
+ GdkDevice *pointer;
+ double sx, sy, nx, ny;
+ graphene_point_t src, dst;
+
+ if (gui.drawarea == NULL)
+ return FALSE;
+ native = gtk_widget_get_native(gui.drawarea);
+ if (native == NULL)
+ return FALSE;
+ surface = gtk_native_get_surface(native);
+ if (surface == NULL)
+ return FALSE;
+ display = gtk_widget_get_display(gui.drawarea);
+ if (display == NULL)
+ return FALSE;
+ seat = gdk_display_get_default_seat(display);
+ if (seat == NULL)
+ return FALSE;
+ pointer = gdk_seat_get_pointer(seat);
+ if (pointer == NULL)
+ return FALSE;
+
+ if (!gdk_surface_get_device_position(surface, pointer, &sx, &sy, NULL))
+ return FALSE;
+
+ gtk_native_get_surface_transform(native, &nx, &ny);
+ src.x = (float)(sx - nx);
+ src.y = (float)(sy - ny);
+ if (!gtk_widget_compute_point(GTK_WIDGET(native), gui.drawarea,
+ &src, &dst))
+ return FALSE;
+
+ *x = (int)dst.x;
+ *y = (int)dst.y;
+ return TRUE;
+}
+
void
gui_mch_getmouse(int *x, int *y)
{
- *x = 0;
- *y = 0;
- // GTK4: No reliable way to query pointer position synchronously.
+ if (!query_pointer_pos(x, y))
+ {
+ *x = 0;
+ *y = 0;
+ }
}
void
@@ -3706,20 +3752,128 @@ gui_mch_destroy_menu(vimmenu_T *menu)
popupmenu_closed_cb(GtkPopover *popover, gpointer data UNUSED)
{
gtk_widget_unparent(GTK_WIDGET(popover));
+ if (gui.drawarea != NULL)
+ gtk_widget_queue_draw(gui.drawarea);
+}
+
+typedef struct {
+ GtkPopover *popover;
+ vimmenu_T *menu;
+} popup_item_data_T;
+
+ static void
+popup_item_clicked_cb(GtkButton *button UNUSED, gpointer data)
+{
+ popup_item_data_T *d = data;
+
+ if (d->popover != NULL)
+ gtk_popover_popdown(d->popover);
+ if (d->menu != NULL)
+ {
+ gui_menu_cb(d->menu);
+ gui_mch_flush();
+ }
+}
+
+ static void
+popup_item_data_free(gpointer data, GClosure *closure UNUSED)
+{
+ g_free(data);
}
void
gui_mch_show_popupmenu(vimmenu_T *menu)
{
- GMenu *gmenu;
- GtkWidget *popover;
+ GtkWidget *popover;
+ GtkWidget *box;
+ GtkWidget *parent;
+ GdkRectangle rect;
+ vimmenu_T *child;
+ int mode;
+ int natural_width = 0;
- if (menu == NULL || menu->submenu_id == NULL)
+ if (menu == NULL || menu->children == NULL)
return;
- gmenu = (GMenu *)(gpointer)menu->submenu_id;
- popover = gtk_popover_menu_new_from_model(G_MENU_MODEL(gmenu));
- gtk_widget_set_parent(popover, gui.drawarea);
+ // Attach the popover to drawarea's parent (the GtkOverlay) rather than
+ // to drawarea itself. GtkDrawingArea is a leaf widget whose snapshot
+ // does not iterate children, and parenting a popover to it has been
+ // observed to leave the drawing area blank while the popover is open.
+ parent = gtk_widget_get_parent(gui.drawarea);
+ if (parent == NULL)
+ parent = gui.drawarea;
+
+ // Build the popover by hand instead of using gtk_popover_menu_new_from_model.
+ // GtkPopoverMenu relies on the "menu.<name>" action-group lookup walking up
+ // the parent chain, which has been observed to silently fail on some
+ // compositors when the popover is parented via gtk_widget_set_parent. Wiring
+ // each menu item to a plain "clicked" signal sidesteps that entirely.
+ popover = gtk_popover_new();
+ gtk_widget_set_parent(popover, parent);
+ gtk_popover_set_has_arrow(GTK_POPOVER(popover), FALSE);
+ gtk_popover_set_position(GTK_POPOVER(popover), GTK_POS_BOTTOM);
+ gtk_widget_add_css_class(popover, "menu");
+
+ box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ gtk_popover_set_child(GTK_POPOVER(popover), box);
+
+ mode = get_menu_mode_flag();
+
+ for (child = menu->children; child != NULL; child = child->next)
+ {
+ GtkWidget *item;
+ char_u *label;
+ popup_item_data_T *cb_data;
+
+ if (menu_is_separator(child->name))
+ {
+ item = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_append(GTK_BOX(box), item);
+ continue;
+ }
+
+ label = CONVERT_TO_UTF8(child->dname);
+ item = gtk_button_new_with_mnemonic(
+ label != NULL ? (const char *)label : "");
+ CONVERT_TO_UTF8_FREE(label);
+
+ gtk_widget_add_css_class(item, "flat");
+ gtk_widget_add_css_class(item, "model");
+ gtk_button_set_has_frame(GTK_BUTTON(item), FALSE);
+ gtk_widget_set_halign(item, GTK_ALIGN_FILL);
+ {
+ GtkWidget *btn_label = gtk_button_get_child(GTK_BUTTON(item));
+ if (GTK_IS_LABEL(btn_label))
+ gtk_label_set_xalign(GTK_LABEL(btn_label), 0.0);
+ }
+
+ if (!(child->modes & child->enabled & mode))
+ gtk_widget_set_sensitive(item, FALSE);
+
+ cb_data = g_new0(popup_item_data_T, 1);
+ cb_data->popover = GTK_POPOVER(popover);
+ cb_data->menu = child;
+ g_signal_connect_data(item, "clicked",
+ G_CALLBACK(popup_item_clicked_cb),
+ cb_data, popup_item_data_free, 0);
+
+ gtk_box_append(GTK_BOX(box), item);
+ }
+
+ if (!query_pointer_pos(&rect.x, &rect.y))
+ {
+ rect.x = 0;
+ rect.y = 0;
+ }
+ // GtkPopover with GTK_POS_BOTTOM centres horizontally on the pointing-to
+ // rectangle. Use the box's natural width so the popover's left edge ends
+ // up at the cursor (down-and-to-the-right of the pointer).
+ gtk_widget_measure(box, GTK_ORIENTATION_HORIZONTAL, -1,
+ NULL, &natural_width, NULL, NULL);
+ rect.width = natural_width > 0 ? natural_width : 1;
+ rect.height = 1;
+ gtk_popover_set_pointing_to(GTK_POPOVER(popover), &rect);
+
g_signal_connect(popover, "closed",
G_CALLBACK(popupmenu_closed_cb), NULL);
gtk_popover_popup(GTK_POPOVER(popover));
diff --git a/src/version.c b/src/version.c
index 4ea454194..f86259cc9 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 537,
/**/
536,
/**/