sarah-walker-pcem/pcem

wxWidget Issues under Wayland

Opened this issue · 14 comments

Describe the bug
Right clicking on the emulated PC on a Wayland environment such as stock Ubuntu 21.10 (My current configuration) causes the right click context menu to disappear shortly after, and it will not function again until PCem is restarted. Running PCem with the environment variable GDK_BACKEND=x11 appears to work around this bug.

To Reproduce
Use a Wayland-based desktop and right click on an emulated PC without any GDK_BACKEND environment var specified

Expected behavior
Menu works and allows you to configure settings such as media connected.

Screenshots
Errors in console:
image

Emulator configuration

  • Machine: Shuttle HOT-557
  • CPU: Pentium MMX 233
  • Graphics/sound cards: Sound Blaster 16, Voodoo 2000
  • Installed OS: Windows 98 SE

Host machine

  • OS: Ubuntu 21.10
  • Emulator version: v17 with source downloaded from the website.

I'll try to reproduce this on 22.04 LTS when that releases some time next month.

I can reproduce this on Fedora 35 amd64 under Wayland.

Requires the same GDK_BACKEND variable to workaround it.

I am wondering if this is a wxWidgets + GTK issue in Wayland. Since I don't use Wayland much, I haven't checked much of this.

I am going to reference #101 as it seems Wayland is also part of this. I will change the name to wxWidget Issues under Wayland

I believe both me and robdaemon were using GNOME. Can someone try KDE Plasma under Wayland as well?

I'll try to reproduce this on 22.04 LTS when that releases some time next month.

Same results on Ubuntu 22.04 LTS. Workaround still works fine.

Same issue on Chrome OS/Crostini. The workaround fixes it.
Adding non-zero x/y coordinates to the window->PopupMenu() call in Frame::OnPopupMenuEvent() also lets the menu appear, but the behavior is not perfect (it might get hidden behind the main window if you click it again).

Thanks @WowItsKaylie ,
I was not able to open the menu either, and I wondered where I put the iso file in the cd-drive.
GDK_BACKEND=x11 pcem fixed the right-cklick menu, and I am able to mount an iso image.

Being on Silverblue lately and now with Nvidia cards can use Wayland, i have seen in other apps where GTK gets wonky with geometry on Wayland so it is 100% a GTK/Wayland issue from what I see

Can confirm the issue and a workaround, Ubuntu 22.04.1 LTS

Being on Silverblue lately and now with Nvidia cards can use Wayland, i have seen in other apps where GTK gets wonky with geometry on Wayland so it is 100% a GTK/Wayland issue from what I see

@michael-manley super late response since someone posted in this thread again, but perhaps we need more testing if its GTK? Needs some more testing in Sway, KDE Wayland, etc.

thp commented

(walls-of-text warning: some logging of looking where the root cause of this bug might be)

Edit: Package versions used locally for the record on Ubuntu 23.04:

Gtk3 3.24.37-1ubuntu1
wxGTK 3.2.2+dfsg-2build1

