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, QemuClipboardInfo *info,
QemuClipboardType type, QemuClipboardType type,
uint32_t size, uint32_t size,
void *data, const void *data,
bool update); bool update);
#endif /* QEMU_CLIPBOARD_H */ #endif /* QEMU_CLIPBOARD_H */

View File

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

View File

@ -1819,11 +1819,22 @@ SRST
old style -sdl/-curses/... options. Use ``-display help`` to list old style -sdl/-curses/... options. Use ``-display help`` to list
the available display types. Valid values for type are 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 Display video output via SDL (usually in a separate graphics
window; see the SDL documentation for other possibilities). 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 Display video output via curses. For graphics device models
which support a text mode, QEMU can display this output using a which support a text mode, QEMU can display this output using a
curses/ncurses interface. Nothing is displayed when the graphics curses/ncurses interface. Nothing is displayed when the graphics
@ -1834,6 +1845,11 @@ SRST
``charset=CP850`` for IBM CP850 encoding. The default is ``charset=CP850`` for IBM CP850 encoding. The default is
``CP437``. ``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`` ``none``
Do not display video output. The guest will still see an Do not display video output. The guest will still see an
emulated graphics card, but its output will not be displayed to 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 also changes the destination of the serial and parallel port
data. 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 ERST
DEF("nographic", 0, QEMU_OPTION_nographic, 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 * Not clear yet what happens to them long-term. Should
* replaced by something better or deprecated and dropped. * replaced by something better or deprecated and dropped.
*/ */
#if defined(CONFIG_SDL)
dpy.type = DISPLAY_TYPE_SDL; dpy.type = DISPLAY_TYPE_SDL;
while (*opts) { while (*opts) {
const char *nextopt; const char *nextopt;
@ -1131,6 +1132,10 @@ static void parse_display(const char *p)
} }
opts = nextopt; opts = nextopt;
} }
#else
error_report("SDL display supported is not available in this binary");
exit(1);
#endif
} else if (strstart(p, "vnc", &opts)) { } else if (strstart(p, "vnc", &opts)) {
/* /*
* vnc isn't a (local) DisplayType but a protocol for remote * 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) static void qemu_create_early_backends(void)
{ {
MachineClass *machine_class = MACHINE_GET_CLASS(current_machine); 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 " error_report("-alt-grab and -ctrl-grab are only valid "
"for SDL, ignoring option"); "for SDL, ignoring option");
} }
if (dpy.has_window_close && if (dpy.has_window_close && !use_gtk && !use_sdl) {
(dpy.type != DISPLAY_TYPE_GTK && dpy.type != DISPLAY_TYPE_SDL)) {
error_report("-no-quit is only valid for GTK and SDL, " error_report("-no-quit is only valid for GTK and SDL, "
"ignoring option"); "ignoring option");
} }

View File

@ -73,7 +73,7 @@ void qemu_clipboard_set_data(QemuClipboardPeer *peer,
QemuClipboardInfo *info, QemuClipboardInfo *info,
QemuClipboardType type, QemuClipboardType type,
uint32_t size, uint32_t size,
void *data, const void *data,
bool update) bool update)
{ {
if (!info || if (!info ||

View File

@ -28,6 +28,7 @@
#include <crt_externs.h> #include <crt_externs.h>
#include "qemu-common.h" #include "qemu-common.h"
#include "ui/clipboard.h"
#include "ui/console.h" #include "ui/console.h"
#include "ui/input.h" #include "ui/input.h"
#include "ui/kbd-state.h" #include "ui/kbd-state.h"
@ -105,6 +106,10 @@ static QemuSemaphore display_init_sem;
static QemuSemaphore app_started_sem; static QemuSemaphore app_started_sem;
static bool allow_events; 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 // Utility functions to run specified code block with iothread lock held
typedef void (^CodeBlock)(void); typedef void (^CodeBlock)(void);
typedef bool (^BoolCodeBlock)(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 - (void) switchSurface:(pixman_image_t *)image
{ {
COCOA_DEBUG("QemuCocoaView: switchSurface\n"); COCOA_DEBUG("QemuCocoaView: switchSurface\n");
@ -1172,6 +1214,16 @@ QemuCocoaView *cocoaView;
return [self verifyQuit]; 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 */ /* Called when the user clicks on a window's close button */
- (BOOL)windowShouldClose:(id)sender - (BOOL)windowShouldClose:(id)sender
{ {
@ -1711,6 +1763,93 @@ static void addRemovableDevicesMenuItems(void)
qapi_free_BlockInfoList(pointerToFree); 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 * 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 * 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"); COCOA_DEBUG("Second thread: calling qemu_main()\n");
status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); status = qemu_main(gArgc, gArgv, *_NSGetEnviron());
COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n");
[cbowner release];
exit(status); exit(status);
} }
@ -1836,6 +1976,8 @@ static void cocoa_switch(DisplayChangeListener *dcl,
COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
[cocoaView updateUIInfo];
// The DisplaySurface will be freed as soon as this callback returns. // The DisplaySurface will be freed as soon as this callback returns.
// We take a reference to the underlying pixman image here so it does // We take a reference to the underlying pixman image here so it does
// not disappear from under our feet; the switchSurface method will // not disappear from under our feet; the switchSurface method will
@ -1865,6 +2007,18 @@ static void cocoa_refresh(DisplayChangeListener *dcl)
[cocoaView setAbsoluteEnabled:YES]; [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]; [pool release];
} }
@ -1890,6 +2044,10 @@ static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
// register vga output callbacks // register vga output callbacks
register_displaychangelistener(&dcl); register_displaychangelistener(&dcl);
qemu_event_init(&cbevent, false);
cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
qemu_clipboard_peer_register(&cbpeer);
} }
static QemuDisplay qemu_display_cocoa = { static QemuDisplay qemu_display_cocoa = {

View File

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