ui: better cocoa integration (ui info + clipboard).

ui: add lang1+lang2 keys, fixes, doc updates.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEoDKM/7k6F6eZAf59TLbY7tPocTgFAmDUQyQACgkQTLbY7tPo
 cTiBVRAAqFkg8yKkLQnIYy8CEZGwwDzUxbtxSMQNqnWRVSrz0HVLvmTViMDy25VP
 lMc+6Eo4l3rx+pyrubc39WtqtRp8uZwCwMnOXstt6VSayiiP61KkYahmSzz2PztM
 AHYcfMbPbARqKXo2c7z4GrrcoXmUpuAEkez7c6mLl17+2S+IQNUp45Wk0p6rZO6J
 I2gh0CaG4/e5981Hxwai4FkRm+WxV9PcBPJU3ac5tOrdt16CC0HWE8+KmCs2RA1L
 JxRuvBgXZH0pHCKnAPT2jOQOq/LLHddbCiaSgiWTtqMEXqF5WSVn+NDkrrOnkfI3
 hsDlHiqMRWBtxiK3slCmFU0GSPz3mijy3PZHx+0RSaLSwbp1EHOERSxDX/n/n6jj
 2Y8sQV5NVmo5YtxrgXdw8fVexaS5C6Gp1mHTThgzepsTAHEgY8vmmLr3M1GXwE4M
 yaD+4hVnDP0tCXP6nVYsYdrE+fgY14JE5EvQWqJ5v23tiudrQ2Ol+mcOiyhLE/aF
 2B1HJFQ5J0h2rxSxIIr0IEwWiAE3ohqvUMfNao9o6+ICFDPVbH3pJnl1N1NGQzbl
 JAcvlkpGskNp5pNmQcgj2b9xT7S/jCR8qlBVQwAefixSPWuscEqNSv7yBRKTaZ/f
 kOlVXqU4go0F7FGe9c5alezRRZGCpQz9Wc9DFFsJX0yUogojFVU=
 =skkB
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/kraxel/tags/ui-20210624-pull-request' into staging

ui: better cocoa integration (ui info + clipboard).
ui: add lang1+lang2 keys, fixes, doc updates.

# gpg: Signature made Thu 24 Jun 2021 09:32:36 BST
# gpg:                using RSA key A0328CFFB93A17A79901FE7D4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" [full]
# gpg:                 aka "Gerd Hoffmann <gerd@kraxel.org>" [full]
# gpg:                 aka "Gerd Hoffmann (private) <kraxel@gmail.com>" [full]
# Primary key fingerprint: A032 8CFF B93A 17A7 9901  FE7D 4CB6 D8EE D3E8 7138

* remotes/kraxel/tags/ui-20210624-pull-request:
  ui: Make the DisplayType enum entries conditional
  Add display suboptions to man pages
  input: Add lang1 and lang2 to QKeyCode
  ui/cocoa: Add clipboard support
  ui/cocoa: Set UI information

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2021-06-25 09:10:37 +01:00
commit e0da9171e0
7 changed files with 224 additions and 30 deletions

View File

@ -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 */

View File

