Skip to content

Commit

Permalink
ui/cocoa: Add clipboard support
Browse files Browse the repository at this point in the history
Signed-off-by: Akihiko Odaki <akihiko.odaki@gmail.com>
Message-Id: <20210616141954.54291-1-akihiko.odaki@gmail.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
  • Loading branch information
akihikodaki authored and kraxel committed Jun 23, 2021
1 parent 15280e8 commit 7e3e20d
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 2 deletions.
2 changes: 1 addition & 1 deletion include/ui/clipboard.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
2 changes: 1 addition & 1 deletion ui/clipboard.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ||
Expand Down
109 changes: 109 additions & 0 deletions ui/cocoa.m
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -105,6 +106,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);
Expand Down Expand Up @@ -1758,6 +1763,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
Expand Down Expand Up @@ -1792,6 +1884,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);
}

Expand Down Expand Up @@ -1914,6 +2007,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];
}

Expand All @@ -1939,6 +2044,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 = {
Expand Down

0 comments on commit 7e3e20d

Please sign in to comment.