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 <[email protected]>
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 <[email protected]>
    Signed-off-by: Yasuhiro Matsumoto <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

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,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1wRZ7s-0068hn-Bq%40256bit.org.

Raspunde prin e-mail lui