From: Yasuhiro Matsumoto Date: Mon, 25 May 2026 17:08:59 +0000 (+0000) Subject: patch 9.2.0537: GTK4: mouse popup menu does not show up at mouse pointer X-Git-Tag: v9.2.0537^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b748f4b2f01ecb30140a1979c8a78a59a1a23629;p=thirdparty%2Fvim.git 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 Signed-off-by: Yasuhiro Matsumoto Signed-off-by: Christian Brabandt --- diff --git a/src/gui_gtk4.c b/src/gui_gtk4.c index 9cbffb3c9e..343b4f62da 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." 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 4ea4541942..f86259cc9e 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, /**/