Message ID | 20210616141954.54291-1-akihiko.odaki@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | ui/cocoa: Add clipboard support | expand |
On Wed, Jun 16, 2021 at 11:19:54PM +0900, Akihiko Odaki wrote:
> Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
Added to UI queue.
thanks,
Gerd
On Wed, 16 Jun 2021 at 15:20, Akihiko Odaki <akihiko.odaki@gmail.com> wrote: > > Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> Hi Akihiko -- I have a similar question here to the other patch about doing things not on the Cocoa UI thread... > +static void cocoa_clipboard_notify(Notifier *notifier, void *data) > +{ > + QemuClipboardInfo *info = data; > + > + if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { > + return; > + } > + > + if (info != cbinfo) { > + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; > + qemu_clipboard_info_unref(cbinfo); > + cbinfo = qemu_clipboard_info_ref(info); > + cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner]; > + [pool release]; Is this OK to do on a non-Cocoa thread with an autorelease pool, or should it be done via dispatch_async ? > + } > + > + qemu_event_set(&cbevent); > +} > /* > * 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 > @@ -1845,6 +1937,7 @@ static void addRemovableDevicesMenuItems(void) > COCOA_DEBUG("Second thread: calling qemu_main()\n"); > status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); > COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); > + [cbowner release]; > exit(status); > } > > @@ -1965,6 +2058,18 @@ static void cocoa_refresh(DisplayChangeListener *dcl) > [cocoaView setAbsoluteEnabled:YES]; > }); > } > + > + if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) { > + qemu_clipboard_info_unref(cbinfo); > + cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD); > + if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) { > + cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; > + } > + qemu_clipboard_update(cbinfo); > + cbchangecount = [[NSPasteboard generalPasteboard] changeCount]; > + qemu_event_set(&cbevent); > + } > + This work in the cocoa_refresh() function is done not on the Cocoa UI thread. Is it OK for it to do that, or should we put it into a dispatch_async block ? > [pool release]; > } thanks -- PMM
On Wed, Feb 9, 2022 at 2:54 AM Peter Maydell <peter.maydell@linaro.org> wrote: > > On Wed, 16 Jun 2021 at 15:20, Akihiko Odaki <akihiko.odaki@gmail.com> wrote: > > > > Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> > > Hi Akihiko -- I have a similar question here to the other > patch about doing things not on the Cocoa UI thread... > > > +static void cocoa_clipboard_notify(Notifier *notifier, void *data) > > +{ > > + QemuClipboardInfo *info = data; > > + > > + if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { > > + return; > > + } > > + > > + if (info != cbinfo) { > > + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; > > + qemu_clipboard_info_unref(cbinfo); > > + cbinfo = qemu_clipboard_info_ref(info); > > + cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner]; > > + [pool release]; > > Is this OK to do on a non-Cocoa thread with an autorelease pool, > or should it be done via dispatch_async ? > > > + } > > + > > + qemu_event_set(&cbevent); > > +} > > > /* > > * 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 > > @@ -1845,6 +1937,7 @@ static void addRemovableDevicesMenuItems(void) > > COCOA_DEBUG("Second thread: calling qemu_main()\n"); > > status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); > > COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); > > + [cbowner release]; > > exit(status); > > } > > > > @@ -1965,6 +2058,18 @@ static void cocoa_refresh(DisplayChangeListener *dcl) > > [cocoaView setAbsoluteEnabled:YES]; > > }); > > } > > + > > + if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) { > > + qemu_clipboard_info_unref(cbinfo); > > + cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD); > > + if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) { > > + cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; > > + } > > + qemu_clipboard_update(cbinfo); > > + cbchangecount = [[NSPasteboard generalPasteboard] changeCount]; > > + qemu_event_set(&cbevent); > > + } > > + > > This work in the cocoa_refresh() function is done not on the Cocoa > UI thread. Is it OK for it to do that, or should we put it into a > dispatch_async block ? > > > [pool release]; > > } > > thanks > -- PMM It should be fine since it doesn't touch NSView. The following documentation is the latest one which I have found so far: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1 It is unfortunate that Apple no longer updates it. They should at least note about the main thread requirement in the documentation of NSView. Regards, Akihiko Odaki
diff --git a/include/ui/clipboard.h b/include/ui/clipboard.h index e5bcb365ed6..b45b984c9fe 100644 --- a/include/ui/clipboard.h +++ b/include/ui/clipboard.h @@ -187,7 +187,7 @@ void qemu_clipboard_set_data(QemuClipboardPeer *peer, QemuClipboardInfo *info, QemuClipboardType type, uint32_t size, - void *data, + const void *data, bool update); #endif /* QEMU_CLIPBOARD_H */ diff --git a/ui/clipboard.c b/ui/clipboard.c index abf2b98f1f8..3525b30178b 100644 --- a/ui/clipboard.c +++ b/ui/clipboard.c @@ -73,7 +73,7 @@ void qemu_clipboard_set_data(QemuClipboardPeer *peer, QemuClipboardInfo *info, QemuClipboardType type, uint32_t size, - void *data, + const void *data, bool update) { if (!info || diff --git a/ui/cocoa.m b/ui/cocoa.m index 995301502be..1efd70ea789 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -28,6 +28,7 @@ #include <crt_externs.h> #include "qemu-common.h" +#include "ui/clipboard.h" #include "ui/console.h" #include "ui/input.h" #include "ui/kbd-state.h" @@ -106,6 +107,10 @@ static void cocoa_switch(DisplayChangeListener *dcl, static QemuSemaphore app_started_sem; static bool allow_events; +static NSInteger cbchangecount = -1; +static QemuClipboardInfo *cbinfo; +static QemuEvent cbevent; + // Utility functions to run specified code block with iothread lock held typedef void (^CodeBlock)(void); typedef bool (^BoolCodeBlock)(void); @@ -1811,6 +1816,93 @@ static void addRemovableDevicesMenuItems(void) qapi_free_BlockInfoList(pointerToFree); } +@interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner> +@end + +@implementation QemuCocoaPasteboardTypeOwner + +- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type +{ + if (type != NSPasteboardTypeString) { + return; + } + + with_iothread_lock(^{ + QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo); + qemu_event_reset(&cbevent); + qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT); + + while (info == cbinfo && + info->types[QEMU_CLIPBOARD_TYPE_TEXT].available && + info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) { + qemu_mutex_unlock_iothread(); + qemu_event_wait(&cbevent); + qemu_mutex_lock_iothread(); + } + + if (info == cbinfo) { + NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data + length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size]; + [sender setData:data forType:NSPasteboardTypeString]; + [data release]; + } + + qemu_clipboard_info_unref(info); + }); +} + +@end + +static QemuCocoaPasteboardTypeOwner *cbowner; + +static void cocoa_clipboard_notify(Notifier *notifier, void *data); +static void cocoa_clipboard_request(QemuClipboardInfo *info, + QemuClipboardType type); + +static QemuClipboardPeer cbpeer = { + .name = "cocoa", + .update = { .notify = cocoa_clipboard_notify }, + .request = cocoa_clipboard_request +}; + +static void cocoa_clipboard_notify(Notifier *notifier, void *data) +{ + QemuClipboardInfo *info = data; + + if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { + return; + } + + if (info != cbinfo) { + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + qemu_clipboard_info_unref(cbinfo); + cbinfo = qemu_clipboard_info_ref(info); + cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner]; + [pool release]; + } + + qemu_event_set(&cbevent); +} + +static void cocoa_clipboard_request(QemuClipboardInfo *info, + QemuClipboardType type) +{ + NSData *text; + + switch (type) { + case QEMU_CLIPBOARD_TYPE_TEXT: + text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString]; + if (text) { + qemu_clipboard_set_data(&cbpeer, info, type, + [text length], [text bytes], true); + [text release]; + } + break; + default: + break; + } +} + /* * 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 @@ -1845,6 +1937,7 @@ static void addRemovableDevicesMenuItems(void) COCOA_DEBUG("Second thread: calling qemu_main()\n"); status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); + [cbowner release]; exit(status); } @@ -1965,6 +2058,18 @@ static void cocoa_refresh(DisplayChangeListener *dcl) [cocoaView setAbsoluteEnabled:YES]; }); } + + if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) { + qemu_clipboard_info_unref(cbinfo); + cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD); + if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) { + cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; + } + qemu_clipboard_update(cbinfo); + cbchangecount = [[NSPasteboard generalPasteboard] changeCount]; + qemu_event_set(&cbevent); + } + [pool release]; } @@ -2001,6 +2106,10 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) // register vga output callbacks register_displaychangelistener(&dcl); + + qemu_event_init(&cbevent, false); + cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init]; + qemu_clipboard_peer_register(&cbpeer); } static QemuDisplay qemu_display_cocoa = {
Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com> --- include/ui/clipboard.h | 2 +- ui/clipboard.c | 2 +- ui/cocoa.m | 109 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 2 deletions(-)