From patchwork Thu Feb 14 10:28:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Peter Maydell X-Patchwork-Id: 158349 Delivered-To: patches@linaro.org Received: by 2002:a02:48:0:0:0:0:0 with SMTP id 69csp1175346jaa; Thu, 14 Feb 2019 02:28:29 -0800 (PST) X-Received: by 2002:a5d:6b8f:: with SMTP id n15mr2291578wrx.110.1550140109439; Thu, 14 Feb 2019 02:28:29 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1550140109; cv=none; d=google.com; s=arc-20160816; b=DhL0oM2IdQ3YsL6DMJEhdaarrA4qyh+oMXRVRAIhzP1NaKrkR+P50JRhZku6kMOcJG bamgfsvcQXHAOofLfJjLRkpDeW/yLlya/hyzv1npG1s/a6uUx1a+AgFn6e6YqLGQKo63 yGGbKnncTEB9eQfjtugN4nrXKJpFgN/SyDjS6wRF9miI4GvhX0kwhOJ6CI09OdP3E6Ww Eq3fmFpsVWzJBsLUCtoZjTi+M8qwmAf52ohZEYkBRqfqCSYLHc9rnKXbipNlXU95Dpml BTBv85gXWM91SB9HOlRnLFT8zUPvOWnG0/7eFF4rvGVJjdP8jXd4ccJZJv7d9sPkcpxq /YpA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:dkim-signature; bh=tHIXdKLzCfVDEdo6vUIvGThxiCS0S7O3izq49fTJxds=; b=PHllWL+XXZu4dTYuMM9bPPEtjvvnMMRJUVFMRtKWlWzH8Vq/n0vc1ouv/H1F+T2Hbj KtFQctIOmEVqqRxJWKMbMKgHyM1RS7+7zAfUEhuPTM+qE1NSqyZwVeBrym88zGNvnw6o LIuh9VTWIlwhTybKwyh3bOOCcRqYXzIQrbtgcktHJuHRiKHowoaD85Urnx1atVWxmrcH grfqmUfzri3ujQJmeNCoaFmUV26p0F61PhpyzT0txWTS8OHfxXAjueL2smmXJx26OzOq 8auzqkDNuX+6AAN23eP3u++e6OKa+kLCZSrcP5TCubQF2rMKz8KKB5i6RAeL3JhETWXL Q+XQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=Mt5oawDT; spf=pass (google.com: domain of peter.maydell@linaro.org designates 209.85.220.65 as permitted sender) smtp.mailfrom=peter.maydell@linaro.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org Return-Path: Received: from mail-sor-f65.google.com (mail-sor-f65.google.com. [209.85.220.65]) by mx.google.com with SMTPS id x5sor1244804wmb.27.2019.02.14.02.28.29 for (Google Transport Security); Thu, 14 Feb 2019 02:28:29 -0800 (PST) Received-SPF: pass (google.com: domain of peter.maydell@linaro.org designates 209.85.220.65 as permitted sender) client-ip=209.85.220.65; Authentication-Results: mx.google.com; dkim=pass header.i=@linaro.org header.s=google header.b=Mt5oawDT; spf=pass (google.com: domain of peter.maydell@linaro.org designates 209.85.220.65 as permitted sender) smtp.mailfrom=peter.maydell@linaro.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=linaro.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=tHIXdKLzCfVDEdo6vUIvGThxiCS0S7O3izq49fTJxds=; b=Mt5oawDTA815orSxLaWc28IjplEQOCjpgLXBmjbRcaZKgzbedG7rbGSdFshE5paSAl DKKeEDlxPmkZ1fpr4WWXBKn5NcqrJ/a5CgXdTkzwa9g92f0qd7hIxG/Ie2nDJ7EaQUFA n8JxJq4CdzSJnK9NWnkQWVue24z7W4A6FdIH5Z57Wu3k24mh5k+j9wXacLjaFFuJ0XwY NMr94qio+w24b3gJzTojHPiF9Uydc15wwBSKuzy+b56xHc3jzuqnQ4jAJktMuWD/LY5a 7UZiSfK495c4l9i00Yit9l3oV286EgNMTTmW283Xs/KG+JOs7qBONeljs8x1lzJey8Ng tcLA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=tHIXdKLzCfVDEdo6vUIvGThxiCS0S7O3izq49fTJxds=; b=dv3Ro0lVQir+m09Jz21XxM5HMnDzngMT5+7+EeEweYZLgXvL3sFB6R9MYG//ehJyKC T8zyBAMx51ab69CZKK0trN3nP1fa2op4KL0ArUg8FEfq9+RgXfhTtzFf2NQhtm/JOsxk 2ZPIf5nSHoVHeSq3hQDiSowcvsxHs+SJ64CDclMFTroH2QNqMZ2gCd2TSVeerGiY3xeV +HrEm1WRyb0z90owU6lKURLUtnQ6XxatAEAE0N8PuFpqgtFoottd5hIN2nuoz7XsKePw 94YfxDtFOiPp3uNVfanx3oR2hT5UVec/R32DfWqg/zqtx+fIeh93Rb9B2Cg9NGthMv7f fOiQ== X-Gm-Message-State: AHQUAuZikp5EdWwXCq3NzxWh9yAcE2l+3yJmHOc38BMqd/q8YoNOOg0e 7DbHks4Y9x6mliCSIwxHNNeBxBNj X-Google-Smtp-Source: AHgI3Ib2MboEP20L1kiUyLqaGRoGVguCK1X8sXXLNxETqQLCDwP5hRWn4l98k03usU2Qfy8az3+x0w== X-Received: by 2002:a1c:2544:: with SMTP id l65mr2224030wml.93.1550140108703; Thu, 14 Feb 2019 02:28:28 -0800 (PST) Return-Path: Received: from orth.archaic.org.uk (orth.archaic.org.uk. [81.2.115.148]) by smtp.gmail.com with ESMTPSA id o5sm6473817wrh.34.2019.02.14.02.28.27 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 14 Feb 2019 02:28:28 -0800 (PST) From: Peter Maydell To: qemu-devel@nongnu.org Cc: patches@linaro.org, John Arbuckle , Roman Bolshakov , Berkus Decker , Gerd Hoffmann , Ben Hekster , BALATON Zoltan Subject: [PATCH v2 7/7] ui/cocoa: Perform UI operations only on the main thread Date: Thu, 14 Feb 2019 10:28:16 +0000 Message-Id: <20190214102816.3393-8-peter.maydell@linaro.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190214102816.3393-1-peter.maydell@linaro.org> References: <20190214102816.3393-1-peter.maydell@linaro.org> MIME-Version: 1.0 The OSX Mojave release is more picky about enforcing the Cocoa API restriction that only the main thread may perform UI calls. To accommodate this we need to restructure the Cocoa code: * the special OSX main() creates a second thread and uses that to call the vl.c qemu_main(); the original main thread goes into the OSX event loop * the refresh, switch and update callbacks asynchronously tell the main thread to do the necessary work * the refresh callback no longer does the "get events from the UI event queue and handle them" loop, since we now use the stock OSX event loop. Instead our NSApplication sendEvent method will either deal with them or pass them on to OSX All these things have to be changed in one commit, to avoid breaking bisection. Note that since we use dispatch_get_main_queue(), this bumps our minimum version requirement to OSX 10.10 Yosemite (released in 2014, unsupported by Apple since 2017). Signed-off-by: Peter Maydell --- v2 changes: call handleEvent from sendEvent --- ui/cocoa.m | 191 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 78 deletions(-) -- 2.17.2 (Apple Git-113) Reviewed-by: Roman Bolshakov Tested-by: Roman Bolshakov diff --git a/ui/cocoa.m b/ui/cocoa.m index 184fbd877d..201a294ed4 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -129,6 +129,9 @@ NSTextField *pauseLabel; NSArray * supportedImageFileTypes; +QemuSemaphore display_init_sem; +QemuSemaphore app_started_sem; + // Utility functions to run specified code block with iothread lock held typedef void (^CodeBlock)(void); typedef bool (^BoolCodeBlock)(void); @@ -325,6 +328,7 @@ @interface QemuCocoaView : NSView NSWindow *fullScreenWindow; float cx,cy,cw,ch,cdx,cdy; CGDataProviderRef dataProviderRef; + pixman_image_t *pixman_image; BOOL modifiers_state[256]; BOOL isMouseGrabbed; BOOL isFullscreen; @@ -535,13 +539,16 @@ - (void) switchSurface:(pixman_image_t *)image } // update screenBuffer - if (dataProviderRef) + if (dataProviderRef) { CGDataProviderRelease(dataProviderRef); + pixman_image_unref(pixman_image); + } //sync host window color space with guests screen.bitsPerPixel = PIXMAN_FORMAT_BPP(image_format); screen.bitsPerComponent = DIV_ROUND_UP(screen.bitsPerPixel, 8) * 2; + pixman_image = image; dataProviderRef = CGDataProviderCreateWithData(NULL, pixman_image_get_data(image), w * 4 * h, NULL); // update windows @@ -1013,7 +1020,6 @@ @interface QemuCocoaAppController : NSObject #endif { } -- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; - (void)doToggleFullScreen:(id)sender; - (void)toggleFullScreen:(id)sender; - (void)showQEMUDoc:(id)sender; @@ -1101,8 +1107,8 @@ - (void) dealloc - (void)applicationDidFinishLaunching: (NSNotification *) note { COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); - // launch QEMU, with the global args - [self startEmulationWithArgc:gArgc argv:(char **)gArgv]; + /* Tell cocoa_display_init to proceed */ + qemu_sem_post(&app_started_sem); } - (void)applicationWillTerminate:(NSNotification *)aNotification @@ -1145,15 +1151,6 @@ - (void) applicationWillResignActive: (NSNotification *)aNotification [cocoaView raiseAllKeys]; } -- (void)startEmulationWithArgc:(int)argc argv:(char**)argv -{ - COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); - - int status; - status = qemu_main(argc, argv, *_NSGetEnviron()); - exit(status); -} - /* We abstract the method called by the Enter Fullscreen menu item * because Mac OS 10.7 and higher disables it. This is because of the * menu item's old selector's name toggleFullScreen: @@ -1485,7 +1482,9 @@ @implementation QemuApplication - (void)sendEvent:(NSEvent *)event { COCOA_DEBUG("QemuApplication: sendEvent\n"); - [super sendEvent: event]; + if (!cocoaView || ![cocoaView handleEvent:event]) { + [super sendEvent: event]; + } } @end @@ -1668,32 +1667,59 @@ static void addRemovableDevicesMenuItems(void) qapi_free_BlockInfoList(pointerToFree); } -int main (int argc, const char * argv[]) { +/* + * 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 vl.c qemu_main(): + * + * Initial thread: 2nd thread: + * in main(): + * create qemu-main thread + * wait on display_init semaphore + * call qemu_main() + * ... + * in cocoa_display_init(): + * post the display_init semaphore + * wait on app_started semaphore + * create application, menus, etc + * enter OSX run loop + * in applicationDidFinishLaunching: + * post app_started semaphore + * tell main thread to fullscreen if needed + * [...] + * run qemu main-loop + * + * We do this in two stages so that we don't do the creation of the + * GUI application menus and so on for command line options like --help + * where we want to just print text to stdout and exit immediately. + */ +static void *call_qemu_main(void *opaque) +{ + int status; + + COCOA_DEBUG("Second thread: calling qemu_main()\n"); + status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); + COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); + exit(status); +} + +int main (int argc, const char * argv[]) { + QemuThread thread; + + COCOA_DEBUG("Entered main()\n"); gArgc = argc; gArgv = (char **)argv; - int i; - /* In case we don't need to display a window, let's not do that */ - for (i = 1; i < argc; i++) { - const char *opt = argv[i]; + qemu_sem_init(&display_init_sem, 0); + qemu_sem_init(&app_started_sem, 0); - if (opt[0] == '-') { - /* Treat --foo the same as -foo. */ - if (opt[1] == '-') { - opt++; - } - if (!strcmp(opt, "-h") || !strcmp(opt, "-help") || - !strcmp(opt, "-vnc") || - !strcmp(opt, "-nographic") || - !strcmp(opt, "-version") || - !strcmp(opt, "-curses") || - !strcmp(opt, "-display") || - !strcmp(opt, "-qtest")) { - return qemu_main(gArgc, gArgv, *_NSGetEnviron()); - } - } - } + qemu_thread_create(&thread, "qemu_main", call_qemu_main, + NULL, QEMU_THREAD_DETACHED); + + COCOA_DEBUG("Main thread: waiting for display_init_sem\n"); + qemu_sem_wait(&display_init_sem); + COCOA_DEBUG("Main thread: initializing app\n"); NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; @@ -1706,12 +1732,24 @@ int main (int argc, const char * argv[]) { create_initial_menus(); + /* + * Create the menu entries which depend on QEMU state (for consoles + * and removeable devices). These make calls back into QEMU functions, + * which is OK because at this point we know that the second thread + * holds the iothread lock and is synchronously waiting for us to + * finish. + */ + add_console_menu_entries(); + addRemovableDevicesMenuItems(); + // Create an Application controller QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; [NSApp setDelegate:appController]; // Start the main event loop + COCOA_DEBUG("Main thread: entering OSX run loop\n"); [NSApp run]; + COCOA_DEBUG("Main thread: left OSX run loop, exiting\n"); [appController release]; [pool release]; @@ -1729,17 +1767,19 @@ static void cocoa_update(DisplayChangeListener *dcl, COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); - NSRect rect; - if ([cocoaView cdx] == 1.0) { - rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); - } else { - rect = NSMakeRect( - x * [cocoaView cdx], - ([cocoaView gscreen].height - y - h) * [cocoaView cdy], - w * [cocoaView cdx], - h * [cocoaView cdy]); - } - [cocoaView setNeedsDisplayInRect:rect]; + dispatch_async(dispatch_get_main_queue(), ^{ + NSRect rect; + if ([cocoaView cdx] == 1.0) { + rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); + } else { + rect = NSMakeRect( + x * [cocoaView cdx], + ([cocoaView gscreen].height - y - h) * [cocoaView cdy], + w * [cocoaView cdx], + h * [cocoaView cdy]); + } + [cocoaView setNeedsDisplayInRect:rect]; + }); [pool release]; } @@ -1748,9 +1788,19 @@ static void cocoa_switch(DisplayChangeListener *dcl, DisplaySurface *surface) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + pixman_image_t *image = surface->image; COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); - [cocoaView switchSurface:surface->image]; + + // The DisplaySurface will be freed as soon as this callback returns. + // We take a reference to the underlying pixman image here so it does + // not disappear from under our feet; the switchSurface method will + // deref the old image when it is done with it. + pixman_image_ref(image); + + dispatch_async(dispatch_get_main_queue(), ^{ + [cocoaView switchSurface:image]; + }); [pool release]; } @@ -1762,26 +1812,15 @@ static void cocoa_refresh(DisplayChangeListener *dcl) graphic_hw_update(NULL); if (qemu_input_is_absolute()) { - if (![cocoaView isAbsoluteEnabled]) { - if ([cocoaView isMouseGrabbed]) { - [cocoaView ungrabMouse]; + dispatch_async(dispatch_get_main_queue(), ^{ + if (![cocoaView isAbsoluteEnabled]) { + if ([cocoaView isMouseGrabbed]) { + [cocoaView ungrabMouse]; + } } - } - [cocoaView setAbsoluteEnabled:YES]; + [cocoaView setAbsoluteEnabled:YES]; + }); } - - NSDate *distantPast; - NSEvent *event; - distantPast = [NSDate distantPast]; - do { - event = [NSApp nextEventMatchingMask:NSEventMaskAny untilDate:distantPast - inMode: NSDefaultRunLoopMode dequeue:YES]; - if (event != nil) { - if (![cocoaView handleEvent:event]) { - [NSApp sendEvent:event]; - } - } - } while(event != nil); [pool release]; } @@ -1802,10 +1841,17 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) { COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); + /* Tell main thread to go ahead and create the app and enter the run loop */ + qemu_sem_post(&display_init_sem); + qemu_sem_wait(&app_started_sem); + COCOA_DEBUG("cocoa_display_init: app start completed\n"); + /* if fullscreen mode is to be used */ if (opts->has_full_screen && opts->full_screen) { - [NSApp activateIgnoringOtherApps: YES]; - [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [NSApp activateIgnoringOtherApps: YES]; + [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; + }); } dcl = g_malloc0(sizeof(DisplayChangeListener)); @@ -1816,17 +1862,6 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) // register cleanup function atexit(cocoa_cleanup); - - /* At this point QEMU has created all the consoles, so we can add View - * menu entries for them. - */ - add_console_menu_entries(); - - /* Give all removable devices a menu item. - * Has to be called after QEMU has started to - * find out what removable devices it has. - */ - addRemovableDevicesMenuItems(); } static QemuDisplay qemu_display_cocoa = {