On my machine, the right-click in the SDL2 window is handled in src/wx-ui/wx-sdl2-display.c:

        while (SDL_PollEvent(&event)) {
                switch (event.type) {
                case SDL_MOUSEBUTTONUP:
                        if (!mousecapture) {
                                if (event.button.button == SDL_BUTTON_LEFT && !pause) {
                                        window_doinputgrab = 1;
                                        if (video_fullscreen)
                                                window_dofullscreen = 1;
                                } else if (event.button.button == SDL_BUTTON_RIGHT)
                                        wx_popupmenu(ghwnd, menu, 0, 0);

It eventually prints the following error messages on the console:

(pcem:112010): Gtk-WARNING **: 21:16:23.631: no trigger event for menu popup

(pcem:112010): Gtk-CRITICAL **: 21:16:23.631: gtk_menu_popup_at_rect: assertion 'GDK_IS_WINDOW (rect_window)' failed

The wx_popupmenu() is defined in src/wx-ui/wx-utils.cc, it just puts a new PopupMenuEvent on the WX event queue. This event type is registered in src/wx-ui/wx-app.cc as Bind(WX_POPUP_MENU_EVENT, &Frame::OnPopupMenuEvent, this);, and the implementation is this:

void Frame::OnPopupMenuEvent(PopupMenuEvent &event) {
        wxWindow *window = event.GetWindow();
        wxMenu *menu = event.GetMenu();
        int *x = event.GetX();
        int *y = event.GetY();
        
        window->Show();
        window->SetFocus();

        if (x && y)
                window->PopupMenu(menu, *x, *y);
        else
                window->PopupMenu(menu);
}

As wx_popupmenu(...) seems to be only called from the SDL2 window code, it always passed null pointers for x and y, which in turn means window->PopupMenu(menu); is the code path that gets called.

This means that right-clicking eventually calls this later in the event loop:

((wxWindow *)ghwnd)->PopupMenu((wxMenu *)menu);

So, where does ghwnd come from? It's a Frame * in src/wx-ui/wx-app.cc:

        frame = new Frame(this, "null frame", wxPoint(500, 500), wxSize(100, 100));

Then, frame->Start(); is called on it, which does:

void Frame::Start() {
        if (wx_start(this))
                ShowConfigSelection();
        else
                Quit(0);
}

wx_start() then takes its first argument and stores it in ghwnd.

The value of menu is obtained from ghwnd via wx_getmenu(), which is (in src/wx-ui/wx-utils.cc):

void *wx_getmenu(void *window) { return ((Frame *)window)->GetMenu(); }

..this is from the same Frame object as above.

So in the end, because Frame IS the window (aka ghwnd, or this in the scope of Frame::OnPopupMenuEvent) and, it has a member that IS the menu, Frame::OnPopupMenuEvent could at the moment be simplified to:

void Frame::OnPopupMenuEvent(PopupMenuEvent &event) {
        PopupMenu(menu);
}       

Now, for the error message:

"no trigger event for menu popup"

This comes from Gtk3:

https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/gtkmenu.c

There are 3 places where this could happen:

  • gtk_menu_popup_at_rect()
  • gtk_menu_popup_at_widget()
  • gtk_menu_popup_at_pointer()

If we grep for which functions are called from wxWidgets (using Git master branch here, the installed version might be slightly older):

https://github.com/wxWidgets/wxWidgets/blob/d7a98a44ea6c9f814e35db1d1aa869d3b5bf10d1/src/gtk/window.cpp#L6050

% ag gtk_menu_popup
src/gtk/window.cpp
6079:            gtk_menu_popup_at_pointer(GTK_MENU(menu->m_menu), nullptr);
6083:            gtk_menu_popup_at_rect(GTK_MENU(menu->m_menu),
6092:        gtk_menu_popup(
6105:    // it is possible for gtk_menu_popup() to fail

All the calls turn out to be in the wxWindowGTK::DoPopupMenu() (which I'm freely assuming is the wxGTK implementation of the PopupMenu() function we're calling above).

The problem is, these functions assume that there's a "trigger event" processed at the moment (e.g. if this call comes from within a mouse event handling routine) and always pass nullptr to the gtk_menu_popup_*() functions which means Gtk tries to find a "trigger event" by asking gtk_get_current_event(), and that fails, because:

  • The event isn't even generated from within Gtk, but it's a totally different window (the SDL2 window)
  • Even if the event would have been generated from Gtk, we just queue the PopupMenuEvent for later processing, and at processing time, that event is gone

It seems like with wxGTK, when compiling against GTK < 3.22 (#if GTK_CHECK_VERSION(3,22,0) in src/gtk/window.cpp) or when these checks fail:

    if (wxGTKImpl::IsWayland(window) && wx_is_at_least_gtk3(22))

..then it falls back to gtk_menu_popup(), which seems to work for X11 (but I tried using it on Wayland, and it doesn't seem to work, which the Gtk docs also mention (and why the function is deprected in Gtk 3.22):

Note that this function does not work very well on GDK backends that do not have global coordinates, such as Wayland or Mir. You should probably use one of the gtk_menu_popup_at_ variants, which do not have this problem.

(which is in fact what wxGTK tries to do...)

In any case, that's how far I have come so far.

As a proof-of-concept workaround (but not really that useful, as the GDK_BACKEND=x11 workaround can fix it as well for now, at least as long as some kind of Xwayland is running in the background), one can show the "null frame" that's used as a menu container (and to pop up the menu) and set its menu bar (it will be a separate window, but the menu on it works, because when the menu is clicked, there is an actual Gtk trigger event). The return; in the right click popup handling code is there, because wxGTK complains that you cannot popup a menu that's attached to a menubar, so this just no-ops the right click.

diff --git a/src/wx-ui/wx-app.cc b/src/wx-ui/wx-app.cc
index 685f1d2e..bd2c9264 100644
--- a/src/wx-ui/wx-app.cc
+++ b/src/wx-ui/wx-app.cc
@@ -58,6 +58,10 @@ bool App::OnInit() {
         InitXmlResource();
 
         frame = new Frame(this, "null frame", wxPoint(500, 500), wxSize(100, 100));
+        wxMenuBar *bar = new wxMenuBar;
+        bar->Append((wxMenu *)wx_getmenu(frame), "Menu");
+        frame->SetMenuBar(bar);
+        frame->Show();
         frame->Start();
         return true;
 }
@@ -67,7 +71,7 @@ int App::OnRun() { return wxApp::OnRun(); }
 #include <sstream>
 
 Frame::Frame(App *app, const wxString &title, const wxPoint &pos, const wxSize &size)
-        : wxFrame(NULL, wxID_ANY, title, pos, size, 0) // wxDEFAULT_FRAME_STYLE & ~(wxRESIZE_BORDER))
+        : wxFrame(NULL, wxID_ANY, title, pos, size, wxDEFAULT_FRAME_STYLE)
 {
         this->closing = false;
         this->menu = wxXmlResource::Get()->LoadMenu(wxT("main_menu"));
@@ -157,6 +161,8 @@ void Frame::OnShowWindowEvent(wxCommandEvent &event) {
 }
 
 void Frame::OnPopupMenuEvent(PopupMenuEvent &event) {
+    return;
+
         wxWindow *window = event.GetWindow();
         wxMenu *menu = event.GetMenu();
         int *x = event.GetX();

In summary, the problem seems to be a Gtk popup not opening with a hidden window on Wayland when a non-Gtk window (SDL2 window in this case) has focus and was clicked.

The only Gtk3+SDL2 code I found via a quick Google search assumes X11, so again cannot be used to "fuse" Gtk3/wxGTK and SDL2 together. If it doesn't need to be a popup menu, the "null frame" could just be shown and focused on right click instead as a separate window (not sure how annoying that would be, though).

I'm not sure at all if my comment is useful:

I had somewhat similar situation with my own project (not related to pcem at all), using SDL2 in general, but using GTK3 for pop-up menus, dialogs etc only. If running on wayland, there can be a situation that SDL still uses the X11 driver, but GTK defaults to native wayland, then the interaction between them goes crazy, no menu can be seen, and other problems. My workaround was something like this (IIRC, I write this now by heart):

if (!strcasecmp(SDL_GetCurrentVideoDriver(), "x11"))
    setenv("GDK_BACKEND", "x11", 1);

Before initializing GTK, (but after initialization SDL2, of course). This will force GTK to use x11 backend as well, if SDL uses that. Of course this scenario may or may not fit this situation with pcem.

I can confirm this is an issue on Fedora Linux 38 as well. Moreover, unless SDL is also forced to use x11 via SDL_VIDEODRIVER=x11 we get a segfault on machine start (see https://bugzilla.redhat.com/show_bug.cgi?id=2212225 for details). I put up #222 as a temporary workaround.

@davide125 At least with my non-pcem experience with SDL+GTK I only had problem when there is a mismatch between SDL and GTK, one using wayland, and the other does not. If both using wayland, it worked for me just fine (for sure, also, when both using X11 already). So my workaround above, to force only GTK to use X11 if SDL uses that too. Surely my clumsy workaround above can have code for the case when SDL uses wayland, and then GTK should be forced to use wayland as well, just in case to have the same backend for the two libraries.