I have a very strange issue with my pod-ui app that only shows on macOS. This is a cross-platform macOS/Linux/Windows GTK3 app written in rust that talks MIDI to an obscure piece of guitar tech. First, it detects the Line6 modelling amp on the MIDI line and requests a full dump from it, then it receives 128 programs over MIDI one by one and populates internal buffers with these, updating the UI (program names are shown on buttons) as the programs are received.
All this works fine in Linux and Windows and used to work fine in macOS. This used to be a normal GTK application, but was recently converted to a GtkApplication. This is when the problem became noticeable. The pod-ui project issue contains videos of the two app versions “side-by-side” and how they behave.
I even went as far as reimplementing g_application_run(), which -to my surprise- helped on my (rather old Mac) machine, but didn’t have an effect on a newer faster Mac.
Once the UI is stuck and not updating, the app still works - there’s a “preferences” button which does open a preferences dialog.
If I grab the window by the titlebar and try moving it around the desktop, after a little while it gets unstuck, the updates finish and all works as expected. Otherwise, it doesn’t get unstuck on its own no matter how much time goes by.
I’m really at a loss of how to even start debugging this. Any pointers on what to try are welcome.
PS. The app is multi-threaded, but all GTK GUI updates are done as a part of a glib::MainContext::channel receiver attach-callback, i.e. a source registered on the GMainContext, running all in one thread.
the “old Mac” is a Intel Core i7 2015 MacBook Pro, running macOS 12.7.2,
the “new Mac” is an Apple M1 Pro 2021 MacBook Pro, running macOS 14.6.1
Both are running the same app, compiled on the “old Mac”, packaged with the same gtk3 3.24.43 library.
The fact that a custom main context loop helped is a fluke, as GTK is running the exact same code under the hood. I’ve updated the code to run on top of gtk-rs 0.18 and done more testing and the UI gets stuck consistently, whether it gunning g_appication_run(), a custom main context loop, or even gtk_main().
As the application handles events like button clicks, I assume that the GLib main loop is spinning normally. You could test that by adding a timeout source with GLib.timeout_add_seconds_full which prints a message to the terminal.
Once the UI is stuck and not updating, the app still works - there’s a “preferences” button which does open a preferences dialog.
The button is part of the window, right? I can see that it’s in the titlebar, but it seems to be a Client-Side (CSD) titlebar. So the app is still handling input events, but graphical updates are blocked. In such case buttons should not highlight when hovering the cursor over them. If you want to test that all input handling keeps working, add a button in the UI that prints a message when clicked.
Finally: when launching your app with the environment variable GTK_DEBUG=interactive, does the inspector work when the app is stuck?
It may be that the window or the frame clock are frozen. Could you prepare a custom build of GTK? I can post here a small patch that prints some info
Yes, the main loop is spinning normally. A timeout added with timeout_add_seconds_full does print a tick message to the terminal correctly.
Yes, the UI is responsive even after the graphics is stuck. The buttons in the titlebar indeed do not highlight, but actually respond to clicks (the preferences button, fires the “app.preferences” action, which gets handled correctly). I can also verify that a simple button that prints a message to the console, as you suggested, also works correctly. The UI controls also generate correct “changed”/“clicked”/“value-changed” events when they are touched as I can see the callbacks connected to those events get called.
To answer you last question I’ll have to recompile GTK to get GTK_DEBUG=interactive working as the one shipped with Homebrew by default doesn’t have this enabled. If you have a patch I can try, I’ll definitely apply it and report back.
Now that you mentioned the frame clock, I added a tick callback to the main window using the gtk_wiget_add_tick_callback and indeed, when the UI freezes the ticks stop coming. After I shake the window awake, I can once again see the ticks coming. If I add a call to gdk_frame_clock_get_frame_counter, I see that the frame no frames are dropped - if it freezed with frame counter 96, the frame counter when it wakes up is 97.
If the frame clock is not cycling then perhaps the CVDispayLinkSource is not acting correctly. Look for gdk_frame_clock_freeze, gdk_frame_clock_thaw in gdk/quartz:
@lb90 I built a debug build-type gtk with your patch applied. The inspector works fine and I can see that the buttons that were updated after the UI froze indeed have the correct labels as they otherwise should.
After the UI gets stuck, I get the following statistics printed:
I incorrectly assumed that I haven’t updated Homebrew gtk between building versions 1.3.0 (the one that doesn’t get stuck) and 1.4.0 (the one that gets stuck). Version 1.3.0 is packages with gtk 3.24.37 and version 1.4.0 with gtk 3.24.38. Now I have 3.24.43, but playing with DYLD_LIBRARY_PATH I can run the app with either of those and indeed without changing anything, when ran with gtk 3.24.37, the app doesn’t get stuck anymore.