On Mon, 11 Nov 2024 at 05:45, Akihiko Odaki <akihiko.od...@daynix.com> wrote: > > On 2024/11/11 6:55, Phil Dennis-Jordan wrote: > > macOS's Cocoa event handling must be done on the initial (main) thread > > of the process. Furthermore, if library or application code uses > > libdispatch, the main dispatch queue must be handling events on the main > > thread as well. > > > > So far, this has affected Qemu in both the Cocoa and SDL UIs, although > > in different ways: the Cocoa UI replaces the default qemu_main function > > with one that spins Qemu's internal main event loop off onto a > > background thread. SDL (which uses Cocoa internally) on the other hand > > uses a polling approach within Qemu's main event loop. Events are > > polled during the SDL UI's dpy_refresh callback, which happens to run > > on the main thread by default. > > > > As UIs are mutually exclusive, this works OK as long as nothing else > > needs platform-native event handling. In the next patch, a new device is > > introduced based on the ParavirtualizedGraphics.framework in macOS. > > This uses libdispatch internally, and only works when events are being > > handled on the main runloop. With the current system, it works when > > using either the Cocoa or the SDL UI. However, it does not when running > > headless. Moreover, any attempt to install a similar scheme to the > > Cocoa UI's main thread replacement fails when combined with the SDL > > UI. > > > > This change tidies up main thread management to be more flexible. > > > > * The qemu_main global function pointer is a custom function for the > > main thread, and it may now be NULL. When it is, the main thread > > runs the main Qemu loop. This represents the traditional setup. > > * When non-null, spawning the main Qemu event loop on a separate > > thread is now done centrally rather than inside the Cocoa UI code. > > * For most platforms, qemu_main is indeed NULL by default, but on > > Darwin, it defaults to a function that runs the CFRunLoop. > > * The Cocoa UI sets qemu_main to a function which runs the > > NSApplication event handling runloop, as is usual for a Cocoa app. > > * The SDL UI overrides the qemu_main function to NULL, thus > > specifying that Qemu's main loop must run on the main > > thread. > > * The GTK UI also overrides the qemu_main function to NULL. > > * For other UIs, or in the absence of UIs, the platform's default > > behaviour is followed. > > > > This means that on macOS, the platform's runloop events are always > > handled, regardless of chosen UI. The new PV graphics device will > > thus work in all configurations. There is no functional change on other > > operating systems. > > > > Signed-off-by: Phil Dennis-Jordan <p...@philjordan.eu> > > Reviewed-by: Akihiko Odaki <akihiko.od...@daynix.com> > > --- > > v5: > > > > * Simplified the way of setting/clearing the main loop by going back > > to setting qemu_main directly, but narrowing the scope of what it > > needs to do, and it can now be NULL. > > > > v6: > > > > * Folded function qemu_run_default_main_on_new_thread's code into > > main() > > * Removed whitespace changes left over on lines near code removed > > between v4 and v5 > > > > v9: > > > > * Set qemu_main to NULL for GTK UI as well. > > > > include/qemu-main.h | 3 +-- > > include/qemu/typedefs.h | 1 + > > system/main.c | 50 ++++++++++++++++++++++++++++++++++---- > > ui/cocoa.m | 54 ++++++++++------------------------------- > > ui/gtk.c | 3 +++ > > ui/sdl2.c | 4 +++ > > 6 files changed, 67 insertions(+), 48 deletions(-) > > > > diff --git a/include/qemu-main.h b/include/qemu-main.h > > index 940960a7dbc..4bd0d667edc 100644 > > --- a/include/qemu-main.h > > +++ b/include/qemu-main.h > > @@ -5,7 +5,6 @@ > > #ifndef QEMU_MAIN_H > > #define QEMU_MAIN_H > > > > -int qemu_default_main(void); > > -extern int (*qemu_main)(void); > > +extern qemu_main_fn qemu_main; > > > > #endif /* QEMU_MAIN_H */ > > diff --git a/include/qemu/typedefs.h b/include/qemu/typedefs.h > > index 3d84efcac47..b02cfe1f328 100644 > > --- a/include/qemu/typedefs.h > > +++ b/include/qemu/typedefs.h > > @@ -131,5 +131,6 @@ typedef struct IRQState *qemu_irq; > > * Function types > > */ > > typedef void (*qemu_irq_handler)(void *opaque, int n, int level); > > +typedef int (*qemu_main_fn)(void); > > > > #endif /* QEMU_TYPEDEFS_H */ > > diff --git a/system/main.c b/system/main.c > > index 9b91d21ea8c..d9397a6d5d0 100644 > > --- a/system/main.c > > +++ b/system/main.c > > @@ -24,13 +24,14 @@ > > > > #include "qemu/osdep.h" > > #include "qemu-main.h" > > +#include "qemu/main-loop.h" > > #include "sysemu/sysemu.h" > > > > -#ifdef CONFIG_SDL > > -#include <SDL.h> > > +#ifdef CONFIG_DARWIN > > +#include <CoreFoundation/CoreFoundation.h> > > #endif > > > > -int qemu_default_main(void) > > +static int qemu_default_main(void) > > { > > int status; > > > > @@ -40,10 +41,49 @@ int qemu_default_main(void) > > return status; > > } > > > > -int (*qemu_main)(void) = qemu_default_main; > > +/* > > + * Various macOS system libraries, including the Cocoa UI and anything > > using > > + * libdispatch, such as ParavirtualizedGraphics.framework, requires that > > the > > + * main runloop, on the main (initial) thread be running or at least > > regularly > > + * polled for events. A special mode is therefore supported, where the QEMU > > + * main loop runs on a separate thread and the main thread handles the > > + * CF/Cocoa runloop. > > + */ > > + > > +static void *call_qemu_default_main(void *opaque) > > +{ > > + int status; > > + > > + bql_lock(); > > + status = qemu_default_main(); > > + bql_unlock(); > > + > > + exit(status); > > +} > > + > > +#ifdef CONFIG_DARWIN > > +static int os_darwin_cfrunloop_main(void) > > +{ > > + CFRunLoopRun(); > > + abort(); > > +} > > + > > +qemu_main_fn qemu_main = os_darwin_cfrunloop_main; > > +#else > > +qemu_main_fn qemu_main; > > +#endif > > > > int main(int argc, char **argv) > > { > > + QemuThread main_loop_thread; > > + > > qemu_init(argc, argv); > > - return qemu_main(); > > + if (qemu_main) { > > + qemu_thread_create(&main_loop_thread, "qemu_main", > > + call_qemu_default_main, NULL, > > QEMU_THREAD_DETACHED); > > + bql_unlock(); > > + return qemu_main(); > > + } else { > > + qemu_default_main(); > > + } > > } > > diff --git a/ui/cocoa.m b/ui/cocoa.m > > index 4c2dd335323..30b8920d929 100644 > > --- a/ui/cocoa.m > > +++ b/ui/cocoa.m > > @@ -73,6 +73,8 @@ > > int height; > > } QEMUScreen; > > > > +@class QemuCocoaPasteboardTypeOwner; > > + > > static void cocoa_update(DisplayChangeListener *dcl, > > int x, int y, int w, int h); > > > > @@ -107,6 +109,7 @@ static void cocoa_switch(DisplayChangeListener *dcl, > > static NSInteger cbchangecount = -1; > > static QemuClipboardInfo *cbinfo; > > static QemuEvent cbevent; > > +static QemuCocoaPasteboardTypeOwner *cbowner; > > > > // Utility functions to run specified code block with the BQL held > > typedef void (^CodeBlock)(void); > > @@ -1321,8 +1324,10 @@ - (void) dealloc > > { > > COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); > > > > - if (cocoaView) > > - [cocoaView release]; > > + [cocoaView release]; > > + [cbowner release]; > > + cbowner = nil; > > + > > [super dealloc]; > > } > > > > @@ -1938,8 +1943,6 @@ - (void)pasteboard:(NSPasteboard *)sender > > provideDataForType:(NSPasteboardType)t > > > > @end > > > > -static QemuCocoaPasteboardTypeOwner *cbowner; > > - > > static void cocoa_clipboard_notify(Notifier *notifier, void *data); > > static void cocoa_clipboard_request(QemuClipboardInfo *info, > > QemuClipboardType type); > > @@ -2002,43 +2005,8 @@ static void > > cocoa_clipboard_request(QemuClipboardInfo *info, > > } > > } > > > > -/* > > - * The startup process for the OSX/Cocoa UI is complicated, because > > - * OSX insists that the UI runs on the initial main thread, and so we > > - * need to start a second thread which runs the qemu_default_main(): > > - * in main(): > > - * in cocoa_display_init(): > > - * assign cocoa_main to qemu_main > > - * create application, menus, etc > > - * in cocoa_main(): > > - * create qemu-main thread > > - * enter OSX run loop > > - */ > > - > > -static void *call_qemu_main(void *opaque) > > -{ > > - int status; > > - > > - COCOA_DEBUG("Second thread: calling qemu_default_main()\n"); > > - bql_lock(); > > - status = qemu_default_main(); > > - bql_unlock(); > > - COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n"); > > - [cbowner release]; > > - exit(status); > > -} > > - > > static int cocoa_main(void) > > { > > - QemuThread thread; > > - > > - COCOA_DEBUG("Entered %s()\n", __func__); > > - > > - bql_unlock(); > > - qemu_thread_create(&thread, "qemu_main", call_qemu_main, > > - NULL, QEMU_THREAD_DETACHED); > > - > > - // Start the main event loop > > COCOA_DEBUG("Main thread: entering OSX run loop\n"); > > [NSApp run]; > > COCOA_DEBUG("Main thread: left OSX run loop, which should never > > happen\n"); > > @@ -2120,8 +2088,6 @@ static void cocoa_display_init(DisplayState *ds, > > DisplayOptions *opts) > > > > COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); > > > > - qemu_main = cocoa_main; > > - > > // Pull this console process up to being a fully-fledged graphical > > // app with a menubar and Dock icon > > ProcessSerialNumber psn = { 0, kCurrentProcess }; > > @@ -2185,6 +2151,12 @@ static void cocoa_display_init(DisplayState *ds, > > DisplayOptions *opts) > > qemu_clipboard_peer_register(&cbpeer); > > > > [pool release]; > > + > > + /* > > + * The Cocoa UI will run the NSApplication runloop on the main thread > > + * rather than the default Core Foundation one. > > + */ > > + qemu_main = cocoa_main; > > } > > > > static QemuDisplay qemu_display_cocoa = { > > diff --git a/ui/gtk.c b/ui/gtk.c > > index bf9d3dd679a..1694146ae37 100644 > > --- a/ui/gtk.c > > +++ b/ui/gtk.c > > @@ -38,6 +38,7 @@ > > #include "qemu/cutils.h" > > #include "qemu/error-report.h" > > #include "qemu/main-loop.h" > > +#include "qemu-main.h" > > > > #include "ui/console.h" > > #include "ui/gtk.h" > > @@ -2485,6 +2486,8 @@ static void gtk_display_init(DisplayState *ds, > > DisplayOptions *opts) > > #ifdef CONFIG_GTK_CLIPBOARD > > gd_clipboard_init(s); > > #endif /* CONFIG_GTK_CLIPBOARD */ > > + > > + qemu_main = NULL; > > Add a comment that corresponds to one in: ui/sdl2.c
I've now got this: /* * GTK+ calls must happen on the main thread at least on some platforms, * and on macOS the main runloop is polled via GTK+'s event handling. * Don't allow Qemu's event loop to be moved off the main thread. */ It's carefully worded to only say things I know to be true - it seems like the GTK+ UI integration doesn't actually consistently follow the rule, which is why that UI currently doesn't work properly on macOS. > > } > > > > static void early_gtk_display_init(DisplayOptions *opts) > > diff --git a/ui/sdl2.c b/ui/sdl2.c > > index bd4f5a9da14..44ab2762262 100644 > > --- a/ui/sdl2.c > > +++ b/ui/sdl2.c > > @@ -34,6 +34,7 @@ > > #include "sysemu/sysemu.h" > > #include "ui/win32-kbd-hook.h" > > #include "qemu/log.h" > > +#include "qemu-main.h" > > > > static int sdl2_num_outputs; > > static struct sdl2_console *sdl2_console; > > @@ -965,6 +966,9 @@ static void sdl2_display_init(DisplayState *ds, > > DisplayOptions *o) > > } > > > > atexit(sdl_cleanup); > > + > > + /* SDL's event polling (in dpy_refresh) must happen on the main > > thread. */ > > + qemu_main = NULL; > > } > > > > static QemuDisplay qemu_display_sdl2 = { > >