@ -786,6 +786,9 @@
# @muhenkan: since 2.12
# @katakanahiragana: since 2.12
#
# @lang1: since 6.1
# @lang2: since 6.1
#
# 'sysrq' was mistakenly added to hack around the fact that
# the ps2 driver was not generating correct scancodes sequences
# when 'alt+print' was pressed. This flaw is now fixed and the
@ -818,7 +821,8 @@
'audionext', 'audioprev', 'audiostop', 'audioplay', 'audiomute',
'volumeup', 'volumedown', 'mediaselect',
'mail', 'calculator', 'computer',
'ac_home', 'ac_back', 'ac_forward', 'ac_refresh', 'ac_bookmarks' ] }
'ac_home', 'ac_back', 'ac_forward', 'ac_refresh', 'ac_bookmarks',
'lang1', 'lang2' ] }
##
# @KeyValue:
@ -1126,9 +1130,16 @@
#
##
{ 'enum' : 'DisplayType',
'data' : [ 'default', 'none', 'gtk', 'sdl',
'egl-headless', 'curses', 'cocoa',
'spice-app'] }
'data' : [
{ 'name': 'default' },
{ 'name': 'none' },
{ 'name': 'gtk', 'if': 'defined(CONFIG_GTK)' },
{ 'name': 'sdl', 'if': 'defined(CONFIG_SDL)' },
{ 'name': 'egl-headless',
'if': 'defined(CONFIG_OPENGL) && defined(CONFIG_GBM)' },
{ 'name': 'curses', 'if': 'defined(CONFIG_CURSES)' },
{ 'name': 'cocoa', 'if': 'defined(CONFIG_COCOA)' },
{ 'name': 'spice-app', 'if': 'defined(CONFIG_SPICE)'} ] }
##
# @DisplayOptions:
@ -1152,9 +1163,13 @@
'*show-cursor' : 'bool',
'*gl' : 'DisplayGLMode' },
'discriminator' : 'type',
'data' : { 'gtk' : 'DisplayGTK',
'curses' : 'DisplayCurses',
'egl-headless' : 'DisplayEGLHeadless'} }
'data' : {
'gtk': { 'type': 'DisplayGTK', 'if': 'defined(CONFIG_GTK)' },
'curses': { 'type': 'DisplayCurses', 'if': 'defined(CONFIG_CURSES)' },
'egl-headless': { 'type': 'DisplayEGLHeadless',
'if': 'defined(CONFIG_OPENGL) && defined(CONFIG_GBM)' }
}
}
##
# @query-display-options:

View File

