Crash when spawning a popover outside a monitor's workarea
While writing a panel program in GTK4, i've encountered a bug if i try to spawn a GtkPopoverMenu parented to the panel, which is outside the “work area” of the monitor as it is a panel/shell element.
The problem appears to come from gdk_x11_surface_layout_popup
defined in gdk/gdksurface-x11.c, this function tries to find wich monitor the surface is on by computing intersections between the surface and each monitor's work area, perhaps it should instead compute the intersection with the whole monitor area, as well as print out warnings and give up trying to display the surface if no such monitor has been found.
Steps to reproduce
- Reserve space on the side of a monitor using window hints
- Place a widget in that space
- Call gtk_popover_popup on a popover whose parent is that widget
Alternatively:
- Compile the attached file with
gcc $(pkg-config --libs --cflags x11 gtk4) gtk-crash.c
- Run the resulting executable in an X11 server running an EWMH compliant window manager
- Right click on the panel that appears at the top of (one of) the monitor(s)
Expected outcome
The popover should display at the correct location, or at the very least a warning should be produced and a crash avoided.
Version information
GTK4 4.2.0 on Void Linux
Backtrace
Thread 1 "gtk-crash" received signal SIGSEGV, Segmentation fault.
0x00007ffff7a226ac in gdk_x11_monitor_get_workarea (monitor=0x0, dest=0x7fffffffcc60)
at ../gdk/x11/gdkmonitor-x11.c:79
79 ../gdk/x11/gdkmonitor-x11.c: No such file or directory.
#0 0x00007ffff7a226ac in gdk_x11_monitor_get_workarea (monitor=0x0, dest=0x7fffffffcc60)
at ../gdk/x11/gdkmonitor-x11.c:79
#1 0x00007ffff7a2cbd2 in gdk_x11_surface_layout_popup (layout=0x555556578420, height=42,
width=54, surface=0x555555662450) at ../gdk/x11/gdksurface-x11.c:1825
#2 gdk_x11_surface_present_popup (layout=0x555556578420, height=42, width=54,
surface=0x555555662450) at ../gdk/x11/gdksurface-x11.c:1888
#3 gdk_x11_popup_present (popup=<optimized out>, width=54, height=42, layout=0x555556578420)
at ../gdk/x11/gdksurface-x11.c:4906
#4 0x00007ffff77f197e in present_popup (popover=popover@entry=0x555555ac4440)
at ../gtk/gtkpopover.c:588
#5 0x00007ffff77f1b7d in gtk_popover_show (widget=0x555555ac4440) at ../gtk/gtkpopover.c:983
#6 0x00007ffff70c8889 in ?? () from /usr/lib/libgobject-2.0.so.0
#7 0x00007ffff70e13cf in g_signal_emit_valist () from /usr/lib/libgobject-2.0.so.0
#8 0x00007ffff70e15af in g_signal_emit () from /usr/lib/libgobject-2.0.so.0
#9 0x00007ffff78e45b6 in gtk_widget_show (widget=0x555555ac4440) at ../gtk/gtkwidget.c:2744
#10 0x00005555555563a9 in on_right_click (gesture=0x555555598320, n_press=1, x=1058, y=0,
data=0x0) at gtk-crash.c:16
#11 0x00007ffff76ad817 in _gtk_marshal_VOID__INT_DOUBLE_DOUBLEv (closure=<optimized out>,
return_value=<optimized out>, instance=<optimized out>, args=<optimized out>,
marshal_data=<optimized out>, n_params=<optimized out>, param_types=0x5555556ca8e0)
at gtk/gtkmarshalers.c:5445
#12 0x00007ffff70c8889 in ?? () from /usr/lib/libgobject-2.0.so.0
#13 0x00007ffff70e13cf in g_signal_emit_valist () from /usr/lib/libgobject-2.0.so.0
#14 0x00007ffff70e15af in g_signal_emit () from /usr/lib/libgobject-2.0.so.0
#15 0x00007ffff7776526 in gtk_gesture_click_begin (gesture=0x555555598320,
sequence=<optimized out>) at ../gtk/gtkgestureclick.c:231
#16 0x00007ffff70cb8bb in g_cclosure_marshal_VOID__BOXEDv () from /usr/lib/libgobject-2.0.so.0
#17 0x00007ffff70c8889 in ?? () from /usr/lib/libgobject-2.0.so.0
#18 0x00007ffff70e13cf in g_signal_emit_valist () from /usr/lib/libgobject-2.0.so.0
#19 0x00007ffff70e15af in g_signal_emit () from /usr/lib/libgobject-2.0.so.0
#20 0x00007ffff77749b6 in _gtk_gesture_set_recognized (recognized=<optimized out>, sequence=0x0,
gesture=0x555555598320) at ../gtk/gtkgesture.c:337
#21 _gtk_gesture_set_recognized (sequence=0x0, recognized=1, gesture=0x555555598320)
at ../gtk/gtkgesture.c:323
#22 _gtk_gesture_check_recognized (gesture=gesture@entry=0x555555598320,
sequence=sequence@entry=0x0) at ../gtk/gtkgesture.c:383
#23 0x00007ffff77755e1 in gtk_gesture_handle_event (controller=<optimized out>,
event=<optimized out>, x=1058, y=0) at ../gtk/gtkgesture.c:642
#24 0x00007ffff77787ff in gtk_gesture_single_handle_event (controller=0x555555598320,
event=0x55555571ad40, x=1058, y=0) at ../gtk/gtkgesturesingle.c:227
#25 0x00007ffff78e58c3 in gtk_event_controller_handle_event (y=0, x=1058, target=0x55555576c280,
event=0x55555571ad40, controller=0x555555598320) at ../gtk/gtkeventcontroller.c:369
#26 gtk_widget_run_controllers (widget=widget@entry=0x55555576c280,
event=event@entry=0x55555571ad40, target=target@entry=0x55555576c280, x=1058, y=0,
phase=phase@entry=GTK_PHASE_BUBBLE) at ../gtk/gtkwidget.c:4572
#27 0x00007ffff78e5bf9 in gtk_widget_event (target=0x55555576c280, event=0x55555571ad40,
widget=0x55555576c280) at ../gtk/gtkwidget.c:4766
#28 gtk_widget_event (widget=0x55555576c280, event=0x55555571ad40, target=0x55555576c280)
at ../gtk/gtkwidget.c:4742
#29 0x00007ffff7aac8a6 in gtk_propagate_event_internal.isra.0 (widget=0x55555576c280,
event=0x55555571ad40, topmost=<optimized out>) at ../gtk/gtkmain.c:1880
#30 0x00007ffff77c2183 in gtk_main_do_event (event=<optimized out>) at ../gtk/gtkmain.c:1622
#31 0x00007ffff78f38f8 in surface_event () at ../gtk/gtkwindow.c:3556
#32 0x00007ffff79ca967 in _gdk_marshal_BOOLEAN__POINTER (closure=0x5555561eff10,
return_value=0x7fffffffdc50, n_param_values=<optimized out>, param_values=0x7fffffffdcb0,
invocation_hint=<optimized out>, marshal_data=<optimized out>) at gdk/gdkmarshalers.c:258
#33 0x00007ffff79f0f8f in gdk_surface_event_marshaller (closure=0x5555561eff10,
return_value=0x7fffffffdc50, n_param_values=2, param_values=0x7fffffffdcb0,
invocation_hint=0x7fffffffdc30, marshal_data=0x0) at ../gdk/gdksurface.c:423
#34 0x00007ffff70c865f in g_closure_invoke () from /usr/lib/libgobject-2.0.so.0
#35 0x00007ffff70daccb in ?? () from /usr/lib/libgobject-2.0.so.0
#36 0x00007ffff70e0d96 in g_signal_emit_valist () from /usr/lib/libgobject-2.0.so.0
#37 0x00007ffff70e15af in g_signal_emit () from /usr/lib/libgobject-2.0.so.0
#38 0x00007ffff7aa7456 in gdk_surface_handle_event.isra.0 (event=0x55555571ad40)
at ../gdk/gdksurface.c:2944
#39 0x00007ffff7a3ec8a in gdk_event_source_dispatch (source=<optimized out>,
callback=<optimized out>, user_data=<optimized out>) at ../gdk/x11/gdkeventsource.c:424
#40 0x00007ffff6fd267b in g_main_context_dispatch () from /usr/lib/libglib-2.0.so.0
#41 0x00007ffff6fd2928 in ?? () from /usr/lib/libglib-2.0.so.0
#42 0x00007ffff6fd29df in g_main_context_iteration () from /usr/lib/libglib-2.0.so.0
#43 0x00007ffff71ee2a5 in g_application_run () from /usr/lib/libgio-2.0.so.0
#44 0x00005555555567b7 in main (argc=1, argv=0x7fffffffe218) at gtk-crash.c:84
[gtk-crash.c](/uploads/758bcf65e7031c2fe86e2c89dbafac18/gtk-crash.c)[gtk-crash.c](/uploads/29c44de5c9b0f0b9db73156f60b15aa8/gtk-crash.c)
Minimal demonstration of the bug
#include <gtk/gtk.h>
#include <gdk/x11/gdkx.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#define PANEL_SIZE 24
GtkWidget *popover_menu = NULL;
static void on_right_click(GtkGestureClick *gesture,
int n_press,
double x, double y,
gpointer data) {
gtk_popover_popup(GTK_POPOVER(popover_menu));
}
static void add_context_menu(GtkWidget *window) {
GMenu *menu = g_menu_new();
popover_menu = gtk_popover_menu_new_from_model(G_MENU_MODEL(menu));
gtk_widget_set_parent(popover_menu, window);
GtkGesture *gesture = gtk_gesture_click_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(gesture), GDK_BUTTON_SECONDARY);
g_signal_connect(gesture, "pressed",
G_CALLBACK(on_right_click), NULL);
gtk_widget_add_controller(window, GTK_EVENT_CONTROLLER(gesture));
}
static void activate(GtkApplication *app, gpointer data) {
GtkWidget *window = gtk_application_window_new(app);
gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
gtk_window_present(GTK_WINDOW(window));
GdkSurface *surface = gtk_native_get_surface(GTK_NATIVE(window));
GdkDisplay *display = gdk_surface_get_display(surface);
Display *x11_display = gdk_x11_display_get_xdisplay(display);
Window x11_window = gdk_x11_surface_get_xid(surface);
GListModel *monitors = gdk_display_get_monitors(display);
GdkMonitor *monitor = g_list_model_get_item(monitors, 0);
GdkRectangle monitor_rect;
gdk_monitor_get_geometry(monitor, &monitor_rect);
/* Set the window type to DOCK */
Atom window_type_dock_atom = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_WINDOW_TYPE_DOCK");
XChangeProperty(x11_display, x11_window,
gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_WINDOW_TYPE"),
XA_ATOM, 32, PropModeReplace, (unsigned char *) &window_type_dock_atom, 1);
/* Reserve space on the bottom of the monitor */
gulong struts[] = {
0, 0, PANEL_SIZE, 0,
0, 0,
0, 0,
0, monitor_rect.width,
0, 0,
};
XChangeProperty(x11_display, x11_window,
gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_STRUT_PARTIAL"),
gdk_x11_get_xatom_by_name_for_display(display, "CARDINAL"),
32, PropModeReplace, (unsigned char *) struts, G_N_ELEMENTS(struts));
/* Set the panel's window size */
gtk_widget_set_size_request(window, monitor_rect.width, PANEL_SIZE);
/* Move the window to the reserved place */
XMoveWindow(x11_display, x11_window,
monitor_rect.x, monitor_rect.y);
/* Move the window somewhere else, no crash as the window intersects the work area */
/* XMoveWindow(x11_display, x11_window, */
/* monitor_rect.x, monitor_rect.y + 100); */
add_context_menu(window);
}
int main(int argc, char **argv) {
GtkApplication *app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
return g_application_run(G_APPLICATION(app), argc, argv);;
}