@ -1819,11 +1819,22 @@ SRST
old style -sdl/-curses/... options. Use ``-display help`` to list
the available display types. Valid values for type are
``sdl``
``spice-app[,gl=on|off]``
Start QEMU as a Spice server and launch the default Spice client
application. The Spice server will redirect the serial consoles
and QEMU monitors. (Since 4.0)
``sdl[,window-close=on|off][,gl=on|core|es|off]``
Display video output via SDL (usually in a separate graphics
window; see the SDL documentation for other possibilities).
``curses``
``gtk[,grab-on-hover=on|off][,gl=on|off]``
Display video output in a GTK window. This interface provides
drop-down menus and other UI elements to configure and control
the VM during runtime.
``curses [,charset=<encoding>]``
Display video output via curses. For graphics device models
which support a text mode, QEMU can display this output using a
curses/ncurses interface. Nothing is displayed when the graphics
@ -1834,6 +1845,11 @@ SRST
``charset=CP850`` for IBM CP850 encoding. The default is
``CP437``.
``egl-headless[,rendernode<file>]``
Offload all OpenGL operations to a local DRI device. For any
graphical display, this display needs to be paired with either
VNC or SPICE displays.
``none``
Do not display video output. The guest will still see an
emulated graphics card, but its output will not be displayed to
@ -1842,23 +1858,8 @@ SRST
also changes the destination of the serial and parallel port
data.
``gtk``
Display video output in a GTK window. This interface provides
drop-down menus and other UI elements to configure and control
the VM during runtime.
``vnc``
Start a VNC server on display <arg>
``egl-headless``
Offload all OpenGL operations to a local DRI device. For any
graphical display, this display needs to be paired with either
VNC or SPICE displays.
``spice-app``
Start QEMU as a Spice server and launch the default Spice client
application. The Spice server will redirect the serial consoles
and QEMU monitors. (Since 4.0)
ERST
DEF("nographic", 0, QEMU_OPTION_nographic,

View File

@ -1068,6 +1068,7 @@ static void parse_display(const char *p)
* Not clear yet what happens to them long-term. Should
* replaced by something better or deprecated and dropped.
*/
#if defined(CONFIG_SDL)
dpy.type = DISPLAY_TYPE_SDL;
while (*opts) {
const char *nextopt;
@ -1131,6 +1132,10 @@ static void parse_display(const char *p)
}
opts = nextopt;
}
#else
error_report("SDL display supported is not available in this binary");
exit(1);
#endif
} else if (strstart(p, "vnc", &opts)) {
/*
* vnc isn't a (local) DisplayType but a protocol for remote
@ -1867,13 +1872,22 @@ static void qemu_apply_machine_options(void)
static void qemu_create_early_backends(void)
{
MachineClass *machine_class = MACHINE_GET_CLASS(current_machine);
#if defined(CONFIG_SDL)
const bool use_sdl = (dpy.type == DISPLAY_TYPE_SDL);
#else
const bool use_sdl = false;
#endif
#if defined(CONFIG_GTK)
const bool use_gtk = (dpy.type == DISPLAY_TYPE_GTK);
#else
const bool use_gtk = false;
#endif
if ((alt_grab || ctrl_grab) && dpy.type != DISPLAY_TYPE_SDL) {
if ((alt_grab || ctrl_grab) && !use_sdl) {
error_report("-alt-grab and -ctrl-grab are only valid "
"for SDL, ignoring option");
}
if (dpy.has_window_close &&
(dpy.type != DISPLAY_TYPE_GTK && dpy.type != DISPLAY_TYPE_SDL)) {
if (dpy.has_window_close && !use_gtk && !use_sdl) {
error_report("-no-quit is only valid for GTK and SDL, "
"ignoring option");
}

View File

@ -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 ||

View File

@ -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"
@ -105,6 +106,10 @@ static QemuSemaphore display_init_sem;
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);
@ -518,6 +523,43 @@ QemuCocoaView *cocoaView;
}
}
- (void) updateUIInfo
{
NSSize frameSize;
QemuUIInfo info;
if (!qemu_console_is_graphic(dcl.con)) {
return;
}
if ([self window]) {
NSDictionary *description = [[[self window] screen] deviceDescription];
CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
NSSize screenSize = [[[self window] screen] frame].size;
CGSize screenPhysicalSize = CGDisplayScreenSize(display);
frameSize = isFullscreen ? screenSize : [self frame].size;
info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
} else {
frameSize = [self frame].size;
info.width_mm = 0;
info.height_mm = 0;
}
info.xoff = 0;
info.yoff = 0;
info.width = frameSize.width;
info.height = frameSize.height;
dpy_set_ui_info(dcl.con, &info);
}
- (void)viewDidMoveToWindow
{
[self updateUIInfo];
}
- (void) switchSurface:(pixman_image_t *)image
{
COCOA_DEBUG("QemuCocoaView: switchSurface\n");
@ -1172,6 +1214,16 @@ QemuCocoaView *cocoaView;
return [self verifyQuit];
}
- (void)windowDidChangeScreen:(NSNotification *)notification
{
[cocoaView updateUIInfo];
}
- (void)windowDidResize:(NSNotification *)notification
{
[cocoaView updateUIInfo];
}
/* Called when the user clicks on a window's close button */
- (BOOL)windowShouldClose:(id)sender
{
@ -1711,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
@ -1745,6 +1884,7 @@ static void *call_qemu_main(void *opaque)
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);
}
@ -1836,6 +1976,8 @@ static void cocoa_switch(DisplayChangeListener *dcl,
COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
[cocoaView updateUIInfo];
// 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
@ -1865,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];
}
@ -1890,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 = {

View File

@ -2370,13 +2370,19 @@ void qemu_display_register(QemuDisplay *ui)
bool qemu_display_find_default(DisplayOptions *opts)
{
static DisplayType prio[] = {
#if defined(CONFIG_GTK)
DISPLAY_TYPE_GTK,
#endif
#if defined(CONFIG_SDL)
DISPLAY_TYPE_SDL,
#endif
#if defined(CONFIG_COCOA)
DISPLAY_TYPE_COCOA
#endif
};
int i;
for (i = 0; i < ARRAY_SIZE(prio); i++) {
for (i = 0; i < (int)ARRAY_SIZE(prio); i++) {
if (dpys[prio[i]] == NULL) {
ui_module_load_one(DisplayType_str(prio[i]));
}