aboutsummaryrefslogtreecommitdiffstats
path: root/ui
diff options
context:
space:
mode:
Diffstat (limited to 'ui')
-rw-r--r--ui/clipboard.c133
-rw-r--r--ui/cocoa.m2063
-rw-r--r--ui/console-gl.c158
-rw-r--r--ui/console.c2460
-rw-r--r--ui/curses.c817
-rw-r--r--ui/curses_keys.h537
-rw-r--r--ui/cursor.c243
-rw-r--r--ui/cursor_hidden.xpm37
-rw-r--r--ui/cursor_left_ptr.xpm39
-rw-r--r--ui/egl-context.c37
-rw-r--r--ui/egl-headless.c217
-rw-r--r--ui/egl-helpers.c529
-rw-r--r--ui/gtk-clipboard.c192
-rw-r--r--ui/gtk-egl.c372
-rw-r--r--ui/gtk-gl-area.c288
-rw-r--r--ui/gtk.c2397
-rw-r--r--ui/icons/Makefile13
-rw-r--r--ui/icons/meson.build13
-rw-r--r--ui/icons/qemu.svg976
-rw-r--r--ui/icons/qemu_128x128.pngbin0 -> 8286 bytes
-rw-r--r--ui/icons/qemu_16x16.pngbin0 -> 765 bytes
-rw-r--r--ui/icons/qemu_24x24.pngbin0 -> 1201 bytes
-rw-r--r--ui/icons/qemu_256x256.pngbin0 -> 17572 bytes
-rw-r--r--ui/icons/qemu_32x32.bmpbin0 -> 4234 bytes
-rw-r--r--ui/icons/qemu_32x32.pngbin0 -> 1696 bytes
-rw-r--r--ui/icons/qemu_48x48.pngbin0 -> 2694 bytes
-rw-r--r--ui/icons/qemu_512x512.pngbin0 -> 38007 bytes
-rw-r--r--ui/icons/qemu_64x64.pngbin0 -> 3807 bytes
-rw-r--r--ui/input-barrier.c746
-rw-r--r--ui/input-barrier.h112
-rw-r--r--ui/input-keymap.c89
-rw-r--r--ui/input-legacy.c275
-rw-r--r--ui/input-linux.c534
-rw-r--r--ui/input.c622
-rw-r--r--ui/kbd-state.c137
-rw-r--r--ui/keymaps.c273
-rw-r--r--ui/keymaps.h62
-rw-r--r--ui/meson.build151
-rw-r--r--ui/qemu-pixman.c280
-rw-r--r--ui/qemu-x509.h9
-rw-r--r--ui/qemu.desktop8
-rw-r--r--ui/sdl2-2d.c165
-rw-r--r--ui/sdl2-gl.c245
-rw-r--r--ui/sdl2-input.c59
-rw-r--r--ui/sdl2.c927
-rw-r--r--ui/shader.c174
-rw-r--r--ui/shader/meson.build14
-rw-r--r--ui/shader/texture-blit-flip.vert10
-rw-r--r--ui/shader/texture-blit.frag10
-rw-r--r--ui/shader/texture-blit.vert10
-rw-r--r--ui/spice-app.c227
-rw-r--r--ui/spice-core.c1039
-rw-r--r--ui/spice-display.c1199
-rw-r--r--ui/spice-input.c260
-rw-r--r--ui/spice-module.c85
-rw-r--r--ui/trace-events137
-rw-r--r--ui/trace.h1
-rw-r--r--ui/udmabuf.c29
-rw-r--r--ui/vdagent.c871
-rw-r--r--ui/vgafont.h4611
-rw-r--r--ui/vnc-auth-sasl.c692
-rw-r--r--ui/vnc-auth-sasl.h74
-rw-r--r--ui/vnc-auth-vencrypt.c166
-rw-r--r--ui/vnc-auth-vencrypt.h32
-rw-r--r--ui/vnc-clipboard.c325
-rw-r--r--ui/vnc-enc-hextile-template.h211
-rw-r--r--ui/vnc-enc-hextile.c84
-rw-r--r--ui/vnc-enc-tight.c1712
-rw-r--r--ui/vnc-enc-tight.h183
-rw-r--r--ui/vnc-enc-zlib.c154
-rw-r--r--ui/vnc-enc-zrle.c366
-rw-r--r--ui/vnc-enc-zrle.c.inc263
-rw-r--r--ui/vnc-enc-zrle.h40
-rw-r--r--ui/vnc-enc-zywrle-template.c171
-rw-r--r--ui/vnc-enc-zywrle.h659
-rw-r--r--ui/vnc-jobs.c382
-rw-r--r--ui/vnc-jobs.h68
-rw-r--r--ui/vnc-palette.c159
-rw-r--r--ui/vnc-palette.h66
-rw-r--r--ui/vnc-stubs.c24
-rw-r--r--ui/vnc-ws.c150
-rw-r--r--ui/vnc-ws.h31
-rw-r--r--ui/vnc.c4312
-rw-r--r--ui/vnc.h645
-rw-r--r--ui/vnc_keysym.h722
-rw-r--r--ui/win32-kbd-hook.c102
-rw-r--r--ui/x_keymap.c119
-rw-r--r--ui/x_keymap.h32
88 files changed, 36836 insertions, 0 deletions
diff --git a/ui/clipboard.c b/ui/clipboard.c
new file mode 100644
index 000000000..d7b008d62
--- /dev/null
+++ b/ui/clipboard.c
@@ -0,0 +1,133 @@
+#include "qemu/osdep.h"
+#include "ui/clipboard.h"
+
+static NotifierList clipboard_notifiers =
+ NOTIFIER_LIST_INITIALIZER(clipboard_notifiers);
+
+static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT];
+
+void qemu_clipboard_peer_register(QemuClipboardPeer *peer)
+{
+ notifier_list_add(&clipboard_notifiers, &peer->update);
+}
+
+void qemu_clipboard_peer_unregister(QemuClipboardPeer *peer)
+{
+ int i;
+
+ for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) {
+ qemu_clipboard_peer_release(peer, i);
+ }
+
+ notifier_remove(&peer->update);
+}
+
+bool qemu_clipboard_peer_owns(QemuClipboardPeer *peer,
+ QemuClipboardSelection selection)
+{
+ QemuClipboardInfo *info = qemu_clipboard_info(selection);
+
+ return info && info->owner == peer;
+}
+
+void qemu_clipboard_peer_release(QemuClipboardPeer *peer,
+ QemuClipboardSelection selection)
+{
+ g_autoptr(QemuClipboardInfo) info = NULL;
+
+ if (qemu_clipboard_peer_owns(peer, selection)) {
+ /* set empty clipboard info */
+ info = qemu_clipboard_info_new(NULL, selection);
+ qemu_clipboard_update(info);
+ }
+}
+
+void qemu_clipboard_update(QemuClipboardInfo *info)
+{
+ g_autoptr(QemuClipboardInfo) old = NULL;
+ assert(info->selection < QEMU_CLIPBOARD_SELECTION__COUNT);
+
+ notifier_list_notify(&clipboard_notifiers, info);
+
+ old = cbinfo[info->selection];
+ cbinfo[info->selection] = qemu_clipboard_info_ref(info);
+}
+
+QemuClipboardInfo *qemu_clipboard_info(QemuClipboardSelection selection)
+{
+ assert(selection < QEMU_CLIPBOARD_SELECTION__COUNT);
+
+ return cbinfo[selection];
+}
+
+QemuClipboardInfo *qemu_clipboard_info_new(QemuClipboardPeer *owner,
+ QemuClipboardSelection selection)
+{
+ QemuClipboardInfo *info = g_new0(QemuClipboardInfo, 1);
+
+ info->owner = owner;
+ info->selection = selection;
+ info->refcount = 1;
+
+ return info;
+}
+
+QemuClipboardInfo *qemu_clipboard_info_ref(QemuClipboardInfo *info)
+{
+ info->refcount++;
+ return info;
+}
+
+void qemu_clipboard_info_unref(QemuClipboardInfo *info)
+{
+ uint32_t type;
+
+ if (!info) {
+ return;
+ }
+
+ info->refcount--;
+ if (info->refcount > 0) {
+ return;
+ }
+
+ for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
+ g_free(info->types[type].data);
+ }
+ g_free(info);
+}
+
+void qemu_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ if (info->types[type].data ||
+ info->types[type].requested ||
+ !info->types[type].available ||
+ !info->owner)
+ return;
+
+ info->types[type].requested = true;
+ info->owner->request(info, type);
+}
+
+void qemu_clipboard_set_data(QemuClipboardPeer *peer,
+ QemuClipboardInfo *info,
+ QemuClipboardType type,
+ uint32_t size,
+ const void *data,
+ bool update)
+{
+ if (!info ||
+ info->owner != peer) {
+ return;
+ }
+
+ g_free(info->types[type].data);
+ info->types[type].data = g_memdup(data, size);
+ info->types[type].size = size;
+ info->types[type].available = true;
+
+ if (update) {
+ qemu_clipboard_update(info);
+ }
+}
diff --git a/ui/cocoa.m b/ui/cocoa.m
new file mode 100644
index 000000000..68a630218
--- /dev/null
+++ b/ui/cocoa.m
@@ -0,0 +1,2063 @@
+/*
+ * QEMU Cocoa CG display driver
+ *
+ * Copyright (c) 2008 Mike Kronenberg
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+#import <Cocoa/Cocoa.h>
+#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"
+#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
+#include "sysemu/cpu-throttle.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-block.h"
+#include "qapi/qapi-commands-machine.h"
+#include "qapi/qapi-commands-misc.h"
+#include "sysemu/blockdev.h"
+#include "qemu-version.h"
+#include "qemu/cutils.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include <Carbon/Carbon.h>
+#include "hw/core/cpu.h"
+
+#ifndef MAC_OS_X_VERSION_10_13
+#define MAC_OS_X_VERSION_10_13 101300
+#endif
+
+/* 10.14 deprecates NSOnState and NSOffState in favor of
+ * NSControlStateValueOn/Off, which were introduced in 10.13.
+ * Define for older versions
+ */
+#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
+#define NSControlStateValueOn NSOnState
+#define NSControlStateValueOff NSOffState
+#endif
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); }
+#else
+#define COCOA_DEBUG(...) ((void) 0)
+#endif
+
+#define cgrect(nsrect) (*(CGRect *)&(nsrect))
+
+typedef struct {
+ int width;
+ int height;
+} QEMUScreen;
+
+static void cocoa_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h);
+
+static void cocoa_switch(DisplayChangeListener *dcl,
+ DisplaySurface *surface);
+
+static void cocoa_refresh(DisplayChangeListener *dcl);
+
+static NSWindow *normalWindow, *about_window;
+static const DisplayChangeListenerOps dcl_ops = {
+ .dpy_name = "cocoa",
+ .dpy_gfx_update = cocoa_update,
+ .dpy_gfx_switch = cocoa_switch,
+ .dpy_refresh = cocoa_refresh,
+};
+static DisplayChangeListener dcl = {
+ .ops = &dcl_ops,
+};
+static int last_buttons;
+static int cursor_hide = 1;
+
+static int gArgc;
+static char **gArgv;
+static bool stretch_video;
+static NSTextField *pauseLabel;
+static NSArray * supportedImageFileTypes;
+
+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);
+
+static void with_iothread_lock(CodeBlock block)
+{
+ bool locked = qemu_mutex_iothread_locked();
+ if (!locked) {
+ qemu_mutex_lock_iothread();
+ }
+ block();
+ if (!locked) {
+ qemu_mutex_unlock_iothread();
+ }
+}
+
+static bool bool_with_iothread_lock(BoolCodeBlock block)
+{
+ bool locked = qemu_mutex_iothread_locked();
+ bool val;
+
+ if (!locked) {
+ qemu_mutex_lock_iothread();
+ }
+ val = block();
+ if (!locked) {
+ qemu_mutex_unlock_iothread();
+ }
+ return val;
+}
+
+// Mac to QKeyCode conversion
+static const int mac_to_qkeycode_map[] = {
+ [kVK_ANSI_A] = Q_KEY_CODE_A,
+ [kVK_ANSI_B] = Q_KEY_CODE_B,
+ [kVK_ANSI_C] = Q_KEY_CODE_C,
+ [kVK_ANSI_D] = Q_KEY_CODE_D,
+ [kVK_ANSI_E] = Q_KEY_CODE_E,
+ [kVK_ANSI_F] = Q_KEY_CODE_F,
+ [kVK_ANSI_G] = Q_KEY_CODE_G,
+ [kVK_ANSI_H] = Q_KEY_CODE_H,
+ [kVK_ANSI_I] = Q_KEY_CODE_I,
+ [kVK_ANSI_J] = Q_KEY_CODE_J,
+ [kVK_ANSI_K] = Q_KEY_CODE_K,
+ [kVK_ANSI_L] = Q_KEY_CODE_L,
+ [kVK_ANSI_M] = Q_KEY_CODE_M,
+ [kVK_ANSI_N] = Q_KEY_CODE_N,
+ [kVK_ANSI_O] = Q_KEY_CODE_O,
+ [kVK_ANSI_P] = Q_KEY_CODE_P,
+ [kVK_ANSI_Q] = Q_KEY_CODE_Q,
+ [kVK_ANSI_R] = Q_KEY_CODE_R,
+ [kVK_ANSI_S] = Q_KEY_CODE_S,
+ [kVK_ANSI_T] = Q_KEY_CODE_T,
+ [kVK_ANSI_U] = Q_KEY_CODE_U,
+ [kVK_ANSI_V] = Q_KEY_CODE_V,
+ [kVK_ANSI_W] = Q_KEY_CODE_W,
+ [kVK_ANSI_X] = Q_KEY_CODE_X,
+ [kVK_ANSI_Y] = Q_KEY_CODE_Y,
+ [kVK_ANSI_Z] = Q_KEY_CODE_Z,
+
+ [kVK_ANSI_0] = Q_KEY_CODE_0,
+ [kVK_ANSI_1] = Q_KEY_CODE_1,
+ [kVK_ANSI_2] = Q_KEY_CODE_2,
+ [kVK_ANSI_3] = Q_KEY_CODE_3,
+ [kVK_ANSI_4] = Q_KEY_CODE_4,
+ [kVK_ANSI_5] = Q_KEY_CODE_5,
+ [kVK_ANSI_6] = Q_KEY_CODE_6,
+ [kVK_ANSI_7] = Q_KEY_CODE_7,
+ [kVK_ANSI_8] = Q_KEY_CODE_8,
+ [kVK_ANSI_9] = Q_KEY_CODE_9,
+
+ [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
+ [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
+ [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
+ [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
+ [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
+ [kVK_Tab] = Q_KEY_CODE_TAB,
+ [kVK_Return] = Q_KEY_CODE_RET,
+ [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
+ [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
+ [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
+ [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
+ [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
+ [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
+ [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
+ [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
+ [kVK_Space] = Q_KEY_CODE_SPC,
+
+ [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
+ [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
+ [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
+ [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
+ [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
+ [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
+ [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
+ [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
+ [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
+ [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
+ [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
+ [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
+ [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
+ [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
+ [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
+ [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
+ [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
+ [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
+
+ [kVK_UpArrow] = Q_KEY_CODE_UP,
+ [kVK_DownArrow] = Q_KEY_CODE_DOWN,
+ [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
+ [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
+
+ [kVK_Help] = Q_KEY_CODE_INSERT,
+ [kVK_Home] = Q_KEY_CODE_HOME,
+ [kVK_PageUp] = Q_KEY_CODE_PGUP,
+ [kVK_PageDown] = Q_KEY_CODE_PGDN,
+ [kVK_End] = Q_KEY_CODE_END,
+ [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
+
+ [kVK_Escape] = Q_KEY_CODE_ESC,
+
+ /* The Power key can't be used directly because the operating system uses
+ * it. This key can be emulated by using it in place of another key such as
+ * F1. Don't forget to disable the real key binding.
+ */
+ /* [kVK_F1] = Q_KEY_CODE_POWER, */
+
+ [kVK_F1] = Q_KEY_CODE_F1,
+ [kVK_F2] = Q_KEY_CODE_F2,
+ [kVK_F3] = Q_KEY_CODE_F3,
+ [kVK_F4] = Q_KEY_CODE_F4,
+ [kVK_F5] = Q_KEY_CODE_F5,
+ [kVK_F6] = Q_KEY_CODE_F6,
+ [kVK_F7] = Q_KEY_CODE_F7,
+ [kVK_F8] = Q_KEY_CODE_F8,
+ [kVK_F9] = Q_KEY_CODE_F9,
+ [kVK_F10] = Q_KEY_CODE_F10,
+ [kVK_F11] = Q_KEY_CODE_F11,
+ [kVK_F12] = Q_KEY_CODE_F12,
+ [kVK_F13] = Q_KEY_CODE_PRINT,
+ [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
+ [kVK_F15] = Q_KEY_CODE_PAUSE,
+
+ // JIS keyboards only
+ [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
+ [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
+ [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
+ [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
+ [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
+
+ /*
+ * The eject and volume keys can't be used here because they are handled at
+ * a lower level than what an Application can see.
+ */
+};
+
+static int cocoa_keycode_to_qemu(int keycode)
+{
+ if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
+ error_report("(cocoa) warning unknown keycode 0x%x", keycode);
+ return 0;
+ }
+ return mac_to_qkeycode_map[keycode];
+}
+
+/* Displays an alert dialog box with the specified message */
+static void QEMU_Alert(NSString *message)
+{
+ NSAlert *alert;
+ alert = [NSAlert new];
+ [alert setMessageText: message];
+ [alert runModal];
+}
+
+/* Handles any errors that happen with a device transaction */
+static void handleAnyDeviceErrors(Error * err)
+{
+ if (err) {
+ QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
+ encoding: NSASCIIStringEncoding]);
+ error_free(err);
+ }
+}
+
+/*
+ ------------------------------------------------------
+ QemuCocoaView
+ ------------------------------------------------------
+*/
+@interface QemuCocoaView : NSView
+{
+ QEMUScreen screen;
+ NSWindow *fullScreenWindow;
+ float cx,cy,cw,ch,cdx,cdy;
+ pixman_image_t *pixman_image;
+ QKbdState *kbd;
+ BOOL isMouseGrabbed;
+ BOOL isFullscreen;
+ BOOL isAbsoluteEnabled;
+}
+- (void) switchSurface:(pixman_image_t *)image;
+- (void) grabMouse;
+- (void) ungrabMouse;
+- (void) toggleFullScreen:(id)sender;
+- (void) handleMonitorInput:(NSEvent *)event;
+- (bool) handleEvent:(NSEvent *)event;
+- (bool) handleEventLocked:(NSEvent *)event;
+- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
+/* The state surrounding mouse grabbing is potentially confusing.
+ * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
+ * pointing device an absolute-position one?"], but is only updated on
+ * next refresh.
+ * isMouseGrabbed tracks whether GUI events are directed to the guest;
+ * it controls whether special keys like Cmd get sent to the guest,
+ * and whether we capture the mouse when in non-absolute mode.
+ */
+- (BOOL) isMouseGrabbed;
+- (BOOL) isAbsoluteEnabled;
+- (float) cdx;
+- (float) cdy;
+- (QEMUScreen) gscreen;
+- (void) raiseAllKeys;
+@end
+
+QemuCocoaView *cocoaView;
+
+@implementation QemuCocoaView
+- (id)initWithFrame:(NSRect)frameRect
+{
+ COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
+
+ self = [super initWithFrame:frameRect];
+ if (self) {
+
+ screen.width = frameRect.size.width;
+ screen.height = frameRect.size.height;
+ kbd = qkbd_state_init(dcl.con);
+
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ COCOA_DEBUG("QemuCocoaView: dealloc\n");
+
+ if (pixman_image) {
+ pixman_image_unref(pixman_image);
+ }
+
+ qkbd_state_free(kbd);
+ [super dealloc];
+}
+
+- (BOOL) isOpaque
+{
+ return YES;
+}
+
+- (BOOL) screenContainsPoint:(NSPoint) p
+{
+ return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
+}
+
+/* Get location of event and convert to virtual screen coordinate */
+- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
+{
+ NSWindow *eventWindow = [ev window];
+ // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10
+ CGRect r = CGRectZero;
+ r.origin = [ev locationInWindow];
+ if (!eventWindow) {
+ if (!isFullscreen) {
+ return [[self window] convertRectFromScreen:r].origin;
+ } else {
+ CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin;
+ CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil];
+ if (stretch_video) {
+ loc.x /= cdx;
+ loc.y /= cdy;
+ }
+ return loc;
+ }
+ } else if ([[self window] isEqual:eventWindow]) {
+ if (!isFullscreen) {
+ return r.origin;
+ } else {
+ CGPoint loc = [self convertPoint:r.origin fromView:nil];
+ if (stretch_video) {
+ loc.x /= cdx;
+ loc.y /= cdy;
+ }
+ return loc;
+ }
+ } else {
+ return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin;
+ }
+}
+
+- (void) hideCursor
+{
+ if (!cursor_hide) {
+ return;
+ }
+ [NSCursor hide];
+}
+
+- (void) unhideCursor
+{
+ if (!cursor_hide) {
+ return;
+ }
+ [NSCursor unhide];
+}
+
+- (void) drawRect:(NSRect) rect
+{
+ COCOA_DEBUG("QemuCocoaView: drawRect\n");
+
+ // get CoreGraphic context
+ CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
+
+ CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
+ CGContextSetShouldAntialias (viewContextRef, NO);
+
+ // draw screen bitmap directly to Core Graphics context
+ if (!pixman_image) {
+ // Draw request before any guest device has set up a framebuffer:
+ // just draw an opaque black rectangle
+ CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
+ CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
+ } else {
+ int w = pixman_image_get_width(pixman_image);
+ int h = pixman_image_get_height(pixman_image);
+ int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
+ int stride = pixman_image_get_stride(pixman_image);
+ CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
+ NULL,
+ pixman_image_get_data(pixman_image),
+ stride * h,
+ NULL
+ );
+ CGImageRef imageRef = CGImageCreate(
+ w, //width
+ h, //height
+ DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
+ bitsPerPixel, //bitsPerPixel
+ stride, //bytesPerRow
+ CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
+ kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
+ dataProviderRef, //provider
+ NULL, //decode
+ 0, //interpolate
+ kCGRenderingIntentDefault //intent
+ );
+ // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
+ const NSRect *rectList;
+ NSInteger rectCount;
+ int i;
+ CGImageRef clipImageRef;
+ CGRect clipRect;
+
+ [self getRectsBeingDrawn:&rectList count:&rectCount];
+ for (i = 0; i < rectCount; i++) {
+ clipRect.origin.x = rectList[i].origin.x / cdx;
+ clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy;
+ clipRect.size.width = rectList[i].size.width / cdx;
+ clipRect.size.height = rectList[i].size.height / cdy;
+ clipImageRef = CGImageCreateWithImageInRect(
+ imageRef,
+ clipRect
+ );
+ CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
+ CGImageRelease (clipImageRef);
+ }
+ CGImageRelease (imageRef);
+ CGDataProviderRelease(dataProviderRef);
+ }
+}
+
+- (void) setContentDimensions
+{
+ COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
+
+ if (isFullscreen) {
+ cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
+ cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
+
+ /* stretches video, but keeps same aspect ratio */
+ if (stretch_video == true) {
+ /* use smallest stretch value - prevents clipping on sides */
+ if (MIN(cdx, cdy) == cdx) {
+ cdy = cdx;
+ } else {
+ cdx = cdy;
+ }
+ } else { /* No stretching */
+ cdx = cdy = 1;
+ }
+ cw = screen.width * cdx;
+ ch = screen.height * cdy;
+ cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
+ cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
+ } else {
+ cx = 0;
+ cy = 0;
+ cw = screen.width;
+ ch = screen.height;
+ cdx = 1.0;
+ cdy = 1.0;
+ }
+}
+
+- (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");
+
+ int w = pixman_image_get_width(image);
+ int h = pixman_image_get_height(image);
+ /* cdx == 0 means this is our very first surface, in which case we need
+ * to recalculate the content dimensions even if it happens to be the size
+ * of the initial empty window.
+ */
+ bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
+
+ int oldh = screen.height;
+ if (isResize) {
+ // Resize before we trigger the redraw, or we'll redraw at the wrong size
+ COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
+ screen.width = w;
+ screen.height = h;
+ [self setContentDimensions];
+ [self setFrame:NSMakeRect(cx, cy, cw, ch)];
+ }
+
+ // update screenBuffer
+ if (pixman_image) {
+ pixman_image_unref(pixman_image);
+ }
+
+ pixman_image = image;
+
+ // update windows
+ if (isFullscreen) {
+ [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
+ [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO];
+ } else {
+ if (qemu_name)
+ [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
+ [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO];
+ }
+
+ if (isResize) {
+ [normalWindow center];
+ }
+}
+
+- (void) toggleFullScreen:(id)sender
+{
+ COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
+
+ if (isFullscreen) { // switch from fullscreen to desktop
+ isFullscreen = FALSE;
+ [self ungrabMouse];
+ [self setContentDimensions];
+ [fullScreenWindow close];
+ [normalWindow setContentView: self];
+ [normalWindow makeKeyAndOrderFront: self];
+ [NSMenu setMenuBarVisible:YES];
+ } else { // switch from desktop to fullscreen
+ isFullscreen = TRUE;
+ [normalWindow orderOut: nil]; /* Hide the window */
+ [self grabMouse];
+ [self setContentDimensions];
+ [NSMenu setMenuBarVisible:NO];
+ fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
+ styleMask:NSWindowStyleMaskBorderless
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ [fullScreenWindow setAcceptsMouseMovedEvents: YES];
+ [fullScreenWindow setHasShadow:NO];
+ [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
+ [self setFrame:NSMakeRect(cx, cy, cw, ch)];
+ [[fullScreenWindow contentView] addSubview: self];
+ [fullScreenWindow makeKeyAndOrderFront:self];
+ }
+}
+
+- (void) toggleKey: (int)keycode {
+ qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
+}
+
+// Does the work of sending input to the monitor
+- (void) handleMonitorInput:(NSEvent *)event
+{
+ int keysym = 0;
+ int control_key = 0;
+
+ // if the control key is down
+ if ([event modifierFlags] & NSEventModifierFlagControl) {
+ control_key = 1;
+ }
+
+ /* translates Macintosh keycodes to QEMU's keysym */
+
+ int without_control_translation[] = {
+ [0 ... 0xff] = 0, // invalid key
+
+ [kVK_UpArrow] = QEMU_KEY_UP,
+ [kVK_DownArrow] = QEMU_KEY_DOWN,
+ [kVK_RightArrow] = QEMU_KEY_RIGHT,
+ [kVK_LeftArrow] = QEMU_KEY_LEFT,
+ [kVK_Home] = QEMU_KEY_HOME,
+ [kVK_End] = QEMU_KEY_END,
+ [kVK_PageUp] = QEMU_KEY_PAGEUP,
+ [kVK_PageDown] = QEMU_KEY_PAGEDOWN,
+ [kVK_ForwardDelete] = QEMU_KEY_DELETE,
+ [kVK_Delete] = QEMU_KEY_BACKSPACE,
+ };
+
+ int with_control_translation[] = {
+ [0 ... 0xff] = 0, // invalid key
+
+ [kVK_UpArrow] = QEMU_KEY_CTRL_UP,
+ [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN,
+ [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT,
+ [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT,
+ [kVK_Home] = QEMU_KEY_CTRL_HOME,
+ [kVK_End] = QEMU_KEY_CTRL_END,
+ [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP,
+ [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN,
+ };
+
+ if (control_key != 0) { /* If the control key is being used */
+ if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
+ keysym = with_control_translation[[event keyCode]];
+ }
+ } else {
+ if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
+ keysym = without_control_translation[[event keyCode]];
+ }
+ }
+
+ // if not a key that needs translating
+ if (keysym == 0) {
+ NSString *ks = [event characters];
+ if ([ks length] > 0) {
+ keysym = [ks characterAtIndex:0];
+ }
+ }
+
+ if (keysym) {
+ kbd_put_keysym(keysym);
+ }
+}
+
+- (bool) handleEvent:(NSEvent *)event
+{
+ if(!allow_events) {
+ /*
+ * Just let OSX have all events that arrive before
+ * applicationDidFinishLaunching.
+ * This avoids a deadlock on the iothread lock, which cocoa_display_init()
+ * will not drop until after the app_started_sem is posted. (In theory
+ * there should not be any such events, but OSX Catalina now emits some.)
+ */
+ return false;
+ }
+ return bool_with_iothread_lock(^{
+ return [self handleEventLocked:event];
+ });
+}
+
+- (bool) handleEventLocked:(NSEvent *)event
+{
+ /* Return true if we handled the event, false if it should be given to OSX */
+ COCOA_DEBUG("QemuCocoaView: handleEvent\n");
+ int buttons = 0;
+ int keycode = 0;
+ bool mouse_event = false;
+ static bool switched_to_fullscreen = false;
+ // Location of event in virtual screen coordinates
+ NSPoint p = [self screenLocationOfEvent:event];
+ NSUInteger modifiers = [event modifierFlags];
+
+ /*
+ * Check -[NSEvent modifierFlags] here.
+ *
+ * There is a NSEventType for an event notifying the change of
+ * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
+ * are performed for any events because a modifier state may change while
+ * the application is inactive (i.e. no events fire) and we don't want to
+ * wait for another modifier state change to detect such a change.
+ *
+ * NSEventModifierFlagCapsLock requires a special treatment. The other flags
+ * are handled in similar manners.
+ *
+ * NSEventModifierFlagCapsLock
+ * ---------------------------
+ *
+ * If CapsLock state is changed, "up" and "down" events will be fired in
+ * sequence, effectively updates CapsLock state on the guest.
+ *
+ * The other flags
+ * ---------------
+ *
+ * If a flag is not set, fire "up" events for all keys which correspond to
+ * the flag. Note that "down" events are not fired here because the flags
+ * checked here do not tell what exact keys are down.
+ *
+ * If one of the keys corresponding to a flag is down, we rely on
+ * -[NSEvent keyCode] of an event whose -[NSEvent type] is
+ * NSEventTypeFlagsChanged to know the exact key which is down, which has
+ * the following two downsides:
+ * - It does not work when the application is inactive as described above.
+ * - It malfactions *after* the modifier state is changed while the
+ * application is inactive. It is because -[NSEvent keyCode] does not tell
+ * if the key is up or down, and requires to infer the current state from
+ * the previous state. It is still possible to fix such a malfanction by
+ * completely leaving your hands from the keyboard, which hopefully makes
+ * this implementation usable enough.
+ */
+ if (!!(modifiers & NSEventModifierFlagCapsLock) !=
+ qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
+ qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
+ qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
+ }
+
+ if (!(modifiers & NSEventModifierFlagShift)) {
+ qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
+ qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
+ }
+ if (!(modifiers & NSEventModifierFlagControl)) {
+ qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
+ qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
+ }
+ if (!(modifiers & NSEventModifierFlagOption)) {
+ qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
+ qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
+ }
+ if (!(modifiers & NSEventModifierFlagCommand)) {
+ qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
+ qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
+ }
+
+ switch ([event type]) {
+ case NSEventTypeFlagsChanged:
+ switch ([event keyCode]) {
+ case kVK_Shift:
+ if (!!(modifiers & NSEventModifierFlagShift)) {
+ [self toggleKey:Q_KEY_CODE_SHIFT];
+ }
+ break;
+
+ case kVK_RightShift:
+ if (!!(modifiers & NSEventModifierFlagShift)) {
+ [self toggleKey:Q_KEY_CODE_SHIFT_R];
+ }
+ break;
+
+ case kVK_Control:
+ if (!!(modifiers & NSEventModifierFlagControl)) {
+ [self toggleKey:Q_KEY_CODE_CTRL];
+ }
+ break;
+
+ case kVK_RightControl:
+ if (!!(modifiers & NSEventModifierFlagControl)) {
+ [self toggleKey:Q_KEY_CODE_CTRL_R];
+ }
+ break;
+
+ case kVK_Option:
+ if (!!(modifiers & NSEventModifierFlagOption)) {
+ [self toggleKey:Q_KEY_CODE_ALT];
+ }
+ break;
+
+ case kVK_RightOption:
+ if (!!(modifiers & NSEventModifierFlagOption)) {
+ [self toggleKey:Q_KEY_CODE_ALT_R];
+ }
+ break;
+
+ /* Don't pass command key changes to guest unless mouse is grabbed */
+ case kVK_Command:
+ if (isMouseGrabbed &&
+ !!(modifiers & NSEventModifierFlagCommand)) {
+ [self toggleKey:Q_KEY_CODE_META_L];
+ }
+ break;
+
+ case kVK_RightCommand:
+ if (isMouseGrabbed &&
+ !!(modifiers & NSEventModifierFlagCommand)) {
+ [self toggleKey:Q_KEY_CODE_META_R];
+ }
+ break;
+ }
+ break;
+ case NSEventTypeKeyDown:
+ keycode = cocoa_keycode_to_qemu([event keyCode]);
+
+ // forward command key combos to the host UI unless the mouse is grabbed
+ if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
+ /*
+ * Prevent the command key from being stuck down in the guest
+ * when using Command-F to switch to full screen mode.
+ */
+ if (keycode == Q_KEY_CODE_F) {
+ switched_to_fullscreen = true;
+ }
+ return false;
+ }
+
+ // default
+
+ // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
+ if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
+ NSString *keychar = [event charactersIgnoringModifiers];
+ if ([keychar length] == 1) {
+ char key = [keychar characterAtIndex:0];
+ switch (key) {
+
+ // enable graphic console
+ case '1' ... '9':
+ console_select(key - '0' - 1); /* ascii math */
+ return true;
+
+ // release the mouse grab
+ case 'g':
+ [self ungrabMouse];
+ return true;
+ }
+ }
+ }
+
+ if (qemu_console_is_graphic(NULL)) {
+ qkbd_state_key_event(kbd, keycode, true);
+ } else {
+ [self handleMonitorInput: event];
+ }
+ break;
+ case NSEventTypeKeyUp:
+ keycode = cocoa_keycode_to_qemu([event keyCode]);
+
+ // don't pass the guest a spurious key-up if we treated this
+ // command-key combo as a host UI action
+ if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
+ return true;
+ }
+
+ if (qemu_console_is_graphic(NULL)) {
+ qkbd_state_key_event(kbd, keycode, false);
+ }
+ break;
+ case NSEventTypeMouseMoved:
+ if (isAbsoluteEnabled) {
+ // Cursor re-entered into a window might generate events bound to screen coordinates
+ // and `nil` window property, and in full screen mode, current window might not be
+ // key window, where event location alone should suffice.
+ if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) {
+ if (isMouseGrabbed) {
+ [self ungrabMouse];
+ }
+ } else {
+ if (!isMouseGrabbed) {
+ [self grabMouse];
+ }
+ }
+ }
+ mouse_event = true;
+ break;
+ case NSEventTypeLeftMouseDown:
+ buttons |= MOUSE_EVENT_LBUTTON;
+ mouse_event = true;
+ break;
+ case NSEventTypeRightMouseDown:
+ buttons |= MOUSE_EVENT_RBUTTON;
+ mouse_event = true;
+ break;
+ case NSEventTypeOtherMouseDown:
+ buttons |= MOUSE_EVENT_MBUTTON;
+ mouse_event = true;
+ break;
+ case NSEventTypeLeftMouseDragged:
+ buttons |= MOUSE_EVENT_LBUTTON;
+ mouse_event = true;
+ break;
+ case NSEventTypeRightMouseDragged:
+ buttons |= MOUSE_EVENT_RBUTTON;
+ mouse_event = true;
+ break;
+ case NSEventTypeOtherMouseDragged:
+ buttons |= MOUSE_EVENT_MBUTTON;
+ mouse_event = true;
+ break;
+ case NSEventTypeLeftMouseUp:
+ mouse_event = true;
+ if (!isMouseGrabbed && [self screenContainsPoint:p]) {
+ /*
+ * In fullscreen mode, the window of cocoaView may not be the
+ * key window, therefore the position relative to the virtual
+ * screen alone will be sufficient.
+ */
+ if(isFullscreen || [[self window] isKeyWindow]) {
+ [self grabMouse];
+ }
+ }
+ break;
+ case NSEventTypeRightMouseUp:
+ mouse_event = true;
+ break;
+ case NSEventTypeOtherMouseUp:
+ mouse_event = true;
+ break;
+ case NSEventTypeScrollWheel:
+ /*
+ * Send wheel events to the guest regardless of window focus.
+ * This is in-line with standard Mac OS X UI behaviour.
+ */
+
+ /*
+ * When deltaY is zero, it means that this scrolling event was
+ * either horizontal, or so fine that it only appears in
+ * scrollingDeltaY. So we drop the event.
+ */
+ if ([event deltaY] != 0) {
+ /* Determine if this is a scroll up or scroll down event */
+ buttons = ([event deltaY] > 0) ?
+ INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
+ qemu_input_queue_btn(dcl.con, buttons, true);
+ qemu_input_event_sync();
+ qemu_input_queue_btn(dcl.con, buttons, false);
+ qemu_input_event_sync();
+ }
+ /*
+ * Since deltaY also reports scroll wheel events we prevent mouse
+ * movement code from executing.
+ */
+ mouse_event = false;
+ break;
+ default:
+ return false;
+ }
+
+ if (mouse_event) {
+ /* Don't send button events to the guest unless we've got a
+ * mouse grab or window focus. If we have neither then this event
+ * is the user clicking on the background window to activate and
+ * bring us to the front, which will be done by the sendEvent
+ * call below. We definitely don't want to pass that click through
+ * to the guest.
+ */
+ if ((isMouseGrabbed || [[self window] isKeyWindow]) &&
+ (last_buttons != buttons)) {
+ static uint32_t bmap[INPUT_BUTTON__MAX] = {
+ [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
+ [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
+ [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON
+ };
+ qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons);
+ last_buttons = buttons;
+ }
+ if (isMouseGrabbed) {
+ if (isAbsoluteEnabled) {
+ /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
+ * The check on screenContainsPoint is to avoid sending out of range values for
+ * clicks in the titlebar.
+ */
+ if ([self screenContainsPoint:p]) {
+ qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width);
+ qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height);
+ }
+ } else {
+ qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]);
+ qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]);
+ }
+ } else {
+ return false;
+ }
+ qemu_input_event_sync();
+ }
+ return true;
+}
+
+- (void) grabMouse
+{
+ COCOA_DEBUG("QemuCocoaView: grabMouse\n");
+
+ if (!isFullscreen) {
+ if (qemu_name)
+ [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]];
+ else
+ [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"];
+ }
+ [self hideCursor];
+ CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
+ isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
+}
+
+- (void) ungrabMouse
+{
+ COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
+
+ if (!isFullscreen) {
+ if (qemu_name)
+ [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
+ else
+ [normalWindow setTitle:@"QEMU"];
+ }
+ [self unhideCursor];
+ CGAssociateMouseAndMouseCursorPosition(TRUE);
+ isMouseGrabbed = FALSE;
+}
+
+- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
+ isAbsoluteEnabled = tIsAbsoluteEnabled;
+ if (isMouseGrabbed) {
+ CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
+ }
+}
+- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
+- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
+- (float) cdx {return cdx;}
+- (float) cdy {return cdy;}
+- (QEMUScreen) gscreen {return screen;}
+
+/*
+ * Makes the target think all down keys are being released.
+ * This prevents a stuck key problem, since we will not see
+ * key up events for those keys after we have lost focus.
+ */
+- (void) raiseAllKeys
+{
+ with_iothread_lock(^{
+ qkbd_state_lift_all_keys(kbd);
+ });
+}
+@end
+
+
+
+/*
+ ------------------------------------------------------
+ QemuCocoaAppController
+ ------------------------------------------------------
+*/
+@interface QemuCocoaAppController : NSObject
+ <NSWindowDelegate, NSApplicationDelegate>
+{
+}
+- (void)doToggleFullScreen:(id)sender;
+- (void)toggleFullScreen:(id)sender;
+- (void)showQEMUDoc:(id)sender;
+- (void)zoomToFit:(id) sender;
+- (void)displayConsole:(id)sender;
+- (void)pauseQEMU:(id)sender;
+- (void)resumeQEMU:(id)sender;
+- (void)displayPause;
+- (void)removePause;
+- (void)restartQEMU:(id)sender;
+- (void)powerDownQEMU:(id)sender;
+- (void)ejectDeviceMedia:(id)sender;
+- (void)changeDeviceMedia:(id)sender;
+- (BOOL)verifyQuit;
+- (void)openDocumentation:(NSString *)filename;
+- (IBAction) do_about_menu_item: (id) sender;
+- (void)make_about_window;
+- (void)adjustSpeed:(id)sender;
+@end
+
+@implementation QemuCocoaAppController
+- (id) init
+{
+ COCOA_DEBUG("QemuCocoaAppController: init\n");
+
+ self = [super init];
+ if (self) {
+
+ // create a view and add it to the window
+ cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
+ if(!cocoaView) {
+ error_report("(cocoa) can't create a view");
+ exit(1);
+ }
+
+ // create a window
+ normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
+ styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
+ backing:NSBackingStoreBuffered defer:NO];
+ if(!normalWindow) {
+ error_report("(cocoa) can't create window");
+ exit(1);
+ }
+ [normalWindow setAcceptsMouseMovedEvents:YES];
+ [normalWindow setTitle:@"QEMU"];
+ [normalWindow setContentView:cocoaView];
+ [normalWindow makeKeyAndOrderFront:self];
+ [normalWindow center];
+ [normalWindow setDelegate: self];
+ stretch_video = false;
+
+ /* Used for displaying pause on the screen */
+ pauseLabel = [NSTextField new];
+ [pauseLabel setBezeled:YES];
+ [pauseLabel setDrawsBackground:YES];
+ [pauseLabel setBackgroundColor: [NSColor whiteColor]];
+ [pauseLabel setEditable:NO];
+ [pauseLabel setSelectable:NO];
+ [pauseLabel setStringValue: @"Paused"];
+ [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
+ [pauseLabel setTextColor: [NSColor blackColor]];
+ [pauseLabel sizeToFit];
+
+ // set the supported image file types that can be opened
+ supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg",
+ @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr",
+ @"toast", nil];
+ [self make_about_window];
+ }
+ return self;
+}
+
+- (void) dealloc
+{
+ COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
+
+ if (cocoaView)
+ [cocoaView release];
+ [super dealloc];
+}
+
+- (void)applicationDidFinishLaunching: (NSNotification *) note
+{
+ COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
+ allow_events = true;
+ /* Tell cocoa_display_init to proceed */
+ qemu_sem_post(&app_started_sem);
+}
+
+- (void)applicationWillTerminate:(NSNotification *)aNotification
+{
+ COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
+
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+
+ /*
+ * Sleep here, because returning will cause OSX to kill us
+ * immediately; the QEMU main loop will handle the shutdown
+ * request and terminate the process.
+ */
+ [NSThread sleepForTimeInterval:INFINITY];
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
+{
+ return YES;
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:
+ (NSApplication *)sender
+{
+ COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
+ 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
+{
+ COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
+ [NSApp terminate: sender];
+ /* If the user allows the application to quit then the call to
+ * NSApp terminate will never return. If we get here then the user
+ * cancelled the quit, so we should return NO to not permit the
+ * closing of this window.
+ */
+ return NO;
+}
+
+/* Called when QEMU goes into the background */
+- (void) applicationWillResignActive: (NSNotification *)aNotification
+{
+ COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n");
+ [cocoaView raiseAllKeys];
+}
+
+/* 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:
+ */
+- (void) doToggleFullScreen:(id)sender
+{
+ [self toggleFullScreen:(id)sender];
+}
+
+- (void)toggleFullScreen:(id)sender
+{
+ COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
+
+ [cocoaView toggleFullScreen:sender];
+}
+
+/* Tries to find then open the specified filename */
+- (void) openDocumentation: (NSString *) filename
+{
+ /* Where to look for local files */
+ NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
+ NSString *full_file_path;
+ NSURL *full_file_url;
+
+ /* iterate thru the possible paths until the file is found */
+ int index;
+ for (index = 0; index < ARRAY_SIZE(path_array); index++) {
+ full_file_path = [[NSBundle mainBundle] executablePath];
+ full_file_path = [full_file_path stringByDeletingLastPathComponent];
+ full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
+ path_array[index], filename];
+ full_file_url = [NSURL fileURLWithPath: full_file_path
+ isDirectory: false];
+ if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
+ return;
+ }
+ }
+
+ /* If none of the paths opened a file */
+ NSBeep();
+ QEMU_Alert(@"Failed to open file");
+}
+
+- (void)showQEMUDoc:(id)sender
+{
+ COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
+
+ [self openDocumentation: @"index.html"];
+}
+
+/* Stretches video to fit host monitor size */
+- (void)zoomToFit:(id) sender
+{
+ stretch_video = !stretch_video;
+ if (stretch_video == true) {
+ [sender setState: NSControlStateValueOn];
+ } else {
+ [sender setState: NSControlStateValueOff];
+ }
+}
+
+/* Displays the console on the screen */
+- (void)displayConsole:(id)sender
+{
+ console_select([sender tag]);
+}
+
+/* Pause the guest */
+- (void)pauseQEMU:(id)sender
+{
+ with_iothread_lock(^{
+ qmp_stop(NULL);
+ });
+ [sender setEnabled: NO];
+ [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
+ [self displayPause];
+}
+
+/* Resume running the guest operating system */
+- (void)resumeQEMU:(id) sender
+{
+ with_iothread_lock(^{
+ qmp_cont(NULL);
+ });
+ [sender setEnabled: NO];
+ [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
+ [self removePause];
+}
+
+/* Displays the word pause on the screen */
+- (void)displayPause
+{
+ /* Coordinates have to be calculated each time because the window can change its size */
+ int xCoord, yCoord, width, height;
+ xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
+ yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
+ width = [pauseLabel frame].size.width;
+ height = [pauseLabel frame].size.height;
+ [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
+ [cocoaView addSubview: pauseLabel];
+}
+
+/* Removes the word pause from the screen */
+- (void)removePause
+{
+ [pauseLabel removeFromSuperview];
+}
+
+/* Restarts QEMU */
+- (void)restartQEMU:(id)sender
+{
+ with_iothread_lock(^{
+ qmp_system_reset(NULL);
+ });
+}
+
+/* Powers down QEMU */
+- (void)powerDownQEMU:(id)sender
+{
+ with_iothread_lock(^{
+ qmp_system_powerdown(NULL);
+ });
+}
+
+/* Ejects the media.
+ * Uses sender's tag to figure out the device to eject.
+ */
+- (void)ejectDeviceMedia:(id)sender
+{
+ NSString * drive;
+ drive = [sender representedObject];
+ if(drive == nil) {
+ NSBeep();
+ QEMU_Alert(@"Failed to find drive to eject!");
+ return;
+ }
+
+ __block Error *err = NULL;
+ with_iothread_lock(^{
+ qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding],
+ false, NULL, false, false, &err);
+ });
+ handleAnyDeviceErrors(err);
+}
+
+/* Displays a dialog box asking the user to select an image file to load.
+ * Uses sender's represented object value to figure out which drive to use.
+ */
+- (void)changeDeviceMedia:(id)sender
+{
+ /* Find the drive name */
+ NSString * drive;
+ drive = [sender representedObject];
+ if(drive == nil) {
+ NSBeep();
+ QEMU_Alert(@"Could not find drive!");
+ return;
+ }
+
+ /* Display the file open dialog */
+ NSOpenPanel * openPanel;
+ openPanel = [NSOpenPanel openPanel];
+ [openPanel setCanChooseFiles: YES];
+ [openPanel setAllowsMultipleSelection: NO];
+ [openPanel setAllowedFileTypes: supportedImageFileTypes];
+ if([openPanel runModal] == NSModalResponseOK) {
+ NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
+ if(file == nil) {
+ NSBeep();
+ QEMU_Alert(@"Failed to convert URL to file path!");
+ return;
+ }
+
+ __block Error *err = NULL;
+ with_iothread_lock(^{
+ qmp_blockdev_change_medium(true,
+ [drive cStringUsingEncoding:
+ NSASCIIStringEncoding],
+ false, NULL,
+ [file cStringUsingEncoding:
+ NSASCIIStringEncoding],
+ true, "raw",
+ false, 0,
+ &err);
+ });
+ handleAnyDeviceErrors(err);
+ }
+}
+
+/* Verifies if the user really wants to quit */
+- (BOOL)verifyQuit
+{
+ NSAlert *alert = [NSAlert new];
+ [alert autorelease];
+ [alert setMessageText: @"Are you sure you want to quit QEMU?"];
+ [alert addButtonWithTitle: @"Cancel"];
+ [alert addButtonWithTitle: @"Quit"];
+ if([alert runModal] == NSAlertSecondButtonReturn) {
+ return YES;
+ } else {
+ return NO;
+ }
+}
+
+/* The action method for the About menu item */
+- (IBAction) do_about_menu_item: (id) sender
+{
+ [about_window makeKeyAndOrderFront: nil];
+}
+
+/* Create and display the about dialog */
+- (void)make_about_window
+{
+ /* Make the window */
+ int x = 0, y = 0, about_width = 400, about_height = 200;
+ NSRect window_rect = NSMakeRect(x, y, about_width, about_height);
+ about_window = [[NSWindow alloc] initWithContentRect:window_rect
+ styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
+ NSWindowStyleMaskMiniaturizable
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ [about_window setTitle: @"About"];
+ [about_window setReleasedWhenClosed: NO];
+ [about_window center];
+ NSView *superView = [about_window contentView];
+
+ /* Create the dimensions of the picture */
+ int picture_width = 80, picture_height = 80;
+ x = (about_width - picture_width)/2;
+ y = about_height - picture_height - 10;
+ NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height);
+
+ /* Make the picture of QEMU */
+ NSImageView *picture_view = [[NSImageView alloc] initWithFrame:
+ picture_rect];
+ char *qemu_image_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
+ NSString *qemu_image_path = [NSString stringWithUTF8String:qemu_image_path_c];
+ g_free(qemu_image_path_c);
+ NSImage *qemu_image = [[NSImage alloc] initWithContentsOfFile:qemu_image_path];
+ [picture_view setImage: qemu_image];
+ [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown];
+ [superView addSubview: picture_view];
+
+ /* Make the name label */
+ NSBundle *bundle = [NSBundle mainBundle];
+ if (bundle) {
+ x = 0;
+ y = y - 25;
+ int name_width = about_width, name_height = 20;
+ NSRect name_rect = NSMakeRect(x, y, name_width, name_height);
+ NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect];
+ [name_label setEditable: NO];
+ [name_label setBezeled: NO];
+ [name_label setDrawsBackground: NO];
+ [name_label setAlignment: NSTextAlignmentCenter];
+ NSString *qemu_name = [[bundle executablePath] lastPathComponent];
+ [name_label setStringValue: qemu_name];
+ [superView addSubview: name_label];
+ }
+
+ /* Set the version label's attributes */
+ x = 0;
+ y = 50;
+ int version_width = about_width, version_height = 20;
+ NSRect version_rect = NSMakeRect(x, y, version_width, version_height);
+ NSTextField *version_label = [[NSTextField alloc] initWithFrame:
+ version_rect];
+ [version_label setEditable: NO];
+ [version_label setBezeled: NO];
+ [version_label setAlignment: NSTextAlignmentCenter];
+ [version_label setDrawsBackground: NO];
+
+ /* Create the version string*/
+ NSString *version_string;
+ version_string = [[NSString alloc] initWithFormat:
+ @"QEMU emulator version %s", QEMU_FULL_VERSION];
+ [version_label setStringValue: version_string];
+ [superView addSubview: version_label];
+
+ /* Make copyright label */
+ x = 0;
+ y = 35;
+ int copyright_width = about_width, copyright_height = 20;
+ NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height);
+ NSTextField *copyright_label = [[NSTextField alloc] initWithFrame:
+ copyright_rect];
+ [copyright_label setEditable: NO];
+ [copyright_label setBezeled: NO];
+ [copyright_label setDrawsBackground: NO];
+ [copyright_label setAlignment: NSTextAlignmentCenter];
+ [copyright_label setStringValue: [NSString stringWithFormat: @"%s",
+ QEMU_COPYRIGHT]];
+ [superView addSubview: copyright_label];
+}
+
+/* Used by the Speed menu items */
+- (void)adjustSpeed:(id)sender
+{
+ int throttle_pct; /* throttle percentage */
+ NSMenu *menu;
+
+ menu = [sender menu];
+ if (menu != nil)
+ {
+ /* Unselect the currently selected item */
+ for (NSMenuItem *item in [menu itemArray]) {
+ if (item.state == NSControlStateValueOn) {
+ [item setState: NSControlStateValueOff];
+ break;
+ }
+ }
+ }
+
+ // check the menu item
+ [sender setState: NSControlStateValueOn];
+
+ // get the throttle percentage
+ throttle_pct = [sender tag];
+
+ with_iothread_lock(^{
+ cpu_throttle_set(throttle_pct);
+ });
+ COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
+}
+
+@end
+
+@interface QemuApplication : NSApplication
+@end
+
+@implementation QemuApplication
+- (void)sendEvent:(NSEvent *)event
+{
+ COCOA_DEBUG("QemuApplication: sendEvent\n");
+ if (![cocoaView handleEvent:event]) {
+ [super sendEvent: event];
+ }
+}
+@end
+
+static void create_initial_menus(void)
+{
+ // Add menus
+ NSMenu *menu;
+ NSMenuItem *menuItem;
+
+ [NSApp setMainMenu:[[NSMenu alloc] init]];
+
+ // Application menu
+ menu = [[NSMenu alloc] initWithTitle:@""];
+ [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
+ [menu addItem:[NSMenuItem separatorItem]]; //Separator
+ [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
+ menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
+ [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
+ [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
+ [menu addItem:[NSMenuItem separatorItem]]; //Separator
+ [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
+ [menuItem setSubmenu:menu];
+ [[NSApp mainMenu] addItem:menuItem];
+ [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
+
+ // Machine menu
+ menu = [[NSMenu alloc] initWithTitle: @"Machine"];
+ [menu setAutoenablesItems: NO];
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
+ menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
+ [menu addItem: menuItem];
+ [menuItem setEnabled: NO];
+ [menu addItem: [NSMenuItem separatorItem]];
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
+ menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
+ [menuItem setSubmenu:menu];
+ [[NSApp mainMenu] addItem:menuItem];
+
+ // View menu
+ menu = [[NSMenu alloc] initWithTitle:@"View"];
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
+ menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
+ [menuItem setSubmenu:menu];
+ [[NSApp mainMenu] addItem:menuItem];
+
+ // Speed menu
+ menu = [[NSMenu alloc] initWithTitle:@"Speed"];
+
+ // Add the rest of the Speed menu items
+ int p, percentage, throttle_pct;
+ for (p = 10; p >= 0; p--)
+ {
+ percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
+
+ menuItem = [[[NSMenuItem alloc]
+ initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
+
+ if (percentage == 100) {
+ [menuItem setState: NSControlStateValueOn];
+ }
+
+ /* Calculate the throttle percentage */
+ throttle_pct = -1 * percentage + 100;
+
+ [menuItem setTag: throttle_pct];
+ [menu addItem: menuItem];
+ }
+ menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
+ [menuItem setSubmenu:menu];
+ [[NSApp mainMenu] addItem:menuItem];
+
+ // Window menu
+ menu = [[NSMenu alloc] initWithTitle:@"Window"];
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
+ menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
+ [menuItem setSubmenu:menu];
+ [[NSApp mainMenu] addItem:menuItem];
+ [NSApp setWindowsMenu:menu];
+
+ // Help menu
+ menu = [[NSMenu alloc] initWithTitle:@"Help"];
+ [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
+ menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
+ [menuItem setSubmenu:menu];
+ [[NSApp mainMenu] addItem:menuItem];
+}
+
+/* Returns a name for a given console */
+static NSString * getConsoleName(QemuConsole * console)
+{
+ return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)];
+}
+
+/* Add an entry to the View menu for each console */
+static void add_console_menu_entries(void)
+{
+ NSMenu *menu;
+ NSMenuItem *menuItem;
+ int index = 0;
+
+ menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
+
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ while (qemu_console_lookup_by_index(index) != NULL) {
+ menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
+ action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
+ [menuItem setTag: index];
+ [menu addItem: menuItem];
+ index++;
+ }
+}
+
+/* Make menu items for all removable devices.
+ * Each device is given an 'Eject' and 'Change' menu item.
+ */
+static void addRemovableDevicesMenuItems(void)
+{
+ NSMenu *menu;
+ NSMenuItem *menuItem;
+ BlockInfoList *currentDevice, *pointerToFree;
+ NSString *deviceName;
+
+ currentDevice = qmp_query_block(NULL);
+ pointerToFree = currentDevice;
+ if(currentDevice == NULL) {
+ NSBeep();
+ QEMU_Alert(@"Failed to query for block devices!");
+ return;
+ }
+
+ menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
+
+ // Add a separator between related groups of menu items
+ [menu addItem:[NSMenuItem separatorItem]];
+
+ // Set the attributes to the "Removable Media" menu item
+ NSString *titleString = @"Removable Media";
+ NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
+ NSColor *newColor = [NSColor blackColor];
+ NSFontManager *fontManager = [NSFontManager sharedFontManager];
+ NSFont *font = [fontManager fontWithFamily:@"Helvetica"
+ traits:NSBoldFontMask|NSItalicFontMask
+ weight:0
+ size:14];
+ [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
+ [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
+ [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
+
+ // Add the "Removable Media" menu item
+ menuItem = [NSMenuItem new];
+ [menuItem setAttributedTitle: attString];
+ [menuItem setEnabled: NO];
+ [menu addItem: menuItem];
+
+ /* Loop through all the block devices in the emulator */
+ while (currentDevice) {
+ deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
+
+ if(currentDevice->value->removable) {
+ menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
+ action: @selector(changeDeviceMedia:)
+ keyEquivalent: @""];
+ [menu addItem: menuItem];
+ [menuItem setRepresentedObject: deviceName];
+ [menuItem autorelease];
+
+ menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
+ action: @selector(ejectDeviceMedia:)
+ keyEquivalent: @""];
+ [menu addItem: menuItem];
+ [menuItem setRepresentedObject: deviceName];
+ [menuItem autorelease];
+ }
+ currentDevice = currentDevice->next;
+ }
+ 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
+ * 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");
+ [cbowner release];
+ exit(status);
+}
+
+int main (int argc, char **argv) {
+ QemuThread thread;
+
+ COCOA_DEBUG("Entered main()\n");
+ gArgc = argc;
+ gArgv = argv;
+
+ qemu_sem_init(&display_init_sem, 0);
+ qemu_sem_init(&app_started_sem, 0);
+
+ 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];
+
+ // Pull this console process up to being a fully-fledged graphical
+ // app with a menubar and Dock icon
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ TransformProcessType(&psn, kProcessTransformToForegroundApplication);
+
+ [QemuApplication sharedApplication];
+
+ 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];
+
+ return 0;
+}
+
+
+
+#pragma mark qemu
+static void cocoa_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
+ COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
+
+ 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];
+}
+
+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 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
+ // 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];
+}
+
+static void cocoa_refresh(DisplayChangeListener *dcl)
+{
+ NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+
+ COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
+ graphic_hw_update(NULL);
+
+ if (qemu_input_is_absolute()) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (![cocoaView isAbsoluteEnabled]) {
+ if ([cocoaView isMouseGrabbed]) {
+ [cocoaView ungrabMouse];
+ }
+ }
+ [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];
+}
+
+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) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [NSApp activateIgnoringOtherApps: YES];
+ [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil];
+ });
+ }
+ if (opts->has_show_cursor && opts->show_cursor) {
+ cursor_hide = 0;
+ }
+
+ // 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 = {
+ .type = DISPLAY_TYPE_COCOA,
+ .init = cocoa_display_init,
+};
+
+static void register_cocoa(void)
+{
+ qemu_display_register(&qemu_display_cocoa);
+}
+
+type_init(register_cocoa);
diff --git a/ui/console-gl.c b/ui/console-gl.c
new file mode 100644
index 000000000..7c9894a51
--- /dev/null
+++ b/ui/console-gl.c
@@ -0,0 +1,158 @@
+/*
+ * QEMU graphical console -- opengl helper bits
+ *
+ * Copyright (c) 2014 Red Hat
+ *
+ * Authors:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/shader.h"
+
+/* ---------------------------------------------------------------------- */
+
+bool console_gl_check_format(DisplayChangeListener *dcl,
+ pixman_format_code_t format)
+{
+ switch (format) {
+ case PIXMAN_BE_b8g8r8x8:
+ case PIXMAN_BE_b8g8r8a8:
+ case PIXMAN_r5g6b5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void surface_gl_create_texture(QemuGLShader *gls,
+ DisplaySurface *surface)
+{
+ assert(gls);
+ assert(QEMU_IS_ALIGNED(surface_stride(surface), surface_bytes_per_pixel(surface)));
+
+ switch (surface->format) {
+ case PIXMAN_BE_b8g8r8x8:
+ case PIXMAN_BE_b8g8r8a8:
+ surface->glformat = GL_BGRA_EXT;
+ surface->gltype = GL_UNSIGNED_BYTE;
+ break;
+ case PIXMAN_BE_x8r8g8b8:
+ case PIXMAN_BE_a8r8g8b8:
+ surface->glformat = GL_RGBA;
+ surface->gltype = GL_UNSIGNED_BYTE;
+ break;
+ case PIXMAN_r5g6b5:
+ surface->glformat = GL_RGB;
+ surface->gltype = GL_UNSIGNED_SHORT_5_6_5;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ glGenTextures(1, &surface->texture);
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, surface->texture);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT,
+ surface_stride(surface) / surface_bytes_per_pixel(surface));
+ if (epoxy_is_desktop_gl()) {
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+ surface_width(surface),
+ surface_height(surface),
+ 0, surface->glformat, surface->gltype,
+ surface_data(surface));
+ } else {
+ glTexImage2D(GL_TEXTURE_2D, 0, surface->glformat,
+ surface_width(surface),
+ surface_height(surface),
+ 0, surface->glformat, surface->gltype,
+ surface_data(surface));
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ONE);
+ }
+
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+}
+
+void surface_gl_update_texture(QemuGLShader *gls,
+ DisplaySurface *surface,
+ int x, int y, int w, int h)
+{
+ uint8_t *data = (void *)surface_data(surface);
+
+ assert(gls);
+
+ if (surface->texture) {
+ glBindTexture(GL_TEXTURE_2D, surface->texture);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT,
+ surface_stride(surface)
+ / surface_bytes_per_pixel(surface));
+ glTexSubImage2D(GL_TEXTURE_2D, 0,
+ x, y, w, h,
+ surface->glformat, surface->gltype,
+ data + surface_stride(surface) * y
+ + surface_bytes_per_pixel(surface) * x);
+ }
+}
+
+void surface_gl_render_texture(QemuGLShader *gls,
+ DisplaySurface *surface)
+{
+ assert(gls);
+
+ glClearColor(0.1f, 0.1f, 0.1f, 0.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ qemu_gl_run_texture_blit(gls, false);
+}
+
+void surface_gl_destroy_texture(QemuGLShader *gls,
+ DisplaySurface *surface)
+{
+ if (!surface || !surface->texture) {
+ return;
+ }
+ glDeleteTextures(1, &surface->texture);
+ surface->texture = 0;
+}
+
+void surface_gl_setup_viewport(QemuGLShader *gls,
+ DisplaySurface *surface,
+ int ww, int wh)
+{
+ int gw, gh, stripe;
+ float sw, sh;
+
+ assert(gls);
+
+ gw = surface_width(surface);
+ gh = surface_height(surface);
+
+ sw = (float)ww/gw;
+ sh = (float)wh/gh;
+ if (sw < sh) {
+ stripe = wh - wh*sw/sh;
+ glViewport(0, stripe / 2, ww, wh - stripe);
+ } else {
+ stripe = ww - ww*sh/sw;
+ glViewport(stripe / 2, 0, ww - stripe, wh);
+ }
+}
diff --git a/ui/console.c b/ui/console.c
new file mode 100644
index 000000000..29a3e3f0f
--- /dev/null
+++ b/ui/console.c
@@ -0,0 +1,2460 @@
+/*
+ * QEMU graphical console
+ *
+ * Copyright (c) 2004 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "hw/qdev-core.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-ui.h"
+#include "qemu/fifo8.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/timer.h"
+#include "chardev/char.h"
+#include "trace.h"
+#include "exec/memory.h"
+#include "io/channel-file.h"
+#include "qom/object.h"
+
+#define DEFAULT_BACKSCROLL 512
+#define CONSOLE_CURSOR_PERIOD 500
+
+typedef struct TextAttributes {
+ uint8_t fgcol:4;
+ uint8_t bgcol:4;
+ uint8_t bold:1;
+ uint8_t uline:1;
+ uint8_t blink:1;
+ uint8_t invers:1;
+ uint8_t unvisible:1;
+} TextAttributes;
+
+typedef struct TextCell {
+ uint8_t ch;
+ TextAttributes t_attrib;
+} TextCell;
+
+#define MAX_ESC_PARAMS 3
+
+enum TTYState {
+ TTY_STATE_NORM,
+ TTY_STATE_ESC,
+ TTY_STATE_CSI,
+};
+
+typedef enum {
+ GRAPHIC_CONSOLE,
+ TEXT_CONSOLE,
+ TEXT_CONSOLE_FIXED_SIZE
+} console_type_t;
+
+struct QemuConsole {
+ Object parent;
+
+ int index;
+ console_type_t console_type;
+ DisplayState *ds;
+ DisplaySurface *surface;
+ int dcls;
+ DisplayChangeListener *gl;
+ bool gl_block;
+ int window_id;
+
+ /* Graphic console state. */
+ Object *device;
+ uint32_t head;
+ QemuUIInfo ui_info;
+ QEMUTimer *ui_timer;
+ const GraphicHwOps *hw_ops;
+ void *hw;
+
+ /* Text console state */
+ int width;
+ int height;
+ int total_height;
+ int backscroll_height;
+ int x, y;
+ int x_saved, y_saved;
+ int y_displayed;
+ int y_base;
+ TextAttributes t_attrib_default; /* default text attributes */
+ TextAttributes t_attrib; /* currently active text attributes */
+ TextCell *cells;
+ int text_x[2], text_y[2], cursor_invalidate;
+ int echo;
+
+ int update_x0;
+ int update_y0;
+ int update_x1;
+ int update_y1;
+
+ enum TTYState state;
+ int esc_params[MAX_ESC_PARAMS];
+ int nb_esc_params;
+
+ Chardev *chr;
+ /* fifo for key pressed */
+ Fifo8 out_fifo;
+ CoQueue dump_queue;
+
+ QTAILQ_ENTRY(QemuConsole) next;
+};
+
+struct DisplayState {
+ QEMUTimer *gui_timer;
+ uint64_t last_update;
+ uint64_t update_interval;
+ bool refreshing;
+ bool have_gfx;
+ bool have_text;
+
+ QLIST_HEAD(, DisplayChangeListener) listeners;
+};
+
+static DisplayState *display_state;
+static QemuConsole *active_console;
+static QTAILQ_HEAD(, QemuConsole) consoles =
+ QTAILQ_HEAD_INITIALIZER(consoles);
+static bool cursor_visible_phase;
+static QEMUTimer *cursor_timer;
+
+static void text_console_do_init(Chardev *chr, DisplayState *ds);
+static void dpy_refresh(DisplayState *s);
+static DisplayState *get_alloc_displaystate(void);
+static void text_console_update_cursor_timer(void);
+static void text_console_update_cursor(void *opaque);
+
+static void gui_update(void *opaque)
+{
+ uint64_t interval = GUI_REFRESH_INTERVAL_IDLE;
+ uint64_t dcl_interval;
+ DisplayState *ds = opaque;
+ DisplayChangeListener *dcl;
+ QemuConsole *con;
+
+ ds->refreshing = true;
+ dpy_refresh(ds);
+ ds->refreshing = false;
+
+ QLIST_FOREACH(dcl, &ds->listeners, next) {
+ dcl_interval = dcl->update_interval ?
+ dcl->update_interval : GUI_REFRESH_INTERVAL_DEFAULT;
+ if (interval > dcl_interval) {
+ interval = dcl_interval;
+ }
+ }
+ if (ds->update_interval != interval) {
+ ds->update_interval = interval;
+ QTAILQ_FOREACH(con, &consoles, next) {
+ if (con->hw_ops->update_interval) {
+ con->hw_ops->update_interval(con->hw, interval);
+ }
+ }
+ trace_console_refresh(interval);
+ }
+ ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+ timer_mod(ds->gui_timer, ds->last_update + interval);
+}
+
+static void gui_setup_refresh(DisplayState *ds)
+{
+ DisplayChangeListener *dcl;
+ bool need_timer = false;
+ bool have_gfx = false;
+ bool have_text = false;
+
+ QLIST_FOREACH(dcl, &ds->listeners, next) {
+ if (dcl->ops->dpy_refresh != NULL) {
+ need_timer = true;
+ }
+ if (dcl->ops->dpy_gfx_update != NULL) {
+ have_gfx = true;
+ }
+ if (dcl->ops->dpy_text_update != NULL) {
+ have_text = true;
+ }
+ }
+
+ if (need_timer && ds->gui_timer == NULL) {
+ ds->gui_timer = timer_new_ms(QEMU_CLOCK_REALTIME, gui_update, ds);
+ timer_mod(ds->gui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME));
+ }
+ if (!need_timer && ds->gui_timer != NULL) {
+ timer_free(ds->gui_timer);
+ ds->gui_timer = NULL;
+ }
+
+ ds->have_gfx = have_gfx;
+ ds->have_text = have_text;
+}
+
+void graphic_hw_update_done(QemuConsole *con)
+{
+ if (con) {
+ qemu_co_queue_restart_all(&con->dump_queue);
+ }
+}
+
+void graphic_hw_update(QemuConsole *con)
+{
+ bool async = false;
+ con = con ? con : active_console;
+ if (!con) {
+ return;
+ }
+ if (con->hw_ops->gfx_update) {
+ con->hw_ops->gfx_update(con->hw);
+ async = con->hw_ops->gfx_update_async;
+ }
+ if (!async) {
+ graphic_hw_update_done(con);
+ }
+}
+
+void graphic_hw_gl_block(QemuConsole *con, bool block)
+{
+ assert(con != NULL);
+
+ con->gl_block = block;
+ if (con->hw_ops->gl_block) {
+ con->hw_ops->gl_block(con->hw, block);
+ }
+}
+
+void graphic_hw_gl_flushed(QemuConsole *con)
+{
+ assert(con != NULL);
+
+ if (con->hw_ops->gl_flushed) {
+ con->hw_ops->gl_flushed(con->hw);
+ }
+}
+
+int qemu_console_get_window_id(QemuConsole *con)
+{
+ return con->window_id;
+}
+
+void qemu_console_set_window_id(QemuConsole *con, int window_id)
+{
+ con->window_id = window_id;
+}
+
+void graphic_hw_invalidate(QemuConsole *con)
+{
+ if (!con) {
+ con = active_console;
+ }
+ if (con && con->hw_ops->invalidate) {
+ con->hw_ops->invalidate(con->hw);
+ }
+}
+
+static bool ppm_save(int fd, pixman_image_t *image, Error **errp)
+{
+ int width = pixman_image_get_width(image);
+ int height = pixman_image_get_height(image);
+ g_autoptr(Object) ioc = OBJECT(qio_channel_file_new_fd(fd));
+ g_autofree char *header = NULL;
+ g_autoptr(pixman_image_t) linebuf = NULL;
+ int y;
+
+ trace_ppm_save(fd, image);
+
+ header = g_strdup_printf("P6\n%d %d\n%d\n", width, height, 255);
+ if (qio_channel_write_all(QIO_CHANNEL(ioc),
+ header, strlen(header), errp) < 0) {
+ return false;
+ }
+
+ linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, width);
+ for (y = 0; y < height; y++) {
+ qemu_pixman_linebuf_fill(linebuf, image, width, 0, y);
+ if (qio_channel_write_all(QIO_CHANNEL(ioc),
+ (char *)pixman_image_get_data(linebuf),
+ pixman_image_get_stride(linebuf), errp) < 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void graphic_hw_update_bh(void *con)
+{
+ graphic_hw_update(con);
+}
+
+/* Safety: coroutine-only, concurrent-coroutine safe, main thread only */
+void coroutine_fn
+qmp_screendump(const char *filename, bool has_device, const char *device,
+ bool has_head, int64_t head, Error **errp)
+{
+ g_autoptr(pixman_image_t) image = NULL;
+ QemuConsole *con;
+ DisplaySurface *surface;
+ int fd;
+
+ if (has_device) {
+ con = qemu_console_lookup_by_device_name(device, has_head ? head : 0,
+ errp);
+ if (!con) {
+ return;
+ }
+ } else {
+ if (has_head) {
+ error_setg(errp, "'head' must be specified together with 'device'");
+ return;
+ }
+ con = qemu_console_lookup_by_index(0);
+ if (!con) {
+ error_setg(errp, "There is no console to take a screendump from");
+ return;
+ }
+ }
+
+ if (qemu_co_queue_empty(&con->dump_queue)) {
+ /* Defer the update, it will restart the pending coroutines */
+ aio_bh_schedule_oneshot(qemu_get_aio_context(),
+ graphic_hw_update_bh, con);
+ }
+ qemu_co_queue_wait(&con->dump_queue, NULL);
+
+ /*
+ * All pending coroutines are woken up, while the BQL is held. No
+ * further graphic update are possible until it is released. Take
+ * an image ref before that.
+ */
+ surface = qemu_console_surface(con);
+ if (!surface) {
+ error_setg(errp, "no surface");
+ return;
+ }
+ image = pixman_image_ref(surface->image);
+
+ fd = qemu_open_old(filename, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
+ if (fd == -1) {
+ error_setg(errp, "failed to open file '%s': %s", filename,
+ strerror(errno));
+ return;
+ }
+
+ /*
+ * The image content could potentially be updated as the coroutine
+ * yields and releases the BQL. It could produce corrupted dump, but
+ * it should be otherwise safe.
+ */
+ if (!ppm_save(fd, image, errp)) {
+ qemu_unlink(filename);
+ }
+}
+
+void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata)
+{
+ if (!con) {
+ con = active_console;
+ }
+ if (con && con->hw_ops->text_update) {
+ con->hw_ops->text_update(con->hw, chardata);
+ }
+}
+
+static void vga_fill_rect(QemuConsole *con,
+ int posx, int posy, int width, int height,
+ pixman_color_t color)
+{
+ DisplaySurface *surface = qemu_console_surface(con);
+ pixman_rectangle16_t rect = {
+ .x = posx, .y = posy, .width = width, .height = height
+ };
+
+ pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image,
+ &color, 1, &rect);
+}
+
+/* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */
+static void vga_bitblt(QemuConsole *con,
+ int xs, int ys, int xd, int yd, int w, int h)
+{
+ DisplaySurface *surface = qemu_console_surface(con);
+
+ pixman_image_composite(PIXMAN_OP_SRC,
+ surface->image, NULL, surface->image,
+ xs, ys, 0, 0, xd, yd, w, h);
+}
+
+/***********************************************************/
+/* basic char display */
+
+#define FONT_HEIGHT 16
+#define FONT_WIDTH 8
+
+#include "vgafont.h"
+
+#define QEMU_RGB(r, g, b) \
+ { .red = r << 8, .green = g << 8, .blue = b << 8, .alpha = 0xffff }
+
+static const pixman_color_t color_table_rgb[2][8] = {
+ { /* dark */
+ [QEMU_COLOR_BLACK] = QEMU_RGB(0x00, 0x00, 0x00), /* black */
+ [QEMU_COLOR_BLUE] = QEMU_RGB(0x00, 0x00, 0xaa), /* blue */
+ [QEMU_COLOR_GREEN] = QEMU_RGB(0x00, 0xaa, 0x00), /* green */
+ [QEMU_COLOR_CYAN] = QEMU_RGB(0x00, 0xaa, 0xaa), /* cyan */
+ [QEMU_COLOR_RED] = QEMU_RGB(0xaa, 0x00, 0x00), /* red */
+ [QEMU_COLOR_MAGENTA] = QEMU_RGB(0xaa, 0x00, 0xaa), /* magenta */
+ [QEMU_COLOR_YELLOW] = QEMU_RGB(0xaa, 0xaa, 0x00), /* yellow */
+ [QEMU_COLOR_WHITE] = QEMU_RGB(0xaa, 0xaa, 0xaa), /* white */
+ },
+ { /* bright */
+ [QEMU_COLOR_BLACK] = QEMU_RGB(0x00, 0x00, 0x00), /* black */
+ [QEMU_COLOR_BLUE] = QEMU_RGB(0x00, 0x00, 0xff), /* blue */
+ [QEMU_COLOR_GREEN] = QEMU_RGB(0x00, 0xff, 0x00), /* green */
+ [QEMU_COLOR_CYAN] = QEMU_RGB(0x00, 0xff, 0xff), /* cyan */
+ [QEMU_COLOR_RED] = QEMU_RGB(0xff, 0x00, 0x00), /* red */
+ [QEMU_COLOR_MAGENTA] = QEMU_RGB(0xff, 0x00, 0xff), /* magenta */
+ [QEMU_COLOR_YELLOW] = QEMU_RGB(0xff, 0xff, 0x00), /* yellow */
+ [QEMU_COLOR_WHITE] = QEMU_RGB(0xff, 0xff, 0xff), /* white */
+ }
+};
+
+static void vga_putcharxy(QemuConsole *s, int x, int y, int ch,
+ TextAttributes *t_attrib)
+{
+ static pixman_image_t *glyphs[256];
+ DisplaySurface *surface = qemu_console_surface(s);
+ pixman_color_t fgcol, bgcol;
+
+ if (t_attrib->invers) {
+ bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
+ fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
+ } else {
+ fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
+ bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
+ }
+
+ if (!glyphs[ch]) {
+ glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch);
+ }
+ qemu_pixman_glyph_render(glyphs[ch], surface->image,
+ &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT);
+}
+
+static void text_console_resize(QemuConsole *s)
+{
+ TextCell *cells, *c, *c1;
+ int w1, x, y, last_width;
+
+ last_width = s->width;
+ s->width = surface_width(s->surface) / FONT_WIDTH;
+ s->height = surface_height(s->surface) / FONT_HEIGHT;
+
+ w1 = last_width;
+ if (s->width < w1)
+ w1 = s->width;
+
+ cells = g_new(TextCell, s->width * s->total_height + 1);
+ for(y = 0; y < s->total_height; y++) {
+ c = &cells[y * s->width];
+ if (w1 > 0) {
+ c1 = &s->cells[y * last_width];
+ for(x = 0; x < w1; x++) {
+ *c++ = *c1++;
+ }
+ }
+ for(x = w1; x < s->width; x++) {
+ c->ch = ' ';
+ c->t_attrib = s->t_attrib_default;
+ c++;
+ }
+ }
+ g_free(s->cells);
+ s->cells = cells;
+}
+
+static inline void text_update_xy(QemuConsole *s, int x, int y)
+{
+ s->text_x[0] = MIN(s->text_x[0], x);
+ s->text_x[1] = MAX(s->text_x[1], x);
+ s->text_y[0] = MIN(s->text_y[0], y);
+ s->text_y[1] = MAX(s->text_y[1], y);
+}
+
+static void invalidate_xy(QemuConsole *s, int x, int y)
+{
+ if (!qemu_console_is_visible(s)) {
+ return;
+ }
+ if (s->update_x0 > x * FONT_WIDTH)
+ s->update_x0 = x * FONT_WIDTH;
+ if (s->update_y0 > y * FONT_HEIGHT)
+ s->update_y0 = y * FONT_HEIGHT;
+ if (s->update_x1 < (x + 1) * FONT_WIDTH)
+ s->update_x1 = (x + 1) * FONT_WIDTH;
+ if (s->update_y1 < (y + 1) * FONT_HEIGHT)
+ s->update_y1 = (y + 1) * FONT_HEIGHT;
+}
+
+static void update_xy(QemuConsole *s, int x, int y)
+{
+ TextCell *c;
+ int y1, y2;
+
+ if (s->ds->have_text) {
+ text_update_xy(s, x, y);
+ }
+
+ y1 = (s->y_base + y) % s->total_height;
+ y2 = y1 - s->y_displayed;
+ if (y2 < 0) {
+ y2 += s->total_height;
+ }
+ if (y2 < s->height) {
+ if (x >= s->width) {
+ x = s->width - 1;
+ }
+ c = &s->cells[y1 * s->width + x];
+ vga_putcharxy(s, x, y2, c->ch,
+ &(c->t_attrib));
+ invalidate_xy(s, x, y2);
+ }
+}
+
+static void console_show_cursor(QemuConsole *s, int show)
+{
+ TextCell *c;
+ int y, y1;
+ int x = s->x;
+
+ if (s->ds->have_text) {
+ s->cursor_invalidate = 1;
+ }
+
+ if (x >= s->width) {
+ x = s->width - 1;
+ }
+ y1 = (s->y_base + s->y) % s->total_height;
+ y = y1 - s->y_displayed;
+ if (y < 0) {
+ y += s->total_height;
+ }
+ if (y < s->height) {
+ c = &s->cells[y1 * s->width + x];
+ if (show && cursor_visible_phase) {
+ TextAttributes t_attrib = s->t_attrib_default;
+ t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */
+ vga_putcharxy(s, x, y, c->ch, &t_attrib);
+ } else {
+ vga_putcharxy(s, x, y, c->ch, &(c->t_attrib));
+ }
+ invalidate_xy(s, x, y);
+ }
+}
+
+static void console_refresh(QemuConsole *s)
+{
+ DisplaySurface *surface = qemu_console_surface(s);
+ TextCell *c;
+ int x, y, y1;
+
+ if (s->ds->have_text) {
+ s->text_x[0] = 0;
+ s->text_y[0] = 0;
+ s->text_x[1] = s->width - 1;
+ s->text_y[1] = s->height - 1;
+ s->cursor_invalidate = 1;
+ }
+
+ vga_fill_rect(s, 0, 0, surface_width(surface), surface_height(surface),
+ color_table_rgb[0][QEMU_COLOR_BLACK]);
+ y1 = s->y_displayed;
+ for (y = 0; y < s->height; y++) {
+ c = s->cells + y1 * s->width;
+ for (x = 0; x < s->width; x++) {
+ vga_putcharxy(s, x, y, c->ch,
+ &(c->t_attrib));
+ c++;
+ }
+ if (++y1 == s->total_height) {
+ y1 = 0;
+ }
+ }
+ console_show_cursor(s, 1);
+ dpy_gfx_update(s, 0, 0,
+ surface_width(surface), surface_height(surface));
+}
+
+static void console_scroll(QemuConsole *s, int ydelta)
+{
+ int i, y1;
+
+ if (ydelta > 0) {
+ for(i = 0; i < ydelta; i++) {
+ if (s->y_displayed == s->y_base)
+ break;
+ if (++s->y_displayed == s->total_height)
+ s->y_displayed = 0;
+ }
+ } else {
+ ydelta = -ydelta;
+ i = s->backscroll_height;
+ if (i > s->total_height - s->height)
+ i = s->total_height - s->height;
+ y1 = s->y_base - i;
+ if (y1 < 0)
+ y1 += s->total_height;
+ for(i = 0; i < ydelta; i++) {
+ if (s->y_displayed == y1)
+ break;
+ if (--s->y_displayed < 0)
+ s->y_displayed = s->total_height - 1;
+ }
+ }
+ console_refresh(s);
+}
+
+static void console_put_lf(QemuConsole *s)
+{
+ TextCell *c;
+ int x, y1;
+
+ s->y++;
+ if (s->y >= s->height) {
+ s->y = s->height - 1;
+
+ if (s->y_displayed == s->y_base) {
+ if (++s->y_displayed == s->total_height)
+ s->y_displayed = 0;
+ }
+ if (++s->y_base == s->total_height)
+ s->y_base = 0;
+ if (s->backscroll_height < s->total_height)
+ s->backscroll_height++;
+ y1 = (s->y_base + s->height - 1) % s->total_height;
+ c = &s->cells[y1 * s->width];
+ for(x = 0; x < s->width; x++) {
+ c->ch = ' ';
+ c->t_attrib = s->t_attrib_default;
+ c++;
+ }
+ if (s->y_displayed == s->y_base) {
+ if (s->ds->have_text) {
+ s->text_x[0] = 0;
+ s->text_y[0] = 0;
+ s->text_x[1] = s->width - 1;
+ s->text_y[1] = s->height - 1;
+ }
+
+ vga_bitblt(s, 0, FONT_HEIGHT, 0, 0,
+ s->width * FONT_WIDTH,
+ (s->height - 1) * FONT_HEIGHT);
+ vga_fill_rect(s, 0, (s->height - 1) * FONT_HEIGHT,
+ s->width * FONT_WIDTH, FONT_HEIGHT,
+ color_table_rgb[0][s->t_attrib_default.bgcol]);
+ s->update_x0 = 0;
+ s->update_y0 = 0;
+ s->update_x1 = s->width * FONT_WIDTH;
+ s->update_y1 = s->height * FONT_HEIGHT;
+ }
+ }
+}
+
+/* Set console attributes depending on the current escape codes.
+ * NOTE: I know this code is not very efficient (checking every color for it
+ * self) but it is more readable and better maintainable.
+ */
+static void console_handle_escape(QemuConsole *s)
+{
+ int i;
+
+ for (i=0; i<s->nb_esc_params; i++) {
+ switch (s->esc_params[i]) {
+ case 0: /* reset all console attributes to default */
+ s->t_attrib = s->t_attrib_default;
+ break;
+ case 1:
+ s->t_attrib.bold = 1;
+ break;
+ case 4:
+ s->t_attrib.uline = 1;
+ break;
+ case 5:
+ s->t_attrib.blink = 1;
+ break;
+ case 7:
+ s->t_attrib.invers = 1;
+ break;
+ case 8:
+ s->t_attrib.unvisible = 1;
+ break;
+ case 22:
+ s->t_attrib.bold = 0;
+ break;
+ case 24:
+ s->t_attrib.uline = 0;
+ break;
+ case 25:
+ s->t_attrib.blink = 0;
+ break;
+ case 27:
+ s->t_attrib.invers = 0;
+ break;
+ case 28:
+ s->t_attrib.unvisible = 0;
+ break;
+ /* set foreground color */
+ case 30:
+ s->t_attrib.fgcol = QEMU_COLOR_BLACK;
+ break;
+ case 31:
+ s->t_attrib.fgcol = QEMU_COLOR_RED;
+ break;
+ case 32:
+ s->t_attrib.fgcol = QEMU_COLOR_GREEN;
+ break;
+ case 33:
+ s->t_attrib.fgcol = QEMU_COLOR_YELLOW;
+ break;
+ case 34:
+ s->t_attrib.fgcol = QEMU_COLOR_BLUE;
+ break;
+ case 35:
+ s->t_attrib.fgcol = QEMU_COLOR_MAGENTA;
+ break;
+ case 36:
+ s->t_attrib.fgcol = QEMU_COLOR_CYAN;
+ break;
+ case 37:
+ s->t_attrib.fgcol = QEMU_COLOR_WHITE;
+ break;
+ /* set background color */
+ case 40:
+ s->t_attrib.bgcol = QEMU_COLOR_BLACK;
+ break;
+ case 41:
+ s->t_attrib.bgcol = QEMU_COLOR_RED;
+ break;
+ case 42:
+ s->t_attrib.bgcol = QEMU_COLOR_GREEN;
+ break;
+ case 43:
+ s->t_attrib.bgcol = QEMU_COLOR_YELLOW;
+ break;
+ case 44:
+ s->t_attrib.bgcol = QEMU_COLOR_BLUE;
+ break;
+ case 45:
+ s->t_attrib.bgcol = QEMU_COLOR_MAGENTA;
+ break;
+ case 46:
+ s->t_attrib.bgcol = QEMU_COLOR_CYAN;
+ break;
+ case 47:
+ s->t_attrib.bgcol = QEMU_COLOR_WHITE;
+ break;
+ }
+ }
+}
+
+static void console_clear_xy(QemuConsole *s, int x, int y)
+{
+ int y1 = (s->y_base + y) % s->total_height;
+ if (x >= s->width) {
+ x = s->width - 1;
+ }
+ TextCell *c = &s->cells[y1 * s->width + x];
+ c->ch = ' ';
+ c->t_attrib = s->t_attrib_default;
+ update_xy(s, x, y);
+}
+
+static void console_put_one(QemuConsole *s, int ch)
+{
+ TextCell *c;
+ int y1;
+ if (s->x >= s->width) {
+ /* line wrap */
+ s->x = 0;
+ console_put_lf(s);
+ }
+ y1 = (s->y_base + s->y) % s->total_height;
+ c = &s->cells[y1 * s->width + s->x];
+ c->ch = ch;
+ c->t_attrib = s->t_attrib;
+ update_xy(s, s->x, s->y);
+ s->x++;
+}
+
+static void console_respond_str(QemuConsole *s, const char *buf)
+{
+ while (*buf) {
+ console_put_one(s, *buf);
+ buf++;
+ }
+}
+
+/* set cursor, checking bounds */
+static void set_cursor(QemuConsole *s, int x, int y)
+{
+ if (x < 0) {
+ x = 0;
+ }
+ if (y < 0) {
+ y = 0;
+ }
+ if (y >= s->height) {
+ y = s->height - 1;
+ }
+ if (x >= s->width) {
+ x = s->width - 1;
+ }
+
+ s->x = x;
+ s->y = y;
+}
+
+static void console_putchar(QemuConsole *s, int ch)
+{
+ int i;
+ int x, y;
+ char response[40];
+
+ switch(s->state) {
+ case TTY_STATE_NORM:
+ switch(ch) {
+ case '\r': /* carriage return */
+ s->x = 0;
+ break;
+ case '\n': /* newline */
+ console_put_lf(s);
+ break;
+ case '\b': /* backspace */
+ if (s->x > 0)
+ s->x--;
+ break;
+ case '\t': /* tabspace */
+ if (s->x + (8 - (s->x % 8)) > s->width) {
+ s->x = 0;
+ console_put_lf(s);
+ } else {
+ s->x = s->x + (8 - (s->x % 8));
+ }
+ break;
+ case '\a': /* alert aka. bell */
+ /* TODO: has to be implemented */
+ break;
+ case 14:
+ /* SI (shift in), character set 0 (ignored) */
+ break;
+ case 15:
+ /* SO (shift out), character set 1 (ignored) */
+ break;
+ case 27: /* esc (introducing an escape sequence) */
+ s->state = TTY_STATE_ESC;
+ break;
+ default:
+ console_put_one(s, ch);
+ break;
+ }
+ break;
+ case TTY_STATE_ESC: /* check if it is a terminal escape sequence */
+ if (ch == '[') {
+ for(i=0;i<MAX_ESC_PARAMS;i++)
+ s->esc_params[i] = 0;
+ s->nb_esc_params = 0;
+ s->state = TTY_STATE_CSI;
+ } else {
+ s->state = TTY_STATE_NORM;
+ }
+ break;
+ case TTY_STATE_CSI: /* handle escape sequence parameters */
+ if (ch >= '0' && ch <= '9') {
+ if (s->nb_esc_params < MAX_ESC_PARAMS) {
+ int *param = &s->esc_params[s->nb_esc_params];
+ int digit = (ch - '0');
+
+ *param = (*param <= (INT_MAX - digit) / 10) ?
+ *param * 10 + digit : INT_MAX;
+ }
+ } else {
+ if (s->nb_esc_params < MAX_ESC_PARAMS)
+ s->nb_esc_params++;
+ if (ch == ';' || ch == '?') {
+ break;
+ }
+ trace_console_putchar_csi(s->esc_params[0], s->esc_params[1],
+ ch, s->nb_esc_params);
+ s->state = TTY_STATE_NORM;
+ switch(ch) {
+ case 'A':
+ /* move cursor up */
+ if (s->esc_params[0] == 0) {
+ s->esc_params[0] = 1;
+ }
+ set_cursor(s, s->x, s->y - s->esc_params[0]);
+ break;
+ case 'B':
+ /* move cursor down */
+ if (s->esc_params[0] == 0) {
+ s->esc_params[0] = 1;
+ }
+ set_cursor(s, s->x, s->y + s->esc_params[0]);
+ break;
+ case 'C':
+ /* move cursor right */
+ if (s->esc_params[0] == 0) {
+ s->esc_params[0] = 1;
+ }
+ set_cursor(s, s->x + s->esc_params[0], s->y);
+ break;
+ case 'D':
+ /* move cursor left */
+ if (s->esc_params[0] == 0) {
+ s->esc_params[0] = 1;
+ }
+ set_cursor(s, s->x - s->esc_params[0], s->y);
+ break;
+ case 'G':
+ /* move cursor to column */
+ set_cursor(s, s->esc_params[0] - 1, s->y);
+ break;
+ case 'f':
+ case 'H':
+ /* move cursor to row, column */
+ set_cursor(s, s->esc_params[1] - 1, s->esc_params[0] - 1);
+ break;
+ case 'J':
+ switch (s->esc_params[0]) {
+ case 0:
+ /* clear to end of screen */
+ for (y = s->y; y < s->height; y++) {
+ for (x = 0; x < s->width; x++) {
+ if (y == s->y && x < s->x) {
+ continue;
+ }
+ console_clear_xy(s, x, y);
+ }
+ }
+ break;
+ case 1:
+ /* clear from beginning of screen */
+ for (y = 0; y <= s->y; y++) {
+ for (x = 0; x < s->width; x++) {
+ if (y == s->y && x > s->x) {
+ break;
+ }
+ console_clear_xy(s, x, y);
+ }
+ }
+ break;
+ case 2:
+ /* clear entire screen */
+ for (y = 0; y <= s->height; y++) {
+ for (x = 0; x < s->width; x++) {
+ console_clear_xy(s, x, y);
+ }
+ }
+ break;
+ }
+ break;
+ case 'K':
+ switch (s->esc_params[0]) {
+ case 0:
+ /* clear to eol */
+ for(x = s->x; x < s->width; x++) {
+ console_clear_xy(s, x, s->y);
+ }
+ break;
+ case 1:
+ /* clear from beginning of line */
+ for (x = 0; x <= s->x && x < s->width; x++) {
+ console_clear_xy(s, x, s->y);
+ }
+ break;
+ case 2:
+ /* clear entire line */
+ for(x = 0; x < s->width; x++) {
+ console_clear_xy(s, x, s->y);
+ }
+ break;
+ }
+ break;
+ case 'm':
+ console_handle_escape(s);
+ break;
+ case 'n':
+ switch (s->esc_params[0]) {
+ case 5:
+ /* report console status (always succeed)*/
+ console_respond_str(s, "\033[0n");
+ break;
+ case 6:
+ /* report cursor position */
+ sprintf(response, "\033[%d;%dR",
+ (s->y_base + s->y) % s->total_height + 1,
+ s->x + 1);
+ console_respond_str(s, response);
+ break;
+ }
+ break;
+ case 's':
+ /* save cursor position */
+ s->x_saved = s->x;
+ s->y_saved = s->y;
+ break;
+ case 'u':
+ /* restore cursor position */
+ s->x = s->x_saved;
+ s->y = s->y_saved;
+ break;
+ default:
+ trace_console_putchar_unhandled(ch);
+ break;
+ }
+ break;
+ }
+ }
+}
+
+void console_select(unsigned int index)
+{
+ DisplayChangeListener *dcl;
+ QemuConsole *s;
+
+ trace_console_select(index);
+ s = qemu_console_lookup_by_index(index);
+ if (s) {
+ DisplayState *ds = s->ds;
+
+ active_console = s;
+ if (ds->have_gfx) {
+ QLIST_FOREACH(dcl, &ds->listeners, next) {
+ if (dcl->con != NULL) {
+ continue;
+ }
+ if (dcl->ops->dpy_gfx_switch) {
+ dcl->ops->dpy_gfx_switch(dcl, s->surface);
+ }
+ }
+ if (s->surface) {
+ dpy_gfx_update(s, 0, 0, surface_width(s->surface),
+ surface_height(s->surface));
+ }
+ }
+ if (ds->have_text) {
+ dpy_text_resize(s, s->width, s->height);
+ }
+ text_console_update_cursor(NULL);
+ }
+}
+
+struct VCChardev {
+ Chardev parent;
+ QemuConsole *console;
+};
+typedef struct VCChardev VCChardev;
+
+#define TYPE_CHARDEV_VC "chardev-vc"
+DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
+ TYPE_CHARDEV_VC)
+
+static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ VCChardev *drv = VC_CHARDEV(chr);
+ QemuConsole *s = drv->console;
+ int i;
+
+ if (!s->ds) {
+ return 0;
+ }
+
+ s->update_x0 = s->width * FONT_WIDTH;
+ s->update_y0 = s->height * FONT_HEIGHT;
+ s->update_x1 = 0;
+ s->update_y1 = 0;
+ console_show_cursor(s, 0);
+ for(i = 0; i < len; i++) {
+ console_putchar(s, buf[i]);
+ }
+ console_show_cursor(s, 1);
+ if (s->ds->have_gfx && s->update_x0 < s->update_x1) {
+ dpy_gfx_update(s, s->update_x0, s->update_y0,
+ s->update_x1 - s->update_x0,
+ s->update_y1 - s->update_y0);
+ }
+ return len;
+}
+
+static void kbd_send_chars(QemuConsole *s)
+{
+ uint32_t len, avail;
+
+ len = qemu_chr_be_can_write(s->chr);
+ avail = fifo8_num_used(&s->out_fifo);
+ while (len > 0 && avail > 0) {
+ const uint8_t *buf;
+ uint32_t size;
+
+ buf = fifo8_pop_buf(&s->out_fifo, MIN(len, avail), &size);
+ qemu_chr_be_write(s->chr, (uint8_t *)buf, size);
+ len = qemu_chr_be_can_write(s->chr);
+ avail -= size;
+ }
+}
+
+/* called when an ascii key is pressed */
+void kbd_put_keysym_console(QemuConsole *s, int keysym)
+{
+ uint8_t buf[16], *q;
+ int c;
+ uint32_t num_free;
+
+ if (!s || (s->console_type == GRAPHIC_CONSOLE))
+ return;
+
+ switch(keysym) {
+ case QEMU_KEY_CTRL_UP:
+ console_scroll(s, -1);
+ break;
+ case QEMU_KEY_CTRL_DOWN:
+ console_scroll(s, 1);
+ break;
+ case QEMU_KEY_CTRL_PAGEUP:
+ console_scroll(s, -10);
+ break;
+ case QEMU_KEY_CTRL_PAGEDOWN:
+ console_scroll(s, 10);
+ break;
+ default:
+ /* convert the QEMU keysym to VT100 key string */
+ q = buf;
+ if (keysym >= 0xe100 && keysym <= 0xe11f) {
+ *q++ = '\033';
+ *q++ = '[';
+ c = keysym - 0xe100;
+ if (c >= 10)
+ *q++ = '0' + (c / 10);
+ *q++ = '0' + (c % 10);
+ *q++ = '~';
+ } else if (keysym >= 0xe120 && keysym <= 0xe17f) {
+ *q++ = '\033';
+ *q++ = '[';
+ *q++ = keysym & 0xff;
+ } else if (s->echo && (keysym == '\r' || keysym == '\n')) {
+ vc_chr_write(s->chr, (const uint8_t *) "\r", 1);
+ *q++ = '\n';
+ } else {
+ *q++ = keysym;
+ }
+ if (s->echo) {
+ vc_chr_write(s->chr, buf, q - buf);
+ }
+ num_free = fifo8_num_free(&s->out_fifo);
+ fifo8_push_all(&s->out_fifo, buf, MIN(num_free, q - buf));
+ kbd_send_chars(s);
+ break;
+ }
+}
+
+static const int qcode_to_keysym[Q_KEY_CODE__MAX] = {
+ [Q_KEY_CODE_UP] = QEMU_KEY_UP,
+ [Q_KEY_CODE_DOWN] = QEMU_KEY_DOWN,
+ [Q_KEY_CODE_RIGHT] = QEMU_KEY_RIGHT,
+ [Q_KEY_CODE_LEFT] = QEMU_KEY_LEFT,
+ [Q_KEY_CODE_HOME] = QEMU_KEY_HOME,
+ [Q_KEY_CODE_END] = QEMU_KEY_END,
+ [Q_KEY_CODE_PGUP] = QEMU_KEY_PAGEUP,
+ [Q_KEY_CODE_PGDN] = QEMU_KEY_PAGEDOWN,
+ [Q_KEY_CODE_DELETE] = QEMU_KEY_DELETE,
+ [Q_KEY_CODE_BACKSPACE] = QEMU_KEY_BACKSPACE,
+};
+
+static const int ctrl_qcode_to_keysym[Q_KEY_CODE__MAX] = {
+ [Q_KEY_CODE_UP] = QEMU_KEY_CTRL_UP,
+ [Q_KEY_CODE_DOWN] = QEMU_KEY_CTRL_DOWN,
+ [Q_KEY_CODE_RIGHT] = QEMU_KEY_CTRL_RIGHT,
+ [Q_KEY_CODE_LEFT] = QEMU_KEY_CTRL_LEFT,
+ [Q_KEY_CODE_HOME] = QEMU_KEY_CTRL_HOME,
+ [Q_KEY_CODE_END] = QEMU_KEY_CTRL_END,
+ [Q_KEY_CODE_PGUP] = QEMU_KEY_CTRL_PAGEUP,
+ [Q_KEY_CODE_PGDN] = QEMU_KEY_CTRL_PAGEDOWN,
+};
+
+bool kbd_put_qcode_console(QemuConsole *s, int qcode, bool ctrl)
+{
+ int keysym;
+
+ keysym = ctrl ? ctrl_qcode_to_keysym[qcode] : qcode_to_keysym[qcode];
+ if (keysym == 0) {
+ return false;
+ }
+ kbd_put_keysym_console(s, keysym);
+ return true;
+}
+
+void kbd_put_string_console(QemuConsole *s, const char *str, int len)
+{
+ int i;
+
+ for (i = 0; i < len && str[i]; i++) {
+ kbd_put_keysym_console(s, str[i]);
+ }
+}
+
+void kbd_put_keysym(int keysym)
+{
+ kbd_put_keysym_console(active_console, keysym);
+}
+
+static void text_console_invalidate(void *opaque)
+{
+ QemuConsole *s = (QemuConsole *) opaque;
+
+ if (s->ds->have_text && s->console_type == TEXT_CONSOLE) {
+ text_console_resize(s);
+ }
+ console_refresh(s);
+}
+
+static void text_console_update(void *opaque, console_ch_t *chardata)
+{
+ QemuConsole *s = (QemuConsole *) opaque;
+ int i, j, src;
+
+ if (s->text_x[0] <= s->text_x[1]) {
+ src = (s->y_base + s->text_y[0]) * s->width;
+ chardata += s->text_y[0] * s->width;
+ for (i = s->text_y[0]; i <= s->text_y[1]; i ++)
+ for (j = 0; j < s->width; j++, src++) {
+ console_write_ch(chardata ++,
+ ATTR2CHTYPE(s->cells[src].ch,
+ s->cells[src].t_attrib.fgcol,
+ s->cells[src].t_attrib.bgcol,
+ s->cells[src].t_attrib.bold));
+ }
+ dpy_text_update(s, s->text_x[0], s->text_y[0],
+ s->text_x[1] - s->text_x[0], i - s->text_y[0]);
+ s->text_x[0] = s->width;
+ s->text_y[0] = s->height;
+ s->text_x[1] = 0;
+ s->text_y[1] = 0;
+ }
+ if (s->cursor_invalidate) {
+ dpy_text_cursor(s, s->x, s->y);
+ s->cursor_invalidate = 0;
+ }
+}
+
+static QemuConsole *new_console(DisplayState *ds, console_type_t console_type,
+ uint32_t head)
+{
+ Object *obj;
+ QemuConsole *s;
+ int i;
+
+ obj = object_new(TYPE_QEMU_CONSOLE);
+ s = QEMU_CONSOLE(obj);
+ qemu_co_queue_init(&s->dump_queue);
+ s->head = head;
+ object_property_add_link(obj, "device", TYPE_DEVICE,
+ (Object **)&s->device,
+ object_property_allow_set_link,
+ OBJ_PROP_LINK_STRONG);
+ object_property_add_uint32_ptr(obj, "head", &s->head,
+ OBJ_PROP_FLAG_READ);
+
+ if (!active_console || ((active_console->console_type != GRAPHIC_CONSOLE) &&
+ (console_type == GRAPHIC_CONSOLE))) {
+ active_console = s;
+ }
+ s->ds = ds;
+ s->console_type = console_type;
+ s->window_id = -1;
+
+ if (QTAILQ_EMPTY(&consoles)) {
+ s->index = 0;
+ QTAILQ_INSERT_TAIL(&consoles, s, next);
+ } else if (console_type != GRAPHIC_CONSOLE || phase_check(PHASE_MACHINE_READY)) {
+ QemuConsole *last = QTAILQ_LAST(&consoles);
+ s->index = last->index + 1;
+ QTAILQ_INSERT_TAIL(&consoles, s, next);
+ } else {
+ /*
+ * HACK: Put graphical consoles before text consoles.
+ *
+ * Only do that for coldplugged devices. After initial device
+ * initialization we will not renumber the consoles any more.
+ */
+ QemuConsole *c = QTAILQ_FIRST(&consoles);
+
+ while (QTAILQ_NEXT(c, next) != NULL &&
+ c->console_type == GRAPHIC_CONSOLE) {
+ c = QTAILQ_NEXT(c, next);
+ }
+ if (c->console_type == GRAPHIC_CONSOLE) {
+ /* have no text consoles */
+ s->index = c->index + 1;
+ QTAILQ_INSERT_AFTER(&consoles, c, s, next);
+ } else {
+ s->index = c->index;
+ QTAILQ_INSERT_BEFORE(c, s, next);
+ /* renumber text consoles */
+ for (i = s->index + 1; c != NULL; c = QTAILQ_NEXT(c, next), i++) {
+ c->index = i;
+ }
+ }
+ }
+ return s;
+}
+
+DisplaySurface *qemu_create_displaysurface(int width, int height)
+{
+ DisplaySurface *surface = g_new0(DisplaySurface, 1);
+
+ trace_displaysurface_create(surface, width, height);
+ surface->format = PIXMAN_x8r8g8b8;
+ surface->image = pixman_image_create_bits(surface->format,
+ width, height,
+ NULL, width * 4);
+ assert(surface->image != NULL);
+ surface->flags = QEMU_ALLOCATED_FLAG;
+
+ return surface;
+}
+
+DisplaySurface *qemu_create_displaysurface_from(int width, int height,
+ pixman_format_code_t format,
+ int linesize, uint8_t *data)
+{
+ DisplaySurface *surface = g_new0(DisplaySurface, 1);
+
+ trace_displaysurface_create_from(surface, width, height, format);
+ surface->format = format;
+ surface->image = pixman_image_create_bits(surface->format,
+ width, height,
+ (void *)data, linesize);
+ assert(surface->image != NULL);
+
+ return surface;
+}
+
+DisplaySurface *qemu_create_displaysurface_pixman(pixman_image_t *image)
+{
+ DisplaySurface *surface = g_new0(DisplaySurface, 1);
+
+ trace_displaysurface_create_pixman(surface);
+ surface->format = pixman_image_get_format(image);
+ surface->image = pixman_image_ref(image);
+
+ return surface;
+}
+
+DisplaySurface *qemu_create_placeholder_surface(int w, int h,
+ const char *msg)
+{
+ DisplaySurface *surface = qemu_create_displaysurface(w, h);
+ pixman_color_t bg = color_table_rgb[0][QEMU_COLOR_BLACK];
+ pixman_color_t fg = color_table_rgb[0][QEMU_COLOR_WHITE];
+ pixman_image_t *glyph;
+ int len, x, y, i;
+
+ len = strlen(msg);
+ x = (w / FONT_WIDTH - len) / 2;
+ y = (h / FONT_HEIGHT - 1) / 2;
+ for (i = 0; i < len; i++) {
+ glyph = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, msg[i]);
+ qemu_pixman_glyph_render(glyph, surface->image, &fg, &bg,
+ x+i, y, FONT_WIDTH, FONT_HEIGHT);
+ qemu_pixman_image_unref(glyph);
+ }
+ surface->flags |= QEMU_PLACEHOLDER_FLAG;
+ return surface;
+}
+
+void qemu_free_displaysurface(DisplaySurface *surface)
+{
+ if (surface == NULL) {
+ return;
+ }
+ trace_displaysurface_free(surface);
+ qemu_pixman_image_unref(surface->image);
+ g_free(surface);
+}
+
+bool console_has_gl(QemuConsole *con)
+{
+ return con->gl != NULL;
+}
+
+static bool displaychangelistener_has_dmabuf(DisplayChangeListener *dcl)
+{
+ if (dcl->ops->dpy_has_dmabuf) {
+ return dcl->ops->dpy_has_dmabuf(dcl);
+ }
+
+ if (dcl->ops->dpy_gl_scanout_dmabuf) {
+ return true;
+ }
+
+ return false;
+}
+
+static bool dpy_compatible_with(QemuConsole *con,
+ DisplayChangeListener *dcl, Error **errp)
+{
+ int flags;
+
+ flags = con->hw_ops->get_flags ? con->hw_ops->get_flags(con->hw) : 0;
+
+ if (flags & GRAPHIC_FLAGS_GL &&
+ !console_has_gl(con)) {
+ error_setg(errp, "The console requires a GL context.");
+ return false;
+
+ }
+
+ if (flags & GRAPHIC_FLAGS_DMABUF &&
+ !displaychangelistener_has_dmabuf(dcl)) {
+ error_setg(errp, "The console requires display DMABUF support.");
+ return false;
+ }
+
+ return true;
+}
+
+void register_displaychangelistener(DisplayChangeListener *dcl)
+{
+ static const char nodev[] =
+ "This VM has no graphic display device.";
+ static DisplaySurface *dummy;
+ QemuConsole *con;
+
+ assert(!dcl->ds);
+
+ if (dcl->ops->dpy_gl_ctx_create) {
+ /* display has opengl support */
+ assert(dcl->con);
+ if (dcl->con->gl) {
+ fprintf(stderr, "can't register two opengl displays (%s, %s)\n",
+ dcl->ops->dpy_name, dcl->con->gl->ops->dpy_name);
+ exit(1);
+ }
+ dcl->con->gl = dcl;
+ }
+
+ if (dcl->con) {
+ dpy_compatible_with(dcl->con, dcl, &error_fatal);
+ }
+
+ trace_displaychangelistener_register(dcl, dcl->ops->dpy_name);
+ dcl->ds = get_alloc_displaystate();
+ QLIST_INSERT_HEAD(&dcl->ds->listeners, dcl, next);
+ gui_setup_refresh(dcl->ds);
+ if (dcl->con) {
+ dcl->con->dcls++;
+ con = dcl->con;
+ } else {
+ con = active_console;
+ }
+ if (dcl->ops->dpy_gfx_switch) {
+ if (con) {
+ dcl->ops->dpy_gfx_switch(dcl, con->surface);
+ } else {
+ if (!dummy) {
+ dummy = qemu_create_placeholder_surface(640, 480, nodev);
+ }
+ dcl->ops->dpy_gfx_switch(dcl, dummy);
+ }
+ }
+ text_console_update_cursor(NULL);
+}
+
+void update_displaychangelistener(DisplayChangeListener *dcl,
+ uint64_t interval)
+{
+ DisplayState *ds = dcl->ds;
+
+ dcl->update_interval = interval;
+ if (!ds->refreshing && ds->update_interval > interval) {
+ timer_mod(ds->gui_timer, ds->last_update + interval);
+ }
+}
+
+void unregister_displaychangelistener(DisplayChangeListener *dcl)
+{
+ DisplayState *ds = dcl->ds;
+ trace_displaychangelistener_unregister(dcl, dcl->ops->dpy_name);
+ if (dcl->con) {
+ dcl->con->dcls--;
+ }
+ QLIST_REMOVE(dcl, next);
+ dcl->ds = NULL;
+ gui_setup_refresh(ds);
+}
+
+static void dpy_set_ui_info_timer(void *opaque)
+{
+ QemuConsole *con = opaque;
+
+ con->hw_ops->ui_info(con->hw, con->head, &con->ui_info);
+}
+
+bool dpy_ui_info_supported(QemuConsole *con)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+
+ return con->hw_ops->ui_info != NULL;
+}
+
+const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+
+ return &con->ui_info;
+}
+
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+
+ if (!dpy_ui_info_supported(con)) {
+ return -1;
+ }
+ if (memcmp(&con->ui_info, info, sizeof(con->ui_info)) == 0) {
+ /* nothing changed -- ignore */
+ return 0;
+ }
+
+ /*
+ * Typically we get a flood of these as the user resizes the window.
+ * Wait until the dust has settled (one second without updates), then
+ * go notify the guest.
+ */
+ con->ui_info = *info;
+ timer_mod(con->ui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1000);
+ return 0;
+}
+
+void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+ int width = w;
+ int height = h;
+
+ if (con->surface) {
+ width = surface_width(con->surface);
+ height = surface_height(con->surface);
+ }
+ x = MAX(x, 0);
+ y = MAX(y, 0);
+ x = MIN(x, width);
+ y = MIN(y, height);
+ w = MIN(w, width - x);
+ h = MIN(h, height - y);
+
+ if (!qemu_console_is_visible(con)) {
+ return;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_gfx_update) {
+ dcl->ops->dpy_gfx_update(dcl, x, y, w, h);
+ }
+ }
+}
+
+void dpy_gfx_update_full(QemuConsole *con)
+{
+ if (!con->surface) {
+ return;
+ }
+ dpy_gfx_update(con, 0, 0,
+ surface_width(con->surface),
+ surface_height(con->surface));
+}
+
+void dpy_gfx_replace_surface(QemuConsole *con,
+ DisplaySurface *surface)
+{
+ static const char placeholder_msg[] = "Display output is not active.";
+ DisplayState *s = con->ds;
+ DisplaySurface *old_surface = con->surface;
+ DisplayChangeListener *dcl;
+ int width;
+ int height;
+
+ if (!surface) {
+ if (old_surface) {
+ width = surface_width(old_surface);
+ height = surface_height(old_surface);
+ } else {
+ width = 640;
+ height = 480;
+ }
+
+ surface = qemu_create_placeholder_surface(width, height, placeholder_msg);
+ }
+
+ assert(old_surface != surface);
+
+ con->surface = surface;
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_gfx_switch) {
+ dcl->ops->dpy_gfx_switch(dcl, surface);
+ }
+ }
+ qemu_free_displaysurface(old_surface);
+}
+
+bool dpy_gfx_check_format(QemuConsole *con,
+ pixman_format_code_t format)
+{
+ DisplayChangeListener *dcl;
+ DisplayState *s = con->ds;
+
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->con && dcl->con != con) {
+ /* dcl bound to another console -> skip */
+ continue;
+ }
+ if (dcl->ops->dpy_gfx_check_format) {
+ if (!dcl->ops->dpy_gfx_check_format(dcl, format)) {
+ return false;
+ }
+ } else {
+ /* default is to allow native 32 bpp only */
+ if (format != qemu_default_pixman_format(32, true)) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+static void dpy_refresh(DisplayState *s)
+{
+ DisplayChangeListener *dcl;
+
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_refresh) {
+ dcl->ops->dpy_refresh(dcl);
+ }
+ }
+}
+
+void dpy_text_cursor(QemuConsole *con, int x, int y)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (!qemu_console_is_visible(con)) {
+ return;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_text_cursor) {
+ dcl->ops->dpy_text_cursor(dcl, x, y);
+ }
+ }
+}
+
+void dpy_text_update(QemuConsole *con, int x, int y, int w, int h)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (!qemu_console_is_visible(con)) {
+ return;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_text_update) {
+ dcl->ops->dpy_text_update(dcl, x, y, w, h);
+ }
+ }
+}
+
+void dpy_text_resize(QemuConsole *con, int w, int h)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (!qemu_console_is_visible(con)) {
+ return;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_text_resize) {
+ dcl->ops->dpy_text_resize(dcl, w, h);
+ }
+ }
+}
+
+void dpy_mouse_set(QemuConsole *con, int x, int y, int on)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (!qemu_console_is_visible(con)) {
+ return;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_mouse_set) {
+ dcl->ops->dpy_mouse_set(dcl, x, y, on);
+ }
+ }
+}
+
+void dpy_cursor_define(QemuConsole *con, QEMUCursor *cursor)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ if (!qemu_console_is_visible(con)) {
+ return;
+ }
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (con != (dcl->con ? dcl->con : active_console)) {
+ continue;
+ }
+ if (dcl->ops->dpy_cursor_define) {
+ dcl->ops->dpy_cursor_define(dcl, cursor);
+ }
+ }
+}
+
+bool dpy_cursor_define_supported(QemuConsole *con)
+{
+ DisplayState *s = con->ds;
+ DisplayChangeListener *dcl;
+
+ QLIST_FOREACH(dcl, &s->listeners, next) {
+ if (dcl->ops->dpy_cursor_define) {
+ return true;
+ }
+ }
+ return false;
+}
+
+QEMUGLContext dpy_gl_ctx_create(QemuConsole *con,
+ struct QEMUGLParams *qparams)
+{
+ assert(con->gl);
+ return con->gl->ops->dpy_gl_ctx_create(con->gl, qparams);
+}
+
+void dpy_gl_ctx_destroy(QemuConsole *con, QEMUGLContext ctx)
+{
+ assert(con->gl);
+ con->gl->ops->dpy_gl_ctx_destroy(con->gl, ctx);
+}
+
+int dpy_gl_ctx_make_current(QemuConsole *con, QEMUGLContext ctx)
+{
+ assert(con->gl);
+ return con->gl->ops->dpy_gl_ctx_make_current(con->gl, ctx);
+}
+
+void dpy_gl_scanout_disable(QemuConsole *con)
+{
+ assert(con->gl);
+ con->gl->ops->dpy_gl_scanout_disable(con->gl);
+}
+
+void dpy_gl_scanout_texture(QemuConsole *con,
+ uint32_t backing_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t width, uint32_t height)
+{
+ assert(con->gl);
+ con->gl->ops->dpy_gl_scanout_texture(con->gl, backing_id,
+ backing_y_0_top,
+ backing_width, backing_height,
+ x, y, width, height);
+}
+
+void dpy_gl_scanout_dmabuf(QemuConsole *con,
+ QemuDmaBuf *dmabuf)
+{
+ assert(con->gl);
+ con->gl->ops->dpy_gl_scanout_dmabuf(con->gl, dmabuf);
+}
+
+void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf,
+ bool have_hot, uint32_t hot_x, uint32_t hot_y)
+{
+ assert(con->gl);
+
+ if (con->gl->ops->dpy_gl_cursor_dmabuf) {
+ con->gl->ops->dpy_gl_cursor_dmabuf(con->gl, dmabuf,
+ have_hot, hot_x, hot_y);
+ }
+}
+
+void dpy_gl_cursor_position(QemuConsole *con,
+ uint32_t pos_x, uint32_t pos_y)
+{
+ assert(con->gl);
+
+ if (con->gl->ops->dpy_gl_cursor_position) {
+ con->gl->ops->dpy_gl_cursor_position(con->gl, pos_x, pos_y);
+ }
+}
+
+void dpy_gl_release_dmabuf(QemuConsole *con,
+ QemuDmaBuf *dmabuf)
+{
+ assert(con->gl);
+
+ if (con->gl->ops->dpy_gl_release_dmabuf) {
+ con->gl->ops->dpy_gl_release_dmabuf(con->gl, dmabuf);
+ }
+}
+
+void dpy_gl_update(QemuConsole *con,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ assert(con->gl);
+ con->gl->ops->dpy_gl_update(con->gl, x, y, w, h);
+}
+
+/***********************************************************/
+/* register display */
+
+/* console.c internal use only */
+static DisplayState *get_alloc_displaystate(void)
+{
+ if (!display_state) {
+ display_state = g_new0(DisplayState, 1);
+ cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ text_console_update_cursor, NULL);
+ }
+ return display_state;
+}
+
+/*
+ * Called by main(), after creating QemuConsoles
+ * and before initializing ui (sdl/vnc/...).
+ */
+DisplayState *init_displaystate(void)
+{
+ gchar *name;
+ QemuConsole *con;
+
+ get_alloc_displaystate();
+ QTAILQ_FOREACH(con, &consoles, next) {
+ if (con->console_type != GRAPHIC_CONSOLE &&
+ con->ds == NULL) {
+ text_console_do_init(con->chr, display_state);
+ }
+
+ /* Hook up into the qom tree here (not in new_console()), once
+ * all QemuConsoles are created and the order / numbering
+ * doesn't change any more */
+ name = g_strdup_printf("console[%d]", con->index);
+ object_property_add_child(container_get(object_get_root(), "/backend"),
+ name, OBJECT(con));
+ g_free(name);
+ }
+
+ return display_state;
+}
+
+void graphic_console_set_hwops(QemuConsole *con,
+ const GraphicHwOps *hw_ops,
+ void *opaque)
+{
+ con->hw_ops = hw_ops;
+ con->hw = opaque;
+}
+
+QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head,
+ const GraphicHwOps *hw_ops,
+ void *opaque)
+{
+ static const char noinit[] =
+ "Guest has not initialized the display (yet).";
+ int width = 640;
+ int height = 480;
+ QemuConsole *s;
+ DisplayState *ds;
+ DisplaySurface *surface;
+
+ ds = get_alloc_displaystate();
+ s = qemu_console_lookup_unused();
+ if (s) {
+ trace_console_gfx_reuse(s->index);
+ if (s->surface) {
+ width = surface_width(s->surface);
+ height = surface_height(s->surface);
+ }
+ } else {
+ trace_console_gfx_new();
+ s = new_console(ds, GRAPHIC_CONSOLE, head);
+ s->ui_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ dpy_set_ui_info_timer, s);
+ }
+ graphic_console_set_hwops(s, hw_ops, opaque);
+ if (dev) {
+ object_property_set_link(OBJECT(s), "device", OBJECT(dev),
+ &error_abort);
+ }
+
+ surface = qemu_create_placeholder_surface(width, height, noinit);
+ dpy_gfx_replace_surface(s, surface);
+ return s;
+}
+
+static const GraphicHwOps unused_ops = {
+ /* no callbacks */
+};
+
+void graphic_console_close(QemuConsole *con)
+{
+ static const char unplugged[] =
+ "Guest display has been unplugged";
+ DisplaySurface *surface;
+ int width = 640;
+ int height = 480;
+
+ if (con->surface) {
+ width = surface_width(con->surface);
+ height = surface_height(con->surface);
+ }
+
+ trace_console_gfx_close(con->index);
+ object_property_set_link(OBJECT(con), "device", NULL, &error_abort);
+ graphic_console_set_hwops(con, &unused_ops, NULL);
+
+ if (con->gl) {
+ dpy_gl_scanout_disable(con);
+ }
+ surface = qemu_create_placeholder_surface(width, height, unplugged);
+ dpy_gfx_replace_surface(con, surface);
+}
+
+QemuConsole *qemu_console_lookup_by_index(unsigned int index)
+{
+ QemuConsole *con;
+
+ QTAILQ_FOREACH(con, &consoles, next) {
+ if (con->index == index) {
+ return con;
+ }
+ }
+ return NULL;
+}
+
+QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head)
+{
+ QemuConsole *con;
+ Object *obj;
+ uint32_t h;
+
+ QTAILQ_FOREACH(con, &consoles, next) {
+ obj = object_property_get_link(OBJECT(con),
+ "device", &error_abort);
+ if (DEVICE(obj) != dev) {
+ continue;
+ }
+ h = object_property_get_uint(OBJECT(con),
+ "head", &error_abort);
+ if (h != head) {
+ continue;
+ }
+ return con;
+ }
+ return NULL;
+}
+
+QemuConsole *qemu_console_lookup_by_device_name(const char *device_id,
+ uint32_t head, Error **errp)
+{
+ DeviceState *dev;
+ QemuConsole *con;
+
+ dev = qdev_find_recursive(sysbus_get_default(), device_id);
+ if (dev == NULL) {
+ error_set(errp, ERROR_CLASS_DEVICE_NOT_FOUND,
+ "Device '%s' not found", device_id);
+ return NULL;
+ }
+
+ con = qemu_console_lookup_by_device(dev, head);
+ if (con == NULL) {
+ error_setg(errp, "Device %s (head %d) is not bound to a QemuConsole",
+ device_id, head);
+ return NULL;
+ }
+
+ return con;
+}
+
+QemuConsole *qemu_console_lookup_unused(void)
+{
+ QemuConsole *con;
+ Object *obj;
+
+ QTAILQ_FOREACH(con, &consoles, next) {
+ if (con->hw_ops != &unused_ops) {
+ continue;
+ }
+ obj = object_property_get_link(OBJECT(con),
+ "device", &error_abort);
+ if (obj != NULL) {
+ continue;
+ }
+ return con;
+ }
+ return NULL;
+}
+
+bool qemu_console_is_visible(QemuConsole *con)
+{
+ return (con == active_console) || (con->dcls > 0);
+}
+
+bool qemu_console_is_graphic(QemuConsole *con)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+ return con && (con->console_type == GRAPHIC_CONSOLE);
+}
+
+bool qemu_console_is_fixedsize(QemuConsole *con)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+ return con && (con->console_type != TEXT_CONSOLE);
+}
+
+bool qemu_console_is_gl_blocked(QemuConsole *con)
+{
+ assert(con != NULL);
+ return con->gl_block;
+}
+
+char *qemu_console_get_label(QemuConsole *con)
+{
+ if (con->console_type == GRAPHIC_CONSOLE) {
+ if (con->device) {
+ return g_strdup(object_get_typename(con->device));
+ }
+ return g_strdup("VGA");
+ } else {
+ if (con->chr && con->chr->label) {
+ return g_strdup(con->chr->label);
+ }
+ return g_strdup_printf("vc%d", con->index);
+ }
+}
+
+int qemu_console_get_index(QemuConsole *con)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+ return con ? con->index : -1;
+}
+
+uint32_t qemu_console_get_head(QemuConsole *con)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+ return con ? con->head : -1;
+}
+
+int qemu_console_get_width(QemuConsole *con, int fallback)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+ return con ? surface_width(con->surface) : fallback;
+}
+
+int qemu_console_get_height(QemuConsole *con, int fallback)
+{
+ if (con == NULL) {
+ con = active_console;
+ }
+ return con ? surface_height(con->surface) : fallback;
+}
+
+static void vc_chr_accept_input(Chardev *chr)
+{
+ VCChardev *drv = VC_CHARDEV(chr);
+ QemuConsole *s = drv->console;
+
+ kbd_send_chars(s);
+}
+
+static void vc_chr_set_echo(Chardev *chr, bool echo)
+{
+ VCChardev *drv = VC_CHARDEV(chr);
+ QemuConsole *s = drv->console;
+
+ s->echo = echo;
+}
+
+static void text_console_update_cursor_timer(void)
+{
+ timer_mod(cursor_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)
+ + CONSOLE_CURSOR_PERIOD / 2);
+}
+
+static void text_console_update_cursor(void *opaque)
+{
+ QemuConsole *s;
+ int count = 0;
+
+ cursor_visible_phase = !cursor_visible_phase;
+
+ QTAILQ_FOREACH(s, &consoles, next) {
+ if (qemu_console_is_graphic(s) ||
+ !qemu_console_is_visible(s)) {
+ continue;
+ }
+ count++;
+ graphic_hw_invalidate(s);
+ }
+
+ if (count) {
+ text_console_update_cursor_timer();
+ }
+}
+
+static const GraphicHwOps text_console_ops = {
+ .invalidate = text_console_invalidate,
+ .text_update = text_console_update,
+};
+
+static void text_console_do_init(Chardev *chr, DisplayState *ds)
+{
+ VCChardev *drv = VC_CHARDEV(chr);
+ QemuConsole *s = drv->console;
+ int g_width = 80 * FONT_WIDTH;
+ int g_height = 24 * FONT_HEIGHT;
+
+ fifo8_create(&s->out_fifo, 16);
+ s->ds = ds;
+
+ s->y_displayed = 0;
+ s->y_base = 0;
+ s->total_height = DEFAULT_BACKSCROLL;
+ s->x = 0;
+ s->y = 0;
+ if (!s->surface) {
+ if (active_console && active_console->surface) {
+ g_width = surface_width(active_console->surface);
+ g_height = surface_height(active_console->surface);
+ }
+ s->surface = qemu_create_displaysurface(g_width, g_height);
+ }
+
+ s->hw_ops = &text_console_ops;
+ s->hw = s;
+
+ /* Set text attribute defaults */
+ s->t_attrib_default.bold = 0;
+ s->t_attrib_default.uline = 0;
+ s->t_attrib_default.blink = 0;
+ s->t_attrib_default.invers = 0;
+ s->t_attrib_default.unvisible = 0;
+ s->t_attrib_default.fgcol = QEMU_COLOR_WHITE;
+ s->t_attrib_default.bgcol = QEMU_COLOR_BLACK;
+ /* set current text attributes to default */
+ s->t_attrib = s->t_attrib_default;
+ text_console_resize(s);
+
+ if (chr->label) {
+ char *msg;
+
+ s->t_attrib.bgcol = QEMU_COLOR_BLUE;
+ msg = g_strdup_printf("%s console\r\n", chr->label);
+ vc_chr_write(chr, (uint8_t *)msg, strlen(msg));
+ g_free(msg);
+ s->t_attrib = s->t_attrib_default;
+ }
+
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+}
+
+static void vc_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevVC *vc = backend->u.vc.data;
+ VCChardev *drv = VC_CHARDEV(chr);
+ QemuConsole *s;
+ unsigned width = 0;
+ unsigned height = 0;
+
+ if (vc->has_width) {
+ width = vc->width;
+ } else if (vc->has_cols) {
+ width = vc->cols * FONT_WIDTH;
+ }
+
+ if (vc->has_height) {
+ height = vc->height;
+ } else if (vc->has_rows) {
+ height = vc->rows * FONT_HEIGHT;
+ }
+
+ trace_console_txt_new(width, height);
+ if (width == 0 || height == 0) {
+ s = new_console(NULL, TEXT_CONSOLE, 0);
+ } else {
+ s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
+ s->surface = qemu_create_displaysurface(width, height);
+ }
+
+ if (!s) {
+ error_setg(errp, "cannot create text console");
+ return;
+ }
+
+ s->chr = chr;
+ drv->console = s;
+
+ if (display_state) {
+ text_console_do_init(chr, display_state);
+ }
+
+ /* console/chardev init sometimes completes elsewhere in a 2nd
+ * stage, so defer OPENED events until they are fully initialized
+ */
+ *be_opened = false;
+}
+
+void qemu_console_resize(QemuConsole *s, int width, int height)
+{
+ DisplaySurface *surface;
+
+ assert(s->console_type == GRAPHIC_CONSOLE);
+
+ if (s->surface && (s->surface->flags & QEMU_ALLOCATED_FLAG) &&
+ pixman_image_get_width(s->surface->image) == width &&
+ pixman_image_get_height(s->surface->image) == height) {
+ return;
+ }
+
+ surface = qemu_create_displaysurface(width, height);
+ dpy_gfx_replace_surface(s, surface);
+}
+
+DisplaySurface *qemu_console_surface(QemuConsole *console)
+{
+ return console->surface;
+}
+
+PixelFormat qemu_default_pixelformat(int bpp)
+{
+ pixman_format_code_t fmt = qemu_default_pixman_format(bpp, true);
+ PixelFormat pf = qemu_pixelformat_from_pixman(fmt);
+ return pf;
+}
+
+static QemuDisplay *dpys[DISPLAY_TYPE__MAX];
+
+void qemu_display_register(QemuDisplay *ui)
+{
+ assert(ui->type < DISPLAY_TYPE__MAX);
+ dpys[ui->type] = 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 < (int)ARRAY_SIZE(prio); i++) {
+ if (dpys[prio[i]] == NULL) {
+ ui_module_load_one(DisplayType_str(prio[i]));
+ }
+ if (dpys[prio[i]] == NULL) {
+ continue;
+ }
+ opts->type = prio[i];
+ return true;
+ }
+ return false;
+}
+
+void qemu_display_early_init(DisplayOptions *opts)
+{
+ assert(opts->type < DISPLAY_TYPE__MAX);
+ if (opts->type == DISPLAY_TYPE_NONE) {
+ return;
+ }
+ if (dpys[opts->type] == NULL) {
+ ui_module_load_one(DisplayType_str(opts->type));
+ }
+ if (dpys[opts->type] == NULL) {
+ error_report("Display '%s' is not available.",
+ DisplayType_str(opts->type));
+ exit(1);
+ }
+ if (dpys[opts->type]->early_init) {
+ dpys[opts->type]->early_init(opts);
+ }
+}
+
+void qemu_display_init(DisplayState *ds, DisplayOptions *opts)
+{
+ assert(opts->type < DISPLAY_TYPE__MAX);
+ if (opts->type == DISPLAY_TYPE_NONE) {
+ return;
+ }
+ assert(dpys[opts->type] != NULL);
+ dpys[opts->type]->init(ds, opts);
+}
+
+void qemu_display_help(void)
+{
+ int idx;
+
+ printf("Available display backend types:\n");
+ printf("none\n");
+ for (idx = DISPLAY_TYPE_NONE; idx < DISPLAY_TYPE__MAX; idx++) {
+ if (!dpys[idx]) {
+ ui_module_load_one(DisplayType_str(idx));
+ }
+ if (dpys[idx]) {
+ printf("%s\n", DisplayType_str(dpys[idx]->type));
+ }
+ }
+}
+
+void qemu_chr_parse_vc(QemuOpts *opts, ChardevBackend *backend, Error **errp)
+{
+ int val;
+ ChardevVC *vc;
+
+ backend->type = CHARDEV_BACKEND_KIND_VC;
+ vc = backend->u.vc.data = g_new0(ChardevVC, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc));
+
+ val = qemu_opt_get_number(opts, "width", 0);
+ if (val != 0) {
+ vc->has_width = true;
+ vc->width = val;
+ }
+
+ val = qemu_opt_get_number(opts, "height", 0);
+ if (val != 0) {
+ vc->has_height = true;
+ vc->height = val;
+ }
+
+ val = qemu_opt_get_number(opts, "cols", 0);
+ if (val != 0) {
+ vc->has_cols = true;
+ vc->cols = val;
+ }
+
+ val = qemu_opt_get_number(opts, "rows", 0);
+ if (val != 0) {
+ vc->has_rows = true;
+ vc->rows = val;
+ }
+}
+
+static const TypeInfo qemu_console_info = {
+ .name = TYPE_QEMU_CONSOLE,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(QemuConsole),
+ .class_size = sizeof(QemuConsoleClass),
+};
+
+static void char_vc_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_vc;
+ cc->open = vc_chr_open;
+ cc->chr_write = vc_chr_write;
+ cc->chr_accept_input = vc_chr_accept_input;
+ cc->chr_set_echo = vc_chr_set_echo;
+}
+
+static const TypeInfo char_vc_type_info = {
+ .name = TYPE_CHARDEV_VC,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(VCChardev),
+ .class_init = char_vc_class_init,
+};
+
+void qemu_console_early_init(void)
+{
+ /* set the default vc driver */
+ if (!object_class_by_name(TYPE_CHARDEV_VC)) {
+ type_register(&char_vc_type_info);
+ }
+}
+
+static void register_types(void)
+{
+ type_register_static(&qemu_console_info);
+}
+
+type_init(register_types);
diff --git a/ui/curses.c b/ui/curses.c
new file mode 100644
index 000000000..861d63244
--- /dev/null
+++ b/ui/curses.c
@@ -0,0 +1,817 @@
+/*
+ * QEMU curses/ncurses display driver
+ *
+ * Copyright (c) 2005 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+#ifndef _WIN32
+#include <sys/ioctl.h>
+#include <termios.h>
+#endif
+#include <locale.h>
+#include <wchar.h>
+#include <iconv.h>
+
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "sysemu/sysemu.h"
+
+#if defined(__APPLE__) || defined(__OpenBSD__)
+#define _XOPEN_SOURCE_EXTENDED 1
+#endif
+
+/* KEY_EVENT is defined in wincon.h and in curses.h. Avoid redefinition. */
+#undef KEY_EVENT
+#include <curses.h>
+#undef KEY_EVENT
+
+#define FONT_HEIGHT 16
+#define FONT_WIDTH 8
+
+enum maybe_keycode {
+ CURSES_KEYCODE,
+ CURSES_CHAR,
+ CURSES_CHAR_OR_KEYCODE,
+};
+
+static DisplayChangeListener *dcl;
+static console_ch_t *screen;
+static WINDOW *screenpad = NULL;
+static int width, height, gwidth, gheight, invalidate;
+static int px, py, sminx, sminy, smaxx, smaxy;
+
+static const char *font_charset = "CP437";
+static cchar_t *vga_to_curses;
+
+static void curses_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ console_ch_t *line;
+ cchar_t curses_line[width];
+ wchar_t wch[CCHARW_MAX];
+ attr_t attrs;
+ short colors;
+ int ret;
+
+ line = screen + y * width;
+ for (h += y; y < h; y ++, line += width) {
+ for (x = 0; x < width; x++) {
+ chtype ch = line[x] & A_CHARTEXT;
+ chtype at = line[x] & A_ATTRIBUTES;
+ short color_pair = PAIR_NUMBER(line[x]);
+
+ ret = getcchar(&vga_to_curses[ch], wch, &attrs, &colors, NULL);
+ if (ret == ERR || wch[0] == 0) {
+ wch[0] = ch;
+ wch[1] = 0;
+ }
+ setcchar(&curses_line[x], wch, at, color_pair, NULL);
+ }
+ mvwadd_wchnstr(screenpad, y, 0, curses_line, width);
+ }
+
+ pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1);
+ refresh();
+}
+
+static void curses_calc_pad(void)
+{
+ if (qemu_console_is_fixedsize(NULL)) {
+ width = gwidth;
+ height = gheight;
+ } else {
+ width = COLS;
+ height = LINES;
+ }
+
+ if (screenpad)
+ delwin(screenpad);
+
+ clear();
+ refresh();
+
+ screenpad = newpad(height, width);
+
+ if (width > COLS) {
+ px = (width - COLS) / 2;
+ sminx = 0;
+ smaxx = COLS;
+ } else {
+ px = 0;
+ sminx = (COLS - width) / 2;
+ smaxx = sminx + width;
+ }
+
+ if (height > LINES) {
+ py = (height - LINES) / 2;
+ sminy = 0;
+ smaxy = LINES;
+ } else {
+ py = 0;
+ sminy = (LINES - height) / 2;
+ smaxy = sminy + height;
+ }
+}
+
+static void curses_resize(DisplayChangeListener *dcl,
+ int width, int height)
+{
+ if (width == gwidth && height == gheight) {
+ return;
+ }
+
+ gwidth = width;
+ gheight = height;
+
+ curses_calc_pad();
+}
+
+#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE)
+static volatile sig_atomic_t got_sigwinch;
+static void curses_winch_check(void)
+{
+ struct winsize {
+ unsigned short ws_row;
+ unsigned short ws_col;
+ unsigned short ws_xpixel; /* unused */
+ unsigned short ws_ypixel; /* unused */
+ } ws;
+
+ if (!got_sigwinch) {
+ return;
+ }
+ got_sigwinch = false;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
+ return;
+ }
+
+ resize_term(ws.ws_row, ws.ws_col);
+ invalidate = 1;
+}
+
+static void curses_winch_handler(int signum)
+{
+ got_sigwinch = true;
+}
+
+static void curses_winch_init(void)
+{
+ struct sigaction old, winch = {
+ .sa_handler = curses_winch_handler,
+ };
+ sigaction(SIGWINCH, &winch, &old);
+}
+#else
+static void curses_winch_check(void) {}
+static void curses_winch_init(void) {}
+#endif
+
+static void curses_cursor_position(DisplayChangeListener *dcl,
+ int x, int y)
+{
+ if (x >= 0) {
+ x = sminx + x - px;
+ y = sminy + y - py;
+
+ if (x >= 0 && y >= 0 && x < COLS && y < LINES) {
+ move(y, x);
+ curs_set(1);
+ /* it seems that curs_set(1) must always be called before
+ * curs_set(2) for the latter to have effect */
+ if (!qemu_console_is_graphic(NULL)) {
+ curs_set(2);
+ }
+ return;
+ }
+ }
+
+ curs_set(0);
+}
+
+/* generic keyboard conversion */
+
+#include "curses_keys.h"
+
+static kbd_layout_t *kbd_layout = NULL;
+
+static wint_t console_getch(enum maybe_keycode *maybe_keycode)
+{
+ wint_t ret;
+ switch (get_wch(&ret)) {
+ case KEY_CODE_YES:
+ *maybe_keycode = CURSES_KEYCODE;
+ break;
+ case OK:
+ *maybe_keycode = CURSES_CHAR;
+ break;
+ case ERR:
+ ret = -1;
+ break;
+ default:
+ abort();
+ }
+ return ret;
+}
+
+static int curses2foo(const int _curses2foo[], const int _curseskey2foo[],
+ int chr, enum maybe_keycode maybe_keycode)
+{
+ int ret = -1;
+ if (maybe_keycode == CURSES_CHAR) {
+ if (chr < CURSES_CHARS) {
+ ret = _curses2foo[chr];
+ }
+ } else {
+ if (chr < CURSES_KEYS) {
+ ret = _curseskey2foo[chr];
+ }
+ if (ret == -1 && maybe_keycode == CURSES_CHAR_OR_KEYCODE &&
+ chr < CURSES_CHARS) {
+ ret = _curses2foo[chr];
+ }
+ }
+ return ret;
+}
+
+#define curses2keycode(chr, maybe_keycode) \
+ curses2foo(_curses2keycode, _curseskey2keycode, chr, maybe_keycode)
+#define curses2keysym(chr, maybe_keycode) \
+ curses2foo(_curses2keysym, _curseskey2keysym, chr, maybe_keycode)
+#define curses2qemu(chr, maybe_keycode) \
+ curses2foo(_curses2qemu, _curseskey2qemu, chr, maybe_keycode)
+
+static void curses_refresh(DisplayChangeListener *dcl)
+{
+ int chr, keysym, keycode, keycode_alt;
+ enum maybe_keycode maybe_keycode = CURSES_KEYCODE;
+
+ curses_winch_check();
+
+ if (invalidate) {
+ clear();
+ refresh();
+ curses_calc_pad();
+ graphic_hw_invalidate(NULL);
+ invalidate = 0;
+ }
+
+ graphic_hw_text_update(NULL, screen);
+
+ while (1) {
+ /* while there are any pending key strokes to process */
+ chr = console_getch(&maybe_keycode);
+
+ if (chr == -1)
+ break;
+
+#ifdef KEY_RESIZE
+ /* this shouldn't occur when we use a custom SIGWINCH handler */
+ if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
+ clear();
+ refresh();
+ curses_calc_pad();
+ curses_update(dcl, 0, 0, width, height);
+ continue;
+ }
+#endif
+
+ keycode = curses2keycode(chr, maybe_keycode);
+ keycode_alt = 0;
+
+ /* alt or esc key */
+ if (keycode == 1) {
+ enum maybe_keycode next_maybe_keycode = CURSES_KEYCODE;
+ int nextchr = console_getch(&next_maybe_keycode);
+
+ if (nextchr != -1) {
+ chr = nextchr;
+ maybe_keycode = next_maybe_keycode;
+ keycode_alt = ALT;
+ keycode = curses2keycode(chr, maybe_keycode);
+
+ if (keycode != -1) {
+ keycode |= ALT;
+
+ /* process keys reserved for qemu */
+ if (keycode >= QEMU_KEY_CONSOLE0 &&
+ keycode < QEMU_KEY_CONSOLE0 + 9) {
+ erase();
+ wnoutrefresh(stdscr);
+ console_select(keycode - QEMU_KEY_CONSOLE0);
+
+ invalidate = 1;
+ continue;
+ }
+ }
+ }
+ }
+
+ if (kbd_layout) {
+ keysym = curses2keysym(chr, maybe_keycode);
+
+ if (keysym == -1) {
+ if (chr < ' ') {
+ keysym = chr + '@';
+ if (keysym >= 'A' && keysym <= 'Z')
+ keysym += 'a' - 'A';
+ keysym |= KEYSYM_CNTRL;
+ } else
+ keysym = chr;
+ }
+
+ keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
+ NULL, false);
+ if (keycode == 0)
+ continue;
+
+ keycode |= (keysym & ~KEYSYM_MASK) >> 16;
+ keycode |= keycode_alt;
+ }
+
+ if (keycode == -1)
+ continue;
+
+ if (qemu_console_is_graphic(NULL)) {
+ /* since terminals don't know about key press and release
+ * events, we need to emit both for each key received */
+ if (keycode & SHIFT) {
+ qemu_input_event_send_key_number(NULL, SHIFT_CODE, true);
+ qemu_input_event_send_key_delay(0);
+ }
+ if (keycode & CNTRL) {
+ qemu_input_event_send_key_number(NULL, CNTRL_CODE, true);
+ qemu_input_event_send_key_delay(0);
+ }
+ if (keycode & ALT) {
+ qemu_input_event_send_key_number(NULL, ALT_CODE, true);
+ qemu_input_event_send_key_delay(0);
+ }
+ if (keycode & ALTGR) {
+ qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
+ qemu_input_event_send_key_delay(0);
+ }
+
+ qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true);
+ qemu_input_event_send_key_delay(0);
+ qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false);
+ qemu_input_event_send_key_delay(0);
+
+ if (keycode & ALTGR) {
+ qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false);
+ qemu_input_event_send_key_delay(0);
+ }
+ if (keycode & ALT) {
+ qemu_input_event_send_key_number(NULL, ALT_CODE, false);
+ qemu_input_event_send_key_delay(0);
+ }
+ if (keycode & CNTRL) {
+ qemu_input_event_send_key_number(NULL, CNTRL_CODE, false);
+ qemu_input_event_send_key_delay(0);
+ }
+ if (keycode & SHIFT) {
+ qemu_input_event_send_key_number(NULL, SHIFT_CODE, false);
+ qemu_input_event_send_key_delay(0);
+ }
+ } else {
+ keysym = curses2qemu(chr, maybe_keycode);
+ if (keysym == -1)
+ keysym = chr;
+
+ kbd_put_keysym(keysym);
+ }
+ }
+}
+
+static void curses_atexit(void)
+{
+ endwin();
+ g_free(vga_to_curses);
+ g_free(screen);
+}
+
+/*
+ * In the following:
+ * - fch is the font glyph number
+ * - uch is the unicode value
+ * - wch is the wchar_t value (may not be unicode, e.g. on BSD/solaris)
+ * - mbch is the native local-dependent multibyte representation
+ */
+
+/* Setup wchar glyph for one UCS-2 char */
+static void convert_ucs(unsigned char fch, uint16_t uch, iconv_t conv)
+{
+ char mbch[MB_LEN_MAX];
+ wchar_t wch[2];
+ char *puch, *pmbch;
+ size_t such, smbch;
+ mbstate_t ps;
+
+ puch = (char *) &uch;
+ pmbch = (char *) mbch;
+ such = sizeof(uch);
+ smbch = sizeof(mbch);
+
+ if (iconv(conv, &puch, &such, &pmbch, &smbch) == (size_t) -1) {
+ fprintf(stderr, "Could not convert 0x%04x "
+ "from UCS-2 to a multibyte character: %s\n",
+ uch, strerror(errno));
+ return;
+ }
+
+ memset(&ps, 0, sizeof(ps));
+ if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
+ fprintf(stderr, "Could not convert 0x%04x "
+ "from a multibyte character to wchar_t: %s\n",
+ uch, strerror(errno));
+ return;
+ }
+
+ wch[1] = 0;
+ setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
+}
+
+/* Setup wchar glyph for one font character */
+static void convert_font(unsigned char fch, iconv_t conv)
+{
+ char mbch[MB_LEN_MAX];
+ wchar_t wch[2];
+ char *pfch, *pmbch;
+ size_t sfch, smbch;
+ mbstate_t ps;
+
+ pfch = (char *) &fch;
+ pmbch = (char *) &mbch;
+ sfch = sizeof(fch);
+ smbch = sizeof(mbch);
+
+ if (iconv(conv, &pfch, &sfch, &pmbch, &smbch) == (size_t) -1) {
+ fprintf(stderr, "Could not convert font glyph 0x%02x "
+ "from %s to a multibyte character: %s\n",
+ fch, font_charset, strerror(errno));
+ return;
+ }
+
+ memset(&ps, 0, sizeof(ps));
+ if (mbrtowc(&wch[0], mbch, sizeof(mbch) - smbch, &ps) == -1) {
+ fprintf(stderr, "Could not convert font glyph 0x%02x "
+ "from a multibyte character to wchar_t: %s\n",
+ fch, strerror(errno));
+ return;
+ }
+
+ wch[1] = 0;
+ setcchar(&vga_to_curses[fch], wch, 0, 0, NULL);
+}
+
+/* Convert one wchar to UCS-2 */
+static uint16_t get_ucs(wchar_t wch, iconv_t conv)
+{
+ char mbch[MB_LEN_MAX];
+ uint16_t uch;
+ char *pmbch, *puch;
+ size_t smbch, such;
+ mbstate_t ps;
+ int ret;
+
+ memset(&ps, 0, sizeof(ps));
+ ret = wcrtomb(mbch, wch, &ps);
+ if (ret == -1) {
+ fprintf(stderr, "Could not convert 0x%04lx "
+ "from wchar_t to a multibyte character: %s\n",
+ (unsigned long)wch, strerror(errno));
+ return 0xFFFD;
+ }
+
+ pmbch = (char *) mbch;
+ puch = (char *) &uch;
+ smbch = ret;
+ such = sizeof(uch);
+
+ if (iconv(conv, &pmbch, &smbch, &puch, &such) == (size_t) -1) {
+ fprintf(stderr, "Could not convert 0x%04lx "
+ "from a multibyte character to UCS-2 : %s\n",
+ (unsigned long)wch, strerror(errno));
+ return 0xFFFD;
+ }
+
+ return uch;
+}
+
+/*
+ * Setup mapping for vga to curses line graphics.
+ */
+static void font_setup(void)
+{
+ iconv_t ucs2_to_nativecharset;
+ iconv_t nativecharset_to_ucs2;
+ iconv_t font_conv;
+ int i;
+ g_autofree gchar *local_codeset = g_get_codeset();
+
+ /*
+ * Control characters are normally non-printable, but VGA does have
+ * well-known glyphs for them.
+ */
+ static const uint16_t control_characters[0x20] = {
+ 0x0020,
+ 0x263a,
+ 0x263b,
+ 0x2665,
+ 0x2666,
+ 0x2663,
+ 0x2660,
+ 0x2022,
+ 0x25d8,
+ 0x25cb,
+ 0x25d9,
+ 0x2642,
+ 0x2640,
+ 0x266a,
+ 0x266b,
+ 0x263c,
+ 0x25ba,
+ 0x25c4,
+ 0x2195,
+ 0x203c,
+ 0x00b6,
+ 0x00a7,
+ 0x25ac,
+ 0x21a8,
+ 0x2191,
+ 0x2193,
+ 0x2192,
+ 0x2190,
+ 0x221f,
+ 0x2194,
+ 0x25b2,
+ 0x25bc
+ };
+
+ ucs2_to_nativecharset = iconv_open(local_codeset, "UCS-2");
+ if (ucs2_to_nativecharset == (iconv_t) -1) {
+ fprintf(stderr, "Could not convert font glyphs from UCS-2: '%s'\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ nativecharset_to_ucs2 = iconv_open("UCS-2", local_codeset);
+ if (nativecharset_to_ucs2 == (iconv_t) -1) {
+ iconv_close(ucs2_to_nativecharset);
+ fprintf(stderr, "Could not convert font glyphs to UCS-2: '%s'\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ font_conv = iconv_open(local_codeset, font_charset);
+ if (font_conv == (iconv_t) -1) {
+ iconv_close(ucs2_to_nativecharset);
+ iconv_close(nativecharset_to_ucs2);
+ fprintf(stderr, "Could not convert font glyphs from %s: '%s'\n",
+ font_charset, strerror(errno));
+ exit(1);
+ }
+
+ /* Control characters */
+ for (i = 0; i <= 0x1F; i++) {
+ convert_ucs(i, control_characters[i], ucs2_to_nativecharset);
+ }
+
+ for (i = 0x20; i <= 0xFF; i++) {
+ convert_font(i, font_conv);
+ }
+
+ /* DEL */
+ convert_ucs(0x7F, 0x2302, ucs2_to_nativecharset);
+
+ if (strcmp(local_codeset, "UTF-8")) {
+ /* Non-Unicode capable, use termcap equivalents for those available */
+ for (i = 0; i <= 0xFF; i++) {
+ wchar_t wch[CCHARW_MAX];
+ attr_t attr;
+ short color;
+ int ret;
+
+ ret = getcchar(&vga_to_curses[i], wch, &attr, &color, NULL);
+ if (ret == ERR)
+ continue;
+
+ switch (get_ucs(wch[0], nativecharset_to_ucs2)) {
+ case 0x00a3:
+ vga_to_curses[i] = *WACS_STERLING;
+ break;
+ case 0x2591:
+ vga_to_curses[i] = *WACS_BOARD;
+ break;
+ case 0x2592:
+ vga_to_curses[i] = *WACS_CKBOARD;
+ break;
+ case 0x2502:
+ vga_to_curses[i] = *WACS_VLINE;
+ break;
+ case 0x2524:
+ vga_to_curses[i] = *WACS_RTEE;
+ break;
+ case 0x2510:
+ vga_to_curses[i] = *WACS_URCORNER;
+ break;
+ case 0x2514:
+ vga_to_curses[i] = *WACS_LLCORNER;
+ break;
+ case 0x2534:
+ vga_to_curses[i] = *WACS_BTEE;
+ break;
+ case 0x252c:
+ vga_to_curses[i] = *WACS_TTEE;
+ break;
+ case 0x251c:
+ vga_to_curses[i] = *WACS_LTEE;
+ break;
+ case 0x2500:
+ vga_to_curses[i] = *WACS_HLINE;
+ break;
+ case 0x253c:
+ vga_to_curses[i] = *WACS_PLUS;
+ break;
+ case 0x256c:
+ vga_to_curses[i] = *WACS_LANTERN;
+ break;
+ case 0x256a:
+ vga_to_curses[i] = *WACS_NEQUAL;
+ break;
+ case 0x2518:
+ vga_to_curses[i] = *WACS_LRCORNER;
+ break;
+ case 0x250c:
+ vga_to_curses[i] = *WACS_ULCORNER;
+ break;
+ case 0x2588:
+ vga_to_curses[i] = *WACS_BLOCK;
+ break;
+ case 0x03c0:
+ vga_to_curses[i] = *WACS_PI;
+ break;
+ case 0x00b1:
+ vga_to_curses[i] = *WACS_PLMINUS;
+ break;
+ case 0x2265:
+ vga_to_curses[i] = *WACS_GEQUAL;
+ break;
+ case 0x2264:
+ vga_to_curses[i] = *WACS_LEQUAL;
+ break;
+ case 0x00b0:
+ vga_to_curses[i] = *WACS_DEGREE;
+ break;
+ case 0x25a0:
+ vga_to_curses[i] = *WACS_BULLET;
+ break;
+ case 0x2666:
+ vga_to_curses[i] = *WACS_DIAMOND;
+ break;
+ case 0x2192:
+ vga_to_curses[i] = *WACS_RARROW;
+ break;
+ case 0x2190:
+ vga_to_curses[i] = *WACS_LARROW;
+ break;
+ case 0x2191:
+ vga_to_curses[i] = *WACS_UARROW;
+ break;
+ case 0x2193:
+ vga_to_curses[i] = *WACS_DARROW;
+ break;
+ case 0x23ba:
+ vga_to_curses[i] = *WACS_S1;
+ break;
+ case 0x23bb:
+ vga_to_curses[i] = *WACS_S3;
+ break;
+ case 0x23bc:
+ vga_to_curses[i] = *WACS_S7;
+ break;
+ case 0x23bd:
+ vga_to_curses[i] = *WACS_S9;
+ break;
+ }
+ }
+ }
+ iconv_close(ucs2_to_nativecharset);
+ iconv_close(nativecharset_to_ucs2);
+ iconv_close(font_conv);
+}
+
+static void curses_setup(void)
+{
+ int i, colour_default[8] = {
+ [QEMU_COLOR_BLACK] = COLOR_BLACK,
+ [QEMU_COLOR_BLUE] = COLOR_BLUE,
+ [QEMU_COLOR_GREEN] = COLOR_GREEN,
+ [QEMU_COLOR_CYAN] = COLOR_CYAN,
+ [QEMU_COLOR_RED] = COLOR_RED,
+ [QEMU_COLOR_MAGENTA] = COLOR_MAGENTA,
+ [QEMU_COLOR_YELLOW] = COLOR_YELLOW,
+ [QEMU_COLOR_WHITE] = COLOR_WHITE,
+ };
+
+ /* input as raw as possible, let everything be interpreted
+ * by the guest system */
+ initscr(); noecho(); intrflush(stdscr, FALSE);
+ nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
+ start_color(); raw(); scrollok(stdscr, FALSE);
+ set_escdelay(25);
+
+ /* Make color pair to match color format (3bits bg:3bits fg) */
+ for (i = 0; i < 64; i++) {
+ init_pair(i, colour_default[i & 7], colour_default[i >> 3]);
+ }
+ /* Set default color for more than 64 for safety. */
+ for (i = 64; i < COLOR_PAIRS; i++) {
+ init_pair(i, COLOR_WHITE, COLOR_BLACK);
+ }
+
+ font_setup();
+}
+
+static void curses_keyboard_setup(void)
+{
+#if defined(__APPLE__)
+ /* always use generic keymaps */
+ if (!keyboard_layout)
+ keyboard_layout = "en-us";
+#endif
+ if(keyboard_layout) {
+ kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
+ &error_fatal);
+ }
+}
+
+static const DisplayChangeListenerOps dcl_ops = {
+ .dpy_name = "curses",
+ .dpy_text_update = curses_update,
+ .dpy_text_resize = curses_resize,
+ .dpy_refresh = curses_refresh,
+ .dpy_text_cursor = curses_cursor_position,
+};
+
+static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
+{
+#ifndef _WIN32
+ if (!isatty(1)) {
+ fprintf(stderr, "We need a terminal output\n");
+ exit(1);
+ }
+#endif
+
+ setlocale(LC_CTYPE, "");
+ if (opts->u.curses.charset) {
+ font_charset = opts->u.curses.charset;
+ }
+ screen = g_new0(console_ch_t, 160 * 100);
+ vga_to_curses = g_new0(cchar_t, 256);
+ curses_setup();
+ curses_keyboard_setup();
+ atexit(curses_atexit);
+
+ curses_winch_init();
+
+ dcl = g_new0(DisplayChangeListener, 1);
+ dcl->ops = &dcl_ops;
+ register_displaychangelistener(dcl);
+
+ invalidate = 1;
+}
+
+static QemuDisplay qemu_display_curses = {
+ .type = DISPLAY_TYPE_CURSES,
+ .init = curses_display_init,
+};
+
+static void register_curses(void)
+{
+ qemu_display_register(&qemu_display_curses);
+}
+
+type_init(register_curses);
diff --git a/ui/curses_keys.h b/ui/curses_keys.h
new file mode 100644
index 000000000..71e04acdc
--- /dev/null
+++ b/ui/curses_keys.h
@@ -0,0 +1,537 @@
+/*
+ * Keycode and keysyms conversion tables for curses
+ *
+ * Copyright (c) 2005 Andrzej Zaborowski <balrog@zabor.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_CURSES_KEYS_H
+#define QEMU_CURSES_KEYS_H
+
+#include <curses.h>
+#include "keymaps.h"
+
+
+#define KEY_MASK SCANCODE_KEYMASK
+#define GREY_CODE 0xe0
+#define GREY SCANCODE_GREY
+#define SHIFT_CODE 0x2a
+#define SHIFT SCANCODE_SHIFT
+#define CNTRL_CODE 0x1d
+#define CNTRL SCANCODE_CTRL
+#define ALT_CODE 0x38
+#define ALT SCANCODE_ALT
+#define ALTGR SCANCODE_ALTGR
+
+#define KEYSYM_MASK 0x0ffffff
+#define KEYSYM_SHIFT (SCANCODE_SHIFT << 16)
+#define KEYSYM_CNTRL (SCANCODE_CTRL << 16)
+#define KEYSYM_ALT (SCANCODE_ALT << 16)
+#define KEYSYM_ALTGR (SCANCODE_ALTGR << 16)
+
+/* curses won't detect a Control + Alt + 1, so use Alt + 1 */
+#define QEMU_KEY_CONSOLE0 (2 | ALT) /* (curses2keycode['1'] | ALT) */
+
+#define CURSES_CHARS 0x100 /* Support latin1 only */
+#define CURSES_KEYS KEY_MAX /* KEY_MAX defined in <curses.h> */
+
+static const int _curses2keysym[CURSES_CHARS] = {
+ [0 ... (CURSES_CHARS - 1)] = -1,
+
+ [0x7f] = KEY_BACKSPACE,
+ ['\r'] = KEY_ENTER,
+ ['\n'] = KEY_ENTER,
+ [27] = 27,
+};
+
+static const int _curseskey2keysym[CURSES_KEYS] = {
+ [0 ... (CURSES_KEYS - 1)] = -1,
+
+ [KEY_BTAB] = '\t' | KEYSYM_SHIFT,
+ [KEY_SPREVIOUS] = KEY_PPAGE | KEYSYM_SHIFT,
+ [KEY_SNEXT] = KEY_NPAGE | KEYSYM_SHIFT,
+};
+
+static const int _curses2keycode[CURSES_CHARS] = {
+ [0 ... (CURSES_CHARS - 1)] = -1,
+
+ [0x01b] = 1, /* Escape */
+ ['1'] = 2,
+ ['2'] = 3,
+ ['3'] = 4,
+ ['4'] = 5,
+ ['5'] = 6,
+ ['6'] = 7,
+ ['7'] = 8,
+ ['8'] = 9,
+ ['9'] = 10,
+ ['0'] = 11,
+ ['-'] = 12,
+ ['='] = 13,
+ [0x07f] = 14, /* Backspace */
+
+ ['\t'] = 15, /* Tab */
+ ['q'] = 16,
+ ['w'] = 17,
+ ['e'] = 18,
+ ['r'] = 19,
+ ['t'] = 20,
+ ['y'] = 21,
+ ['u'] = 22,
+ ['i'] = 23,
+ ['o'] = 24,
+ ['p'] = 25,
+ ['['] = 26,
+ [']'] = 27,
+ ['\n'] = 28, /* Return */
+ ['\r'] = 28, /* Return */
+
+ ['a'] = 30,
+ ['s'] = 31,
+ ['d'] = 32,
+ ['f'] = 33,
+ ['g'] = 34,
+ ['h'] = 35,
+ ['j'] = 36,
+ ['k'] = 37,
+ ['l'] = 38,
+ [';'] = 39,
+ ['\''] = 40, /* Single quote */
+ ['`'] = 41,
+ ['\\'] = 43, /* Backslash */
+
+ ['z'] = 44,
+ ['x'] = 45,
+ ['c'] = 46,
+ ['v'] = 47,
+ ['b'] = 48,
+ ['n'] = 49,
+ ['m'] = 50,
+ [','] = 51,
+ ['.'] = 52,
+ ['/'] = 53,
+
+ [' '] = 57,
+
+ ['!'] = 2 | SHIFT,
+ ['@'] = 3 | SHIFT,
+ ['#'] = 4 | SHIFT,
+ ['$'] = 5 | SHIFT,
+ ['%'] = 6 | SHIFT,
+ ['^'] = 7 | SHIFT,
+ ['&'] = 8 | SHIFT,
+ ['*'] = 9 | SHIFT,
+ ['('] = 10 | SHIFT,
+ [')'] = 11 | SHIFT,
+ ['_'] = 12 | SHIFT,
+ ['+'] = 13 | SHIFT,
+
+ ['Q'] = 16 | SHIFT,
+ ['W'] = 17 | SHIFT,
+ ['E'] = 18 | SHIFT,
+ ['R'] = 19 | SHIFT,
+ ['T'] = 20 | SHIFT,
+ ['Y'] = 21 | SHIFT,
+ ['U'] = 22 | SHIFT,
+ ['I'] = 23 | SHIFT,
+ ['O'] = 24 | SHIFT,
+ ['P'] = 25 | SHIFT,
+ ['{'] = 26 | SHIFT,
+ ['}'] = 27 | SHIFT,
+
+ ['A'] = 30 | SHIFT,
+ ['S'] = 31 | SHIFT,
+ ['D'] = 32 | SHIFT,
+ ['F'] = 33 | SHIFT,
+ ['G'] = 34 | SHIFT,
+ ['H'] = 35 | SHIFT,
+ ['J'] = 36 | SHIFT,
+ ['K'] = 37 | SHIFT,
+ ['L'] = 38 | SHIFT,
+ [':'] = 39 | SHIFT,
+ ['"'] = 40 | SHIFT,
+ ['~'] = 41 | SHIFT,
+ ['|'] = 43 | SHIFT,
+
+ ['Z'] = 44 | SHIFT,
+ ['X'] = 45 | SHIFT,
+ ['C'] = 46 | SHIFT,
+ ['V'] = 47 | SHIFT,
+ ['B'] = 48 | SHIFT,
+ ['N'] = 49 | SHIFT,
+ ['M'] = 50 | SHIFT,
+ ['<'] = 51 | SHIFT,
+ ['>'] = 52 | SHIFT,
+ ['?'] = 53 | SHIFT,
+
+ ['Q' - '@'] = 16 | CNTRL, /* Control + q */
+ ['W' - '@'] = 17 | CNTRL, /* Control + w */
+ ['E' - '@'] = 18 | CNTRL, /* Control + e */
+ ['R' - '@'] = 19 | CNTRL, /* Control + r */
+ ['T' - '@'] = 20 | CNTRL, /* Control + t */
+ ['Y' - '@'] = 21 | CNTRL, /* Control + y */
+ ['U' - '@'] = 22 | CNTRL, /* Control + u */
+ /* Control + i collides with Tab */
+ ['O' - '@'] = 24 | CNTRL, /* Control + o */
+ ['P' - '@'] = 25 | CNTRL, /* Control + p */
+
+ ['A' - '@'] = 30 | CNTRL, /* Control + a */
+ ['S' - '@'] = 31 | CNTRL, /* Control + s */
+ ['D' - '@'] = 32 | CNTRL, /* Control + d */
+ ['F' - '@'] = 33 | CNTRL, /* Control + f */
+ ['G' - '@'] = 34 | CNTRL, /* Control + g */
+ ['H' - '@'] = 35 | CNTRL, /* Control + h */
+ /* Control + j collides with Return */
+ ['K' - '@'] = 37 | CNTRL, /* Control + k */
+ ['L' - '@'] = 38 | CNTRL, /* Control + l */
+
+ ['Z' - '@'] = 44 | CNTRL, /* Control + z */
+ ['X' - '@'] = 45 | CNTRL, /* Control + x */
+ ['C' - '@'] = 46 | CNTRL, /* Control + c */
+ ['V' - '@'] = 47 | CNTRL, /* Control + v */
+ ['B' - '@'] = 48 | CNTRL, /* Control + b */
+ ['N' - '@'] = 49 | CNTRL, /* Control + n */
+ /* Control + m collides with the keycode for Enter */
+
+};
+
+static const int _curseskey2keycode[CURSES_KEYS] = {
+ [0 ... (CURSES_KEYS - 1)] = -1,
+
+ [KEY_BACKSPACE] = 14, /* Backspace */
+
+ [KEY_ENTER] = 28, /* Return */
+
+ [KEY_F(1)] = 59, /* Function Key 1 */
+ [KEY_F(2)] = 60, /* Function Key 2 */
+ [KEY_F(3)] = 61, /* Function Key 3 */
+ [KEY_F(4)] = 62, /* Function Key 4 */
+ [KEY_F(5)] = 63, /* Function Key 5 */
+ [KEY_F(6)] = 64, /* Function Key 6 */
+ [KEY_F(7)] = 65, /* Function Key 7 */
+ [KEY_F(8)] = 66, /* Function Key 8 */
+ [KEY_F(9)] = 67, /* Function Key 9 */
+ [KEY_F(10)] = 68, /* Function Key 10 */
+ [KEY_F(11)] = 87, /* Function Key 11 */
+ [KEY_F(12)] = 88, /* Function Key 12 */
+
+ [KEY_HOME] = 71 | GREY, /* Home */
+ [KEY_UP] = 72 | GREY, /* Up Arrow */
+ [KEY_PPAGE] = 73 | GREY, /* Page Up */
+ [KEY_LEFT] = 75 | GREY, /* Left Arrow */
+ [KEY_RIGHT] = 77 | GREY, /* Right Arrow */
+ [KEY_END] = 79 | GREY, /* End */
+ [KEY_DOWN] = 80 | GREY, /* Down Arrow */
+ [KEY_NPAGE] = 81 | GREY, /* Page Down */
+ [KEY_IC] = 82 | GREY, /* Insert */
+ [KEY_DC] = 83 | GREY, /* Delete */
+
+ [KEY_SPREVIOUS] = 73 | GREY | SHIFT, /* Shift + Page Up */
+ [KEY_SNEXT] = 81 | GREY | SHIFT, /* Shift + Page Down */
+
+ [KEY_BTAB] = 15 | SHIFT, /* Shift + Tab */
+
+ [KEY_F(13)] = 59 | SHIFT, /* Shift + Function Key 1 */
+ [KEY_F(14)] = 60 | SHIFT, /* Shift + Function Key 2 */
+ [KEY_F(15)] = 61 | SHIFT, /* Shift + Function Key 3 */
+ [KEY_F(16)] = 62 | SHIFT, /* Shift + Function Key 4 */
+ [KEY_F(17)] = 63 | SHIFT, /* Shift + Function Key 5 */
+ [KEY_F(18)] = 64 | SHIFT, /* Shift + Function Key 6 */
+ [KEY_F(19)] = 65 | SHIFT, /* Shift + Function Key 7 */
+ [KEY_F(20)] = 66 | SHIFT, /* Shift + Function Key 8 */
+ [KEY_F(21)] = 67 | SHIFT, /* Shift + Function Key 9 */
+ [KEY_F(22)] = 68 | SHIFT, /* Shift + Function Key 10 */
+ [KEY_F(23)] = 69 | SHIFT, /* Shift + Function Key 11 */
+ [KEY_F(24)] = 70 | SHIFT, /* Shift + Function Key 12 */
+};
+
+static const int _curses2qemu[CURSES_CHARS] = {
+ [0 ... (CURSES_CHARS - 1)] = -1,
+
+ ['\n'] = '\n',
+ ['\r'] = '\n',
+
+ [0x07f] = QEMU_KEY_BACKSPACE,
+};
+
+static const int _curseskey2qemu[CURSES_KEYS] = {
+ [0 ... (CURSES_KEYS - 1)] = -1,
+
+ [KEY_DOWN] = QEMU_KEY_DOWN,
+ [KEY_UP] = QEMU_KEY_UP,
+ [KEY_LEFT] = QEMU_KEY_LEFT,
+ [KEY_RIGHT] = QEMU_KEY_RIGHT,
+ [KEY_HOME] = QEMU_KEY_HOME,
+ [KEY_BACKSPACE] = QEMU_KEY_BACKSPACE,
+
+ [KEY_DC] = QEMU_KEY_DELETE,
+ [KEY_NPAGE] = QEMU_KEY_PAGEDOWN,
+ [KEY_PPAGE] = QEMU_KEY_PAGEUP,
+ [KEY_ENTER] = '\n',
+ [KEY_END] = QEMU_KEY_END,
+
+};
+
+static const name2keysym_t name2keysym[] = {
+ /* Plain ASCII */
+ { "space", 0x020 },
+ { "exclam", 0x021 },
+ { "quotedbl", 0x022 },
+ { "numbersign", 0x023 },
+ { "dollar", 0x024 },
+ { "percent", 0x025 },
+ { "ampersand", 0x026 },
+ { "apostrophe", 0x027 },
+ { "parenleft", 0x028 },
+ { "parenright", 0x029 },
+ { "asterisk", 0x02a },
+ { "plus", 0x02b },
+ { "comma", 0x02c },
+ { "minus", 0x02d },
+ { "period", 0x02e },
+ { "slash", 0x02f },
+ { "0", 0x030 },
+ { "1", 0x031 },
+ { "2", 0x032 },
+ { "3", 0x033 },
+ { "4", 0x034 },
+ { "5", 0x035 },
+ { "6", 0x036 },
+ { "7", 0x037 },
+ { "8", 0x038 },
+ { "9", 0x039 },
+ { "colon", 0x03a },
+ { "semicolon", 0x03b },
+ { "less", 0x03c },
+ { "equal", 0x03d },
+ { "greater", 0x03e },
+ { "question", 0x03f },
+ { "at", 0x040 },
+ { "A", 0x041 },
+ { "B", 0x042 },
+ { "C", 0x043 },
+ { "D", 0x044 },
+ { "E", 0x045 },
+ { "F", 0x046 },
+ { "G", 0x047 },
+ { "H", 0x048 },
+ { "I", 0x049 },
+ { "J", 0x04a },
+ { "K", 0x04b },
+ { "L", 0x04c },
+ { "M", 0x04d },
+ { "N", 0x04e },
+ { "O", 0x04f },
+ { "P", 0x050 },
+ { "Q", 0x051 },
+ { "R", 0x052 },
+ { "S", 0x053 },
+ { "T", 0x054 },
+ { "U", 0x055 },
+ { "V", 0x056 },
+ { "W", 0x057 },
+ { "X", 0x058 },
+ { "Y", 0x059 },
+ { "Z", 0x05a },
+ { "bracketleft", 0x05b },
+ { "backslash", 0x05c },
+ { "bracketright", 0x05d },
+ { "asciicircum", 0x05e },
+ { "underscore", 0x05f },
+ { "grave", 0x060 },
+ { "a", 0x061 },
+ { "b", 0x062 },
+ { "c", 0x063 },
+ { "d", 0x064 },
+ { "e", 0x065 },
+ { "f", 0x066 },
+ { "g", 0x067 },
+ { "h", 0x068 },
+ { "i", 0x069 },
+ { "j", 0x06a },
+ { "k", 0x06b },
+ { "l", 0x06c },
+ { "m", 0x06d },
+ { "n", 0x06e },
+ { "o", 0x06f },
+ { "p", 0x070 },
+ { "q", 0x071 },
+ { "r", 0x072 },
+ { "s", 0x073 },
+ { "t", 0x074 },
+ { "u", 0x075 },
+ { "v", 0x076 },
+ { "w", 0x077 },
+ { "x", 0x078 },
+ { "y", 0x079 },
+ { "z", 0x07a },
+ { "braceleft", 0x07b },
+ { "bar", 0x07c },
+ { "braceright", 0x07d },
+ { "asciitilde", 0x07e },
+
+ /* Latin-1 extensions */
+ { "nobreakspace", 0x0a0 },
+ { "exclamdown", 0x0a1 },
+ { "cent", 0x0a2 },
+ { "sterling", 0x0a3 },
+ { "currency", 0x0a4 },
+ { "yen", 0x0a5 },
+ { "brokenbar", 0x0a6 },
+ { "section", 0x0a7 },
+ { "diaeresis", 0x0a8 },
+ { "copyright", 0x0a9 },
+ { "ordfeminine", 0x0aa },
+ { "guillemotleft", 0x0ab },
+ { "notsign", 0x0ac },
+ { "hyphen", 0x0ad },
+ { "registered", 0x0ae },
+ { "macron", 0x0af },
+ { "degree", 0x0b0 },
+ { "plusminus", 0x0b1 },
+ { "twosuperior", 0x0b2 },
+ { "threesuperior", 0x0b3 },
+ { "acute", 0x0b4 },
+ { "mu", 0x0b5 },
+ { "paragraph", 0x0b6 },
+ { "periodcentered", 0x0b7 },
+ { "cedilla", 0x0b8 },
+ { "onesuperior", 0x0b9 },
+ { "masculine", 0x0ba },
+ { "guillemotright", 0x0bb },
+ { "onequarter", 0x0bc },
+ { "onehalf", 0x0bd },
+ { "threequarters", 0x0be },
+ { "questiondown", 0x0bf },
+ { "Agrave", 0x0c0 },
+ { "Aacute", 0x0c1 },
+ { "Acircumflex", 0x0c2 },
+ { "Atilde", 0x0c3 },
+ { "Adiaeresis", 0x0c4 },
+ { "Aring", 0x0c5 },
+ { "AE", 0x0c6 },
+ { "Ccedilla", 0x0c7 },
+ { "Egrave", 0x0c8 },
+ { "Eacute", 0x0c9 },
+ { "Ecircumflex", 0x0ca },
+ { "Ediaeresis", 0x0cb },
+ { "Igrave", 0x0cc },
+ { "Iacute", 0x0cd },
+ { "Icircumflex", 0x0ce },
+ { "Idiaeresis", 0x0cf },
+ { "ETH", 0x0d0 },
+ { "Eth", 0x0d0 },
+ { "Ntilde", 0x0d1 },
+ { "Ograve", 0x0d2 },
+ { "Oacute", 0x0d3 },
+ { "Ocircumflex", 0x0d4 },
+ { "Otilde", 0x0d5 },
+ { "Odiaeresis", 0x0d6 },
+ { "multiply", 0x0d7 },
+ { "Ooblique", 0x0d8 },
+ { "Oslash", 0x0d8 },
+ { "Ugrave", 0x0d9 },
+ { "Uacute", 0x0da },
+ { "Ucircumflex", 0x0db },
+ { "Udiaeresis", 0x0dc },
+ { "Yacute", 0x0dd },
+ { "THORN", 0x0de },
+ { "Thorn", 0x0de },
+ { "ssharp", 0x0df },
+ { "agrave", 0x0e0 },
+ { "aacute", 0x0e1 },
+ { "acircumflex", 0x0e2 },
+ { "atilde", 0x0e3 },
+ { "adiaeresis", 0x0e4 },
+ { "aring", 0x0e5 },
+ { "ae", 0x0e6 },
+ { "ccedilla", 0x0e7 },
+ { "egrave", 0x0e8 },
+ { "eacute", 0x0e9 },
+ { "ecircumflex", 0x0ea },
+ { "ediaeresis", 0x0eb },
+ { "igrave", 0x0ec },
+ { "iacute", 0x0ed },
+ { "icircumflex", 0x0ee },
+ { "idiaeresis", 0x0ef },
+ { "eth", 0x0f0 },
+ { "ntilde", 0x0f1 },
+ { "ograve", 0x0f2 },
+ { "oacute", 0x0f3 },
+ { "ocircumflex", 0x0f4 },
+ { "otilde", 0x0f5 },
+ { "odiaeresis", 0x0f6 },
+ { "division", 0x0f7 },
+ { "oslash", 0x0f8 },
+ { "ooblique", 0x0f8 },
+ { "ugrave", 0x0f9 },
+ { "uacute", 0x0fa },
+ { "ucircumflex", 0x0fb },
+ { "udiaeresis", 0x0fc },
+ { "yacute", 0x0fd },
+ { "thorn", 0x0fe },
+ { "ydiaeresis", 0x0ff },
+
+ /* Special keys */
+ { "BackSpace", KEY_BACKSPACE },
+ { "Tab", '\t' },
+ { "Return", KEY_ENTER },
+ { "Right", KEY_RIGHT },
+ { "Left", KEY_LEFT },
+ { "Up", KEY_UP },
+ { "Down", KEY_DOWN },
+ { "Next", KEY_NPAGE },
+ { "Page_Down", KEY_NPAGE },
+ { "Prior", KEY_PPAGE },
+ { "Page_Up", KEY_PPAGE },
+ { "Insert", KEY_IC },
+ { "Delete", KEY_DC },
+ { "Home", KEY_HOME },
+ { "End", KEY_END },
+ { "F1", KEY_F(1) },
+ { "F2", KEY_F(2) },
+ { "F3", KEY_F(3) },
+ { "F4", KEY_F(4) },
+ { "F5", KEY_F(5) },
+ { "F6", KEY_F(6) },
+ { "F7", KEY_F(7) },
+ { "F8", KEY_F(8) },
+ { "F9", KEY_F(9) },
+ { "F10", KEY_F(10) },
+ { "F11", KEY_F(11) },
+ { "F12", KEY_F(12) },
+ { "F13", KEY_F(13) },
+ { "F14", KEY_F(14) },
+ { "F15", KEY_F(15) },
+ { "F16", KEY_F(16) },
+ { "F17", KEY_F(17) },
+ { "F18", KEY_F(18) },
+ { "F19", KEY_F(19) },
+ { "F20", KEY_F(20) },
+ { "F21", KEY_F(21) },
+ { "F22", KEY_F(22) },
+ { "F23", KEY_F(23) },
+ { "F24", KEY_F(24) },
+ { "Escape", 27 },
+
+ { NULL, 0 },
+};
+
+#endif
diff --git a/ui/cursor.c b/ui/cursor.c
new file mode 100644
index 000000000..1d62ddd4d
--- /dev/null
+++ b/ui/cursor.c
@@ -0,0 +1,243 @@
+#include "qemu/osdep.h"
+#include "ui/console.h"
+
+#include "cursor_hidden.xpm"
+#include "cursor_left_ptr.xpm"
+
+/* for creating built-in cursors */
+static QEMUCursor *cursor_parse_xpm(const char *xpm[])
+{
+ QEMUCursor *c;
+ uint32_t ctab[128];
+ unsigned int width, height, colors, chars;
+ unsigned int line = 0, i, r, g, b, x, y, pixel;
+ char name[16];
+ uint8_t idx;
+
+ /* parse header line: width, height, #colors, #chars */
+ if (sscanf(xpm[line], "%u %u %u %u",
+ &width, &height, &colors, &chars) != 4) {
+ fprintf(stderr, "%s: header parse error: \"%s\"\n",
+ __func__, xpm[line]);
+ return NULL;
+ }
+ if (chars != 1) {
+ fprintf(stderr, "%s: chars != 1 not supported\n", __func__);
+ return NULL;
+ }
+ line++;
+
+ /* parse color table */
+ for (i = 0; i < colors; i++, line++) {
+ if (sscanf(xpm[line], "%c c %15s", &idx, name) == 2) {
+ if (sscanf(name, "#%02x%02x%02x", &r, &g, &b) == 3) {
+ ctab[idx] = (0xff << 24) | (b << 16) | (g << 8) | r;
+ continue;
+ }
+ if (strcmp(name, "None") == 0) {
+ ctab[idx] = 0x00000000;
+ continue;
+ }
+ }
+ fprintf(stderr, "%s: color parse error: \"%s\"\n",
+ __func__, xpm[line]);
+ return NULL;
+ }
+
+ /* parse pixel data */
+ c = cursor_alloc(width, height);
+ for (pixel = 0, y = 0; y < height; y++, line++) {
+ for (x = 0; x < height; x++, pixel++) {
+ idx = xpm[line][x];
+ c->data[pixel] = ctab[idx];
+ }
+ }
+ return c;
+}
+
+/* nice for debugging */
+void cursor_print_ascii_art(QEMUCursor *c, const char *prefix)
+{
+ uint32_t *data = c->data;
+ int x,y;
+
+ for (y = 0; y < c->height; y++) {
+ fprintf(stderr, "%s: %2d: |", prefix, y);
+ for (x = 0; x < c->width; x++, data++) {
+ if ((*data & 0xff000000) != 0xff000000) {
+ fprintf(stderr, " "); /* transparent */
+ } else if ((*data & 0x00ffffff) == 0x00ffffff) {
+ fprintf(stderr, "."); /* white */
+ } else if ((*data & 0x00ffffff) == 0x00000000) {
+ fprintf(stderr, "X"); /* black */
+ } else {
+ fprintf(stderr, "o"); /* other */
+ }
+ }
+ fprintf(stderr, "|\n");
+ }
+}
+
+QEMUCursor *cursor_builtin_hidden(void)
+{
+ return cursor_parse_xpm(cursor_hidden_xpm);
+}
+
+QEMUCursor *cursor_builtin_left_ptr(void)
+{
+ return cursor_parse_xpm(cursor_left_ptr_xpm);
+}
+
+QEMUCursor *cursor_alloc(int width, int height)
+{
+ QEMUCursor *c;
+ int datasize = width * height * sizeof(uint32_t);
+
+ c = g_malloc0(sizeof(QEMUCursor) + datasize);
+ c->width = width;
+ c->height = height;
+ c->refcount = 1;
+ return c;
+}
+
+void cursor_get(QEMUCursor *c)
+{
+ c->refcount++;
+}
+
+void cursor_put(QEMUCursor *c)
+{
+ if (c == NULL)
+ return;
+ c->refcount--;
+ if (c->refcount)
+ return;
+ g_free(c);
+}
+
+int cursor_get_mono_bpl(QEMUCursor *c)
+{
+ return DIV_ROUND_UP(c->width, 8);
+}
+
+void cursor_set_mono(QEMUCursor *c,
+ uint32_t foreground, uint32_t background, uint8_t *image,
+ int transparent, uint8_t *mask)
+{
+ uint32_t *data = c->data;
+ uint8_t bit;
+ int x,y,bpl;
+ bool expand_bitmap_only = image == mask;
+ bool has_inverted_colors = false;
+ const uint32_t inverted = 0x80000000;
+
+ /*
+ * Converts a monochrome bitmap with XOR mask 'image' and AND mask 'mask':
+ * https://docs.microsoft.com/en-us/windows-hardware/drivers/display/drawing-monochrome-pointers
+ */
+ bpl = cursor_get_mono_bpl(c);
+ for (y = 0; y < c->height; y++) {
+ bit = 0x80;
+ for (x = 0; x < c->width; x++, data++) {
+ if (transparent && mask[x/8] & bit) {
+ if (!expand_bitmap_only && image[x / 8] & bit) {
+ *data = inverted;
+ has_inverted_colors = true;
+ } else {
+ *data = 0x00000000;
+ }
+ } else if (!transparent && !(mask[x/8] & bit)) {
+ *data = 0x00000000;
+ } else if (image[x/8] & bit) {
+ *data = 0xff000000 | foreground;
+ } else {
+ *data = 0xff000000 | background;
+ }
+ bit >>= 1;
+ if (bit == 0) {
+ bit = 0x80;
+ }
+ }
+ mask += bpl;
+ image += bpl;
+ }
+
+ /*
+ * If there are any pixels with inverted colors, create an outline (fill
+ * transparent neighbors with the background color) and use the foreground
+ * color as "inverted" color.
+ */
+ if (has_inverted_colors) {
+ data = c->data;
+ for (y = 0; y < c->height; y++) {
+ for (x = 0; x < c->width; x++, data++) {
+ if (*data == 0 /* transparent */ &&
+ ((x > 0 && data[-1] == inverted) ||
+ (x + 1 < c->width && data[1] == inverted) ||
+ (y > 0 && data[-c->width] == inverted) ||
+ (y + 1 < c->height && data[c->width] == inverted))) {
+ *data = 0xff000000 | background;
+ }
+ }
+ }
+ data = c->data;
+ for (x = 0; x < c->width * c->height; x++, data++) {
+ if (*data == inverted) {
+ *data = 0xff000000 | foreground;
+ }
+ }
+ }
+}
+
+void cursor_get_mono_image(QEMUCursor *c, int foreground, uint8_t *image)
+{
+ uint32_t *data = c->data;
+ uint8_t bit;
+ int x,y,bpl;
+
+ bpl = cursor_get_mono_bpl(c);
+ memset(image, 0, bpl * c->height);
+ for (y = 0; y < c->height; y++) {
+ bit = 0x80;
+ for (x = 0; x < c->width; x++, data++) {
+ if (((*data & 0xff000000) == 0xff000000) &&
+ ((*data & 0x00ffffff) == foreground)) {
+ image[x/8] |= bit;
+ }
+ bit >>= 1;
+ if (bit == 0) {
+ bit = 0x80;
+ }
+ }
+ image += bpl;
+ }
+}
+
+void cursor_get_mono_mask(QEMUCursor *c, int transparent, uint8_t *mask)
+{
+ uint32_t *data = c->data;
+ uint8_t bit;
+ int x,y,bpl;
+
+ bpl = cursor_get_mono_bpl(c);
+ memset(mask, 0, bpl * c->height);
+ for (y = 0; y < c->height; y++) {
+ bit = 0x80;
+ for (x = 0; x < c->width; x++, data++) {
+ if ((*data & 0xff000000) != 0xff000000) {
+ if (transparent != 0) {
+ mask[x/8] |= bit;
+ }
+ } else {
+ if (transparent == 0) {
+ mask[x/8] |= bit;
+ }
+ }
+ bit >>= 1;
+ if (bit == 0) {
+ bit = 0x80;
+ }
+ }
+ mask += bpl;
+ }
+}
diff --git a/ui/cursor_hidden.xpm b/ui/cursor_hidden.xpm
new file mode 100644
index 000000000..354e7a939
--- /dev/null
+++ b/ui/cursor_hidden.xpm
@@ -0,0 +1,37 @@
+/* XPM */
+static const char *cursor_hidden_xpm[] = {
+ "32 32 1 1",
+ " c None",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+};
diff --git a/ui/cursor_left_ptr.xpm b/ui/cursor_left_ptr.xpm
new file mode 100644
index 000000000..6c9ada902
--- /dev/null
+++ b/ui/cursor_left_ptr.xpm
@@ -0,0 +1,39 @@
+/* XPM */
+static const char *cursor_left_ptr_xpm[] = {
+ "32 32 3 1",
+ "X c #000000",
+ ". c #ffffff",
+ " c None",
+ "X ",
+ "XX ",
+ "X.X ",
+ "X..X ",
+ "X...X ",
+ "X....X ",
+ "X.....X ",
+ "X......X ",
+ "X.......X ",
+ "X........X ",
+ "X.....XXXXX ",
+ "X..X..X ",
+ "X.X X..X ",
+ "XX X..X ",
+ "X X..X ",
+ " X..X ",
+ " X..X ",
+ " X..X ",
+ " XX ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+ " ",
+};
diff --git a/ui/egl-context.c b/ui/egl-context.c
new file mode 100644
index 000000000..368ffa49d
--- /dev/null
+++ b/ui/egl-context.c
@@ -0,0 +1,37 @@
+#include "qemu/osdep.h"
+#include "ui/egl-context.h"
+
+QEMUGLContext qemu_egl_create_context(DisplayChangeListener *dcl,
+ QEMUGLParams *params)
+{
+ EGLContext ctx;
+ EGLint ctx_att_core[] = {
+ EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
+ EGL_CONTEXT_CLIENT_VERSION, params->major_ver,
+ EGL_CONTEXT_MINOR_VERSION_KHR, params->minor_ver,
+ EGL_NONE
+ };
+ EGLint ctx_att_gles[] = {
+ EGL_CONTEXT_CLIENT_VERSION, params->major_ver,
+ EGL_CONTEXT_MINOR_VERSION_KHR, params->minor_ver,
+ EGL_NONE
+ };
+ bool gles = (qemu_egl_mode == DISPLAYGL_MODE_ES);
+
+ ctx = eglCreateContext(qemu_egl_display, qemu_egl_config,
+ eglGetCurrentContext(),
+ gles ? ctx_att_gles : ctx_att_core);
+ return ctx;
+}
+
+void qemu_egl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+{
+ eglDestroyContext(qemu_egl_display, ctx);
+}
+
+int qemu_egl_make_context_current(DisplayChangeListener *dcl,
+ QEMUGLContext ctx)
+{
+ return eglMakeCurrent(qemu_egl_display,
+ EGL_NO_SURFACE, EGL_NO_SURFACE, ctx);
+}
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
new file mode 100644
index 000000000..a26a2520c
--- /dev/null
+++ b/ui/egl-headless.c
@@ -0,0 +1,217 @@
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "sysemu/sysemu.h"
+#include "ui/console.h"
+#include "ui/egl-helpers.h"
+#include "ui/egl-context.h"
+#include "ui/shader.h"
+
+typedef struct egl_dpy {
+ DisplayChangeListener dcl;
+ DisplaySurface *ds;
+ QemuGLShader *gls;
+ egl_fb guest_fb;
+ egl_fb cursor_fb;
+ egl_fb blit_fb;
+ bool y_0_top;
+ uint32_t pos_x;
+ uint32_t pos_y;
+} egl_dpy;
+
+/* ------------------------------------------------------------------ */
+
+static void egl_refresh(DisplayChangeListener *dcl)
+{
+ graphic_hw_update(dcl->con);
+}
+
+static void egl_gfx_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+}
+
+static void egl_gfx_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
+
+ edpy->ds = new_surface;
+}
+
+static QEMUGLContext egl_create_context(DisplayChangeListener *dcl,
+ QEMUGLParams *params)
+{
+ eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ qemu_egl_rn_ctx);
+ return qemu_egl_create_context(dcl, params);
+}
+
+static void egl_scanout_disable(DisplayChangeListener *dcl)
+{
+ egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
+
+ egl_fb_destroy(&edpy->guest_fb);
+ egl_fb_destroy(&edpy->blit_fb);
+}
+
+static void egl_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t backing_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
+
+ edpy->y_0_top = backing_y_0_top;
+
+ /* source framebuffer */
+ egl_fb_setup_for_tex(&edpy->guest_fb,
+ backing_width, backing_height, backing_id, false);
+
+ /* dest framebuffer */
+ if (edpy->blit_fb.width != backing_width ||
+ edpy->blit_fb.height != backing_height) {
+ egl_fb_destroy(&edpy->blit_fb);
+ egl_fb_setup_new_tex(&edpy->blit_fb, backing_width, backing_height);
+ }
+}
+
+static void egl_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+
+ egl_scanout_texture(dcl, dmabuf->texture,
+ false, dmabuf->width, dmabuf->height,
+ 0, 0, dmabuf->width, dmabuf->height);
+}
+
+static void egl_cursor_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf, bool have_hot,
+ uint32_t hot_x, uint32_t hot_y)
+{
+ egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
+
+ if (dmabuf) {
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+ egl_fb_setup_for_tex(&edpy->cursor_fb, dmabuf->width, dmabuf->height,
+ dmabuf->texture, false);
+ } else {
+ egl_fb_destroy(&edpy->cursor_fb);
+ }
+}
+
+static void egl_cursor_position(DisplayChangeListener *dcl,
+ uint32_t pos_x, uint32_t pos_y)
+{
+ egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
+
+ edpy->pos_x = pos_x;
+ edpy->pos_y = pos_y;
+}
+
+static void egl_release_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ egl_dmabuf_release_texture(dmabuf);
+}
+
+static void egl_scanout_flush(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ egl_dpy *edpy = container_of(dcl, egl_dpy, dcl);
+
+ if (!edpy->guest_fb.texture || !edpy->ds) {
+ return;
+ }
+ assert(surface_format(edpy->ds) == PIXMAN_x8r8g8b8);
+
+ if (edpy->cursor_fb.texture) {
+ /* have cursor -> render using textures */
+ egl_texture_blit(edpy->gls, &edpy->blit_fb, &edpy->guest_fb,
+ !edpy->y_0_top);
+ egl_texture_blend(edpy->gls, &edpy->blit_fb, &edpy->cursor_fb,
+ !edpy->y_0_top, edpy->pos_x, edpy->pos_y,
+ 1.0, 1.0);
+ } else {
+ /* no cursor -> use simple framebuffer blit */
+ egl_fb_blit(&edpy->blit_fb, &edpy->guest_fb, edpy->y_0_top);
+ }
+
+ egl_fb_read(edpy->ds, &edpy->blit_fb);
+ dpy_gfx_update(edpy->dcl.con, x, y, w, h);
+}
+
+static const DisplayChangeListenerOps egl_ops = {
+ .dpy_name = "egl-headless",
+ .dpy_refresh = egl_refresh,
+ .dpy_gfx_update = egl_gfx_update,
+ .dpy_gfx_switch = egl_gfx_switch,
+
+ .dpy_gl_ctx_create = egl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+
+ .dpy_gl_scanout_disable = egl_scanout_disable,
+ .dpy_gl_scanout_texture = egl_scanout_texture,
+ .dpy_gl_scanout_dmabuf = egl_scanout_dmabuf,
+ .dpy_gl_cursor_dmabuf = egl_cursor_dmabuf,
+ .dpy_gl_cursor_position = egl_cursor_position,
+ .dpy_gl_release_dmabuf = egl_release_dmabuf,
+ .dpy_gl_update = egl_scanout_flush,
+};
+
+static void early_egl_headless_init(DisplayOptions *opts)
+{
+ display_opengl = 1;
+}
+
+static void egl_headless_init(DisplayState *ds, DisplayOptions *opts)
+{
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_ON;
+ QemuConsole *con;
+ egl_dpy *edpy;
+ int idx;
+
+ if (egl_rendernode_init(opts->u.egl_headless.rendernode, mode) < 0) {
+ error_report("egl: render node init failed");
+ exit(1);
+ }
+
+ for (idx = 0;; idx++) {
+ con = qemu_console_lookup_by_index(idx);
+ if (!con || !qemu_console_is_graphic(con)) {
+ break;
+ }
+
+ edpy = g_new0(egl_dpy, 1);
+ edpy->dcl.con = con;
+ edpy->dcl.ops = &egl_ops;
+ edpy->gls = qemu_gl_init_shader();
+ register_displaychangelistener(&edpy->dcl);
+ }
+}
+
+static QemuDisplay qemu_display_egl = {
+ .type = DISPLAY_TYPE_EGL_HEADLESS,
+ .early_init = early_egl_headless_init,
+ .init = egl_headless_init,
+};
+
+static void register_egl(void)
+{
+ qemu_display_register(&qemu_display_egl);
+}
+
+type_init(register_egl);
+
+module_dep("ui-opengl");
diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
new file mode 100644
index 000000000..3a88245b6
--- /dev/null
+++ b/ui/egl-helpers.c
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2015-2016 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "qemu/osdep.h"
+#include "qemu/drm.h"
+#include "qemu/error-report.h"
+#include "ui/console.h"
+#include "ui/egl-helpers.h"
+
+EGLDisplay *qemu_egl_display;
+EGLConfig qemu_egl_config;
+DisplayGLMode qemu_egl_mode;
+
+/* ------------------------------------------------------------------ */
+
+static void egl_fb_delete_texture(egl_fb *fb)
+{
+ if (!fb->delete_texture) {
+ return;
+ }
+
+ glDeleteTextures(1, &fb->texture);
+ fb->delete_texture = false;
+}
+
+void egl_fb_destroy(egl_fb *fb)
+{
+ if (!fb->framebuffer) {
+ return;
+ }
+
+ egl_fb_delete_texture(fb);
+ glDeleteFramebuffers(1, &fb->framebuffer);
+
+ fb->width = 0;
+ fb->height = 0;
+ fb->texture = 0;
+ fb->framebuffer = 0;
+}
+
+void egl_fb_setup_default(egl_fb *fb, int width, int height)
+{
+ fb->width = width;
+ fb->height = height;
+ fb->framebuffer = 0; /* default framebuffer */
+}
+
+void egl_fb_setup_for_tex(egl_fb *fb, int width, int height,
+ GLuint texture, bool delete)
+{
+ egl_fb_delete_texture(fb);
+
+ fb->width = width;
+ fb->height = height;
+ fb->texture = texture;
+ fb->delete_texture = delete;
+ if (!fb->framebuffer) {
+ glGenFramebuffers(1, &fb->framebuffer);
+ }
+
+ glBindFramebuffer(GL_FRAMEBUFFER_EXT, fb->framebuffer);
+ glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
+ GL_TEXTURE_2D, fb->texture, 0);
+}
+
+void egl_fb_setup_new_tex(egl_fb *fb, int width, int height)
+{
+ GLuint texture;
+
+ glGenTextures(1, &texture);
+ glBindTexture(GL_TEXTURE_2D, texture);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height,
+ 0, GL_BGRA, GL_UNSIGNED_BYTE, 0);
+
+ egl_fb_setup_for_tex(fb, width, height, texture, true);
+}
+
+void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip)
+{
+ GLuint x1 = 0;
+ GLuint y1 = 0;
+ GLuint x2, y2;
+ GLuint w = src->width;
+ GLuint h = src->height;
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->framebuffer);
+ glViewport(0, 0, dst->width, dst->height);
+
+ if (src->dmabuf) {
+ x1 = src->dmabuf->x;
+ y1 = src->dmabuf->y;
+ w = src->dmabuf->scanout_width;
+ h = src->dmabuf->scanout_height;
+ }
+
+ w = (x1 + w) > src->width ? src->width - x1 : w;
+ h = (y1 + h) > src->height ? src->height - y1 : h;
+
+ y2 = flip ? y1 : h + y1;
+ y1 = flip ? h + y1 : y1;
+ x2 = x1 + w;
+
+ glBlitFramebuffer(x1, y1, x2, y2,
+ 0, 0, dst->width, dst->height,
+ GL_COLOR_BUFFER_BIT, GL_LINEAR);
+}
+
+void egl_fb_read(DisplaySurface *dst, egl_fb *src)
+{
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
+ glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
+ glReadPixels(0, 0, surface_width(dst), surface_height(dst),
+ GL_BGRA, GL_UNSIGNED_BYTE, surface_data(dst));
+}
+
+void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip)
+{
+ glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
+ glViewport(0, 0, dst->width, dst->height);
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, src->texture);
+ qemu_gl_run_texture_blit(gls, flip);
+}
+
+void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip,
+ int x, int y, double scale_x, double scale_y)
+{
+ glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer);
+ int w = scale_x * src->width;
+ int h = scale_y * src->height;
+ if (flip) {
+ glViewport(x, y, w, h);
+ } else {
+ glViewport(x, dst->height - h - y, w, h);
+ }
+ glEnable(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, src->texture);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ qemu_gl_run_texture_blit(gls, flip);
+ glDisable(GL_BLEND);
+}
+
+/* ---------------------------------------------------------------------- */
+
+#ifdef CONFIG_GBM
+
+int qemu_egl_rn_fd;
+struct gbm_device *qemu_egl_rn_gbm_dev;
+EGLContext qemu_egl_rn_ctx;
+
+int egl_rendernode_init(const char *rendernode, DisplayGLMode mode)
+{
+ qemu_egl_rn_fd = -1;
+ int rc;
+
+ qemu_egl_rn_fd = qemu_drm_rendernode_open(rendernode);
+ if (qemu_egl_rn_fd == -1) {
+ error_report("egl: no drm render node available");
+ goto err;
+ }
+
+ qemu_egl_rn_gbm_dev = gbm_create_device(qemu_egl_rn_fd);
+ if (!qemu_egl_rn_gbm_dev) {
+ error_report("egl: gbm_create_device failed");
+ goto err;
+ }
+
+ rc = qemu_egl_init_dpy_mesa((EGLNativeDisplayType)qemu_egl_rn_gbm_dev,
+ mode);
+ if (rc != 0) {
+ /* qemu_egl_init_dpy_mesa reports error */
+ goto err;
+ }
+
+ if (!epoxy_has_egl_extension(qemu_egl_display,
+ "EGL_KHR_surfaceless_context")) {
+ error_report("egl: EGL_KHR_surfaceless_context not supported");
+ goto err;
+ }
+ if (!epoxy_has_egl_extension(qemu_egl_display,
+ "EGL_MESA_image_dma_buf_export")) {
+ error_report("egl: EGL_MESA_image_dma_buf_export not supported");
+ goto err;
+ }
+
+ qemu_egl_rn_ctx = qemu_egl_init_ctx();
+ if (!qemu_egl_rn_ctx) {
+ error_report("egl: egl_init_ctx failed");
+ goto err;
+ }
+
+ return 0;
+
+err:
+ if (qemu_egl_rn_gbm_dev) {
+ gbm_device_destroy(qemu_egl_rn_gbm_dev);
+ }
+ if (qemu_egl_rn_fd != -1) {
+ close(qemu_egl_rn_fd);
+ }
+
+ return -1;
+}
+
+int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc,
+ EGLuint64KHR *modifier)
+{
+ EGLImageKHR image;
+ EGLint num_planes, fd;
+
+ image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(),
+ EGL_GL_TEXTURE_2D_KHR,
+ (EGLClientBuffer)(unsigned long)tex_id,
+ NULL);
+ if (!image) {
+ return -1;
+ }
+
+ eglExportDMABUFImageQueryMESA(qemu_egl_display, image, fourcc,
+ &num_planes, modifier);
+ if (num_planes != 1) {
+ eglDestroyImageKHR(qemu_egl_display, image);
+ return -1;
+ }
+ eglExportDMABUFImageMESA(qemu_egl_display, image, &fd, stride, NULL);
+ eglDestroyImageKHR(qemu_egl_display, image);
+
+ return fd;
+}
+
+void egl_dmabuf_import_texture(QemuDmaBuf *dmabuf)
+{
+ EGLImageKHR image = EGL_NO_IMAGE_KHR;
+ EGLint attrs[64];
+ int i = 0;
+
+ if (dmabuf->texture != 0) {
+ return;
+ }
+
+ attrs[i++] = EGL_WIDTH;
+ attrs[i++] = dmabuf->width;
+ attrs[i++] = EGL_HEIGHT;
+ attrs[i++] = dmabuf->height;
+ attrs[i++] = EGL_LINUX_DRM_FOURCC_EXT;
+ attrs[i++] = dmabuf->fourcc;
+
+ attrs[i++] = EGL_DMA_BUF_PLANE0_FD_EXT;
+ attrs[i++] = dmabuf->fd;
+ attrs[i++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
+ attrs[i++] = dmabuf->stride;
+ attrs[i++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
+ attrs[i++] = 0;
+#ifdef EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT
+ if (dmabuf->modifier) {
+ attrs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
+ attrs[i++] = (dmabuf->modifier >> 0) & 0xffffffff;
+ attrs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
+ attrs[i++] = (dmabuf->modifier >> 32) & 0xffffffff;
+ }
+#endif
+ attrs[i++] = EGL_NONE;
+
+ image = eglCreateImageKHR(qemu_egl_display,
+ EGL_NO_CONTEXT,
+ EGL_LINUX_DMA_BUF_EXT,
+ NULL, attrs);
+ if (image == EGL_NO_IMAGE_KHR) {
+ error_report("eglCreateImageKHR failed");
+ return;
+ }
+
+ glGenTextures(1, &dmabuf->texture);
+ glBindTexture(GL_TEXTURE_2D, dmabuf->texture);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+ glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
+ eglDestroyImageKHR(qemu_egl_display, image);
+}
+
+void egl_dmabuf_release_texture(QemuDmaBuf *dmabuf)
+{
+ if (dmabuf->texture == 0) {
+ return;
+ }
+
+ glDeleteTextures(1, &dmabuf->texture);
+ dmabuf->texture = 0;
+}
+
+void egl_dmabuf_create_sync(QemuDmaBuf *dmabuf)
+{
+ EGLSyncKHR sync;
+
+ if (epoxy_has_egl_extension(qemu_egl_display,
+ "EGL_KHR_fence_sync") &&
+ epoxy_has_egl_extension(qemu_egl_display,
+ "EGL_ANDROID_native_fence_sync")) {
+ sync = eglCreateSyncKHR(qemu_egl_display,
+ EGL_SYNC_NATIVE_FENCE_ANDROID, NULL);
+ if (sync != EGL_NO_SYNC_KHR) {
+ dmabuf->sync = sync;
+ }
+ }
+}
+
+void egl_dmabuf_create_fence(QemuDmaBuf *dmabuf)
+{
+ if (dmabuf->sync) {
+ dmabuf->fence_fd = eglDupNativeFenceFDANDROID(qemu_egl_display,
+ dmabuf->sync);
+ eglDestroySyncKHR(qemu_egl_display, dmabuf->sync);
+ dmabuf->sync = NULL;
+ }
+}
+
+#endif /* CONFIG_GBM */
+
+/* ---------------------------------------------------------------------- */
+
+EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win)
+{
+ EGLSurface esurface;
+ EGLBoolean b;
+
+ esurface = eglCreateWindowSurface(qemu_egl_display,
+ qemu_egl_config,
+ win, NULL);
+ if (esurface == EGL_NO_SURFACE) {
+ error_report("egl: eglCreateWindowSurface failed");
+ return NULL;
+ }
+
+ b = eglMakeCurrent(qemu_egl_display, esurface, esurface, ectx);
+ if (b == EGL_FALSE) {
+ error_report("egl: eglMakeCurrent failed");
+ return NULL;
+ }
+
+ return esurface;
+}
+
+/* ---------------------------------------------------------------------- */
+
+#if defined(CONFIG_X11) || defined(CONFIG_GBM)
+
+/*
+ * Taken from glamor_egl.h from the Xorg xserver, which is MIT licensed
+ *
+ * Create an EGLDisplay from a native display type. This is a little quirky
+ * for a few reasons.
+ *
+ * 1: GetPlatformDisplayEXT and GetPlatformDisplay are the API you want to
+ * use, but have different function signatures in the third argument; this
+ * happens not to matter for us, at the moment, but it means epoxy won't alias
+ * them together.
+ *
+ * 2: epoxy 1.3 and earlier don't understand EGL client extensions, which
+ * means you can't call "eglGetPlatformDisplayEXT" directly, as the resolver
+ * will crash.
+ *
+ * 3: You can't tell whether you have EGL 1.5 at this point, because
+ * eglQueryString(EGL_VERSION) is a property of the display, which we don't
+ * have yet. So you have to query for extensions no matter what. Fortunately
+ * epoxy_has_egl_extension _does_ let you query for client extensions, so
+ * we don't have to write our own extension string parsing.
+ *
+ * 4. There is no EGL_KHR_platform_base to complement the EXT one, thus one
+ * needs to know EGL 1.5 is supported in order to use the eglGetPlatformDisplay
+ * function pointer.
+ * We can workaround this (circular dependency) by probing for the EGL 1.5
+ * platform extensions (EGL_KHR_platform_gbm and friends) yet it doesn't seem
+ * like mesa will be able to advertise these (even though it can do EGL 1.5).
+ */
+static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native,
+ EGLenum platform)
+{
+ EGLDisplay dpy = EGL_NO_DISPLAY;
+
+ /* In practise any EGL 1.5 implementation would support the EXT extension */
+ if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) {
+ PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT =
+ (void *) eglGetProcAddress("eglGetPlatformDisplayEXT");
+ if (getPlatformDisplayEXT && platform != 0) {
+ dpy = getPlatformDisplayEXT(platform, native, NULL);
+ }
+ }
+
+ if (dpy == EGL_NO_DISPLAY) {
+ /* fallback */
+ dpy = eglGetDisplay(native);
+ }
+ return dpy;
+}
+
+static int qemu_egl_init_dpy(EGLNativeDisplayType dpy,
+ EGLenum platform,
+ DisplayGLMode mode)
+{
+ static const EGLint conf_att_core[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
+ EGL_RED_SIZE, 5,
+ EGL_GREEN_SIZE, 5,
+ EGL_BLUE_SIZE, 5,
+ EGL_ALPHA_SIZE, 0,
+ EGL_NONE,
+ };
+ static const EGLint conf_att_gles[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL_RED_SIZE, 5,
+ EGL_GREEN_SIZE, 5,
+ EGL_BLUE_SIZE, 5,
+ EGL_ALPHA_SIZE, 0,
+ EGL_NONE,
+ };
+ EGLint major, minor;
+ EGLBoolean b;
+ EGLint n;
+ bool gles = (mode == DISPLAYGL_MODE_ES);
+
+ qemu_egl_display = qemu_egl_get_display(dpy, platform);
+ if (qemu_egl_display == EGL_NO_DISPLAY) {
+ error_report("egl: eglGetDisplay failed");
+ return -1;
+ }
+
+ b = eglInitialize(qemu_egl_display, &major, &minor);
+ if (b == EGL_FALSE) {
+ error_report("egl: eglInitialize failed");
+ return -1;
+ }
+
+ b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API);
+ if (b == EGL_FALSE) {
+ error_report("egl: eglBindAPI failed (%s mode)",
+ gles ? "gles" : "core");
+ return -1;
+ }
+
+ b = eglChooseConfig(qemu_egl_display,
+ gles ? conf_att_gles : conf_att_core,
+ &qemu_egl_config, 1, &n);
+ if (b == EGL_FALSE || n != 1) {
+ error_report("egl: eglChooseConfig failed (%s mode)",
+ gles ? "gles" : "core");
+ return -1;
+ }
+
+ qemu_egl_mode = gles ? DISPLAYGL_MODE_ES : DISPLAYGL_MODE_CORE;
+ return 0;
+}
+
+int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode)
+{
+#ifdef EGL_KHR_platform_x11
+ return qemu_egl_init_dpy(dpy, EGL_PLATFORM_X11_KHR, mode);
+#else
+ return qemu_egl_init_dpy(dpy, 0, mode);
+#endif
+}
+
+int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode)
+{
+#ifdef EGL_MESA_platform_gbm
+ return qemu_egl_init_dpy(dpy, EGL_PLATFORM_GBM_MESA, mode);
+#else
+ return qemu_egl_init_dpy(dpy, 0, mode);
+#endif
+}
+
+#endif
+
+bool qemu_egl_has_dmabuf(void)
+{
+ if (qemu_egl_display == EGL_NO_DISPLAY) {
+ return false;
+ }
+
+ return epoxy_has_egl_extension(qemu_egl_display,
+ "EGL_EXT_image_dma_buf_import");
+}
+
+EGLContext qemu_egl_init_ctx(void)
+{
+ static const EGLint ctx_att_core[] = {
+ EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
+ EGL_NONE
+ };
+ static const EGLint ctx_att_gles[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+ bool gles = (qemu_egl_mode == DISPLAYGL_MODE_ES);
+ EGLContext ectx;
+ EGLBoolean b;
+
+ ectx = eglCreateContext(qemu_egl_display, qemu_egl_config, EGL_NO_CONTEXT,
+ gles ? ctx_att_gles : ctx_att_core);
+ if (ectx == EGL_NO_CONTEXT) {
+ error_report("egl: eglCreateContext failed");
+ return NULL;
+ }
+
+ b = eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx);
+ if (b == EGL_FALSE) {
+ error_report("egl: eglMakeCurrent failed");
+ return NULL;
+ }
+
+ return ectx;
+}
diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c
new file mode 100644
index 000000000..35b7a2c22
--- /dev/null
+++ b/ui/gtk-clipboard.c
@@ -0,0 +1,192 @@
+/*
+ * GTK UI -- clipboard support
+ *
+ * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/main-loop.h"
+
+#include "ui/gtk.h"
+
+static QemuClipboardSelection gd_find_selection(GtkDisplayState *gd,
+ GtkClipboard *clipboard)
+{
+ QemuClipboardSelection s;
+
+ for (s = 0; s < QEMU_CLIPBOARD_SELECTION__COUNT; s++) {
+ if (gd->gtkcb[s] == clipboard) {
+ return s;
+ }
+ }
+ return QEMU_CLIPBOARD_SELECTION_CLIPBOARD;
+}
+
+static void gd_clipboard_get_data(GtkClipboard *clipboard,
+ GtkSelectionData *selection_data,
+ guint selection_info,
+ gpointer data)
+{
+ GtkDisplayState *gd = data;
+ QemuClipboardSelection s = gd_find_selection(gd, clipboard);
+ QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
+ g_autoptr(QemuClipboardInfo) info = NULL;
+
+ info = qemu_clipboard_info_ref(qemu_clipboard_info(s));
+
+ qemu_clipboard_request(info, type);
+ while (info == qemu_clipboard_info(s) &&
+ info->types[type].available &&
+ info->types[type].data == NULL) {
+ main_loop_wait(false);
+ }
+
+ if (info == qemu_clipboard_info(s) && gd->cbowner[s]) {
+ gtk_selection_data_set_text(selection_data,
+ info->types[type].data,
+ info->types[type].size);
+ } else {
+ /* clipboard owner changed while waiting for the data */
+ }
+}
+
+static void gd_clipboard_clear(GtkClipboard *clipboard,
+ gpointer data)
+{
+ GtkDisplayState *gd = data;
+ QemuClipboardSelection s = gd_find_selection(gd, clipboard);
+
+ gd->cbowner[s] = false;
+}
+
+static void gd_clipboard_notify(Notifier *notifier, void *data)
+{
+ GtkDisplayState *gd = container_of(notifier, GtkDisplayState, cbpeer.update);
+ QemuClipboardInfo *info = data;
+ QemuClipboardSelection s = info->selection;
+ bool self_update = info->owner == &gd->cbpeer;
+
+ if (info != qemu_clipboard_info(s)) {
+ gd->cbpending[s] = 0;
+ if (!self_update) {
+ GtkTargetList *list;
+ GtkTargetEntry *targets;
+ gint n_targets;
+
+ list = gtk_target_list_new(NULL, 0);
+ if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ gtk_target_list_add_text_targets(list, 0);
+ }
+ targets = gtk_target_table_new_from_list(list, &n_targets);
+
+ gtk_clipboard_clear(gd->gtkcb[s]);
+ gd->cbowner[s] = true;
+ gtk_clipboard_set_with_data(gd->gtkcb[s],
+ targets, n_targets,
+ gd_clipboard_get_data,
+ gd_clipboard_clear,
+ gd);
+
+ gtk_target_table_free(targets, n_targets);
+ gtk_target_list_unref(list);
+ }
+ return;
+ }
+
+ if (self_update) {
+ return;
+ }
+
+ /*
+ * Clipboard got updated, with data probably. No action here, we
+ * are waiting for updates in gd_clipboard_get_data().
+ */
+}
+
+static void gd_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ GtkDisplayState *gd = container_of(info->owner, GtkDisplayState, cbpeer);
+ char *text;
+
+ switch (type) {
+ case QEMU_CLIPBOARD_TYPE_TEXT:
+ text = gtk_clipboard_wait_for_text(gd->gtkcb[info->selection]);
+ if (text) {
+ qemu_clipboard_set_data(&gd->cbpeer, info, type,
+ strlen(text), text, true);
+ g_free(text);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void gd_owner_change(GtkClipboard *clipboard,
+ GdkEvent *event,
+ gpointer data)
+{
+ GtkDisplayState *gd = data;
+ QemuClipboardSelection s = gd_find_selection(gd, clipboard);
+ QemuClipboardInfo *info;
+
+ if (gd->cbowner[s]) {
+ /* ignore notifications about our own grabs */
+ return;
+ }
+
+
+ switch (event->owner_change.reason) {
+ case GDK_OWNER_CHANGE_NEW_OWNER:
+ info = qemu_clipboard_info_new(&gd->cbpeer, s);
+ if (gtk_clipboard_wait_is_text_available(clipboard)) {
+ info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+ }
+
+ qemu_clipboard_update(info);
+ qemu_clipboard_info_unref(info);
+ break;
+ default:
+ qemu_clipboard_peer_release(&gd->cbpeer, s);
+ gd->cbowner[s] = false;
+ break;
+ }
+}
+
+void gd_clipboard_init(GtkDisplayState *gd)
+{
+ gd->cbpeer.name = "gtk";
+ gd->cbpeer.update.notify = gd_clipboard_notify;
+ gd->cbpeer.request = gd_clipboard_request;
+ qemu_clipboard_peer_register(&gd->cbpeer);
+
+ gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD] =
+ gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+ gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY] =
+ gtk_clipboard_get(GDK_SELECTION_PRIMARY);
+ gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY] =
+ gtk_clipboard_get(GDK_SELECTION_SECONDARY);
+
+ g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_CLIPBOARD],
+ "owner-change", G_CALLBACK(gd_owner_change), gd);
+ g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_PRIMARY],
+ "owner-change", G_CALLBACK(gd_owner_change), gd);
+ g_signal_connect(gd->gtkcb[QEMU_CLIPBOARD_SELECTION_SECONDARY],
+ "owner-change", G_CALLBACK(gd_owner_change), gd);
+}
diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c
new file mode 100644
index 000000000..45cb67712
--- /dev/null
+++ b/ui/gtk-egl.c
@@ -0,0 +1,372 @@
+/*
+ * GTK UI -- egl opengl code.
+ *
+ * Note that gtk 3.16+ (released 2015-03-23) has a GtkGLArea widget,
+ * which is GtkDrawingArea like widget with opengl rendering support.
+ *
+ * This code handles opengl support on older gtk versions, using egl
+ * to get a opengl context for the X11 window.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+
+#include "trace.h"
+
+#include "ui/console.h"
+#include "ui/gtk.h"
+#include "ui/egl-helpers.h"
+#include "ui/shader.h"
+
+#include "sysemu/sysemu.h"
+
+static void gtk_egl_set_scanout_mode(VirtualConsole *vc, bool scanout)
+{
+ if (vc->gfx.scanout_mode == scanout) {
+ return;
+ }
+
+ vc->gfx.scanout_mode = scanout;
+ if (!vc->gfx.scanout_mode) {
+ egl_fb_destroy(&vc->gfx.guest_fb);
+ if (vc->gfx.surface) {
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+ }
+}
+
+/** DisplayState Callbacks (opengl version) **/
+
+void gd_egl_init(VirtualConsole *vc)
+{
+ GdkWindow *gdk_window = gtk_widget_get_window(vc->gfx.drawing_area);
+ if (!gdk_window) {
+ return;
+ }
+
+ Window x11_window = gdk_x11_window_get_xid(gdk_window);
+ if (!x11_window) {
+ return;
+ }
+
+ vc->gfx.ectx = qemu_egl_init_ctx();
+ vc->gfx.esurface = qemu_egl_init_surface_x11
+ (vc->gfx.ectx, (EGLNativeWindowType)x11_window);
+
+ assert(vc->gfx.esurface);
+}
+
+void gd_egl_draw(VirtualConsole *vc)
+{
+ GdkWindow *window;
+#ifdef CONFIG_GBM
+ QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
+#endif
+ int ww, wh;
+
+ if (!vc->gfx.gls) {
+ return;
+ }
+
+ window = gtk_widget_get_window(vc->gfx.drawing_area);
+ ww = gdk_window_get_width(window);
+ wh = gdk_window_get_height(window);
+
+ if (vc->gfx.scanout_mode) {
+#ifdef CONFIG_GBM
+ if (dmabuf) {
+ if (!dmabuf->draw_submitted) {
+ return;
+ } else {
+ dmabuf->draw_submitted = false;
+ }
+ }
+#endif
+ gd_egl_scanout_flush(&vc->gfx.dcl, 0, 0, vc->gfx.w, vc->gfx.h);
+
+ vc->gfx.scale_x = (double)ww / vc->gfx.w;
+ vc->gfx.scale_y = (double)wh / vc->gfx.h;
+
+ glFlush();
+#ifdef CONFIG_GBM
+ if (dmabuf) {
+ egl_dmabuf_create_fence(dmabuf);
+ if (dmabuf->fence_fd > 0) {
+ qemu_set_fd_handler(dmabuf->fence_fd, gd_hw_gl_flushed, NULL, vc);
+ return;
+ }
+ graphic_hw_gl_block(vc->gfx.dcl.con, false);
+ }
+#endif
+ } else {
+ if (!vc->gfx.ds) {
+ return;
+ }
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+
+ surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
+ surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
+
+ eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
+
+ vc->gfx.scale_x = (double)ww / surface_width(vc->gfx.ds);
+ vc->gfx.scale_y = (double)wh / surface_height(vc->gfx.ds);
+
+ glFlush();
+ }
+
+ graphic_hw_gl_flushed(vc->gfx.dcl.con);
+}
+
+void gd_egl_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ if (!vc->gfx.gls || !vc->gfx.ds) {
+ return;
+ }
+
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+ surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h);
+ vc->gfx.glupdates++;
+}
+
+void gd_egl_refresh(DisplayChangeListener *dcl)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ vc->gfx.dcl.update_interval = gd_monitor_update_interval(
+ vc->window ? vc->window : vc->gfx.drawing_area);
+
+ if (!vc->gfx.esurface) {
+ gd_egl_init(vc);
+ if (!vc->gfx.esurface) {
+ return;
+ }
+ vc->gfx.gls = qemu_gl_init_shader();
+ if (vc->gfx.ds) {
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+#ifdef CONFIG_GBM
+ if (vc->gfx.guest_fb.dmabuf) {
+ egl_dmabuf_release_texture(vc->gfx.guest_fb.dmabuf);
+ gd_egl_scanout_dmabuf(dcl, vc->gfx.guest_fb.dmabuf);
+ }
+#endif
+ }
+
+ graphic_hw_update(dcl->con);
+
+ if (vc->gfx.glupdates) {
+ vc->gfx.glupdates = 0;
+ gtk_egl_set_scanout_mode(vc, false);
+ gd_egl_draw(vc);
+ }
+}
+
+void gd_egl_switch(DisplayChangeListener *dcl,
+ DisplaySurface *surface)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ bool resized = true;
+
+ trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
+
+ if (vc->gfx.ds &&
+ surface_width(vc->gfx.ds) == surface_width(surface) &&
+ surface_height(vc->gfx.ds) == surface_height(surface)) {
+ resized = false;
+ }
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ vc->gfx.ds = surface;
+ if (vc->gfx.gls) {
+ surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+
+ if (resized) {
+ gd_update_windowsize(vc);
+ }
+}
+
+QEMUGLContext gd_egl_create_context(DisplayChangeListener *dcl,
+ QEMUGLParams *params)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+ return qemu_egl_create_context(dcl, params);
+}
+
+void gd_egl_scanout_disable(DisplayChangeListener *dcl)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ vc->gfx.w = 0;
+ vc->gfx.h = 0;
+ gtk_egl_set_scanout_mode(vc, false);
+}
+
+void gd_egl_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t backing_id, bool backing_y_0_top,
+ uint32_t backing_width, uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ vc->gfx.x = x;
+ vc->gfx.y = y;
+ vc->gfx.w = w;
+ vc->gfx.h = h;
+ vc->gfx.y0_top = backing_y_0_top;
+
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+
+ gtk_egl_set_scanout_mode(vc, true);
+ egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height,
+ backing_id, false);
+}
+
+void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+#ifdef CONFIG_GBM
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+
+ gd_egl_scanout_texture(dcl, dmabuf->texture,
+ false, dmabuf->width, dmabuf->height,
+ 0, 0, dmabuf->width, dmabuf->height);
+
+ if (dmabuf->allow_fences) {
+ vc->gfx.guest_fb.dmabuf = dmabuf;
+ }
+#endif
+}
+
+void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf, bool have_hot,
+ uint32_t hot_x, uint32_t hot_y)
+{
+#ifdef CONFIG_GBM
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ if (dmabuf) {
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+ egl_fb_setup_for_tex(&vc->gfx.cursor_fb, dmabuf->width, dmabuf->height,
+ dmabuf->texture, false);
+ } else {
+ egl_fb_destroy(&vc->gfx.cursor_fb);
+ }
+#endif
+}
+
+void gd_egl_cursor_position(DisplayChangeListener *dcl,
+ uint32_t pos_x, uint32_t pos_y)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ vc->gfx.cursor_x = pos_x * vc->gfx.scale_x;
+ vc->gfx.cursor_y = pos_y * vc->gfx.scale_y;
+}
+
+void gd_egl_scanout_flush(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkWindow *window;
+ int ww, wh;
+
+ if (!vc->gfx.scanout_mode) {
+ return;
+ }
+ if (!vc->gfx.guest_fb.framebuffer) {
+ return;
+ }
+
+ eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, vc->gfx.ectx);
+
+ window = gtk_widget_get_window(vc->gfx.drawing_area);
+ ww = gdk_window_get_width(window);
+ wh = gdk_window_get_height(window);
+ egl_fb_setup_default(&vc->gfx.win_fb, ww, wh);
+ if (vc->gfx.cursor_fb.texture) {
+ egl_texture_blit(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.guest_fb,
+ vc->gfx.y0_top);
+ egl_texture_blend(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.cursor_fb,
+ vc->gfx.y0_top,
+ vc->gfx.cursor_x, vc->gfx.cursor_y,
+ vc->gfx.scale_x, vc->gfx.scale_y);
+ } else {
+ egl_fb_blit(&vc->gfx.win_fb, &vc->gfx.guest_fb, !vc->gfx.y0_top);
+ }
+
+#ifdef CONFIG_GBM
+ if (vc->gfx.guest_fb.dmabuf) {
+ egl_dmabuf_create_sync(vc->gfx.guest_fb.dmabuf);
+ }
+#endif
+
+ eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
+}
+
+void gd_egl_flush(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GtkWidget *area = vc->gfx.drawing_area;
+
+ if (vc->gfx.guest_fb.dmabuf) {
+ graphic_hw_gl_block(vc->gfx.dcl.con, true);
+ vc->gfx.guest_fb.dmabuf->draw_submitted = true;
+ gtk_widget_queue_draw_area(area, x, y, w, h);
+ return;
+ }
+
+ gd_egl_scanout_flush(&vc->gfx.dcl, x, y, w, h);
+}
+
+void gtk_egl_init(DisplayGLMode mode)
+{
+ GdkDisplay *gdk_display = gdk_display_get_default();
+ Display *x11_display = gdk_x11_display_get_xdisplay(gdk_display);
+
+ if (qemu_egl_init_dpy_x11(x11_display, mode) < 0) {
+ return;
+ }
+
+ display_opengl = 1;
+}
+
+int gd_egl_make_current(DisplayChangeListener *dcl,
+ QEMUGLContext ctx)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface,
+ vc->gfx.esurface, ctx);
+}
diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c
new file mode 100644
index 000000000..01e4e74ee
--- /dev/null
+++ b/ui/gtk-gl-area.c
@@ -0,0 +1,288 @@
+/*
+ * GTK UI -- glarea opengl code.
+ *
+ * Requires 3.16+ (GtkGLArea widget).
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+
+#include "trace.h"
+
+#include "ui/console.h"
+#include "ui/gtk.h"
+#include "ui/egl-helpers.h"
+
+#include "sysemu/sysemu.h"
+
+static void gtk_gl_area_set_scanout_mode(VirtualConsole *vc, bool scanout)
+{
+ if (vc->gfx.scanout_mode == scanout) {
+ return;
+ }
+
+ vc->gfx.scanout_mode = scanout;
+ if (!vc->gfx.scanout_mode) {
+ egl_fb_destroy(&vc->gfx.guest_fb);
+ if (vc->gfx.surface) {
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+ }
+}
+
+/** DisplayState Callbacks (opengl version) **/
+
+void gd_gl_area_draw(VirtualConsole *vc)
+{
+#ifdef CONFIG_GBM
+ QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
+#endif
+ int ww, wh, ws, y1, y2;
+
+ if (!vc->gfx.gls) {
+ return;
+ }
+
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ ws = gdk_window_get_scale_factor(gtk_widget_get_window(vc->gfx.drawing_area));
+ ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area) * ws;
+ wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area) * ws;
+
+ if (vc->gfx.scanout_mode) {
+ if (!vc->gfx.guest_fb.framebuffer) {
+ return;
+ }
+
+#ifdef CONFIG_GBM
+ if (dmabuf) {
+ if (!dmabuf->draw_submitted) {
+ return;
+ } else {
+ dmabuf->draw_submitted = false;
+ }
+ }
+#endif
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, vc->gfx.guest_fb.framebuffer);
+ /* GtkGLArea sets GL_DRAW_FRAMEBUFFER for us */
+
+ glViewport(0, 0, ww, wh);
+ y1 = vc->gfx.y0_top ? 0 : vc->gfx.h;
+ y2 = vc->gfx.y0_top ? vc->gfx.h : 0;
+ glBlitFramebuffer(0, y1, vc->gfx.w, y2,
+ 0, 0, ww, wh,
+ GL_COLOR_BUFFER_BIT, GL_NEAREST);
+#ifdef CONFIG_GBM
+ if (dmabuf) {
+ egl_dmabuf_create_sync(dmabuf);
+ }
+#endif
+ glFlush();
+#ifdef CONFIG_GBM
+ if (dmabuf) {
+ egl_dmabuf_create_fence(dmabuf);
+ if (dmabuf->fence_fd > 0) {
+ qemu_set_fd_handler(dmabuf->fence_fd, gd_hw_gl_flushed, NULL, vc);
+ return;
+ }
+ graphic_hw_gl_block(vc->gfx.dcl.con, false);
+ }
+#endif
+ } else {
+ if (!vc->gfx.ds) {
+ return;
+ }
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+
+ surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh);
+ surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+
+ graphic_hw_gl_flushed(vc->gfx.dcl.con);
+}
+
+void gd_gl_area_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ if (!vc->gfx.gls || !vc->gfx.ds) {
+ return;
+ }
+
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ surface_gl_update_texture(vc->gfx.gls, vc->gfx.ds, x, y, w, h);
+ vc->gfx.glupdates++;
+}
+
+void gd_gl_area_refresh(DisplayChangeListener *dcl)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ vc->gfx.dcl.update_interval = gd_monitor_update_interval(
+ vc->window ? vc->window : vc->gfx.drawing_area);
+
+ if (!vc->gfx.gls) {
+ if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
+ return;
+ }
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ vc->gfx.gls = qemu_gl_init_shader();
+ if (vc->gfx.ds) {
+ surface_gl_create_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+ }
+
+ graphic_hw_update(dcl->con);
+
+ if (vc->gfx.glupdates) {
+ vc->gfx.glupdates = 0;
+ gtk_gl_area_set_scanout_mode(vc, false);
+ gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
+ }
+}
+
+void gd_gl_area_switch(DisplayChangeListener *dcl,
+ DisplaySurface *surface)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ bool resized = true;
+
+ trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
+
+ if (vc->gfx.ds &&
+ surface_width(vc->gfx.ds) == surface_width(surface) &&
+ surface_height(vc->gfx.ds) == surface_height(surface)) {
+ resized = false;
+ }
+
+ if (vc->gfx.gls) {
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ surface_gl_destroy_texture(vc->gfx.gls, vc->gfx.ds);
+ surface_gl_create_texture(vc->gfx.gls, surface);
+ }
+ vc->gfx.ds = surface;
+
+ if (resized) {
+ gd_update_windowsize(vc);
+ }
+}
+
+QEMUGLContext gd_gl_area_create_context(DisplayChangeListener *dcl,
+ QEMUGLParams *params)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkWindow *window;
+ GdkGLContext *ctx;
+ GError *err = NULL;
+
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ window = gtk_widget_get_window(vc->gfx.drawing_area);
+ ctx = gdk_window_create_gl_context(window, &err);
+ if (err) {
+ g_printerr("Create gdk gl context failed: %s\n", err->message);
+ g_error_free(err);
+ return NULL;
+ }
+ gdk_gl_context_set_required_version(ctx,
+ params->major_ver,
+ params->minor_ver);
+ gdk_gl_context_realize(ctx, &err);
+ if (err) {
+ g_printerr("Realize gdk gl context failed: %s\n", err->message);
+ g_error_free(err);
+ g_clear_object(&ctx);
+ return NULL;
+ }
+ return ctx;
+}
+
+void gd_gl_area_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+{
+ /* FIXME */
+}
+
+void gd_gl_area_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t backing_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ vc->gfx.x = x;
+ vc->gfx.y = y;
+ vc->gfx.w = w;
+ vc->gfx.h = h;
+ vc->gfx.y0_top = backing_y_0_top;
+
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+
+ if (backing_id == 0 || vc->gfx.w == 0 || vc->gfx.h == 0) {
+ gtk_gl_area_set_scanout_mode(vc, false);
+ return;
+ }
+
+ gtk_gl_area_set_scanout_mode(vc, true);
+ egl_fb_setup_for_tex(&vc->gfx.guest_fb, backing_width, backing_height,
+ backing_id, false);
+}
+
+void gd_gl_area_scanout_disable(DisplayChangeListener *dcl)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ gtk_gl_area_set_scanout_mode(vc, false);
+}
+
+void gd_gl_area_scanout_flush(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ if (vc->gfx.guest_fb.dmabuf) {
+ graphic_hw_gl_block(vc->gfx.dcl.con, true);
+ vc->gfx.guest_fb.dmabuf->draw_submitted = true;
+ }
+ gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
+}
+
+void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+#ifdef CONFIG_GBM
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+
+ gd_gl_area_scanout_texture(dcl, dmabuf->texture,
+ false, dmabuf->width, dmabuf->height,
+ 0, 0, dmabuf->width, dmabuf->height);
+
+ if (dmabuf->allow_fences) {
+ vc->gfx.guest_fb.dmabuf = dmabuf;
+ }
+#endif
+}
+
+void gtk_gl_area_init(void)
+{
+ display_opengl = 1;
+}
+
+int gd_gl_area_make_current(DisplayChangeListener *dcl,
+ QEMUGLContext ctx)
+{
+ gdk_gl_context_make_current(ctx);
+ return 0;
+}
diff --git a/ui/gtk.c b/ui/gtk.c
new file mode 100644
index 000000000..428f02f2d
--- /dev/null
+++ b/ui/gtk.c
@@ -0,0 +1,2397 @@
+/*
+ * GTK UI
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Portions from gtk-vnc (originally licensed under the LGPL v2+):
+ *
+ * GTK VNC Widget
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ */
+
+#define GETTEXT_PACKAGE "qemu"
+#define LOCALEDIR "po"
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-control.h"
+#include "qapi/qapi-commands-machine.h"
+#include "qapi/qapi-commands-misc.h"
+#include "qemu/cutils.h"
+#include "qemu/main-loop.h"
+
+#include "ui/console.h"
+#include "ui/gtk.h"
+#ifdef G_OS_WIN32
+#include <gdk/gdkwin32.h>
+#endif
+#include "ui/win32-kbd-hook.h"
+
+#include <glib/gi18n.h>
+#include <locale.h>
+#if defined(CONFIG_VTE)
+#include <vte/vte.h>
+#endif
+#include <math.h>
+
+#include "trace.h"
+#include "qemu/cutils.h"
+#include "ui/input.h"
+#include "sysemu/runstate.h"
+#include "sysemu/sysemu.h"
+#include "keymaps.h"
+#include "chardev/char.h"
+#include "qom/object.h"
+
+#define VC_WINDOW_X_MIN 320
+#define VC_WINDOW_Y_MIN 240
+#define VC_TERM_X_MIN 80
+#define VC_TERM_Y_MIN 25
+#define VC_SCALE_MIN 0.25
+#define VC_SCALE_STEP 0.25
+
+#ifdef GDK_WINDOWING_X11
+#include "x_keymap.h"
+
+/* Gtk2 compat */
+#ifndef GDK_IS_X11_DISPLAY
+#define GDK_IS_X11_DISPLAY(dpy) (dpy != NULL)
+#endif
+#endif
+
+
+#ifdef GDK_WINDOWING_WAYLAND
+/* Gtk2 compat */
+#ifndef GDK_IS_WAYLAND_DISPLAY
+#define GDK_IS_WAYLAND_DISPLAY(dpy) (dpy != NULL)
+#endif
+#endif
+
+
+#ifdef GDK_WINDOWING_WIN32
+/* Gtk2 compat */
+#ifndef GDK_IS_WIN32_DISPLAY
+#define GDK_IS_WIN32_DISPLAY(dpy) (dpy != NULL)
+#endif
+#endif
+
+
+#ifdef GDK_WINDOWING_BROADWAY
+/* Gtk2 compat */
+#ifndef GDK_IS_BROADWAY_DISPLAY
+#define GDK_IS_BROADWAY_DISPLAY(dpy) (dpy != NULL)
+#endif
+#endif
+
+
+#ifdef GDK_WINDOWING_QUARTZ
+/* Gtk2 compat */
+#ifndef GDK_IS_QUARTZ_DISPLAY
+#define GDK_IS_QUARTZ_DISPLAY(dpy) (dpy != NULL)
+#endif
+#endif
+
+
+#if !defined(CONFIG_VTE)
+# define VTE_CHECK_VERSION(a, b, c) 0
+#endif
+
+#define HOTKEY_MODIFIERS (GDK_CONTROL_MASK | GDK_MOD1_MASK)
+
+static const guint16 *keycode_map;
+static size_t keycode_maplen;
+
+struct VCChardev {
+ Chardev parent;
+ VirtualConsole *console;
+ bool echo;
+};
+typedef struct VCChardev VCChardev;
+
+#define TYPE_CHARDEV_VC "chardev-vc"
+DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
+ TYPE_CHARDEV_VC)
+
+bool gtk_use_gl_area;
+
+static void gd_grab_pointer(VirtualConsole *vc, const char *reason);
+static void gd_ungrab_pointer(GtkDisplayState *s);
+static void gd_grab_keyboard(VirtualConsole *vc, const char *reason);
+static void gd_ungrab_keyboard(GtkDisplayState *s);
+
+/** Utility Functions **/
+
+static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s)
+{
+ VirtualConsole *vc;
+ gint i;
+
+ for (i = 0; i < s->nb_vcs; i++) {
+ vc = &s->vc[i];
+ if (gtk_check_menu_item_get_active
+ (GTK_CHECK_MENU_ITEM(vc->menu_item))) {
+ return vc;
+ }
+ }
+ return NULL;
+}
+
+static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page)
+{
+ VirtualConsole *vc;
+ gint i, p;
+
+ for (i = 0; i < s->nb_vcs; i++) {
+ vc = &s->vc[i];
+ p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item);
+ if (p == page) {
+ return vc;
+ }
+ }
+ return NULL;
+}
+
+static VirtualConsole *gd_vc_find_current(GtkDisplayState *s)
+{
+ gint page;
+
+ page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook));
+ return gd_vc_find_by_page(s, page);
+}
+
+static bool gd_is_grab_active(GtkDisplayState *s)
+{
+ return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item));
+}
+
+static bool gd_grab_on_hover(GtkDisplayState *s)
+{
+ return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item));
+}
+
+static void gd_update_cursor(VirtualConsole *vc)
+{
+ GtkDisplayState *s = vc->s;
+ GdkWindow *window;
+
+ if (vc->type != GD_VC_GFX ||
+ !qemu_console_is_graphic(vc->gfx.dcl.con)) {
+ return;
+ }
+
+ if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
+ return;
+ }
+
+ window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area));
+ if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) {
+ gdk_window_set_cursor(window, s->null_cursor);
+ } else {
+ gdk_window_set_cursor(window, NULL);
+ }
+}
+
+static void gd_update_caption(GtkDisplayState *s)
+{
+ const char *status = "";
+ gchar *prefix;
+ gchar *title;
+ const char *grab = "";
+ bool is_paused = !runstate_is_running();
+ int i;
+
+ if (qemu_name) {
+ prefix = g_strdup_printf("QEMU (%s)", qemu_name);
+ } else {
+ prefix = g_strdup_printf("QEMU");
+ }
+
+ if (s->ptr_owner != NULL &&
+ s->ptr_owner->window == NULL) {
+ grab = _(" - Press Ctrl+Alt+G to release grab");
+ }
+
+ if (is_paused) {
+ status = _(" [Paused]");
+ }
+ s->external_pause_update = true;
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->pause_item),
+ is_paused);
+ s->external_pause_update = false;
+
+ title = g_strdup_printf("%s%s%s", prefix, status, grab);
+ gtk_window_set_title(GTK_WINDOW(s->window), title);
+ g_free(title);
+
+ for (i = 0; i < s->nb_vcs; i++) {
+ VirtualConsole *vc = &s->vc[i];
+
+ if (!vc->window) {
+ continue;
+ }
+ title = g_strdup_printf("%s: %s%s%s", prefix, vc->label,
+ vc == s->kbd_owner ? " +kbd" : "",
+ vc == s->ptr_owner ? " +ptr" : "");
+ gtk_window_set_title(GTK_WINDOW(vc->window), title);
+ g_free(title);
+ }
+
+ g_free(prefix);
+}
+
+static void gd_update_geometry_hints(VirtualConsole *vc)
+{
+ GtkDisplayState *s = vc->s;
+ GdkWindowHints mask = 0;
+ GdkGeometry geo = {};
+ GtkWidget *geo_widget = NULL;
+ GtkWindow *geo_window;
+
+ if (vc->type == GD_VC_GFX) {
+ if (!vc->gfx.ds) {
+ return;
+ }
+ if (s->free_scale) {
+ geo.min_width = surface_width(vc->gfx.ds) * VC_SCALE_MIN;
+ geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN;
+ mask |= GDK_HINT_MIN_SIZE;
+ } else {
+ geo.min_width = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
+ geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
+ mask |= GDK_HINT_MIN_SIZE;
+ }
+ geo_widget = vc->gfx.drawing_area;
+ gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height);
+
+#if defined(CONFIG_VTE)
+ } else if (vc->type == GD_VC_VTE) {
+ VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
+ GtkBorder padding = { 0 };
+
+#if VTE_CHECK_VERSION(0, 37, 0)
+ gtk_style_context_get_padding(
+ gtk_widget_get_style_context(vc->vte.terminal),
+ gtk_widget_get_state_flags(vc->vte.terminal),
+ &padding);
+#else
+ {
+ GtkBorder *ib = NULL;
+ gtk_widget_style_get(vc->vte.terminal, "inner-border", &ib, NULL);
+ if (ib) {
+ padding = *ib;
+ gtk_border_free(ib);
+ }
+ }
+#endif
+
+ geo.width_inc = vte_terminal_get_char_width(term);
+ geo.height_inc = vte_terminal_get_char_height(term);
+ mask |= GDK_HINT_RESIZE_INC;
+ geo.base_width = geo.width_inc;
+ geo.base_height = geo.height_inc;
+ mask |= GDK_HINT_BASE_SIZE;
+ geo.min_width = geo.width_inc * VC_TERM_X_MIN;
+ geo.min_height = geo.height_inc * VC_TERM_Y_MIN;
+ mask |= GDK_HINT_MIN_SIZE;
+
+ geo.base_width += padding.left + padding.right;
+ geo.base_height += padding.top + padding.bottom;
+ geo.min_width += padding.left + padding.right;
+ geo.min_height += padding.top + padding.bottom;
+ geo_widget = vc->vte.terminal;
+#endif
+ }
+
+ geo_window = GTK_WINDOW(vc->window ? vc->window : s->window);
+ gtk_window_set_geometry_hints(geo_window, geo_widget, &geo, mask);
+}
+
+void gd_update_windowsize(VirtualConsole *vc)
+{
+ GtkDisplayState *s = vc->s;
+
+ gd_update_geometry_hints(vc);
+
+ if (vc->type == GD_VC_GFX && !s->full_screen && !s->free_scale) {
+ gtk_window_resize(GTK_WINDOW(vc->window ? vc->window : s->window),
+ VC_WINDOW_X_MIN, VC_WINDOW_Y_MIN);
+ }
+}
+
+static void gd_update_full_redraw(VirtualConsole *vc)
+{
+ GtkWidget *area = vc->gfx.drawing_area;
+ int ww, wh;
+ ww = gdk_window_get_width(gtk_widget_get_window(area));
+ wh = gdk_window_get_height(gtk_widget_get_window(area));
+#if defined(CONFIG_OPENGL)
+ if (vc->gfx.gls && gtk_use_gl_area) {
+ gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
+ return;
+ }
+#endif
+ gtk_widget_queue_draw_area(area, 0, 0, ww, wh);
+}
+
+static void gtk_release_modifiers(GtkDisplayState *s)
+{
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (vc->type != GD_VC_GFX ||
+ !qemu_console_is_graphic(vc->gfx.dcl.con)) {
+ return;
+ }
+ qkbd_state_lift_all_keys(vc->gfx.kbd);
+}
+
+static void gd_widget_reparent(GtkWidget *from, GtkWidget *to,
+ GtkWidget *widget)
+{
+ g_object_ref(G_OBJECT(widget));
+ gtk_container_remove(GTK_CONTAINER(from), widget);
+ gtk_container_add(GTK_CONTAINER(to), widget);
+ g_object_unref(G_OBJECT(widget));
+}
+
+static void *gd_win32_get_hwnd(VirtualConsole *vc)
+{
+#ifdef G_OS_WIN32
+ return gdk_win32_window_get_impl_hwnd(
+ gtk_widget_get_window(vc->window ? vc->window : vc->s->window));
+#else
+ return NULL;
+#endif
+}
+
+/** DisplayState Callbacks **/
+
+static void gd_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkWindow *win;
+ int x1, x2, y1, y2;
+ int mx, my;
+ int fbw, fbh;
+ int ww, wh;
+
+ trace_gd_update(vc->label, x, y, w, h);
+
+ if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
+ return;
+ }
+
+ if (vc->gfx.convert) {
+ pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
+ NULL, vc->gfx.convert,
+ x, y, 0, 0, x, y, w, h);
+ }
+
+ x1 = floor(x * vc->gfx.scale_x);
+ y1 = floor(y * vc->gfx.scale_y);
+
+ x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x);
+ y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y);
+
+ fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
+ fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
+
+ win = gtk_widget_get_window(vc->gfx.drawing_area);
+ if (!win) {
+ return;
+ }
+ ww = gdk_window_get_width(win);
+ wh = gdk_window_get_height(win);
+
+ mx = my = 0;
+ if (ww > fbw) {
+ mx = (ww - fbw) / 2;
+ }
+ if (wh > fbh) {
+ my = (wh - fbh) / 2;
+ }
+
+ gtk_widget_queue_draw_area(vc->gfx.drawing_area,
+ mx + x1, my + y1, (x2 - x1), (y2 - y1));
+}
+
+static void gd_refresh(DisplayChangeListener *dcl)
+{
+ graphic_hw_update(dcl->con);
+}
+
+static GdkDevice *gd_get_pointer(GdkDisplay *dpy)
+{
+ return gdk_seat_get_pointer(gdk_display_get_default_seat(dpy));
+}
+
+static void gd_mouse_set(DisplayChangeListener *dcl,
+ int x, int y, int visible)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkDisplay *dpy;
+ gint x_root, y_root;
+
+ if (qemu_input_is_absolute()) {
+ return;
+ }
+
+ dpy = gtk_widget_get_display(vc->gfx.drawing_area);
+ gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area),
+ x, y, &x_root, &y_root);
+ gdk_device_warp(gd_get_pointer(dpy),
+ gtk_widget_get_screen(vc->gfx.drawing_area),
+ x_root, y_root);
+ vc->s->last_x = x;
+ vc->s->last_y = y;
+}
+
+static void gd_cursor_define(DisplayChangeListener *dcl,
+ QEMUCursor *c)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkPixbuf *pixbuf;
+ GdkCursor *cursor;
+
+ if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
+ return;
+ }
+
+ pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data),
+ GDK_COLORSPACE_RGB, true, 8,
+ c->width, c->height, c->width * 4,
+ NULL, NULL);
+ cursor = gdk_cursor_new_from_pixbuf
+ (gtk_widget_get_display(vc->gfx.drawing_area),
+ pixbuf, c->hot_x, c->hot_y);
+ gdk_window_set_cursor(gtk_widget_get_window(vc->gfx.drawing_area), cursor);
+ g_object_unref(pixbuf);
+ g_object_unref(cursor);
+}
+
+static void gd_switch(DisplayChangeListener *dcl,
+ DisplaySurface *surface)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ bool resized = true;
+
+ trace_gd_switch(vc->label, surface_width(surface), surface_height(surface));
+
+ if (vc->gfx.surface) {
+ cairo_surface_destroy(vc->gfx.surface);
+ vc->gfx.surface = NULL;
+ }
+ if (vc->gfx.convert) {
+ pixman_image_unref(vc->gfx.convert);
+ vc->gfx.convert = NULL;
+ }
+
+ if (vc->gfx.ds &&
+ surface_width(vc->gfx.ds) == surface_width(surface) &&
+ surface_height(vc->gfx.ds) == surface_height(surface)) {
+ resized = false;
+ }
+ vc->gfx.ds = surface;
+
+ if (surface->format == PIXMAN_x8r8g8b8) {
+ /*
+ * PIXMAN_x8r8g8b8 == CAIRO_FORMAT_RGB24
+ *
+ * No need to convert, use surface directly. Should be the
+ * common case as this is qemu_default_pixelformat(32) too.
+ */
+ vc->gfx.surface = cairo_image_surface_create_for_data
+ (surface_data(surface),
+ CAIRO_FORMAT_RGB24,
+ surface_width(surface),
+ surface_height(surface),
+ surface_stride(surface));
+ } else {
+ /* Must convert surface, use pixman to do it. */
+ vc->gfx.convert = pixman_image_create_bits(PIXMAN_x8r8g8b8,
+ surface_width(surface),
+ surface_height(surface),
+ NULL, 0);
+ vc->gfx.surface = cairo_image_surface_create_for_data
+ ((void *)pixman_image_get_data(vc->gfx.convert),
+ CAIRO_FORMAT_RGB24,
+ pixman_image_get_width(vc->gfx.convert),
+ pixman_image_get_height(vc->gfx.convert),
+ pixman_image_get_stride(vc->gfx.convert));
+ pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image,
+ NULL, vc->gfx.convert,
+ 0, 0, 0, 0, 0, 0,
+ pixman_image_get_width(vc->gfx.convert),
+ pixman_image_get_height(vc->gfx.convert));
+ }
+
+ if (resized) {
+ gd_update_windowsize(vc);
+ } else {
+ gd_update_full_redraw(vc);
+ }
+}
+
+static const DisplayChangeListenerOps dcl_ops = {
+ .dpy_name = "gtk",
+ .dpy_gfx_update = gd_update,
+ .dpy_gfx_switch = gd_switch,
+ .dpy_gfx_check_format = qemu_pixman_check_format,
+ .dpy_refresh = gd_refresh,
+ .dpy_mouse_set = gd_mouse_set,
+ .dpy_cursor_define = gd_cursor_define,
+};
+
+
+#if defined(CONFIG_OPENGL)
+
+static bool gd_has_dmabuf(DisplayChangeListener *dcl)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ if (gtk_use_gl_area && !gtk_widget_get_realized(vc->gfx.drawing_area)) {
+ /* FIXME: Assume it will work, actual check done after realize */
+ /* fixing this would require delaying listener registration */
+ return true;
+ }
+
+ return vc->gfx.has_dmabuf;
+}
+
+static void gd_gl_release_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+#ifdef CONFIG_GBM
+ egl_dmabuf_release_texture(dmabuf);
+#endif
+}
+
+void gd_hw_gl_flushed(void *vcon)
+{
+ VirtualConsole *vc = vcon;
+ QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf;
+
+ qemu_set_fd_handler(dmabuf->fence_fd, NULL, NULL, NULL);
+ close(dmabuf->fence_fd);
+ dmabuf->fence_fd = -1;
+ graphic_hw_gl_block(vc->gfx.dcl.con, false);
+ graphic_hw_gl_flushed(vc->gfx.dcl.con);
+}
+
+/** DisplayState Callbacks (opengl version) **/
+
+static const DisplayChangeListenerOps dcl_gl_area_ops = {
+ .dpy_name = "gtk-egl",
+ .dpy_gfx_update = gd_gl_area_update,
+ .dpy_gfx_switch = gd_gl_area_switch,
+ .dpy_gfx_check_format = console_gl_check_format,
+ .dpy_refresh = gd_gl_area_refresh,
+ .dpy_mouse_set = gd_mouse_set,
+ .dpy_cursor_define = gd_cursor_define,
+
+ .dpy_gl_ctx_create = gd_gl_area_create_context,
+ .dpy_gl_ctx_destroy = gd_gl_area_destroy_context,
+ .dpy_gl_ctx_make_current = gd_gl_area_make_current,
+ .dpy_gl_scanout_texture = gd_gl_area_scanout_texture,
+ .dpy_gl_scanout_disable = gd_gl_area_scanout_disable,
+ .dpy_gl_update = gd_gl_area_scanout_flush,
+ .dpy_gl_scanout_dmabuf = gd_gl_area_scanout_dmabuf,
+ .dpy_gl_release_dmabuf = gd_gl_release_dmabuf,
+ .dpy_has_dmabuf = gd_has_dmabuf,
+};
+
+#ifdef CONFIG_X11
+
+static const DisplayChangeListenerOps dcl_egl_ops = {
+ .dpy_name = "gtk-egl",
+ .dpy_gfx_update = gd_egl_update,
+ .dpy_gfx_switch = gd_egl_switch,
+ .dpy_gfx_check_format = console_gl_check_format,
+ .dpy_refresh = gd_egl_refresh,
+ .dpy_mouse_set = gd_mouse_set,
+ .dpy_cursor_define = gd_cursor_define,
+
+ .dpy_gl_ctx_create = gd_egl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = gd_egl_make_current,
+ .dpy_gl_scanout_disable = gd_egl_scanout_disable,
+ .dpy_gl_scanout_texture = gd_egl_scanout_texture,
+ .dpy_gl_scanout_dmabuf = gd_egl_scanout_dmabuf,
+ .dpy_gl_cursor_dmabuf = gd_egl_cursor_dmabuf,
+ .dpy_gl_cursor_position = gd_egl_cursor_position,
+ .dpy_gl_update = gd_egl_flush,
+ .dpy_gl_release_dmabuf = gd_gl_release_dmabuf,
+ .dpy_has_dmabuf = gd_has_dmabuf,
+};
+
+#endif
+
+#endif /* CONFIG_OPENGL */
+
+/** QEMU Events **/
+
+static void gd_change_runstate(void *opaque, bool running, RunState state)
+{
+ GtkDisplayState *s = opaque;
+
+ gd_update_caption(s);
+}
+
+static void gd_mouse_mode_change(Notifier *notify, void *data)
+{
+ GtkDisplayState *s;
+ int i;
+
+ s = container_of(notify, GtkDisplayState, mouse_mode_notifier);
+ /* release the grab at switching to absolute mode */
+ if (qemu_input_is_absolute() && gd_is_grab_active(s)) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ FALSE);
+ }
+ for (i = 0; i < s->nb_vcs; i++) {
+ VirtualConsole *vc = &s->vc[i];
+ gd_update_cursor(vc);
+ }
+}
+
+/** GTK Events **/
+
+static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
+ void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ bool allow_close = true;
+
+ if (s->opts->has_window_close && !s->opts->window_close) {
+ allow_close = false;
+ }
+
+ if (allow_close) {
+ qmp_quit(NULL);
+ }
+
+ return TRUE;
+}
+
+static void gd_set_ui_info(VirtualConsole *vc, gint width, gint height)
+{
+ QemuUIInfo info;
+
+ memset(&info, 0, sizeof(info));
+ info.width = width;
+ info.height = height;
+ dpy_set_ui_info(vc->gfx.dcl.con, &info);
+}
+
+#if defined(CONFIG_OPENGL)
+
+static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context,
+ void *opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ if (vc->gfx.gls) {
+ gd_gl_area_draw(vc);
+ }
+ return TRUE;
+}
+
+static void gd_resize_event(GtkGLArea *area,
+ gint width, gint height, gpointer *opaque)
+{
+ VirtualConsole *vc = (void *)opaque;
+
+ gd_set_ui_info(vc, width, height);
+}
+
+#endif
+
+/*
+ * If available, return the update interval of the monitor in ms,
+ * else return 0 (the default update interval).
+ */
+int gd_monitor_update_interval(GtkWidget *widget)
+{
+#ifdef GDK_VERSION_3_22
+ GdkWindow *win = gtk_widget_get_window(widget);
+
+ if (win) {
+ GdkDisplay *dpy = gtk_widget_get_display(widget);
+ GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
+ int refresh_rate = gdk_monitor_get_refresh_rate(monitor); /* [mHz] */
+
+ if (refresh_rate) {
+ /* T = 1 / f = 1 [s*Hz] / f = 1000*1000 [ms*mHz] / f */
+ return MIN(1000 * 1000 / refresh_rate,
+ GUI_REFRESH_INTERVAL_DEFAULT);
+ }
+ }
+#endif
+ return 0;
+}
+
+static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+ int mx, my;
+ int ww, wh;
+ int fbw, fbh;
+
+#if defined(CONFIG_OPENGL)
+ if (vc->gfx.gls) {
+ if (gtk_use_gl_area) {
+ /* invoke render callback please */
+ return FALSE;
+ } else {
+#ifdef CONFIG_X11
+ gd_egl_draw(vc);
+ return TRUE;
+#else
+ abort();
+#endif
+ }
+ }
+#endif
+
+ if (!gtk_widget_get_realized(widget)) {
+ return FALSE;
+ }
+ if (!vc->gfx.ds) {
+ return FALSE;
+ }
+ if (!vc->gfx.surface) {
+ return FALSE;
+ }
+
+ vc->gfx.dcl.update_interval =
+ gd_monitor_update_interval(vc->window ? vc->window : s->window);
+
+ fbw = surface_width(vc->gfx.ds);
+ fbh = surface_height(vc->gfx.ds);
+
+ ww = gdk_window_get_width(gtk_widget_get_window(widget));
+ wh = gdk_window_get_height(gtk_widget_get_window(widget));
+
+ if (s->full_screen) {
+ vc->gfx.scale_x = (double)ww / fbw;
+ vc->gfx.scale_y = (double)wh / fbh;
+ } else if (s->free_scale) {
+ double sx, sy;
+
+ sx = (double)ww / fbw;
+ sy = (double)wh / fbh;
+
+ vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
+ }
+
+ fbw *= vc->gfx.scale_x;
+ fbh *= vc->gfx.scale_y;
+
+ mx = my = 0;
+ if (ww > fbw) {
+ mx = (ww - fbw) / 2;
+ }
+ if (wh > fbh) {
+ my = (wh - fbh) / 2;
+ }
+
+ cairo_rectangle(cr, 0, 0, ww, wh);
+
+ /* Optionally cut out the inner area where the pixmap
+ will be drawn. This avoids 'flashing' since we're
+ not double-buffering. Note we're using the undocumented
+ behaviour of drawing the rectangle from right to left
+ to cut out the whole */
+ cairo_rectangle(cr, mx + fbw, my,
+ -1 * fbw, fbh);
+ cairo_fill(cr);
+
+ cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y);
+ cairo_set_source_surface(cr, vc->gfx.surface,
+ mx / vc->gfx.scale_x, my / vc->gfx.scale_y);
+ cairo_paint(cr);
+
+ return TRUE;
+}
+
+static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
+ void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+ GdkWindow *window;
+ int x, y;
+ int mx, my;
+ int fbh, fbw;
+ int ww, wh, ws;
+
+ if (!vc->gfx.ds) {
+ return TRUE;
+ }
+
+ fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
+ fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
+
+ window = gtk_widget_get_window(vc->gfx.drawing_area);
+ ww = gdk_window_get_width(window);
+ wh = gdk_window_get_height(window);
+ ws = gdk_window_get_scale_factor(window);
+
+ mx = my = 0;
+ if (ww > fbw) {
+ mx = (ww - fbw) / 2;
+ }
+ if (wh > fbh) {
+ my = (wh - fbh) / 2;
+ }
+
+ x = (motion->x - mx) / vc->gfx.scale_x * ws;
+ y = (motion->y - my) / vc->gfx.scale_y * ws;
+
+ if (qemu_input_is_absolute()) {
+ if (x < 0 || y < 0 ||
+ x >= surface_width(vc->gfx.ds) ||
+ y >= surface_height(vc->gfx.ds)) {
+ return TRUE;
+ }
+ qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x,
+ 0, surface_width(vc->gfx.ds));
+ qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y,
+ 0, surface_height(vc->gfx.ds));
+ qemu_input_event_sync();
+ } else if (s->last_set && s->ptr_owner == vc) {
+ qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x);
+ qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y);
+ qemu_input_event_sync();
+ }
+ s->last_x = x;
+ s->last_y = y;
+ s->last_set = TRUE;
+
+ if (!qemu_input_is_absolute() && s->ptr_owner == vc) {
+ GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area);
+ GdkDisplay *dpy = gtk_widget_get_display(widget);
+ GdkWindow *win = gtk_widget_get_window(widget);
+ GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
+ GdkRectangle geometry;
+
+ int x = (int)motion->x_root;
+ int y = (int)motion->y_root;
+
+ gdk_monitor_get_geometry(monitor, &geometry);
+
+ /* In relative mode check to see if client pointer hit
+ * one of the monitor edges, and if so move it back to the
+ * center of the monitor. This is important because the pointer
+ * in the server doesn't correspond 1-for-1, and so
+ * may still be only half way across the screen. Without
+ * this warp, the server pointer would thus appear to hit
+ * an invisible wall */
+ if (x <= geometry.x || x - geometry.x >= geometry.width - 1 ||
+ y <= geometry.y || y - geometry.y >= geometry.height - 1) {
+ GdkDevice *dev = gdk_event_get_device((GdkEvent *)motion);
+ x = geometry.x + geometry.width / 2;
+ y = geometry.y + geometry.height / 2;
+
+ gdk_device_warp(dev, screen, x, y);
+ s->last_set = FALSE;
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
+ void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+ InputButton btn;
+
+ /* implicitly grab the input at the first click in the relative mode */
+ if (button->button == 1 && button->type == GDK_BUTTON_PRESS &&
+ !qemu_input_is_absolute() && s->ptr_owner != vc) {
+ if (!vc->window) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ TRUE);
+ } else {
+ gd_grab_pointer(vc, "relative-mode-click");
+ }
+ return TRUE;
+ }
+
+ if (button->button == 1) {
+ btn = INPUT_BUTTON_LEFT;
+ } else if (button->button == 2) {
+ btn = INPUT_BUTTON_MIDDLE;
+ } else if (button->button == 3) {
+ btn = INPUT_BUTTON_RIGHT;
+ } else if (button->button == 8) {
+ btn = INPUT_BUTTON_SIDE;
+ } else if (button->button == 9) {
+ btn = INPUT_BUTTON_EXTRA;
+ } else {
+ return TRUE;
+ }
+
+ qemu_input_queue_btn(vc->gfx.dcl.con, btn,
+ button->type == GDK_BUTTON_PRESS);
+ qemu_input_event_sync();
+ return TRUE;
+}
+
+static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll,
+ void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ InputButton btn;
+
+ if (scroll->direction == GDK_SCROLL_UP) {
+ btn = INPUT_BUTTON_WHEEL_UP;
+ } else if (scroll->direction == GDK_SCROLL_DOWN) {
+ btn = INPUT_BUTTON_WHEEL_DOWN;
+ } else if (scroll->direction == GDK_SCROLL_SMOOTH) {
+ gdouble delta_x, delta_y;
+ if (!gdk_event_get_scroll_deltas((GdkEvent *)scroll,
+ &delta_x, &delta_y)) {
+ return TRUE;
+ }
+ if (delta_y == 0) {
+ return TRUE;
+ } else if (delta_y > 0) {
+ btn = INPUT_BUTTON_WHEEL_DOWN;
+ } else {
+ btn = INPUT_BUTTON_WHEEL_UP;
+ }
+ } else {
+ return TRUE;
+ }
+
+ qemu_input_queue_btn(vc->gfx.dcl.con, btn, true);
+ qemu_input_event_sync();
+ qemu_input_queue_btn(vc->gfx.dcl.con, btn, false);
+ qemu_input_event_sync();
+ return TRUE;
+}
+
+
+static const guint16 *gd_get_keymap(size_t *maplen)
+{
+ GdkDisplay *dpy = gdk_display_get_default();
+
+#ifdef GDK_WINDOWING_X11
+ if (GDK_IS_X11_DISPLAY(dpy)) {
+ trace_gd_keymap_windowing("x11");
+ return qemu_xkeymap_mapping_table(
+ gdk_x11_display_get_xdisplay(dpy), maplen);
+ }
+#endif
+
+#ifdef GDK_WINDOWING_WAYLAND
+ if (GDK_IS_WAYLAND_DISPLAY(dpy)) {
+ trace_gd_keymap_windowing("wayland");
+ *maplen = qemu_input_map_xorgevdev_to_qcode_len;
+ return qemu_input_map_xorgevdev_to_qcode;
+ }
+#endif
+
+#ifdef GDK_WINDOWING_WIN32
+ if (GDK_IS_WIN32_DISPLAY(dpy)) {
+ trace_gd_keymap_windowing("win32");
+ *maplen = qemu_input_map_atset1_to_qcode_len;
+ return qemu_input_map_atset1_to_qcode;
+ }
+#endif
+
+#ifdef GDK_WINDOWING_QUARTZ
+ if (GDK_IS_QUARTZ_DISPLAY(dpy)) {
+ trace_gd_keymap_windowing("quartz");
+ *maplen = qemu_input_map_osx_to_qcode_len;
+ return qemu_input_map_osx_to_qcode;
+ }
+#endif
+
+#ifdef GDK_WINDOWING_BROADWAY
+ if (GDK_IS_BROADWAY_DISPLAY(dpy)) {
+ trace_gd_keymap_windowing("broadway");
+ g_warning("experimental: using broadway, x11 virtual keysym\n"
+ "mapping - with very limited support. See also\n"
+ "https://bugzilla.gnome.org/show_bug.cgi?id=700105");
+ *maplen = qemu_input_map_x11_to_qcode_len;
+ return qemu_input_map_x11_to_qcode;
+ }
+#endif
+
+ g_warning("Unsupported GDK Windowing platform.\n"
+ "Disabling extended keycode tables.\n"
+ "Please report to qemu-devel@nongnu.org\n"
+ "including the following information:\n"
+ "\n"
+ " - Operating system\n"
+ " - GDK Windowing system build\n");
+ return NULL;
+}
+
+
+static int gd_map_keycode(int scancode)
+{
+ if (!keycode_map) {
+ return 0;
+ }
+ if (scancode > keycode_maplen) {
+ return 0;
+ }
+
+ return keycode_map[scancode];
+}
+
+static int gd_get_keycode(GdkEventKey *key)
+{
+#ifdef G_OS_WIN32
+ int scancode = gdk_event_get_scancode((GdkEvent *)key);
+
+ /* translate Windows native scancodes to atset1 keycodes */
+ switch (scancode & (KF_EXTENDED | 0xff)) {
+ case 0x145: /* NUMLOCK */
+ return scancode & 0xff;
+ }
+
+ return scancode & KF_EXTENDED ?
+ 0xe000 | (scancode & 0xff) : scancode & 0xff;
+
+#else
+ return key->hardware_keycode;
+#endif
+}
+
+static gboolean gd_text_key_down(GtkWidget *widget,
+ GdkEventKey *key, void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ QemuConsole *con = vc->gfx.dcl.con;
+
+ if (key->keyval == GDK_KEY_Delete) {
+ kbd_put_qcode_console(con, Q_KEY_CODE_DELETE, false);
+ } else if (key->length) {
+ kbd_put_string_console(con, key->string, key->length);
+ } else {
+ int qcode = gd_map_keycode(gd_get_keycode(key));
+ kbd_put_qcode_console(con, qcode, false);
+ }
+ return TRUE;
+}
+
+static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ int keycode, qcode;
+
+#ifdef G_OS_WIN32
+ /* on windows, we ought to ignore the reserved key event? */
+ if (key->hardware_keycode == 0xff)
+ return false;
+
+ if (!vc->s->kbd_owner) {
+ if (key->hardware_keycode == VK_LWIN ||
+ key->hardware_keycode == VK_RWIN) {
+ return FALSE;
+ }
+ }
+#endif
+
+ if (key->keyval == GDK_KEY_Pause
+#ifdef G_OS_WIN32
+ /* for some reason GDK does not fill keyval for VK_PAUSE
+ * See https://bugzilla.gnome.org/show_bug.cgi?id=769214
+ */
+ || key->hardware_keycode == VK_PAUSE
+#endif
+ ) {
+ qkbd_state_key_event(vc->gfx.kbd, Q_KEY_CODE_PAUSE,
+ key->type == GDK_KEY_PRESS);
+ return TRUE;
+ }
+
+ keycode = gd_get_keycode(key);
+ qcode = gd_map_keycode(keycode);
+
+ trace_gd_key_event(vc->label, keycode, qcode,
+ (key->type == GDK_KEY_PRESS) ? "down" : "up");
+
+ qkbd_state_key_event(vc->gfx.kbd, qcode,
+ key->type == GDK_KEY_PRESS);
+
+ return TRUE;
+}
+
+static gboolean gd_grab_broken_event(GtkWidget *widget,
+ GdkEventGrabBroken *event, void *opaque)
+{
+#ifdef CONFIG_WIN32
+ /*
+ * On Windows the Ctrl-Alt-Del key combination can't be grabbed. This
+ * key combination leaves all three keys in a stuck condition. We use
+ * the grab-broken-event to release all keys.
+ */
+ if (event->keyboard) {
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ gtk_release_modifiers(s);
+ }
+#endif
+ return TRUE;
+}
+
+static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque)
+{
+ if (event->type == GDK_MOTION_NOTIFY) {
+ return gd_motion_event(widget, &event->motion, opaque);
+ }
+ return FALSE;
+}
+
+/** Window Menu Actions **/
+
+static void gd_menu_pause(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+
+ if (s->external_pause_update) {
+ return;
+ }
+ if (runstate_is_running()) {
+ qmp_stop(NULL);
+ } else {
+ qmp_cont(NULL);
+ }
+}
+
+static void gd_menu_reset(GtkMenuItem *item, void *opaque)
+{
+ qmp_system_reset(NULL);
+}
+
+static void gd_menu_powerdown(GtkMenuItem *item, void *opaque)
+{
+ qmp_system_powerdown(NULL);
+}
+
+static void gd_menu_quit(GtkMenuItem *item, void *opaque)
+{
+ qmp_quit(NULL);
+}
+
+static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_by_menu(s);
+ GtkNotebook *nb = GTK_NOTEBOOK(s->notebook);
+ gint page;
+
+ gtk_release_modifiers(s);
+ if (vc) {
+ page = gtk_notebook_page_num(nb, vc->tab_item);
+ gtk_notebook_set_current_page(nb, page);
+ gtk_widget_grab_focus(vc->focus);
+ }
+}
+
+static void gd_accel_switch_vc(void *opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
+}
+
+static void gd_menu_show_tabs(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->show_tabs_item))) {
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), TRUE);
+ } else {
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
+ }
+ gd_update_windowsize(vc);
+}
+
+static gboolean gd_tab_window_close(GtkWidget *widget, GdkEvent *event,
+ void *opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ gtk_widget_set_sensitive(vc->menu_item, true);
+ gd_widget_reparent(vc->window, s->notebook, vc->tab_item);
+ gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(s->notebook),
+ vc->tab_item, vc->label);
+ gtk_widget_destroy(vc->window);
+ vc->window = NULL;
+#if defined(CONFIG_OPENGL)
+ if (vc->gfx.esurface) {
+ eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
+ vc->gfx.esurface = NULL;
+ }
+ if (vc->gfx.ectx) {
+ eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
+ vc->gfx.ectx = NULL;
+ }
+#endif
+ return TRUE;
+}
+
+static gboolean gd_win_grab(void *opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ fprintf(stderr, "%s: %s\n", __func__, vc->label);
+ if (vc->s->ptr_owner) {
+ gd_ungrab_pointer(vc->s);
+ } else {
+ gd_grab_pointer(vc, "user-request-detached-tab");
+ }
+ return TRUE;
+}
+
+static void gd_menu_untabify(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (vc->type == GD_VC_GFX &&
+ qemu_console_is_graphic(vc->gfx.dcl.con)) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ FALSE);
+ }
+ if (!vc->window) {
+ gtk_widget_set_sensitive(vc->menu_item, false);
+ vc->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+#if defined(CONFIG_OPENGL)
+ if (vc->gfx.esurface) {
+ eglDestroySurface(qemu_egl_display, vc->gfx.esurface);
+ vc->gfx.esurface = NULL;
+ }
+ if (vc->gfx.esurface) {
+ eglDestroyContext(qemu_egl_display, vc->gfx.ectx);
+ vc->gfx.ectx = NULL;
+ }
+#endif
+ gd_widget_reparent(s->notebook, vc->window, vc->tab_item);
+
+ g_signal_connect(vc->window, "delete-event",
+ G_CALLBACK(gd_tab_window_close), vc);
+ gtk_widget_show_all(vc->window);
+
+ if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
+ GtkAccelGroup *ag = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(vc->window), ag);
+
+ GClosure *cb = g_cclosure_new_swap(G_CALLBACK(gd_win_grab),
+ vc, NULL);
+ gtk_accel_group_connect(ag, GDK_KEY_g, HOTKEY_MODIFIERS, 0, cb);
+ }
+
+ gd_update_geometry_hints(vc);
+ gd_update_caption(s);
+ }
+}
+
+static void gd_menu_show_menubar(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (s->full_screen) {
+ return;
+ }
+
+ if (gtk_check_menu_item_get_active(
+ GTK_CHECK_MENU_ITEM(s->show_menubar_item))) {
+ gtk_widget_show(s->menu_bar);
+ } else {
+ gtk_widget_hide(s->menu_bar);
+ }
+ gd_update_windowsize(vc);
+}
+
+static void gd_accel_show_menubar(void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->show_menubar_item));
+}
+
+static void gd_menu_full_screen(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (!s->full_screen) {
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
+ gtk_widget_hide(s->menu_bar);
+ if (vc->type == GD_VC_GFX) {
+ gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1);
+ }
+ gtk_window_fullscreen(GTK_WINDOW(s->window));
+ s->full_screen = TRUE;
+ } else {
+ gtk_window_unfullscreen(GTK_WINDOW(s->window));
+ gd_menu_show_tabs(GTK_MENU_ITEM(s->show_tabs_item), s);
+ if (gtk_check_menu_item_get_active(
+ GTK_CHECK_MENU_ITEM(s->show_menubar_item))) {
+ gtk_widget_show(s->menu_bar);
+ }
+ s->full_screen = FALSE;
+ if (vc->type == GD_VC_GFX) {
+ vc->gfx.scale_x = 1.0;
+ vc->gfx.scale_y = 1.0;
+ gd_update_windowsize(vc);
+ }
+ }
+
+ gd_update_cursor(vc);
+}
+
+static void gd_accel_full_screen(void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
+}
+
+static void gd_menu_zoom_in(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
+ FALSE);
+
+ vc->gfx.scale_x += VC_SCALE_STEP;
+ vc->gfx.scale_y += VC_SCALE_STEP;
+
+ gd_update_windowsize(vc);
+}
+
+static void gd_accel_zoom_in(void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_in_item));
+}
+
+static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item),
+ FALSE);
+
+ vc->gfx.scale_x -= VC_SCALE_STEP;
+ vc->gfx.scale_y -= VC_SCALE_STEP;
+
+ vc->gfx.scale_x = MAX(vc->gfx.scale_x, VC_SCALE_MIN);
+ vc->gfx.scale_y = MAX(vc->gfx.scale_y, VC_SCALE_MIN);
+
+ gd_update_windowsize(vc);
+}
+
+static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ vc->gfx.scale_x = 1.0;
+ vc->gfx.scale_y = 1.0;
+
+ gd_update_windowsize(vc);
+}
+
+static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) {
+ s->free_scale = TRUE;
+ } else {
+ s->free_scale = FALSE;
+ vc->gfx.scale_x = 1.0;
+ vc->gfx.scale_y = 1.0;
+ }
+
+ gd_update_windowsize(vc);
+ gd_update_full_redraw(vc);
+}
+
+static void gd_grab_update(VirtualConsole *vc, bool kbd, bool ptr)
+{
+ GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
+ GdkSeat *seat = gdk_display_get_default_seat(display);
+ GdkWindow *window = gtk_widget_get_window(vc->gfx.drawing_area);
+ GdkSeatCapabilities caps = 0;
+ GdkCursor *cursor = NULL;
+
+ if (kbd) {
+ caps |= GDK_SEAT_CAPABILITY_KEYBOARD;
+ }
+ if (ptr) {
+ caps |= GDK_SEAT_CAPABILITY_ALL_POINTING;
+ cursor = vc->s->null_cursor;
+ }
+
+ if (caps) {
+ gdk_seat_grab(seat, window, caps, false, cursor,
+ NULL, NULL, NULL);
+ } else {
+ gdk_seat_ungrab(seat);
+ }
+}
+
+static void gd_grab_keyboard(VirtualConsole *vc, const char *reason)
+{
+ if (vc->s->kbd_owner) {
+ if (vc->s->kbd_owner == vc) {
+ return;
+ } else {
+ gd_ungrab_keyboard(vc->s);
+ }
+ }
+
+ win32_kbd_set_grab(true);
+ gd_grab_update(vc, true, vc->s->ptr_owner == vc);
+ vc->s->kbd_owner = vc;
+ gd_update_caption(vc->s);
+ trace_gd_grab(vc->label, "kbd", reason);
+}
+
+static void gd_ungrab_keyboard(GtkDisplayState *s)
+{
+ VirtualConsole *vc = s->kbd_owner;
+
+ if (vc == NULL) {
+ return;
+ }
+ s->kbd_owner = NULL;
+
+ win32_kbd_set_grab(false);
+ gd_grab_update(vc, false, vc->s->ptr_owner == vc);
+ gd_update_caption(s);
+ trace_gd_ungrab(vc->label, "kbd");
+}
+
+static void gd_grab_pointer(VirtualConsole *vc, const char *reason)
+{
+ GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area);
+
+ if (vc->s->ptr_owner) {
+ if (vc->s->ptr_owner == vc) {
+ return;
+ } else {
+ gd_ungrab_pointer(vc->s);
+ }
+ }
+
+ gd_grab_update(vc, vc->s->kbd_owner == vc, true);
+ gdk_device_get_position(gd_get_pointer(display),
+ NULL, &vc->s->grab_x_root, &vc->s->grab_y_root);
+ vc->s->ptr_owner = vc;
+ gd_update_caption(vc->s);
+ trace_gd_grab(vc->label, "ptr", reason);
+}
+
+static void gd_ungrab_pointer(GtkDisplayState *s)
+{
+ VirtualConsole *vc = s->ptr_owner;
+ GdkDisplay *display;
+
+ if (vc == NULL) {
+ return;
+ }
+ s->ptr_owner = NULL;
+
+ display = gtk_widget_get_display(vc->gfx.drawing_area);
+ gd_grab_update(vc, vc->s->kbd_owner == vc, false);
+ gdk_device_warp(gd_get_pointer(display),
+ gtk_widget_get_screen(vc->gfx.drawing_area),
+ vc->s->grab_x_root, vc->s->grab_y_root);
+ gd_update_caption(s);
+ trace_gd_ungrab(vc->label, "ptr");
+}
+
+static void gd_menu_grab_input(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+ if (gd_is_grab_active(s)) {
+ gd_grab_keyboard(vc, "user-request-main-window");
+ gd_grab_pointer(vc, "user-request-main-window");
+ } else {
+ gd_ungrab_keyboard(s);
+ gd_ungrab_pointer(s);
+ }
+
+ gd_update_cursor(vc);
+}
+
+static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
+ gpointer data)
+{
+ GtkDisplayState *s = data;
+ VirtualConsole *vc;
+ gboolean on_vga;
+
+ if (!gtk_widget_get_realized(s->notebook)) {
+ return;
+ }
+
+ vc = gd_vc_find_by_page(s, arg2);
+ if (!vc) {
+ return;
+ }
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item),
+ TRUE);
+ on_vga = (vc->type == GD_VC_GFX &&
+ qemu_console_is_graphic(vc->gfx.dcl.con));
+ if (!on_vga) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ FALSE);
+ } else if (s->full_screen) {
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
+ TRUE);
+ }
+ gtk_widget_set_sensitive(s->grab_item, on_vga);
+#ifdef CONFIG_VTE
+ gtk_widget_set_sensitive(s->copy_item, vc->type == GD_VC_VTE);
+#endif
+
+ gd_update_windowsize(vc);
+ gd_update_cursor(vc);
+}
+
+static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
+ gpointer opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ if (gd_grab_on_hover(s)) {
+ gd_grab_keyboard(vc, "grab-on-hover");
+ }
+ return TRUE;
+}
+
+static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
+ gpointer opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ if (gd_grab_on_hover(s)) {
+ gd_ungrab_keyboard(s);
+ }
+ return TRUE;
+}
+
+static gboolean gd_focus_in_event(GtkWidget *widget,
+ GdkEventFocus *event, gpointer opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ win32_kbd_set_window(gd_win32_get_hwnd(vc));
+ return TRUE;
+}
+
+static gboolean gd_focus_out_event(GtkWidget *widget,
+ GdkEventFocus *event, gpointer opaque)
+{
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
+
+ win32_kbd_set_window(NULL);
+ gtk_release_modifiers(s);
+ return TRUE;
+}
+
+static gboolean gd_configure(GtkWidget *widget,
+ GdkEventConfigure *cfg, gpointer opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ gd_set_ui_info(vc, cfg->width, cfg->height);
+ return FALSE;
+}
+
+/** Virtual Console Callbacks **/
+
+static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
+ int idx, GSList *group, GtkWidget *view_menu)
+{
+ vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label);
+ gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx,
+ HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL));
+ gtk_accel_label_set_accel(
+ GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))),
+ GDK_KEY_1 + idx, HOTKEY_MODIFIERS);
+
+ g_signal_connect(vc->menu_item, "activate",
+ G_CALLBACK(gd_menu_switch_vc), s);
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
+
+ return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
+}
+
+#if defined(CONFIG_VTE)
+static void gd_menu_copy(GtkMenuItem *item, void *opaque)
+{
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
+
+#if VTE_CHECK_VERSION(0, 50, 0)
+ vte_terminal_copy_clipboard_format(VTE_TERMINAL(vc->vte.terminal),
+ VTE_FORMAT_TEXT);
+#else
+ vte_terminal_copy_clipboard(VTE_TERMINAL(vc->vte.terminal));
+#endif
+}
+
+static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque)
+{
+ VirtualConsole *vc = opaque;
+
+ if (gtk_adjustment_get_upper(adjustment) >
+ gtk_adjustment_get_page_size(adjustment)) {
+ gtk_widget_show(vc->vte.scrollbar);
+ } else {
+ gtk_widget_hide(vc->vte.scrollbar);
+ }
+}
+
+static void gd_vc_send_chars(VirtualConsole *vc)
+{
+ uint32_t len, avail;
+
+ len = qemu_chr_be_can_write(vc->vte.chr);
+ avail = fifo8_num_used(&vc->vte.out_fifo);
+ while (len > 0 && avail > 0) {
+ const uint8_t *buf;
+ uint32_t size;
+
+ buf = fifo8_pop_buf(&vc->vte.out_fifo, MIN(len, avail), &size);
+ qemu_chr_be_write(vc->vte.chr, (uint8_t *)buf, size);
+ len = qemu_chr_be_can_write(vc->vte.chr);
+ avail -= size;
+ }
+}
+
+static int gd_vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ VCChardev *vcd = VC_CHARDEV(chr);
+ VirtualConsole *vc = vcd->console;
+
+ vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
+ return len;
+}
+
+static void gd_vc_chr_accept_input(Chardev *chr)
+{
+ VCChardev *vcd = VC_CHARDEV(chr);
+ VirtualConsole *vc = vcd->console;
+
+ gd_vc_send_chars(vc);
+}
+
+static void gd_vc_chr_set_echo(Chardev *chr, bool echo)
+{
+ VCChardev *vcd = VC_CHARDEV(chr);
+ VirtualConsole *vc = vcd->console;
+
+ if (vc) {
+ vc->vte.echo = echo;
+ } else {
+ vcd->echo = echo;
+ }
+}
+
+static int nb_vcs;
+static Chardev *vcs[MAX_VCS];
+static void gd_vc_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ if (nb_vcs == MAX_VCS) {
+ error_setg(errp, "Maximum number of consoles reached");
+ return;
+ }
+
+ vcs[nb_vcs++] = chr;
+
+ /* console/chardev init sometimes completes elsewhere in a 2nd
+ * stage, so defer OPENED events until they are fully initialized
+ */
+ *be_opened = false;
+}
+
+static void char_gd_vc_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_vc;
+ cc->open = gd_vc_open;
+ cc->chr_write = gd_vc_chr_write;
+ cc->chr_accept_input = gd_vc_chr_accept_input;
+ cc->chr_set_echo = gd_vc_chr_set_echo;
+}
+
+static const TypeInfo char_gd_vc_type_info = {
+ .name = TYPE_CHARDEV_VC,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(VCChardev),
+ .class_init = char_gd_vc_class_init,
+};
+
+static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
+ gpointer user_data)
+{
+ VirtualConsole *vc = user_data;
+ uint32_t free;
+
+ if (vc->vte.echo) {
+ VteTerminal *term = VTE_TERMINAL(vc->vte.terminal);
+ int i;
+ for (i = 0; i < size; i++) {
+ uint8_t c = text[i];
+ if (c >= 128 || isprint(c)) {
+ /* 8-bit characters are considered printable. */
+ vte_terminal_feed(term, &text[i], 1);
+ } else if (c == '\r' || c == '\n') {
+ vte_terminal_feed(term, "\r\n", 2);
+ } else {
+ char ctrl[2] = { '^', 0};
+ ctrl[1] = text[i] ^ 64;
+ vte_terminal_feed(term, ctrl, 2);
+ }
+ }
+ }
+
+ free = fifo8_num_free(&vc->vte.out_fifo);
+ fifo8_push_all(&vc->vte.out_fifo, (uint8_t *)text, MIN(free, size));
+ gd_vc_send_chars(vc);
+
+ return TRUE;
+}
+
+static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc,
+ Chardev *chr, int idx,
+ GSList *group, GtkWidget *view_menu)
+{
+ char buffer[32];
+ GtkWidget *box;
+ GtkWidget *scrollbar;
+ GtkAdjustment *vadjustment;
+ VCChardev *vcd = VC_CHARDEV(chr);
+
+ vc->s = s;
+ vc->vte.echo = vcd->echo;
+ vc->vte.chr = chr;
+ fifo8_create(&vc->vte.out_fifo, 4096);
+ vcd->console = vc;
+
+ snprintf(buffer, sizeof(buffer), "vc%d", idx);
+ vc->label = g_strdup_printf("%s", vc->vte.chr->label
+ ? vc->vte.chr->label : buffer);
+ group = gd_vc_menu_init(s, vc, idx, group, view_menu);
+
+ vc->vte.terminal = vte_terminal_new();
+ g_signal_connect(vc->vte.terminal, "commit", G_CALLBACK(gd_vc_in), vc);
+
+ /* The documentation says that the default is UTF-8, but actually it is
+ * 7-bit ASCII at least in VTE 0.38. The function is deprecated since
+ * VTE 0.54 (only UTF-8 is supported now). */
+#if !VTE_CHECK_VERSION(0, 54, 0)
+#if VTE_CHECK_VERSION(0, 38, 0)
+ vte_terminal_set_encoding(VTE_TERMINAL(vc->vte.terminal), "UTF-8", NULL);
+#else
+ vte_terminal_set_encoding(VTE_TERMINAL(vc->vte.terminal), "UTF-8");
+#endif
+#endif
+
+ vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1);
+ vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal),
+ VC_TERM_X_MIN, VC_TERM_Y_MIN);
+
+#if VTE_CHECK_VERSION(0, 28, 0)
+ vadjustment = gtk_scrollable_get_vadjustment
+ (GTK_SCROLLABLE(vc->vte.terminal));
+#else
+ vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
+#endif
+
+ box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
+ scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
+
+ gtk_box_pack_end(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
+
+ vc->vte.box = box;
+ vc->vte.scrollbar = scrollbar;
+
+ g_signal_connect(vadjustment, "changed",
+ G_CALLBACK(gd_vc_adjustment_changed), vc);
+
+ vc->type = GD_VC_VTE;
+ vc->tab_item = box;
+ vc->focus = vc->vte.terminal;
+ gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item,
+ gtk_label_new(vc->label));
+
+ qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
+
+ return group;
+}
+
+static void gd_vcs_init(GtkDisplayState *s, GSList *group,
+ GtkWidget *view_menu)
+{
+ int i;
+
+ for (i = 0; i < nb_vcs; i++) {
+ VirtualConsole *vc = &s->vc[s->nb_vcs];
+ group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu);
+ s->nb_vcs++;
+ }
+}
+#endif /* CONFIG_VTE */
+
+/** Window Creation **/
+
+static void gd_connect_vc_gfx_signals(VirtualConsole *vc)
+{
+ g_signal_connect(vc->gfx.drawing_area, "draw",
+ G_CALLBACK(gd_draw_event), vc);
+#if defined(CONFIG_OPENGL)
+ if (gtk_use_gl_area) {
+ /* wire up GtkGlArea events */
+ g_signal_connect(vc->gfx.drawing_area, "render",
+ G_CALLBACK(gd_render_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "resize",
+ G_CALLBACK(gd_resize_event), vc);
+ }
+#endif
+ if (qemu_console_is_graphic(vc->gfx.dcl.con)) {
+ g_signal_connect(vc->gfx.drawing_area, "event",
+ G_CALLBACK(gd_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "button-press-event",
+ G_CALLBACK(gd_button_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "button-release-event",
+ G_CALLBACK(gd_button_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "scroll-event",
+ G_CALLBACK(gd_scroll_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "key-press-event",
+ G_CALLBACK(gd_key_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "key-release-event",
+ G_CALLBACK(gd_key_event), vc);
+
+ g_signal_connect(vc->gfx.drawing_area, "enter-notify-event",
+ G_CALLBACK(gd_enter_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "leave-notify-event",
+ G_CALLBACK(gd_leave_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "focus-in-event",
+ G_CALLBACK(gd_focus_in_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "focus-out-event",
+ G_CALLBACK(gd_focus_out_event), vc);
+ g_signal_connect(vc->gfx.drawing_area, "configure-event",
+ G_CALLBACK(gd_configure), vc);
+ g_signal_connect(vc->gfx.drawing_area, "grab-broken-event",
+ G_CALLBACK(gd_grab_broken_event), vc);
+ } else {
+ g_signal_connect(vc->gfx.drawing_area, "key-press-event",
+ G_CALLBACK(gd_text_key_down), vc);
+ }
+}
+
+static void gd_connect_signals(GtkDisplayState *s)
+{
+ g_signal_connect(s->show_tabs_item, "activate",
+ G_CALLBACK(gd_menu_show_tabs), s);
+ g_signal_connect(s->untabify_item, "activate",
+ G_CALLBACK(gd_menu_untabify), s);
+ g_signal_connect(s->show_menubar_item, "activate",
+ G_CALLBACK(gd_menu_show_menubar), s);
+
+ g_signal_connect(s->window, "delete-event",
+ G_CALLBACK(gd_window_close), s);
+
+ g_signal_connect(s->pause_item, "activate",
+ G_CALLBACK(gd_menu_pause), s);
+ g_signal_connect(s->reset_item, "activate",
+ G_CALLBACK(gd_menu_reset), s);
+ g_signal_connect(s->powerdown_item, "activate",
+ G_CALLBACK(gd_menu_powerdown), s);
+ g_signal_connect(s->quit_item, "activate",
+ G_CALLBACK(gd_menu_quit), s);
+#if defined(CONFIG_VTE)
+ g_signal_connect(s->copy_item, "activate",
+ G_CALLBACK(gd_menu_copy), s);
+#endif
+ g_signal_connect(s->full_screen_item, "activate",
+ G_CALLBACK(gd_menu_full_screen), s);
+ g_signal_connect(s->zoom_in_item, "activate",
+ G_CALLBACK(gd_menu_zoom_in), s);
+ g_signal_connect(s->zoom_out_item, "activate",
+ G_CALLBACK(gd_menu_zoom_out), s);
+ g_signal_connect(s->zoom_fixed_item, "activate",
+ G_CALLBACK(gd_menu_zoom_fixed), s);
+ g_signal_connect(s->zoom_fit_item, "activate",
+ G_CALLBACK(gd_menu_zoom_fit), s);
+ g_signal_connect(s->grab_item, "activate",
+ G_CALLBACK(gd_menu_grab_input), s);
+ g_signal_connect(s->notebook, "switch-page",
+ G_CALLBACK(gd_change_page), s);
+}
+
+static GtkWidget *gd_create_menu_machine(GtkDisplayState *s)
+{
+ GtkWidget *machine_menu;
+ GtkWidget *separator;
+
+ machine_menu = gtk_menu_new();
+ gtk_menu_set_accel_group(GTK_MENU(machine_menu), s->accel_group);
+
+ s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item);
+
+ separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
+
+ s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item);
+
+ s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item);
+
+ separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
+
+ s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit"));
+ gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item),
+ "<QEMU>/Machine/Quit");
+ gtk_accel_map_add_entry("<QEMU>/Machine/Quit",
+ GDK_KEY_q, HOTKEY_MODIFIERS);
+ gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item);
+
+ return machine_menu;
+}
+
+#if defined(CONFIG_OPENGL)
+static void gl_area_realize(GtkGLArea *area, VirtualConsole *vc)
+{
+ gtk_gl_area_make_current(area);
+ qemu_egl_display = eglGetCurrentDisplay();
+ vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
+ if (!vc->gfx.has_dmabuf) {
+ error_report("GtkGLArea console lacks DMABUF support.");
+ }
+}
+#endif
+
+static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc,
+ QemuConsole *con, int idx,
+ GSList *group, GtkWidget *view_menu)
+{
+ bool zoom_to_fit = false;
+
+ vc->label = qemu_console_get_label(con);
+ vc->s = s;
+ vc->gfx.scale_x = 1.0;
+ vc->gfx.scale_y = 1.0;
+
+#if defined(CONFIG_OPENGL)
+ if (display_opengl) {
+ if (gtk_use_gl_area) {
+ vc->gfx.drawing_area = gtk_gl_area_new();
+ g_signal_connect(vc->gfx.drawing_area, "realize",
+ G_CALLBACK(gl_area_realize), vc);
+ vc->gfx.dcl.ops = &dcl_gl_area_ops;
+ } else {
+#ifdef CONFIG_X11
+ vc->gfx.drawing_area = gtk_drawing_area_new();
+ /*
+ * gtk_widget_set_double_buffered() was deprecated in 3.14.
+ * It is required for opengl rendering on X11 though. A
+ * proper replacement (native opengl support) is only
+ * available in 3.16+. Silence the warning if possible.
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+ gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE);
+#pragma GCC diagnostic pop
+ vc->gfx.dcl.ops = &dcl_egl_ops;
+ vc->gfx.has_dmabuf = qemu_egl_has_dmabuf();
+#else
+ abort();
+#endif
+ }
+ } else
+#endif
+ {
+ vc->gfx.drawing_area = gtk_drawing_area_new();
+ vc->gfx.dcl.ops = &dcl_ops;
+ }
+
+
+ gtk_widget_add_events(vc->gfx.drawing_area,
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON_MOTION_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK |
+ GDK_SCROLL_MASK |
+ GDK_SMOOTH_SCROLL_MASK |
+ GDK_KEY_PRESS_MASK);
+ gtk_widget_set_can_focus(vc->gfx.drawing_area, TRUE);
+
+ vc->type = GD_VC_GFX;
+ vc->tab_item = vc->gfx.drawing_area;
+ vc->focus = vc->gfx.drawing_area;
+ gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook),
+ vc->tab_item, gtk_label_new(vc->label));
+
+ vc->gfx.kbd = qkbd_state_init(con);
+ vc->gfx.dcl.con = con;
+
+ register_displaychangelistener(&vc->gfx.dcl);
+
+ gd_connect_vc_gfx_signals(vc);
+ group = gd_vc_menu_init(s, vc, idx, group, view_menu);
+
+ if (dpy_ui_info_supported(vc->gfx.dcl.con)) {
+ zoom_to_fit = true;
+ }
+ if (s->opts->u.gtk.has_zoom_to_fit) {
+ zoom_to_fit = s->opts->u.gtk.zoom_to_fit;
+ }
+ if (zoom_to_fit) {
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item));
+ s->free_scale = true;
+ }
+
+ return group;
+}
+
+static GtkWidget *gd_create_menu_view(GtkDisplayState *s)
+{
+ GSList *group = NULL;
+ GtkWidget *view_menu;
+ GtkWidget *separator;
+ QemuConsole *con;
+ int vc;
+
+ view_menu = gtk_menu_new();
+ gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
+
+ s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen"));
+
+#if defined(CONFIG_VTE)
+ s->copy_item = gtk_menu_item_new_with_mnemonic(_("_Copy"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->copy_item);
+#endif
+
+ gtk_accel_group_connect(s->accel_group, GDK_KEY_f, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_full_screen), s, NULL));
+ gtk_accel_label_set_accel(
+ GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->full_screen_item))),
+ GDK_KEY_f, HOTKEY_MODIFIERS);
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item);
+
+ separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
+
+ s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In"));
+ gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item),
+ "<QEMU>/View/Zoom In");
+ gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus,
+ HOTKEY_MODIFIERS);
+ gtk_accel_group_connect(s->accel_group, GDK_KEY_equal, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_zoom_in), s, NULL));
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item);
+
+ s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out"));
+ gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item),
+ "<QEMU>/View/Zoom Out");
+ gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus,
+ HOTKEY_MODIFIERS);
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item);
+
+ s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit"));
+ gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item),
+ "<QEMU>/View/Zoom Fixed");
+ gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0,
+ HOTKEY_MODIFIERS);
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item);
+
+ s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item);
+
+ separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
+
+ s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_on_hover_item);
+
+ s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input"));
+ gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item),
+ "<QEMU>/View/Grab Input");
+ gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g,
+ HOTKEY_MODIFIERS);
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item);
+
+ separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
+
+ /* gfx */
+ for (vc = 0;; vc++) {
+ con = qemu_console_lookup_by_index(vc);
+ if (!con) {
+ break;
+ }
+ group = gd_vc_gfx_init(s, &s->vc[vc], con,
+ vc, group, view_menu);
+ s->nb_vcs++;
+ }
+
+#if defined(CONFIG_VTE)
+ /* vte */
+ gd_vcs_init(s, group, view_menu);
+#endif
+
+ separator = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
+
+ s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item);
+
+ s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab"));
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item);
+
+ s->show_menubar_item = gtk_check_menu_item_new_with_mnemonic(
+ _("Show Menubar"));
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->show_menubar_item),
+ TRUE);
+ gtk_accel_group_connect(s->accel_group, GDK_KEY_m, HOTKEY_MODIFIERS, 0,
+ g_cclosure_new_swap(G_CALLBACK(gd_accel_show_menubar), s, NULL));
+ gtk_accel_label_set_accel(
+ GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->show_menubar_item))),
+ GDK_KEY_m, HOTKEY_MODIFIERS);
+ gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_menubar_item);
+
+ return view_menu;
+}
+
+static void gd_create_menus(GtkDisplayState *s)
+{
+ GtkSettings *settings;
+
+ s->accel_group = gtk_accel_group_new();
+ s->machine_menu = gd_create_menu_machine(s);
+ s->view_menu = gd_create_menu_view(s);
+
+ s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine"));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item),
+ s->machine_menu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->machine_menu_item);
+
+ s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View"));
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item);
+
+ g_object_set_data(G_OBJECT(s->window), "accel_group", s->accel_group);
+ gtk_window_add_accel_group(GTK_WINDOW(s->window), s->accel_group);
+
+ /* Disable the default "F10" menu shortcut. */
+ settings = gtk_widget_get_settings(s->window);
+ g_object_set(G_OBJECT(settings), "gtk-menu-bar-accel", "", NULL);
+}
+
+
+static gboolean gtkinit;
+
+static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
+{
+ VirtualConsole *vc;
+
+ GtkDisplayState *s = g_malloc0(sizeof(*s));
+ GdkDisplay *window_display;
+ GtkIconTheme *theme;
+ char *dir;
+
+ if (!gtkinit) {
+ fprintf(stderr, "gtk initialization failed\n");
+ exit(1);
+ }
+ assert(opts->type == DISPLAY_TYPE_GTK);
+ s->opts = opts;
+
+ theme = gtk_icon_theme_get_default();
+ dir = get_relocated_path(CONFIG_QEMU_ICONDIR);
+ gtk_icon_theme_prepend_search_path(theme, dir);
+ g_free(dir);
+ g_set_prgname("qemu");
+
+ s->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
+ s->notebook = gtk_notebook_new();
+ s->menu_bar = gtk_menu_bar_new();
+
+ s->free_scale = FALSE;
+
+ /* Mostly LC_MESSAGES only. See early_gtk_display_init() for details. For
+ * LC_CTYPE, we need to make sure that non-ASCII characters are considered
+ * printable, but without changing any of the character classes to make
+ * sure that we don't accidentally break implicit assumptions. */
+ setlocale(LC_MESSAGES, "");
+ setlocale(LC_CTYPE, "C.UTF-8");
+ dir = get_relocated_path(CONFIG_QEMU_LOCALEDIR);
+ bindtextdomain("qemu", dir);
+ g_free(dir);
+ bind_textdomain_codeset("qemu", "UTF-8");
+ textdomain("qemu");
+
+ window_display = gtk_widget_get_display(s->window);
+ if (s->opts->has_show_cursor && s->opts->show_cursor) {
+ s->null_cursor = NULL; /* default pointer */
+ } else {
+ s->null_cursor = gdk_cursor_new_for_display(window_display,
+ GDK_BLANK_CURSOR);
+ }
+
+ s->mouse_mode_notifier.notify = gd_mouse_mode_change;
+ qemu_add_mouse_mode_change_notifier(&s->mouse_mode_notifier);
+ qemu_add_vm_change_state_handler(gd_change_runstate, s);
+
+ gtk_window_set_icon_name(GTK_WINDOW(s->window), "qemu");
+
+ gd_create_menus(s);
+
+ gd_connect_signals(s);
+
+ gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
+ gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
+
+ gd_update_caption(s);
+
+ gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0);
+ gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0);
+
+ gtk_container_add(GTK_CONTAINER(s->window), s->vbox);
+
+ gtk_widget_show_all(s->window);
+
+ vc = gd_vc_find_current(s);
+ gtk_widget_set_sensitive(s->view_menu, vc != NULL);
+#ifdef CONFIG_VTE
+ gtk_widget_set_sensitive(s->copy_item,
+ vc && vc->type == GD_VC_VTE);
+#endif
+
+ if (opts->has_full_screen &&
+ opts->full_screen) {
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item));
+ }
+ if (opts->u.gtk.has_grab_on_hover &&
+ opts->u.gtk.grab_on_hover) {
+ gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item));
+ }
+ gd_clipboard_init(s);
+}
+
+static void early_gtk_display_init(DisplayOptions *opts)
+{
+ /* The QEMU code relies on the assumption that it's always run in
+ * the C locale. Therefore it is not prepared to deal with
+ * operations that produce different results depending on the
+ * locale, such as printf's formatting of decimal numbers, and
+ * possibly others.
+ *
+ * Since GTK+ calls setlocale() by default -importing the locale
+ * settings from the environment- we must prevent it from doing so
+ * using gtk_disable_setlocale().
+ *
+ * QEMU's GTK+ UI, however, _does_ have translations for some of
+ * the menu items. As a trade-off between a functionally correct
+ * QEMU and a fully internationalized UI we support importing
+ * LC_MESSAGES from the environment (see the setlocale() call
+ * earlier in this file). This allows us to display translated
+ * messages leaving everything else untouched.
+ */
+ gtk_disable_setlocale();
+ gtkinit = gtk_init_check(NULL, NULL);
+ if (!gtkinit) {
+ /* don't exit yet, that'll break -help */
+ return;
+ }
+
+ assert(opts->type == DISPLAY_TYPE_GTK);
+ if (opts->has_gl && opts->gl != DISPLAYGL_MODE_OFF) {
+#if defined(CONFIG_OPENGL)
+#if defined(GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
+ gtk_use_gl_area = true;
+ gtk_gl_area_init();
+ } else
+#endif
+ {
+#ifdef CONFIG_X11
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_ON;
+ gtk_egl_init(mode);
+#endif
+ }
+#endif
+ }
+
+ keycode_map = gd_get_keymap(&keycode_maplen);
+
+#if defined(CONFIG_VTE)
+ type_register(&char_gd_vc_type_info);
+#endif
+}
+
+static QemuDisplay qemu_display_gtk = {
+ .type = DISPLAY_TYPE_GTK,
+ .early_init = early_gtk_display_init,
+ .init = gtk_display_init,
+};
+
+static void register_gtk(void)
+{
+ qemu_display_register(&qemu_display_gtk);
+}
+
+type_init(register_gtk);
+
+#ifdef CONFIG_OPENGL
+module_dep("ui-opengl");
+#endif
diff --git a/ui/icons/Makefile b/ui/icons/Makefile
new file mode 100644
index 000000000..20bd64ccc
--- /dev/null
+++ b/ui/icons/Makefile
@@ -0,0 +1,13 @@
+
+# Regenerate bitmaps from the SVG using inkscape CLI export
+# and ImageMagick. Don't use ImageMagick for the initial
+# SVG conversion, since it merely calls inkscape, but uses
+# 96 DPI res resulting in poor quality output.
+
+regenerate:
+ for s in 16 24 32 48 64 128 256 512; \
+ do \
+ inkscape --without-gui --export-png=qemu_$${s}x$${s}.png \
+ --export-width=$$s --export-height=$$s qemu.svg ; \
+ done
+ convert qemu_32x32.png qemu_32x32.bmp
diff --git a/ui/icons/meson.build b/ui/icons/meson.build
new file mode 100644
index 000000000..12c52080e
--- /dev/null
+++ b/ui/icons/meson.build
@@ -0,0 +1,13 @@
+foreach s: [16, 24, 32, 48, 64, 128, 256, 512]
+ s = '@0@x@0@'.format(s.to_string())
+ install_data('qemu_@0@.png'.format(s),
+ rename: 'qemu.png',
+ install_dir: qemu_icondir / 'hicolor' / s / 'apps')
+endforeach
+
+install_data('qemu_32x32.bmp',
+ rename: 'qemu.bmp',
+ install_dir: qemu_icondir / 'hicolor' / '32x32' / 'apps')
+
+install_data('qemu.svg',
+ install_dir: qemu_icondir / 'hicolor' / 'scalable' / 'apps')
diff --git a/ui/icons/qemu.svg b/ui/icons/qemu.svg
new file mode 100644
index 000000000..24ca23a1e
--- /dev/null
+++ b/ui/icons/qemu.svg
@@ -0,0 +1,976 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="111.71874"
+ height="111.12498"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.2 r9819"
+ sodipodi:docname="qemu_logo_no_text.svg">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient4686">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4688" />
+ <stop
+ id="stop3956"
+ offset="0.75"
+ style="stop-color:#000000;stop-opacity:0.87843138;" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.43921569;"
+ offset="0.75"
+ id="stop3958" />
+ <stop
+ id="stop3960"
+ offset="0.88"
+ style="stop-color:#181818;stop-opacity:1;" />
+ <stop
+ style="stop-color:#242424;stop-opacity:1;"
+ offset="0.88"
+ id="stop3962" />
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="1"
+ id="stop4690" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4467">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4469" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.8974359;"
+ offset="1"
+ id="stop4471" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4431">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4433" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4435" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4466">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4468" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4470" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4321">
+ <stop
+ style="stop-color:#ff6702;stop-opacity:1;"
+ offset="0"
+ id="stop4323" />
+ <stop
+ style="stop-color:#ff9a55;stop-opacity:1;"
+ offset="1"
+ id="stop4325" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4283">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4285" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4287" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4251">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4253" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4255" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4007">
+ <stop
+ style="stop-color:#ff6600;stop-opacity:1;"
+ offset="0"
+ id="stop4009" />
+ <stop
+ style="stop-color:#ff9148;stop-opacity:1;"
+ offset="1"
+ id="stop4011" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999">
+ <stop
+ style="stop-color:#fff7f2;stop-opacity:1;"
+ offset="0"
+ id="stop4001" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4003" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3890">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3892" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3894" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3880">
+ <stop
+ style="stop-color:#eb7400;stop-opacity:1;"
+ offset="0"
+ id="stop3882" />
+ <stop
+ style="stop-color:#f7b06a;stop-opacity:1;"
+ offset="1"
+ id="stop3884" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4011">
+ <stop
+ style="stop-color:#042dc8;stop-opacity:1;"
+ offset="0"
+ id="stop4013" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.90598291;"
+ offset="0"
+ id="stop3881" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3869">
+ <stop
+ style="stop-color:#c95000;stop-opacity:1;"
+ offset="0"
+ id="stop3871" />
+ <stop
+ style="stop-color:#ff9e5e;stop-opacity:1;"
+ offset="1"
+ id="stop3873" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3861">
+ <stop
+ style="stop-color:#f06000;stop-opacity:1;"
+ offset="0"
+ id="stop3863" />
+ <stop
+ style="stop-color:#ffccaa;stop-opacity:1;"
+ offset="1"
+ id="stop3865" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3826">
+ <stop
+ style="stop-color:#ff6600;stop-opacity:1;"
+ offset="0"
+ id="stop3828" />
+ <stop
+ style="stop-color:#ff893b;stop-opacity:1;"
+ offset="1"
+ id="stop3830" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879-6">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.90598291;"
+ offset="0"
+ id="stop3881-4" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-7" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3869-5">
+ <stop
+ style="stop-color:#c95000;stop-opacity:1;"
+ offset="0"
+ id="stop3871-9" />
+ <stop
+ style="stop-color:#ff9e5e;stop-opacity:1;"
+ offset="1"
+ id="stop3873-4" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879-4"
+ id="linearGradient3885-6"
+ x1="76.025352"
+ y1="124.8497"
+ x2="75.874107"
+ y2="143.03978"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3879-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3869-2">
+ <stop
+ style="stop-color:#c95000;stop-opacity:1;"
+ offset="0"
+ id="stop3871-99" />
+ <stop
+ style="stop-color:#ff9e5e;stop-opacity:1;"
+ offset="1"
+ id="stop3873-6" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011"
+ id="radialGradient4017"
+ cx="66.639"
+ cy="93.096375"
+ fx="66.639"
+ fy="93.096375"
+ r="11.515625"
+ gradientTransform="matrix(0.23244854,1.600893,-1.0124495,0.14700695,145.40424,-26.300303)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879-4-7"
+ id="linearGradient3885-6-2"
+ x1="76.025352"
+ y1="124.8497"
+ x2="75.874107"
+ y2="143.03978"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3879-4-7">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-7" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-6" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011-5"
+ id="radialGradient4017-7"
+ cx="66.639"
+ cy="93.096375"
+ fx="66.639"
+ fy="93.096375"
+ r="11.515625"
+ gradientTransform="matrix(0.99779178,6.8718773,-4.3459674,0.6310314,452.75975,-225.98471)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4011-5">
+ <stop
+ style="stop-color:#042dc8;stop-opacity:1;"
+ offset="0"
+ id="stop4013-1" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015-3" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879-4-75"
+ id="linearGradient3885-6-8"
+ x1="76.025352"
+ y1="124.8497"
+ x2="75.874107"
+ y2="143.03978"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3879-4-75">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-4" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011-0"
+ id="radialGradient4017-5"
+ cx="66.639"
+ cy="93.096375"
+ fx="66.639"
+ fy="93.096375"
+ r="11.515625"
+ gradientTransform="matrix(0.23244854,1.600893,-1.0124495,0.14700695,146.34996,53.681728)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4011-0">
+ <stop
+ style="stop-color:#042dc8;stop-opacity:1;"
+ offset="0"
+ id="stop4013-4" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015-0" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011-0"
+ id="linearGradient4117"
+ x1="107.03001"
+ y1="189.72537"
+ x2="107.18476"
+ y2="173.47537"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879-4-7-2"
+ id="linearGradient3885-6-2-8"
+ x1="76.025352"
+ y1="124.8497"
+ x2="75.874107"
+ y2="143.03978"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3879-4-7-2">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-7-9" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-6-9" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011-5-1"
+ id="radialGradient4017-7-9"
+ cx="66.639"
+ cy="93.096375"
+ fx="66.639"
+ fy="93.096375"
+ r="11.515625"
+ gradientTransform="matrix(0.99779178,6.8718773,-4.3459674,0.6310314,448.94742,-406.99277)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4011-5-1">
+ <stop
+ style="stop-color:#042dc8;stop-opacity:1;"
+ offset="0"
+ id="stop4013-1-9" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015-3-8" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879-4-7-2-7"
+ id="linearGradient3885-6-2-8-0"
+ x1="76.025352"
+ y1="124.8497"
+ x2="75.874107"
+ y2="143.03978"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3879-4-7-2-7">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-7-9-3" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-6-9-6" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011-5-1-5"
+ id="radialGradient4017-7-9-5"
+ cx="66.639"
+ cy="93.096375"
+ fx="66.639"
+ fy="93.096375"
+ r="11.515625"
+ gradientTransform="matrix(0.55965334,3.8543806,-2.4376181,0.3539404,454.75182,-145.44353)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4011-5-1-5">
+ <stop
+ style="stop-color:#042dc8;stop-opacity:1;"
+ offset="0"
+ id="stop4013-1-9-6" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015-3-8-9" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient3879-4-7-2-4"
+ id="linearGradient3885-6-2-8-4"
+ x1="76.025352"
+ y1="124.8497"
+ x2="75.874107"
+ y2="143.03978"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient3879-4-7-2-4">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-7-9-9" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-6-9-3" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4011-5-1-7"
+ id="radialGradient4017-7-9-7"
+ cx="66.639"
+ cy="93.096375"
+ fx="66.639"
+ fy="93.096375"
+ r="11.515625"
+ gradientTransform="matrix(0.26837158,1.8482981,-1.1689154,0.16972569,466.57614,26.180822)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4011-5-1-7">
+ <stop
+ style="stop-color:#042dc8;stop-opacity:1;"
+ offset="0"
+ id="stop4013-1-9-1" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015-3-8-5" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879-4-7-2-0">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-7-9-7" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-6-9-8" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4011-5-1-55">
+ <stop
+ style="stop-color:#000a30;stop-opacity:1;"
+ offset="0"
+ id="stop4013-1-9-8" />
+ <stop
+ style="stop-color:#4260d5;stop-opacity:1;"
+ offset="1"
+ id="stop4015-3-8-3" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3890-9">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3892-0" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3894-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3880-4">
+ <stop
+ style="stop-color:#eb7400;stop-opacity:1;"
+ offset="0"
+ id="stop3882-5" />
+ <stop
+ style="stop-color:#f7b06a;stop-opacity:1;"
+ offset="1"
+ id="stop3884-1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999-7">
+ <stop
+ style="stop-color:#fff7f2;stop-opacity:1;"
+ offset="0"
+ id="stop4001-9" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4003-4" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4007-9">
+ <stop
+ style="stop-color:#ff6600;stop-opacity:1;"
+ offset="0"
+ id="stop4009-1" />
+ <stop
+ style="stop-color:#ff9148;stop-opacity:1;"
+ offset="1"
+ id="stop4011-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4007-9-5">
+ <stop
+ style="stop-color:#ff6600;stop-opacity:1;"
+ offset="0"
+ id="stop4009-1-9" />
+ <stop
+ style="stop-color:#ff9148;stop-opacity:1;"
+ offset="1"
+ id="stop4011-9-5" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999-7-1">
+ <stop
+ style="stop-color:#fff7f2;stop-opacity:1;"
+ offset="0"
+ id="stop4001-9-1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4003-4-4" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4007-9-5-3">
+ <stop
+ style="stop-color:#ff6600;stop-opacity:1;"
+ offset="0"
+ id="stop4009-1-9-3" />
+ <stop
+ style="stop-color:#ff9148;stop-opacity:1;"
+ offset="1"
+ id="stop4011-9-5-9" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3999-7-1-4">
+ <stop
+ style="stop-color:#fff7f2;stop-opacity:1;"
+ offset="0"
+ id="stop4001-9-1-4" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4003-4-4-4" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3879-4-7-2-3">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.93162394;"
+ offset="0"
+ id="stop3881-6-7-9-1" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop3883-74-6-9-87" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4011-5-1-1">
+ <stop
+ style="stop-color:#fde8a1;stop-opacity:1;"
+ offset="0"
+ id="stop4013-1-9-63" />
+ <stop
+ style="stop-color:#2947b9;stop-opacity:1;"
+ offset="1"
+ id="stop4015-3-8-8" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4466"
+ id="linearGradient4472"
+ x1="161.7561"
+ y1="540.72662"
+ x2="161.7561"
+ y2="579.80206"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4321"
+ id="radialGradient4474"
+ cx="130.8242"
+ cy="575.27838"
+ fx="130.8242"
+ fy="575.27838"
+ r="49.498173"
+ gradientTransform="matrix(0.95670828,0.96684666,-0.72623533,0.71862001,423.45109,35.05138)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4466-5"
+ id="linearGradient4472-9"
+ x1="161.7561"
+ y1="540.72662"
+ x2="161.7561"
+ y2="579.80206"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4466-5">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4468-2" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4470-3" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4321-0"
+ id="radialGradient4474-6"
+ cx="130.8242"
+ cy="575.27838"
+ fx="130.8242"
+ fy="575.27838"
+ r="49.498173"
+ gradientTransform="matrix(0.95670828,0.96684666,-0.72623533,0.71862001,442.64399,170.9169)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4321-0">
+ <stop
+ style="stop-color:#ff6702;stop-opacity:1;"
+ offset="0"
+ id="stop4323-3" />
+ <stop
+ style="stop-color:#ff9a55;stop-opacity:1;"
+ offset="1"
+ id="stop4325-1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4466-5-5">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4468-2-9" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4470-3-4" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4321-0-0">
+ <stop
+ style="stop-color:#ff6702;stop-opacity:1;"
+ offset="0"
+ id="stop4323-3-9" />
+ <stop
+ style="stop-color:#ff9a55;stop-opacity:1;"
+ offset="1"
+ id="stop4325-1-1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4466-5-9">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4468-2-7" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4470-3-7" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4321-0-7">
+ <stop
+ style="stop-color:#ff6702;stop-opacity:1;"
+ offset="0"
+ id="stop4323-3-3" />
+ <stop
+ style="stop-color:#ff9a55;stop-opacity:1;"
+ offset="1"
+ id="stop4325-1-6" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4431"
+ id="linearGradient4437"
+ x1="142.81854"
+ y1="831.52283"
+ x2="142.81854"
+ y2="878.90735"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4467"
+ id="radialGradient4475"
+ cx="116.51958"
+ cy="98.282051"
+ fx="116.51958"
+ fy="98.282051"
+ r="55.859375"
+ gradientTransform="matrix(0.97442557,1.5088911,-0.83559154,0.53961599,79.641615,-130.28522)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4431-3"
+ id="linearGradient4437-6"
+ x1="142.81854"
+ y1="831.52283"
+ x2="142.81854"
+ y2="878.90735"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4431-3">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop4433-0" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0;"
+ offset="1"
+ id="stop4435-2" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4467-7"
+ id="radialGradient4475-0"
+ cx="116.51958"
+ cy="98.282051"
+ fx="116.51958"
+ fy="98.282051"
+ r="55.859375"
+ gradientTransform="matrix(0.97442557,1.5088911,-0.83559154,0.53961599,225.10358,63.664066)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4467-7">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4469-4" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0.8974359;"
+ offset="1"
+ id="stop4471-7" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4467"
+ id="radialGradient3262"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.97442557,1.5088911,-0.83559154,0.53961599,59.641615,-150.28522)"
+ cx="116.51958"
+ cy="98.282051"
+ fx="116.51958"
+ fy="98.282051"
+ r="55.859375" />
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4431"
+ id="linearGradient3264"
+ gradientUnits="userSpaceOnUse"
+ x1="142.81854"
+ y1="831.52283"
+ x2="142.81854"
+ y2="878.90735" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6"
+ inkscape:cx="31.144191"
+ inkscape:cy="38.335716"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ showguides="false"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1056"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0"
+ fit-margin-left="0">
+ <sodipodi:guide
+ orientation="0,1"
+ position="72.563745,37.346999"
+ id="guide2989" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="74.584055,7.2949693"
+ id="guide2991" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="71.048515,20.426949"
+ id="guide2993" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="97.817565,20.174409"
+ id="guide2995" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="71.048515,20.426949"
+ id="guide3017" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid3019"
+ empspacing="5"
+ visible="true"
+ enabled="true"
+ snapvisiblegridlinesonly="true" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="105.6589,-12.377861"
+ id="guide3021" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="126.6589,-16.377861"
+ id="guide3023" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="110.6589,-3.3778607"
+ id="guide3025" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="110.6589,-27.377861"
+ id="guide3027" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="19.658895,-35.37786"
+ id="guide3810" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="21.658895,-70.377861"
+ id="guide3814" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="2.301752,-9.4850007"
+ id="guide3856" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="26.601806,-9.4850007"
+ id="guide3887" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="44.658283,37.346999"
+ id="guide4019" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="126.6589,-27.377861"
+ id="guide4481" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="159.08747,-213.94929"
+ id="guide4483" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-42.341105,-35.859333)">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:url(#radialGradient3262);fill-opacity:1;stroke:none"
+ d="m 98.2161,35.859333 c -30.850815,0 -55.874995,24.87043 -55.874995,55.562497 0,30.69207 25.02418,55.56249 55.874995,55.56249 10.09496,0 19.54625,-2.6525 27.71875,-7.3125 l 2.90625,7.3125 2.40625,0 20,0 0.125,0 -8.8125,-21.78124 c 7.21537,-9.3622 11.5,-21.07236 11.5,-33.78125 0,-30.692067 -24.99293,-55.562497 -55.84375,-55.562497 z"
+ id="path3834-7-7-2-5-5-0-5-4" />
+ <path
+ sodipodi:type="arc"
+ style="fill:url(#linearGradient3264);fill-opacity:1;stroke:none"
+ id="path3661"
+ sodipodi:cx="142.5"
+ sodipodi:cy="856.29077"
+ sodipodi:rx="35.357143"
+ sodipodi:ry="24.642857"
+ d="m 177.85714,856.29077 c 0,13.60988 -15.82993,24.64286 -35.35714,24.64286 -19.52721,0 -35.35714,-11.03298 -35.35714,-24.64286 0,-13.60987 15.82993,-24.64286 35.35714,-24.64286 19.52721,0 35.35714,11.03299 35.35714,24.64286 z"
+ transform="matrix(1.0465082,0,0,1.2920463,-51.641235,-1036.8612)" />
+ <path
+ sodipodi:type="arc"
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="path4442"
+ sodipodi:cx="115.66247"
+ sodipodi:cy="856.39258"
+ sodipodi:rx="6.5659914"
+ sodipodi:ry="6.5659914"
+ d="m 122.22846,856.39258 c 0,3.6263 -2.9397,6.56599 -6.56599,6.56599 -3.6263,0 -6.56599,-2.93969 -6.56599,-6.56599 0,-3.6263 2.93969,-6.56599 6.56599,-6.56599 3.62629,0 6.56599,2.93969 6.56599,6.56599 z"
+ transform="translate(-12.329975,-797.60351)" />
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="rect4444"
+ width="37.643608"
+ height="5.5005069"
+ x="101.55376"
+ y="48.297417"
+ transform="matrix(0.98974903,0.14281759,-0.18972639,0.981837,0,0)" />
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:none"
+ id="rect4446"
+ width="6.5659914"
+ height="2.9041886"
+ x="124.92451"
+ y="69.016899" />
+ <path
+ style="fill:#ff6600;fill-opacity:1"
+ d="m 83.38797,45.010543 c -0.057,2.18531 -3.865755,0.28296 -4.031245,2.78125 -4.22387,-1.88052 0.32884,2.87188 -0.0937,3.3125 l -0.0312,0 -0.3125,-0.0312 c -0.20386,-0.0728 -0.49977,-0.19904 -0.9375,-0.46875 -2.9499,2.35025 -3.02157,7.23369 -6.0625,9.9375 -1.99467,4.30504 -2.47977,8.98337 -3.9375,13.46875 -0.71796,4.30292 -1.34881,8.597857 -0.28125,12.906247 0.32053,3.50159 -0.68919,8.25865 2.5,10.71875 4.72728,3.88304 8.65575,8.79543 12.624995,13.46875 6.21914,7.65333 11.72948,15.86251 16.59375,24.4375 0.32431,-2.11756 1.10954,4.26459 2.53125,4.6875 -0.49161,-3.19231 -1.13213,-8.26328 -1.4375,-12.1875 -1.5814,-10.2909 -6.65305,-19.64903 -8.5625,-29.84375 -0.0587,-0.43037 -0.12809,-0.87203 -0.1875,-1.3125 l 0,-1.28125 -0.15625,0 c -0.62551,-5.04297 -0.8504,-10.46546 2.8125,-14.40625 3.73968,-3.772097 9.30633,-4.722447 13.8125,-7.343747 1.00194,-0.59119 2.04921,-1.07174 3.125,-1.40625 0.009,-0.003 0.0228,0.003 0.0312,0 3.11701,-0.96341 6.44862,-0.93323 9.6875,-0.40625 0.0479,0.008 0.10841,0.0233 0.15625,0.0312 0.29455,0.0493 0.61389,0.099 0.90625,0.15625 2.37136,0.21133 7.14463,1.13687 8,-0.5 -3.27225,-2.78631 -7.98526,-2.59211 -11.96875,-3.6875 -0.63059,-0.11469 -1.41182,-0.24041 -2.1875,-0.3125 l -3.90625,-0.875 -0.96875,-0.25 0,0.0312 -13.96875,-2.71875 c -0.22212,-0.20226 -0.46434,-0.40933 -0.6875,-0.5625 l 13.625,1.6875 0,-0.0625 c 0.48011,0.10699 0.95576,0.19361 1.4375,0.25 l 0,0.0312 9.625,1.78125 c 1.66103,0.61952 3.4322,1.08374 5.09375,1.1875 2.74263,0.39907 6.22526,4.49092 7.125,4.6875 -0.44096,-4.307 -4.7422,-6.23586 -8.3125,-7.5 -4.1712,-2.02803 -10.4023,-1.95417 -11.0625,-7.5625 -0.1756,-0.39076 -0.34902,-0.78118 -0.5625,-1.15625 l -1.625,-2.15625 0.0625,-0.0312 c -2.21724,-2.61691 -5.34011,-4.52196 -8.65625,-5.25 -3.2914,-1.13611 -6.98773,-2.2671 -10.46875,-2.71875 -1.18132,3.47826 -2.5031,-2.75561 -5.34375,-0.90625 -2.48996,0.29488 -2.14614,0.95256 -4,-0.625 z m 17.90625,10.15625 c 0.90187,-0.0238 1.93277,0.14208 2.96875,0.5 2.76259,0.95447 4.56151,2.96523 4.03125,4.5 -0.53026,1.53477 -3.20616,1.98572 -5.96875,1.03125 -2.76259,-0.95447 -4.5615,-2.93398 -4.03125,-4.46875 0.33141,-0.95923 1.49689,-1.52281 3,-1.5625 z"
+ id="path3499-9-7"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/ui/icons/qemu_128x128.png b/ui/icons/qemu_128x128.png
new file mode 100644
index 000000000..96831807b
--- /dev/null
+++ b/ui/icons/qemu_128x128.png
Binary files differ
diff --git a/ui/icons/qemu_16x16.png b/ui/icons/qemu_16x16.png
new file mode 100644
index 000000000..ff4f04602
--- /dev/null
+++ b/ui/icons/qemu_16x16.png
Binary files differ
diff --git a/ui/icons/qemu_24x24.png b/ui/icons/qemu_24x24.png
new file mode 100644
index 000000000..f039c6e25
--- /dev/null
+++ b/ui/icons/qemu_24x24.png
Binary files differ
diff --git a/ui/icons/qemu_256x256.png b/ui/icons/qemu_256x256.png
new file mode 100644
index 000000000..a39c0e307
--- /dev/null
+++ b/ui/icons/qemu_256x256.png
Binary files differ
diff --git a/ui/icons/qemu_32x32.bmp b/ui/icons/qemu_32x32.bmp
new file mode 100644
index 000000000..c0daa54ab
--- /dev/null
+++ b/ui/icons/qemu_32x32.bmp
Binary files differ
diff --git a/ui/icons/qemu_32x32.png b/ui/icons/qemu_32x32.png
new file mode 100644
index 000000000..b746096cf
--- /dev/null
+++ b/ui/icons/qemu_32x32.png
Binary files differ
diff --git a/ui/icons/qemu_48x48.png b/ui/icons/qemu_48x48.png
new file mode 100644
index 000000000..067281225
--- /dev/null
+++ b/ui/icons/qemu_48x48.png
Binary files differ
diff --git a/ui/icons/qemu_512x512.png b/ui/icons/qemu_512x512.png
new file mode 100644
index 000000000..86aaa6395
--- /dev/null
+++ b/ui/icons/qemu_512x512.png
Binary files differ
diff --git a/ui/icons/qemu_64x64.png b/ui/icons/qemu_64x64.png
new file mode 100644
index 000000000..e00c8b4c9
--- /dev/null
+++ b/ui/icons/qemu_64x64.png
Binary files differ
diff --git a/ui/input-barrier.c b/ui/input-barrier.c
new file mode 100644
index 000000000..2d57ca707
--- /dev/null
+++ b/ui/input-barrier.c
@@ -0,0 +1,746 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * TODO:
+ *
+ * - Enable SSL
+ * - Manage SetOptions/ResetOptions commands
+ */
+
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "qemu/main-loop.h"
+#include "qemu/sockets.h"
+#include "qapi/error.h"
+#include "qom/object_interfaces.h"
+#include "io/channel-socket.h"
+#include "ui/input.h"
+#include "qom/object.h"
+#include "ui/vnc_keysym.h" /* use name2keysym from VNC as we use X11 values */
+#include "qemu/cutils.h"
+#include "qapi/qmp/qerror.h"
+#include "input-barrier.h"
+
+#define TYPE_INPUT_BARRIER "input-barrier"
+OBJECT_DECLARE_SIMPLE_TYPE(InputBarrier,
+ INPUT_BARRIER)
+
+
+#define MAX_HELLO_LENGTH 1024
+
+struct InputBarrier {
+ Object parent;
+
+ QIOChannelSocket *sioc;
+ guint ioc_tag;
+
+ /* display properties */
+ gchar *name;
+ int16_t x_origin, y_origin;
+ int16_t width, height;
+
+ /* keyboard/mouse server */
+
+ SocketAddress saddr;
+
+ char buffer[MAX_HELLO_LENGTH];
+};
+
+
+static const char *cmd_names[] = {
+ [barrierCmdCNoop] = "CNOP",
+ [barrierCmdCClose] = "CBYE",
+ [barrierCmdCEnter] = "CINN",
+ [barrierCmdCLeave] = "COUT",
+ [barrierCmdCClipboard] = "CCLP",
+ [barrierCmdCScreenSaver] = "CSEC",
+ [barrierCmdCResetOptions] = "CROP",
+ [barrierCmdCInfoAck] = "CIAK",
+ [barrierCmdCKeepAlive] = "CALV",
+ [barrierCmdDKeyDown] = "DKDN",
+ [barrierCmdDKeyRepeat] = "DKRP",
+ [barrierCmdDKeyUp] = "DKUP",
+ [barrierCmdDMouseDown] = "DMDN",
+ [barrierCmdDMouseUp] = "DMUP",
+ [barrierCmdDMouseMove] = "DMMV",
+ [barrierCmdDMouseRelMove] = "DMRM",
+ [barrierCmdDMouseWheel] = "DMWM",
+ [barrierCmdDClipboard] = "DCLP",
+ [barrierCmdDInfo] = "DINF",
+ [barrierCmdDSetOptions] = "DSOP",
+ [barrierCmdDFileTransfer] = "DFTR",
+ [barrierCmdDDragInfo] = "DDRG",
+ [barrierCmdQInfo] = "QINF",
+ [barrierCmdEIncompatible] = "EICV",
+ [barrierCmdEBusy] = "EBSY",
+ [barrierCmdEUnknown] = "EUNK",
+ [barrierCmdEBad] = "EBAD",
+ [barrierCmdHello] = "Barrier",
+ [barrierCmdHelloBack] = "Barrier",
+};
+
+static kbd_layout_t *kbd_layout;
+
+static int input_barrier_to_qcode(uint16_t keyid, uint16_t keycode)
+{
+ /* keycode is optional, if it is not provided use keyid */
+ if (keycode && keycode <= qemu_input_map_xorgkbd_to_qcode_len) {
+ return qemu_input_map_xorgkbd_to_qcode[keycode];
+ }
+
+ if (keyid >= 0xE000 && keyid <= 0xEFFF) {
+ keyid += 0x1000;
+ }
+
+ /* keyid is the X11 key id */
+ if (kbd_layout) {
+ keycode = keysym2scancode(kbd_layout, keyid, NULL, false);
+
+ return qemu_input_key_number_to_qcode(keycode);
+ }
+
+ return qemu_input_map_x11_to_qcode[keyid];
+}
+
+static int input_barrier_to_mouse(uint8_t buttonid)
+{
+ switch (buttonid) {
+ case barrierButtonLeft:
+ return INPUT_BUTTON_LEFT;
+ case barrierButtonMiddle:
+ return INPUT_BUTTON_MIDDLE;
+ case barrierButtonRight:
+ return INPUT_BUTTON_RIGHT;
+ case barrierButtonExtra0:
+ return INPUT_BUTTON_SIDE;
+ }
+ return buttonid;
+}
+
+#define read_char(x, p, l) \
+do { \
+ int size = sizeof(char); \
+ if (l < size) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ x = *(char *)p; \
+ p += size; \
+ l -= size; \
+} while (0)
+
+#define read_short(x, p, l) \
+do { \
+ int size = sizeof(short); \
+ if (l < size) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ x = ntohs(*(short *)p); \
+ p += size; \
+ l -= size; \
+} while (0)
+
+#define write_short(p, x, l) \
+do { \
+ int size = sizeof(short); \
+ if (l < size) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ *(short *)p = htons(x); \
+ p += size; \
+ l -= size; \
+} while (0)
+
+#define read_int(x, p, l) \
+do { \
+ int size = sizeof(int); \
+ if (l < size) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ x = ntohl(*(int *)p); \
+ p += size; \
+ l -= size; \
+} while (0)
+
+#define write_int(p, x, l) \
+do { \
+ int size = sizeof(int); \
+ if (l < size) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ *(int *)p = htonl(x); \
+ p += size; \
+ l -= size; \
+} while (0)
+
+#define write_cmd(p, c, l) \
+do { \
+ int size = strlen(cmd_names[c]); \
+ if (l < size) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ memcpy(p, cmd_names[c], size); \
+ p += size; \
+ l -= size; \
+} while (0)
+
+#define write_string(p, s, l) \
+do { \
+ int size = strlen(s); \
+ if (l < size + sizeof(int)) { \
+ return G_SOURCE_REMOVE; \
+ } \
+ *(int *)p = htonl(size); \
+ p += sizeof(size); \
+ l -= sizeof(size); \
+ memcpy(p, s, size); \
+ p += size; \
+ l -= size; \
+} while (0)
+
+static gboolean readcmd(InputBarrier *ib, struct barrierMsg *msg)
+{
+ int ret, len, i;
+ enum barrierCmd cmd;
+ char *p;
+
+ ret = qio_channel_read(QIO_CHANNEL(ib->sioc), (char *)&len, sizeof(len),
+ NULL);
+ if (ret < 0) {
+ return G_SOURCE_REMOVE;
+ }
+
+ len = ntohl(len);
+ if (len > MAX_HELLO_LENGTH) {
+ return G_SOURCE_REMOVE;
+ }
+
+ ret = qio_channel_read(QIO_CHANNEL(ib->sioc), ib->buffer, len, NULL);
+ if (ret < 0) {
+ return G_SOURCE_REMOVE;
+ }
+
+ p = ib->buffer;
+ if (len >= strlen(cmd_names[barrierCmdHello]) &&
+ memcmp(p, cmd_names[barrierCmdHello],
+ strlen(cmd_names[barrierCmdHello])) == 0) {
+ cmd = barrierCmdHello;
+ p += strlen(cmd_names[barrierCmdHello]);
+ len -= strlen(cmd_names[barrierCmdHello]);
+ } else {
+ for (cmd = 0; cmd < barrierCmdHello; cmd++) {
+ if (memcmp(ib->buffer, cmd_names[cmd], 4) == 0) {
+ break;
+ }
+ }
+
+ if (cmd == barrierCmdHello) {
+ return G_SOURCE_REMOVE;
+ }
+ p += 4;
+ len -= 4;
+ }
+
+ msg->cmd = cmd;
+ switch (cmd) {
+ /* connection */
+ case barrierCmdHello:
+ read_short(msg->version.major, p, len);
+ read_short(msg->version.minor, p, len);
+ break;
+ case barrierCmdDSetOptions:
+ read_int(msg->set.nb, p, len);
+ msg->set.nb /= 2;
+ if (msg->set.nb > BARRIER_MAX_OPTIONS) {
+ msg->set.nb = BARRIER_MAX_OPTIONS;
+ }
+ i = 0;
+ while (len && i < msg->set.nb) {
+ read_int(msg->set.option[i].id, p, len);
+ /* it's a string, restore endianness */
+ msg->set.option[i].id = htonl(msg->set.option[i].id);
+ msg->set.option[i].nul = 0;
+ read_int(msg->set.option[i].value, p, len);
+ i++;
+ }
+ break;
+ case barrierCmdQInfo:
+ break;
+
+ /* mouse */
+ case barrierCmdDMouseMove:
+ case barrierCmdDMouseRelMove:
+ read_short(msg->mousepos.x, p, len);
+ read_short(msg->mousepos.y, p, len);
+ break;
+ case barrierCmdDMouseDown:
+ case barrierCmdDMouseUp:
+ read_char(msg->mousebutton.buttonid, p, len);
+ break;
+ case barrierCmdDMouseWheel:
+ read_short(msg->mousepos.y, p, len);
+ msg->mousepos.x = 0;
+ if (len) {
+ msg->mousepos.x = msg->mousepos.y;
+ read_short(msg->mousepos.y, p, len);
+ }
+ break;
+
+ /* keyboard */
+ case barrierCmdDKeyDown:
+ case barrierCmdDKeyUp:
+ read_short(msg->key.keyid, p, len);
+ read_short(msg->key.modifier, p, len);
+ msg->key.button = 0;
+ if (len) {
+ read_short(msg->key.button, p, len);
+ }
+ break;
+ case barrierCmdDKeyRepeat:
+ read_short(msg->repeat.keyid, p, len);
+ read_short(msg->repeat.modifier, p, len);
+ read_short(msg->repeat.repeat, p, len);
+ msg->repeat.button = 0;
+ if (len) {
+ read_short(msg->repeat.button, p, len);
+ }
+ break;
+ case barrierCmdCInfoAck:
+ case barrierCmdCResetOptions:
+ case barrierCmdCEnter:
+ case barrierCmdDClipboard:
+ case barrierCmdCKeepAlive:
+ case barrierCmdCLeave:
+ case barrierCmdCClose:
+ break;
+
+ /* Invalid from the server */
+ case barrierCmdHelloBack:
+ case barrierCmdCNoop:
+ case barrierCmdDInfo:
+ break;
+
+ /* Error codes */
+ case barrierCmdEIncompatible:
+ read_short(msg->version.major, p, len);
+ read_short(msg->version.minor, p, len);
+ break;
+ case barrierCmdEBusy:
+ case barrierCmdEUnknown:
+ case barrierCmdEBad:
+ break;
+ default:
+ return G_SOURCE_REMOVE;
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean writecmd(InputBarrier *ib, struct barrierMsg *msg)
+{
+ char *p;
+ int ret, i;
+ int avail, len;
+
+ p = ib->buffer;
+ avail = MAX_HELLO_LENGTH;
+
+ /* reserve space to store the length */
+ p += sizeof(int);
+ avail -= sizeof(int);
+
+ switch (msg->cmd) {
+ case barrierCmdHello:
+ if (msg->version.major < BARRIER_VERSION_MAJOR ||
+ (msg->version.major == BARRIER_VERSION_MAJOR &&
+ msg->version.minor < BARRIER_VERSION_MINOR)) {
+ ib->ioc_tag = 0;
+ return G_SOURCE_REMOVE;
+ }
+ write_cmd(p, barrierCmdHelloBack, avail);
+ write_short(p, BARRIER_VERSION_MAJOR, avail);
+ write_short(p, BARRIER_VERSION_MINOR, avail);
+ write_string(p, ib->name, avail);
+ break;
+ case barrierCmdCClose:
+ ib->ioc_tag = 0;
+ return G_SOURCE_REMOVE;
+ case barrierCmdQInfo:
+ write_cmd(p, barrierCmdDInfo, avail);
+ write_short(p, ib->x_origin, avail);
+ write_short(p, ib->y_origin, avail);
+ write_short(p, ib->width, avail);
+ write_short(p, ib->height, avail);
+ write_short(p, 0, avail); /* warpsize (obsolete) */
+ write_short(p, 0, avail); /* mouse x */
+ write_short(p, 0, avail); /* mouse y */
+ break;
+ case barrierCmdCInfoAck:
+ break;
+ case barrierCmdCResetOptions:
+ /* TODO: reset options */
+ break;
+ case barrierCmdDSetOptions:
+ /* TODO: set options */
+ break;
+ case barrierCmdCEnter:
+ break;
+ case barrierCmdDClipboard:
+ break;
+ case barrierCmdCKeepAlive:
+ write_cmd(p, barrierCmdCKeepAlive, avail);
+ break;
+ case barrierCmdCLeave:
+ break;
+
+ /* mouse */
+ case barrierCmdDMouseMove:
+ qemu_input_queue_abs(NULL, INPUT_AXIS_X, msg->mousepos.x,
+ ib->x_origin, ib->width);
+ qemu_input_queue_abs(NULL, INPUT_AXIS_Y, msg->mousepos.y,
+ ib->y_origin, ib->height);
+ qemu_input_event_sync();
+ break;
+ case barrierCmdDMouseRelMove:
+ qemu_input_queue_rel(NULL, INPUT_AXIS_X, msg->mousepos.x);
+ qemu_input_queue_rel(NULL, INPUT_AXIS_Y, msg->mousepos.y);
+ qemu_input_event_sync();
+ break;
+ case barrierCmdDMouseDown:
+ qemu_input_queue_btn(NULL,
+ input_barrier_to_mouse(msg->mousebutton.buttonid),
+ true);
+ qemu_input_event_sync();
+ break;
+ case barrierCmdDMouseUp:
+ qemu_input_queue_btn(NULL,
+ input_barrier_to_mouse(msg->mousebutton.buttonid),
+ false);
+ qemu_input_event_sync();
+ break;
+ case barrierCmdDMouseWheel:
+ qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP
+ : INPUT_BUTTON_WHEEL_DOWN, true);
+ qemu_input_event_sync();
+ qemu_input_queue_btn(NULL, (msg->mousepos.y > 0) ? INPUT_BUTTON_WHEEL_UP
+ : INPUT_BUTTON_WHEEL_DOWN, false);
+ qemu_input_event_sync();
+ break;
+
+ /* keyboard */
+ case barrierCmdDKeyDown:
+ qemu_input_event_send_key_qcode(NULL,
+ input_barrier_to_qcode(msg->key.keyid, msg->key.button),
+ true);
+ break;
+ case barrierCmdDKeyRepeat:
+ for (i = 0; i < msg->repeat.repeat; i++) {
+ qemu_input_event_send_key_qcode(NULL,
+ input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button),
+ false);
+ qemu_input_event_send_key_qcode(NULL,
+ input_barrier_to_qcode(msg->repeat.keyid, msg->repeat.button),
+ true);
+ }
+ break;
+ case barrierCmdDKeyUp:
+ qemu_input_event_send_key_qcode(NULL,
+ input_barrier_to_qcode(msg->key.keyid, msg->key.button),
+ false);
+ break;
+ default:
+ write_cmd(p, barrierCmdEUnknown, avail);
+ break;
+ }
+
+ len = MAX_HELLO_LENGTH - avail - sizeof(int);
+ if (len) {
+ p = ib->buffer;
+ avail = sizeof(len);
+ write_int(p, len, avail);
+ ret = qio_channel_write(QIO_CHANNEL(ib->sioc), ib->buffer,
+ len + sizeof(len), NULL);
+ if (ret < 0) {
+ ib->ioc_tag = 0;
+ return G_SOURCE_REMOVE;
+ }
+ }
+
+ return G_SOURCE_CONTINUE;
+}
+
+static gboolean input_barrier_event(QIOChannel *ioc G_GNUC_UNUSED,
+ GIOCondition condition, void *opaque)
+{
+ InputBarrier *ib = opaque;
+ int ret;
+ struct barrierMsg msg;
+
+ ret = readcmd(ib, &msg);
+ if (ret == G_SOURCE_REMOVE) {
+ ib->ioc_tag = 0;
+ return G_SOURCE_REMOVE;
+ }
+
+ return writecmd(ib, &msg);
+}
+
+static void input_barrier_complete(UserCreatable *uc, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(uc);
+ Error *local_err = NULL;
+
+ if (!ib->name) {
+ error_setg(errp, QERR_MISSING_PARAMETER, "name");
+ return;
+ }
+
+ /*
+ * Connect to the primary
+ * Primary is the server where the keyboard and the mouse
+ * are connected and forwarded to the secondary (the client)
+ */
+
+ ib->sioc = qio_channel_socket_new();
+ qio_channel_set_name(QIO_CHANNEL(ib->sioc), "barrier-client");
+
+ qio_channel_socket_connect_sync(ib->sioc, &ib->saddr, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return;
+ }
+
+ qio_channel_set_delay(QIO_CHANNEL(ib->sioc), false);
+
+ ib->ioc_tag = qio_channel_add_watch(QIO_CHANNEL(ib->sioc), G_IO_IN,
+ input_barrier_event, ib, NULL);
+}
+
+static void input_barrier_instance_finalize(Object *obj)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ if (ib->ioc_tag) {
+ g_source_remove(ib->ioc_tag);
+ ib->ioc_tag = 0;
+ }
+
+ if (ib->sioc) {
+ qio_channel_close(QIO_CHANNEL(ib->sioc), NULL);
+ object_unref(OBJECT(ib->sioc));
+ }
+ g_free(ib->name);
+ g_free(ib->saddr.u.inet.host);
+ g_free(ib->saddr.u.inet.port);
+}
+
+static char *input_barrier_get_name(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup(ib->name);
+}
+
+static void input_barrier_set_name(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ if (ib->name) {
+ error_setg(errp, "name property already set");
+ return;
+ }
+ ib->name = g_strdup(value);
+}
+
+static char *input_barrier_get_server(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup(ib->saddr.u.inet.host);
+}
+
+static void input_barrier_set_server(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ g_free(ib->saddr.u.inet.host);
+ ib->saddr.u.inet.host = g_strdup(value);
+}
+
+static char *input_barrier_get_port(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup(ib->saddr.u.inet.port);
+}
+
+static void input_barrier_set_port(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ g_free(ib->saddr.u.inet.port);
+ ib->saddr.u.inet.port = g_strdup(value);
+}
+
+static void input_barrier_set_x_origin(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+ int result, err;
+
+ err = qemu_strtoi(value, NULL, 0, &result);
+ if (err < 0 || result < 0 || result > SHRT_MAX) {
+ error_setg(errp,
+ "x-origin property must be in the range [0..%d]", SHRT_MAX);
+ return;
+ }
+ ib->x_origin = result;
+}
+
+static char *input_barrier_get_x_origin(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup_printf("%d", ib->x_origin);
+}
+
+static void input_barrier_set_y_origin(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+ int result, err;
+
+ err = qemu_strtoi(value, NULL, 0, &result);
+ if (err < 0 || result < 0 || result > SHRT_MAX) {
+ error_setg(errp,
+ "y-origin property must be in the range [0..%d]", SHRT_MAX);
+ return;
+ }
+ ib->y_origin = result;
+}
+
+static char *input_barrier_get_y_origin(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup_printf("%d", ib->y_origin);
+}
+
+static void input_barrier_set_width(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+ int result, err;
+
+ err = qemu_strtoi(value, NULL, 0, &result);
+ if (err < 0 || result < 0 || result > SHRT_MAX) {
+ error_setg(errp,
+ "width property must be in the range [0..%d]", SHRT_MAX);
+ return;
+ }
+ ib->width = result;
+}
+
+static char *input_barrier_get_width(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup_printf("%d", ib->width);
+}
+
+static void input_barrier_set_height(Object *obj, const char *value,
+ Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+ int result, err;
+
+ err = qemu_strtoi(value, NULL, 0, &result);
+ if (err < 0 || result < 0 || result > SHRT_MAX) {
+ error_setg(errp,
+ "height property must be in the range [0..%d]", SHRT_MAX);
+ return;
+ }
+ ib->height = result;
+}
+
+static char *input_barrier_get_height(Object *obj, Error **errp)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ return g_strdup_printf("%d", ib->height);
+}
+
+static void input_barrier_instance_init(Object *obj)
+{
+ InputBarrier *ib = INPUT_BARRIER(obj);
+
+ /* always use generic keymaps */
+ if (keyboard_layout && !kbd_layout) {
+ /* We use X11 key id, so use VNC name2keysym */
+ kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
+ &error_fatal);
+ }
+
+ ib->saddr.type = SOCKET_ADDRESS_TYPE_INET;
+ ib->saddr.u.inet.host = g_strdup("localhost");
+ ib->saddr.u.inet.port = g_strdup("24800");
+
+ ib->x_origin = 0;
+ ib->y_origin = 0;
+ ib->width = 1920;
+ ib->height = 1080;
+}
+
+static void input_barrier_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+ ucc->complete = input_barrier_complete;
+
+ object_class_property_add_str(oc, "name",
+ input_barrier_get_name,
+ input_barrier_set_name);
+ object_class_property_add_str(oc, "server",
+ input_barrier_get_server,
+ input_barrier_set_server);
+ object_class_property_add_str(oc, "port",
+ input_barrier_get_port,
+ input_barrier_set_port);
+ object_class_property_add_str(oc, "x-origin",
+ input_barrier_get_x_origin,
+ input_barrier_set_x_origin);
+ object_class_property_add_str(oc, "y-origin",
+ input_barrier_get_y_origin,
+ input_barrier_set_y_origin);
+ object_class_property_add_str(oc, "width",
+ input_barrier_get_width,
+ input_barrier_set_width);
+ object_class_property_add_str(oc, "height",
+ input_barrier_get_height,
+ input_barrier_set_height);
+}
+
+static const TypeInfo input_barrier_info = {
+ .name = TYPE_INPUT_BARRIER,
+ .parent = TYPE_OBJECT,
+ .class_init = input_barrier_class_init,
+ .instance_size = sizeof(InputBarrier),
+ .instance_init = input_barrier_instance_init,
+ .instance_finalize = input_barrier_instance_finalize,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+static void register_types(void)
+{
+ type_register_static(&input_barrier_info);
+}
+
+type_init(register_types);
diff --git a/ui/input-barrier.h b/ui/input-barrier.h
new file mode 100644
index 000000000..e5b090590
--- /dev/null
+++ b/ui/input-barrier.h
@@ -0,0 +1,112 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef UI_INPUT_BARRIER_H
+#define UI_INPUT_BARRIER_H
+
+/* Barrier protocol */
+#define BARRIER_VERSION_MAJOR 1
+#define BARRIER_VERSION_MINOR 6
+
+enum barrierCmd {
+ barrierCmdCNoop,
+ barrierCmdCClose,
+ barrierCmdCEnter,
+ barrierCmdCLeave,
+ barrierCmdCClipboard,
+ barrierCmdCScreenSaver,
+ barrierCmdCResetOptions,
+ barrierCmdCInfoAck,
+ barrierCmdCKeepAlive,
+ barrierCmdDKeyDown,
+ barrierCmdDKeyRepeat,
+ barrierCmdDKeyUp,
+ barrierCmdDMouseDown,
+ barrierCmdDMouseUp,
+ barrierCmdDMouseMove,
+ barrierCmdDMouseRelMove,
+ barrierCmdDMouseWheel,
+ barrierCmdDClipboard,
+ barrierCmdDInfo,
+ barrierCmdDSetOptions,
+ barrierCmdDFileTransfer,
+ barrierCmdDDragInfo,
+ barrierCmdQInfo,
+ barrierCmdEIncompatible,
+ barrierCmdEBusy,
+ barrierCmdEUnknown,
+ barrierCmdEBad,
+ /* connection sequence */
+ barrierCmdHello,
+ barrierCmdHelloBack,
+};
+
+enum {
+ barrierButtonNone,
+ barrierButtonLeft,
+ barrierButtonMiddle,
+ barrierButtonRight,
+ barrierButtonExtra0
+};
+
+struct barrierVersion {
+ int16_t major;
+ int16_t minor;
+};
+
+struct barrierMouseButton {
+ int8_t buttonid;
+};
+
+struct barrierEnter {
+ int16_t x;
+ int16_t y;
+ int32_t seqn;
+ int16_t modifier;
+};
+
+struct barrierMousePos {
+ int16_t x;
+ int16_t y;
+};
+
+struct barrierKey {
+ int16_t keyid;
+ int16_t modifier;
+ int16_t button;
+};
+
+struct barrierRepeat {
+ int16_t keyid;
+ int16_t modifier;
+ int16_t repeat;
+ int16_t button;
+};
+
+#define BARRIER_MAX_OPTIONS 32
+struct barrierSet {
+ int nb;
+ struct {
+ int id;
+ char nul;
+ int value;
+ } option[BARRIER_MAX_OPTIONS];
+};
+
+struct barrierMsg {
+ enum barrierCmd cmd;
+ union {
+ struct barrierVersion version;
+ struct barrierMouseButton mousebutton;
+ struct barrierMousePos mousepos;
+ struct barrierEnter enter;
+ struct barrierKey key;
+ struct barrierRepeat repeat;
+ struct barrierSet set;
+ };
+};
+#endif
diff --git a/ui/input-keymap.c b/ui/input-keymap.c
new file mode 100644
index 000000000..1b756a697
--- /dev/null
+++ b/ui/input-keymap.c
@@ -0,0 +1,89 @@
+#include "qemu/osdep.h"
+#include "keymaps.h"
+#include "ui/input.h"
+
+#include "standard-headers/linux/input.h"
+
+#include "ui/input-keymap-atset1-to-qcode.c.inc"
+#include "ui/input-keymap-linux-to-qcode.c.inc"
+#include "ui/input-keymap-qcode-to-atset1.c.inc"
+#include "ui/input-keymap-qcode-to-atset2.c.inc"
+#include "ui/input-keymap-qcode-to-atset3.c.inc"
+#include "ui/input-keymap-qcode-to-linux.c.inc"
+#include "ui/input-keymap-qcode-to-qnum.c.inc"
+#include "ui/input-keymap-qcode-to-sun.c.inc"
+#include "ui/input-keymap-qnum-to-qcode.c.inc"
+#include "ui/input-keymap-usb-to-qcode.c.inc"
+#include "ui/input-keymap-win32-to-qcode.c.inc"
+#include "ui/input-keymap-x11-to-qcode.c.inc"
+#include "ui/input-keymap-xorgevdev-to-qcode.c.inc"
+#include "ui/input-keymap-xorgkbd-to-qcode.c.inc"
+#include "ui/input-keymap-xorgxquartz-to-qcode.c.inc"
+#include "ui/input-keymap-xorgxwin-to-qcode.c.inc"
+#include "ui/input-keymap-osx-to-qcode.c.inc"
+
+int qemu_input_linux_to_qcode(unsigned int lnx)
+{
+ if (lnx >= qemu_input_map_linux_to_qcode_len) {
+ return 0;
+ }
+ return qemu_input_map_linux_to_qcode[lnx];
+}
+
+int qemu_input_key_value_to_number(const KeyValue *value)
+{
+ if (value->type == KEY_VALUE_KIND_QCODE) {
+ if (value->u.qcode.data >= qemu_input_map_qcode_to_qnum_len) {
+ return 0;
+ }
+ return qemu_input_map_qcode_to_qnum[value->u.qcode.data];
+ } else {
+ assert(value->type == KEY_VALUE_KIND_NUMBER);
+ return value->u.number.data;
+ }
+}
+
+int qemu_input_key_number_to_qcode(unsigned int nr)
+{
+ if (nr >= qemu_input_map_qnum_to_qcode_len) {
+ return 0;
+ }
+ return qemu_input_map_qnum_to_qcode[nr];
+}
+
+int qemu_input_key_value_to_qcode(const KeyValue *value)
+{
+ if (value->type == KEY_VALUE_KIND_QCODE) {
+ return value->u.qcode.data;
+ } else {
+ assert(value->type == KEY_VALUE_KIND_NUMBER);
+ return qemu_input_key_number_to_qcode(value->u.number.data);
+ }
+}
+
+int qemu_input_key_value_to_scancode(const KeyValue *value, bool down,
+ int *codes)
+{
+ int keycode = qemu_input_key_value_to_number(value);
+ int count = 0;
+
+ if (value->type == KEY_VALUE_KIND_QCODE &&
+ value->u.qcode.data == Q_KEY_CODE_PAUSE) {
+ /* specific case */
+ int v = down ? 0 : 0x80;
+ codes[count++] = 0xe1;
+ codes[count++] = 0x1d | v;
+ codes[count++] = 0x45 | v;
+ return count;
+ }
+ if (keycode & SCANCODE_GREY) {
+ codes[count++] = SCANCODE_EMUL0;
+ keycode &= ~SCANCODE_GREY;
+ }
+ if (!down) {
+ keycode |= SCANCODE_UP;
+ }
+ codes[count++] = keycode;
+
+ return count;
+}
diff --git a/ui/input-legacy.c b/ui/input-legacy.c
new file mode 100644
index 000000000..9fc78a639
--- /dev/null
+++ b/ui/input-legacy.c
@@ -0,0 +1,275 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/qapi-commands-ui.h"
+#include "ui/console.h"
+#include "keymaps.h"
+#include "ui/input.h"
+
+struct QEMUPutMouseEntry {
+ QEMUPutMouseEvent *qemu_put_mouse_event;
+ void *qemu_put_mouse_event_opaque;
+ int qemu_put_mouse_event_absolute;
+
+ /* new input core */
+ QemuInputHandler h;
+ QemuInputHandlerState *s;
+ int axis[INPUT_AXIS__MAX];
+ int buttons;
+};
+
+struct QEMUPutKbdEntry {
+ QEMUPutKBDEvent *put_kbd;
+ void *opaque;
+ QemuInputHandlerState *s;
+};
+
+struct QEMUPutLEDEntry {
+ QEMUPutLEDEvent *put_led;
+ void *opaque;
+ QTAILQ_ENTRY(QEMUPutLEDEntry) next;
+};
+
+static QTAILQ_HEAD(, QEMUPutLEDEntry) led_handlers =
+ QTAILQ_HEAD_INITIALIZER(led_handlers);
+
+int index_from_key(const char *key, size_t key_length)
+{
+ int i;
+
+ for (i = 0; i < Q_KEY_CODE__MAX; i++) {
+ if (!strncmp(key, QKeyCode_str(i), key_length) &&
+ !QKeyCode_str(i)[key_length]) {
+ break;
+ }
+ }
+
+ /* Return Q_KEY_CODE__MAX if the key is invalid */
+ return i;
+}
+
+static KeyValue *copy_key_value(KeyValue *src)
+{
+ KeyValue *dst = g_new(KeyValue, 1);
+ memcpy(dst, src, sizeof(*src));
+ if (dst->type == KEY_VALUE_KIND_NUMBER) {
+ QKeyCode code = qemu_input_key_number_to_qcode(dst->u.number.data);
+ dst->type = KEY_VALUE_KIND_QCODE;
+ dst->u.qcode.data = code;
+ }
+ return dst;
+}
+
+void qmp_send_key(KeyValueList *keys, bool has_hold_time, int64_t hold_time,
+ Error **errp)
+{
+ KeyValueList *p;
+ KeyValue **up = NULL;
+ int count = 0;
+
+ if (!has_hold_time) {
+ hold_time = 0; /* use default */
+ }
+
+ for (p = keys; p != NULL; p = p->next) {
+ qemu_input_event_send_key(NULL, copy_key_value(p->value), true);
+ qemu_input_event_send_key_delay(hold_time);
+ up = g_realloc(up, sizeof(*up) * (count+1));
+ up[count] = copy_key_value(p->value);
+ count++;
+ }
+ while (count) {
+ count--;
+ qemu_input_event_send_key(NULL, up[count], false);
+ qemu_input_event_send_key_delay(hold_time);
+ }
+ g_free(up);
+}
+
+static void legacy_kbd_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ QEMUPutKbdEntry *entry = (QEMUPutKbdEntry *)dev;
+ int scancodes[3], i, count;
+ InputKeyEvent *key = evt->u.key.data;
+
+ if (!entry || !entry->put_kbd) {
+ return;
+ }
+ count = qemu_input_key_value_to_scancode(key->key,
+ key->down,
+ scancodes);
+ for (i = 0; i < count; i++) {
+ entry->put_kbd(entry->opaque, scancodes[i]);
+ }
+}
+
+static QemuInputHandler legacy_kbd_handler = {
+ .name = "legacy-kbd",
+ .mask = INPUT_EVENT_MASK_KEY,
+ .event = legacy_kbd_event,
+};
+
+QEMUPutKbdEntry *qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque)
+{
+ QEMUPutKbdEntry *entry;
+
+ entry = g_new0(QEMUPutKbdEntry, 1);
+ entry->put_kbd = func;
+ entry->opaque = opaque;
+ entry->s = qemu_input_handler_register((DeviceState *)entry,
+ &legacy_kbd_handler);
+ qemu_input_handler_activate(entry->s);
+ return entry;
+}
+
+static void legacy_mouse_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ static const int bmap[INPUT_BUTTON__MAX] = {
+ [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON,
+ [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON,
+ [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON,
+ };
+ QEMUPutMouseEntry *s = (QEMUPutMouseEntry *)dev;
+ InputBtnEvent *btn;
+ InputMoveEvent *move;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ if (btn->down) {
+ s->buttons |= bmap[btn->button];
+ } else {
+ s->buttons &= ~bmap[btn->button];
+ }
+ if (btn->down && btn->button == INPUT_BUTTON_WHEEL_UP) {
+ s->qemu_put_mouse_event(s->qemu_put_mouse_event_opaque,
+ s->axis[INPUT_AXIS_X],
+ s->axis[INPUT_AXIS_Y],
+ -1,
+ s->buttons);
+ }
+ if (btn->down && btn->button == INPUT_BUTTON_WHEEL_DOWN) {
+ s->qemu_put_mouse_event(s->qemu_put_mouse_event_opaque,
+ s->axis[INPUT_AXIS_X],
+ s->axis[INPUT_AXIS_Y],
+ 1,
+ s->buttons);
+ }
+ break;
+ case INPUT_EVENT_KIND_ABS:
+ move = evt->u.abs.data;
+ s->axis[move->axis] = move->value;
+ break;
+ case INPUT_EVENT_KIND_REL:
+ move = evt->u.rel.data;
+ s->axis[move->axis] += move->value;
+ break;
+ default:
+ break;
+ }
+}
+
+static void legacy_mouse_sync(DeviceState *dev)
+{
+ QEMUPutMouseEntry *s = (QEMUPutMouseEntry *)dev;
+
+ s->qemu_put_mouse_event(s->qemu_put_mouse_event_opaque,
+ s->axis[INPUT_AXIS_X],
+ s->axis[INPUT_AXIS_Y],
+ 0,
+ s->buttons);
+
+ if (!s->qemu_put_mouse_event_absolute) {
+ s->axis[INPUT_AXIS_X] = 0;
+ s->axis[INPUT_AXIS_Y] = 0;
+ }
+}
+
+QEMUPutMouseEntry *qemu_add_mouse_event_handler(QEMUPutMouseEvent *func,
+ void *opaque, int absolute,
+ const char *name)
+{
+ QEMUPutMouseEntry *s;
+
+ s = g_new0(QEMUPutMouseEntry, 1);
+
+ s->qemu_put_mouse_event = func;
+ s->qemu_put_mouse_event_opaque = opaque;
+ s->qemu_put_mouse_event_absolute = absolute;
+
+ s->h.name = name;
+ s->h.mask = INPUT_EVENT_MASK_BTN |
+ (absolute ? INPUT_EVENT_MASK_ABS : INPUT_EVENT_MASK_REL);
+ s->h.event = legacy_mouse_event;
+ s->h.sync = legacy_mouse_sync;
+ s->s = qemu_input_handler_register((DeviceState *)s,
+ &s->h);
+
+ return s;
+}
+
+void qemu_activate_mouse_event_handler(QEMUPutMouseEntry *entry)
+{
+ qemu_input_handler_activate(entry->s);
+}
+
+void qemu_remove_mouse_event_handler(QEMUPutMouseEntry *entry)
+{
+ qemu_input_handler_unregister(entry->s);
+
+ g_free(entry);
+}
+
+QEMUPutLEDEntry *qemu_add_led_event_handler(QEMUPutLEDEvent *func,
+ void *opaque)
+{
+ QEMUPutLEDEntry *s;
+
+ s = g_new0(QEMUPutLEDEntry, 1);
+
+ s->put_led = func;
+ s->opaque = opaque;
+ QTAILQ_INSERT_TAIL(&led_handlers, s, next);
+ return s;
+}
+
+void qemu_remove_led_event_handler(QEMUPutLEDEntry *entry)
+{
+ if (entry == NULL)
+ return;
+ QTAILQ_REMOVE(&led_handlers, entry, next);
+ g_free(entry);
+}
+
+void kbd_put_ledstate(int ledstate)
+{
+ QEMUPutLEDEntry *cursor;
+
+ QTAILQ_FOREACH(cursor, &led_handlers, next) {
+ cursor->put_led(cursor->opaque, ledstate);
+ }
+}
diff --git a/ui/input-linux.c b/ui/input-linux.c
new file mode 100644
index 000000000..05c0c9881
--- /dev/null
+++ b/ui/input-linux.c
@@ -0,0 +1,534 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/config-file.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/sockets.h"
+#include "ui/input.h"
+#include "qom/object_interfaces.h"
+#include "sysemu/iothread.h"
+#include "block/aio.h"
+
+#include <sys/ioctl.h>
+#include "standard-headers/linux/input.h"
+#include "qom/object.h"
+
+static bool linux_is_button(unsigned int lnx)
+{
+ if (lnx < 0x100) {
+ return false;
+ }
+ if (lnx >= 0x160 && lnx < 0x2c0) {
+ return false;
+ }
+ return true;
+}
+
+#define TYPE_INPUT_LINUX "input-linux"
+OBJECT_DECLARE_SIMPLE_TYPE(InputLinux,
+ INPUT_LINUX)
+
+
+struct InputLinux {
+ Object parent;
+
+ char *evdev;
+ int fd;
+ bool repeat;
+ bool grab_request;
+ bool grab_active;
+ bool grab_all;
+ bool keydown[KEY_CNT];
+ int keycount;
+ int wheel;
+ bool initialized;
+
+ bool has_rel_x;
+ bool has_abs_x;
+ int num_keys;
+ int num_btns;
+ int abs_x_min;
+ int abs_x_max;
+ int abs_y_min;
+ int abs_y_max;
+ struct input_event event;
+ int read_offset;
+
+ enum GrabToggleKeys grab_toggle;
+
+ QTAILQ_ENTRY(InputLinux) next;
+};
+
+
+static QTAILQ_HEAD(, InputLinux) inputs = QTAILQ_HEAD_INITIALIZER(inputs);
+
+static void input_linux_toggle_grab(InputLinux *il)
+{
+ intptr_t request = !il->grab_active;
+ InputLinux *item;
+ int rc;
+
+ rc = ioctl(il->fd, EVIOCGRAB, request);
+ if (rc < 0) {
+ return;
+ }
+ il->grab_active = !il->grab_active;
+
+ if (!il->grab_all) {
+ return;
+ }
+ QTAILQ_FOREACH(item, &inputs, next) {
+ if (item == il || item->grab_all) {
+ /* avoid endless loops */
+ continue;
+ }
+ if (item->grab_active != il->grab_active) {
+ input_linux_toggle_grab(item);
+ }
+ }
+}
+
+static bool input_linux_check_toggle(InputLinux *il)
+{
+ switch (il->grab_toggle) {
+ case GRAB_TOGGLE_KEYS_CTRL_CTRL:
+ return il->keydown[KEY_LEFTCTRL] &&
+ il->keydown[KEY_RIGHTCTRL];
+
+ case GRAB_TOGGLE_KEYS_ALT_ALT:
+ return il->keydown[KEY_LEFTALT] &&
+ il->keydown[KEY_RIGHTALT];
+
+ case GRAB_TOGGLE_KEYS_SHIFT_SHIFT:
+ return il->keydown[KEY_LEFTSHIFT] &&
+ il->keydown[KEY_RIGHTSHIFT];
+
+ case GRAB_TOGGLE_KEYS_META_META:
+ return il->keydown[KEY_LEFTMETA] &&
+ il->keydown[KEY_RIGHTMETA];
+
+ case GRAB_TOGGLE_KEYS_SCROLLLOCK:
+ return il->keydown[KEY_SCROLLLOCK];
+
+ case GRAB_TOGGLE_KEYS_CTRL_SCROLLLOCK:
+ return (il->keydown[KEY_LEFTCTRL] ||
+ il->keydown[KEY_RIGHTCTRL]) &&
+ il->keydown[KEY_SCROLLLOCK];
+
+ case GRAB_TOGGLE_KEYS__MAX:
+ /* avoid gcc error */
+ break;
+ }
+ return false;
+}
+
+static bool input_linux_should_skip(InputLinux *il,
+ struct input_event *event)
+{
+ return (il->grab_toggle == GRAB_TOGGLE_KEYS_SCROLLLOCK ||
+ il->grab_toggle == GRAB_TOGGLE_KEYS_CTRL_SCROLLLOCK) &&
+ event->code == KEY_SCROLLLOCK;
+}
+
+static void input_linux_handle_keyboard(InputLinux *il,
+ struct input_event *event)
+{
+ if (event->type == EV_KEY) {
+ if (event->value > 2 || (event->value > 1 && !il->repeat)) {
+ /*
+ * ignore autorepeat + unknown key events
+ * 0 == up, 1 == down, 2 == autorepeat, other == undefined
+ */
+ return;
+ }
+ if (event->code >= KEY_CNT) {
+ /*
+ * Should not happen. But better safe than sorry,
+ * and we make Coverity happy too.
+ */
+ return;
+ }
+
+ /* keep track of key state */
+ if (!il->keydown[event->code] && event->value) {
+ il->keydown[event->code] = true;
+ il->keycount++;
+ }
+ if (il->keydown[event->code] && !event->value) {
+ il->keydown[event->code] = false;
+ il->keycount--;
+ }
+
+ /* send event to guest when grab is active */
+ if (il->grab_active && !input_linux_should_skip(il, event)) {
+ int qcode = qemu_input_linux_to_qcode(event->code);
+ qemu_input_event_send_key_qcode(NULL, qcode, event->value);
+ }
+
+ /* hotkey -> record switch request ... */
+ if (input_linux_check_toggle(il)) {
+ il->grab_request = true;
+ }
+
+ /*
+ * ... and do the switch when all keys are lifted, so we
+ * confuse neither guest nor host with keys which seem to
+ * be stuck due to missing key-up events.
+ */
+ if (il->grab_request && !il->keycount) {
+ il->grab_request = false;
+ input_linux_toggle_grab(il);
+ }
+ }
+}
+
+static void input_linux_event_mouse_button(int button)
+{
+ qemu_input_queue_btn(NULL, button, true);
+ qemu_input_event_sync();
+ qemu_input_queue_btn(NULL, button, false);
+ qemu_input_event_sync();
+}
+
+static void input_linux_handle_mouse(InputLinux *il, struct input_event *event)
+{
+ if (!il->grab_active) {
+ return;
+ }
+
+ switch (event->type) {
+ case EV_KEY:
+ switch (event->code) {
+ case BTN_LEFT:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_LEFT, event->value);
+ break;
+ case BTN_RIGHT:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_RIGHT, event->value);
+ break;
+ case BTN_MIDDLE:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_MIDDLE, event->value);
+ break;
+ case BTN_GEAR_UP:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_WHEEL_UP, event->value);
+ break;
+ case BTN_GEAR_DOWN:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_WHEEL_DOWN,
+ event->value);
+ break;
+ case BTN_SIDE:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_SIDE, event->value);
+ break;
+ case BTN_EXTRA:
+ qemu_input_queue_btn(NULL, INPUT_BUTTON_EXTRA, event->value);
+ break;
+ };
+ break;
+ case EV_REL:
+ switch (event->code) {
+ case REL_X:
+ qemu_input_queue_rel(NULL, INPUT_AXIS_X, event->value);
+ break;
+ case REL_Y:
+ qemu_input_queue_rel(NULL, INPUT_AXIS_Y, event->value);
+ break;
+ case REL_WHEEL:
+ il->wheel = event->value;
+ break;
+ }
+ break;
+ case EV_ABS:
+ switch (event->code) {
+ case ABS_X:
+ qemu_input_queue_abs(NULL, INPUT_AXIS_X, event->value,
+ il->abs_x_min, il->abs_x_max);
+ break;
+ case ABS_Y:
+ qemu_input_queue_abs(NULL, INPUT_AXIS_Y, event->value,
+ il->abs_y_min, il->abs_y_max);
+ break;
+ }
+ break;
+ case EV_SYN:
+ qemu_input_event_sync();
+ if (il->wheel != 0) {
+ input_linux_event_mouse_button((il->wheel > 0)
+ ? INPUT_BUTTON_WHEEL_UP
+ : INPUT_BUTTON_WHEEL_DOWN);
+ il->wheel = 0;
+ }
+ break;
+ }
+}
+
+static void input_linux_event(void *opaque)
+{
+ InputLinux *il = opaque;
+ int rc;
+ int read_size;
+ uint8_t *p = (uint8_t *)&il->event;
+
+ for (;;) {
+ read_size = sizeof(il->event) - il->read_offset;
+ rc = read(il->fd, &p[il->read_offset], read_size);
+ if (rc != read_size) {
+ if (rc < 0 && errno != EAGAIN) {
+ fprintf(stderr, "%s: read: %s\n", __func__, strerror(errno));
+ qemu_set_fd_handler(il->fd, NULL, NULL, NULL);
+ close(il->fd);
+ } else if (rc > 0) {
+ il->read_offset += rc;
+ }
+ break;
+ }
+ il->read_offset = 0;
+
+ if (il->num_keys) {
+ input_linux_handle_keyboard(il, &il->event);
+ }
+ if ((il->has_rel_x || il->has_abs_x) && il->num_btns) {
+ input_linux_handle_mouse(il, &il->event);
+ }
+ }
+}
+
+static void input_linux_complete(UserCreatable *uc, Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(uc);
+ uint8_t evtmap, relmap, absmap;
+ uint8_t keymap[KEY_CNT / 8], keystate[KEY_CNT / 8];
+ unsigned int i;
+ int rc, ver;
+ struct input_absinfo absinfo;
+
+ if (!il->evdev) {
+ error_setg(errp, "no input device specified");
+ return;
+ }
+
+ il->fd = open(il->evdev, O_RDWR);
+ if (il->fd < 0) {
+ error_setg_file_open(errp, errno, il->evdev);
+ return;
+ }
+ qemu_set_nonblock(il->fd);
+
+ rc = ioctl(il->fd, EVIOCGVERSION, &ver);
+ if (rc < 0) {
+ error_setg(errp, "%s: is not an evdev device", il->evdev);
+ goto err_close;
+ }
+
+ rc = ioctl(il->fd, EVIOCGBIT(0, sizeof(evtmap)), &evtmap);
+ if (rc < 0) {
+ goto err_read_event_bits;
+ }
+
+ if (evtmap & (1 << EV_REL)) {
+ relmap = 0;
+ rc = ioctl(il->fd, EVIOCGBIT(EV_REL, sizeof(relmap)), &relmap);
+ if (rc < 0) {
+ goto err_read_event_bits;
+ }
+ if (relmap & (1 << REL_X)) {
+ il->has_rel_x = true;
+ }
+ }
+
+ if (evtmap & (1 << EV_ABS)) {
+ absmap = 0;
+ rc = ioctl(il->fd, EVIOCGBIT(EV_ABS, sizeof(absmap)), &absmap);
+ if (rc < 0) {
+ goto err_read_event_bits;
+ }
+ if (absmap & (1 << ABS_X)) {
+ il->has_abs_x = true;
+ rc = ioctl(il->fd, EVIOCGABS(ABS_X), &absinfo);
+ if (rc < 0) {
+ error_setg(errp, "%s: failed to get get absolute X value",
+ il->evdev);
+ goto err_close;
+ }
+ il->abs_x_min = absinfo.minimum;
+ il->abs_x_max = absinfo.maximum;
+ rc = ioctl(il->fd, EVIOCGABS(ABS_Y), &absinfo);
+ if (rc < 0) {
+ error_setg(errp, "%s: failed to get get absolute Y value",
+ il->evdev);
+ goto err_close;
+ }
+ il->abs_y_min = absinfo.minimum;
+ il->abs_y_max = absinfo.maximum;
+ }
+ }
+
+ if (evtmap & (1 << EV_KEY)) {
+ memset(keymap, 0, sizeof(keymap));
+ rc = ioctl(il->fd, EVIOCGBIT(EV_KEY, sizeof(keymap)), keymap);
+ if (rc < 0) {
+ goto err_read_event_bits;
+ }
+ rc = ioctl(il->fd, EVIOCGKEY(sizeof(keystate)), keystate);
+ if (rc < 0) {
+ error_setg(errp, "%s: failed to get global key state", il->evdev);
+ goto err_close;
+ }
+ for (i = 0; i < KEY_CNT; i++) {
+ if (keymap[i / 8] & (1 << (i % 8))) {
+ if (linux_is_button(i)) {
+ il->num_btns++;
+ } else {
+ il->num_keys++;
+ }
+ if (keystate[i / 8] & (1 << (i % 8))) {
+ il->keydown[i] = true;
+ il->keycount++;
+ }
+ }
+ }
+ }
+
+ qemu_set_fd_handler(il->fd, input_linux_event, NULL, il);
+ if (il->keycount) {
+ /* delay grab until all keys are released */
+ il->grab_request = true;
+ } else {
+ input_linux_toggle_grab(il);
+ }
+ QTAILQ_INSERT_TAIL(&inputs, il, next);
+ il->initialized = true;
+ return;
+
+err_read_event_bits:
+ error_setg(errp, "%s: failed to read event bits", il->evdev);
+
+err_close:
+ close(il->fd);
+ return;
+}
+
+static void input_linux_instance_finalize(Object *obj)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ if (il->initialized) {
+ QTAILQ_REMOVE(&inputs, il, next);
+ qemu_set_fd_handler(il->fd, NULL, NULL, NULL);
+ close(il->fd);
+ }
+ g_free(il->evdev);
+}
+
+static char *input_linux_get_evdev(Object *obj, Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ return g_strdup(il->evdev);
+}
+
+static void input_linux_set_evdev(Object *obj, const char *value,
+ Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ if (il->evdev) {
+ error_setg(errp, "evdev property already set");
+ return;
+ }
+ il->evdev = g_strdup(value);
+}
+
+static bool input_linux_get_grab_all(Object *obj, Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ return il->grab_all;
+}
+
+static void input_linux_set_grab_all(Object *obj, bool value,
+ Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ il->grab_all = value;
+}
+
+static bool input_linux_get_repeat(Object *obj, Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ return il->repeat;
+}
+
+static void input_linux_set_repeat(Object *obj, bool value,
+ Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ il->repeat = value;
+}
+
+static int input_linux_get_grab_toggle(Object *obj, Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ return il->grab_toggle;
+}
+
+static void input_linux_set_grab_toggle(Object *obj, int value,
+ Error **errp)
+{
+ InputLinux *il = INPUT_LINUX(obj);
+
+ il->grab_toggle = value;
+}
+
+static void input_linux_instance_init(Object *obj)
+{
+}
+
+static void input_linux_class_init(ObjectClass *oc, void *data)
+{
+ UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+
+ ucc->complete = input_linux_complete;
+
+ object_class_property_add_str(oc, "evdev",
+ input_linux_get_evdev,
+ input_linux_set_evdev);
+ object_class_property_add_bool(oc, "grab_all",
+ input_linux_get_grab_all,
+ input_linux_set_grab_all);
+ object_class_property_add_bool(oc, "repeat",
+ input_linux_get_repeat,
+ input_linux_set_repeat);
+ object_class_property_add_enum(oc, "grab-toggle", "GrabToggleKeys",
+ &GrabToggleKeys_lookup,
+ input_linux_get_grab_toggle,
+ input_linux_set_grab_toggle);
+}
+
+static const TypeInfo input_linux_info = {
+ .name = TYPE_INPUT_LINUX,
+ .parent = TYPE_OBJECT,
+ .class_init = input_linux_class_init,
+ .instance_size = sizeof(InputLinux),
+ .instance_init = input_linux_instance_init,
+ .instance_finalize = input_linux_instance_finalize,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_USER_CREATABLE },
+ { }
+ }
+};
+
+static void register_types(void)
+{
+ type_register_static(&input_linux_info);
+}
+
+type_init(register_types);
diff --git a/ui/input.c b/ui/input.c
new file mode 100644
index 000000000..8ac407dec
--- /dev/null
+++ b/ui/input.c
@@ -0,0 +1,622 @@
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-ui.h"
+#include "qapi/qmp/qdict.h"
+#include "qemu/error-report.h"
+#include "trace.h"
+#include "ui/input.h"
+#include "ui/console.h"
+#include "sysemu/replay.h"
+#include "sysemu/runstate.h"
+
+struct QemuInputHandlerState {
+ DeviceState *dev;
+ QemuInputHandler *handler;
+ int id;
+ int events;
+ QemuConsole *con;
+ QTAILQ_ENTRY(QemuInputHandlerState) node;
+};
+
+typedef struct QemuInputEventQueue QemuInputEventQueue;
+typedef QTAILQ_HEAD(QemuInputEventQueueHead, QemuInputEventQueue)
+ QemuInputEventQueueHead;
+
+struct QemuInputEventQueue {
+ enum {
+ QEMU_INPUT_QUEUE_DELAY = 1,
+ QEMU_INPUT_QUEUE_EVENT,
+ QEMU_INPUT_QUEUE_SYNC,
+ } type;
+ QEMUTimer *timer;
+ uint32_t delay_ms;
+ QemuConsole *src;
+ InputEvent *evt;
+ QTAILQ_ENTRY(QemuInputEventQueue) node;
+};
+
+static QTAILQ_HEAD(, QemuInputHandlerState) handlers =
+ QTAILQ_HEAD_INITIALIZER(handlers);
+static NotifierList mouse_mode_notifiers =
+ NOTIFIER_LIST_INITIALIZER(mouse_mode_notifiers);
+
+static QemuInputEventQueueHead kbd_queue = QTAILQ_HEAD_INITIALIZER(kbd_queue);
+static QEMUTimer *kbd_timer;
+static uint32_t kbd_default_delay_ms = 10;
+static uint32_t queue_count;
+static uint32_t queue_limit = 1024;
+
+QemuInputHandlerState *qemu_input_handler_register(DeviceState *dev,
+ QemuInputHandler *handler)
+{
+ QemuInputHandlerState *s = g_new0(QemuInputHandlerState, 1);
+ static int id = 1;
+
+ s->dev = dev;
+ s->handler = handler;
+ s->id = id++;
+ QTAILQ_INSERT_TAIL(&handlers, s, node);
+
+ qemu_input_check_mode_change();
+ return s;
+}
+
+void qemu_input_handler_activate(QemuInputHandlerState *s)
+{
+ QTAILQ_REMOVE(&handlers, s, node);
+ QTAILQ_INSERT_HEAD(&handlers, s, node);
+ qemu_input_check_mode_change();
+}
+
+void qemu_input_handler_deactivate(QemuInputHandlerState *s)
+{
+ QTAILQ_REMOVE(&handlers, s, node);
+ QTAILQ_INSERT_TAIL(&handlers, s, node);
+ qemu_input_check_mode_change();
+}
+
+void qemu_input_handler_unregister(QemuInputHandlerState *s)
+{
+ QTAILQ_REMOVE(&handlers, s, node);
+ g_free(s);
+ qemu_input_check_mode_change();
+}
+
+void qemu_input_handler_bind(QemuInputHandlerState *s,
+ const char *device_id, int head,
+ Error **errp)
+{
+ QemuConsole *con;
+ Error *err = NULL;
+
+ con = qemu_console_lookup_by_device_name(device_id, head, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ s->con = con;
+}
+
+static QemuInputHandlerState*
+qemu_input_find_handler(uint32_t mask, QemuConsole *con)
+{
+ QemuInputHandlerState *s;
+
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (s->con == NULL || s->con != con) {
+ continue;
+ }
+ if (mask & s->handler->mask) {
+ return s;
+ }
+ }
+
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (s->con != NULL) {
+ continue;
+ }
+ if (mask & s->handler->mask) {
+ return s;
+ }
+ }
+ return NULL;
+}
+
+void qmp_input_send_event(bool has_device, const char *device,
+ bool has_head, int64_t head,
+ InputEventList *events, Error **errp)
+{
+ InputEventList *e;
+ QemuConsole *con;
+ Error *err = NULL;
+
+ con = NULL;
+ if (has_device) {
+ if (!has_head) {
+ head = 0;
+ }
+ con = qemu_console_lookup_by_device_name(device, head, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ }
+
+ if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
+ error_setg(errp, "VM not running");
+ return;
+ }
+
+ for (e = events; e != NULL; e = e->next) {
+ InputEvent *event = e->value;
+
+ if (!qemu_input_find_handler(1 << event->type, con)) {
+ error_setg(errp, "Input handler not found for "
+ "event type %s",
+ InputEventKind_str(event->type));
+ return;
+ }
+ }
+
+ for (e = events; e != NULL; e = e->next) {
+ InputEvent *evt = e->value;
+
+ if (evt->type == INPUT_EVENT_KIND_KEY &&
+ evt->u.key.data->key->type == KEY_VALUE_KIND_NUMBER) {
+ KeyValue *key = evt->u.key.data->key;
+ QKeyCode code = qemu_input_key_number_to_qcode(key->u.number.data);
+ qemu_input_event_send_key_qcode(con, code, evt->u.key.data->down);
+ } else {
+ qemu_input_event_send(con, evt);
+ }
+ }
+
+ qemu_input_event_sync();
+}
+
+static int qemu_input_transform_invert_abs_value(int value)
+{
+ return (int64_t)INPUT_EVENT_ABS_MAX - value + INPUT_EVENT_ABS_MIN;
+}
+
+static void qemu_input_transform_abs_rotate(InputEvent *evt)
+{
+ InputMoveEvent *move = evt->u.abs.data;
+ switch (graphic_rotate) {
+ case 90:
+ if (move->axis == INPUT_AXIS_X) {
+ move->axis = INPUT_AXIS_Y;
+ } else if (move->axis == INPUT_AXIS_Y) {
+ move->axis = INPUT_AXIS_X;
+ move->value = qemu_input_transform_invert_abs_value(move->value);
+ }
+ break;
+ case 180:
+ move->value = qemu_input_transform_invert_abs_value(move->value);
+ break;
+ case 270:
+ if (move->axis == INPUT_AXIS_X) {
+ move->axis = INPUT_AXIS_Y;
+ move->value = qemu_input_transform_invert_abs_value(move->value);
+ } else if (move->axis == INPUT_AXIS_Y) {
+ move->axis = INPUT_AXIS_X;
+ }
+ break;
+ }
+}
+
+static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt)
+{
+ const char *name;
+ int qcode, idx = -1;
+ InputKeyEvent *key;
+ InputBtnEvent *btn;
+ InputMoveEvent *move;
+
+ if (src) {
+ idx = qemu_console_get_index(src);
+ }
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_KEY:
+ key = evt->u.key.data;
+ switch (key->key->type) {
+ case KEY_VALUE_KIND_NUMBER:
+ qcode = qemu_input_key_number_to_qcode(key->key->u.number.data);
+ name = QKeyCode_str(qcode);
+ trace_input_event_key_number(idx, key->key->u.number.data,
+ name, key->down);
+ break;
+ case KEY_VALUE_KIND_QCODE:
+ name = QKeyCode_str(key->key->u.qcode.data);
+ trace_input_event_key_qcode(idx, name, key->down);
+ break;
+ case KEY_VALUE_KIND__MAX:
+ /* keep gcc happy */
+ break;
+ }
+ break;
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ name = InputButton_str(btn->button);
+ trace_input_event_btn(idx, name, btn->down);
+ break;
+ case INPUT_EVENT_KIND_REL:
+ move = evt->u.rel.data;
+ name = InputAxis_str(move->axis);
+ trace_input_event_rel(idx, name, move->value);
+ break;
+ case INPUT_EVENT_KIND_ABS:
+ move = evt->u.abs.data;
+ name = InputAxis_str(move->axis);
+ trace_input_event_abs(idx, name, move->value);
+ break;
+ case INPUT_EVENT_KIND__MAX:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void qemu_input_queue_process(void *opaque)
+{
+ QemuInputEventQueueHead *queue = opaque;
+ QemuInputEventQueue *item;
+
+ g_assert(!QTAILQ_EMPTY(queue));
+ item = QTAILQ_FIRST(queue);
+ g_assert(item->type == QEMU_INPUT_QUEUE_DELAY);
+ QTAILQ_REMOVE(queue, item, node);
+ queue_count--;
+ g_free(item);
+
+ while (!QTAILQ_EMPTY(queue)) {
+ item = QTAILQ_FIRST(queue);
+ switch (item->type) {
+ case QEMU_INPUT_QUEUE_DELAY:
+ timer_mod(item->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL)
+ + item->delay_ms);
+ return;
+ case QEMU_INPUT_QUEUE_EVENT:
+ qemu_input_event_send(item->src, item->evt);
+ qapi_free_InputEvent(item->evt);
+ break;
+ case QEMU_INPUT_QUEUE_SYNC:
+ qemu_input_event_sync();
+ break;
+ }
+ QTAILQ_REMOVE(queue, item, node);
+ queue_count--;
+ g_free(item);
+ }
+}
+
+static void qemu_input_queue_delay(QemuInputEventQueueHead *queue,
+ QEMUTimer *timer, uint32_t delay_ms)
+{
+ QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1);
+ bool start_timer = QTAILQ_EMPTY(queue);
+
+ item->type = QEMU_INPUT_QUEUE_DELAY;
+ item->delay_ms = delay_ms;
+ item->timer = timer;
+ QTAILQ_INSERT_TAIL(queue, item, node);
+ queue_count++;
+
+ if (start_timer) {
+ timer_mod(item->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL)
+ + item->delay_ms);
+ }
+}
+
+static void qemu_input_queue_event(QemuInputEventQueueHead *queue,
+ QemuConsole *src, InputEvent *evt)
+{
+ QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1);
+
+ item->type = QEMU_INPUT_QUEUE_EVENT;
+ item->src = src;
+ item->evt = evt;
+ QTAILQ_INSERT_TAIL(queue, item, node);
+ queue_count++;
+}
+
+static void qemu_input_queue_sync(QemuInputEventQueueHead *queue)
+{
+ QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1);
+
+ item->type = QEMU_INPUT_QUEUE_SYNC;
+ QTAILQ_INSERT_TAIL(queue, item, node);
+ queue_count++;
+}
+
+void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt)
+{
+ QemuInputHandlerState *s;
+
+ qemu_input_event_trace(src, evt);
+
+ /* pre processing */
+ if (graphic_rotate && (evt->type == INPUT_EVENT_KIND_ABS)) {
+ qemu_input_transform_abs_rotate(evt);
+ }
+
+ /* send event */
+ s = qemu_input_find_handler(1 << evt->type, src);
+ if (!s) {
+ return;
+ }
+ s->handler->event(s->dev, src, evt);
+ s->events++;
+}
+
+void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
+{
+ /* Expect all parts of QEMU to send events with QCodes exclusively.
+ * Key numbers are only supported as end-user input via QMP */
+ assert(!(evt->type == INPUT_EVENT_KIND_KEY &&
+ evt->u.key.data->key->type == KEY_VALUE_KIND_NUMBER));
+
+
+ /*
+ * '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
+ * 'sysrq' key serves no further purpose. We normalize it to
+ * 'print', so that downstream receivers of the event don't
+ * neeed to deal with this mistake
+ */
+ if (evt->type == INPUT_EVENT_KIND_KEY &&
+ evt->u.key.data->key->u.qcode.data == Q_KEY_CODE_SYSRQ) {
+ evt->u.key.data->key->u.qcode.data = Q_KEY_CODE_PRINT;
+ }
+
+ if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
+ return;
+ }
+
+ replay_input_event(src, evt);
+}
+
+void qemu_input_event_sync_impl(void)
+{
+ QemuInputHandlerState *s;
+
+ trace_input_event_sync();
+
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (!s->events) {
+ continue;
+ }
+ if (s->handler->sync) {
+ s->handler->sync(s->dev);
+ }
+ s->events = 0;
+ }
+}
+
+void qemu_input_event_sync(void)
+{
+ if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
+ return;
+ }
+
+ replay_input_sync_event();
+}
+
+static InputEvent *qemu_input_event_new_key(KeyValue *key, bool down)
+{
+ InputEvent *evt = g_new0(InputEvent, 1);
+ evt->u.key.data = g_new0(InputKeyEvent, 1);
+ evt->type = INPUT_EVENT_KIND_KEY;
+ evt->u.key.data->key = key;
+ evt->u.key.data->down = down;
+ return evt;
+}
+
+void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down)
+{
+ InputEvent *evt;
+ evt = qemu_input_event_new_key(key, down);
+ if (QTAILQ_EMPTY(&kbd_queue)) {
+ qemu_input_event_send(src, evt);
+ qemu_input_event_sync();
+ qapi_free_InputEvent(evt);
+ } else if (queue_count < queue_limit) {
+ qemu_input_queue_event(&kbd_queue, src, evt);
+ qemu_input_queue_sync(&kbd_queue);
+ } else {
+ qapi_free_InputEvent(evt);
+ }
+}
+
+void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down)
+{
+ QKeyCode code = qemu_input_key_number_to_qcode(num);
+ qemu_input_event_send_key_qcode(src, code, down);
+}
+
+void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down)
+{
+ KeyValue *key = g_new0(KeyValue, 1);
+ key->type = KEY_VALUE_KIND_QCODE;
+ key->u.qcode.data = q;
+ qemu_input_event_send_key(src, key, down);
+}
+
+void qemu_input_event_send_key_delay(uint32_t delay_ms)
+{
+ if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
+ return;
+ }
+
+ if (!kbd_timer) {
+ kbd_timer = timer_new_full(NULL, QEMU_CLOCK_VIRTUAL,
+ SCALE_MS, QEMU_TIMER_ATTR_EXTERNAL,
+ qemu_input_queue_process, &kbd_queue);
+ }
+ if (queue_count < queue_limit) {
+ qemu_input_queue_delay(&kbd_queue, kbd_timer,
+ delay_ms ? delay_ms : kbd_default_delay_ms);
+ }
+}
+
+void qemu_input_queue_btn(QemuConsole *src, InputButton btn, bool down)
+{
+ InputBtnEvent bevt = {
+ .button = btn,
+ .down = down,
+ };
+ InputEvent evt = {
+ .type = INPUT_EVENT_KIND_BTN,
+ .u.btn.data = &bevt,
+ };
+
+ qemu_input_event_send(src, &evt);
+}
+
+void qemu_input_update_buttons(QemuConsole *src, uint32_t *button_map,
+ uint32_t button_old, uint32_t button_new)
+{
+ InputButton btn;
+ uint32_t mask;
+
+ for (btn = 0; btn < INPUT_BUTTON__MAX; btn++) {
+ mask = button_map[btn];
+ if ((button_old & mask) == (button_new & mask)) {
+ continue;
+ }
+ qemu_input_queue_btn(src, btn, button_new & mask);
+ }
+}
+
+bool qemu_input_is_absolute(void)
+{
+ QemuInputHandlerState *s;
+
+ s = qemu_input_find_handler(INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS,
+ NULL);
+ return (s != NULL) && (s->handler->mask & INPUT_EVENT_MASK_ABS);
+}
+
+int qemu_input_scale_axis(int value,
+ int min_in, int max_in,
+ int min_out, int max_out)
+{
+ int64_t range_in = (int64_t)max_in - min_in;
+ int64_t range_out = (int64_t)max_out - min_out;
+
+ if (range_in < 1) {
+ return min_out + range_out / 2;
+ }
+ return ((int64_t)value - min_in) * range_out / range_in + min_out;
+}
+
+void qemu_input_queue_rel(QemuConsole *src, InputAxis axis, int value)
+{
+ InputMoveEvent move = {
+ .axis = axis,
+ .value = value,
+ };
+ InputEvent evt = {
+ .type = INPUT_EVENT_KIND_REL,
+ .u.rel.data = &move,
+ };
+
+ qemu_input_event_send(src, &evt);
+}
+
+void qemu_input_queue_abs(QemuConsole *src, InputAxis axis, int value,
+ int min_in, int max_in)
+{
+ InputMoveEvent move = {
+ .axis = axis,
+ .value = qemu_input_scale_axis(value, min_in, max_in,
+ INPUT_EVENT_ABS_MIN,
+ INPUT_EVENT_ABS_MAX),
+ };
+ InputEvent evt = {
+ .type = INPUT_EVENT_KIND_ABS,
+ .u.abs.data = &move,
+ };
+
+ qemu_input_event_send(src, &evt);
+}
+
+void qemu_input_check_mode_change(void)
+{
+ static int current_is_absolute;
+ int is_absolute;
+
+ is_absolute = qemu_input_is_absolute();
+
+ if (is_absolute != current_is_absolute) {
+ trace_input_mouse_mode(is_absolute);
+ notifier_list_notify(&mouse_mode_notifiers, NULL);
+ }
+
+ current_is_absolute = is_absolute;
+}
+
+void qemu_add_mouse_mode_change_notifier(Notifier *notify)
+{
+ notifier_list_add(&mouse_mode_notifiers, notify);
+}
+
+void qemu_remove_mouse_mode_change_notifier(Notifier *notify)
+{
+ notifier_remove(notify);
+}
+
+MouseInfoList *qmp_query_mice(Error **errp)
+{
+ MouseInfoList *mice_list = NULL;
+ MouseInfo *info;
+ QemuInputHandlerState *s;
+ bool current = true;
+
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (!(s->handler->mask &
+ (INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS))) {
+ continue;
+ }
+
+ info = g_new0(MouseInfo, 1);
+ info->index = s->id;
+ info->name = g_strdup(s->handler->name);
+ info->absolute = s->handler->mask & INPUT_EVENT_MASK_ABS;
+ info->current = current;
+
+ current = false;
+ QAPI_LIST_PREPEND(mice_list, info);
+ }
+
+ return mice_list;
+}
+
+void hmp_mouse_set(Monitor *mon, const QDict *qdict)
+{
+ QemuInputHandlerState *s;
+ int index = qdict_get_int(qdict, "index");
+ int found = 0;
+
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (s->id != index) {
+ continue;
+ }
+ if (!(s->handler->mask & (INPUT_EVENT_MASK_REL |
+ INPUT_EVENT_MASK_ABS))) {
+ error_report("Input device '%s' is not a mouse", s->handler->name);
+ return;
+ }
+ found = 1;
+ qemu_input_handler_activate(s);
+ break;
+ }
+
+ if (!found) {
+ error_report("Mouse at index '%d' not found", index);
+ }
+
+ qemu_input_check_mode_change();
+}
diff --git a/ui/kbd-state.c b/ui/kbd-state.c
new file mode 100644
index 000000000..62d42a7a2
--- /dev/null
+++ b/ui/kbd-state.c
@@ -0,0 +1,137 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "qemu/bitmap.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/kbd-state.h"
+
+struct QKbdState {
+ QemuConsole *con;
+ int key_delay_ms;
+ DECLARE_BITMAP(keys, Q_KEY_CODE__MAX);
+ DECLARE_BITMAP(mods, QKBD_MOD__MAX);
+};
+
+static void qkbd_state_modifier_update(QKbdState *kbd,
+ QKeyCode qcode1, QKeyCode qcode2,
+ QKbdModifier mod)
+{
+ if (test_bit(qcode1, kbd->keys) || test_bit(qcode2, kbd->keys)) {
+ set_bit(mod, kbd->mods);
+ } else {
+ clear_bit(mod, kbd->mods);
+ }
+}
+
+bool qkbd_state_modifier_get(QKbdState *kbd, QKbdModifier mod)
+{
+ return test_bit(mod, kbd->mods);
+}
+
+bool qkbd_state_key_get(QKbdState *kbd, QKeyCode qcode)
+{
+ return test_bit(qcode, kbd->keys);
+}
+
+void qkbd_state_key_event(QKbdState *kbd, QKeyCode qcode, bool down)
+{
+ bool state = test_bit(qcode, kbd->keys);
+
+ if (down == false /* got key-up event */ &&
+ state == false /* key is not pressed */) {
+ /*
+ * Filter out suspicious key-up events.
+ *
+ * This allows simply sending along all key-up events, and
+ * this function will filter out everything where the
+ * corresponding key-down event wasn't sent to the guest, for
+ * example due to being a host hotkey.
+ *
+ * Note that key-down events on already pressed keys are *not*
+ * suspicious, those are keyboard autorepeat events.
+ */
+ return;
+ }
+
+ /* update key and modifier state */
+ if (down) {
+ set_bit(qcode, kbd->keys);
+ } else {
+ clear_bit(qcode, kbd->keys);
+ }
+ switch (qcode) {
+ case Q_KEY_CODE_SHIFT:
+ case Q_KEY_CODE_SHIFT_R:
+ qkbd_state_modifier_update(kbd, Q_KEY_CODE_SHIFT, Q_KEY_CODE_SHIFT_R,
+ QKBD_MOD_SHIFT);
+ break;
+ case Q_KEY_CODE_CTRL:
+ case Q_KEY_CODE_CTRL_R:
+ qkbd_state_modifier_update(kbd, Q_KEY_CODE_CTRL, Q_KEY_CODE_CTRL_R,
+ QKBD_MOD_CTRL);
+ break;
+ case Q_KEY_CODE_ALT:
+ qkbd_state_modifier_update(kbd, Q_KEY_CODE_ALT, Q_KEY_CODE_ALT,
+ QKBD_MOD_ALT);
+ break;
+ case Q_KEY_CODE_ALT_R:
+ qkbd_state_modifier_update(kbd, Q_KEY_CODE_ALT_R, Q_KEY_CODE_ALT_R,
+ QKBD_MOD_ALTGR);
+ break;
+ case Q_KEY_CODE_CAPS_LOCK:
+ if (down) {
+ change_bit(QKBD_MOD_CAPSLOCK, kbd->mods);
+ }
+ break;
+ case Q_KEY_CODE_NUM_LOCK:
+ if (down) {
+ change_bit(QKBD_MOD_NUMLOCK, kbd->mods);
+ }
+ break;
+ default:
+ /* keep gcc happy */
+ break;
+ }
+
+ /* send to guest */
+ if (qemu_console_is_graphic(kbd->con)) {
+ qemu_input_event_send_key_qcode(kbd->con, qcode, down);
+ if (kbd->key_delay_ms) {
+ qemu_input_event_send_key_delay(kbd->key_delay_ms);
+ }
+ }
+}
+
+void qkbd_state_lift_all_keys(QKbdState *kbd)
+{
+ int qcode;
+
+ for (qcode = 0; qcode < Q_KEY_CODE__MAX; qcode++) {
+ if (test_bit(qcode, kbd->keys)) {
+ qkbd_state_key_event(kbd, qcode, false);
+ }
+ }
+}
+
+void qkbd_state_set_delay(QKbdState *kbd, int delay_ms)
+{
+ kbd->key_delay_ms = delay_ms;
+}
+
+void qkbd_state_free(QKbdState *kbd)
+{
+ g_free(kbd);
+}
+
+QKbdState *qkbd_state_init(QemuConsole *con)
+{
+ QKbdState *kbd = g_new0(QKbdState, 1);
+
+ kbd->con = con;
+
+ return kbd;
+}
diff --git a/ui/keymaps.c b/ui/keymaps.c
new file mode 100644
index 000000000..d4a647464
--- /dev/null
+++ b/ui/keymaps.c
@@ -0,0 +1,273 @@
+/*
+ * QEMU keysym to keycode conversion using rdesktop keymaps
+ *
+ * Copyright (c) 2004 Johannes Schindelin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/datadir.h"
+#include "keymaps.h"
+#include "trace.h"
+#include "qemu/ctype.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "ui/input.h"
+
+struct keysym2code {
+ uint32_t count;
+ uint16_t keycodes[4];
+};
+
+struct kbd_layout_t {
+ GHashTable *hash;
+};
+
+static int get_keysym(const name2keysym_t *table,
+ const char *name)
+{
+ const name2keysym_t *p;
+ for(p = table; p->name != NULL; p++) {
+ if (!strcmp(p->name, name)) {
+ return p->keysym;
+ }
+ }
+ if (name[0] == 'U' && strlen(name) == 5) { /* try unicode Uxxxx */
+ char *end;
+ int ret = (int)strtoul(name + 1, &end, 16);
+ if (*end == '\0' && ret > 0) {
+ return ret;
+ }
+ }
+ return 0;
+}
+
+
+static void add_keysym(char *line, int keysym, int keycode, kbd_layout_t *k)
+{
+ struct keysym2code *keysym2code;
+
+ keysym2code = g_hash_table_lookup(k->hash, GINT_TO_POINTER(keysym));
+ if (keysym2code) {
+ if (keysym2code->count < ARRAY_SIZE(keysym2code->keycodes)) {
+ keysym2code->keycodes[keysym2code->count++] = keycode;
+ } else {
+ warn_report("more than %zd keycodes for keysym %d",
+ ARRAY_SIZE(keysym2code->keycodes), keysym);
+ }
+ return;
+ }
+
+ keysym2code = g_new0(struct keysym2code, 1);
+ keysym2code->keycodes[0] = keycode;
+ keysym2code->count = 1;
+ g_hash_table_replace(k->hash, GINT_TO_POINTER(keysym), keysym2code);
+ trace_keymap_add(keysym, keycode, line);
+}
+
+static int parse_keyboard_layout(kbd_layout_t *k,
+ const name2keysym_t *table,
+ const char *language, Error **errp)
+{
+ int ret;
+ FILE *f;
+ char * filename;
+ char line[1024];
+ char keyname[64];
+ int len;
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_KEYMAP, language);
+ trace_keymap_parse(filename);
+ f = filename ? fopen(filename, "r") : NULL;
+ g_free(filename);
+ if (!f) {
+ error_setg(errp, "could not read keymap file: '%s'", language);
+ return -1;
+ }
+
+ for(;;) {
+ if (fgets(line, 1024, f) == NULL) {
+ break;
+ }
+ len = strlen(line);
+ if (len > 0 && line[len - 1] == '\n') {
+ line[len - 1] = '\0';
+ }
+ if (line[0] == '#') {
+ continue;
+ }
+ if (!strncmp(line, "map ", 4)) {
+ continue;
+ }
+ if (!strncmp(line, "include ", 8)) {
+ error_setg(errp, "keymap include files are not supported any more");
+ ret = -1;
+ goto out;
+ } else {
+ int offset = 0;
+ while (line[offset] != 0 &&
+ line[offset] != ' ' &&
+ offset < sizeof(keyname) - 1) {
+ keyname[offset] = line[offset];
+ offset++;
+ }
+ keyname[offset] = 0;
+ if (strlen(keyname)) {
+ int keysym;
+ keysym = get_keysym(table, keyname);
+ if (keysym == 0) {
+ /* warn_report("unknown keysym %s", line);*/
+ } else {
+ const char *rest = line + offset + 1;
+ int keycode = strtol(rest, NULL, 0);
+
+ if (strstr(rest, "shift")) {
+ keycode |= SCANCODE_SHIFT;
+ }
+ if (strstr(rest, "altgr")) {
+ keycode |= SCANCODE_ALTGR;
+ }
+ if (strstr(rest, "ctrl")) {
+ keycode |= SCANCODE_CTRL;
+ }
+
+ add_keysym(line, keysym, keycode, k);
+
+ if (strstr(rest, "addupper")) {
+ char *c;
+ for (c = keyname; *c; c++) {
+ *c = qemu_toupper(*c);
+ }
+ keysym = get_keysym(table, keyname);
+ if (keysym) {
+ add_keysym(line, keysym,
+ keycode | SCANCODE_SHIFT, k);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ ret = 0;
+out:
+ fclose(f);
+ return ret;
+}
+
+
+kbd_layout_t *init_keyboard_layout(const name2keysym_t *table,
+ const char *language, Error **errp)
+{
+ kbd_layout_t *k;
+
+ k = g_new0(kbd_layout_t, 1);
+ k->hash = g_hash_table_new(NULL, NULL);
+ if (parse_keyboard_layout(k, table, language, errp) < 0) {
+ g_hash_table_unref(k->hash);
+ g_free(k);
+ return NULL;
+ }
+ return k;
+}
+
+
+int keysym2scancode(kbd_layout_t *k, int keysym,
+ QKbdState *kbd, bool down)
+{
+ static const uint32_t mask =
+ SCANCODE_SHIFT | SCANCODE_ALTGR | SCANCODE_CTRL;
+ uint32_t mods, i;
+ struct keysym2code *keysym2code;
+
+#ifdef XK_ISO_Left_Tab
+ if (keysym == XK_ISO_Left_Tab) {
+ keysym = XK_Tab;
+ }
+#endif
+
+ keysym2code = g_hash_table_lookup(k->hash, GINT_TO_POINTER(keysym));
+ if (!keysym2code) {
+ trace_keymap_unmapped(keysym);
+ warn_report("no scancode found for keysym %d", keysym);
+ return 0;
+ }
+
+ if (keysym2code->count == 1) {
+ return keysym2code->keycodes[0];
+ }
+
+ /* We have multiple keysym -> keycode mappings. */
+ if (down) {
+ /*
+ * On keydown: Check whenever we find one mapping where the
+ * modifier state of the mapping matches the current user
+ * interface modifier state. If so, prefer that one.
+ */
+ mods = 0;
+ if (kbd && qkbd_state_modifier_get(kbd, QKBD_MOD_SHIFT)) {
+ mods |= SCANCODE_SHIFT;
+ }
+ if (kbd && qkbd_state_modifier_get(kbd, QKBD_MOD_ALTGR)) {
+ mods |= SCANCODE_ALTGR;
+ }
+ if (kbd && qkbd_state_modifier_get(kbd, QKBD_MOD_CTRL)) {
+ mods |= SCANCODE_CTRL;
+ }
+
+ for (i = 0; i < keysym2code->count; i++) {
+ if ((keysym2code->keycodes[i] & mask) == mods) {
+ return keysym2code->keycodes[i];
+ }
+ }
+ } else {
+ /*
+ * On keyup: Try find a key which is actually down.
+ */
+ for (i = 0; i < keysym2code->count; i++) {
+ QKeyCode qcode = qemu_input_key_number_to_qcode
+ (keysym2code->keycodes[i]);
+ if (kbd && qkbd_state_key_get(kbd, qcode)) {
+ return keysym2code->keycodes[i];
+ }
+ }
+ }
+ return keysym2code->keycodes[0];
+}
+
+int keycode_is_keypad(kbd_layout_t *k, int keycode)
+{
+ if (keycode >= 0x47 && keycode <= 0x53) {
+ return true;
+ }
+ return false;
+}
+
+int keysym_is_numlock(kbd_layout_t *k, int keysym)
+{
+ switch (keysym) {
+ case 0xffb0 ... 0xffb9: /* KP_0 .. KP_9 */
+ case 0xffac: /* KP_Separator */
+ case 0xffae: /* KP_Decimal */
+ return true;
+ }
+ return false;
+}
diff --git a/ui/keymaps.h b/ui/keymaps.h
new file mode 100644
index 000000000..647340548
--- /dev/null
+++ b/ui/keymaps.h
@@ -0,0 +1,62 @@
+/*
+ * QEMU keysym to keycode conversion using rdesktop keymaps
+ *
+ * Copyright (c) 2004 Johannes Schindelin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_KEYMAPS_H
+#define QEMU_KEYMAPS_H
+
+#include "ui/kbd-state.h"
+
+typedef struct {
+ const char* name;
+ int keysym;
+} name2keysym_t;
+
+/* scancode without modifiers */
+#define SCANCODE_KEYMASK 0xff
+/* scancode without grey or up bit */
+#define SCANCODE_KEYCODEMASK 0x7f
+
+/* "grey" keys will usually need a 0xe0 prefix */
+#define SCANCODE_GREY 0x80
+#define SCANCODE_EMUL0 0xE0
+#define SCANCODE_EMUL1 0xE1
+/* "up" flag */
+#define SCANCODE_UP 0x80
+
+/* Additional modifiers to use if not catched another way. */
+#define SCANCODE_SHIFT 0x100
+#define SCANCODE_CTRL 0x200
+#define SCANCODE_ALT 0x400
+#define SCANCODE_ALTGR 0x800
+
+typedef struct kbd_layout_t kbd_layout_t;
+
+kbd_layout_t *init_keyboard_layout(const name2keysym_t *table,
+ const char *language, Error **errp);
+int keysym2scancode(kbd_layout_t *k, int keysym,
+ QKbdState *kbd, bool down);
+int keycode_is_keypad(kbd_layout_t *k, int keycode);
+int keysym_is_numlock(kbd_layout_t *k, int keysym);
+
+#endif /* QEMU_KEYMAPS_H */
diff --git a/ui/meson.build b/ui/meson.build
new file mode 100644
index 000000000..ee8ef2771
--- /dev/null
+++ b/ui/meson.build
@@ -0,0 +1,151 @@
+softmmu_ss.add(pixman)
+specific_ss.add(when: ['CONFIG_SOFTMMU'], if_true: pixman) # for the include path
+
+softmmu_ss.add(files(
+ 'clipboard.c',
+ 'console.c',
+ 'cursor.c',
+ 'input-keymap.c',
+ 'input-legacy.c',
+ 'input-barrier.c',
+ 'input.c',
+ 'kbd-state.c',
+ 'keymaps.c',
+ 'qemu-pixman.c',
+))
+softmmu_ss.add([spice_headers, files('spice-module.c')])
+softmmu_ss.add(when: spice_protocol, if_true: files('vdagent.c'))
+
+softmmu_ss.add(when: 'CONFIG_LINUX', if_true: files(
+ 'input-linux.c',
+ 'udmabuf.c',
+))
+softmmu_ss.add(when: cocoa, if_true: files('cocoa.m'))
+
+vnc_ss = ss.source_set()
+vnc_ss.add(files(
+ 'vnc.c',
+ 'vnc-enc-zlib.c',
+ 'vnc-enc-hextile.c',
+ 'vnc-enc-tight.c',
+ 'vnc-palette.c',
+ 'vnc-enc-zrle.c',
+ 'vnc-auth-vencrypt.c',
+ 'vnc-ws.c',
+ 'vnc-jobs.c',
+ 'vnc-clipboard.c',
+))
+vnc_ss.add(zlib, png, jpeg, gnutls)
+vnc_ss.add(when: sasl, if_true: files('vnc-auth-sasl.c'))
+softmmu_ss.add_all(when: vnc, if_true: vnc_ss)
+softmmu_ss.add(when: vnc, if_false: files('vnc-stubs.c'))
+specific_ss.add(when: ['CONFIG_SOFTMMU'], if_true: opengl)
+
+ui_modules = {}
+
+if curses.found()
+ curses_ss = ss.source_set()
+ curses_ss.add(when: [curses, iconv], if_true: [files('curses.c'), pixman])
+ ui_modules += {'curses' : curses_ss}
+endif
+
+if config_host.has_key('CONFIG_OPENGL')
+ opengl_ss = ss.source_set()
+ opengl_ss.add(gbm)
+ opengl_ss.add(when: [opengl, pixman, 'CONFIG_OPENGL'],
+ if_true: files('shader.c', 'console-gl.c', 'egl-helpers.c', 'egl-context.c'))
+ ui_modules += {'opengl' : opengl_ss}
+endif
+
+if config_host.has_key('CONFIG_OPENGL') and gbm.found()
+ egl_headless_ss = ss.source_set()
+ egl_headless_ss.add(when: [opengl, gbm, pixman, 'CONFIG_OPENGL'],
+ if_true: files('egl-headless.c'))
+ ui_modules += {'egl-headless' : egl_headless_ss}
+endif
+
+if gtk.found()
+ softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
+
+ gtk_ss = ss.source_set()
+ gtk_ss.add(gtk, vte, pixman, files('gtk.c', 'gtk-clipboard.c'))
+ gtk_ss.add(when: x11, if_true: files('x_keymap.c'))
+ gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: files('gtk-gl-area.c'))
+ gtk_ss.add(when: [x11, opengl, 'CONFIG_OPENGL'], if_true: files('gtk-egl.c'))
+ ui_modules += {'gtk' : gtk_ss}
+endif
+
+if sdl.found()
+ softmmu_ss.add(when: 'CONFIG_WIN32', if_true: files('win32-kbd-hook.c'))
+
+ sdl_ss = ss.source_set()
+ sdl_ss.add(sdl, sdl_image, pixman, glib, files(
+ 'sdl2-2d.c',
+ 'sdl2-input.c',
+ 'sdl2.c',
+ ))
+ sdl_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: files('sdl2-gl.c'))
+ sdl_ss.add(when: x11, if_true: files('x_keymap.c'))
+ ui_modules += {'sdl' : sdl_ss}
+endif
+
+if spice.found()
+ spice_core_ss = ss.source_set()
+ spice_core_ss.add(spice, pixman, files(
+ 'spice-core.c',
+ 'spice-input.c',
+ 'spice-display.c'
+ ))
+ ui_modules += {'spice-core' : spice_core_ss}
+endif
+
+if spice.found() and config_host.has_key('CONFIG_GIO')
+ spice_ss = ss.source_set()
+ spice_ss.add(spice, gio, pixman, files('spice-app.c'))
+ ui_modules += {'spice-app': spice_ss}
+endif
+
+keymaps = [
+ ['atset1', 'qcode'],
+ ['linux', 'qcode'],
+ ['qcode', 'atset1'],
+ ['qcode', 'atset2'],
+ ['qcode', 'atset3'],
+ ['qcode', 'linux'],
+ ['qcode', 'qnum'],
+ ['qcode', 'sun'],
+ ['qnum', 'qcode'],
+ ['usb', 'qcode'],
+ ['win32', 'qcode'],
+ ['x11', 'qcode'],
+ ['xorgevdev', 'qcode'],
+ ['xorgkbd', 'qcode'],
+ ['xorgxquartz', 'qcode'],
+ ['xorgxwin', 'qcode'],
+ ['osx', 'qcode'],
+]
+
+if have_system or xkbcommon.found()
+ foreach e : keymaps
+ output = 'input-keymap-@0@-to-@1@.c.inc'.format(e[0], e[1])
+ genh += custom_target(output,
+ output: output,
+ capture: true,
+ input: files('keycodemapdb/data/keymaps.csv'),
+ command: [python, files('keycodemapdb/tools/keymap-gen'),
+ 'code-map',
+ '--lang', 'glib2',
+ '--varname', 'qemu_input_map_@0@_to_@1@'.format(e[0], e[1]),
+ '@INPUT0@', e[0], e[1]])
+ endforeach
+endif
+
+subdir('shader')
+
+if have_system
+ subdir('icons')
+
+ install_data('qemu.desktop', install_dir: qemu_desktopdir)
+endif
+
+modules += {'ui': ui_modules}
diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c
new file mode 100644
index 000000000..3ab7e2e95
--- /dev/null
+++ b/ui/qemu-pixman.c
@@ -0,0 +1,280 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "standard-headers/drm/drm_fourcc.h"
+
+PixelFormat qemu_pixelformat_from_pixman(pixman_format_code_t format)
+{
+ PixelFormat pf;
+ uint8_t bpp;
+
+ bpp = pf.bits_per_pixel = PIXMAN_FORMAT_BPP(format);
+ pf.bytes_per_pixel = PIXMAN_FORMAT_BPP(format) / 8;
+ pf.depth = PIXMAN_FORMAT_DEPTH(format);
+
+ pf.abits = PIXMAN_FORMAT_A(format);
+ pf.rbits = PIXMAN_FORMAT_R(format);
+ pf.gbits = PIXMAN_FORMAT_G(format);
+ pf.bbits = PIXMAN_FORMAT_B(format);
+
+ switch (PIXMAN_FORMAT_TYPE(format)) {
+ case PIXMAN_TYPE_ARGB:
+ pf.ashift = pf.bbits + pf.gbits + pf.rbits;
+ pf.rshift = pf.bbits + pf.gbits;
+ pf.gshift = pf.bbits;
+ pf.bshift = 0;
+ break;
+ case PIXMAN_TYPE_ABGR:
+ pf.ashift = pf.rbits + pf.gbits + pf.bbits;
+ pf.bshift = pf.rbits + pf.gbits;
+ pf.gshift = pf.rbits;
+ pf.rshift = 0;
+ break;
+ case PIXMAN_TYPE_BGRA:
+ pf.bshift = bpp - pf.bbits;
+ pf.gshift = bpp - (pf.bbits + pf.gbits);
+ pf.rshift = bpp - (pf.bbits + pf.gbits + pf.rbits);
+ pf.ashift = 0;
+ break;
+ case PIXMAN_TYPE_RGBA:
+ pf.rshift = bpp - pf.rbits;
+ pf.gshift = bpp - (pf.rbits + pf.gbits);
+ pf.bshift = bpp - (pf.rbits + pf.gbits + pf.bbits);
+ pf.ashift = 0;
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+
+ pf.amax = (1 << pf.abits) - 1;
+ pf.rmax = (1 << pf.rbits) - 1;
+ pf.gmax = (1 << pf.gbits) - 1;
+ pf.bmax = (1 << pf.bbits) - 1;
+ pf.amask = pf.amax << pf.ashift;
+ pf.rmask = pf.rmax << pf.rshift;
+ pf.gmask = pf.gmax << pf.gshift;
+ pf.bmask = pf.bmax << pf.bshift;
+
+ return pf;
+}
+
+pixman_format_code_t qemu_default_pixman_format(int bpp, bool native_endian)
+{
+ if (native_endian) {
+ switch (bpp) {
+ case 15:
+ return PIXMAN_x1r5g5b5;
+ case 16:
+ return PIXMAN_r5g6b5;
+ case 24:
+ return PIXMAN_r8g8b8;
+ case 32:
+ return PIXMAN_x8r8g8b8;
+ }
+ } else {
+ switch (bpp) {
+ case 24:
+ return PIXMAN_b8g8r8;
+ case 32:
+ return PIXMAN_b8g8r8x8;
+ break;
+ }
+ }
+ return 0;
+}
+
+/* Note: drm is little endian, pixman is native endian */
+static const struct {
+ uint32_t drm_format;
+ pixman_format_code_t pixman_format;
+} drm_format_pixman_map[] = {
+ { DRM_FORMAT_RGB888, PIXMAN_LE_r8g8b8 },
+ { DRM_FORMAT_ARGB8888, PIXMAN_LE_a8r8g8b8 },
+ { DRM_FORMAT_XRGB8888, PIXMAN_LE_x8r8g8b8 }
+};
+
+pixman_format_code_t qemu_drm_format_to_pixman(uint32_t drm_format)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(drm_format_pixman_map); i++) {
+ if (drm_format == drm_format_pixman_map[i].drm_format) {
+ return drm_format_pixman_map[i].pixman_format;
+ }
+ }
+ return 0;
+}
+
+uint32_t qemu_pixman_to_drm_format(pixman_format_code_t pixman_format)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(drm_format_pixman_map); i++) {
+ if (pixman_format == drm_format_pixman_map[i].pixman_format) {
+ return drm_format_pixman_map[i].drm_format;
+ }
+ }
+ return 0;
+}
+
+int qemu_pixman_get_type(int rshift, int gshift, int bshift)
+{
+ int type = PIXMAN_TYPE_OTHER;
+
+ if (rshift > gshift && gshift > bshift) {
+ if (bshift == 0) {
+ type = PIXMAN_TYPE_ARGB;
+ } else {
+ type = PIXMAN_TYPE_RGBA;
+ }
+ } else if (rshift < gshift && gshift < bshift) {
+ if (rshift == 0) {
+ type = PIXMAN_TYPE_ABGR;
+ } else {
+ type = PIXMAN_TYPE_BGRA;
+ }
+ }
+ return type;
+}
+
+pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf)
+{
+ pixman_format_code_t format;
+ int type;
+
+ type = qemu_pixman_get_type(pf->rshift, pf->gshift, pf->bshift);
+ format = PIXMAN_FORMAT(pf->bits_per_pixel, type,
+ pf->abits, pf->rbits, pf->gbits, pf->bbits);
+ if (!pixman_format_supported_source(format)) {
+ return 0;
+ }
+ return format;
+}
+
+/*
+ * Return true for known-good pixman conversions.
+ *
+ * UIs using pixman for format conversion can hook this into
+ * DisplayChangeListenerOps->dpy_gfx_check_format
+ */
+bool qemu_pixman_check_format(DisplayChangeListener *dcl,
+ pixman_format_code_t format)
+{
+ switch (format) {
+ /* 32 bpp */
+ case PIXMAN_x8r8g8b8:
+ case PIXMAN_a8r8g8b8:
+ case PIXMAN_b8g8r8x8:
+ case PIXMAN_b8g8r8a8:
+ /* 24 bpp */
+ case PIXMAN_r8g8b8:
+ case PIXMAN_b8g8r8:
+ /* 16 bpp */
+ case PIXMAN_x1r5g5b5:
+ case PIXMAN_r5g6b5:
+ return true;
+ default:
+ return false;
+ }
+}
+
+pixman_image_t *qemu_pixman_linebuf_create(pixman_format_code_t format,
+ int width)
+{
+ pixman_image_t *image = pixman_image_create_bits(format, width, 1, NULL, 0);
+ assert(image != NULL);
+ return image;
+}
+
+/* fill linebuf from framebuffer */
+void qemu_pixman_linebuf_fill(pixman_image_t *linebuf, pixman_image_t *fb,
+ int width, int x, int y)
+{
+ pixman_image_composite(PIXMAN_OP_SRC, fb, NULL, linebuf,
+ x, y, 0, 0, 0, 0, width, 1);
+}
+
+/* copy linebuf to framebuffer */
+void qemu_pixman_linebuf_copy(pixman_image_t *fb, int width, int x, int y,
+ pixman_image_t *linebuf)
+{
+ pixman_image_composite(PIXMAN_OP_SRC, linebuf, NULL, fb,
+ 0, 0, 0, 0, x, y, width, 1);
+}
+
+pixman_image_t *qemu_pixman_mirror_create(pixman_format_code_t format,
+ pixman_image_t *image)
+{
+ return pixman_image_create_bits(format,
+ pixman_image_get_width(image),
+ pixman_image_get_height(image),
+ NULL,
+ pixman_image_get_stride(image));
+}
+
+void qemu_pixman_image_unref(pixman_image_t *image)
+{
+ if (image == NULL) {
+ return;
+ }
+ pixman_image_unref(image);
+}
+
+pixman_color_t qemu_pixman_color(PixelFormat *pf, uint32_t color)
+{
+ pixman_color_t c;
+
+ c.red = ((color & pf->rmask) >> pf->rshift) << (16 - pf->rbits);
+ c.green = ((color & pf->gmask) >> pf->gshift) << (16 - pf->gbits);
+ c.blue = ((color & pf->bmask) >> pf->bshift) << (16 - pf->bbits);
+ c.alpha = ((color & pf->amask) >> pf->ashift) << (16 - pf->abits);
+ return c;
+}
+
+pixman_image_t *qemu_pixman_glyph_from_vgafont(int height, const uint8_t *font,
+ unsigned int ch)
+{
+ pixman_image_t *glyph;
+ uint8_t *data;
+ bool bit;
+ int x, y;
+
+ glyph = pixman_image_create_bits(PIXMAN_a8, 8, height,
+ NULL, 0);
+ data = (uint8_t *)pixman_image_get_data(glyph);
+
+ font += height * ch;
+ for (y = 0; y < height; y++, font++) {
+ for (x = 0; x < 8; x++, data++) {
+ bit = (*font) & (1 << (7-x));
+ *data = bit ? 0xff : 0x00;
+ }
+ }
+ return glyph;
+}
+
+void qemu_pixman_glyph_render(pixman_image_t *glyph,
+ pixman_image_t *surface,
+ pixman_color_t *fgcol,
+ pixman_color_t *bgcol,
+ int x, int y, int cw, int ch)
+{
+ pixman_image_t *ifg = pixman_image_create_solid_fill(fgcol);
+ pixman_image_t *ibg = pixman_image_create_solid_fill(bgcol);
+
+ pixman_image_composite(PIXMAN_OP_SRC, ibg, NULL, surface,
+ 0, 0, 0, 0,
+ cw * x, ch * y,
+ cw, ch);
+ pixman_image_composite(PIXMAN_OP_OVER, ifg, glyph, surface,
+ 0, 0, 0, 0,
+ cw * x, ch * y,
+ cw, ch);
+ pixman_image_unref(ifg);
+ pixman_image_unref(ibg);
+}
diff --git a/ui/qemu-x509.h b/ui/qemu-x509.h
new file mode 100644
index 000000000..095aec161
--- /dev/null
+++ b/ui/qemu-x509.h
@@ -0,0 +1,9 @@
+#ifndef QEMU_X509_H
+#define QEMU_X509_H
+
+#define X509_CA_CERT_FILE "ca-cert.pem"
+#define X509_CA_CRL_FILE "ca-crl.pem"
+#define X509_SERVER_KEY_FILE "server-key.pem"
+#define X509_SERVER_CERT_FILE "server-cert.pem"
+
+#endif /* QEMU_X509_H */
diff --git a/ui/qemu.desktop b/ui/qemu.desktop
new file mode 100644
index 000000000..20f09f56b
--- /dev/null
+++ b/ui/qemu.desktop
@@ -0,0 +1,8 @@
+[Desktop Entry]
+Version=1.0
+Name=QEMU
+Icon=qemu
+Type=Application
+Terminal=false
+Keywords=Emulators;Virtualization;KVM;
+NoDisplay=true
diff --git a/ui/sdl2-2d.c b/ui/sdl2-2d.c
new file mode 100644
index 000000000..bfebbdeae
--- /dev/null
+++ b/ui/sdl2-2d.c
@@ -0,0 +1,165 @@
+/*
+ * QEMU SDL display driver
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/* Ported SDL 1.2 code to 2.0 by Dave Airlie. */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/sdl2.h"
+
+void sdl2_2d_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ DisplaySurface *surf = scon->surface;
+ SDL_Rect rect;
+ size_t surface_data_offset;
+ assert(!scon->opengl);
+
+ if (!scon->texture) {
+ return;
+ }
+
+ surface_data_offset = surface_bytes_per_pixel(surf) * x +
+ surface_stride(surf) * y;
+ rect.x = x;
+ rect.y = y;
+ rect.w = w;
+ rect.h = h;
+
+ SDL_UpdateTexture(scon->texture, &rect,
+ surface_data(surf) + surface_data_offset,
+ surface_stride(surf));
+ SDL_RenderClear(scon->real_renderer);
+ SDL_RenderCopy(scon->real_renderer, scon->texture, NULL, NULL);
+ SDL_RenderPresent(scon->real_renderer);
+}
+
+void sdl2_2d_switch(DisplayChangeListener *dcl,
+ DisplaySurface *new_surface)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ DisplaySurface *old_surface = scon->surface;
+ int format = 0;
+
+ assert(!scon->opengl);
+
+ scon->surface = new_surface;
+
+ if (scon->texture) {
+ SDL_DestroyTexture(scon->texture);
+ scon->texture = NULL;
+ }
+
+ if (is_placeholder(new_surface) && qemu_console_get_index(dcl->con)) {
+ sdl2_window_destroy(scon);
+ return;
+ }
+
+ if (!scon->real_window) {
+ sdl2_window_create(scon);
+ } else if (old_surface &&
+ ((surface_width(old_surface) != surface_width(new_surface)) ||
+ (surface_height(old_surface) != surface_height(new_surface)))) {
+ sdl2_window_resize(scon);
+ }
+
+ SDL_RenderSetLogicalSize(scon->real_renderer,
+ surface_width(new_surface),
+ surface_height(new_surface));
+
+ switch (surface_format(scon->surface)) {
+ case PIXMAN_x1r5g5b5:
+ format = SDL_PIXELFORMAT_ARGB1555;
+ break;
+ case PIXMAN_r5g6b5:
+ format = SDL_PIXELFORMAT_RGB565;
+ break;
+ case PIXMAN_a8r8g8b8:
+ case PIXMAN_x8r8g8b8:
+ format = SDL_PIXELFORMAT_ARGB8888;
+ break;
+ case PIXMAN_a8b8g8r8:
+ case PIXMAN_x8b8g8r8:
+ format = SDL_PIXELFORMAT_ABGR8888;
+ break;
+ case PIXMAN_r8g8b8a8:
+ case PIXMAN_r8g8b8x8:
+ format = SDL_PIXELFORMAT_RGBA8888;
+ break;
+ case PIXMAN_b8g8r8x8:
+ format = SDL_PIXELFORMAT_BGRX8888;
+ break;
+ case PIXMAN_b8g8r8a8:
+ format = SDL_PIXELFORMAT_BGRA8888;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+ scon->texture = SDL_CreateTexture(scon->real_renderer, format,
+ SDL_TEXTUREACCESS_STREAMING,
+ surface_width(new_surface),
+ surface_height(new_surface));
+ sdl2_2d_redraw(scon);
+}
+
+void sdl2_2d_refresh(DisplayChangeListener *dcl)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+
+ assert(!scon->opengl);
+ graphic_hw_update(dcl->con);
+ sdl2_poll_events(scon);
+}
+
+void sdl2_2d_redraw(struct sdl2_console *scon)
+{
+ assert(!scon->opengl);
+
+ if (!scon->surface) {
+ return;
+ }
+ sdl2_2d_update(&scon->dcl, 0, 0,
+ surface_width(scon->surface),
+ surface_height(scon->surface));
+}
+
+bool sdl2_2d_check_format(DisplayChangeListener *dcl,
+ pixman_format_code_t format)
+{
+ /*
+ * We let SDL convert for us a few more formats than,
+ * the native ones. Thes are the ones I have tested.
+ */
+ return (format == PIXMAN_x8r8g8b8 ||
+ format == PIXMAN_a8r8g8b8 ||
+ format == PIXMAN_a8b8g8r8 ||
+ format == PIXMAN_x8b8g8r8 ||
+ format == PIXMAN_b8g8r8x8 ||
+ format == PIXMAN_b8g8r8a8 ||
+ format == PIXMAN_r8g8b8x8 ||
+ format == PIXMAN_r8g8b8a8 ||
+ format == PIXMAN_x1r5g5b5 ||
+ format == PIXMAN_r5g6b5);
+}
diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c
new file mode 100644
index 000000000..a21d2deed
--- /dev/null
+++ b/ui/sdl2-gl.c
@@ -0,0 +1,245 @@
+/*
+ * QEMU SDL display driver -- opengl support
+ *
+ * Copyright (c) 2014 Red Hat
+ *
+ * Authors:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/sdl2.h"
+
+static void sdl2_set_scanout_mode(struct sdl2_console *scon, bool scanout)
+{
+ if (scon->scanout_mode == scanout) {
+ return;
+ }
+
+ scon->scanout_mode = scanout;
+ if (!scon->scanout_mode) {
+ egl_fb_destroy(&scon->guest_fb);
+ if (scon->surface) {
+ surface_gl_destroy_texture(scon->gls, scon->surface);
+ surface_gl_create_texture(scon->gls, scon->surface);
+ }
+ }
+}
+
+static void sdl2_gl_render_surface(struct sdl2_console *scon)
+{
+ int ww, wh;
+
+ SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
+ sdl2_set_scanout_mode(scon, false);
+
+ SDL_GetWindowSize(scon->real_window, &ww, &wh);
+ surface_gl_setup_viewport(scon->gls, scon->surface, ww, wh);
+
+ surface_gl_render_texture(scon->gls, scon->surface);
+ SDL_GL_SwapWindow(scon->real_window);
+ graphic_hw_gl_flushed(scon->dcl.con);
+}
+
+void sdl2_gl_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+
+ assert(scon->opengl);
+
+ SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
+ surface_gl_update_texture(scon->gls, scon->surface, x, y, w, h);
+ scon->updates++;
+}
+
+void sdl2_gl_switch(DisplayChangeListener *dcl,
+ DisplaySurface *new_surface)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ DisplaySurface *old_surface = scon->surface;
+
+ assert(scon->opengl);
+
+ SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
+ surface_gl_destroy_texture(scon->gls, scon->surface);
+
+ scon->surface = new_surface;
+
+ if (is_placeholder(new_surface) && qemu_console_get_index(dcl->con)) {
+ qemu_gl_fini_shader(scon->gls);
+ scon->gls = NULL;
+ sdl2_window_destroy(scon);
+ return;
+ }
+
+ if (!scon->real_window) {
+ sdl2_window_create(scon);
+ scon->gls = qemu_gl_init_shader();
+ } else if (old_surface &&
+ ((surface_width(old_surface) != surface_width(new_surface)) ||
+ (surface_height(old_surface) != surface_height(new_surface)))) {
+ sdl2_window_resize(scon);
+ }
+
+ surface_gl_create_texture(scon->gls, scon->surface);
+}
+
+void sdl2_gl_refresh(DisplayChangeListener *dcl)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+
+ assert(scon->opengl);
+
+ graphic_hw_update(dcl->con);
+ if (scon->updates && scon->real_window) {
+ scon->updates = 0;
+ sdl2_gl_render_surface(scon);
+ }
+ sdl2_poll_events(scon);
+}
+
+void sdl2_gl_redraw(struct sdl2_console *scon)
+{
+ assert(scon->opengl);
+
+ if (scon->scanout_mode) {
+ /* sdl2_gl_scanout_flush actually only care about
+ * the first argument. */
+ return sdl2_gl_scanout_flush(&scon->dcl, 0, 0, 0, 0);
+ }
+ if (scon->surface) {
+ sdl2_gl_render_surface(scon);
+ }
+}
+
+QEMUGLContext sdl2_gl_create_context(DisplayChangeListener *dcl,
+ QEMUGLParams *params)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ SDL_GLContext ctx;
+
+ assert(scon->opengl);
+
+ SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
+
+ SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
+ if (scon->opts->gl == DISPLAYGL_MODE_ON ||
+ scon->opts->gl == DISPLAYGL_MODE_CORE) {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
+ SDL_GL_CONTEXT_PROFILE_CORE);
+ } else if (scon->opts->gl == DISPLAYGL_MODE_ES) {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
+ SDL_GL_CONTEXT_PROFILE_ES);
+ }
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, params->major_ver);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, params->minor_ver);
+
+ ctx = SDL_GL_CreateContext(scon->real_window);
+
+ /* If SDL fail to create a GL context and we use the "on" flag,
+ * then try to fallback to GLES.
+ */
+ if (!ctx && scon->opts->gl == DISPLAYGL_MODE_ON) {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
+ SDL_GL_CONTEXT_PROFILE_ES);
+ ctx = SDL_GL_CreateContext(scon->real_window);
+ }
+ return (QEMUGLContext)ctx;
+}
+
+void sdl2_gl_destroy_context(DisplayChangeListener *dcl, QEMUGLContext ctx)
+{
+ SDL_GLContext sdlctx = (SDL_GLContext)ctx;
+
+ SDL_GL_DeleteContext(sdlctx);
+}
+
+int sdl2_gl_make_context_current(DisplayChangeListener *dcl,
+ QEMUGLContext ctx)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ SDL_GLContext sdlctx = (SDL_GLContext)ctx;
+
+ assert(scon->opengl);
+
+ return SDL_GL_MakeCurrent(scon->real_window, sdlctx);
+}
+
+void sdl2_gl_scanout_disable(DisplayChangeListener *dcl)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+
+ assert(scon->opengl);
+ scon->w = 0;
+ scon->h = 0;
+ sdl2_set_scanout_mode(scon, false);
+}
+
+void sdl2_gl_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t backing_id,
+ bool backing_y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+
+ assert(scon->opengl);
+ scon->x = x;
+ scon->y = y;
+ scon->w = w;
+ scon->h = h;
+ scon->y0_top = backing_y_0_top;
+
+ SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
+
+ sdl2_set_scanout_mode(scon, true);
+ egl_fb_setup_for_tex(&scon->guest_fb, backing_width, backing_height,
+ backing_id, false);
+}
+
+void sdl2_gl_scanout_flush(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+ int ww, wh;
+
+ assert(scon->opengl);
+ if (!scon->scanout_mode) {
+ return;
+ }
+ if (!scon->guest_fb.framebuffer) {
+ return;
+ }
+
+ SDL_GL_MakeCurrent(scon->real_window, scon->winctx);
+
+ SDL_GetWindowSize(scon->real_window, &ww, &wh);
+ egl_fb_setup_default(&scon->win_fb, ww, wh);
+ egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top);
+
+ SDL_GL_SwapWindow(scon->real_window);
+ graphic_hw_gl_flushed(dcl->con);
+}
diff --git a/ui/sdl2-input.c b/ui/sdl2-input.c
new file mode 100644
index 000000000..f06838220
--- /dev/null
+++ b/ui/sdl2-input.c
@@ -0,0 +1,59 @@
+/*
+ * QEMU SDL display driver
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/* Ported SDL 1.2 code to 2.0 by Dave Airlie. */
+
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/sdl2.h"
+#include "trace.h"
+
+void sdl2_process_key(struct sdl2_console *scon,
+ SDL_KeyboardEvent *ev)
+{
+ int qcode;
+ QemuConsole *con = scon->dcl.con;
+
+ if (ev->keysym.scancode >= qemu_input_map_usb_to_qcode_len) {
+ return;
+ }
+ qcode = qemu_input_map_usb_to_qcode[ev->keysym.scancode];
+ trace_sdl2_process_key(ev->keysym.scancode, qcode,
+ ev->type == SDL_KEYDOWN ? "down" : "up");
+ qkbd_state_key_event(scon->kbd, qcode, ev->type == SDL_KEYDOWN);
+
+ if (!qemu_console_is_graphic(con)) {
+ bool ctrl = qkbd_state_modifier_get(scon->kbd, QKBD_MOD_CTRL);
+ if (ev->type == SDL_KEYDOWN) {
+ switch (qcode) {
+ case Q_KEY_CODE_RET:
+ kbd_put_keysym_console(con, '\n');
+ break;
+ default:
+ kbd_put_qcode_console(con, qcode, ctrl);
+ break;
+ }
+ }
+ }
+}
diff --git a/ui/sdl2.c b/ui/sdl2.c
new file mode 100644
index 000000000..17c0ec30e
--- /dev/null
+++ b/ui/sdl2.c
@@ -0,0 +1,927 @@
+/*
+ * QEMU SDL display driver
+ *
+ * Copyright (c) 2003 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+/* Ported SDL 1.2 code to 2.0 by Dave Airlie. */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/cutils.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "ui/sdl2.h"
+#include "sysemu/runstate.h"
+#include "sysemu/runstate-action.h"
+#include "sysemu/sysemu.h"
+#include "ui/win32-kbd-hook.h"
+
+static int sdl2_num_outputs;
+static struct sdl2_console *sdl2_console;
+
+static SDL_Surface *guest_sprite_surface;
+static int gui_grab; /* if true, all keyboard/mouse events are grabbed */
+
+static int gui_saved_grab;
+static int gui_fullscreen;
+static int gui_grab_code = KMOD_LALT | KMOD_LCTRL;
+static SDL_Cursor *sdl_cursor_normal;
+static SDL_Cursor *sdl_cursor_hidden;
+static int absolute_enabled;
+static int guest_cursor;
+static int guest_x, guest_y;
+static SDL_Cursor *guest_sprite;
+static Notifier mouse_mode_notifier;
+
+#define SDL2_REFRESH_INTERVAL_BUSY 10
+#define SDL2_MAX_IDLE_COUNT (2 * GUI_REFRESH_INTERVAL_DEFAULT \
+ / SDL2_REFRESH_INTERVAL_BUSY + 1)
+
+static void sdl_update_caption(struct sdl2_console *scon);
+
+static struct sdl2_console *get_scon_from_window(uint32_t window_id)
+{
+ int i;
+ for (i = 0; i < sdl2_num_outputs; i++) {
+ if (sdl2_console[i].real_window == SDL_GetWindowFromID(window_id)) {
+ return &sdl2_console[i];
+ }
+ }
+ return NULL;
+}
+
+void sdl2_window_create(struct sdl2_console *scon)
+{
+ int flags = 0;
+
+ if (!scon->surface) {
+ return;
+ }
+ assert(!scon->real_window);
+
+ if (gui_fullscreen) {
+ flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+ } else {
+ flags |= SDL_WINDOW_RESIZABLE;
+ }
+ if (scon->hidden) {
+ flags |= SDL_WINDOW_HIDDEN;
+ }
+#ifdef CONFIG_OPENGL
+ if (scon->opengl) {
+ flags |= SDL_WINDOW_OPENGL;
+ }
+#endif
+
+ scon->real_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ surface_width(scon->surface),
+ surface_height(scon->surface),
+ flags);
+ scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0);
+ if (scon->opengl) {
+ scon->winctx = SDL_GL_GetCurrentContext();
+ }
+ sdl_update_caption(scon);
+}
+
+void sdl2_window_destroy(struct sdl2_console *scon)
+{
+ if (!scon->real_window) {
+ return;
+ }
+
+ SDL_DestroyRenderer(scon->real_renderer);
+ scon->real_renderer = NULL;
+ SDL_DestroyWindow(scon->real_window);
+ scon->real_window = NULL;
+}
+
+void sdl2_window_resize(struct sdl2_console *scon)
+{
+ if (!scon->real_window) {
+ return;
+ }
+
+ SDL_SetWindowSize(scon->real_window,
+ surface_width(scon->surface),
+ surface_height(scon->surface));
+}
+
+static void sdl2_redraw(struct sdl2_console *scon)
+{
+ if (scon->opengl) {
+#ifdef CONFIG_OPENGL
+ sdl2_gl_redraw(scon);
+#endif
+ } else {
+ sdl2_2d_redraw(scon);
+ }
+}
+
+static void sdl_update_caption(struct sdl2_console *scon)
+{
+ char win_title[1024];
+ char icon_title[1024];
+ const char *status = "";
+
+ if (!runstate_is_running()) {
+ status = " [Stopped]";
+ } else if (gui_grab) {
+ if (alt_grab) {
+ status = " - Press Ctrl-Alt-Shift-G to exit grab";
+ } else if (ctrl_grab) {
+ status = " - Press Right-Ctrl-G to exit grab";
+ } else {
+ status = " - Press Ctrl-Alt-G to exit grab";
+ }
+ }
+
+ if (qemu_name) {
+ snprintf(win_title, sizeof(win_title), "QEMU (%s-%d)%s", qemu_name,
+ scon->idx, status);
+ snprintf(icon_title, sizeof(icon_title), "QEMU (%s)", qemu_name);
+ } else {
+ snprintf(win_title, sizeof(win_title), "QEMU%s", status);
+ snprintf(icon_title, sizeof(icon_title), "QEMU");
+ }
+
+ if (scon->real_window) {
+ SDL_SetWindowTitle(scon->real_window, win_title);
+ }
+}
+
+static void sdl_hide_cursor(struct sdl2_console *scon)
+{
+ if (scon->opts->has_show_cursor && scon->opts->show_cursor) {
+ return;
+ }
+
+ SDL_ShowCursor(SDL_DISABLE);
+ SDL_SetCursor(sdl_cursor_hidden);
+
+ if (!qemu_input_is_absolute()) {
+ SDL_SetRelativeMouseMode(SDL_TRUE);
+ }
+}
+
+static void sdl_show_cursor(struct sdl2_console *scon)
+{
+ if (scon->opts->has_show_cursor && scon->opts->show_cursor) {
+ return;
+ }
+
+ if (!qemu_input_is_absolute()) {
+ SDL_SetRelativeMouseMode(SDL_FALSE);
+ }
+
+ if (guest_cursor &&
+ (gui_grab || qemu_input_is_absolute() || absolute_enabled)) {
+ SDL_SetCursor(guest_sprite);
+ } else {
+ SDL_SetCursor(sdl_cursor_normal);
+ }
+
+ SDL_ShowCursor(SDL_ENABLE);
+}
+
+static void sdl_grab_start(struct sdl2_console *scon)
+{
+ QemuConsole *con = scon ? scon->dcl.con : NULL;
+
+ if (!con || !qemu_console_is_graphic(con)) {
+ return;
+ }
+ /*
+ * If the application is not active, do not try to enter grab state. This
+ * prevents 'SDL_WM_GrabInput(SDL_GRAB_ON)' from blocking all the
+ * application (SDL bug).
+ */
+ if (!(SDL_GetWindowFlags(scon->real_window) & SDL_WINDOW_INPUT_FOCUS)) {
+ return;
+ }
+ if (guest_cursor) {
+ SDL_SetCursor(guest_sprite);
+ if (!qemu_input_is_absolute() && !absolute_enabled) {
+ SDL_WarpMouseInWindow(scon->real_window, guest_x, guest_y);
+ }
+ } else {
+ sdl_hide_cursor(scon);
+ }
+ SDL_SetWindowGrab(scon->real_window, SDL_TRUE);
+ gui_grab = 1;
+ win32_kbd_set_grab(true);
+ sdl_update_caption(scon);
+}
+
+static void sdl_grab_end(struct sdl2_console *scon)
+{
+ SDL_SetWindowGrab(scon->real_window, SDL_FALSE);
+ gui_grab = 0;
+ win32_kbd_set_grab(false);
+ sdl_show_cursor(scon);
+ sdl_update_caption(scon);
+}
+
+static void absolute_mouse_grab(struct sdl2_console *scon)
+{
+ int mouse_x, mouse_y;
+ int scr_w, scr_h;
+ SDL_GetMouseState(&mouse_x, &mouse_y);
+ SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
+ if (mouse_x > 0 && mouse_x < scr_w - 1 &&
+ mouse_y > 0 && mouse_y < scr_h - 1) {
+ sdl_grab_start(scon);
+ }
+}
+
+static void sdl_mouse_mode_change(Notifier *notify, void *data)
+{
+ if (qemu_input_is_absolute()) {
+ if (!absolute_enabled) {
+ absolute_enabled = 1;
+ SDL_SetRelativeMouseMode(SDL_FALSE);
+ absolute_mouse_grab(&sdl2_console[0]);
+ }
+ } else if (absolute_enabled) {
+ if (!gui_fullscreen) {
+ sdl_grab_end(&sdl2_console[0]);
+ }
+ absolute_enabled = 0;
+ }
+}
+
+static void sdl_send_mouse_event(struct sdl2_console *scon, int dx, int dy,
+ int x, int y, int state)
+{
+ static uint32_t bmap[INPUT_BUTTON__MAX] = {
+ [INPUT_BUTTON_LEFT] = SDL_BUTTON(SDL_BUTTON_LEFT),
+ [INPUT_BUTTON_MIDDLE] = SDL_BUTTON(SDL_BUTTON_MIDDLE),
+ [INPUT_BUTTON_RIGHT] = SDL_BUTTON(SDL_BUTTON_RIGHT),
+ [INPUT_BUTTON_SIDE] = SDL_BUTTON(SDL_BUTTON_X1),
+ [INPUT_BUTTON_EXTRA] = SDL_BUTTON(SDL_BUTTON_X2)
+ };
+ static uint32_t prev_state;
+
+ if (prev_state != state) {
+ qemu_input_update_buttons(scon->dcl.con, bmap, prev_state, state);
+ prev_state = state;
+ }
+
+ if (qemu_input_is_absolute()) {
+ qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X,
+ x, 0, surface_width(scon->surface));
+ qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y,
+ y, 0, surface_height(scon->surface));
+ } else {
+ if (guest_cursor) {
+ x -= guest_x;
+ y -= guest_y;
+ guest_x += x;
+ guest_y += y;
+ dx = x;
+ dy = y;
+ }
+ qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_X, dx);
+ qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_Y, dy);
+ }
+ qemu_input_event_sync();
+}
+
+static void toggle_full_screen(struct sdl2_console *scon)
+{
+ gui_fullscreen = !gui_fullscreen;
+ if (gui_fullscreen) {
+ SDL_SetWindowFullscreen(scon->real_window,
+ SDL_WINDOW_FULLSCREEN_DESKTOP);
+ gui_saved_grab = gui_grab;
+ sdl_grab_start(scon);
+ } else {
+ if (!gui_saved_grab) {
+ sdl_grab_end(scon);
+ }
+ SDL_SetWindowFullscreen(scon->real_window, 0);
+ }
+ sdl2_redraw(scon);
+}
+
+static int get_mod_state(void)
+{
+ SDL_Keymod mod = SDL_GetModState();
+
+ if (alt_grab) {
+ return (mod & (gui_grab_code | KMOD_LSHIFT)) ==
+ (gui_grab_code | KMOD_LSHIFT);
+ } else if (ctrl_grab) {
+ return (mod & KMOD_RCTRL) == KMOD_RCTRL;
+ } else {
+ return (mod & gui_grab_code) == gui_grab_code;
+ }
+}
+
+static void *sdl2_win32_get_hwnd(struct sdl2_console *scon)
+{
+#ifdef CONFIG_WIN32
+ SDL_SysWMinfo info;
+
+ SDL_VERSION(&info.version);
+ if (SDL_GetWindowWMInfo(scon->real_window, &info)) {
+ return info.info.win.window;
+ }
+#endif
+ return NULL;
+}
+
+static void handle_keydown(SDL_Event *ev)
+{
+ int win;
+ struct sdl2_console *scon = get_scon_from_window(ev->key.windowID);
+ int gui_key_modifier_pressed = get_mod_state();
+ int gui_keysym = 0;
+
+ if (!scon) {
+ return;
+ }
+
+ if (!scon->ignore_hotkeys && gui_key_modifier_pressed && !ev->key.repeat) {
+ switch (ev->key.keysym.scancode) {
+ case SDL_SCANCODE_2:
+ case SDL_SCANCODE_3:
+ case SDL_SCANCODE_4:
+ case SDL_SCANCODE_5:
+ case SDL_SCANCODE_6:
+ case SDL_SCANCODE_7:
+ case SDL_SCANCODE_8:
+ case SDL_SCANCODE_9:
+ if (gui_grab) {
+ sdl_grab_end(scon);
+ }
+
+ win = ev->key.keysym.scancode - SDL_SCANCODE_1;
+ if (win < sdl2_num_outputs) {
+ sdl2_console[win].hidden = !sdl2_console[win].hidden;
+ if (sdl2_console[win].real_window) {
+ if (sdl2_console[win].hidden) {
+ SDL_HideWindow(sdl2_console[win].real_window);
+ } else {
+ SDL_ShowWindow(sdl2_console[win].real_window);
+ }
+ }
+ gui_keysym = 1;
+ }
+ break;
+ case SDL_SCANCODE_F:
+ toggle_full_screen(scon);
+ gui_keysym = 1;
+ break;
+ case SDL_SCANCODE_G:
+ gui_keysym = 1;
+ if (!gui_grab) {
+ sdl_grab_start(scon);
+ } else if (!gui_fullscreen) {
+ sdl_grab_end(scon);
+ }
+ break;
+ case SDL_SCANCODE_U:
+ sdl2_window_resize(scon);
+ if (!scon->opengl) {
+ /* re-create scon->texture */
+ sdl2_2d_switch(&scon->dcl, scon->surface);
+ }
+ gui_keysym = 1;
+ break;
+#if 0
+ case SDL_SCANCODE_KP_PLUS:
+ case SDL_SCANCODE_KP_MINUS:
+ if (!gui_fullscreen) {
+ int scr_w, scr_h;
+ int width, height;
+ SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
+
+ width = MAX(scr_w + (ev->key.keysym.scancode ==
+ SDL_SCANCODE_KP_PLUS ? 50 : -50),
+ 160);
+ height = (surface_height(scon->surface) * width) /
+ surface_width(scon->surface);
+ fprintf(stderr, "%s: scale to %dx%d\n",
+ __func__, width, height);
+ sdl_scale(scon, width, height);
+ sdl2_redraw(scon);
+ gui_keysym = 1;
+ }
+#endif
+ default:
+ break;
+ }
+ }
+ if (!gui_keysym) {
+ sdl2_process_key(scon, &ev->key);
+ }
+}
+
+static void handle_keyup(SDL_Event *ev)
+{
+ struct sdl2_console *scon = get_scon_from_window(ev->key.windowID);
+
+ if (!scon) {
+ return;
+ }
+
+ scon->ignore_hotkeys = false;
+ sdl2_process_key(scon, &ev->key);
+}
+
+static void handle_textinput(SDL_Event *ev)
+{
+ struct sdl2_console *scon = get_scon_from_window(ev->text.windowID);
+ QemuConsole *con = scon ? scon->dcl.con : NULL;
+
+ if (!con) {
+ return;
+ }
+
+ if (qemu_console_is_graphic(con)) {
+ return;
+ }
+ kbd_put_string_console(con, ev->text.text, strlen(ev->text.text));
+}
+
+static void handle_mousemotion(SDL_Event *ev)
+{
+ int max_x, max_y;
+ struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID);
+
+ if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
+ return;
+ }
+
+ if (qemu_input_is_absolute() || absolute_enabled) {
+ int scr_w, scr_h;
+ SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h);
+ max_x = scr_w - 1;
+ max_y = scr_h - 1;
+ if (gui_grab && !gui_fullscreen
+ && (ev->motion.x == 0 || ev->motion.y == 0 ||
+ ev->motion.x == max_x || ev->motion.y == max_y)) {
+ sdl_grab_end(scon);
+ }
+ if (!gui_grab &&
+ (ev->motion.x > 0 && ev->motion.x < max_x &&
+ ev->motion.y > 0 && ev->motion.y < max_y)) {
+ sdl_grab_start(scon);
+ }
+ }
+ if (gui_grab || qemu_input_is_absolute() || absolute_enabled) {
+ sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel,
+ ev->motion.x, ev->motion.y, ev->motion.state);
+ }
+}
+
+static void handle_mousebutton(SDL_Event *ev)
+{
+ int buttonstate = SDL_GetMouseState(NULL, NULL);
+ SDL_MouseButtonEvent *bev;
+ struct sdl2_console *scon = get_scon_from_window(ev->button.windowID);
+
+ if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
+ return;
+ }
+
+ bev = &ev->button;
+ if (!gui_grab && !qemu_input_is_absolute()) {
+ if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) {
+ /* start grabbing all events */
+ sdl_grab_start(scon);
+ }
+ } else {
+ if (ev->type == SDL_MOUSEBUTTONDOWN) {
+ buttonstate |= SDL_BUTTON(bev->button);
+ } else {
+ buttonstate &= ~SDL_BUTTON(bev->button);
+ }
+ sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate);
+ }
+}
+
+static void handle_mousewheel(SDL_Event *ev)
+{
+ struct sdl2_console *scon = get_scon_from_window(ev->wheel.windowID);
+ SDL_MouseWheelEvent *wev = &ev->wheel;
+ InputButton btn;
+
+ if (!scon || !qemu_console_is_graphic(scon->dcl.con)) {
+ return;
+ }
+
+ if (wev->y > 0) {
+ btn = INPUT_BUTTON_WHEEL_UP;
+ } else if (wev->y < 0) {
+ btn = INPUT_BUTTON_WHEEL_DOWN;
+ } else {
+ return;
+ }
+
+ qemu_input_queue_btn(scon->dcl.con, btn, true);
+ qemu_input_event_sync();
+ qemu_input_queue_btn(scon->dcl.con, btn, false);
+ qemu_input_event_sync();
+}
+
+static void handle_windowevent(SDL_Event *ev)
+{
+ struct sdl2_console *scon = get_scon_from_window(ev->window.windowID);
+ bool allow_close = true;
+
+ if (!scon) {
+ return;
+ }
+
+ switch (ev->window.event) {
+ case SDL_WINDOWEVENT_RESIZED:
+ {
+ QemuUIInfo info;
+ memset(&info, 0, sizeof(info));
+ info.width = ev->window.data1;
+ info.height = ev->window.data2;
+ dpy_set_ui_info(scon->dcl.con, &info);
+ }
+ sdl2_redraw(scon);
+ break;
+ case SDL_WINDOWEVENT_EXPOSED:
+ sdl2_redraw(scon);
+ break;
+ case SDL_WINDOWEVENT_FOCUS_GAINED:
+ win32_kbd_set_grab(gui_grab);
+ if (qemu_console_is_graphic(scon->dcl.con)) {
+ win32_kbd_set_window(sdl2_win32_get_hwnd(scon));
+ }
+ /* fall through */
+ case SDL_WINDOWEVENT_ENTER:
+ if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) {
+ absolute_mouse_grab(scon);
+ }
+ /* If a new console window opened using a hotkey receives the
+ * focus, SDL sends another KEYDOWN event to the new window,
+ * closing the console window immediately after.
+ *
+ * Work around this by ignoring further hotkey events until a
+ * key is released.
+ */
+ scon->ignore_hotkeys = get_mod_state();
+ break;
+ case SDL_WINDOWEVENT_FOCUS_LOST:
+ if (qemu_console_is_graphic(scon->dcl.con)) {
+ win32_kbd_set_window(NULL);
+ }
+ if (gui_grab && !gui_fullscreen) {
+ sdl_grab_end(scon);
+ }
+ break;
+ case SDL_WINDOWEVENT_RESTORED:
+ update_displaychangelistener(&scon->dcl, GUI_REFRESH_INTERVAL_DEFAULT);
+ break;
+ case SDL_WINDOWEVENT_MINIMIZED:
+ update_displaychangelistener(&scon->dcl, 500);
+ break;
+ case SDL_WINDOWEVENT_CLOSE:
+ if (qemu_console_is_graphic(scon->dcl.con)) {
+ if (scon->opts->has_window_close && !scon->opts->window_close) {
+ allow_close = false;
+ }
+ if (allow_close) {
+ shutdown_action = SHUTDOWN_ACTION_POWEROFF;
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+ }
+ } else {
+ SDL_HideWindow(scon->real_window);
+ scon->hidden = true;
+ }
+ break;
+ case SDL_WINDOWEVENT_SHOWN:
+ scon->hidden = false;
+ break;
+ case SDL_WINDOWEVENT_HIDDEN:
+ scon->hidden = true;
+ break;
+ }
+}
+
+void sdl2_poll_events(struct sdl2_console *scon)
+{
+ SDL_Event ev1, *ev = &ev1;
+ bool allow_close = true;
+ int idle = 1;
+
+ if (scon->last_vm_running != runstate_is_running()) {
+ scon->last_vm_running = runstate_is_running();
+ sdl_update_caption(scon);
+ }
+
+ while (SDL_PollEvent(ev)) {
+ switch (ev->type) {
+ case SDL_KEYDOWN:
+ idle = 0;
+ handle_keydown(ev);
+ break;
+ case SDL_KEYUP:
+ idle = 0;
+ handle_keyup(ev);
+ break;
+ case SDL_TEXTINPUT:
+ idle = 0;
+ handle_textinput(ev);
+ break;
+ case SDL_QUIT:
+ if (scon->opts->has_window_close && !scon->opts->window_close) {
+ allow_close = false;
+ }
+ if (allow_close) {
+ shutdown_action = SHUTDOWN_ACTION_POWEROFF;
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+ }
+ break;
+ case SDL_MOUSEMOTION:
+ idle = 0;
+ handle_mousemotion(ev);
+ break;
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ idle = 0;
+ handle_mousebutton(ev);
+ break;
+ case SDL_MOUSEWHEEL:
+ idle = 0;
+ handle_mousewheel(ev);
+ break;
+ case SDL_WINDOWEVENT:
+ handle_windowevent(ev);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (idle) {
+ if (scon->idle_counter < SDL2_MAX_IDLE_COUNT) {
+ scon->idle_counter++;
+ if (scon->idle_counter >= SDL2_MAX_IDLE_COUNT) {
+ scon->dcl.update_interval = GUI_REFRESH_INTERVAL_DEFAULT;
+ }
+ }
+ } else {
+ scon->idle_counter = 0;
+ scon->dcl.update_interval = SDL2_REFRESH_INTERVAL_BUSY;
+ }
+}
+
+static void sdl_mouse_warp(DisplayChangeListener *dcl,
+ int x, int y, int on)
+{
+ struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl);
+
+ if (!qemu_console_is_graphic(scon->dcl.con)) {
+ return;
+ }
+
+ if (on) {
+ if (!guest_cursor) {
+ sdl_show_cursor(scon);
+ }
+ if (gui_grab || qemu_input_is_absolute() || absolute_enabled) {
+ SDL_SetCursor(guest_sprite);
+ if (!qemu_input_is_absolute() && !absolute_enabled) {
+ SDL_WarpMouseInWindow(scon->real_window, x, y);
+ }
+ }
+ } else if (gui_grab) {
+ sdl_hide_cursor(scon);
+ }
+ guest_cursor = on;
+ guest_x = x, guest_y = y;
+}
+
+static void sdl_mouse_define(DisplayChangeListener *dcl,
+ QEMUCursor *c)
+{
+
+ if (guest_sprite) {
+ SDL_FreeCursor(guest_sprite);
+ }
+
+ if (guest_sprite_surface) {
+ SDL_FreeSurface(guest_sprite_surface);
+ }
+
+ guest_sprite_surface =
+ SDL_CreateRGBSurfaceFrom(c->data, c->width, c->height, 32, c->width * 4,
+ 0xff0000, 0x00ff00, 0xff, 0xff000000);
+
+ if (!guest_sprite_surface) {
+ fprintf(stderr, "Failed to make rgb surface from %p\n", c);
+ return;
+ }
+ guest_sprite = SDL_CreateColorCursor(guest_sprite_surface,
+ c->hot_x, c->hot_y);
+ if (!guest_sprite) {
+ fprintf(stderr, "Failed to make color cursor from %p\n", c);
+ return;
+ }
+ if (guest_cursor &&
+ (gui_grab || qemu_input_is_absolute() || absolute_enabled)) {
+ SDL_SetCursor(guest_sprite);
+ }
+}
+
+static void sdl_cleanup(void)
+{
+ if (guest_sprite) {
+ SDL_FreeCursor(guest_sprite);
+ }
+ SDL_QuitSubSystem(SDL_INIT_VIDEO);
+}
+
+static const DisplayChangeListenerOps dcl_2d_ops = {
+ .dpy_name = "sdl2-2d",
+ .dpy_gfx_update = sdl2_2d_update,
+ .dpy_gfx_switch = sdl2_2d_switch,
+ .dpy_gfx_check_format = sdl2_2d_check_format,
+ .dpy_refresh = sdl2_2d_refresh,
+ .dpy_mouse_set = sdl_mouse_warp,
+ .dpy_cursor_define = sdl_mouse_define,
+};
+
+#ifdef CONFIG_OPENGL
+static const DisplayChangeListenerOps dcl_gl_ops = {
+ .dpy_name = "sdl2-gl",
+ .dpy_gfx_update = sdl2_gl_update,
+ .dpy_gfx_switch = sdl2_gl_switch,
+ .dpy_gfx_check_format = console_gl_check_format,
+ .dpy_refresh = sdl2_gl_refresh,
+ .dpy_mouse_set = sdl_mouse_warp,
+ .dpy_cursor_define = sdl_mouse_define,
+
+ .dpy_gl_ctx_create = sdl2_gl_create_context,
+ .dpy_gl_ctx_destroy = sdl2_gl_destroy_context,
+ .dpy_gl_ctx_make_current = sdl2_gl_make_context_current,
+ .dpy_gl_scanout_disable = sdl2_gl_scanout_disable,
+ .dpy_gl_scanout_texture = sdl2_gl_scanout_texture,
+ .dpy_gl_update = sdl2_gl_scanout_flush,
+};
+#endif
+
+static void sdl2_display_early_init(DisplayOptions *o)
+{
+ assert(o->type == DISPLAY_TYPE_SDL);
+ if (o->has_gl && o->gl) {
+#ifdef CONFIG_OPENGL
+ display_opengl = 1;
+#endif
+ }
+}
+
+static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
+{
+ uint8_t data = 0;
+ int i;
+ SDL_SysWMinfo info;
+ SDL_Surface *icon = NULL;
+ char *dir;
+
+ assert(o->type == DISPLAY_TYPE_SDL);
+
+#ifdef __linux__
+ /* on Linux, SDL may use fbcon|directfb|svgalib when run without
+ * accessible $DISPLAY to open X11 window. This is often the case
+ * when qemu is run using sudo. But in this case, and when actually
+ * run in X11 environment, SDL fights with X11 for the video card,
+ * making current display unavailable, often until reboot.
+ * So make x11 the default SDL video driver if this variable is unset.
+ * This is a bit hackish but saves us from bigger problem.
+ * Maybe it's a good idea to fix this in SDL instead.
+ */
+ if (!g_setenv("SDL_VIDEODRIVER", "x11", 0)) {
+ fprintf(stderr, "Could not set SDL_VIDEODRIVER environment variable\n");
+ exit(1);
+ }
+#endif
+
+ if (SDL_Init(SDL_INIT_VIDEO)) {
+ fprintf(stderr, "Could not initialize SDL(%s) - exiting\n",
+ SDL_GetError());
+ exit(1);
+ }
+#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* only available since SDL 2.0.8 */
+ SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
+#endif
+ SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1");
+ memset(&info, 0, sizeof(info));
+ SDL_VERSION(&info.version);
+
+ gui_fullscreen = o->has_full_screen && o->full_screen;
+
+ for (i = 0;; i++) {
+ QemuConsole *con = qemu_console_lookup_by_index(i);
+ if (!con) {
+ break;
+ }
+ }
+ sdl2_num_outputs = i;
+ if (sdl2_num_outputs == 0) {
+ return;
+ }
+ sdl2_console = g_new0(struct sdl2_console, sdl2_num_outputs);
+ for (i = 0; i < sdl2_num_outputs; i++) {
+ QemuConsole *con = qemu_console_lookup_by_index(i);
+ assert(con != NULL);
+ if (!qemu_console_is_graphic(con) &&
+ qemu_console_get_index(con) != 0) {
+ sdl2_console[i].hidden = true;
+ }
+ sdl2_console[i].idx = i;
+ sdl2_console[i].opts = o;
+#ifdef CONFIG_OPENGL
+ sdl2_console[i].opengl = display_opengl;
+ sdl2_console[i].dcl.ops = display_opengl ? &dcl_gl_ops : &dcl_2d_ops;
+#else
+ sdl2_console[i].opengl = 0;
+ sdl2_console[i].dcl.ops = &dcl_2d_ops;
+#endif
+ sdl2_console[i].dcl.con = con;
+ sdl2_console[i].kbd = qkbd_state_init(con);
+ register_displaychangelistener(&sdl2_console[i].dcl);
+
+#if defined(SDL_VIDEO_DRIVER_WINDOWS) || defined(SDL_VIDEO_DRIVER_X11)
+ if (SDL_GetWindowWMInfo(sdl2_console[i].real_window, &info)) {
+#if defined(SDL_VIDEO_DRIVER_WINDOWS)
+ qemu_console_set_window_id(con, (uintptr_t)info.info.win.window);
+#elif defined(SDL_VIDEO_DRIVER_X11)
+ qemu_console_set_window_id(con, info.info.x11.window);
+#endif
+ }
+#endif
+ }
+
+#ifdef CONFIG_SDL_IMAGE
+ dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/128x128/apps/qemu.png");
+ icon = IMG_Load(dir);
+#else
+ /* Load a 32x32x4 image. White pixels are transparent. */
+ dir = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/32x32/apps/qemu.bmp");
+ icon = SDL_LoadBMP(dir);
+ if (icon) {
+ uint32_t colorkey = SDL_MapRGB(icon->format, 255, 255, 255);
+ SDL_SetColorKey(icon, SDL_TRUE, colorkey);
+ }
+#endif
+ g_free(dir);
+ if (icon) {
+ SDL_SetWindowIcon(sdl2_console[0].real_window, icon);
+ }
+
+ mouse_mode_notifier.notify = sdl_mouse_mode_change;
+ qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier);
+
+ sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0);
+ sdl_cursor_normal = SDL_GetCursor();
+
+ if (gui_fullscreen) {
+ sdl_grab_start(&sdl2_console[0]);
+ }
+
+ atexit(sdl_cleanup);
+}
+
+static QemuDisplay qemu_display_sdl2 = {
+ .type = DISPLAY_TYPE_SDL,
+ .early_init = sdl2_display_early_init,
+ .init = sdl2_display_init,
+};
+
+static void register_sdl1(void)
+{
+ qemu_display_register(&qemu_display_sdl2);
+}
+
+type_init(register_sdl1);
+
+#ifdef CONFIG_OPENGL
+module_dep("ui-opengl");
+#endif
diff --git a/ui/shader.c b/ui/shader.c
new file mode 100644
index 000000000..e8b8d321b
--- /dev/null
+++ b/ui/shader.c
@@ -0,0 +1,174 @@
+/*
+ * QEMU opengl shader helper functions
+ *
+ * Copyright (c) 2014 Red Hat
+ *
+ * Authors:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "ui/shader.h"
+
+#include "ui/shader/texture-blit-vert.h"
+#include "ui/shader/texture-blit-flip-vert.h"
+#include "ui/shader/texture-blit-frag.h"
+
+struct QemuGLShader {
+ GLint texture_blit_prog;
+ GLint texture_blit_flip_prog;
+ GLint texture_blit_vao;
+};
+
+/* ---------------------------------------------------------------------- */
+
+static GLuint qemu_gl_init_texture_blit(GLint texture_blit_prog)
+{
+ static const GLfloat in_position[] = {
+ -1, -1,
+ 1, -1,
+ -1, 1,
+ 1, 1,
+ };
+ GLint l_position;
+ GLuint vao, buffer;
+
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ /* this is the VBO that holds the vertex data */
+ glGenBuffers(1, &buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(in_position), in_position,
+ GL_STATIC_DRAW);
+
+ l_position = glGetAttribLocation(texture_blit_prog, "in_position");
+ glVertexAttribPointer(l_position, 2, GL_FLOAT, GL_FALSE, 0, 0);
+ glEnableVertexAttribArray(l_position);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindVertexArray(0);
+
+ return vao;
+}
+
+void qemu_gl_run_texture_blit(QemuGLShader *gls, bool flip)
+{
+ glUseProgram(flip
+ ? gls->texture_blit_flip_prog
+ : gls->texture_blit_prog);
+ glBindVertexArray(gls->texture_blit_vao);
+ glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static GLuint qemu_gl_create_compile_shader(GLenum type, const GLchar *src)
+{
+ GLuint shader;
+ GLint status, length;
+ char *errmsg;
+
+ shader = glCreateShader(type);
+ glShaderSource(shader, 1, &src, 0);
+ glCompileShader(shader);
+
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (!status) {
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
+ errmsg = g_malloc(length);
+ glGetShaderInfoLog(shader, length, &length, errmsg);
+ fprintf(stderr, "%s: compile %s error\n%s\n", __func__,
+ (type == GL_VERTEX_SHADER) ? "vertex" : "fragment",
+ errmsg);
+ g_free(errmsg);
+ return 0;
+ }
+ return shader;
+}
+
+static GLuint qemu_gl_create_link_program(GLuint vert, GLuint frag)
+{
+ GLuint program;
+ GLint status, length;
+ char *errmsg;
+
+ program = glCreateProgram();
+ glAttachShader(program, vert);
+ glAttachShader(program, frag);
+ glLinkProgram(program);
+
+ glGetProgramiv(program, GL_LINK_STATUS, &status);
+ if (!status) {
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
+ errmsg = g_malloc(length);
+ glGetProgramInfoLog(program, length, &length, errmsg);
+ fprintf(stderr, "%s: link program: %s\n", __func__, errmsg);
+ g_free(errmsg);
+ return 0;
+ }
+ return program;
+}
+
+static GLuint qemu_gl_create_compile_link_program(const GLchar *vert_src,
+ const GLchar *frag_src)
+{
+ GLuint vert_shader, frag_shader, program;
+
+ vert_shader = qemu_gl_create_compile_shader(GL_VERTEX_SHADER, vert_src);
+ frag_shader = qemu_gl_create_compile_shader(GL_FRAGMENT_SHADER, frag_src);
+ if (!vert_shader || !frag_shader) {
+ return 0;
+ }
+
+ program = qemu_gl_create_link_program(vert_shader, frag_shader);
+ glDeleteShader(vert_shader);
+ glDeleteShader(frag_shader);
+
+ return program;
+}
+
+/* ---------------------------------------------------------------------- */
+
+QemuGLShader *qemu_gl_init_shader(void)
+{
+ QemuGLShader *gls = g_new0(QemuGLShader, 1);
+
+ gls->texture_blit_prog = qemu_gl_create_compile_link_program
+ (texture_blit_vert_src, texture_blit_frag_src);
+ gls->texture_blit_flip_prog = qemu_gl_create_compile_link_program
+ (texture_blit_flip_vert_src, texture_blit_frag_src);
+ if (!gls->texture_blit_prog || !gls->texture_blit_flip_prog) {
+ exit(1);
+ }
+
+ gls->texture_blit_vao =
+ qemu_gl_init_texture_blit(gls->texture_blit_prog);
+
+ return gls;
+}
+
+void qemu_gl_fini_shader(QemuGLShader *gls)
+{
+ if (!gls) {
+ return;
+ }
+ g_free(gls);
+}
diff --git a/ui/shader/meson.build b/ui/shader/meson.build
new file mode 100644
index 000000000..592bf596b
--- /dev/null
+++ b/ui/shader/meson.build
@@ -0,0 +1,14 @@
+shaders = [
+ ['texture-blit', 'frag'],
+ ['texture-blit', 'vert'],
+ ['texture-blit-flip', 'vert'],
+]
+
+foreach e : shaders
+ output = '@0@-@1@.h'.format(e[0], e[1])
+ genh += custom_target(output,
+ output: output,
+ capture: true,
+ input: files('@0@.@1@'.format(e[0], e[1])),
+ command: [shaderinclude, '@INPUT0@'])
+endforeach
diff --git a/ui/shader/texture-blit-flip.vert b/ui/shader/texture-blit-flip.vert
new file mode 100644
index 000000000..ba081fa5a
--- /dev/null
+++ b/ui/shader/texture-blit-flip.vert
@@ -0,0 +1,10 @@
+
+#version 300 es
+
+in vec2 in_position;
+out vec2 ex_tex_coord;
+
+void main(void) {
+ gl_Position = vec4(in_position, 0.0, 1.0);
+ ex_tex_coord = vec2(1.0 + in_position.x, 1.0 + in_position.y) * 0.5;
+}
diff --git a/ui/shader/texture-blit.frag b/ui/shader/texture-blit.frag
new file mode 100644
index 000000000..bfa202c22
--- /dev/null
+++ b/ui/shader/texture-blit.frag
@@ -0,0 +1,10 @@
+
+#version 300 es
+
+uniform sampler2D image;
+in mediump vec2 ex_tex_coord;
+out mediump vec4 out_frag_color;
+
+void main(void) {
+ out_frag_color = texture(image, ex_tex_coord);
+}
diff --git a/ui/shader/texture-blit.vert b/ui/shader/texture-blit.vert
new file mode 100644
index 000000000..6fe2744d6
--- /dev/null
+++ b/ui/shader/texture-blit.vert
@@ -0,0 +1,10 @@
+
+#version 300 es
+
+in vec2 in_position;
+out vec2 ex_tex_coord;
+
+void main(void) {
+ gl_Position = vec4(in_position, 0.0, 1.0);
+ ex_tex_coord = vec2(1.0 + in_position.x, 1.0 - in_position.y) * 0.5;
+}
diff --git a/ui/spice-app.c b/ui/spice-app.c
new file mode 100644
index 000000000..7e71e18da
--- /dev/null
+++ b/ui/spice-app.c
@@ -0,0 +1,227 @@
+/*
+ * QEMU external Spice client display driver
+ *
+ * Copyright (c) 2018 Marc-André Lureau <marcandre.lureau@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+#include <gio/gio.h>
+
+#include "ui/console.h"
+#include "ui/spice-display.h"
+#include "qemu/config-file.h"
+#include "qemu/option.h"
+#include "qemu/cutils.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "io/channel-command.h"
+#include "chardev/spice.h"
+#include "sysemu/sysemu.h"
+#include "qom/object.h"
+
+static const char *tmp_dir;
+static char *app_dir;
+static char *sock_path;
+
+struct VCChardev {
+ SpiceChardev parent;
+};
+
+struct VCChardevClass {
+ ChardevClass parent;
+ void (*parent_open)(Chardev *chr, ChardevBackend *backend,
+ bool *be_opened, Error **errp);
+};
+
+#define TYPE_CHARDEV_VC "chardev-vc"
+OBJECT_DECLARE_TYPE(VCChardev, VCChardevClass, CHARDEV_VC)
+
+static ChardevBackend *
+chr_spice_backend_new(void)
+{
+ ChardevBackend *be = g_new0(ChardevBackend, 1);
+
+ be->type = CHARDEV_BACKEND_KIND_SPICEPORT;
+ be->u.spiceport.data = g_new0(ChardevSpicePort, 1);
+
+ return be;
+}
+
+static void vc_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ VCChardevClass *vc = CHARDEV_VC_GET_CLASS(chr);
+ ChardevBackend *be;
+ const char *fqdn = NULL;
+
+ if (strstart(chr->label, "serial", NULL)) {
+ fqdn = "org.qemu.console.serial.0";
+ } else if (strstart(chr->label, "parallel", NULL)) {
+ fqdn = "org.qemu.console.parallel.0";
+ } else if (strstart(chr->label, "compat_monitor", NULL)) {
+ fqdn = "org.qemu.monitor.hmp.0";
+ }
+
+ be = chr_spice_backend_new();
+ be->u.spiceport.data->fqdn = fqdn ?
+ g_strdup(fqdn) : g_strdup_printf("org.qemu.console.%s", chr->label);
+ vc->parent_open(chr, be, be_opened, errp);
+ qapi_free_ChardevBackend(be);
+}
+
+static void vc_chr_set_echo(Chardev *chr, bool echo)
+{
+ /* TODO: set echo for frontends QMP and qtest */
+}
+
+static void char_vc_class_init(ObjectClass *oc, void *data)
+{
+ VCChardevClass *vc = CHARDEV_VC_CLASS(oc);
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ vc->parent_open = cc->open;
+
+ cc->parse = qemu_chr_parse_vc;
+ cc->open = vc_chr_open;
+ cc->chr_set_echo = vc_chr_set_echo;
+}
+
+static const TypeInfo char_vc_type_info = {
+ .name = TYPE_CHARDEV_VC,
+ .parent = TYPE_CHARDEV_SPICEPORT,
+ .instance_size = sizeof(VCChardev),
+ .class_init = char_vc_class_init,
+ .class_size = sizeof(VCChardevClass),
+};
+
+static void spice_app_atexit(void)
+{
+ if (sock_path) {
+ unlink(sock_path);
+ }
+ if (tmp_dir) {
+ rmdir(tmp_dir);
+ }
+ g_free(sock_path);
+ g_free(app_dir);
+}
+
+static void spice_app_display_early_init(DisplayOptions *opts)
+{
+ QemuOpts *qopts;
+ QemuOptsList *list;
+ GError *err = NULL;
+
+ if (opts->has_full_screen) {
+ error_report("spice-app full-screen isn't supported yet.");
+ exit(1);
+ }
+ if (opts->has_window_close) {
+ error_report("spice-app window-close isn't supported yet.");
+ exit(1);
+ }
+
+ atexit(spice_app_atexit);
+
+ if (qemu_name) {
+ app_dir = g_build_filename(g_get_user_runtime_dir(),
+ "qemu", qemu_name, NULL);
+ if (g_mkdir_with_parents(app_dir, S_IRWXU) < -1) {
+ error_report("Failed to create directory %s: %s",
+ app_dir, strerror(errno));
+ exit(1);
+ }
+ } else {
+ app_dir = g_dir_make_tmp(NULL, &err);
+ tmp_dir = app_dir;
+ if (err) {
+ error_report("Failed to create temporary directory: %s",
+ err->message);
+ exit(1);
+ }
+ }
+ list = qemu_find_opts("spice");
+ if (list == NULL) {
+ error_report("spice-app missing spice support");
+ exit(1);
+ }
+
+ type_register(&char_vc_type_info);
+
+ sock_path = g_strjoin("", app_dir, "/", "spice.sock", NULL);
+ qopts = qemu_opts_create(list, NULL, 0, &error_abort);
+ qemu_opt_set(qopts, "disable-ticketing", "on", &error_abort);
+ qemu_opt_set(qopts, "unix", "on", &error_abort);
+ qemu_opt_set(qopts, "addr", sock_path, &error_abort);
+ qemu_opt_set(qopts, "image-compression", "off", &error_abort);
+ qemu_opt_set(qopts, "streaming-video", "off", &error_abort);
+#ifdef HAVE_SPICE_GL
+ qemu_opt_set(qopts, "gl", opts->has_gl ? "on" : "off", &error_abort);
+ display_opengl = opts->has_gl;
+#endif
+}
+
+static void spice_app_display_init(DisplayState *ds, DisplayOptions *opts)
+{
+ ChardevBackend *be = chr_spice_backend_new();
+ QemuOpts *qopts;
+ GError *err = NULL;
+ gchar *uri;
+
+ be->u.spiceport.data->fqdn = g_strdup("org.qemu.monitor.qmp.0");
+ qemu_chardev_new("org.qemu.monitor.qmp", TYPE_CHARDEV_SPICEPORT,
+ be, NULL, &error_abort);
+ qopts = qemu_opts_create(qemu_find_opts("mon"),
+ NULL, 0, &error_fatal);
+ qemu_opt_set(qopts, "chardev", "org.qemu.monitor.qmp", &error_abort);
+ qemu_opt_set(qopts, "mode", "control", &error_abort);
+
+ qapi_free_ChardevBackend(be);
+ uri = g_strjoin("", "spice+unix://", app_dir, "/", "spice.sock", NULL);
+ info_report("Launching display with URI: %s", uri);
+ g_app_info_launch_default_for_uri(uri, NULL, &err);
+ if (err) {
+ error_report("Failed to launch %s URI: %s", uri, err->message);
+ error_report("You need a capable Spice client, "
+ "such as virt-viewer 8.0");
+ exit(1);
+ }
+ g_free(uri);
+}
+
+static QemuDisplay qemu_display_spice_app = {
+ .type = DISPLAY_TYPE_SPICE_APP,
+ .early_init = spice_app_display_early_init,
+ .init = spice_app_display_init,
+};
+
+static void register_spice_app(void)
+{
+ qemu_display_register(&qemu_display_spice_app);
+}
+
+type_init(register_spice_app);
+
+module_dep("ui-spice-core");
+module_dep("chardev-spice");
diff --git a/ui/spice-core.c b/ui/spice-core.c
new file mode 100644
index 000000000..31974b8d6
--- /dev/null
+++ b/ui/spice-core.c
@@ -0,0 +1,1039 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include <spice.h>
+
+#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
+#include "ui/qemu-spice.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/thread.h"
+#include "qemu/timer.h"
+#include "qemu/queue.h"
+#include "qemu-x509.h"
+#include "qemu/sockets.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-ui.h"
+#include "qapi/qapi-events-ui.h"
+#include "qemu/notify.h"
+#include "qemu/option.h"
+#include "crypto/secret_common.h"
+#include "migration/misc.h"
+#include "hw/pci/pci_bus.h"
+#include "ui/spice-display.h"
+
+/* core bits */
+
+static SpiceServer *spice_server;
+static Notifier migration_state;
+static const char *auth = "spice";
+static char *auth_passwd;
+static time_t auth_expires = TIME_MAX;
+static int spice_migration_completed;
+static int spice_display_is_running;
+static int spice_have_target_host;
+
+static QemuThread me;
+
+struct SpiceTimer {
+ QEMUTimer *timer;
+};
+
+static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
+{
+ SpiceTimer *timer;
+
+ timer = g_malloc0(sizeof(*timer));
+ timer->timer = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque);
+ return timer;
+}
+
+static void timer_start(SpiceTimer *timer, uint32_t ms)
+{
+ timer_mod(timer->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + ms);
+}
+
+static void timer_cancel(SpiceTimer *timer)
+{
+ timer_del(timer->timer);
+}
+
+static void timer_remove(SpiceTimer *timer)
+{
+ timer_free(timer->timer);
+ g_free(timer);
+}
+
+struct SpiceWatch {
+ int fd;
+ SpiceWatchFunc func;
+ void *opaque;
+};
+
+static void watch_read(void *opaque)
+{
+ SpiceWatch *watch = opaque;
+ watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque);
+}
+
+static void watch_write(void *opaque)
+{
+ SpiceWatch *watch = opaque;
+ watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque);
+}
+
+static void watch_update_mask(SpiceWatch *watch, int event_mask)
+{
+ IOHandler *on_read = NULL;
+ IOHandler *on_write = NULL;
+
+ if (event_mask & SPICE_WATCH_EVENT_READ) {
+ on_read = watch_read;
+ }
+ if (event_mask & SPICE_WATCH_EVENT_WRITE) {
+ on_write = watch_write;
+ }
+ qemu_set_fd_handler(watch->fd, on_read, on_write, watch);
+}
+
+static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
+{
+ SpiceWatch *watch;
+
+ watch = g_malloc0(sizeof(*watch));
+ watch->fd = fd;
+ watch->func = func;
+ watch->opaque = opaque;
+
+ watch_update_mask(watch, event_mask);
+ return watch;
+}
+
+static void watch_remove(SpiceWatch *watch)
+{
+ qemu_set_fd_handler(watch->fd, NULL, NULL, NULL);
+ g_free(watch);
+}
+
+typedef struct ChannelList ChannelList;
+struct ChannelList {
+ SpiceChannelEventInfo *info;
+ QTAILQ_ENTRY(ChannelList) link;
+};
+static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list);
+
+static void channel_list_add(SpiceChannelEventInfo *info)
+{
+ ChannelList *item;
+
+ item = g_malloc0(sizeof(*item));
+ item->info = info;
+ QTAILQ_INSERT_TAIL(&channel_list, item, link);
+}
+
+static void channel_list_del(SpiceChannelEventInfo *info)
+{
+ ChannelList *item;
+
+ QTAILQ_FOREACH(item, &channel_list, link) {
+ if (item->info != info) {
+ continue;
+ }
+ QTAILQ_REMOVE(&channel_list, item, link);
+ g_free(item);
+ return;
+ }
+}
+
+static void add_addr_info(SpiceBasicInfo *info, struct sockaddr *addr, int len)
+{
+ char host[NI_MAXHOST], port[NI_MAXSERV];
+
+ getnameinfo(addr, len, host, sizeof(host), port, sizeof(port),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ info->host = g_strdup(host);
+ info->port = g_strdup(port);
+ info->family = inet_netfamily(addr->sa_family);
+}
+
+static void add_channel_info(SpiceChannel *sc, SpiceChannelEventInfo *info)
+{
+ int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
+
+ sc->connection_id = info->connection_id;
+ sc->channel_type = info->type;
+ sc->channel_id = info->id;
+ sc->tls = !!tls;
+}
+
+static void channel_event(int event, SpiceChannelEventInfo *info)
+{
+ SpiceServerInfo *server = g_malloc0(sizeof(*server));
+ SpiceChannel *client = g_malloc0(sizeof(*client));
+
+ /*
+ * Spice server might have called us from spice worker thread
+ * context (happens on display channel disconnects). Spice should
+ * not do that. It isn't that easy to fix it in spice and even
+ * when it is fixed we still should cover the already released
+ * spice versions. So detect that we've been called from another
+ * thread and grab the iothread lock if so before calling qemu
+ * functions.
+ */
+ bool need_lock = !qemu_thread_is_self(&me);
+ if (need_lock) {
+ qemu_mutex_lock_iothread();
+ }
+
+ if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) {
+ add_addr_info(qapi_SpiceChannel_base(client),
+ (struct sockaddr *)&info->paddr_ext,
+ info->plen_ext);
+ add_addr_info(qapi_SpiceServerInfo_base(server),
+ (struct sockaddr *)&info->laddr_ext,
+ info->llen_ext);
+ } else {
+ error_report("spice: %s, extended address is expected",
+ __func__);
+ }
+
+ switch (event) {
+ case SPICE_CHANNEL_EVENT_CONNECTED:
+ qapi_event_send_spice_connected(qapi_SpiceServerInfo_base(server),
+ qapi_SpiceChannel_base(client));
+ break;
+ case SPICE_CHANNEL_EVENT_INITIALIZED:
+ if (auth) {
+ server->has_auth = true;
+ server->auth = g_strdup(auth);
+ }
+ add_channel_info(client, info);
+ channel_list_add(info);
+ qapi_event_send_spice_initialized(server, client);
+ break;
+ case SPICE_CHANNEL_EVENT_DISCONNECTED:
+ channel_list_del(info);
+ qapi_event_send_spice_disconnected(qapi_SpiceServerInfo_base(server),
+ qapi_SpiceChannel_base(client));
+ break;
+ default:
+ break;
+ }
+
+ if (need_lock) {
+ qemu_mutex_unlock_iothread();
+ }
+
+ qapi_free_SpiceServerInfo(server);
+ qapi_free_SpiceChannel(client);
+}
+
+static SpiceCoreInterface core_interface = {
+ .base.type = SPICE_INTERFACE_CORE,
+ .base.description = "qemu core services",
+ .base.major_version = SPICE_INTERFACE_CORE_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_CORE_MINOR,
+
+ .timer_add = timer_add,
+ .timer_start = timer_start,
+ .timer_cancel = timer_cancel,
+ .timer_remove = timer_remove,
+
+ .watch_add = watch_add,
+ .watch_update_mask = watch_update_mask,
+ .watch_remove = watch_remove,
+
+ .channel_event = channel_event,
+};
+
+static void migrate_connect_complete_cb(SpiceMigrateInstance *sin);
+static void migrate_end_complete_cb(SpiceMigrateInstance *sin);
+
+static const SpiceMigrateInterface migrate_interface = {
+ .base.type = SPICE_INTERFACE_MIGRATION,
+ .base.description = "migration",
+ .base.major_version = SPICE_INTERFACE_MIGRATION_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_MIGRATION_MINOR,
+ .migrate_connect_complete = migrate_connect_complete_cb,
+ .migrate_end_complete = migrate_end_complete_cb,
+};
+
+static SpiceMigrateInstance spice_migrate;
+
+static void migrate_connect_complete_cb(SpiceMigrateInstance *sin)
+{
+ /* nothing, but libspice-server expects this cb being present. */
+}
+
+static void migrate_end_complete_cb(SpiceMigrateInstance *sin)
+{
+ qapi_event_send_spice_migrate_completed();
+ spice_migration_completed = true;
+}
+
+/* config string parsing */
+
+static int name2enum(const char *string, const char *table[], int entries)
+{
+ int i;
+
+ if (string) {
+ for (i = 0; i < entries; i++) {
+ if (!table[i]) {
+ continue;
+ }
+ if (strcmp(string, table[i]) != 0) {
+ continue;
+ }
+ return i;
+ }
+ }
+ return -1;
+}
+
+static int parse_name(const char *string, const char *optname,
+ const char *table[], int entries)
+{
+ int value = name2enum(string, table, entries);
+
+ if (value != -1) {
+ return value;
+ }
+ error_report("spice: invalid %s: %s", optname, string);
+ exit(1);
+}
+
+static const char *stream_video_names[] = {
+ [ SPICE_STREAM_VIDEO_OFF ] = "off",
+ [ SPICE_STREAM_VIDEO_ALL ] = "all",
+ [ SPICE_STREAM_VIDEO_FILTER ] = "filter",
+};
+#define parse_stream_video(_name) \
+ parse_name(_name, "stream video control", \
+ stream_video_names, ARRAY_SIZE(stream_video_names))
+
+static const char *compression_names[] = {
+ [ SPICE_IMAGE_COMPRESS_OFF ] = "off",
+ [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz",
+ [ SPICE_IMAGE_COMPRESS_AUTO_LZ ] = "auto_lz",
+ [ SPICE_IMAGE_COMPRESS_QUIC ] = "quic",
+ [ SPICE_IMAGE_COMPRESS_GLZ ] = "glz",
+ [ SPICE_IMAGE_COMPRESS_LZ ] = "lz",
+};
+#define parse_compression(_name) \
+ parse_name(_name, "image compression", \
+ compression_names, ARRAY_SIZE(compression_names))
+
+static const char *wan_compression_names[] = {
+ [ SPICE_WAN_COMPRESSION_AUTO ] = "auto",
+ [ SPICE_WAN_COMPRESSION_NEVER ] = "never",
+ [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always",
+};
+#define parse_wan_compression(_name) \
+ parse_name(_name, "wan compression", \
+ wan_compression_names, ARRAY_SIZE(wan_compression_names))
+
+/* functions for the rest of qemu */
+
+static SpiceChannelList *qmp_query_spice_channels(void)
+{
+ SpiceChannelList *head = NULL, **tail = &head;
+ ChannelList *item;
+
+ QTAILQ_FOREACH(item, &channel_list, link) {
+ SpiceChannel *chan;
+ char host[NI_MAXHOST], port[NI_MAXSERV];
+ struct sockaddr *paddr;
+ socklen_t plen;
+
+ assert(item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT);
+
+ chan = g_malloc0(sizeof(*chan));
+
+ paddr = (struct sockaddr *)&item->info->paddr_ext;
+ plen = item->info->plen_ext;
+ getnameinfo(paddr, plen,
+ host, sizeof(host), port, sizeof(port),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ chan->host = g_strdup(host);
+ chan->port = g_strdup(port);
+ chan->family = inet_netfamily(paddr->sa_family);
+
+ chan->connection_id = item->info->connection_id;
+ chan->channel_type = item->info->type;
+ chan->channel_id = item->info->id;
+ chan->tls = item->info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
+
+ QAPI_LIST_APPEND(tail, chan);
+ }
+
+ return head;
+}
+
+static QemuOptsList qemu_spice_opts = {
+ .name = "spice",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head),
+ .merge_lists = true,
+ .desc = {
+ {
+ .name = "port",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "tls-port",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "addr",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "ipv4",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "ipv6",
+ .type = QEMU_OPT_BOOL,
+#ifdef SPICE_ADDR_FLAG_UNIX_ONLY
+ },{
+ .name = "unix",
+ .type = QEMU_OPT_BOOL,
+#endif
+ },{
+ .name = "password",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "password-secret",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "disable-ticketing",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "disable-copy-paste",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "disable-agent-file-xfer",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "sasl",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "x509-dir",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "x509-key-file",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "x509-key-password",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "x509-cert-file",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "x509-cacert-file",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "x509-dh-key-file",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "tls-ciphers",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "tls-channel",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "plaintext-channel",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "image-compression",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "jpeg-wan-compression",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "zlib-glz-wan-compression",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "streaming-video",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "agent-mouse",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "playback-compression",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "seamless-migration",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "display",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "head",
+ .type = QEMU_OPT_NUMBER,
+#ifdef HAVE_SPICE_GL
+ },{
+ .name = "gl",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "rendernode",
+ .type = QEMU_OPT_STRING,
+#endif
+ },
+ { /* end of list */ }
+ },
+};
+
+static SpiceInfo *qmp_query_spice_real(Error **errp)
+{
+ QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
+ int port, tls_port;
+ const char *addr;
+ SpiceInfo *info;
+ unsigned int major;
+ unsigned int minor;
+ unsigned int micro;
+
+ info = g_malloc0(sizeof(*info));
+
+ if (!spice_server || !opts) {
+ info->enabled = false;
+ return info;
+ }
+
+ info->enabled = true;
+ info->migrated = spice_migration_completed;
+
+ addr = qemu_opt_get(opts, "addr");
+ port = qemu_opt_get_number(opts, "port", 0);
+ tls_port = qemu_opt_get_number(opts, "tls-port", 0);
+
+ info->has_auth = true;
+ info->auth = g_strdup(auth);
+
+ info->has_host = true;
+ info->host = g_strdup(addr ? addr : "*");
+
+ info->has_compiled_version = true;
+ major = (SPICE_SERVER_VERSION & 0xff0000) >> 16;
+ minor = (SPICE_SERVER_VERSION & 0xff00) >> 8;
+ micro = SPICE_SERVER_VERSION & 0xff;
+ info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro);
+
+ if (port) {
+ info->has_port = true;
+ info->port = port;
+ }
+ if (tls_port) {
+ info->has_tls_port = true;
+ info->tls_port = tls_port;
+ }
+
+ info->mouse_mode = spice_server_is_server_mouse(spice_server) ?
+ SPICE_QUERY_MOUSE_MODE_SERVER :
+ SPICE_QUERY_MOUSE_MODE_CLIENT;
+
+ /* for compatibility with the original command */
+ info->has_channels = true;
+ info->channels = qmp_query_spice_channels();
+
+ return info;
+}
+
+static void migration_state_notifier(Notifier *notifier, void *data)
+{
+ MigrationState *s = data;
+
+ if (!spice_have_target_host) {
+ return;
+ }
+
+ if (migration_in_setup(s)) {
+ spice_server_migrate_start(spice_server);
+ } else if (migration_has_finished(s) ||
+ migration_in_postcopy_after_devices(s)) {
+ spice_server_migrate_end(spice_server, true);
+ spice_have_target_host = false;
+ } else if (migration_has_failed(s)) {
+ spice_server_migrate_end(spice_server, false);
+ spice_have_target_host = false;
+ }
+}
+
+int qemu_spice_migrate_info(const char *hostname, int port, int tls_port,
+ const char *subject)
+{
+ int ret;
+
+ ret = spice_server_migrate_connect(spice_server, hostname,
+ port, tls_port, subject);
+ spice_have_target_host = true;
+ return ret;
+}
+
+static int add_channel(void *opaque, const char *name, const char *value,
+ Error **errp)
+{
+ int security = 0;
+ int rc;
+
+ if (strcmp(name, "tls-channel") == 0) {
+ int *tls_port = opaque;
+ if (!*tls_port) {
+ error_setg(errp, "spice: tried to setup tls-channel"
+ " without specifying a TLS port");
+ return -1;
+ }
+ security = SPICE_CHANNEL_SECURITY_SSL;
+ }
+ if (strcmp(name, "plaintext-channel") == 0) {
+ security = SPICE_CHANNEL_SECURITY_NONE;
+ }
+ if (security == 0) {
+ return 0;
+ }
+ if (strcmp(value, "default") == 0) {
+ rc = spice_server_set_channel_security(spice_server, NULL, security);
+ } else {
+ rc = spice_server_set_channel_security(spice_server, value, security);
+ }
+ if (rc != 0) {
+ error_setg(errp, "spice: failed to set channel security for %s",
+ value);
+ return -1;
+ }
+ return 0;
+}
+
+static void vm_change_state_handler(void *opaque, bool running,
+ RunState state)
+{
+ if (running) {
+ qemu_spice_display_start();
+ } else if (state != RUN_STATE_PAUSED) {
+ qemu_spice_display_stop();
+ }
+}
+
+void qemu_spice_display_init_done(void)
+{
+ if (runstate_is_running()) {
+ qemu_spice_display_start();
+ }
+ qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
+}
+
+static void qemu_spice_init(void)
+{
+ QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
+ char *password = NULL;
+ const char *passwordSecret;
+ const char *str, *x509_dir, *addr,
+ *x509_key_password = NULL,
+ *x509_dh_file = NULL,
+ *tls_ciphers = NULL;
+ char *x509_key_file = NULL,
+ *x509_cert_file = NULL,
+ *x509_cacert_file = NULL;
+ int port, tls_port, addr_flags;
+ spice_image_compression_t compression;
+ spice_wan_compression_t wan_compr;
+ bool seamless_migration;
+
+ qemu_thread_get_self(&me);
+
+ if (!opts) {
+ return;
+ }
+ port = qemu_opt_get_number(opts, "port", 0);
+ tls_port = qemu_opt_get_number(opts, "tls-port", 0);
+ if (port < 0 || port > 65535) {
+ error_report("spice port is out of range");
+ exit(1);
+ }
+ if (tls_port < 0 || tls_port > 65535) {
+ error_report("spice tls-port is out of range");
+ exit(1);
+ }
+ passwordSecret = qemu_opt_get(opts, "password-secret");
+ if (passwordSecret) {
+ if (qemu_opt_get(opts, "password")) {
+ error_report("'password' option is mutually exclusive with "
+ "'password-secret'");
+ exit(1);
+ }
+ password = qcrypto_secret_lookup_as_utf8(passwordSecret,
+ &error_fatal);
+ } else {
+ str = qemu_opt_get(opts, "password");
+ if (str) {
+ warn_report("'password' option is deprecated and insecure, "
+ "use 'password-secret' instead");
+ password = g_strdup(str);
+ }
+ }
+
+ if (tls_port) {
+ x509_dir = qemu_opt_get(opts, "x509-dir");
+ if (!x509_dir) {
+ x509_dir = ".";
+ }
+
+ str = qemu_opt_get(opts, "x509-key-file");
+ if (str) {
+ x509_key_file = g_strdup(str);
+ } else {
+ x509_key_file = g_strdup_printf("%s/%s", x509_dir,
+ X509_SERVER_KEY_FILE);
+ }
+
+ str = qemu_opt_get(opts, "x509-cert-file");
+ if (str) {
+ x509_cert_file = g_strdup(str);
+ } else {
+ x509_cert_file = g_strdup_printf("%s/%s", x509_dir,
+ X509_SERVER_CERT_FILE);
+ }
+
+ str = qemu_opt_get(opts, "x509-cacert-file");
+ if (str) {
+ x509_cacert_file = g_strdup(str);
+ } else {
+ x509_cacert_file = g_strdup_printf("%s/%s", x509_dir,
+ X509_CA_CERT_FILE);
+ }
+
+ x509_key_password = qemu_opt_get(opts, "x509-key-password");
+ x509_dh_file = qemu_opt_get(opts, "x509-dh-key-file");
+ tls_ciphers = qemu_opt_get(opts, "tls-ciphers");
+ }
+
+ addr = qemu_opt_get(opts, "addr");
+ addr_flags = 0;
+ if (qemu_opt_get_bool(opts, "ipv4", 0)) {
+ addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY;
+ } else if (qemu_opt_get_bool(opts, "ipv6", 0)) {
+ addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY;
+#ifdef SPICE_ADDR_FLAG_UNIX_ONLY
+ } else if (qemu_opt_get_bool(opts, "unix", 0)) {
+ addr_flags |= SPICE_ADDR_FLAG_UNIX_ONLY;
+#endif
+ }
+
+ spice_server = spice_server_new();
+ spice_server_set_addr(spice_server, addr ? addr : "", addr_flags);
+ if (port) {
+ spice_server_set_port(spice_server, port);
+ }
+ if (tls_port) {
+ spice_server_set_tls(spice_server, tls_port,
+ x509_cacert_file,
+ x509_cert_file,
+ x509_key_file,
+ x509_key_password,
+ x509_dh_file,
+ tls_ciphers);
+ }
+ if (password) {
+ qemu_spice.set_passwd(password, false, false);
+ }
+ if (qemu_opt_get_bool(opts, "sasl", 0)) {
+ if (spice_server_set_sasl(spice_server, 1) == -1) {
+ error_report("spice: failed to enable sasl");
+ exit(1);
+ }
+ auth = "sasl";
+ }
+ if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) {
+ auth = "none";
+ spice_server_set_noauth(spice_server);
+ }
+
+ if (qemu_opt_get_bool(opts, "disable-copy-paste", 0)) {
+ spice_server_set_agent_copypaste(spice_server, false);
+ }
+
+ if (qemu_opt_get_bool(opts, "disable-agent-file-xfer", 0)) {
+ spice_server_set_agent_file_xfer(spice_server, false);
+ }
+
+ compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
+ str = qemu_opt_get(opts, "image-compression");
+ if (str) {
+ compression = parse_compression(str);
+ }
+ spice_server_set_image_compression(spice_server, compression);
+
+ wan_compr = SPICE_WAN_COMPRESSION_AUTO;
+ str = qemu_opt_get(opts, "jpeg-wan-compression");
+ if (str) {
+ wan_compr = parse_wan_compression(str);
+ }
+ spice_server_set_jpeg_compression(spice_server, wan_compr);
+
+ wan_compr = SPICE_WAN_COMPRESSION_AUTO;
+ str = qemu_opt_get(opts, "zlib-glz-wan-compression");
+ if (str) {
+ wan_compr = parse_wan_compression(str);
+ }
+ spice_server_set_zlib_glz_compression(spice_server, wan_compr);
+
+ str = qemu_opt_get(opts, "streaming-video");
+ if (str) {
+ int streaming_video = parse_stream_video(str);
+ spice_server_set_streaming_video(spice_server, streaming_video);
+ } else {
+ spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF);
+ }
+
+ spice_server_set_agent_mouse
+ (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1));
+ spice_server_set_playback_compression
+ (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1));
+
+ qemu_opt_foreach(opts, add_channel, &tls_port, &error_fatal);
+
+ spice_server_set_name(spice_server, qemu_name ?: "QEMU " QEMU_VERSION);
+ spice_server_set_uuid(spice_server, (unsigned char *)&qemu_uuid);
+
+ seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0);
+ spice_server_set_seamless_migration(spice_server, seamless_migration);
+ spice_server_set_sasl_appname(spice_server, "qemu");
+ if (spice_server_init(spice_server, &core_interface) != 0) {
+ error_report("failed to initialize spice server");
+ exit(1);
+ };
+ using_spice = 1;
+
+ migration_state.notify = migration_state_notifier;
+ add_migration_state_change_notifier(&migration_state);
+ spice_migrate.base.sif = &migrate_interface.base;
+ qemu_spice.add_interface(&spice_migrate.base);
+
+ qemu_spice_input_init();
+
+ qemu_spice_display_stop();
+
+ g_free(x509_key_file);
+ g_free(x509_cert_file);
+ g_free(x509_cacert_file);
+ g_free(password);
+
+#ifdef HAVE_SPICE_GL
+ if (qemu_opt_get_bool(opts, "gl", 0)) {
+ if ((port != 0) || (tls_port != 0)) {
+ error_report("SPICE GL support is local-only for now and "
+ "incompatible with -spice port/tls-port");
+ exit(1);
+ }
+ if (egl_rendernode_init(qemu_opt_get(opts, "rendernode"),
+ DISPLAYGL_MODE_ON) != 0) {
+ error_report("Failed to initialize EGL render node for SPICE GL");
+ exit(1);
+ }
+ display_opengl = 1;
+ spice_opengl = 1;
+ }
+#endif
+}
+
+static int qemu_spice_add_interface(SpiceBaseInstance *sin)
+{
+ if (!spice_server) {
+ if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) {
+ error_report("Oops: spice configured but not active");
+ exit(1);
+ }
+ /*
+ * Create a spice server instance.
+ * It does *not* listen on the network.
+ * It handles QXL local rendering only.
+ *
+ * With a command line like '-vnc :0 -vga qxl' you'll end up here.
+ */
+ spice_server = spice_server_new();
+ spice_server_set_sasl_appname(spice_server, "qemu");
+ spice_server_init(spice_server, &core_interface);
+ qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
+ }
+
+ return spice_server_add_interface(spice_server, sin);
+}
+
+static GSList *spice_consoles;
+
+bool qemu_spice_have_display_interface(QemuConsole *con)
+{
+ if (g_slist_find(spice_consoles, con)) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Recursively (in reverse order) appends addresses of PCI devices as it moves
+ * up in the PCI hierarchy.
+ *
+ * @returns true on success, false when the buffer wasn't large enough
+ */
+static bool append_pci_address(char *buf, size_t buf_size, const PCIDevice *pci)
+{
+ PCIBus *bus = pci_get_bus(pci);
+ /*
+ * equivalent to if (!pci_bus_is_root(bus)), but the function is not built
+ * with PCI_CONFIG=n, avoid using an #ifdef by checking directly
+ */
+ if (bus->parent_dev != NULL) {
+ append_pci_address(buf, buf_size, bus->parent_dev);
+ }
+
+ size_t len = strlen(buf);
+ ssize_t written = snprintf(buf + len, buf_size - len, "/%02x.%x",
+ PCI_SLOT(pci->devfn), PCI_FUNC(pci->devfn));
+
+ return written > 0 && written < buf_size - len;
+}
+
+bool qemu_spice_fill_device_address(QemuConsole *con,
+ char *device_address,
+ size_t size)
+{
+ DeviceState *dev = DEVICE(object_property_get_link(OBJECT(con),
+ "device",
+ &error_abort));
+ PCIDevice *pci = (PCIDevice *) object_dynamic_cast(OBJECT(dev),
+ TYPE_PCI_DEVICE);
+
+ if (pci == NULL) {
+ warn_report("Setting device address of a display device to SPICE: "
+ "Not a PCI device.");
+ return false;
+ }
+
+ strncpy(device_address, "pci/0000", size);
+ if (!append_pci_address(device_address, size, pci)) {
+ warn_report("Setting device address of a display device to SPICE: "
+ "Too many PCI devices in the chain.");
+ return false;
+ }
+
+ return true;
+}
+
+int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con)
+{
+ if (g_slist_find(spice_consoles, con)) {
+ return -1;
+ }
+ qxlin->id = qemu_console_get_index(con);
+ spice_consoles = g_slist_append(spice_consoles, con);
+ return qemu_spice_add_interface(&qxlin->base);
+}
+
+static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn)
+{
+ time_t lifetime, now = time(NULL);
+ char *passwd;
+
+ if (now < auth_expires) {
+ passwd = auth_passwd;
+ lifetime = (auth_expires - now);
+ if (lifetime > INT_MAX) {
+ lifetime = INT_MAX;
+ }
+ } else {
+ passwd = NULL;
+ lifetime = 1;
+ }
+ return spice_server_set_ticket(spice_server, passwd, lifetime,
+ fail_if_conn, disconnect_if_conn);
+}
+
+static int qemu_spice_set_passwd(const char *passwd,
+ bool fail_if_conn, bool disconnect_if_conn)
+{
+ if (strcmp(auth, "spice") != 0) {
+ return -1;
+ }
+
+ g_free(auth_passwd);
+ auth_passwd = g_strdup(passwd);
+ return qemu_spice_set_ticket(fail_if_conn, disconnect_if_conn);
+}
+
+static int qemu_spice_set_pw_expire(time_t expires)
+{
+ auth_expires = expires;
+ return qemu_spice_set_ticket(false, false);
+}
+
+static int qemu_spice_display_add_client(int csock, int skipauth, int tls)
+{
+ if (tls) {
+ return spice_server_add_ssl_client(spice_server, csock, skipauth);
+ } else {
+ return spice_server_add_client(spice_server, csock, skipauth);
+ }
+}
+
+void qemu_spice_display_start(void)
+{
+ if (spice_display_is_running) {
+ return;
+ }
+
+ spice_display_is_running = true;
+ spice_server_vm_start(spice_server);
+}
+
+void qemu_spice_display_stop(void)
+{
+ if (!spice_display_is_running) {
+ return;
+ }
+
+ spice_server_vm_stop(spice_server);
+ spice_display_is_running = false;
+}
+
+int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd)
+{
+ return spice_display_is_running;
+}
+
+static struct QemuSpiceOps real_spice_ops = {
+ .init = qemu_spice_init,
+ .display_init = qemu_spice_display_init,
+ .migrate_info = qemu_spice_migrate_info,
+ .set_passwd = qemu_spice_set_passwd,
+ .set_pw_expire = qemu_spice_set_pw_expire,
+ .display_add_client = qemu_spice_display_add_client,
+ .add_interface = qemu_spice_add_interface,
+ .qmp_query = qmp_query_spice_real,
+};
+
+static void spice_register_config(void)
+{
+ qemu_spice = real_spice_ops;
+ qemu_add_opts(&qemu_spice_opts);
+}
+opts_init(spice_register_config);
+module_opts("spice");
+
+#ifdef HAVE_SPICE_GL
+module_dep("ui-opengl");
+#endif
diff --git a/ui/spice-display.c b/ui/spice-display.c
new file mode 100644
index 000000000..f59c69882
--- /dev/null
+++ b/ui/spice-display.c
@@ -0,0 +1,1199 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "ui/qemu-spice.h"
+#include "qemu/timer.h"
+#include "qemu/lockable.h"
+#include "qemu/main-loop.h"
+#include "qemu/option.h"
+#include "qemu/queue.h"
+#include "ui/console.h"
+#include "trace.h"
+
+#include "ui/spice-display.h"
+
+bool spice_opengl;
+
+int qemu_spice_rect_is_empty(const QXLRect* r)
+{
+ return r->top == r->bottom || r->left == r->right;
+}
+
+void qemu_spice_rect_union(QXLRect *dest, const QXLRect *r)
+{
+ if (qemu_spice_rect_is_empty(r)) {
+ return;
+ }
+
+ if (qemu_spice_rect_is_empty(dest)) {
+ *dest = *r;
+ return;
+ }
+
+ dest->top = MIN(dest->top, r->top);
+ dest->left = MIN(dest->left, r->left);
+ dest->bottom = MAX(dest->bottom, r->bottom);
+ dest->right = MAX(dest->right, r->right);
+}
+
+QXLCookie *qxl_cookie_new(int type, uint64_t io)
+{
+ QXLCookie *cookie;
+
+ cookie = g_malloc0(sizeof(*cookie));
+ cookie->type = type;
+ cookie->io = io;
+ return cookie;
+}
+
+void qemu_spice_add_memslot(SimpleSpiceDisplay *ssd, QXLDevMemSlot *memslot,
+ qxl_async_io async)
+{
+ trace_qemu_spice_add_memslot(ssd->qxl.id, memslot->slot_id,
+ memslot->virt_start, memslot->virt_end,
+ async);
+
+ if (async != QXL_SYNC) {
+ spice_qxl_add_memslot_async(&ssd->qxl, memslot,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_MEMSLOT_ADD_ASYNC));
+ } else {
+ spice_qxl_add_memslot(&ssd->qxl, memslot);
+ }
+}
+
+void qemu_spice_del_memslot(SimpleSpiceDisplay *ssd, uint32_t gid, uint32_t sid)
+{
+ trace_qemu_spice_del_memslot(ssd->qxl.id, gid, sid);
+ spice_qxl_del_memslot(&ssd->qxl, gid, sid);
+}
+
+void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id,
+ QXLDevSurfaceCreate *surface,
+ qxl_async_io async)
+{
+ trace_qemu_spice_create_primary_surface(ssd->qxl.id, id, surface, async);
+ if (async != QXL_SYNC) {
+ spice_qxl_create_primary_surface_async(&ssd->qxl, id, surface,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_CREATE_PRIMARY_ASYNC));
+ } else {
+ spice_qxl_create_primary_surface(&ssd->qxl, id, surface);
+ }
+}
+
+void qemu_spice_destroy_primary_surface(SimpleSpiceDisplay *ssd,
+ uint32_t id, qxl_async_io async)
+{
+ trace_qemu_spice_destroy_primary_surface(ssd->qxl.id, id, async);
+ if (async != QXL_SYNC) {
+ spice_qxl_destroy_primary_surface_async(&ssd->qxl, id,
+ (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_DESTROY_PRIMARY_ASYNC));
+ } else {
+ spice_qxl_destroy_primary_surface(&ssd->qxl, id);
+ }
+}
+
+void qemu_spice_wakeup(SimpleSpiceDisplay *ssd)
+{
+ trace_qemu_spice_wakeup(ssd->qxl.id);
+ spice_qxl_wakeup(&ssd->qxl);
+}
+
+static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd,
+ QXLRect *rect)
+{
+ SimpleSpiceUpdate *update;
+ QXLDrawable *drawable;
+ QXLImage *image;
+ QXLCommand *cmd;
+ int bw, bh;
+ struct timespec time_space;
+ pixman_image_t *dest;
+
+ trace_qemu_spice_create_update(
+ rect->left, rect->right,
+ rect->top, rect->bottom);
+
+ update = g_malloc0(sizeof(*update));
+ drawable = &update->drawable;
+ image = &update->image;
+ cmd = &update->ext.cmd;
+
+ bw = rect->right - rect->left;
+ bh = rect->bottom - rect->top;
+ update->bitmap = g_malloc(bw * bh * 4);
+
+ drawable->bbox = *rect;
+ drawable->clip.type = SPICE_CLIP_TYPE_NONE;
+ drawable->effect = QXL_EFFECT_OPAQUE;
+ drawable->release_info.id = (uintptr_t)(&update->ext);
+ drawable->type = QXL_DRAW_COPY;
+ drawable->surfaces_dest[0] = -1;
+ drawable->surfaces_dest[1] = -1;
+ drawable->surfaces_dest[2] = -1;
+ clock_gettime(CLOCK_MONOTONIC, &time_space);
+ /* time in milliseconds from epoch. */
+ drawable->mm_time = time_space.tv_sec * 1000
+ + time_space.tv_nsec / 1000 / 1000;
+
+ drawable->u.copy.rop_descriptor = SPICE_ROPD_OP_PUT;
+ drawable->u.copy.src_bitmap = (uintptr_t)image;
+ drawable->u.copy.src_area.right = bw;
+ drawable->u.copy.src_area.bottom = bh;
+
+ QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, ssd->unique++);
+ image->descriptor.type = SPICE_IMAGE_TYPE_BITMAP;
+ image->bitmap.flags = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN;
+ image->bitmap.stride = bw * 4;
+ image->descriptor.width = image->bitmap.x = bw;
+ image->descriptor.height = image->bitmap.y = bh;
+ image->bitmap.data = (uintptr_t)(update->bitmap);
+ image->bitmap.palette = 0;
+ image->bitmap.format = SPICE_BITMAP_FMT_32BIT;
+
+ dest = pixman_image_create_bits(PIXMAN_LE_x8r8g8b8, bw, bh,
+ (void *)update->bitmap, bw * 4);
+ pixman_image_composite(PIXMAN_OP_SRC, ssd->surface, NULL, ssd->mirror,
+ rect->left, rect->top, 0, 0,
+ rect->left, rect->top, bw, bh);
+ pixman_image_composite(PIXMAN_OP_SRC, ssd->mirror, NULL, dest,
+ rect->left, rect->top, 0, 0,
+ 0, 0, bw, bh);
+ pixman_image_unref(dest);
+
+ cmd->type = QXL_CMD_DRAW;
+ cmd->data = (uintptr_t)drawable;
+
+ QTAILQ_INSERT_TAIL(&ssd->updates, update, next);
+}
+
+static void qemu_spice_create_update(SimpleSpiceDisplay *ssd)
+{
+ static const int blksize = 32;
+ int blocks = DIV_ROUND_UP(surface_width(ssd->ds), blksize);
+ int dirty_top[blocks];
+ int y, yoff1, yoff2, x, xoff, blk, bw;
+ int bpp = surface_bytes_per_pixel(ssd->ds);
+ uint8_t *guest, *mirror;
+
+ if (qemu_spice_rect_is_empty(&ssd->dirty)) {
+ return;
+ };
+
+ for (blk = 0; blk < blocks; blk++) {
+ dirty_top[blk] = -1;
+ }
+
+ guest = surface_data(ssd->ds);
+ mirror = (void *)pixman_image_get_data(ssd->mirror);
+ for (y = ssd->dirty.top; y < ssd->dirty.bottom; y++) {
+ yoff1 = y * surface_stride(ssd->ds);
+ yoff2 = y * pixman_image_get_stride(ssd->mirror);
+ for (x = ssd->dirty.left; x < ssd->dirty.right; x += blksize) {
+ xoff = x * bpp;
+ blk = x / blksize;
+ bw = MIN(blksize, ssd->dirty.right - x);
+ if (memcmp(guest + yoff1 + xoff,
+ mirror + yoff2 + xoff,
+ bw * bpp) == 0) {
+ if (dirty_top[blk] != -1) {
+ QXLRect update = {
+ .top = dirty_top[blk],
+ .bottom = y,
+ .left = x,
+ .right = x + bw,
+ };
+ qemu_spice_create_one_update(ssd, &update);
+ dirty_top[blk] = -1;
+ }
+ } else {
+ if (dirty_top[blk] == -1) {
+ dirty_top[blk] = y;
+ }
+ }
+ }
+ }
+
+ for (x = ssd->dirty.left; x < ssd->dirty.right; x += blksize) {
+ blk = x / blksize;
+ bw = MIN(blksize, ssd->dirty.right - x);
+ if (dirty_top[blk] != -1) {
+ QXLRect update = {
+ .top = dirty_top[blk],
+ .bottom = ssd->dirty.bottom,
+ .left = x,
+ .right = x + bw,
+ };
+ qemu_spice_create_one_update(ssd, &update);
+ dirty_top[blk] = -1;
+ }
+ }
+
+ memset(&ssd->dirty, 0, sizeof(ssd->dirty));
+}
+
+static SimpleSpiceCursor*
+qemu_spice_create_cursor_update(SimpleSpiceDisplay *ssd,
+ QEMUCursor *c,
+ int on)
+{
+ size_t size = c ? c->width * c->height * 4 : 0;
+ SimpleSpiceCursor *update;
+ QXLCursorCmd *ccmd;
+ QXLCursor *cursor;
+ QXLCommand *cmd;
+
+ update = g_malloc0(sizeof(*update) + size);
+ ccmd = &update->cmd;
+ cursor = &update->cursor;
+ cmd = &update->ext.cmd;
+
+ if (c) {
+ ccmd->type = QXL_CURSOR_SET;
+ ccmd->u.set.position.x = ssd->ptr_x + ssd->hot_x;
+ ccmd->u.set.position.y = ssd->ptr_y + ssd->hot_y;
+ ccmd->u.set.visible = true;
+ ccmd->u.set.shape = (uintptr_t)cursor;
+ cursor->header.unique = ssd->unique++;
+ cursor->header.type = SPICE_CURSOR_TYPE_ALPHA;
+ cursor->header.width = c->width;
+ cursor->header.height = c->height;
+ cursor->header.hot_spot_x = c->hot_x;
+ cursor->header.hot_spot_y = c->hot_y;
+ cursor->data_size = size;
+ cursor->chunk.data_size = size;
+ memcpy(cursor->chunk.data, c->data, size);
+ } else if (!on) {
+ ccmd->type = QXL_CURSOR_HIDE;
+ } else {
+ ccmd->type = QXL_CURSOR_MOVE;
+ ccmd->u.position.x = ssd->ptr_x + ssd->hot_x;
+ ccmd->u.position.y = ssd->ptr_y + ssd->hot_y;
+ }
+ ccmd->release_info.id = (uintptr_t)(&update->ext);
+
+ cmd->type = QXL_CMD_CURSOR;
+ cmd->data = (uintptr_t)ccmd;
+
+ return update;
+}
+
+/*
+ * Called from spice server thread context (via interface_release_resource)
+ * We do *not* hold the global qemu mutex here, so extra care is needed
+ * when calling qemu functions. QEMU interfaces used:
+ * - g_free (underlying glibc free is re-entrant).
+ */
+void qemu_spice_destroy_update(SimpleSpiceDisplay *sdpy, SimpleSpiceUpdate *update)
+{
+ g_free(update->bitmap);
+ g_free(update);
+}
+
+void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd)
+{
+ QXLDevMemSlot memslot;
+
+ memset(&memslot, 0, sizeof(memslot));
+ memslot.slot_group_id = MEMSLOT_GROUP_HOST;
+ memslot.virt_end = ~0;
+ qemu_spice_add_memslot(ssd, &memslot, QXL_SYNC);
+}
+
+void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd)
+{
+ QXLDevSurfaceCreate surface;
+ uint64_t surface_size;
+
+ memset(&surface, 0, sizeof(surface));
+
+ surface_size = (uint64_t) surface_width(ssd->ds) *
+ surface_height(ssd->ds) * 4;
+ assert(surface_size > 0);
+ assert(surface_size < INT_MAX);
+ if (ssd->bufsize < surface_size) {
+ ssd->bufsize = surface_size;
+ g_free(ssd->buf);
+ ssd->buf = g_malloc(ssd->bufsize);
+ }
+
+ surface.format = SPICE_SURFACE_FMT_32_xRGB;
+ surface.width = surface_width(ssd->ds);
+ surface.height = surface_height(ssd->ds);
+ surface.stride = -surface.width * 4;
+ surface.mouse_mode = true;
+ surface.flags = 0;
+ surface.type = 0;
+ surface.mem = (uintptr_t)ssd->buf;
+ surface.group_id = MEMSLOT_GROUP_HOST;
+
+ qemu_spice_create_primary_surface(ssd, 0, &surface, QXL_SYNC);
+}
+
+void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd)
+{
+ qemu_spice_destroy_primary_surface(ssd, 0, QXL_SYNC);
+}
+
+void qemu_spice_display_init_common(SimpleSpiceDisplay *ssd)
+{
+ qemu_mutex_init(&ssd->lock);
+ QTAILQ_INIT(&ssd->updates);
+ ssd->mouse_x = -1;
+ ssd->mouse_y = -1;
+ if (ssd->num_surfaces == 0) {
+ ssd->num_surfaces = 1024;
+ }
+}
+
+/* display listener callbacks */
+
+void qemu_spice_display_update(SimpleSpiceDisplay *ssd,
+ int x, int y, int w, int h)
+{
+ QXLRect update_area;
+
+ trace_qemu_spice_display_update(ssd->qxl.id, x, y, w, h);
+ update_area.left = x,
+ update_area.right = x + w;
+ update_area.top = y;
+ update_area.bottom = y + h;
+
+ if (qemu_spice_rect_is_empty(&ssd->dirty)) {
+ ssd->notify++;
+ }
+ qemu_spice_rect_union(&ssd->dirty, &update_area);
+}
+
+void qemu_spice_display_switch(SimpleSpiceDisplay *ssd,
+ DisplaySurface *surface)
+{
+ SimpleSpiceUpdate *update;
+ bool need_destroy;
+
+ if (ssd->surface &&
+ surface_width(surface) == pixman_image_get_width(ssd->surface) &&
+ surface_height(surface) == pixman_image_get_height(ssd->surface) &&
+ surface_format(surface) == pixman_image_get_format(ssd->surface)) {
+ /* no-resize fast path: just swap backing store */
+ trace_qemu_spice_display_surface(ssd->qxl.id,
+ surface_width(surface),
+ surface_height(surface),
+ true);
+ qemu_mutex_lock(&ssd->lock);
+ ssd->ds = surface;
+ pixman_image_unref(ssd->surface);
+ ssd->surface = pixman_image_ref(ssd->ds->image);
+ qemu_mutex_unlock(&ssd->lock);
+ qemu_spice_display_update(ssd, 0, 0,
+ surface_width(surface),
+ surface_height(surface));
+ return;
+ }
+
+ /* full mode switch */
+ trace_qemu_spice_display_surface(ssd->qxl.id,
+ surface_width(surface),
+ surface_height(surface),
+ false);
+
+ memset(&ssd->dirty, 0, sizeof(ssd->dirty));
+ if (ssd->surface) {
+ pixman_image_unref(ssd->surface);
+ ssd->surface = NULL;
+ pixman_image_unref(ssd->mirror);
+ ssd->mirror = NULL;
+ }
+
+ qemu_mutex_lock(&ssd->lock);
+ need_destroy = (ssd->ds != NULL);
+ ssd->ds = surface;
+ while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) {
+ QTAILQ_REMOVE(&ssd->updates, update, next);
+ qemu_spice_destroy_update(ssd, update);
+ }
+ qemu_mutex_unlock(&ssd->lock);
+ if (need_destroy) {
+ qemu_spice_destroy_host_primary(ssd);
+ }
+ if (ssd->ds) {
+ ssd->surface = pixman_image_ref(ssd->ds->image);
+ ssd->mirror = qemu_pixman_mirror_create(ssd->ds->format,
+ ssd->ds->image);
+ qemu_spice_create_host_primary(ssd);
+ }
+
+ memset(&ssd->dirty, 0, sizeof(ssd->dirty));
+ ssd->notify++;
+
+ qemu_mutex_lock(&ssd->lock);
+ if (ssd->cursor) {
+ g_free(ssd->ptr_define);
+ ssd->ptr_define = qemu_spice_create_cursor_update(ssd, ssd->cursor, 0);
+ }
+ qemu_mutex_unlock(&ssd->lock);
+}
+
+void qemu_spice_cursor_refresh_bh(void *opaque)
+{
+ SimpleSpiceDisplay *ssd = opaque;
+
+ qemu_mutex_lock(&ssd->lock);
+ if (ssd->cursor) {
+ QEMUCursor *c = ssd->cursor;
+ assert(ssd->dcl.con);
+ cursor_get(c);
+ qemu_mutex_unlock(&ssd->lock);
+ dpy_cursor_define(ssd->dcl.con, c);
+ qemu_mutex_lock(&ssd->lock);
+ cursor_put(c);
+ }
+
+ if (ssd->mouse_x != -1 && ssd->mouse_y != -1) {
+ int x, y;
+ assert(ssd->dcl.con);
+ x = ssd->mouse_x;
+ y = ssd->mouse_y;
+ ssd->mouse_x = -1;
+ ssd->mouse_y = -1;
+ qemu_mutex_unlock(&ssd->lock);
+ dpy_mouse_set(ssd->dcl.con, x, y, 1);
+ } else {
+ qemu_mutex_unlock(&ssd->lock);
+ }
+}
+
+void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd)
+{
+ graphic_hw_update(ssd->dcl.con);
+
+ WITH_QEMU_LOCK_GUARD(&ssd->lock) {
+ if (QTAILQ_EMPTY(&ssd->updates) && ssd->ds) {
+ qemu_spice_create_update(ssd);
+ ssd->notify++;
+ }
+ }
+
+ trace_qemu_spice_display_refresh(ssd->qxl.id, ssd->notify);
+ if (ssd->notify) {
+ ssd->notify = 0;
+ qemu_spice_wakeup(ssd);
+ }
+}
+
+/* spice display interface callbacks */
+
+static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
+{
+ /* nothing to do */
+}
+
+static void interface_set_compression_level(QXLInstance *sin, int level)
+{
+ /* nothing to do */
+}
+
+#if SPICE_NEEDS_SET_MM_TIME
+static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
+{
+ /* nothing to do */
+}
+#endif
+
+static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
+{
+ SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+
+ info->memslot_gen_bits = MEMSLOT_GENERATION_BITS;
+ info->memslot_id_bits = MEMSLOT_SLOT_BITS;
+ info->num_memslots = NUM_MEMSLOTS;
+ info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
+ info->internal_groupslot_id = 0;
+ info->qxl_ram_size = 16 * 1024 * 1024;
+ info->n_surfaces = ssd->num_surfaces;
+}
+
+static int interface_get_command(QXLInstance *sin, QXLCommandExt *ext)
+{
+ SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+ SimpleSpiceUpdate *update;
+ int ret = false;
+
+ qemu_mutex_lock(&ssd->lock);
+ update = QTAILQ_FIRST(&ssd->updates);
+ if (update != NULL) {
+ QTAILQ_REMOVE(&ssd->updates, update, next);
+ *ext = update->ext;
+ ret = true;
+ }
+ qemu_mutex_unlock(&ssd->lock);
+
+ return ret;
+}
+
+static int interface_req_cmd_notification(QXLInstance *sin)
+{
+ return 1;
+}
+
+static void interface_release_resource(QXLInstance *sin,
+ QXLReleaseInfoExt rext)
+{
+ SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+ SimpleSpiceUpdate *update;
+ SimpleSpiceCursor *cursor;
+ QXLCommandExt *ext;
+
+ if (!rext.info) {
+ return;
+ }
+
+ ext = (void *)(intptr_t)(rext.info->id);
+ switch (ext->cmd.type) {
+ case QXL_CMD_DRAW:
+ update = container_of(ext, SimpleSpiceUpdate, ext);
+ qemu_spice_destroy_update(ssd, update);
+ break;
+ case QXL_CMD_CURSOR:
+ cursor = container_of(ext, SimpleSpiceCursor, ext);
+ g_free(cursor);
+ break;
+ default:
+ g_assert_not_reached();
+ }
+}
+
+static int interface_get_cursor_command(QXLInstance *sin, QXLCommandExt *ext)
+{
+ SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+ int ret;
+
+ QEMU_LOCK_GUARD(&ssd->lock);
+ if (ssd->ptr_define) {
+ *ext = ssd->ptr_define->ext;
+ ssd->ptr_define = NULL;
+ ret = true;
+ } else if (ssd->ptr_move) {
+ *ext = ssd->ptr_move->ext;
+ ssd->ptr_move = NULL;
+ ret = true;
+ } else {
+ ret = false;
+ }
+ return ret;
+}
+
+static int interface_req_cursor_notification(QXLInstance *sin)
+{
+ return 1;
+}
+
+static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
+{
+ fprintf(stderr, "%s: abort()\n", __func__);
+ abort();
+}
+
+static int interface_flush_resources(QXLInstance *sin)
+{
+ fprintf(stderr, "%s: abort()\n", __func__);
+ abort();
+ return 0;
+}
+
+static void interface_update_area_complete(QXLInstance *sin,
+ uint32_t surface_id,
+ QXLRect *dirty, uint32_t num_updated_rects)
+{
+ /* should never be called, used in qxl native mode only */
+ fprintf(stderr, "%s: abort()\n", __func__);
+ abort();
+}
+
+/* called from spice server thread context only */
+static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
+{
+ QXLCookie *cookie = (QXLCookie *)(uintptr_t)cookie_token;
+
+ switch (cookie->type) {
+#ifdef HAVE_SPICE_GL
+ case QXL_COOKIE_TYPE_GL_DRAW_DONE:
+ {
+ SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+ qemu_bh_schedule(ssd->gl_unblock_bh);
+ break;
+ }
+ case QXL_COOKIE_TYPE_IO:
+ if (cookie->io == QXL_IO_MONITORS_CONFIG_ASYNC) {
+ g_free(cookie->u.data);
+ }
+ break;
+#endif
+ default:
+ /* should never be called, used in qxl native mode only */
+ fprintf(stderr, "%s: abort()\n", __func__);
+ abort();
+ }
+ g_free(cookie);
+}
+
+static void interface_set_client_capabilities(QXLInstance *sin,
+ uint8_t client_present,
+ uint8_t caps[58])
+{
+ /* nothing to do */
+}
+
+static int interface_client_monitors_config(QXLInstance *sin,
+ VDAgentMonitorsConfig *mc)
+{
+ SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
+ QemuUIInfo info;
+ int head;
+
+ if (!dpy_ui_info_supported(ssd->dcl.con)) {
+ return 0; /* == not supported by guest */
+ }
+
+ if (!mc) {
+ return 1;
+ }
+
+ info = *dpy_get_ui_info(ssd->dcl.con);
+
+ head = qemu_console_get_index(ssd->dcl.con);
+ if (mc->num_of_monitors > head) {
+ info.width = mc->monitors[head].width;
+ info.height = mc->monitors[head].height;
+#if SPICE_SERVER_VERSION >= 0x000e04 /* release 0.14.4 */
+ if (mc->flags & VD_AGENT_CONFIG_MONITORS_FLAG_PHYSICAL_SIZE) {
+ VDAgentMonitorMM *mm = (void *)&mc->monitors[mc->num_of_monitors];
+ info.width_mm = mm[head].width;
+ info.height_mm = mm[head].height;
+ }
+#endif
+ }
+
+ trace_qemu_spice_ui_info(ssd->qxl.id, info.width, info.height);
+ dpy_set_ui_info(ssd->dcl.con, &info);
+ return 1;
+}
+
+static const QXLInterface dpy_interface = {
+ .base.type = SPICE_INTERFACE_QXL,
+ .base.description = "qemu simple display",
+ .base.major_version = SPICE_INTERFACE_QXL_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_QXL_MINOR,
+
+ .attache_worker = interface_attach_worker,
+ .set_compression_level = interface_set_compression_level,
+#if SPICE_NEEDS_SET_MM_TIME
+ .set_mm_time = interface_set_mm_time,
+#endif
+ .get_init_info = interface_get_init_info,
+
+ /* the callbacks below are called from spice server thread context */
+ .get_command = interface_get_command,
+ .req_cmd_notification = interface_req_cmd_notification,
+ .release_resource = interface_release_resource,
+ .get_cursor_command = interface_get_cursor_command,
+ .req_cursor_notification = interface_req_cursor_notification,
+ .notify_update = interface_notify_update,
+ .flush_resources = interface_flush_resources,
+ .async_complete = interface_async_complete,
+ .update_area_complete = interface_update_area_complete,
+ .set_client_capabilities = interface_set_client_capabilities,
+ .client_monitors_config = interface_client_monitors_config,
+};
+
+static void display_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ qemu_spice_display_update(ssd, x, y, w, h);
+}
+
+static void display_switch(DisplayChangeListener *dcl,
+ DisplaySurface *surface)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ qemu_spice_display_switch(ssd, surface);
+}
+
+static void display_refresh(DisplayChangeListener *dcl)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ qemu_spice_display_refresh(ssd);
+}
+
+static void display_mouse_set(DisplayChangeListener *dcl,
+ int x, int y, int on)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ qemu_mutex_lock(&ssd->lock);
+ ssd->ptr_x = x;
+ ssd->ptr_y = y;
+ g_free(ssd->ptr_move);
+ ssd->ptr_move = qemu_spice_create_cursor_update(ssd, NULL, on);
+ qemu_mutex_unlock(&ssd->lock);
+ qemu_spice_wakeup(ssd);
+}
+
+static void display_mouse_define(DisplayChangeListener *dcl,
+ QEMUCursor *c)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ qemu_mutex_lock(&ssd->lock);
+ cursor_get(c);
+ cursor_put(ssd->cursor);
+ ssd->cursor = c;
+ ssd->hot_x = c->hot_x;
+ ssd->hot_y = c->hot_y;
+ g_free(ssd->ptr_move);
+ ssd->ptr_move = NULL;
+ g_free(ssd->ptr_define);
+ ssd->ptr_define = qemu_spice_create_cursor_update(ssd, c, 0);
+ qemu_mutex_unlock(&ssd->lock);
+ qemu_spice_wakeup(ssd);
+}
+
+static const DisplayChangeListenerOps display_listener_ops = {
+ .dpy_name = "spice",
+ .dpy_gfx_update = display_update,
+ .dpy_gfx_switch = display_switch,
+ .dpy_gfx_check_format = qemu_pixman_check_format,
+ .dpy_refresh = display_refresh,
+ .dpy_mouse_set = display_mouse_set,
+ .dpy_cursor_define = display_mouse_define,
+};
+
+#ifdef HAVE_SPICE_GL
+
+static void qemu_spice_gl_monitor_config(SimpleSpiceDisplay *ssd,
+ int x, int y, int w, int h)
+{
+ QXLMonitorsConfig *config;
+ QXLCookie *cookie;
+
+ config = g_malloc0(sizeof(QXLMonitorsConfig) + sizeof(QXLHead));
+ config->count = 1;
+ config->max_allowed = 1;
+ config->heads[0].x = x;
+ config->heads[0].y = y;
+ config->heads[0].width = w;
+ config->heads[0].height = h;
+ cookie = qxl_cookie_new(QXL_COOKIE_TYPE_IO,
+ QXL_IO_MONITORS_CONFIG_ASYNC);
+ cookie->u.data = config;
+
+ spice_qxl_monitors_config_async(&ssd->qxl,
+ (uintptr_t)config,
+ MEMSLOT_GROUP_HOST,
+ (uintptr_t)cookie);
+}
+
+static void qemu_spice_gl_block(SimpleSpiceDisplay *ssd, bool block)
+{
+ uint64_t timeout;
+
+ if (block) {
+ timeout = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+ timeout += 1000; /* one sec */
+ timer_mod(ssd->gl_unblock_timer, timeout);
+ } else {
+ timer_del(ssd->gl_unblock_timer);
+ }
+ graphic_hw_gl_block(ssd->dcl.con, block);
+}
+
+static void qemu_spice_gl_unblock_bh(void *opaque)
+{
+ SimpleSpiceDisplay *ssd = opaque;
+
+ qemu_spice_gl_block(ssd, false);
+ graphic_hw_gl_flushed(ssd->dcl.con);
+}
+
+static void qemu_spice_gl_block_timer(void *opaque)
+{
+ warn_report("spice: no gl-draw-done within one second");
+}
+
+static void spice_gl_refresh(DisplayChangeListener *dcl)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ uint64_t cookie;
+
+ if (!ssd->ds || qemu_console_is_gl_blocked(ssd->dcl.con)) {
+ return;
+ }
+
+ graphic_hw_update(dcl->con);
+ if (ssd->gl_updates && ssd->have_surface) {
+ qemu_spice_gl_block(ssd, true);
+ glFlush();
+ cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
+ spice_qxl_gl_draw_async(&ssd->qxl, 0, 0,
+ surface_width(ssd->ds),
+ surface_height(ssd->ds),
+ cookie);
+ ssd->gl_updates = 0;
+ }
+}
+
+static void spice_gl_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ surface_gl_update_texture(ssd->gls, ssd->ds, x, y, w, h);
+ ssd->gl_updates++;
+}
+
+static void spice_gl_switch(DisplayChangeListener *dcl,
+ struct DisplaySurface *new_surface)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ EGLint stride, fourcc;
+ int fd;
+
+ if (ssd->ds) {
+ surface_gl_destroy_texture(ssd->gls, ssd->ds);
+ }
+ ssd->ds = new_surface;
+ if (ssd->ds) {
+ surface_gl_create_texture(ssd->gls, ssd->ds);
+ fd = egl_get_fd_for_texture(ssd->ds->texture,
+ &stride, &fourcc,
+ NULL);
+ if (fd < 0) {
+ surface_gl_destroy_texture(ssd->gls, ssd->ds);
+ return;
+ }
+
+ trace_qemu_spice_gl_surface(ssd->qxl.id,
+ surface_width(ssd->ds),
+ surface_height(ssd->ds),
+ fourcc);
+
+ /* note: spice server will close the fd */
+ spice_qxl_gl_scanout(&ssd->qxl, fd,
+ surface_width(ssd->ds),
+ surface_height(ssd->ds),
+ stride, fourcc, false);
+ ssd->have_surface = true;
+ ssd->have_scanout = false;
+
+ qemu_spice_gl_monitor_config(ssd, 0, 0,
+ surface_width(ssd->ds),
+ surface_height(ssd->ds));
+ }
+}
+
+static QEMUGLContext qemu_spice_gl_create_context(DisplayChangeListener *dcl,
+ QEMUGLParams *params)
+{
+ eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+ qemu_egl_rn_ctx);
+ return qemu_egl_create_context(dcl, params);
+}
+
+static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ trace_qemu_spice_gl_scanout_disable(ssd->qxl.id);
+ spice_qxl_gl_scanout(&ssd->qxl, -1, 0, 0, 0, 0, false);
+ qemu_spice_gl_monitor_config(ssd, 0, 0, 0, 0);
+ ssd->have_surface = false;
+ ssd->have_scanout = false;
+}
+
+static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl,
+ uint32_t tex_id,
+ bool y_0_top,
+ uint32_t backing_width,
+ uint32_t backing_height,
+ uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ EGLint stride = 0, fourcc = 0;
+ int fd = -1;
+
+ assert(tex_id);
+ fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL);
+ if (fd < 0) {
+ fprintf(stderr, "%s: failed to get fd for texture\n", __func__);
+ return;
+ }
+ trace_qemu_spice_gl_scanout_texture(ssd->qxl.id, w, h, fourcc);
+
+ /* note: spice server will close the fd */
+ spice_qxl_gl_scanout(&ssd->qxl, fd, backing_width, backing_height,
+ stride, fourcc, y_0_top);
+ qemu_spice_gl_monitor_config(ssd, x, y, w, h);
+ ssd->have_surface = false;
+ ssd->have_scanout = true;
+}
+
+static void qemu_spice_gl_scanout_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ ssd->guest_dmabuf = dmabuf;
+ ssd->guest_dmabuf_refresh = true;
+
+ ssd->have_surface = false;
+ ssd->have_scanout = true;
+}
+
+static void qemu_spice_gl_cursor_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf, bool have_hot,
+ uint32_t hot_x, uint32_t hot_y)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ ssd->have_hot = have_hot;
+ ssd->hot_x = hot_x;
+ ssd->hot_y = hot_y;
+
+ trace_qemu_spice_gl_cursor(ssd->qxl.id, dmabuf != NULL, have_hot);
+ if (dmabuf) {
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+ egl_fb_setup_for_tex(&ssd->cursor_fb, dmabuf->width, dmabuf->height,
+ dmabuf->texture, false);
+ } else {
+ egl_fb_destroy(&ssd->cursor_fb);
+ }
+}
+
+static void qemu_spice_gl_cursor_position(DisplayChangeListener *dcl,
+ uint32_t pos_x, uint32_t pos_y)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ qemu_mutex_lock(&ssd->lock);
+ ssd->ptr_x = pos_x;
+ ssd->ptr_y = pos_y;
+ qemu_mutex_unlock(&ssd->lock);
+}
+
+static void qemu_spice_gl_release_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+
+ if (ssd->guest_dmabuf == dmabuf) {
+ ssd->guest_dmabuf = NULL;
+ ssd->guest_dmabuf_refresh = false;
+ }
+ egl_dmabuf_release_texture(dmabuf);
+}
+
+static void qemu_spice_gl_update(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
+ EGLint stride = 0, fourcc = 0;
+ bool render_cursor = false;
+ bool y_0_top = false; /* FIXME */
+ uint64_t cookie;
+ int fd;
+
+ if (!ssd->have_scanout) {
+ return;
+ }
+
+ if (ssd->cursor_fb.texture) {
+ render_cursor = true;
+ }
+ if (ssd->render_cursor != render_cursor) {
+ ssd->render_cursor = render_cursor;
+ ssd->guest_dmabuf_refresh = true;
+ egl_fb_destroy(&ssd->blit_fb);
+ }
+
+ if (ssd->guest_dmabuf_refresh) {
+ QemuDmaBuf *dmabuf = ssd->guest_dmabuf;
+ if (render_cursor) {
+ egl_dmabuf_import_texture(dmabuf);
+ if (!dmabuf->texture) {
+ return;
+ }
+
+ /* source framebuffer */
+ egl_fb_setup_for_tex(&ssd->guest_fb,
+ dmabuf->width, dmabuf->height,
+ dmabuf->texture, false);
+
+ /* dest framebuffer */
+ if (ssd->blit_fb.width != dmabuf->width ||
+ ssd->blit_fb.height != dmabuf->height) {
+ trace_qemu_spice_gl_render_dmabuf(ssd->qxl.id, dmabuf->width,
+ dmabuf->height);
+ egl_fb_destroy(&ssd->blit_fb);
+ egl_fb_setup_new_tex(&ssd->blit_fb,
+ dmabuf->width, dmabuf->height);
+ fd = egl_get_fd_for_texture(ssd->blit_fb.texture,
+ &stride, &fourcc, NULL);
+ spice_qxl_gl_scanout(&ssd->qxl, fd,
+ dmabuf->width, dmabuf->height,
+ stride, fourcc, false);
+ }
+ } else {
+ trace_qemu_spice_gl_forward_dmabuf(ssd->qxl.id,
+ dmabuf->width, dmabuf->height);
+ /* note: spice server will close the fd, so hand over a dup */
+ spice_qxl_gl_scanout(&ssd->qxl, dup(dmabuf->fd),
+ dmabuf->width, dmabuf->height,
+ dmabuf->stride, dmabuf->fourcc,
+ dmabuf->y0_top);
+ }
+ qemu_spice_gl_monitor_config(ssd, 0, 0, dmabuf->width, dmabuf->height);
+ ssd->guest_dmabuf_refresh = false;
+ }
+
+ if (render_cursor) {
+ int x, y;
+ qemu_mutex_lock(&ssd->lock);
+ x = ssd->ptr_x;
+ y = ssd->ptr_y;
+ qemu_mutex_unlock(&ssd->lock);
+ egl_texture_blit(ssd->gls, &ssd->blit_fb, &ssd->guest_fb,
+ !y_0_top);
+ egl_texture_blend(ssd->gls, &ssd->blit_fb, &ssd->cursor_fb,
+ !y_0_top, x, y, 1.0, 1.0);
+ glFlush();
+ }
+
+ trace_qemu_spice_gl_update(ssd->qxl.id, w, h, x, y);
+ qemu_spice_gl_block(ssd, true);
+ glFlush();
+ cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0);
+ spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie);
+}
+
+static const DisplayChangeListenerOps display_listener_gl_ops = {
+ .dpy_name = "spice-egl",
+ .dpy_gfx_update = spice_gl_update,
+ .dpy_gfx_switch = spice_gl_switch,
+ .dpy_gfx_check_format = console_gl_check_format,
+ .dpy_refresh = spice_gl_refresh,
+ .dpy_mouse_set = display_mouse_set,
+ .dpy_cursor_define = display_mouse_define,
+
+ .dpy_gl_ctx_create = qemu_spice_gl_create_context,
+ .dpy_gl_ctx_destroy = qemu_egl_destroy_context,
+ .dpy_gl_ctx_make_current = qemu_egl_make_context_current,
+
+ .dpy_gl_scanout_disable = qemu_spice_gl_scanout_disable,
+ .dpy_gl_scanout_texture = qemu_spice_gl_scanout_texture,
+ .dpy_gl_scanout_dmabuf = qemu_spice_gl_scanout_dmabuf,
+ .dpy_gl_cursor_dmabuf = qemu_spice_gl_cursor_dmabuf,
+ .dpy_gl_cursor_position = qemu_spice_gl_cursor_position,
+ .dpy_gl_release_dmabuf = qemu_spice_gl_release_dmabuf,
+ .dpy_gl_update = qemu_spice_gl_update,
+};
+
+#endif /* HAVE_SPICE_GL */
+
+static void qemu_spice_display_init_one(QemuConsole *con)
+{
+ SimpleSpiceDisplay *ssd = g_new0(SimpleSpiceDisplay, 1);
+
+ qemu_spice_display_init_common(ssd);
+
+ ssd->dcl.ops = &display_listener_ops;
+#ifdef HAVE_SPICE_GL
+ if (spice_opengl) {
+ ssd->dcl.ops = &display_listener_gl_ops;
+ ssd->gl_unblock_bh = qemu_bh_new(qemu_spice_gl_unblock_bh, ssd);
+ ssd->gl_unblock_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
+ qemu_spice_gl_block_timer, ssd);
+ ssd->gls = qemu_gl_init_shader();
+ ssd->have_surface = false;
+ ssd->have_scanout = false;
+ }
+#endif
+ ssd->dcl.con = con;
+
+ ssd->qxl.base.sif = &dpy_interface.base;
+ qemu_spice_add_display_interface(&ssd->qxl, con);
+
+#if SPICE_SERVER_VERSION >= 0x000e02 /* release 0.14.2 */
+ char device_address[256] = "";
+ if (qemu_spice_fill_device_address(con, device_address, 256)) {
+ spice_qxl_set_device_info(&ssd->qxl,
+ device_address,
+ qemu_console_get_head(con),
+ 1);
+ }
+#endif
+
+ qemu_spice_create_host_memslot(ssd);
+
+ register_displaychangelistener(&ssd->dcl);
+}
+
+void qemu_spice_display_init(void)
+{
+ QemuOptsList *olist = qemu_find_opts("spice");
+ QemuOpts *opts = QTAILQ_FIRST(&olist->head);
+ QemuConsole *spice_con, *con;
+ const char *str;
+ int i;
+
+ str = qemu_opt_get(opts, "display");
+ if (str) {
+ int head = qemu_opt_get_number(opts, "head", 0);
+ Error *err = NULL;
+
+ spice_con = qemu_console_lookup_by_device_name(str, head, &err);
+ if (err) {
+ error_report("Failed to lookup display/head");
+ exit(1);
+ }
+ } else {
+ spice_con = NULL;
+ }
+
+ for (i = 0;; i++) {
+ con = qemu_console_lookup_by_index(i);
+ if (!con || !qemu_console_is_graphic(con)) {
+ break;
+ }
+ if (qemu_spice_have_display_interface(con)) {
+ continue;
+ }
+ if (spice_con != NULL && spice_con != con) {
+ continue;
+ }
+ qemu_spice_display_init_one(con);
+ }
+
+ qemu_spice_display_init_done();
+}
diff --git a/ui/spice-input.c b/ui/spice-input.c
new file mode 100644
index 000000000..bbd502564
--- /dev/null
+++ b/ui/spice-input.c
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+
+#include <spice.h>
+#include <spice/enums.h>
+
+#include "ui/qemu-spice.h"
+#include "ui/console.h"
+#include "keymaps.h"
+#include "ui/input.h"
+
+/* keyboard bits */
+
+typedef struct QemuSpiceKbd {
+ SpiceKbdInstance sin;
+ int ledstate;
+ bool emul0;
+ size_t pauseseq;
+} QemuSpiceKbd;
+
+static void kbd_push_key(SpiceKbdInstance *sin, uint8_t frag);
+static uint8_t kbd_get_leds(SpiceKbdInstance *sin);
+
+static const SpiceKbdInterface kbd_interface = {
+ .base.type = SPICE_INTERFACE_KEYBOARD,
+ .base.description = "qemu keyboard",
+ .base.major_version = SPICE_INTERFACE_KEYBOARD_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_KEYBOARD_MINOR,
+ .push_scan_freg = kbd_push_key,
+ .get_leds = kbd_get_leds,
+};
+
+static void kbd_push_key(SpiceKbdInstance *sin, uint8_t scancode)
+{
+ static const uint8_t pauseseq[] = { 0xe1, 0x1d, 0x45, 0xe1, 0x9d, 0xc5 };
+ QemuSpiceKbd *kbd = container_of(sin, QemuSpiceKbd, sin);
+ int keycode;
+ bool up;
+
+ if (scancode == SCANCODE_EMUL0) {
+ kbd->emul0 = true;
+ return;
+ }
+
+ if (scancode == pauseseq[kbd->pauseseq]) {
+ kbd->pauseseq++;
+ if (kbd->pauseseq == G_N_ELEMENTS(pauseseq)) {
+ qemu_input_event_send_key_qcode(NULL, Q_KEY_CODE_PAUSE, true);
+ kbd->pauseseq = 0;
+ }
+ return;
+ } else {
+ kbd->pauseseq = 0;
+ }
+
+ keycode = scancode & ~SCANCODE_UP;
+ up = scancode & SCANCODE_UP;
+ if (kbd->emul0) {
+ kbd->emul0 = false;
+ keycode |= SCANCODE_GREY;
+ }
+
+ qemu_input_event_send_key_number(NULL, keycode, !up);
+}
+
+static uint8_t kbd_get_leds(SpiceKbdInstance *sin)
+{
+ QemuSpiceKbd *kbd = container_of(sin, QemuSpiceKbd, sin);
+ return kbd->ledstate;
+}
+
+static void kbd_leds(void *opaque, int ledstate)
+{
+ QemuSpiceKbd *kbd = opaque;
+
+ kbd->ledstate = 0;
+ if (ledstate & QEMU_SCROLL_LOCK_LED) {
+ kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK;
+ }
+ if (ledstate & QEMU_NUM_LOCK_LED) {
+ kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK;
+ }
+ if (ledstate & QEMU_CAPS_LOCK_LED) {
+ kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK;
+ }
+ spice_server_kbd_leds(&kbd->sin, kbd->ledstate);
+}
+
+/* mouse bits */
+
+typedef struct QemuSpicePointer {
+ SpiceMouseInstance mouse;
+ SpiceTabletInstance tablet;
+ int width, height;
+ uint32_t last_bmask;
+ Notifier mouse_mode;
+ bool absolute;
+} QemuSpicePointer;
+
+static void spice_update_buttons(QemuSpicePointer *pointer,
+ int wheel, uint32_t button_mask)
+{
+ static uint32_t bmap[INPUT_BUTTON__MAX] = {
+ [INPUT_BUTTON_LEFT] = 0x01,
+ [INPUT_BUTTON_MIDDLE] = 0x04,
+ [INPUT_BUTTON_RIGHT] = 0x02,
+ [INPUT_BUTTON_WHEEL_UP] = 0x10,
+ [INPUT_BUTTON_WHEEL_DOWN] = 0x20,
+ [INPUT_BUTTON_SIDE] = 0x40,
+ [INPUT_BUTTON_EXTRA] = 0x80,
+ };
+
+ if (wheel < 0) {
+ button_mask |= 0x10;
+ }
+ if (wheel > 0) {
+ button_mask |= 0x20;
+ }
+
+ if (pointer->last_bmask == button_mask) {
+ return;
+ }
+ qemu_input_update_buttons(NULL, bmap, pointer->last_bmask, button_mask);
+ pointer->last_bmask = button_mask;
+}
+
+static void mouse_motion(SpiceMouseInstance *sin, int dx, int dy, int dz,
+ uint32_t buttons_state)
+{
+ QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, mouse);
+ spice_update_buttons(pointer, dz, buttons_state);
+ qemu_input_queue_rel(NULL, INPUT_AXIS_X, dx);
+ qemu_input_queue_rel(NULL, INPUT_AXIS_Y, dy);
+ qemu_input_event_sync();
+}
+
+static void mouse_buttons(SpiceMouseInstance *sin, uint32_t buttons_state)
+{
+ QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, mouse);
+ spice_update_buttons(pointer, 0, buttons_state);
+ qemu_input_event_sync();
+}
+
+static const SpiceMouseInterface mouse_interface = {
+ .base.type = SPICE_INTERFACE_MOUSE,
+ .base.description = "mouse",
+ .base.major_version = SPICE_INTERFACE_MOUSE_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_MOUSE_MINOR,
+ .motion = mouse_motion,
+ .buttons = mouse_buttons,
+};
+
+static void tablet_set_logical_size(SpiceTabletInstance* sin, int width, int height)
+{
+ QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+ if (height < 16) {
+ height = 16;
+ }
+ if (width < 16) {
+ width = 16;
+ }
+ pointer->width = width;
+ pointer->height = height;
+}
+
+static void tablet_position(SpiceTabletInstance* sin, int x, int y,
+ uint32_t buttons_state)
+{
+ QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+ spice_update_buttons(pointer, 0, buttons_state);
+ qemu_input_queue_abs(NULL, INPUT_AXIS_X, x, 0, pointer->width);
+ qemu_input_queue_abs(NULL, INPUT_AXIS_Y, y, 0, pointer->height);
+ qemu_input_event_sync();
+}
+
+
+static void tablet_wheel(SpiceTabletInstance* sin, int wheel,
+ uint32_t buttons_state)
+{
+ QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+ spice_update_buttons(pointer, wheel, buttons_state);
+ qemu_input_event_sync();
+}
+
+static void tablet_buttons(SpiceTabletInstance *sin,
+ uint32_t buttons_state)
+{
+ QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
+
+ spice_update_buttons(pointer, 0, buttons_state);
+ qemu_input_event_sync();
+}
+
+static const SpiceTabletInterface tablet_interface = {
+ .base.type = SPICE_INTERFACE_TABLET,
+ .base.description = "tablet",
+ .base.major_version = SPICE_INTERFACE_TABLET_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_TABLET_MINOR,
+ .set_logical_size = tablet_set_logical_size,
+ .position = tablet_position,
+ .wheel = tablet_wheel,
+ .buttons = tablet_buttons,
+};
+
+static void mouse_mode_notifier(Notifier *notifier, void *data)
+{
+ QemuSpicePointer *pointer = container_of(notifier, QemuSpicePointer, mouse_mode);
+ bool is_absolute = qemu_input_is_absolute();
+
+ if (pointer->absolute == is_absolute) {
+ return;
+ }
+
+ if (is_absolute) {
+ qemu_spice.add_interface(&pointer->tablet.base);
+ } else {
+ spice_server_remove_interface(&pointer->tablet.base);
+ }
+ pointer->absolute = is_absolute;
+}
+
+void qemu_spice_input_init(void)
+{
+ QemuSpiceKbd *kbd;
+ QemuSpicePointer *pointer;
+
+ kbd = g_malloc0(sizeof(*kbd));
+ kbd->sin.base.sif = &kbd_interface.base;
+ qemu_spice.add_interface(&kbd->sin.base);
+ qemu_add_led_event_handler(kbd_leds, kbd);
+
+ pointer = g_malloc0(sizeof(*pointer));
+ pointer->mouse.base.sif = &mouse_interface.base;
+ pointer->tablet.base.sif = &tablet_interface.base;
+ qemu_spice.add_interface(&pointer->mouse.base);
+
+ pointer->absolute = false;
+ pointer->mouse_mode.notify = mouse_mode_notifier;
+ qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode);
+ mouse_mode_notifier(&pointer->mouse_mode, NULL);
+}
diff --git a/ui/spice-module.c b/ui/spice-module.c
new file mode 100644
index 000000000..322233587
--- /dev/null
+++ b/ui/spice-module.c
@@ -0,0 +1,85 @@
+/*
+ * spice module support, also spice stubs.
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "qapi/qapi-types-ui.h"
+#include "qapi/qapi-commands-ui.h"
+#include "ui/qemu-spice-module.h"
+
+int using_spice;
+
+static void qemu_spice_init_stub(void)
+{
+}
+
+static void qemu_spice_display_init_stub(void)
+{
+ /* This must never be called if CONFIG_SPICE is disabled */
+ error_report("spice support is disabled");
+ abort();
+}
+
+static int qemu_spice_migrate_info_stub(const char *h, int p, int t,
+ const char *s)
+{
+ return -1;
+}
+
+static int qemu_spice_set_passwd_stub(const char *passwd,
+ bool fail_if_connected,
+ bool disconnect_if_connected)
+{
+ return -1;
+}
+
+static int qemu_spice_set_pw_expire_stub(time_t expires)
+{
+ return -1;
+}
+
+static int qemu_spice_display_add_client_stub(int csock, int skipauth,
+ int tls)
+{
+ return -1;
+}
+
+struct QemuSpiceOps qemu_spice = {
+ .init = qemu_spice_init_stub,
+ .display_init = qemu_spice_display_init_stub,
+ .migrate_info = qemu_spice_migrate_info_stub,
+ .set_passwd = qemu_spice_set_passwd_stub,
+ .set_pw_expire = qemu_spice_set_pw_expire_stub,
+ .display_add_client = qemu_spice_display_add_client_stub,
+};
+
+#ifdef CONFIG_SPICE
+
+SpiceInfo *qmp_query_spice(Error **errp)
+{
+ if (!qemu_spice.qmp_query) {
+ SpiceInfo *info = g_new0(SpiceInfo, 1);
+ info->enabled = false;
+ return info;
+ }
+ return qemu_spice.qmp_query(errp);
+}
+
+#endif
diff --git a/ui/trace-events b/ui/trace-events
new file mode 100644
index 000000000..b9c0dd0fa
--- /dev/null
+++ b/ui/trace-events
@@ -0,0 +1,137 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# console.c
+console_gfx_new(void) ""
+console_gfx_reuse(int index) "%d"
+console_gfx_close(int index) "%d"
+console_putchar_csi(int esc_param0, int esc_param1, int ch, int nb_esc_params) "escape sequence CSI%d;%d%c, %d parameters"
+console_putchar_unhandled(int ch) "unhandled escape character '%c'"
+console_txt_new(int w, int h) "%dx%d"
+console_select(int nr) "%d"
+console_refresh(int interval) "interval %d ms"
+displaysurface_create(void *display_surface, int w, int h) "surface=%p, %dx%d"
+displaysurface_create_from(void *display_surface, int w, int h, uint32_t format) "surface=%p, %dx%d, format 0x%x"
+displaysurface_create_pixman(void *display_surface) "surface=%p"
+displaysurface_free(void *display_surface) "surface=%p"
+displaychangelistener_register(void *dcl, const char *name) "%p [ %s ]"
+displaychangelistener_unregister(void *dcl, const char *name) "%p [ %s ]"
+ppm_save(int fd, void *image) "fd=%d image=%p"
+
+# gtk-egl.c
+# gtk-gl-area.c
+# gtk.c
+gd_switch(const char *tab, int width, int height) "tab=%s, width=%d, height=%d"
+gd_update(const char *tab, int x, int y, int w, int h) "tab=%s, x=%d, y=%d, w=%d, h=%d"
+gd_key_event(const char *tab, int gdk_keycode, int qkeycode, const char *action) "tab=%s, translated GDK keycode %d to QKeyCode %d (%s)"
+gd_grab(const char *tab, const char *device, const char *reason) "tab=%s, dev=%s, reason=%s"
+gd_ungrab(const char *tab, const char *device) "tab=%s, dev=%s"
+gd_keymap_windowing(const char *name) "backend=%s"
+
+# vnc-auth-sasl.c
+# vnc-auth-vencrypt.c
+# vnc-ws.c
+# vnc.c
+vnc_key_guest_leds(bool caps, bool num, bool scroll) "caps %d, num %d, scroll %d"
+vnc_key_map_init(const char *layout) "%s"
+vnc_key_event_ext(bool down, int sym, int keycode, const char *name) "down %d, sym 0x%x, keycode 0x%x [%s]"
+vnc_key_event_map(bool down, int sym, int keycode, const char *name) "down %d, sym 0x%x -> keycode 0x%x [%s]"
+vnc_key_sync_numlock(bool on) "%d"
+vnc_key_sync_capslock(bool on) "%d"
+vnc_msg_server_audio_begin(void *state, void *ioc) "VNC server msg audio begin state=%p ioc=%p"
+vnc_msg_server_audio_end(void *state, void *ioc) "VNC server msg audio end state=%p ioc=%p"
+vnc_msg_server_audio_data(void *state, void *ioc, const void *buf, size_t len) "VNC server msg audio data state=%p ioc=%p buf=%p len=%zd"
+vnc_msg_server_desktop_resize(void *state, void *ioc, int width, int height) "VNC server msg ext resize state=%p ioc=%p size=%dx%d"
+vnc_msg_server_ext_desktop_resize(void *state, void *ioc, int width, int height, int reason) "VNC server msg ext resize state=%p ioc=%p size=%dx%d reason=%d"
+vnc_msg_client_audio_enable(void *state, void *ioc) "VNC client msg audio enable state=%p ioc=%p"
+vnc_msg_client_audio_disable(void *state, void *ioc) "VNC client msg audio disable state=%p ioc=%p"
+vnc_msg_client_audio_format(void *state, void *ioc, int fmt, int channels, int freq) "VNC client msg audio format state=%p ioc=%p fmt=%d channels=%d freq=%d"
+vnc_msg_client_set_desktop_size(void *state, void *ioc, int width, int height, int screens) "VNC client msg set desktop size state=%p ioc=%p size=%dx%d screens=%d"
+vnc_client_eof(void *state, void *ioc) "VNC client EOF state=%p ioc=%p"
+vnc_client_io_error(void *state, void *ioc, const char *msg) "VNC client I/O error state=%p ioc=%p errmsg=%s"
+vnc_client_connect(void *state, void *ioc) "VNC client connect state=%p ioc=%p"
+vnc_client_disconnect_start(void *state, void *ioc) "VNC client disconnect start state=%p ioc=%p"
+vnc_client_disconnect_finish(void *state, void *ioc) "VNC client disconnect finish state=%p ioc=%p"
+vnc_client_io_wrap(void *state, void *ioc, const char *type) "VNC client I/O wrap state=%p ioc=%p type=%s"
+vnc_client_throttle_threshold(void *state, void *ioc, size_t oldoffset, size_t offset, int client_width, int client_height, int bytes_per_pixel, void *audio_cap) "VNC client throttle threshold state=%p ioc=%p oldoffset=%zu newoffset=%zu width=%d height=%d bpp=%d audio=%p"
+vnc_client_throttle_incremental(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle incremental state=%p ioc=%p job-update=%d offset=%zu"
+vnc_client_throttle_forced(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle forced state=%p ioc=%p job-update=%d offset=%zu"
+vnc_client_throttle_audio(void *state, void *ioc, size_t offset) "VNC client throttle audio state=%p ioc=%p offset=%zu"
+vnc_client_unthrottle_forced(void *state, void *ioc) "VNC client unthrottle forced offset state=%p ioc=%p"
+vnc_client_unthrottle_incremental(void *state, void *ioc, size_t offset) "VNC client unthrottle incremental state=%p ioc=%p offset=%zu"
+vnc_client_output_limit(void *state, void *ioc, size_t offset, size_t threshold) "VNC client output limit state=%p ioc=%p offset=%zu threshold=%zu"
+vnc_server_dpy_pageflip(void *dpy, int w, int h, int fmt) "VNC server dpy pageflip dpy=%p size=%dx%d fmt=%d"
+vnc_server_dpy_recreate(void *dpy, int w, int h, int fmt) "VNC server dpy recreate dpy=%p size=%dx%d fmt=%d"
+vnc_job_add_rect(void *state, void *job, int x, int y, int w, int h) "VNC add rect state=%p job=%p offset=%d,%d size=%dx%d"
+vnc_job_discard_rect(void *state, void *job, int x, int y, int w, int h) "VNC job discard rect state=%p job=%p offset=%d,%d size=%dx%d"
+vnc_job_clamp_rect(void *state, void *job, int x, int y, int w, int h) "VNC job clamp rect state=%p job=%p offset=%d,%d size=%dx%d"
+vnc_job_clamped_rect(void *state, void *job, int x, int y, int w, int h) "VNC job clamp rect state=%p job=%p offset=%d,%d size=%dx%d"
+vnc_job_nrects(void *state, void *job, int nrects) "VNC job state=%p job=%p nrects=%d"
+vnc_auth_init(void *display, int websock, int auth, int subauth) "VNC auth init state=%p websock=%d auth=%d subauth=%d"
+vnc_auth_start(void *state, int method) "VNC client auth start state=%p method=%d"
+vnc_auth_pass(void *state, int method) "VNC client auth passed state=%p method=%d"
+vnc_auth_fail(void *state, int method, const char *message, const char *reason) "VNC client auth failed state=%p method=%d message=%s reason=%s"
+vnc_auth_reject(void *state, int expect, int got) "VNC client auth rejected state=%p method expected=%d got=%d"
+vnc_auth_vencrypt_version(void *state, int major, int minor) "VNC client auth vencrypt version state=%p major=%d minor=%d"
+vnc_auth_vencrypt_subauth(void *state, int auth) "VNC client auth vencrypt subauth state=%p auth=%d"
+vnc_auth_sasl_mech_list(void *state, const char *mechs) "VNC client auth SASL state=%p mechlist=%s"
+vnc_auth_sasl_mech_choose(void *state, const char *mech) "VNC client auth SASL state=%p mech=%s"
+vnc_auth_sasl_start(void *state, const void *clientdata, size_t clientlen, const void *serverdata, size_t severlen, int ret) "VNC client auth SASL start state=%p clientdata=%p clientlen=%zu serverdata=%p serverlen=%zu ret=%d"
+vnc_auth_sasl_step(void *state, const void *clientdata, size_t clientlen, const void *serverdata, size_t severlen, int ret) "VNC client auth SASL step state=%p clientdata=%p clientlen=%zu serverdata=%p serverlen=%zu ret=%d"
+vnc_auth_sasl_ssf(void *state, int ssf) "VNC client auth SASL SSF state=%p size=%d"
+vnc_auth_sasl_username(void *state, const char *name) "VNC client auth SASL user state=%p name=%s"
+vnc_auth_sasl_acl(void *state, int allow) "VNC client auth SASL ACL state=%p allow=%d"
+
+
+# input.c
+input_event_key_number(int conidx, int number, const char *qcode, bool down) "con %d, key number 0x%x [%s], down %d"
+input_event_key_qcode(int conidx, const char *qcode, bool down) "con %d, key qcode %s, down %d"
+input_event_btn(int conidx, const char *btn, bool down) "con %d, button %s, down %d"
+input_event_rel(int conidx, const char *axis, int value) "con %d, axis %s, value %d"
+input_event_abs(int conidx, const char *axis, int value) "con %d, axis %s, value 0x%x"
+input_event_sync(void) ""
+input_mouse_mode(int absolute) "absolute %d"
+
+# sdl2-input.c
+sdl2_process_key(int sdl_scancode, int qcode, const char *action) "translated SDL scancode %d to QKeyCode %d (%s)"
+
+# spice-display.c
+qemu_spice_add_memslot(int qid, uint32_t slot_id, unsigned long virt_start, unsigned long virt_end, int async) "%d %u: host virt 0x%lx - 0x%lx async=%d"
+qemu_spice_del_memslot(int qid, uint32_t gid, uint32_t slot_id) "%d gid=%u sid=%u"
+qemu_spice_create_primary_surface(int qid, uint32_t sid, void *surface, int async) "%d sid=%u surface=%p async=%d"
+qemu_spice_destroy_primary_surface(int qid, uint32_t sid, int async) "%d sid=%u async=%d"
+qemu_spice_wakeup(uint32_t qid) "%d"
+qemu_spice_create_update(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom) "lr %d -> %d, tb -> %d -> %d"
+qemu_spice_display_update(int qid, uint32_t x, uint32_t y, uint32_t w, uint32_t h) "%d +%d+%d %dx%d"
+qemu_spice_display_surface(int qid, uint32_t w, uint32_t h, int fast) "%d %dx%d, fast %d"
+qemu_spice_display_refresh(int qid, int notify) "%d notify %d"
+qemu_spice_ui_info(int qid, uint32_t width, uint32_t height) "%d %dx%d"
+
+qemu_spice_gl_surface(int qid, uint32_t w, uint32_t h, uint32_t fourcc) "%d %dx%d, fourcc 0x%x"
+qemu_spice_gl_scanout_disable(int qid) "%d"
+qemu_spice_gl_scanout_texture(int qid, uint32_t w, uint32_t h, uint32_t fourcc) "%d %dx%d, fourcc 0x%x"
+qemu_spice_gl_cursor(int qid, bool enabled, bool hotspot) "%d enabled %d, hotspot %d"
+qemu_spice_gl_forward_dmabuf(int qid, uint32_t width, uint32_t height) "%d %dx%d"
+qemu_spice_gl_render_dmabuf(int qid, uint32_t width, uint32_t height) "%d %dx%d"
+qemu_spice_gl_update(int qid, uint32_t x, uint32_t y, uint32_t w, uint32_t h) "%d +%d+%d %dx%d"
+
+# keymaps.c
+keymap_parse(const char *file) "file %s"
+keymap_add(int sym, int code, const char *line) "sym=0x%04x code=0x%04x (line: %s)"
+keymap_unmapped(int sym) "sym=0x%04x"
+
+# x_keymap.c
+xkeymap_extension(const char *name) "extension '%s'"
+xkeymap_vendor(const char *name) "vendor '%s'"
+xkeymap_keycodes(const char *name) "keycodes '%s'"
+xkeymap_keymap(const char *name) "keymap '%s'"
+
+# vdagent.c
+vdagent_open(void) ""
+vdagent_close(void) ""
+vdagent_send(const char *name) "msg %s"
+vdagent_send_empty_clipboard(void) ""
+vdagent_recv_chunk(uint32_t size) "size %d"
+vdagent_recv_msg(const char *name, uint32_t size) "msg %s, size %d"
+vdagent_peer_cap(const char *name) "cap %s"
+vdagent_cb_grab_selection(const char *name) "selection %s"
+vdagent_cb_grab_type(const char *name) "type %s"
diff --git a/ui/trace.h b/ui/trace.h
new file mode 100644
index 000000000..a89d76962
--- /dev/null
+++ b/ui/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-ui.h"
diff --git a/ui/udmabuf.c b/ui/udmabuf.c
new file mode 100644
index 000000000..cebceb261
--- /dev/null
+++ b/ui/udmabuf.c
@@ -0,0 +1,29 @@
+/*
+ * udmabuf helper functions.
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "ui/console.h"
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+int udmabuf_fd(void)
+{
+ static bool first = true;
+ static int udmabuf;
+
+ if (!first) {
+ return udmabuf;
+ }
+ first = false;
+
+ udmabuf = open("/dev/udmabuf", O_RDWR);
+ if (udmabuf < 0) {
+ warn_report("open /dev/udmabuf: %s", strerror(errno));
+ }
+ return udmabuf;
+}
diff --git a/ui/vdagent.c b/ui/vdagent.c
new file mode 100644
index 000000000..19e8fbfc9
--- /dev/null
+++ b/ui/vdagent.c
@@ -0,0 +1,871 @@
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "include/qemu-common.h"
+#include "chardev/char.h"
+#include "qemu/buffer.h"
+#include "qemu/option.h"
+#include "qemu/units.h"
+#include "hw/qdev-core.h"
+#include "migration/blocker.h"
+#include "ui/clipboard.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "trace.h"
+
+#include "qapi/qapi-types-char.h"
+#include "qapi/qapi-types-ui.h"
+
+#include "spice/vd_agent.h"
+
+#define VDAGENT_BUFFER_LIMIT (1 * MiB)
+#define VDAGENT_MOUSE_DEFAULT true
+#define VDAGENT_CLIPBOARD_DEFAULT false
+
+struct VDAgentChardev {
+ Chardev parent;
+
+ /* TODO: migration isn't yet supported */
+ Error *migration_blocker;
+
+ /* config */
+ bool mouse;
+ bool clipboard;
+
+ /* guest vdagent */
+ uint32_t caps;
+ VDIChunkHeader chunk;
+ uint32_t chunksize;
+ uint8_t *msgbuf;
+ uint32_t msgsize;
+ uint8_t *xbuf;
+ uint32_t xoff, xsize;
+ Buffer outbuf;
+
+ /* mouse */
+ DeviceState mouse_dev;
+ uint32_t mouse_x;
+ uint32_t mouse_y;
+ uint32_t mouse_btn;
+ uint32_t mouse_display;
+ QemuInputHandlerState *mouse_hs;
+
+ /* clipboard */
+ QemuClipboardPeer cbpeer;
+ uint32_t cbpending[QEMU_CLIPBOARD_SELECTION__COUNT];
+};
+typedef struct VDAgentChardev VDAgentChardev;
+
+#define TYPE_CHARDEV_QEMU_VDAGENT "chardev-qemu-vdagent"
+
+DECLARE_INSTANCE_CHECKER(VDAgentChardev, QEMU_VDAGENT_CHARDEV,
+ TYPE_CHARDEV_QEMU_VDAGENT);
+
+/* ------------------------------------------------------------------ */
+/* names, for debug logging */
+
+static const char *cap_name[] = {
+ [VD_AGENT_CAP_MOUSE_STATE] = "mouse-state",
+ [VD_AGENT_CAP_MONITORS_CONFIG] = "monitors-config",
+ [VD_AGENT_CAP_REPLY] = "reply",
+ [VD_AGENT_CAP_CLIPBOARD] = "clipboard",
+ [VD_AGENT_CAP_DISPLAY_CONFIG] = "display-config",
+ [VD_AGENT_CAP_CLIPBOARD_BY_DEMAND] = "clipboard-by-demand",
+ [VD_AGENT_CAP_CLIPBOARD_SELECTION] = "clipboard-selection",
+ [VD_AGENT_CAP_SPARSE_MONITORS_CONFIG] = "sparse-monitors-config",
+ [VD_AGENT_CAP_GUEST_LINEEND_LF] = "guest-lineend-lf",
+ [VD_AGENT_CAP_GUEST_LINEEND_CRLF] = "guest-lineend-crlf",
+ [VD_AGENT_CAP_MAX_CLIPBOARD] = "max-clipboard",
+ [VD_AGENT_CAP_AUDIO_VOLUME_SYNC] = "audio-volume-sync",
+ [VD_AGENT_CAP_MONITORS_CONFIG_POSITION] = "monitors-config-position",
+ [VD_AGENT_CAP_FILE_XFER_DISABLED] = "file-xfer-disabled",
+ [VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS] = "file-xfer-detailed-errors",
+#if 0
+ [VD_AGENT_CAP_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
+ [VD_AGENT_CAP_CLIPBOARD_NO_RELEASE_ON_REGRAB] = "clipboard-no-release-on-regrab",
+ [VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL] = "clipboard-grab-serial",
+#endif
+};
+
+static const char *msg_name[] = {
+ [VD_AGENT_MOUSE_STATE] = "mouse-state",
+ [VD_AGENT_MONITORS_CONFIG] = "monitors-config",
+ [VD_AGENT_REPLY] = "reply",
+ [VD_AGENT_CLIPBOARD] = "clipboard",
+ [VD_AGENT_DISPLAY_CONFIG] = "display-config",
+ [VD_AGENT_ANNOUNCE_CAPABILITIES] = "announce-capabilities",
+ [VD_AGENT_CLIPBOARD_GRAB] = "clipboard-grab",
+ [VD_AGENT_CLIPBOARD_REQUEST] = "clipboard-request",
+ [VD_AGENT_CLIPBOARD_RELEASE] = "clipboard-release",
+ [VD_AGENT_FILE_XFER_START] = "file-xfer-start",
+ [VD_AGENT_FILE_XFER_STATUS] = "file-xfer-status",
+ [VD_AGENT_FILE_XFER_DATA] = "file-xfer-data",
+ [VD_AGENT_CLIENT_DISCONNECTED] = "client-disconnected",
+ [VD_AGENT_MAX_CLIPBOARD] = "max-clipboard",
+ [VD_AGENT_AUDIO_VOLUME_SYNC] = "audio-volume-sync",
+#if 0
+ [VD_AGENT_GRAPHICS_DEVICE_INFO] = "graphics-device-info",
+#endif
+};
+
+static const char *sel_name[] = {
+ [VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD] = "clipboard",
+ [VD_AGENT_CLIPBOARD_SELECTION_PRIMARY] = "primary",
+ [VD_AGENT_CLIPBOARD_SELECTION_SECONDARY] = "secondary",
+};
+
+static const char *type_name[] = {
+ [VD_AGENT_CLIPBOARD_NONE] = "none",
+ [VD_AGENT_CLIPBOARD_UTF8_TEXT] = "text",
+ [VD_AGENT_CLIPBOARD_IMAGE_PNG] = "png",
+ [VD_AGENT_CLIPBOARD_IMAGE_BMP] = "bmp",
+ [VD_AGENT_CLIPBOARD_IMAGE_TIFF] = "tiff",
+ [VD_AGENT_CLIPBOARD_IMAGE_JPG] = "jpg",
+#if 0
+ [VD_AGENT_CLIPBOARD_FILE_LIST] = "files",
+#endif
+};
+
+#define GET_NAME(_m, _v) \
+ (((_v) < ARRAY_SIZE(_m) && (_m[_v])) ? (_m[_v]) : "???")
+
+/* ------------------------------------------------------------------ */
+/* send messages */
+
+static void vdagent_send_buf(VDAgentChardev *vd)
+{
+ uint32_t len;
+
+ while (!buffer_empty(&vd->outbuf)) {
+ len = qemu_chr_be_can_write(CHARDEV(vd));
+ if (len == 0) {
+ return;
+ }
+ if (len > vd->outbuf.offset) {
+ len = vd->outbuf.offset;
+ }
+ qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len);
+ buffer_advance(&vd->outbuf, len);
+ }
+}
+
+static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+ uint8_t *msgbuf = (void *)msg;
+ uint32_t msgsize = sizeof(VDAgentMessage) + msg->size;
+ uint32_t msgoff = 0;
+ VDIChunkHeader chunk;
+
+ trace_vdagent_send(GET_NAME(msg_name, msg->type));
+
+ msg->protocol = VD_AGENT_PROTOCOL;
+
+ if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) {
+ error_report("buffer full, dropping message");
+ return;
+ }
+
+ while (msgoff < msgsize) {
+ chunk.port = VDP_CLIENT_PORT;
+ chunk.size = msgsize - msgoff;
+ if (chunk.size > 1024) {
+ chunk.size = 1024;
+ }
+ buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size);
+ buffer_append(&vd->outbuf, &chunk, sizeof(chunk));
+ buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size);
+ msgoff += chunk.size;
+ }
+ vdagent_send_buf(vd);
+}
+
+static void vdagent_send_caps(VDAgentChardev *vd)
+{
+ g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+ sizeof(VDAgentAnnounceCapabilities) +
+ sizeof(uint32_t));
+ VDAgentAnnounceCapabilities *caps = (void *)msg->data;
+
+ msg->type = VD_AGENT_ANNOUNCE_CAPABILITIES;
+ msg->size = sizeof(VDAgentAnnounceCapabilities) + sizeof(uint32_t);
+ if (vd->mouse) {
+ caps->caps[0] |= (1 << VD_AGENT_CAP_MOUSE_STATE);
+ }
+ if (vd->clipboard) {
+ caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
+ caps->caps[0] |= (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+ }
+
+ vdagent_send_msg(vd, msg);
+}
+
+/* ------------------------------------------------------------------ */
+/* mouse events */
+
+static bool have_mouse(VDAgentChardev *vd)
+{
+ return vd->mouse &&
+ (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE));
+}
+
+static void vdagent_send_mouse(VDAgentChardev *vd)
+{
+ g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+ sizeof(VDAgentMouseState));
+ VDAgentMouseState *mouse = (void *)msg->data;
+
+ msg->type = VD_AGENT_MOUSE_STATE;
+ msg->size = sizeof(VDAgentMouseState);
+
+ mouse->x = vd->mouse_x;
+ mouse->y = vd->mouse_y;
+ mouse->buttons = vd->mouse_btn;
+ mouse->display_id = vd->mouse_display;
+
+ vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_pointer_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ static const int bmap[INPUT_BUTTON__MAX] = {
+ [INPUT_BUTTON_LEFT] = VD_AGENT_LBUTTON_MASK,
+ [INPUT_BUTTON_RIGHT] = VD_AGENT_RBUTTON_MASK,
+ [INPUT_BUTTON_MIDDLE] = VD_AGENT_MBUTTON_MASK,
+ [INPUT_BUTTON_WHEEL_UP] = VD_AGENT_UBUTTON_MASK,
+ [INPUT_BUTTON_WHEEL_DOWN] = VD_AGENT_DBUTTON_MASK,
+#ifdef VD_AGENT_EBUTTON_MASK
+ [INPUT_BUTTON_SIDE] = VD_AGENT_SBUTTON_MASK,
+ [INPUT_BUTTON_EXTRA] = VD_AGENT_EBUTTON_MASK,
+#endif
+ };
+
+ VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev);
+ InputMoveEvent *move;
+ InputBtnEvent *btn;
+ uint32_t xres, yres;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_ABS:
+ move = evt->u.abs.data;
+ xres = qemu_console_get_width(src, 1024);
+ yres = qemu_console_get_height(src, 768);
+ if (move->axis == INPUT_AXIS_X) {
+ vd->mouse_x = qemu_input_scale_axis(move->value,
+ INPUT_EVENT_ABS_MIN,
+ INPUT_EVENT_ABS_MAX,
+ 0, xres);
+ } else if (move->axis == INPUT_AXIS_Y) {
+ vd->mouse_y = qemu_input_scale_axis(move->value,
+ INPUT_EVENT_ABS_MIN,
+ INPUT_EVENT_ABS_MAX,
+ 0, yres);
+ }
+ vd->mouse_display = qemu_console_get_index(src);
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ if (btn->down) {
+ vd->mouse_btn |= bmap[btn->button];
+ } else {
+ vd->mouse_btn &= ~bmap[btn->button];
+ }
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void vdagent_pointer_sync(DeviceState *dev)
+{
+ VDAgentChardev *vd = container_of(dev, struct VDAgentChardev, mouse_dev);
+
+ if (vd->caps & (1 << VD_AGENT_CAP_MOUSE_STATE)) {
+ vdagent_send_mouse(vd);
+ }
+}
+
+static QemuInputHandler vdagent_mouse_handler = {
+ .name = "vdagent mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = vdagent_pointer_event,
+ .sync = vdagent_pointer_sync,
+};
+
+/* ------------------------------------------------------------------ */
+/* clipboard */
+
+static bool have_clipboard(VDAgentChardev *vd)
+{
+ return vd->clipboard &&
+ (vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_BY_DEMAND));
+}
+
+static bool have_selection(VDAgentChardev *vd)
+{
+ return vd->caps & (1 << VD_AGENT_CAP_CLIPBOARD_SELECTION);
+}
+
+static uint32_t type_qemu_to_vdagent(enum QemuClipboardType type)
+{
+ switch (type) {
+ case QEMU_CLIPBOARD_TYPE_TEXT:
+ return VD_AGENT_CLIPBOARD_UTF8_TEXT;
+ default:
+ return VD_AGENT_CLIPBOARD_NONE;
+ }
+}
+
+static void vdagent_send_clipboard_grab(VDAgentChardev *vd,
+ QemuClipboardInfo *info)
+{
+ g_autofree VDAgentMessage *msg =
+ g_malloc0(sizeof(VDAgentMessage) +
+ sizeof(uint32_t) * (QEMU_CLIPBOARD_TYPE__COUNT + 1));
+ uint8_t *s = msg->data;
+ uint32_t *data = (uint32_t *)msg->data;
+ uint32_t q, type;
+
+ if (have_selection(vd)) {
+ *s = info->selection;
+ data++;
+ msg->size += sizeof(uint32_t);
+ } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
+ return;
+ }
+
+ for (q = 0; q < QEMU_CLIPBOARD_TYPE__COUNT; q++) {
+ type = type_qemu_to_vdagent(q);
+ if (type != VD_AGENT_CLIPBOARD_NONE && info->types[q].available) {
+ *data = type;
+ data++;
+ msg->size += sizeof(uint32_t);
+ }
+ }
+
+ msg->type = VD_AGENT_CLIPBOARD_GRAB;
+ vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_send_clipboard_release(VDAgentChardev *vd,
+ QemuClipboardInfo *info)
+{
+ g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+ sizeof(uint32_t));
+
+ if (have_selection(vd)) {
+ uint8_t *s = msg->data;
+ *s = info->selection;
+ msg->size += sizeof(uint32_t);
+ } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
+ return;
+ }
+
+ msg->type = VD_AGENT_CLIPBOARD_RELEASE;
+ vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_send_clipboard_data(VDAgentChardev *vd,
+ QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+ sizeof(uint32_t) * 2 +
+ info->types[type].size);
+
+ uint8_t *s = msg->data;
+ uint32_t *data = (uint32_t *)msg->data;
+
+ if (have_selection(vd)) {
+ *s = info->selection;
+ data++;
+ msg->size += sizeof(uint32_t);
+ } else if (info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
+ return;
+ }
+
+ *data = type_qemu_to_vdagent(type);
+ data++;
+ msg->size += sizeof(uint32_t);
+
+ memcpy(data, info->types[type].data, info->types[type].size);
+ msg->size += info->types[type].size;
+
+ msg->type = VD_AGENT_CLIPBOARD;
+ vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_send_empty_clipboard_data(VDAgentChardev *vd,
+ QemuClipboardSelection selection,
+ QemuClipboardType type)
+{
+ g_autoptr(QemuClipboardInfo) info = qemu_clipboard_info_new(&vd->cbpeer, selection);
+
+ trace_vdagent_send_empty_clipboard();
+ vdagent_send_clipboard_data(vd, info, type);
+}
+
+static void vdagent_clipboard_notify(Notifier *notifier, void *data)
+{
+ VDAgentChardev *vd = container_of(notifier, VDAgentChardev, cbpeer.update);
+ QemuClipboardInfo *info = data;
+ QemuClipboardSelection s = info->selection;
+ QemuClipboardType type;
+ bool self_update = info->owner == &vd->cbpeer;
+
+ if (info != qemu_clipboard_info(s)) {
+ vd->cbpending[s] = 0;
+ if (!self_update) {
+ if (info->owner) {
+ vdagent_send_clipboard_grab(vd, info);
+ } else {
+ vdagent_send_clipboard_release(vd, info);
+ }
+ }
+ return;
+ }
+
+ if (self_update) {
+ return;
+ }
+
+ for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
+ if (vd->cbpending[s] & (1 << type)) {
+ vd->cbpending[s] &= ~(1 << type);
+ vdagent_send_clipboard_data(vd, info, type);
+ }
+ }
+}
+
+static void vdagent_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType qtype)
+{
+ VDAgentChardev *vd = container_of(info->owner, VDAgentChardev, cbpeer);
+ g_autofree VDAgentMessage *msg = g_malloc0(sizeof(VDAgentMessage) +
+ sizeof(uint32_t) * 2);
+ uint32_t type = type_qemu_to_vdagent(qtype);
+ uint8_t *s = msg->data;
+ uint32_t *data = (uint32_t *)msg->data;
+
+ if (type == VD_AGENT_CLIPBOARD_NONE) {
+ return;
+ }
+
+ if (have_selection(vd)) {
+ *s = info->selection;
+ data++;
+ msg->size += sizeof(uint32_t);
+ }
+
+ *data = type;
+ msg->size += sizeof(uint32_t);
+
+ msg->type = VD_AGENT_CLIPBOARD_REQUEST;
+ vdagent_send_msg(vd, msg);
+}
+
+static void vdagent_clipboard_recv_grab(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data)
+{
+ g_autoptr(QemuClipboardInfo) info = NULL;
+
+ trace_vdagent_cb_grab_selection(GET_NAME(sel_name, s));
+ info = qemu_clipboard_info_new(&vd->cbpeer, s);
+ if (size > sizeof(uint32_t) * 10) {
+ /*
+ * spice has 6 types as of 2021. Limiting to 10 entries
+ * so we we have some wiggle room.
+ */
+ return;
+ }
+ while (size >= sizeof(uint32_t)) {
+ trace_vdagent_cb_grab_type(GET_NAME(type_name, *(uint32_t *)data));
+ switch (*(uint32_t *)data) {
+ case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+ info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+ break;
+ default:
+ break;
+ }
+ data += sizeof(uint32_t);
+ size -= sizeof(uint32_t);
+ }
+ qemu_clipboard_update(info);
+}
+
+static void vdagent_clipboard_recv_request(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data)
+{
+ QemuClipboardType type;
+ QemuClipboardInfo *info;
+
+ if (size < sizeof(uint32_t)) {
+ return;
+ }
+ switch (*(uint32_t *)data) {
+ case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+ type = QEMU_CLIPBOARD_TYPE_TEXT;
+ break;
+ default:
+ return;
+ }
+
+ info = qemu_clipboard_info(s);
+ if (info && info->types[type].available && info->owner != &vd->cbpeer) {
+ if (info->types[type].data) {
+ vdagent_send_clipboard_data(vd, info, type);
+ } else {
+ vd->cbpending[s] |= (1 << type);
+ qemu_clipboard_request(info, type);
+ }
+ } else {
+ vdagent_send_empty_clipboard_data(vd, s, type);
+ }
+}
+
+static void vdagent_clipboard_recv_data(VDAgentChardev *vd, uint8_t s, uint32_t size, void *data)
+{
+ QemuClipboardType type;
+
+ if (size < sizeof(uint32_t)) {
+ return;
+ }
+ switch (*(uint32_t *)data) {
+ case VD_AGENT_CLIPBOARD_UTF8_TEXT:
+ type = QEMU_CLIPBOARD_TYPE_TEXT;
+ break;
+ default:
+ return;
+ }
+ data += 4;
+ size -= 4;
+
+ if (qemu_clipboard_peer_owns(&vd->cbpeer, s)) {
+ qemu_clipboard_set_data(&vd->cbpeer, qemu_clipboard_info(s),
+ type, size, data, true);
+ }
+}
+
+static void vdagent_clipboard_recv_release(VDAgentChardev *vd, uint8_t s)
+{
+ qemu_clipboard_peer_release(&vd->cbpeer, s);
+}
+
+static void vdagent_chr_recv_clipboard(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+ uint8_t s = VD_AGENT_CLIPBOARD_SELECTION_CLIPBOARD;
+ uint32_t size = msg->size;
+ void *data = msg->data;
+
+ if (have_selection(vd)) {
+ if (size < 4) {
+ return;
+ }
+ s = *(uint8_t *)data;
+ if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
+ return;
+ }
+ data += 4;
+ size -= 4;
+ }
+
+ switch (msg->type) {
+ case VD_AGENT_CLIPBOARD_GRAB:
+ return vdagent_clipboard_recv_grab(vd, s, size, data);
+ case VD_AGENT_CLIPBOARD_REQUEST:
+ return vdagent_clipboard_recv_request(vd, s, size, data);
+ case VD_AGENT_CLIPBOARD: /* data */
+ return vdagent_clipboard_recv_data(vd, s, size, data);
+ case VD_AGENT_CLIPBOARD_RELEASE:
+ return vdagent_clipboard_recv_release(vd, s);
+ default:
+ g_assert_not_reached();
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* chardev backend */
+
+static void vdagent_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
+ ChardevQemuVDAgent *cfg = backend->u.qemu_vdagent.data;
+
+#if defined(HOST_WORDS_BIGENDIAN)
+ /*
+ * TODO: vdagent protocol is defined to be LE,
+ * so we have to byteswap everything on BE hosts.
+ */
+ error_setg(errp, "vdagent is not supported on bigendian hosts");
+ return;
+#endif
+
+ if (migrate_add_blocker(vd->migration_blocker, errp) != 0) {
+ return;
+ }
+
+ vd->mouse = VDAGENT_MOUSE_DEFAULT;
+ if (cfg->has_mouse) {
+ vd->mouse = cfg->mouse;
+ }
+
+ vd->clipboard = VDAGENT_CLIPBOARD_DEFAULT;
+ if (cfg->has_clipboard) {
+ vd->clipboard = cfg->clipboard;
+ }
+
+ if (vd->mouse) {
+ vd->mouse_hs = qemu_input_handler_register(&vd->mouse_dev,
+ &vdagent_mouse_handler);
+ }
+
+ *be_opened = true;
+}
+
+static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+ VDAgentAnnounceCapabilities *caps = (void *)msg->data;
+ int i;
+
+ if (msg->size < (sizeof(VDAgentAnnounceCapabilities) +
+ sizeof(uint32_t))) {
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(cap_name); i++) {
+ if (caps->caps[0] & (1 << i)) {
+ trace_vdagent_peer_cap(GET_NAME(cap_name, i));
+ }
+ }
+
+ vd->caps = caps->caps[0];
+ if (caps->request) {
+ vdagent_send_caps(vd);
+ }
+ if (have_mouse(vd) && vd->mouse_hs) {
+ qemu_input_handler_activate(vd->mouse_hs);
+ }
+ if (have_clipboard(vd) && vd->cbpeer.update.notify == NULL) {
+ vd->cbpeer.name = "vdagent";
+ vd->cbpeer.update.notify = vdagent_clipboard_notify;
+ vd->cbpeer.request = vdagent_clipboard_request;
+ qemu_clipboard_peer_register(&vd->cbpeer);
+ }
+}
+
+static void vdagent_chr_recv_msg(VDAgentChardev *vd, VDAgentMessage *msg)
+{
+ trace_vdagent_recv_msg(GET_NAME(msg_name, msg->type), msg->size);
+
+ switch (msg->type) {
+ case VD_AGENT_ANNOUNCE_CAPABILITIES:
+ vdagent_chr_recv_caps(vd, msg);
+ break;
+ case VD_AGENT_CLIPBOARD:
+ case VD_AGENT_CLIPBOARD_GRAB:
+ case VD_AGENT_CLIPBOARD_REQUEST:
+ case VD_AGENT_CLIPBOARD_RELEASE:
+ if (have_clipboard(vd)) {
+ vdagent_chr_recv_clipboard(vd, msg);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void vdagent_reset_xbuf(VDAgentChardev *vd)
+{
+ g_clear_pointer(&vd->xbuf, g_free);
+ vd->xoff = 0;
+ vd->xsize = 0;
+}
+
+static void vdagent_chr_recv_chunk(VDAgentChardev *vd)
+{
+ VDAgentMessage *msg = (void *)vd->msgbuf;
+
+ if (!vd->xsize) {
+ if (vd->msgsize < sizeof(*msg)) {
+ error_report("%s: message too small: %d < %zd", __func__,
+ vd->msgsize, sizeof(*msg));
+ return;
+ }
+ if (vd->msgsize == msg->size + sizeof(*msg)) {
+ vdagent_chr_recv_msg(vd, msg);
+ return;
+ }
+ }
+
+ if (!vd->xsize) {
+ vd->xsize = msg->size + sizeof(*msg);
+ vd->xbuf = g_malloc0(vd->xsize);
+ }
+
+ if (vd->xoff + vd->msgsize > vd->xsize) {
+ error_report("%s: Oops: %d+%d > %d", __func__,
+ vd->xoff, vd->msgsize, vd->xsize);
+ vdagent_reset_xbuf(vd);
+ return;
+ }
+
+ memcpy(vd->xbuf + vd->xoff, vd->msgbuf, vd->msgsize);
+ vd->xoff += vd->msgsize;
+ if (vd->xoff < vd->xsize) {
+ return;
+ }
+
+ msg = (void *)vd->xbuf;
+ vdagent_chr_recv_msg(vd, msg);
+ vdagent_reset_xbuf(vd);
+}
+
+static void vdagent_reset_bufs(VDAgentChardev *vd)
+{
+ memset(&vd->chunk, 0, sizeof(vd->chunk));
+ vd->chunksize = 0;
+ g_free(vd->msgbuf);
+ vd->msgbuf = NULL;
+ vd->msgsize = 0;
+}
+
+static int vdagent_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
+ uint32_t copy, ret = len;
+
+ while (len) {
+ if (vd->chunksize < sizeof(vd->chunk)) {
+ copy = sizeof(vd->chunk) - vd->chunksize;
+ if (copy > len) {
+ copy = len;
+ }
+ memcpy((void *)(&vd->chunk) + vd->chunksize, buf, copy);
+ vd->chunksize += copy;
+ buf += copy;
+ len -= copy;
+ if (vd->chunksize < sizeof(vd->chunk)) {
+ break;
+ }
+
+ assert(vd->msgbuf == NULL);
+ vd->msgbuf = g_malloc0(vd->chunk.size);
+ }
+
+ copy = vd->chunk.size - vd->msgsize;
+ if (copy > len) {
+ copy = len;
+ }
+ memcpy(vd->msgbuf + vd->msgsize, buf, copy);
+ vd->msgsize += copy;
+ buf += copy;
+ len -= copy;
+
+ if (vd->msgsize == vd->chunk.size) {
+ trace_vdagent_recv_chunk(vd->chunk.size);
+ vdagent_chr_recv_chunk(vd);
+ vdagent_reset_bufs(vd);
+ }
+ }
+
+ return ret;
+}
+
+static void vdagent_chr_accept_input(Chardev *chr)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
+
+ vdagent_send_buf(vd);
+}
+
+static void vdagent_disconnect(VDAgentChardev *vd)
+{
+ buffer_reset(&vd->outbuf);
+ vdagent_reset_bufs(vd);
+ vd->caps = 0;
+ if (vd->mouse_hs) {
+ qemu_input_handler_deactivate(vd->mouse_hs);
+ }
+ if (vd->cbpeer.update.notify) {
+ qemu_clipboard_peer_unregister(&vd->cbpeer);
+ memset(&vd->cbpeer, 0, sizeof(vd->cbpeer));
+ }
+}
+
+static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(chr);
+
+ if (!fe_open) {
+ trace_vdagent_close();
+ vdagent_disconnect(vd);
+ return;
+ }
+
+ trace_vdagent_open();
+}
+
+static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ ChardevQemuVDAgent *cfg;
+
+ backend->type = CHARDEV_BACKEND_KIND_QEMU_VDAGENT;
+ cfg = backend->u.qemu_vdagent.data = g_new0(ChardevQemuVDAgent, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevQemuVDAgent_base(cfg));
+ cfg->has_mouse = true;
+ cfg->mouse = qemu_opt_get_bool(opts, "mouse", VDAGENT_MOUSE_DEFAULT);
+ cfg->has_clipboard = true;
+ cfg->clipboard = qemu_opt_get_bool(opts, "clipboard", VDAGENT_CLIPBOARD_DEFAULT);
+}
+
+/* ------------------------------------------------------------------ */
+
+static void vdagent_chr_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = vdagent_chr_parse;
+ cc->open = vdagent_chr_open;
+ cc->chr_write = vdagent_chr_write;
+ cc->chr_set_fe_open = vdagent_chr_set_fe_open;
+ cc->chr_accept_input = vdagent_chr_accept_input;
+}
+
+static void vdagent_chr_init(Object *obj)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
+
+ buffer_init(&vd->outbuf, "vdagent-outbuf");
+ error_setg(&vd->migration_blocker,
+ "The vdagent chardev doesn't yet support migration");
+}
+
+static void vdagent_chr_fini(Object *obj)
+{
+ VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj);
+
+ migrate_del_blocker(vd->migration_blocker);
+ vdagent_disconnect(vd);
+ buffer_free(&vd->outbuf);
+ error_free(vd->migration_blocker);
+}
+
+static const TypeInfo vdagent_chr_type_info = {
+ .name = TYPE_CHARDEV_QEMU_VDAGENT,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(VDAgentChardev),
+ .instance_init = vdagent_chr_init,
+ .instance_finalize = vdagent_chr_fini,
+ .class_init = vdagent_chr_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&vdagent_chr_type_info);
+}
+
+type_init(register_types);
diff --git a/ui/vgafont.h b/ui/vgafont.h
new file mode 100644
index 000000000..3606dd733
--- /dev/null
+++ b/ui/vgafont.h
@@ -0,0 +1,4611 @@
+static const uint8_t vgafont16[256 * 16] = {
+
+ /* 0 0x00 '^@' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 1 0x01 '^A' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x81, /* 10000001 */
+ 0xa5, /* 10100101 */
+ 0x81, /* 10000001 */
+ 0x81, /* 10000001 */
+ 0xbd, /* 10111101 */
+ 0x99, /* 10011001 */
+ 0x81, /* 10000001 */
+ 0x81, /* 10000001 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 2 0x02 '^B' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0xff, /* 11111111 */
+ 0xdb, /* 11011011 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xc3, /* 11000011 */
+ 0xe7, /* 11100111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 3 0x03 '^C' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 4 0x04 '^D' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x7c, /* 01111100 */
+ 0xfe, /* 11111110 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 5 0x05 '^E' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0xe7, /* 11100111 */
+ 0xe7, /* 11100111 */
+ 0xe7, /* 11100111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 6 0x06 '^F' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 7 0x07 '^G' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 8 0x08 '^H' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xe7, /* 11100111 */
+ 0xc3, /* 11000011 */
+ 0xc3, /* 11000011 */
+ 0xe7, /* 11100111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 9 0x09 '^I' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x42, /* 01000010 */
+ 0x42, /* 01000010 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 10 0x0a '^J' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xc3, /* 11000011 */
+ 0x99, /* 10011001 */
+ 0xbd, /* 10111101 */
+ 0xbd, /* 10111101 */
+ 0x99, /* 10011001 */
+ 0xc3, /* 11000011 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 11 0x0b '^K' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1e, /* 00011110 */
+ 0x0e, /* 00001110 */
+ 0x1a, /* 00011010 */
+ 0x32, /* 00110010 */
+ 0x78, /* 01111000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 12 0x0c '^L' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 13 0x0d '^M' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3f, /* 00111111 */
+ 0x33, /* 00110011 */
+ 0x3f, /* 00111111 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x70, /* 01110000 */
+ 0xf0, /* 11110000 */
+ 0xe0, /* 11100000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 14 0x0e '^N' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7f, /* 01111111 */
+ 0x63, /* 01100011 */
+ 0x7f, /* 01111111 */
+ 0x63, /* 01100011 */
+ 0x63, /* 01100011 */
+ 0x63, /* 01100011 */
+ 0x63, /* 01100011 */
+ 0x67, /* 01100111 */
+ 0xe7, /* 11100111 */
+ 0xe6, /* 11100110 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 15 0x0f '^O' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xdb, /* 11011011 */
+ 0x3c, /* 00111100 */
+ 0xe7, /* 11100111 */
+ 0x3c, /* 00111100 */
+ 0xdb, /* 11011011 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 16 0x10 '^P' */
+ 0x00, /* 00000000 */
+ 0x80, /* 10000000 */
+ 0xc0, /* 11000000 */
+ 0xe0, /* 11100000 */
+ 0xf0, /* 11110000 */
+ 0xf8, /* 11111000 */
+ 0xfe, /* 11111110 */
+ 0xf8, /* 11111000 */
+ 0xf0, /* 11110000 */
+ 0xe0, /* 11100000 */
+ 0xc0, /* 11000000 */
+ 0x80, /* 10000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 17 0x11 '^Q' */
+ 0x00, /* 00000000 */
+ 0x02, /* 00000010 */
+ 0x06, /* 00000110 */
+ 0x0e, /* 00001110 */
+ 0x1e, /* 00011110 */
+ 0x3e, /* 00111110 */
+ 0xfe, /* 11111110 */
+ 0x3e, /* 00111110 */
+ 0x1e, /* 00011110 */
+ 0x0e, /* 00001110 */
+ 0x06, /* 00000110 */
+ 0x02, /* 00000010 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 18 0x12 '^R' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 19 0x13 '^S' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 20 0x14 '^T' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7f, /* 01111111 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0x7b, /* 01111011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 21 0x15 '^U' */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x0c, /* 00001100 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 22 0x16 '^V' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 23 0x17 '^W' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 24 0x18 '^X' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 25 0x19 '^Y' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 26 0x1a '^Z' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0xfe, /* 11111110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 27 0x1b '^[' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xfe, /* 11111110 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 28 0x1c '^\' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 29 0x1d '^]' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x28, /* 00101000 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x28, /* 00101000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 30 0x1e '^^' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x7c, /* 01111100 */
+ 0x7c, /* 01111100 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 31 0x1f '^_' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0x7c, /* 01111100 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 32 0x20 ' ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 33 0x21 '!' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 34 0x22 '"' */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x24, /* 00100100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 35 0x23 '#' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 36 0x24 '$' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0x7c, /* 01111100 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x86, /* 10000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 37 0x25 '%' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc2, /* 11000010 */
+ 0xc6, /* 11000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc6, /* 11000110 */
+ 0x86, /* 10000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 38 0x26 '&' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 39 0x27 ''' */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 40 0x28 '(' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 41 0x29 ')' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 42 0x2a '*' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0xff, /* 11111111 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 43 0x2b '+' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 44 0x2c ',' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 45 0x2d '-' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 46 0x2e '.' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 47 0x2f '/' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x02, /* 00000010 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0x80, /* 10000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 48 0x30 '0' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 49 0x31 '1' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x38, /* 00111000 */
+ 0x78, /* 01111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 50 0x32 '2' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 51 0x33 '3' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x3c, /* 00111100 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 52 0x34 '4' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x1c, /* 00011100 */
+ 0x3c, /* 00111100 */
+ 0x6c, /* 01101100 */
+ 0xcc, /* 11001100 */
+ 0xfe, /* 11111110 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x1e, /* 00011110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 53 0x35 '5' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xfc, /* 11111100 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 54 0x36 '6' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xfc, /* 11111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 55 0x37 '7' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 56 0x38 '8' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 57 0x39 '9' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7e, /* 01111110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 58 0x3a ':' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 59 0x3b ';' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 60 0x3c '<' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 61 0x3d '=' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 62 0x3e '>' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 63 0x3f '?' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 64 0x40 '@' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xde, /* 11011110 */
+ 0xde, /* 11011110 */
+ 0xde, /* 11011110 */
+ 0xdc, /* 11011100 */
+ 0xc0, /* 11000000 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 65 0x41 'A' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 66 0x42 'B' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfc, /* 11111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xfc, /* 11111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 67 0x43 'C' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc2, /* 11000010 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 68 0x44 'D' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 69 0x45 'E' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x66, /* 01100110 */
+ 0x62, /* 01100010 */
+ 0x68, /* 01101000 */
+ 0x78, /* 01111000 */
+ 0x68, /* 01101000 */
+ 0x60, /* 01100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 70 0x46 'F' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x66, /* 01100110 */
+ 0x62, /* 01100010 */
+ 0x68, /* 01101000 */
+ 0x78, /* 01111000 */
+ 0x68, /* 01101000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 71 0x47 'G' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xde, /* 11011110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x66, /* 01100110 */
+ 0x3a, /* 00111010 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 72 0x48 'H' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 73 0x49 'I' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 74 0x4a 'J' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1e, /* 00011110 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 75 0x4b 'K' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe6, /* 11100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x78, /* 01111000 */
+ 0x78, /* 01111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 76 0x4c 'L' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf0, /* 11110000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 77 0x4d 'M' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xee, /* 11101110 */
+ 0xfe, /* 11111110 */
+ 0xfe, /* 11111110 */
+ 0xd6, /* 11010110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 78 0x4e 'N' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xe6, /* 11100110 */
+ 0xf6, /* 11110110 */
+ 0xfe, /* 11111110 */
+ 0xde, /* 11011110 */
+ 0xce, /* 11001110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 79 0x4f 'O' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 80 0x50 'P' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfc, /* 11111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 81 0x51 'Q' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xde, /* 11011110 */
+ 0x7c, /* 01111100 */
+ 0x0c, /* 00001100 */
+ 0x0e, /* 00001110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 82 0x52 'R' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfc, /* 11111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 83 0x53 'S' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x38, /* 00111000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 84 0x54 'T' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x5a, /* 01011010 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 85 0x55 'U' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 86 0x56 'V' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 87 0x57 'W' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xfe, /* 11111110 */
+ 0xee, /* 11101110 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 88 0x58 'X' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x7c, /* 01111100 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x7c, /* 01111100 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 89 0x59 'Y' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 90 0x5a 'Z' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0x86, /* 10000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc2, /* 11000010 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 91 0x5b '[' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 92 0x5c '\' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x80, /* 10000000 */
+ 0xc0, /* 11000000 */
+ 0xe0, /* 11100000 */
+ 0x70, /* 01110000 */
+ 0x38, /* 00111000 */
+ 0x1c, /* 00011100 */
+ 0x0e, /* 00001110 */
+ 0x06, /* 00000110 */
+ 0x02, /* 00000010 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 93 0x5d ']' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 94 0x5e '^' */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 95 0x5f '_' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 96 0x60 '`' */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 97 0x61 'a' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 98 0x62 'b' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe0, /* 11100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x78, /* 01111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 99 0x63 'c' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 100 0x64 'd' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1c, /* 00011100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x3c, /* 00111100 */
+ 0x6c, /* 01101100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 101 0x65 'e' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 102 0x66 'f' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1c, /* 00011100 */
+ 0x36, /* 00110110 */
+ 0x32, /* 00110010 */
+ 0x30, /* 00110000 */
+ 0x78, /* 01111000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 103 0x67 'g' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x7c, /* 01111100 */
+ 0x0c, /* 00001100 */
+ 0xcc, /* 11001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+
+ /* 104 0x68 'h' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe0, /* 11100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x6c, /* 01101100 */
+ 0x76, /* 01110110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 105 0x69 'i' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 106 0x6a 'j' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+
+ /* 107 0x6b 'k' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xe0, /* 11100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x78, /* 01111000 */
+ 0x78, /* 01111000 */
+ 0x6c, /* 01101100 */
+ 0x66, /* 01100110 */
+ 0xe6, /* 11100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 108 0x6c 'l' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 109 0x6d 'm' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xec, /* 11101100 */
+ 0xfe, /* 11111110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 110 0x6e 'n' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 111 0x6f 'o' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 112 0x70 'p' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+
+ /* 113 0x71 'q' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x7c, /* 01111100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x1e, /* 00011110 */
+ 0x00, /* 00000000 */
+
+ /* 114 0x72 'r' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x76, /* 01110110 */
+ 0x66, /* 01100110 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 115 0x73 's' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x38, /* 00111000 */
+ 0x0c, /* 00001100 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 116 0x74 't' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0xfc, /* 11111100 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x36, /* 00110110 */
+ 0x1c, /* 00011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 117 0x75 'u' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 118 0x76 'v' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 119 0x77 'w' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xd6, /* 11010110 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 120 0x78 'x' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 121 0x79 'y' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7e, /* 01111110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+
+ /* 122 0x7a 'z' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xcc, /* 11001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 123 0x7b '{' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x0e, /* 00001110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 124 0x7c '|' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 125 0x7d '}' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x70, /* 01110000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x0e, /* 00001110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 126 0x7e '~' */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 127 0x7f '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 128 0x80 '€' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0xc2, /* 11000010 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc2, /* 11000010 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 129 0x81 '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 130 0x82 '‚' */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 131 0x83 'ƒ' */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 132 0x84 '„' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 133 0x85 '…' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 134 0x86 '†' */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 135 0x87 '‡' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x18, /* 00011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 136 0x88 'ˆ' */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 137 0x89 '‰' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 138 0x8a 'Š' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 139 0x8b '‹' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 140 0x8c 'Œ' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 141 0x8d '' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 142 0x8e 'Ž' */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 143 0x8f '' */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 144 0x90 '' */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x66, /* 01100110 */
+ 0x62, /* 01100010 */
+ 0x68, /* 01101000 */
+ 0x78, /* 01111000 */
+ 0x68, /* 01101000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 145 0x91 '‘' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xec, /* 11101100 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x7e, /* 01111110 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0x6e, /* 01101110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 146 0x92 '’' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3e, /* 00111110 */
+ 0x6c, /* 01101100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xfe, /* 11111110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xce, /* 11001110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 147 0x93 '“' */
+ 0x00, /* 00000000 */
+ 0x10, /* 00010000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 148 0x94 '”' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 149 0x95 '•' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 150 0x96 '–' */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x78, /* 01111000 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 151 0x97 '—' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 152 0x98 '˜' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7e, /* 01111110 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x78, /* 01111000 */
+ 0x00, /* 00000000 */
+
+ /* 153 0x99 '™' */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 154 0x9a 'š' */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 155 0x9b '›' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 156 0x9c 'œ' */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x64, /* 01100100 */
+ 0x60, /* 01100000 */
+ 0xf0, /* 11110000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xe6, /* 11100110 */
+ 0xfc, /* 11111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 157 0x9d '' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 158 0x9e 'ž' */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xf8, /* 11111000 */
+ 0xc4, /* 11000100 */
+ 0xcc, /* 11001100 */
+ 0xde, /* 11011110 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 159 0x9f 'Ÿ' */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x1b, /* 00011011 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xd8, /* 11011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 160 0xa0 ' ' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0x0c, /* 00001100 */
+ 0x7c, /* 01111100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 161 0xa1 '¡' */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 162 0xa2 '¢' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 163 0xa3 '£' */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x00, /* 00000000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 164 0xa4 '¤' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0xdc, /* 11011100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 165 0xa5 '¥' */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0xc6, /* 11000110 */
+ 0xe6, /* 11100110 */
+ 0xf6, /* 11110110 */
+ 0xfe, /* 11111110 */
+ 0xde, /* 11011110 */
+ 0xce, /* 11001110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 166 0xa6 '¦' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x3e, /* 00111110 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 167 0xa7 '§' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 168 0xa8 '¨' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x7c, /* 01111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 169 0xa9 '©' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 170 0xaa 'ª' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 171 0xab '«' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0xe0, /* 11100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xdc, /* 11011100 */
+ 0x86, /* 10000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x3e, /* 00111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 172 0xac '¬' */
+ 0x00, /* 00000000 */
+ 0x60, /* 01100000 */
+ 0xe0, /* 11100000 */
+ 0x62, /* 01100010 */
+ 0x66, /* 01100110 */
+ 0x6c, /* 01101100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x66, /* 01100110 */
+ 0xce, /* 11001110 */
+ 0x9a, /* 10011010 */
+ 0x3f, /* 00111111 */
+ 0x06, /* 00000110 */
+ 0x06, /* 00000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 173 0xad '­' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 174 0xae '®' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x36, /* 00110110 */
+ 0x6c, /* 01101100 */
+ 0xd8, /* 11011000 */
+ 0x6c, /* 01101100 */
+ 0x36, /* 00110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 175 0xaf '¯' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xd8, /* 11011000 */
+ 0x6c, /* 01101100 */
+ 0x36, /* 00110110 */
+ 0x6c, /* 01101100 */
+ 0xd8, /* 11011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 176 0xb0 '°' */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+ 0x11, /* 00010001 */
+ 0x44, /* 01000100 */
+
+ /* 177 0xb1 '±' */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+ 0x55, /* 01010101 */
+ 0xaa, /* 10101010 */
+
+ /* 178 0xb2 '²' */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+ 0xdd, /* 11011101 */
+ 0x77, /* 01110111 */
+
+ /* 179 0xb3 '³' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 180 0xb4 '´' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 181 0xb5 'µ' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 182 0xb6 '¶' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf6, /* 11110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 183 0xb7 '·' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 184 0xb8 '¸' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 185 0xb9 '¹' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf6, /* 11110110 */
+ 0x06, /* 00000110 */
+ 0xf6, /* 11110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 186 0xba 'º' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 187 0xbb '»' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x06, /* 00000110 */
+ 0xf6, /* 11110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 188 0xbc '¼' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf6, /* 11110110 */
+ 0x06, /* 00000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 189 0xbd '½' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 190 0xbe '¾' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 191 0xbf '¿' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xf8, /* 11111000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 192 0xc0 'À' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 193 0xc1 'Á' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 194 0xc2 'Â' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 195 0xc3 'Ã' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 196 0xc4 'Ä' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 197 0xc5 'Å' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 198 0xc6 'Æ' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 199 0xc7 'Ç' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x37, /* 00110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 200 0xc8 'È' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x37, /* 00110111 */
+ 0x30, /* 00110000 */
+ 0x3f, /* 00111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 201 0xc9 'É' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3f, /* 00111111 */
+ 0x30, /* 00110000 */
+ 0x37, /* 00110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 202 0xca 'Ê' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf7, /* 11110111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 203 0xcb 'Ë' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xf7, /* 11110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 204 0xcc 'Ì' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x37, /* 00110111 */
+ 0x30, /* 00110000 */
+ 0x37, /* 00110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 205 0xcd 'Í' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 206 0xce 'Î' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xf7, /* 11110111 */
+ 0x00, /* 00000000 */
+ 0xf7, /* 11110111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 207 0xcf 'Ï' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 208 0xd0 'Ð' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 209 0xd1 'Ñ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 210 0xd2 'Ò' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 211 0xd3 'Ó' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x3f, /* 00111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 212 0xd4 'Ô' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 213 0xd5 'Õ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 214 0xd6 'Ö' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x3f, /* 00111111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 215 0xd7 '×' */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0xff, /* 11111111 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+
+ /* 216 0xd8 'Ø' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0xff, /* 11111111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 217 0xd9 'Ù' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xf8, /* 11111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 218 0xda 'Ú' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1f, /* 00011111 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 219 0xdb 'Û' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 220 0xdc 'Ü' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+
+ /* 221 0xdd 'Ý' */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+ 0xf0, /* 11110000 */
+
+ /* 222 0xde 'Þ' */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+ 0x0f, /* 00001111 */
+
+ /* 223 0xdf 'ß' */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0xff, /* 11111111 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 224 0xe0 'à' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xdc, /* 11011100 */
+ 0x76, /* 01110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 225 0xe1 'á' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x78, /* 01111000 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xcc, /* 11001100 */
+ 0xd8, /* 11011000 */
+ 0xcc, /* 11001100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xcc, /* 11001100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 226 0xe2 'â' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 227 0xe3 'ã' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 228 0xe4 'ä' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 229 0xe5 'å' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 230 0xe6 'æ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+
+ /* 231 0xe7 'ç' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 232 0xe8 'è' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 233 0xe9 'é' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xfe, /* 11111110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 234 0xea 'ê' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0xee, /* 11101110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 235 0xeb 'ë' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1e, /* 00011110 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x3e, /* 00111110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x66, /* 01100110 */
+ 0x3c, /* 00111100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 236 0xec 'ì' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 237 0xed 'í' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x03, /* 00000011 */
+ 0x06, /* 00000110 */
+ 0x7e, /* 01111110 */
+ 0xdb, /* 11011011 */
+ 0xdb, /* 11011011 */
+ 0xf3, /* 11110011 */
+ 0x7e, /* 01111110 */
+ 0x60, /* 01100000 */
+ 0xc0, /* 11000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 238 0xee 'î' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x1c, /* 00011100 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x7c, /* 01111100 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x1c, /* 00011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 239 0xef 'ï' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7c, /* 01111100 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0xc6, /* 11000110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 240 0xf0 'ð' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0xfe, /* 11111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 241 0xf1 'ñ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x7e, /* 01111110 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 242 0xf2 'ò' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x06, /* 00000110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 243 0xf3 'ó' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x30, /* 00110000 */
+ 0x60, /* 01100000 */
+ 0x30, /* 00110000 */
+ 0x18, /* 00011000 */
+ 0x0c, /* 00001100 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 244 0xf4 'ô' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x0e, /* 00001110 */
+ 0x1b, /* 00011011 */
+ 0x1b, /* 00011011 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+
+ /* 245 0xf5 'õ' */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0xd8, /* 11011000 */
+ 0x70, /* 01110000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 246 0xf6 'ö' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 247 0xf7 '÷' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0x76, /* 01110110 */
+ 0xdc, /* 11011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 248 0xf8 'ø' */
+ 0x00, /* 00000000 */
+ 0x38, /* 00111000 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x38, /* 00111000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 249 0xf9 'ù' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 250 0xfa 'ú' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x18, /* 00011000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 251 0xfb 'û' */
+ 0x00, /* 00000000 */
+ 0x0f, /* 00001111 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0x0c, /* 00001100 */
+ 0xec, /* 11101100 */
+ 0x6c, /* 01101100 */
+ 0x6c, /* 01101100 */
+ 0x3c, /* 00111100 */
+ 0x1c, /* 00011100 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 252 0xfc 'ü' */
+ 0x00, /* 00000000 */
+ 0x6c, /* 01101100 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x36, /* 00110110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 253 0xfd 'ý' */
+ 0x00, /* 00000000 */
+ 0x3c, /* 00111100 */
+ 0x66, /* 01100110 */
+ 0x0c, /* 00001100 */
+ 0x18, /* 00011000 */
+ 0x32, /* 00110010 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 254 0xfe 'þ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x7e, /* 01111110 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+ /* 255 0xff 'ÿ' */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+ 0x00, /* 00000000 */
+
+};
diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
new file mode 100644
index 000000000..47fdae5b2
--- /dev/null
+++ b/ui/vnc-auth-sasl.c
@@ -0,0 +1,692 @@
+/*
+ * QEMU VNC display driver: SASL auth protocol
+ *
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "authz/base.h"
+#include "vnc.h"
+#include "trace.h"
+
+/*
+ * Apple has deprecated sasl.h functions in OS X 10.11. Therefore,
+ * files that use SASL API need to disable -Wdeprecated-declarations.
+ */
+#ifdef CONFIG_DARWIN
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+/* Max amount of data we send/recv for SASL steps to prevent DOS */
+#define SASL_DATA_MAX_LEN (1024 * 1024)
+
+
+bool vnc_sasl_server_init(Error **errp)
+{
+ int saslErr = sasl_server_init(NULL, "qemu");
+
+ if (saslErr != SASL_OK) {
+ error_setg(errp, "Failed to initialize SASL auth: %s",
+ sasl_errstring(saslErr, NULL, NULL));
+ return false;
+ }
+ return true;
+}
+
+void vnc_sasl_client_cleanup(VncState *vs)
+{
+ if (vs->sasl.conn) {
+ vs->sasl.runSSF = false;
+ vs->sasl.wantSSF = false;
+ vs->sasl.waitWriteSSF = 0;
+ vs->sasl.encodedLength = vs->sasl.encodedOffset = 0;
+ vs->sasl.encoded = NULL;
+ g_free(vs->sasl.username);
+ g_free(vs->sasl.mechlist);
+ vs->sasl.username = vs->sasl.mechlist = NULL;
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ }
+}
+
+
+size_t vnc_client_write_sasl(VncState *vs)
+{
+ size_t ret;
+
+ VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd "
+ "Encoded: %p size %d offset %d\n",
+ vs->output.buffer, vs->output.capacity, vs->output.offset,
+ vs->sasl.encoded, vs->sasl.encodedLength, vs->sasl.encodedOffset);
+
+ if (!vs->sasl.encoded) {
+ int err;
+ err = sasl_encode(vs->sasl.conn,
+ (char *)vs->output.buffer,
+ vs->output.offset,
+ (const char **)&vs->sasl.encoded,
+ &vs->sasl.encodedLength);
+ if (err != SASL_OK)
+ return vnc_client_io_error(vs, -1, NULL);
+
+ vs->sasl.encodedRawLength = vs->output.offset;
+ vs->sasl.encodedOffset = 0;
+ }
+
+ ret = vnc_client_write_buf(vs,
+ vs->sasl.encoded + vs->sasl.encodedOffset,
+ vs->sasl.encodedLength - vs->sasl.encodedOffset);
+ if (!ret)
+ return 0;
+
+ vs->sasl.encodedOffset += ret;
+ if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
+ bool throttled = vs->force_update_offset != 0;
+ size_t offset;
+ if (vs->sasl.encodedRawLength >= vs->force_update_offset) {
+ vs->force_update_offset = 0;
+ } else {
+ vs->force_update_offset -= vs->sasl.encodedRawLength;
+ }
+ if (throttled && vs->force_update_offset == 0) {
+ trace_vnc_client_unthrottle_forced(vs, vs->ioc);
+ }
+ offset = vs->output.offset;
+ buffer_advance(&vs->output, vs->sasl.encodedRawLength);
+ if (offset >= vs->throttle_output_offset &&
+ vs->output.offset < vs->throttle_output_offset) {
+ trace_vnc_client_unthrottle_incremental(vs, vs->ioc,
+ vs->output.offset);
+ }
+ vs->sasl.encoded = NULL;
+ vs->sasl.encodedOffset = vs->sasl.encodedLength = 0;
+ }
+
+ /* Can't merge this block with one above, because
+ * someone might have written more unencrypted
+ * data in vs->output while we were processing
+ * SASL encoded output
+ */
+ if (vs->output.offset == 0) {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vnc_client_io, vs, NULL);
+ }
+
+ return ret;
+}
+
+
+size_t vnc_client_read_sasl(VncState *vs)
+{
+ size_t ret;
+ uint8_t encoded[4096];
+ const char *decoded;
+ unsigned int decodedLen;
+ int err;
+
+ ret = vnc_client_read_buf(vs, encoded, sizeof(encoded));
+ if (!ret)
+ return 0;
+
+ err = sasl_decode(vs->sasl.conn,
+ (char *)encoded, ret,
+ &decoded, &decodedLen);
+
+ if (err != SASL_OK)
+ return vnc_client_io_error(vs, -1, NULL);
+ VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n",
+ encoded, ret, decoded, decodedLen);
+ buffer_reserve(&vs->input, decodedLen);
+ buffer_append(&vs->input, decoded, decodedLen);
+ return decodedLen;
+}
+
+
+static int vnc_auth_sasl_check_access(VncState *vs)
+{
+ const void *val;
+ int rv;
+ Error *err = NULL;
+ bool allow;
+
+ rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
+ if (rv != SASL_OK) {
+ trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
+ sasl_errstring(rv, NULL, NULL));
+ return -1;
+ }
+ if (val == NULL) {
+ trace_vnc_auth_fail(vs, vs->auth, "No SASL username set", "");
+ return -1;
+ }
+
+ vs->sasl.username = g_strdup((const char*)val);
+ trace_vnc_auth_sasl_username(vs, vs->sasl.username);
+
+ if (vs->vd->sasl.authzid == NULL) {
+ trace_vnc_auth_sasl_acl(vs, 1);
+ return 0;
+ }
+
+ allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
+ vs->sasl.username, &err);
+ if (err) {
+ trace_vnc_auth_fail(vs, vs->auth, "Error from authz",
+ error_get_pretty(err));
+ error_free(err);
+ return -1;
+ }
+
+ trace_vnc_auth_sasl_acl(vs, allow);
+ return allow ? 0 : -1;
+}
+
+static int vnc_auth_sasl_check_ssf(VncState *vs)
+{
+ const void *val;
+ int err, ssf;
+
+ if (!vs->sasl.wantSSF)
+ return 1;
+
+ err = sasl_getprop(vs->sasl.conn, SASL_SSF, &val);
+ if (err != SASL_OK)
+ return 0;
+
+ ssf = *(const int *)val;
+
+ trace_vnc_auth_sasl_ssf(vs, ssf);
+
+ if (ssf < 56)
+ return 0; /* 56 is good for Kerberos */
+
+ /* Only setup for read initially, because we're about to send an RPC
+ * reply which must be in plain text. When the next incoming RPC
+ * arrives, we'll switch on writes too
+ *
+ * cf qemudClientReadSASL in qemud.c
+ */
+ vs->sasl.runSSF = 1;
+
+ /* We have a SSF that's good enough */
+ return 1;
+}
+
+/*
+ * Step Msg
+ *
+ * Input from client:
+ *
+ * u32 clientin-length
+ * u8-array clientin-string
+ *
+ * Output to client:
+ *
+ * u32 serverout-length
+ * u8-array serverout-strin
+ * u8 continue
+ */
+
+static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len);
+
+static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t len)
+{
+ uint32_t datalen = len;
+ const char *serverout;
+ unsigned int serveroutlen;
+ int err;
+ char *clientdata = NULL;
+
+ /* NB, distinction of NULL vs "" is *critical* in SASL */
+ if (datalen) {
+ clientdata = (char*)data;
+ clientdata[datalen-1] = '\0'; /* Wire includes '\0', but make sure */
+ datalen--; /* Don't count NULL byte when passing to _start() */
+ }
+
+ err = sasl_server_step(vs->sasl.conn,
+ clientdata,
+ datalen,
+ &serverout,
+ &serveroutlen);
+ trace_vnc_auth_sasl_step(vs, data, len, serverout, serveroutlen, err);
+ if (err != SASL_OK &&
+ err != SASL_CONTINUE) {
+ trace_vnc_auth_fail(vs, vs->auth, "Cannot step SASL auth",
+ sasl_errdetail(vs->sasl.conn));
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+
+ if (serveroutlen > SASL_DATA_MAX_LEN) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+
+ if (serveroutlen) {
+ vnc_write_u32(vs, serveroutlen + 1);
+ vnc_write(vs, serverout, serveroutlen + 1);
+ } else {
+ vnc_write_u32(vs, 0);
+ }
+
+ /* Whether auth is complete */
+ vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);
+
+ if (err == SASL_CONTINUE) {
+ /* Wait for step length */
+ vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
+ } else {
+ if (!vnc_auth_sasl_check_ssf(vs)) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
+ goto authreject;
+ }
+
+ /* Check the username access control list */
+ if (vnc_auth_sasl_check_access(vs) < 0) {
+ goto authreject;
+ }
+
+ trace_vnc_auth_pass(vs, vs->auth);
+ vnc_write_u32(vs, 0); /* Accept auth */
+ /*
+ * Delay writing in SSF encoded mode until pending output
+ * buffer is written
+ */
+ if (vs->sasl.runSSF)
+ vs->sasl.waitWriteSSF = vs->output.offset;
+ start_client_init(vs);
+ }
+
+ return 0;
+
+ authreject:
+ vnc_write_u32(vs, 1); /* Reject auth */
+ vnc_write_u32(vs, sizeof("Authentication failed"));
+ vnc_write(vs, "Authentication failed", sizeof("Authentication failed"));
+ vnc_flush(vs);
+ vnc_client_error(vs);
+ return -1;
+
+ authabort:
+ vnc_client_error(vs);
+ return -1;
+}
+
+static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len)
+{
+ uint32_t steplen = read_u32(data, 0);
+
+ if (steplen > SASL_DATA_MAX_LEN) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL step len too large", "");
+ vnc_client_error(vs);
+ return -1;
+ }
+
+ if (steplen == 0)
+ return protocol_client_auth_sasl_step(vs, NULL, 0);
+ else
+ vnc_read_when(vs, protocol_client_auth_sasl_step, steplen);
+ return 0;
+}
+
+/*
+ * Start Msg
+ *
+ * Input from client:
+ *
+ * u32 clientin-length
+ * u8-array clientin-string
+ *
+ * Output to client:
+ *
+ * u32 serverout-length
+ * u8-array serverout-strin
+ * u8 continue
+ */
+
+#define SASL_DATA_MAX_LEN (1024 * 1024)
+
+static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t len)
+{
+ uint32_t datalen = len;
+ const char *serverout;
+ unsigned int serveroutlen;
+ int err;
+ char *clientdata = NULL;
+
+ /* NB, distinction of NULL vs "" is *critical* in SASL */
+ if (datalen) {
+ clientdata = (char*)data;
+ clientdata[datalen-1] = '\0'; /* Should be on wire, but make sure */
+ datalen--; /* Don't count NULL byte when passing to _start() */
+ }
+
+ err = sasl_server_start(vs->sasl.conn,
+ vs->sasl.mechlist,
+ clientdata,
+ datalen,
+ &serverout,
+ &serveroutlen);
+ trace_vnc_auth_sasl_start(vs, data, len, serverout, serveroutlen, err);
+ if (err != SASL_OK &&
+ err != SASL_CONTINUE) {
+ trace_vnc_auth_fail(vs, vs->auth, "Cannot start SASL auth",
+ sasl_errdetail(vs->sasl.conn));
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+ if (serveroutlen > SASL_DATA_MAX_LEN) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+
+ if (serveroutlen) {
+ vnc_write_u32(vs, serveroutlen + 1);
+ vnc_write(vs, serverout, serveroutlen + 1);
+ } else {
+ vnc_write_u32(vs, 0);
+ }
+
+ /* Whether auth is complete */
+ vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);
+
+ if (err == SASL_CONTINUE) {
+ /* Wait for step length */
+ vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
+ } else {
+ if (!vnc_auth_sasl_check_ssf(vs)) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
+ goto authreject;
+ }
+
+ /* Check the username access control list */
+ if (vnc_auth_sasl_check_access(vs) < 0) {
+ goto authreject;
+ }
+
+ trace_vnc_auth_pass(vs, vs->auth);
+ vnc_write_u32(vs, 0); /* Accept auth */
+ start_client_init(vs);
+ }
+
+ return 0;
+
+ authreject:
+ vnc_write_u32(vs, 1); /* Reject auth */
+ vnc_write_u32(vs, sizeof("Authentication failed"));
+ vnc_write(vs, "Authentication failed", sizeof("Authentication failed"));
+ vnc_flush(vs);
+ vnc_client_error(vs);
+ return -1;
+
+ authabort:
+ vnc_client_error(vs);
+ return -1;
+}
+
+static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size_t len)
+{
+ uint32_t startlen = read_u32(data, 0);
+
+ if (startlen > SASL_DATA_MAX_LEN) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL start len too large", "");
+ vnc_client_error(vs);
+ return -1;
+ }
+
+ if (startlen == 0)
+ return protocol_client_auth_sasl_start(vs, NULL, 0);
+
+ vnc_read_when(vs, protocol_client_auth_sasl_start, startlen);
+ return 0;
+}
+
+static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_t len)
+{
+ char *mechname = g_strndup((const char *) data, len);
+ trace_vnc_auth_sasl_mech_choose(vs, mechname);
+
+ if (strncmp(vs->sasl.mechlist, mechname, len) == 0) {
+ if (vs->sasl.mechlist[len] != '\0' &&
+ vs->sasl.mechlist[len] != ',') {
+ goto fail;
+ }
+ } else {
+ char *offset = strstr(vs->sasl.mechlist, mechname);
+ if (!offset) {
+ goto fail;
+ }
+ if (offset[-1] != ',' ||
+ (offset[len] != '\0'&&
+ offset[len] != ',')) {
+ goto fail;
+ }
+ }
+
+ g_free(vs->sasl.mechlist);
+ vs->sasl.mechlist = mechname;
+
+ vnc_read_when(vs, protocol_client_auth_sasl_start_len, 4);
+ return 0;
+
+ fail:
+ trace_vnc_auth_fail(vs, vs->auth, "Unsupported mechname", mechname);
+ vnc_client_error(vs);
+ g_free(mechname);
+ return -1;
+}
+
+static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, size_t len)
+{
+ uint32_t mechlen = read_u32(data, 0);
+
+ if (mechlen > 100) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too long", "");
+ vnc_client_error(vs);
+ return -1;
+ }
+ if (mechlen < 1) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too short", "");
+ vnc_client_error(vs);
+ return -1;
+ }
+ vnc_read_when(vs, protocol_client_auth_sasl_mechname,mechlen);
+ return 0;
+}
+
+static char *
+vnc_socket_ip_addr_string(QIOChannelSocket *ioc,
+ bool local,
+ Error **errp)
+{
+ SocketAddress *addr;
+ char *ret;
+
+ if (local) {
+ addr = qio_channel_socket_get_local_address(ioc, errp);
+ } else {
+ addr = qio_channel_socket_get_remote_address(ioc, errp);
+ }
+ if (!addr) {
+ return NULL;
+ }
+
+ if (addr->type != SOCKET_ADDRESS_TYPE_INET) {
+ error_setg(errp, "Not an inet socket type");
+ qapi_free_SocketAddress(addr);
+ return NULL;
+ }
+ ret = g_strdup_printf("%s;%s", addr->u.inet.host, addr->u.inet.port);
+ qapi_free_SocketAddress(addr);
+ return ret;
+}
+
+void start_auth_sasl(VncState *vs)
+{
+ const char *mechlist = NULL;
+ sasl_security_properties_t secprops;
+ int err;
+ Error *local_err = NULL;
+ char *localAddr, *remoteAddr;
+ int mechlistlen;
+
+ /* Get local & remote client addresses in form IPADDR;PORT */
+ localAddr = vnc_socket_ip_addr_string(vs->sioc, true, &local_err);
+ if (!localAddr) {
+ trace_vnc_auth_fail(vs, vs->auth, "Cannot format local IP",
+ error_get_pretty(local_err));
+ goto authabort;
+ }
+
+ remoteAddr = vnc_socket_ip_addr_string(vs->sioc, false, &local_err);
+ if (!remoteAddr) {
+ trace_vnc_auth_fail(vs, vs->auth, "Cannot format remote IP",
+ error_get_pretty(local_err));
+ g_free(localAddr);
+ goto authabort;
+ }
+
+ err = sasl_server_new("vnc",
+ NULL, /* FQDN - just delegates to gethostname */
+ NULL, /* User realm */
+ localAddr,
+ remoteAddr,
+ NULL, /* Callbacks, not needed */
+ SASL_SUCCESS_DATA,
+ &vs->sasl.conn);
+ g_free(localAddr);
+ g_free(remoteAddr);
+ localAddr = remoteAddr = NULL;
+
+ if (err != SASL_OK) {
+ trace_vnc_auth_fail(vs, vs->auth, "SASL context setup failed",
+ sasl_errstring(err, NULL, NULL));
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+
+ /* Inform SASL that we've got an external SSF layer from TLS/x509 */
+ if (vs->auth == VNC_AUTH_VENCRYPT &&
+ vs->subauth == VNC_AUTH_VENCRYPT_X509SASL) {
+ int keysize;
+ sasl_ssf_t ssf;
+
+ keysize = qcrypto_tls_session_get_key_size(vs->tls,
+ &local_err);
+ if (keysize < 0) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot TLS get cipher size",
+ error_get_pretty(local_err));
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+ ssf = keysize * CHAR_BIT; /* tls key size is bytes, sasl wants bits */
+
+ err = sasl_setprop(vs->sasl.conn, SASL_SSF_EXTERNAL, &ssf);
+ if (err != SASL_OK) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot set SASL external SSF",
+ sasl_errstring(err, NULL, NULL));
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+ } else {
+ vs->sasl.wantSSF = 1;
+ }
+
+ memset (&secprops, 0, sizeof secprops);
+ /* Inform SASL that we've got an external SSF layer from TLS.
+ *
+ * Disable SSF, if using TLS+x509+SASL only. TLS without x509
+ * is not sufficiently strong
+ */
+ if (vs->vd->is_unix ||
+ (vs->auth == VNC_AUTH_VENCRYPT &&
+ vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)) {
+ /* If we've got TLS or UNIX domain sock, we don't care about SSF */
+ secprops.min_ssf = 0;
+ secprops.max_ssf = 0;
+ secprops.maxbufsize = 8192;
+ secprops.security_flags = 0;
+ } else {
+ /* Plain TCP, better get an SSF layer */
+ secprops.min_ssf = 56; /* Good enough to require kerberos */
+ secprops.max_ssf = 100000; /* Arbitrary big number */
+ secprops.maxbufsize = 8192;
+ /* Forbid any anonymous or trivially crackable auth */
+ secprops.security_flags =
+ SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT;
+ }
+
+ err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops);
+ if (err != SASL_OK) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot set SASL security props",
+ sasl_errstring(err, NULL, NULL));
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+
+ err = sasl_listmech(vs->sasl.conn,
+ NULL, /* Don't need to set user */
+ "", /* Prefix */
+ ",", /* Separator */
+ "", /* Suffix */
+ &mechlist,
+ NULL,
+ NULL);
+ if (err != SASL_OK) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot list SASL mechanisms",
+ sasl_errdetail(vs->sasl.conn));
+ sasl_dispose(&vs->sasl.conn);
+ vs->sasl.conn = NULL;
+ goto authabort;
+ }
+ trace_vnc_auth_sasl_mech_list(vs, mechlist);
+
+ vs->sasl.mechlist = g_strdup(mechlist);
+ mechlistlen = strlen(mechlist);
+ vnc_write_u32(vs, mechlistlen);
+ vnc_write(vs, mechlist, mechlistlen);
+ vnc_flush(vs);
+
+ vnc_read_when(vs, protocol_client_auth_sasl_mechname_len, 4);
+
+ return;
+
+ authabort:
+ error_free(local_err);
+ vnc_client_error(vs);
+}
+
+
diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h
new file mode 100644
index 000000000..367b8672c
--- /dev/null
+++ b/ui/vnc-auth-sasl.h
@@ -0,0 +1,74 @@
+/*
+ * QEMU VNC display driver: SASL auth protocol
+ *
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_VNC_AUTH_SASL_H
+#define QEMU_VNC_AUTH_SASL_H
+
+#include <sasl/sasl.h>
+
+typedef struct VncStateSASL VncStateSASL;
+typedef struct VncDisplaySASL VncDisplaySASL;
+
+#include "authz/base.h"
+
+struct VncStateSASL {
+ sasl_conn_t *conn;
+ /* If we want to negotiate an SSF layer with client */
+ bool wantSSF;
+ /* If we are now running the SSF layer */
+ bool runSSF;
+ /*
+ * If this is non-zero, then wait for that many bytes
+ * to be written plain, before switching to SSF encoding
+ * This allows the VNC auth result to finish being
+ * written in plain.
+ */
+ unsigned int waitWriteSSF;
+
+ /*
+ * Buffering encoded data to allow more clear data
+ * to be stuffed onto the output buffer
+ */
+ const uint8_t *encoded;
+ unsigned int encodedLength;
+ unsigned int encodedRawLength;
+ unsigned int encodedOffset;
+ char *username;
+ char *mechlist;
+};
+
+struct VncDisplaySASL {
+ QAuthZ *authz;
+ char *authzid;
+};
+
+bool vnc_sasl_server_init(Error **errp);
+void vnc_sasl_client_cleanup(VncState *vs);
+
+size_t vnc_client_read_sasl(VncState *vs);
+size_t vnc_client_write_sasl(VncState *vs);
+
+void start_auth_sasl(VncState *vs);
+
+#endif /* QEMU_VNC_AUTH_SASL_H */
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
new file mode 100644
index 000000000..d9c212ff3
--- /dev/null
+++ b/ui/vnc-auth-vencrypt.c
@@ -0,0 +1,166 @@
+/*
+ * QEMU VNC display driver: VeNCrypt authentication setup
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "vnc.h"
+#include "qapi/error.h"
+#include "qemu/main-loop.h"
+#include "trace.h"
+
+static void start_auth_vencrypt_subauth(VncState *vs)
+{
+ switch (vs->subauth) {
+ case VNC_AUTH_VENCRYPT_TLSNONE:
+ case VNC_AUTH_VENCRYPT_X509NONE:
+ vnc_write_u32(vs, 0); /* Accept auth completion */
+ start_client_init(vs);
+ break;
+
+ case VNC_AUTH_VENCRYPT_TLSVNC:
+ case VNC_AUTH_VENCRYPT_X509VNC:
+ start_auth_vnc(vs);
+ break;
+
+#ifdef CONFIG_VNC_SASL
+ case VNC_AUTH_VENCRYPT_TLSSASL:
+ case VNC_AUTH_VENCRYPT_X509SASL:
+ start_auth_sasl(vs);
+ break;
+#endif /* CONFIG_VNC_SASL */
+
+ default: /* Should not be possible, but just in case */
+ trace_vnc_auth_fail(vs, vs->auth, "Unhandled VeNCrypt subauth", "");
+ vnc_write_u8(vs, 1);
+ if (vs->minor >= 8) {
+ static const char err[] = "Unsupported authentication type";
+ vnc_write_u32(vs, sizeof(err));
+ vnc_write(vs, err, sizeof(err));
+ }
+ vnc_client_error(vs);
+ }
+}
+
+static void vnc_tls_handshake_done(QIOTask *task,
+ gpointer user_data)
+{
+ VncState *vs = user_data;
+ Error *err = NULL;
+
+ if (qio_task_propagate_error(task, &err)) {
+ trace_vnc_auth_fail(vs, vs->auth, "TLS handshake failed",
+ error_get_pretty(err));
+ vnc_client_error(vs);
+ error_free(err);
+ } else {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_OUT,
+ vnc_client_io, vs, NULL);
+ start_auth_vencrypt_subauth(vs);
+ }
+}
+
+
+static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len)
+{
+ int auth = read_u32(data, 0);
+
+ trace_vnc_auth_vencrypt_subauth(vs, auth);
+ if (auth != vs->subauth) {
+ trace_vnc_auth_fail(vs, vs->auth, "Unsupported sub-auth version", "");
+ vnc_write_u8(vs, 0); /* Reject auth */
+ vnc_flush(vs);
+ vnc_client_error(vs);
+ } else {
+ Error *err = NULL;
+ QIOChannelTLS *tls;
+ vnc_write_u8(vs, 1); /* Accept auth */
+ vnc_flush(vs);
+
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
+ }
+
+ tls = qio_channel_tls_new_server(
+ vs->ioc,
+ vs->vd->tlscreds,
+ vs->vd->tlsauthzid,
+ &err);
+ if (!tls) {
+ trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
+ error_get_pretty(err));
+ error_free(err);
+ vnc_client_error(vs);
+ return 0;
+ }
+
+ qio_channel_set_name(QIO_CHANNEL(tls), "vnc-server-tls");
+ object_unref(OBJECT(vs->ioc));
+ vs->ioc = QIO_CHANNEL(tls);
+ trace_vnc_client_io_wrap(vs, vs->ioc, "tls");
+ vs->tls = qio_channel_tls_get_session(tls);
+
+ qio_channel_tls_handshake(tls,
+ vnc_tls_handshake_done,
+ vs,
+ NULL,
+ NULL);
+ }
+ return 0;
+}
+
+static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len)
+{
+ trace_vnc_auth_vencrypt_version(vs, (int)data[0], (int)data[1]);
+ if (data[0] != 0 ||
+ data[1] != 2) {
+ trace_vnc_auth_fail(vs, vs->auth, "Unsupported version", "");
+ vnc_write_u8(vs, 1); /* Reject version */
+ vnc_flush(vs);
+ vnc_client_error(vs);
+ } else {
+ vnc_write_u8(vs, 0); /* Accept version */
+ vnc_write_u8(vs, 1); /* Number of sub-auths */
+ vnc_write_u32(vs, vs->subauth); /* The supported auth */
+ vnc_flush(vs);
+ vnc_read_when(vs, protocol_client_vencrypt_auth, 4);
+ }
+ return 0;
+}
+
+
+void start_auth_vencrypt(VncState *vs)
+{
+ /* Send VeNCrypt version 0.2 */
+ vnc_write_u8(vs, 0);
+ vnc_write_u8(vs, 2);
+
+ vnc_read_when(vs, protocol_client_vencrypt_init, 2);
+}
+
diff --git a/ui/vnc-auth-vencrypt.h b/ui/vnc-auth-vencrypt.h
new file mode 100644
index 000000000..1e3540666
--- /dev/null
+++ b/ui/vnc-auth-vencrypt.h
@@ -0,0 +1,32 @@
+/*
+ * QEMU VNC display driver
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_VNC_AUTH_VENCRYPT_H
+#define QEMU_VNC_AUTH_VENCRYPT_H
+
+void start_auth_vencrypt(VncState *vs);
+
+#endif /* QEMU_VNC_AUTH_VENCRYPT_H */
diff --git a/ui/vnc-clipboard.c b/ui/vnc-clipboard.c
new file mode 100644
index 000000000..67284b556
--- /dev/null
+++ b/ui/vnc-clipboard.c
@@ -0,0 +1,325 @@
+/*
+ * QEMU VNC display driver -- clipboard support
+ *
+ * Copyright (C) 2021 Gerd Hoffmann <kraxel@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "vnc.h"
+#include "vnc-jobs.h"
+
+static uint8_t *inflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
+{
+ z_stream stream = {
+ .next_in = in,
+ .avail_in = in_len,
+ .zalloc = Z_NULL,
+ .zfree = Z_NULL,
+ };
+ uint32_t out_len = 8;
+ uint8_t *out = g_malloc(out_len);
+ int ret;
+
+ stream.next_out = out + stream.total_out;
+ stream.avail_out = out_len - stream.total_out;
+
+ ret = inflateInit(&stream);
+ if (ret != Z_OK) {
+ goto err;
+ }
+
+ while (stream.avail_in) {
+ ret = inflate(&stream, Z_FINISH);
+ switch (ret) {
+ case Z_OK:
+ case Z_STREAM_END:
+ break;
+ case Z_BUF_ERROR:
+ out_len <<= 1;
+ if (out_len > (1 << 20)) {
+ goto err_end;
+ }
+ out = g_realloc(out, out_len);
+ stream.next_out = out + stream.total_out;
+ stream.avail_out = out_len - stream.total_out;
+ break;
+ default:
+ goto err_end;
+ }
+ }
+
+ *size = stream.total_out;
+ inflateEnd(&stream);
+
+ return out;
+
+err_end:
+ inflateEnd(&stream);
+err:
+ g_free(out);
+ return NULL;
+}
+
+static uint8_t *deflate_buffer(uint8_t *in, uint32_t in_len, uint32_t *size)
+{
+ z_stream stream = {
+ .next_in = in,
+ .avail_in = in_len,
+ .zalloc = Z_NULL,
+ .zfree = Z_NULL,
+ };
+ uint32_t out_len = 8;
+ uint8_t *out = g_malloc(out_len);
+ int ret;
+
+ stream.next_out = out + stream.total_out;
+ stream.avail_out = out_len - stream.total_out;
+
+ ret = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
+ if (ret != Z_OK) {
+ goto err;
+ }
+
+ while (ret != Z_STREAM_END) {
+ ret = deflate(&stream, Z_FINISH);
+ switch (ret) {
+ case Z_OK:
+ case Z_STREAM_END:
+ break;
+ case Z_BUF_ERROR:
+ out_len <<= 1;
+ if (out_len > (1 << 20)) {
+ goto err_end;
+ }
+ out = g_realloc(out, out_len);
+ stream.next_out = out + stream.total_out;
+ stream.avail_out = out_len - stream.total_out;
+ break;
+ default:
+ goto err_end;
+ }
+ }
+
+ *size = stream.total_out;
+ deflateEnd(&stream);
+
+ return out;
+
+err_end:
+ deflateEnd(&stream);
+err:
+ g_free(out);
+ return NULL;
+}
+
+static void vnc_clipboard_send(VncState *vs, uint32_t count, uint32_t *dwords)
+{
+ int i;
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
+ vnc_write_u8(vs, 0);
+ vnc_write_u8(vs, 0);
+ vnc_write_u8(vs, 0);
+ vnc_write_s32(vs, -(count * sizeof(uint32_t))); /* -(message length) */
+ for (i = 0; i < count; i++) {
+ vnc_write_u32(vs, dwords[i]);
+ }
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void vnc_clipboard_provide(VncState *vs,
+ QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ uint32_t flags = 0;
+ g_autofree uint8_t *buf = NULL;
+ g_autofree void *zbuf = NULL;
+ uint32_t zsize;
+
+ switch (type) {
+ case QEMU_CLIPBOARD_TYPE_TEXT:
+ flags |= VNC_CLIPBOARD_TEXT;
+ break;
+ default:
+ return;
+ }
+ flags |= VNC_CLIPBOARD_PROVIDE;
+
+ buf = g_malloc(info->types[type].size + 4);
+ buf[0] = (info->types[type].size >> 24) & 0xff;
+ buf[1] = (info->types[type].size >> 16) & 0xff;
+ buf[2] = (info->types[type].size >> 8) & 0xff;
+ buf[3] = (info->types[type].size >> 0) & 0xff;
+ memcpy(buf + 4, info->types[type].data, info->types[type].size);
+ zbuf = deflate_buffer(buf, info->types[type].size + 4, &zsize);
+ if (!zbuf) {
+ return;
+ }
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_CUT_TEXT);
+ vnc_write_u8(vs, 0);
+ vnc_write_u8(vs, 0);
+ vnc_write_u8(vs, 0);
+ vnc_write_s32(vs, -(sizeof(uint32_t) + zsize)); /* -(message length) */
+ vnc_write_u32(vs, flags);
+ vnc_write(vs, zbuf, zsize);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void vnc_clipboard_notify(Notifier *notifier, void *data)
+{
+ VncState *vs = container_of(notifier, VncState, cbpeer.update);
+ QemuClipboardInfo *info = data;
+ QemuClipboardType type;
+ bool self_update = info->owner == &vs->cbpeer;
+ uint32_t flags = 0;
+
+ if (info != vs->cbinfo) {
+ qemu_clipboard_info_unref(vs->cbinfo);
+ vs->cbinfo = qemu_clipboard_info_ref(info);
+ vs->cbpending = 0;
+ if (!self_update) {
+ if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ flags |= VNC_CLIPBOARD_TEXT;
+ }
+ flags |= VNC_CLIPBOARD_NOTIFY;
+ vnc_clipboard_send(vs, 1, &flags);
+ }
+ return;
+ }
+
+ if (self_update) {
+ return;
+ }
+
+ for (type = 0; type < QEMU_CLIPBOARD_TYPE__COUNT; type++) {
+ if (vs->cbpending & (1 << type)) {
+ vs->cbpending &= ~(1 << type);
+ vnc_clipboard_provide(vs, info, type);
+ }
+ }
+}
+
+static void vnc_clipboard_request(QemuClipboardInfo *info,
+ QemuClipboardType type)
+{
+ VncState *vs = container_of(info->owner, VncState, cbpeer);
+ uint32_t flags = 0;
+
+ if (type == QEMU_CLIPBOARD_TYPE_TEXT) {
+ flags |= VNC_CLIPBOARD_TEXT;
+ }
+ if (!flags) {
+ return;
+ }
+ flags |= VNC_CLIPBOARD_REQUEST;
+
+ vnc_clipboard_send(vs, 1, &flags);
+}
+
+void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data)
+{
+ if (flags & VNC_CLIPBOARD_CAPS) {
+ /* need store caps somewhere ? */
+ return;
+ }
+
+ if (flags & VNC_CLIPBOARD_NOTIFY) {
+ QemuClipboardInfo *info =
+ qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+ if (flags & VNC_CLIPBOARD_TEXT) {
+ info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
+ }
+ qemu_clipboard_update(info);
+ qemu_clipboard_info_unref(info);
+ return;
+ }
+
+ if (flags & VNC_CLIPBOARD_PROVIDE &&
+ vs->cbinfo &&
+ vs->cbinfo->owner == &vs->cbpeer) {
+ uint32_t size = 0;
+ g_autofree uint8_t *buf = inflate_buffer(data, len - 4, &size);
+ if ((flags & VNC_CLIPBOARD_TEXT) &&
+ buf && size >= 4) {
+ uint32_t tsize = read_u32(buf, 0);
+ uint8_t *tbuf = buf + 4;
+ if (tsize < size) {
+ qemu_clipboard_set_data(&vs->cbpeer, vs->cbinfo,
+ QEMU_CLIPBOARD_TYPE_TEXT,
+ tsize, tbuf, true);
+ }
+ }
+ }
+
+ if (flags & VNC_CLIPBOARD_REQUEST &&
+ vs->cbinfo &&
+ vs->cbinfo->owner != &vs->cbpeer) {
+ if ((flags & VNC_CLIPBOARD_TEXT) &&
+ vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
+ if (vs->cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].data) {
+ vnc_clipboard_provide(vs, vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
+ } else {
+ vs->cbpending |= (1 << QEMU_CLIPBOARD_TYPE_TEXT);
+ qemu_clipboard_request(vs->cbinfo, QEMU_CLIPBOARD_TYPE_TEXT);
+ }
+ }
+ }
+}
+
+void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text)
+{
+ QemuClipboardInfo *info =
+ qemu_clipboard_info_new(&vs->cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
+
+ qemu_clipboard_set_data(&vs->cbpeer, info, QEMU_CLIPBOARD_TYPE_TEXT,
+ len, text, true);
+ qemu_clipboard_info_unref(info);
+}
+
+void vnc_server_cut_text_caps(VncState *vs)
+{
+ uint32_t caps[2];
+
+ if (!vnc_has_feature(vs, VNC_FEATURE_CLIPBOARD_EXT)) {
+ return;
+ }
+
+ caps[0] = (VNC_CLIPBOARD_PROVIDE |
+ VNC_CLIPBOARD_NOTIFY |
+ VNC_CLIPBOARD_REQUEST |
+ VNC_CLIPBOARD_CAPS |
+ VNC_CLIPBOARD_TEXT);
+ caps[1] = 0;
+ vnc_clipboard_send(vs, 2, caps);
+
+ if (!vs->cbpeer.update.notify) {
+ vs->cbpeer.name = "vnc";
+ vs->cbpeer.update.notify = vnc_clipboard_notify;
+ vs->cbpeer.request = vnc_clipboard_request;
+ qemu_clipboard_peer_register(&vs->cbpeer);
+ }
+}
diff --git a/ui/vnc-enc-hextile-template.h b/ui/vnc-enc-hextile-template.h
new file mode 100644
index 000000000..0c56262af
--- /dev/null
+++ b/ui/vnc-enc-hextile-template.h
@@ -0,0 +1,211 @@
+#define CONCAT_I(a, b) a ## b
+#define CONCAT(a, b) CONCAT_I(a, b)
+#define pixel_t CONCAT(uint, CONCAT(BPP, _t))
+#ifdef GENERIC
+#define NAME CONCAT(generic_, BPP)
+#else
+#define NAME BPP
+#endif
+
+static void CONCAT(send_hextile_tile_, NAME)(VncState *vs,
+ int x, int y, int w, int h,
+ void *last_bg_,
+ void *last_fg_,
+ int *has_bg, int *has_fg)
+{
+ VncDisplay *vd = vs->vd;
+ uint8_t *row = vnc_server_fb_ptr(vd, x, y);
+ pixel_t *irow = (pixel_t *)row;
+ int j, i;
+ pixel_t *last_bg = (pixel_t *)last_bg_;
+ pixel_t *last_fg = (pixel_t *)last_fg_;
+ pixel_t bg = 0;
+ pixel_t fg = 0;
+ int n_colors = 0;
+ int bg_count = 0;
+ int fg_count = 0;
+ int flags = 0;
+ uint8_t data[(vs->client_pf.bytes_per_pixel + 2) * 16 * 16];
+ int n_data = 0;
+ int n_subtiles = 0;
+
+ for (j = 0; j < h; j++) {
+ for (i = 0; i < w; i++) {
+ switch (n_colors) {
+ case 0:
+ bg = irow[i];
+ n_colors = 1;
+ break;
+ case 1:
+ if (irow[i] != bg) {
+ fg = irow[i];
+ n_colors = 2;
+ }
+ break;
+ case 2:
+ if (irow[i] != bg && irow[i] != fg) {
+ n_colors = 3;
+ } else {
+ if (irow[i] == bg)
+ bg_count++;
+ else if (irow[i] == fg)
+ fg_count++;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (n_colors > 2)
+ break;
+ irow += vnc_server_fb_stride(vd) / sizeof(pixel_t);
+ }
+
+ if (n_colors > 1 && fg_count > bg_count) {
+ pixel_t tmp = fg;
+ fg = bg;
+ bg = tmp;
+ }
+
+ if (!*has_bg || *last_bg != bg) {
+ flags |= 0x02;
+ *has_bg = 1;
+ *last_bg = bg;
+ }
+
+ if (n_colors < 3 && (!*has_fg || *last_fg != fg)) {
+ flags |= 0x04;
+ *has_fg = 1;
+ *last_fg = fg;
+ }
+
+ switch (n_colors) {
+ case 1:
+ n_data = 0;
+ break;
+ case 2:
+ flags |= 0x08;
+
+ irow = (pixel_t *)row;
+
+ for (j = 0; j < h; j++) {
+ int min_x = -1;
+ for (i = 0; i < w; i++) {
+ if (irow[i] == fg) {
+ if (min_x == -1)
+ min_x = i;
+ } else if (min_x != -1) {
+ hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1);
+ n_data += 2;
+ n_subtiles++;
+ min_x = -1;
+ }
+ }
+ if (min_x != -1) {
+ hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1);
+ n_data += 2;
+ n_subtiles++;
+ }
+ irow += vnc_server_fb_stride(vd) / sizeof(pixel_t);
+ }
+ break;
+ case 3:
+ flags |= 0x18;
+
+ irow = (pixel_t *)row;
+
+ if (!*has_bg || *last_bg != bg)
+ flags |= 0x02;
+
+ for (j = 0; j < h; j++) {
+ int has_color = 0;
+ int min_x = -1;
+ pixel_t color = 0; /* shut up gcc */
+
+ for (i = 0; i < w; i++) {
+ if (!has_color) {
+ if (irow[i] == bg)
+ continue;
+ color = irow[i];
+ min_x = i;
+ has_color = 1;
+ } else if (irow[i] != color) {
+ has_color = 0;
+#ifdef GENERIC
+ vnc_convert_pixel(vs, data + n_data, color);
+ n_data += vs->client_pf.bytes_per_pixel;
+#else
+ memcpy(data + n_data, &color, sizeof(color));
+ n_data += sizeof(pixel_t);
+#endif
+ hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1);
+ n_data += 2;
+ n_subtiles++;
+
+ min_x = -1;
+ if (irow[i] != bg) {
+ color = irow[i];
+ min_x = i;
+ has_color = 1;
+ }
+ }
+ }
+ if (has_color) {
+#ifdef GENERIC
+ vnc_convert_pixel(vs, data + n_data, color);
+ n_data += vs->client_pf.bytes_per_pixel;
+#else
+ memcpy(data + n_data, &color, sizeof(color));
+ n_data += sizeof(pixel_t);
+#endif
+ hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1);
+ n_data += 2;
+ n_subtiles++;
+ }
+ irow += vnc_server_fb_stride(vd) / sizeof(pixel_t);
+ }
+
+ /* A SubrectsColoured subtile invalidates the foreground color */
+ *has_fg = 0;
+ if (n_data > (w * h * sizeof(pixel_t))) {
+ n_colors = 4;
+ flags = 0x01;
+ *has_bg = 0;
+
+ /* we really don't have to invalidate either the bg or fg
+ but we've lost the old values. oh well. */
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (n_colors > 3) {
+ flags = 0x01;
+ *has_fg = 0;
+ *has_bg = 0;
+ n_colors = 4;
+ }
+
+ vnc_write_u8(vs, flags);
+ if (n_colors < 4) {
+ if (flags & 0x02)
+ vs->write_pixels(vs, last_bg, sizeof(pixel_t));
+ if (flags & 0x04)
+ vs->write_pixels(vs, last_fg, sizeof(pixel_t));
+ if (n_subtiles) {
+ vnc_write_u8(vs, n_subtiles);
+ vnc_write(vs, data, n_data);
+ }
+ } else {
+ for (j = 0; j < h; j++) {
+ vs->write_pixels(vs, row, w * 4);
+ row += vnc_server_fb_stride(vd);
+ }
+ }
+}
+
+#undef NAME
+#undef pixel_t
+#undef CONCAT_I
+#undef CONCAT
diff --git a/ui/vnc-enc-hextile.c b/ui/vnc-enc-hextile.c
new file mode 100644
index 000000000..4215bd7da
--- /dev/null
+++ b/ui/vnc-enc-hextile.c
@@ -0,0 +1,84 @@
+/*
+ * QEMU VNC display driver: hextile encoding
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "vnc.h"
+
+static void hextile_enc_cord(uint8_t *ptr, int x, int y, int w, int h)
+{
+ ptr[0] = ((x & 0x0F) << 4) | (y & 0x0F);
+ ptr[1] = (((w - 1) & 0x0F) << 4) | ((h - 1) & 0x0F);
+}
+
+#define BPP 32
+#include "vnc-enc-hextile-template.h"
+#undef BPP
+
+#define GENERIC
+#define BPP 32
+#include "vnc-enc-hextile-template.h"
+#undef BPP
+#undef GENERIC
+
+int vnc_hextile_send_framebuffer_update(VncState *vs, int x,
+ int y, int w, int h)
+{
+ int i, j;
+ int has_fg, has_bg;
+ uint8_t *last_fg, *last_bg;
+
+ last_fg = (uint8_t *) g_malloc(VNC_SERVER_FB_BYTES);
+ last_bg = (uint8_t *) g_malloc(VNC_SERVER_FB_BYTES);
+ has_fg = has_bg = 0;
+ for (j = y; j < (y + h); j += 16) {
+ for (i = x; i < (x + w); i += 16) {
+ vs->hextile.send_tile(vs, i, j,
+ MIN(16, x + w - i), MIN(16, y + h - j),
+ last_bg, last_fg, &has_bg, &has_fg);
+ }
+ }
+ g_free(last_fg);
+ g_free(last_bg);
+
+ return 1;
+}
+
+void vnc_hextile_set_pixel_conversion(VncState *vs, int generic)
+{
+ if (!generic) {
+ switch (VNC_SERVER_FB_BITS) {
+ case 32:
+ vs->hextile.send_tile = send_hextile_tile_32;
+ break;
+ }
+ } else {
+ switch (VNC_SERVER_FB_BITS) {
+ case 32:
+ vs->hextile.send_tile = send_hextile_tile_generic_32;
+ break;
+ }
+ }
+}
diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c
new file mode 100644
index 000000000..cebd35841
--- /dev/null
+++ b/ui/vnc-enc-tight.c
@@ -0,0 +1,1712 @@
+/*
+ * QEMU VNC display driver: tight encoding
+ *
+ * From libvncserver/libvncserver/tight.c
+ * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+
+/* This needs to be before jpeglib.h line because of conflict with
+ INT32 definitions between jmorecfg.h (included by jpeglib.h) and
+ Win32 basetsd.h (included by windows.h). */
+
+#ifdef CONFIG_VNC_PNG
+/* The following define is needed by pngconf.h. Otherwise it won't compile,
+ because setjmp.h was already included by qemu-common.h. */
+#define PNG_SKIP_SETJMP_CHECK
+#include <png.h>
+#endif
+#ifdef CONFIG_VNC_JPEG
+#include <jpeglib.h>
+#endif
+
+#include "qemu/bswap.h"
+#include "vnc.h"
+#include "vnc-enc-tight.h"
+#include "vnc-palette.h"
+
+/* Compression level stuff. The following array contains various
+ encoder parameters for each of 10 compression levels (0..9).
+ Last three parameters correspond to JPEG quality levels (0..9). */
+
+static const struct {
+ int max_rect_size, max_rect_width;
+ int mono_min_rect_size, gradient_min_rect_size;
+ int idx_zlib_level, mono_zlib_level, raw_zlib_level, gradient_zlib_level;
+ int gradient_threshold, gradient_threshold24;
+ int idx_max_colors_divisor;
+ int jpeg_quality, jpeg_threshold, jpeg_threshold24;
+} tight_conf[] = {
+ { 512, 32, 6, 65536, 0, 0, 0, 0, 0, 0, 4, 5, 10000, 23000 },
+ { 2048, 128, 6, 65536, 1, 1, 1, 0, 0, 0, 8, 10, 8000, 18000 },
+ { 6144, 256, 8, 65536, 3, 3, 2, 0, 0, 0, 24, 15, 6500, 15000 },
+ { 10240, 1024, 12, 65536, 5, 5, 3, 0, 0, 0, 32, 25, 5000, 12000 },
+ { 16384, 2048, 12, 65536, 6, 6, 4, 0, 0, 0, 32, 37, 4000, 10000 },
+ { 32768, 2048, 12, 4096, 7, 7, 5, 4, 150, 380, 32, 50, 3000, 8000 },
+ { 65536, 2048, 16, 4096, 7, 7, 6, 4, 170, 420, 48, 60, 2000, 5000 },
+ { 65536, 2048, 16, 4096, 8, 8, 7, 5, 180, 450, 64, 70, 1000, 2500 },
+ { 65536, 2048, 32, 8192, 9, 9, 8, 6, 190, 475, 64, 75, 500, 1200 },
+ { 65536, 2048, 32, 8192, 9, 9, 9, 6, 200, 500, 96, 80, 200, 500 }
+};
+
+
+static int tight_send_framebuffer_update(VncState *vs, int x, int y,
+ int w, int h);
+
+#ifdef CONFIG_VNC_JPEG
+static const struct {
+ double jpeg_freq_min; /* Don't send JPEG if the freq is bellow */
+ double jpeg_freq_threshold; /* Always send JPEG if the freq is above */
+ int jpeg_idx; /* Allow indexed JPEG */
+ int jpeg_full; /* Allow full color JPEG */
+} tight_jpeg_conf[] = {
+ { 0, 8, 1, 1 },
+ { 0, 8, 1, 1 },
+ { 0, 8, 1, 1 },
+ { 0, 8, 1, 1 },
+ { 0, 10, 1, 1 },
+ { 0.1, 10, 1, 1 },
+ { 0.2, 10, 1, 1 },
+ { 0.3, 12, 0, 0 },
+ { 0.4, 14, 0, 0 },
+ { 0.5, 16, 0, 0 },
+};
+#endif
+
+#ifdef CONFIG_VNC_PNG
+static const struct {
+ int png_zlib_level, png_filters;
+} tight_png_conf[] = {
+ { 0, PNG_NO_FILTERS },
+ { 1, PNG_NO_FILTERS },
+ { 2, PNG_NO_FILTERS },
+ { 3, PNG_NO_FILTERS },
+ { 4, PNG_NO_FILTERS },
+ { 5, PNG_ALL_FILTERS },
+ { 6, PNG_ALL_FILTERS },
+ { 7, PNG_ALL_FILTERS },
+ { 8, PNG_ALL_FILTERS },
+ { 9, PNG_ALL_FILTERS },
+};
+
+static int send_png_rect(VncState *vs, int x, int y, int w, int h,
+ VncPalette *palette);
+
+static bool tight_can_send_png_rect(VncState *vs, int w, int h)
+{
+ if (vs->tight->type != VNC_ENCODING_TIGHT_PNG) {
+ return false;
+ }
+
+ if (surface_bytes_per_pixel(vs->vd->ds) == 1 ||
+ vs->client_pf.bytes_per_pixel == 1) {
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+/*
+ * Code to guess if given rectangle is suitable for smooth image
+ * compression (by applying "gradient" filter or JPEG coder).
+ */
+
+static unsigned int
+tight_detect_smooth_image24(VncState *vs, int w, int h)
+{
+ int off;
+ int x, y, d, dx;
+ unsigned int c;
+ unsigned int stats[256];
+ int pixels = 0;
+ int pix, left[3];
+ unsigned int errors;
+ unsigned char *buf = vs->tight->tight.buffer;
+
+ /*
+ * If client is big-endian, color samples begin from the second
+ * byte (offset 1) of a 32-bit pixel value.
+ */
+ off = vs->client_be;
+
+ memset(stats, 0, sizeof (stats));
+
+ for (y = 0, x = 0; y < h && x < w;) {
+ for (d = 0; d < h - y && d < w - x - VNC_TIGHT_DETECT_SUBROW_WIDTH;
+ d++) {
+ for (c = 0; c < 3; c++) {
+ left[c] = buf[((y+d)*w+x+d)*4+off+c] & 0xFF;
+ }
+ for (dx = 1; dx <= VNC_TIGHT_DETECT_SUBROW_WIDTH; dx++) {
+ for (c = 0; c < 3; c++) {
+ pix = buf[((y+d)*w+x+d+dx)*4+off+c] & 0xFF;
+ stats[abs(pix - left[c])]++;
+ left[c] = pix;
+ }
+ pixels++;
+ }
+ }
+ if (w > h) {
+ x += h;
+ y = 0;
+ } else {
+ x = 0;
+ y += w;
+ }
+ }
+
+ if (pixels == 0) {
+ return 0;
+ }
+
+ /* 95% smooth or more ... */
+ if (stats[0] * 33 / pixels >= 95) {
+ return 0;
+ }
+
+ errors = 0;
+ for (c = 1; c < 8; c++) {
+ errors += stats[c] * (c * c);
+ if (stats[c] == 0 || stats[c] > stats[c-1] * 2) {
+ return 0;
+ }
+ }
+ for (; c < 256; c++) {
+ errors += stats[c] * (c * c);
+ }
+ errors /= (pixels * 3 - stats[0]);
+
+ return errors;
+}
+
+#define DEFINE_DETECT_FUNCTION(bpp) \
+ \
+ static unsigned int \
+ tight_detect_smooth_image##bpp(VncState *vs, int w, int h) { \
+ bool endian; \
+ uint##bpp##_t pix; \
+ int max[3], shift[3]; \
+ int x, y, d, dx; \
+ unsigned int c; \
+ unsigned int stats[256]; \
+ int pixels = 0; \
+ int sample, sum, left[3]; \
+ unsigned int errors; \
+ unsigned char *buf = vs->tight->tight.buffer; \
+ \
+ endian = 0; /* FIXME */ \
+ \
+ \
+ max[0] = vs->client_pf.rmax; \
+ max[1] = vs->client_pf.gmax; \
+ max[2] = vs->client_pf.bmax; \
+ shift[0] = vs->client_pf.rshift; \
+ shift[1] = vs->client_pf.gshift; \
+ shift[2] = vs->client_pf.bshift; \
+ \
+ memset(stats, 0, sizeof(stats)); \
+ \
+ y = 0, x = 0; \
+ while (y < h && x < w) { \
+ for (d = 0; d < h - y && \
+ d < w - x - VNC_TIGHT_DETECT_SUBROW_WIDTH; d++) { \
+ pix = ((uint##bpp##_t *)buf)[(y+d)*w+x+d]; \
+ if (endian) { \
+ pix = bswap##bpp(pix); \
+ } \
+ for (c = 0; c < 3; c++) { \
+ left[c] = (int)(pix >> shift[c] & max[c]); \
+ } \
+ for (dx = 1; dx <= VNC_TIGHT_DETECT_SUBROW_WIDTH; \
+ dx++) { \
+ pix = ((uint##bpp##_t *)buf)[(y+d)*w+x+d+dx]; \
+ if (endian) { \
+ pix = bswap##bpp(pix); \
+ } \
+ sum = 0; \
+ for (c = 0; c < 3; c++) { \
+ sample = (int)(pix >> shift[c] & max[c]); \
+ sum += abs(sample - left[c]); \
+ left[c] = sample; \
+ } \
+ if (sum > 255) { \
+ sum = 255; \
+ } \
+ stats[sum]++; \
+ pixels++; \
+ } \
+ } \
+ if (w > h) { \
+ x += h; \
+ y = 0; \
+ } else { \
+ x = 0; \
+ y += w; \
+ } \
+ } \
+ if (pixels == 0) { \
+ return 0; \
+ } \
+ if ((stats[0] + stats[1]) * 100 / pixels >= 90) { \
+ return 0; \
+ } \
+ \
+ errors = 0; \
+ for (c = 1; c < 8; c++) { \
+ errors += stats[c] * (c * c); \
+ if (stats[c] == 0 || stats[c] > stats[c-1] * 2) { \
+ return 0; \
+ } \
+ } \
+ for (; c < 256; c++) { \
+ errors += stats[c] * (c * c); \
+ } \
+ errors /= (pixels - stats[0]); \
+ \
+ return errors; \
+ }
+
+DEFINE_DETECT_FUNCTION(16)
+DEFINE_DETECT_FUNCTION(32)
+
+static int
+tight_detect_smooth_image(VncState *vs, int w, int h)
+{
+ unsigned int errors;
+ int compression = vs->tight->compression;
+ int quality = vs->tight->quality;
+
+ if (!vs->vd->lossy) {
+ return 0;
+ }
+
+ if (surface_bytes_per_pixel(vs->vd->ds) == 1 ||
+ vs->client_pf.bytes_per_pixel == 1 ||
+ w < VNC_TIGHT_DETECT_MIN_WIDTH || h < VNC_TIGHT_DETECT_MIN_HEIGHT) {
+ return 0;
+ }
+
+ if (vs->tight->quality != (uint8_t)-1) {
+ if (w * h < VNC_TIGHT_JPEG_MIN_RECT_SIZE) {
+ return 0;
+ }
+ } else {
+ if (w * h < tight_conf[compression].gradient_min_rect_size) {
+ return 0;
+ }
+ }
+
+ if (vs->client_pf.bytes_per_pixel == 4) {
+ if (vs->tight->pixel24) {
+ errors = tight_detect_smooth_image24(vs, w, h);
+ if (vs->tight->quality != (uint8_t)-1) {
+ return (errors < tight_conf[quality].jpeg_threshold24);
+ }
+ return (errors < tight_conf[compression].gradient_threshold24);
+ } else {
+ errors = tight_detect_smooth_image32(vs, w, h);
+ }
+ } else {
+ errors = tight_detect_smooth_image16(vs, w, h);
+ }
+ if (quality != (uint8_t)-1) {
+ return (errors < tight_conf[quality].jpeg_threshold);
+ }
+ return (errors < tight_conf[compression].gradient_threshold);
+}
+
+/*
+ * Code to determine how many different colors used in rectangle.
+ */
+#define DEFINE_FILL_PALETTE_FUNCTION(bpp) \
+ \
+ static int \
+ tight_fill_palette##bpp(VncState *vs, int x, int y, \
+ int max, size_t count, \
+ uint32_t *bg, uint32_t *fg, \
+ VncPalette *palette) { \
+ uint##bpp##_t *data; \
+ uint##bpp##_t c0, c1, ci; \
+ int i, n0, n1; \
+ \
+ data = (uint##bpp##_t *)vs->tight->tight.buffer; \
+ \
+ c0 = data[0]; \
+ i = 1; \
+ while (i < count && data[i] == c0) \
+ i++; \
+ if (i >= count) { \
+ *bg = *fg = c0; \
+ return 1; \
+ } \
+ \
+ if (max < 2) { \
+ return 0; \
+ } \
+ \
+ n0 = i; \
+ c1 = data[i]; \
+ n1 = 0; \
+ for (i++; i < count; i++) { \
+ ci = data[i]; \
+ if (ci == c0) { \
+ n0++; \
+ } else if (ci == c1) { \
+ n1++; \
+ } else \
+ break; \
+ } \
+ if (i >= count) { \
+ if (n0 > n1) { \
+ *bg = (uint32_t)c0; \
+ *fg = (uint32_t)c1; \
+ } else { \
+ *bg = (uint32_t)c1; \
+ *fg = (uint32_t)c0; \
+ } \
+ return 2; \
+ } \
+ \
+ if (max == 2) { \
+ return 0; \
+ } \
+ \
+ palette_init(palette, max, bpp); \
+ palette_put(palette, c0); \
+ palette_put(palette, c1); \
+ palette_put(palette, ci); \
+ \
+ for (i++; i < count; i++) { \
+ if (data[i] == ci) { \
+ continue; \
+ } else { \
+ ci = data[i]; \
+ if (!palette_put(palette, (uint32_t)ci)) { \
+ return 0; \
+ } \
+ } \
+ } \
+ \
+ return palette_size(palette); \
+ }
+
+DEFINE_FILL_PALETTE_FUNCTION(8)
+DEFINE_FILL_PALETTE_FUNCTION(16)
+DEFINE_FILL_PALETTE_FUNCTION(32)
+
+static int tight_fill_palette(VncState *vs, int x, int y,
+ size_t count, uint32_t *bg, uint32_t *fg,
+ VncPalette *palette)
+{
+ int max;
+
+ max = count / tight_conf[vs->tight->compression].idx_max_colors_divisor;
+ if (max < 2 &&
+ count >= tight_conf[vs->tight->compression].mono_min_rect_size) {
+ max = 2;
+ }
+ if (max >= 256) {
+ max = 256;
+ }
+
+ switch (vs->client_pf.bytes_per_pixel) {
+ case 4:
+ return tight_fill_palette32(vs, x, y, max, count, bg, fg, palette);
+ case 2:
+ return tight_fill_palette16(vs, x, y, max, count, bg, fg, palette);
+ default:
+ max = 2;
+ return tight_fill_palette8(vs, x, y, max, count, bg, fg, palette);
+ }
+ return 0;
+}
+
+/*
+ * Converting truecolor samples into palette indices.
+ */
+#define DEFINE_IDX_ENCODE_FUNCTION(bpp) \
+ \
+ static void \
+ tight_encode_indexed_rect##bpp(uint8_t *buf, int count, \
+ VncPalette *palette) { \
+ uint##bpp##_t *src; \
+ uint##bpp##_t rgb; \
+ int i, rep; \
+ uint8_t idx; \
+ \
+ src = (uint##bpp##_t *) buf; \
+ \
+ for (i = 0; i < count; ) { \
+ \
+ rgb = *src++; \
+ i++; \
+ rep = 0; \
+ while (i < count && *src == rgb) { \
+ rep++, src++, i++; \
+ } \
+ idx = palette_idx(palette, rgb); \
+ /* \
+ * Should never happen, but don't break everything \
+ * if it does, use the first color instead \
+ */ \
+ if (idx == (uint8_t)-1) { \
+ idx = 0; \
+ } \
+ while (rep >= 0) { \
+ *buf++ = idx; \
+ rep--; \
+ } \
+ } \
+ }
+
+DEFINE_IDX_ENCODE_FUNCTION(16)
+DEFINE_IDX_ENCODE_FUNCTION(32)
+
+#define DEFINE_MONO_ENCODE_FUNCTION(bpp) \
+ \
+ static void \
+ tight_encode_mono_rect##bpp(uint8_t *buf, int w, int h, \
+ uint##bpp##_t bg, uint##bpp##_t fg) { \
+ uint##bpp##_t *ptr; \
+ unsigned int value, mask; \
+ int aligned_width; \
+ int x, y, bg_bits; \
+ \
+ ptr = (uint##bpp##_t *) buf; \
+ aligned_width = w - w % 8; \
+ \
+ for (y = 0; y < h; y++) { \
+ for (x = 0; x < aligned_width; x += 8) { \
+ for (bg_bits = 0; bg_bits < 8; bg_bits++) { \
+ if (*ptr++ != bg) { \
+ break; \
+ } \
+ } \
+ if (bg_bits == 8) { \
+ *buf++ = 0; \
+ continue; \
+ } \
+ mask = 0x80 >> bg_bits; \
+ value = mask; \
+ for (bg_bits++; bg_bits < 8; bg_bits++) { \
+ mask >>= 1; \
+ if (*ptr++ != bg) { \
+ value |= mask; \
+ } \
+ } \
+ *buf++ = (uint8_t)value; \
+ } \
+ \
+ mask = 0x80; \
+ value = 0; \
+ if (x >= w) { \
+ continue; \
+ } \
+ \
+ for (; x < w; x++) { \
+ if (*ptr++ != bg) { \
+ value |= mask; \
+ } \
+ mask >>= 1; \
+ } \
+ *buf++ = (uint8_t)value; \
+ } \
+ }
+
+DEFINE_MONO_ENCODE_FUNCTION(8)
+DEFINE_MONO_ENCODE_FUNCTION(16)
+DEFINE_MONO_ENCODE_FUNCTION(32)
+
+/*
+ * ``Gradient'' filter for 24-bit color samples.
+ * Should be called only when redMax, greenMax and blueMax are 255.
+ * Color components assumed to be byte-aligned.
+ */
+
+static void
+tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h)
+{
+ uint32_t *buf32;
+ uint32_t pix32;
+ int shift[3];
+ int *prev;
+ int here[3], upper[3], left[3], upperleft[3];
+ int prediction;
+ int x, y, c;
+
+ buf32 = (uint32_t *)buf;
+ memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int));
+
+ if (1 /* FIXME */) {
+ shift[0] = vs->client_pf.rshift;
+ shift[1] = vs->client_pf.gshift;
+ shift[2] = vs->client_pf.bshift;
+ } else {
+ shift[0] = 24 - vs->client_pf.rshift;
+ shift[1] = 24 - vs->client_pf.gshift;
+ shift[2] = 24 - vs->client_pf.bshift;
+ }
+
+ for (y = 0; y < h; y++) {
+ for (c = 0; c < 3; c++) {
+ upper[c] = 0;
+ here[c] = 0;
+ }
+ prev = (int *)vs->tight->gradient.buffer;
+ for (x = 0; x < w; x++) {
+ pix32 = *buf32++;
+ for (c = 0; c < 3; c++) {
+ upperleft[c] = upper[c];
+ left[c] = here[c];
+ upper[c] = *prev;
+ here[c] = (int)(pix32 >> shift[c] & 0xFF);
+ *prev++ = here[c];
+
+ prediction = left[c] + upper[c] - upperleft[c];
+ if (prediction < 0) {
+ prediction = 0;
+ } else if (prediction > 0xFF) {
+ prediction = 0xFF;
+ }
+ *buf++ = (char)(here[c] - prediction);
+ }
+ }
+ }
+}
+
+
+/*
+ * ``Gradient'' filter for other color depths.
+ */
+
+#define DEFINE_GRADIENT_FILTER_FUNCTION(bpp) \
+ \
+ static void \
+ tight_filter_gradient##bpp(VncState *vs, uint##bpp##_t *buf, \
+ int w, int h) { \
+ uint##bpp##_t pix, diff; \
+ bool endian; \
+ int *prev; \
+ int max[3], shift[3]; \
+ int here[3], upper[3], left[3], upperleft[3]; \
+ int prediction; \
+ int x, y, c; \
+ \
+ memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int)); \
+ \
+ endian = 0; /* FIXME */ \
+ \
+ max[0] = vs->client_pf.rmax; \
+ max[1] = vs->client_pf.gmax; \
+ max[2] = vs->client_pf.bmax; \
+ shift[0] = vs->client_pf.rshift; \
+ shift[1] = vs->client_pf.gshift; \
+ shift[2] = vs->client_pf.bshift; \
+ \
+ for (y = 0; y < h; y++) { \
+ for (c = 0; c < 3; c++) { \
+ upper[c] = 0; \
+ here[c] = 0; \
+ } \
+ prev = (int *)vs->tight->gradient.buffer; \
+ for (x = 0; x < w; x++) { \
+ pix = *buf; \
+ if (endian) { \
+ pix = bswap##bpp(pix); \
+ } \
+ diff = 0; \
+ for (c = 0; c < 3; c++) { \
+ upperleft[c] = upper[c]; \
+ left[c] = here[c]; \
+ upper[c] = *prev; \
+ here[c] = (int)(pix >> shift[c] & max[c]); \
+ *prev++ = here[c]; \
+ \
+ prediction = left[c] + upper[c] - upperleft[c]; \
+ if (prediction < 0) { \
+ prediction = 0; \
+ } else if (prediction > max[c]) { \
+ prediction = max[c]; \
+ } \
+ diff |= ((here[c] - prediction) & max[c]) \
+ << shift[c]; \
+ } \
+ if (endian) { \
+ diff = bswap##bpp(diff); \
+ } \
+ *buf++ = diff; \
+ } \
+ } \
+ }
+
+DEFINE_GRADIENT_FILTER_FUNCTION(16)
+DEFINE_GRADIENT_FILTER_FUNCTION(32)
+
+/*
+ * Check if a rectangle is all of the same color. If needSameColor is
+ * set to non-zero, then also check that its color equals to the
+ * *colorPtr value. The result is 1 if the test is successful, and in
+ * that case new color will be stored in *colorPtr.
+ */
+
+static bool
+check_solid_tile32(VncState *vs, int x, int y, int w, int h,
+ uint32_t *color, bool samecolor)
+{
+ VncDisplay *vd = vs->vd;
+ uint32_t *fbptr;
+ uint32_t c;
+ int dx, dy;
+
+ fbptr = vnc_server_fb_ptr(vd, x, y);
+
+ c = *fbptr;
+ if (samecolor && (uint32_t)c != *color) {
+ return false;
+ }
+
+ for (dy = 0; dy < h; dy++) {
+ for (dx = 0; dx < w; dx++) {
+ if (c != fbptr[dx]) {
+ return false;
+ }
+ }
+ fbptr = (uint32_t *)
+ ((uint8_t *)fbptr + vnc_server_fb_stride(vd));
+ }
+
+ *color = (uint32_t)c;
+ return true;
+}
+
+static bool check_solid_tile(VncState *vs, int x, int y, int w, int h,
+ uint32_t* color, bool samecolor)
+{
+ QEMU_BUILD_BUG_ON(VNC_SERVER_FB_BYTES != 4);
+ return check_solid_tile32(vs, x, y, w, h, color, samecolor);
+}
+
+static void find_best_solid_area(VncState *vs, int x, int y, int w, int h,
+ uint32_t color, int *w_ptr, int *h_ptr)
+{
+ int dx, dy, dw, dh;
+ int w_prev;
+ int w_best = 0, h_best = 0;
+
+ w_prev = w;
+
+ for (dy = y; dy < y + h; dy += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) {
+
+ dh = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, y + h - dy);
+ dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, w_prev);
+
+ if (!check_solid_tile(vs, x, dy, dw, dh, &color, true)) {
+ break;
+ }
+
+ for (dx = x + dw; dx < x + w_prev;) {
+ dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, x + w_prev - dx);
+
+ if (!check_solid_tile(vs, dx, dy, dw, dh, &color, true)) {
+ break;
+ }
+ dx += dw;
+ }
+
+ w_prev = dx - x;
+ if (w_prev * (dy + dh - y) > w_best * h_best) {
+ w_best = w_prev;
+ h_best = dy + dh - y;
+ }
+ }
+
+ *w_ptr = w_best;
+ *h_ptr = h_best;
+}
+
+static void extend_solid_area(VncState *vs, int x, int y, int w, int h,
+ uint32_t color, int *x_ptr, int *y_ptr,
+ int *w_ptr, int *h_ptr)
+{
+ int cx, cy;
+
+ /* Try to extend the area upwards. */
+ for ( cy = *y_ptr - 1;
+ cy >= y && check_solid_tile(vs, *x_ptr, cy, *w_ptr, 1, &color, true);
+ cy-- );
+ *h_ptr += *y_ptr - (cy + 1);
+ *y_ptr = cy + 1;
+
+ /* ... downwards. */
+ for ( cy = *y_ptr + *h_ptr;
+ cy < y + h &&
+ check_solid_tile(vs, *x_ptr, cy, *w_ptr, 1, &color, true);
+ cy++ );
+ *h_ptr += cy - (*y_ptr + *h_ptr);
+
+ /* ... to the left. */
+ for ( cx = *x_ptr - 1;
+ cx >= x && check_solid_tile(vs, cx, *y_ptr, 1, *h_ptr, &color, true);
+ cx-- );
+ *w_ptr += *x_ptr - (cx + 1);
+ *x_ptr = cx + 1;
+
+ /* ... to the right. */
+ for ( cx = *x_ptr + *w_ptr;
+ cx < x + w &&
+ check_solid_tile(vs, cx, *y_ptr, 1, *h_ptr, &color, true);
+ cx++ );
+ *w_ptr += cx - (*x_ptr + *w_ptr);
+}
+
+static int tight_init_stream(VncState *vs, int stream_id,
+ int level, int strategy)
+{
+ z_streamp zstream = &vs->tight->stream[stream_id];
+
+ if (zstream->opaque == NULL) {
+ int err;
+
+ VNC_DEBUG("VNC: TIGHT: initializing zlib stream %d\n", stream_id);
+ VNC_DEBUG("VNC: TIGHT: opaque = %p | vs = %p\n", zstream->opaque, vs);
+ zstream->zalloc = vnc_zlib_zalloc;
+ zstream->zfree = vnc_zlib_zfree;
+
+ err = deflateInit2(zstream, level, Z_DEFLATED, MAX_WBITS,
+ MAX_MEM_LEVEL, strategy);
+
+ if (err != Z_OK) {
+ fprintf(stderr, "VNC: error initializing zlib\n");
+ return -1;
+ }
+
+ vs->tight->levels[stream_id] = level;
+ zstream->opaque = vs;
+ }
+
+ if (vs->tight->levels[stream_id] != level) {
+ if (deflateParams(zstream, level, strategy) != Z_OK) {
+ return -1;
+ }
+ vs->tight->levels[stream_id] = level;
+ }
+ return 0;
+}
+
+static void tight_send_compact_size(VncState *vs, size_t len)
+{
+ int lpc = 0;
+ int bytes = 0;
+ char buf[3] = {0, 0, 0};
+
+ buf[bytes++] = len & 0x7F;
+ if (len > 0x7F) {
+ buf[bytes-1] |= 0x80;
+ buf[bytes++] = (len >> 7) & 0x7F;
+ if (len > 0x3FFF) {
+ buf[bytes-1] |= 0x80;
+ buf[bytes++] = (len >> 14) & 0xFF;
+ }
+ }
+ for (lpc = 0; lpc < bytes; lpc++) {
+ vnc_write_u8(vs, buf[lpc]);
+ }
+}
+
+static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
+ int level, int strategy)
+{
+ z_streamp zstream = &vs->tight->stream[stream_id];
+ int previous_out;
+
+ if (bytes < VNC_TIGHT_MIN_TO_COMPRESS) {
+ vnc_write(vs, vs->tight->tight.buffer, vs->tight->tight.offset);
+ return bytes;
+ }
+
+ if (tight_init_stream(vs, stream_id, level, strategy)) {
+ return -1;
+ }
+
+ /* reserve memory in output buffer */
+ buffer_reserve(&vs->tight->zlib, bytes + 64);
+
+ /* set pointers */
+ zstream->next_in = vs->tight->tight.buffer;
+ zstream->avail_in = vs->tight->tight.offset;
+ zstream->next_out = vs->tight->zlib.buffer + vs->tight->zlib.offset;
+ zstream->avail_out = vs->tight->zlib.capacity - vs->tight->zlib.offset;
+ previous_out = zstream->avail_out;
+ zstream->data_type = Z_BINARY;
+
+ /* start encoding */
+ if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) {
+ fprintf(stderr, "VNC: error during tight compression\n");
+ return -1;
+ }
+
+ vs->tight->zlib.offset = vs->tight->zlib.capacity - zstream->avail_out;
+ /* ...how much data has actually been produced by deflate() */
+ bytes = previous_out - zstream->avail_out;
+
+ tight_send_compact_size(vs, bytes);
+ vnc_write(vs, vs->tight->zlib.buffer, bytes);
+
+ buffer_reset(&vs->tight->zlib);
+
+ return bytes;
+}
+
+/*
+ * Subencoding implementations.
+ */
+static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret)
+{
+ uint8_t *buf8;
+ uint32_t pix;
+ int rshift, gshift, bshift;
+
+ buf8 = buf;
+
+ if (1 /* FIXME */) {
+ rshift = vs->client_pf.rshift;
+ gshift = vs->client_pf.gshift;
+ bshift = vs->client_pf.bshift;
+ } else {
+ rshift = 24 - vs->client_pf.rshift;
+ gshift = 24 - vs->client_pf.gshift;
+ bshift = 24 - vs->client_pf.bshift;
+ }
+
+ if (ret) {
+ *ret = count * 3;
+ }
+
+ while (count--) {
+ pix = ldl_he_p(buf8);
+ *buf++ = (char)(pix >> rshift);
+ *buf++ = (char)(pix >> gshift);
+ *buf++ = (char)(pix >> bshift);
+ buf8 += 4;
+ }
+}
+
+static int send_full_color_rect(VncState *vs, int x, int y, int w, int h)
+{
+ int stream = 0;
+ ssize_t bytes;
+
+#ifdef CONFIG_VNC_PNG
+ if (tight_can_send_png_rect(vs, w, h)) {
+ return send_png_rect(vs, x, y, w, h, NULL);
+ }
+#endif
+
+ vnc_write_u8(vs, stream << 4); /* no flushing, no filter */
+
+ if (vs->tight->pixel24) {
+ tight_pack24(vs, vs->tight->tight.buffer, w * h,
+ &vs->tight->tight.offset);
+ bytes = 3;
+ } else {
+ bytes = vs->client_pf.bytes_per_pixel;
+ }
+
+ bytes = tight_compress_data(vs, stream, w * h * bytes,
+ tight_conf[vs->tight->compression].raw_zlib_level,
+ Z_DEFAULT_STRATEGY);
+
+ return (bytes >= 0);
+}
+
+static int send_solid_rect(VncState *vs)
+{
+ size_t bytes;
+
+ vnc_write_u8(vs, VNC_TIGHT_FILL << 4); /* no flushing, no filter */
+
+ if (vs->tight->pixel24) {
+ tight_pack24(vs, vs->tight->tight.buffer, 1, &vs->tight->tight.offset);
+ bytes = 3;
+ } else {
+ bytes = vs->client_pf.bytes_per_pixel;
+ }
+
+ vnc_write(vs, vs->tight->tight.buffer, bytes);
+ return 1;
+}
+
+static int send_mono_rect(VncState *vs, int x, int y,
+ int w, int h, uint32_t bg, uint32_t fg)
+{
+ ssize_t bytes;
+ int stream = 1;
+ int level = tight_conf[vs->tight->compression].mono_zlib_level;
+
+#ifdef CONFIG_VNC_PNG
+ if (tight_can_send_png_rect(vs, w, h)) {
+ int ret;
+ int bpp = vs->client_pf.bytes_per_pixel * 8;
+ VncPalette *palette = palette_new(2, bpp);
+
+ palette_put(palette, bg);
+ palette_put(palette, fg);
+ ret = send_png_rect(vs, x, y, w, h, palette);
+ palette_destroy(palette);
+ return ret;
+ }
+#endif
+
+ bytes = DIV_ROUND_UP(w, 8) * h;
+
+ vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
+ vnc_write_u8(vs, VNC_TIGHT_FILTER_PALETTE);
+ vnc_write_u8(vs, 1);
+
+ switch (vs->client_pf.bytes_per_pixel) {
+ case 4:
+ {
+ uint32_t buf[2] = {bg, fg};
+ size_t ret = sizeof (buf);
+
+ if (vs->tight->pixel24) {
+ tight_pack24(vs, (unsigned char*)buf, 2, &ret);
+ }
+ vnc_write(vs, buf, ret);
+
+ tight_encode_mono_rect32(vs->tight->tight.buffer, w, h, bg, fg);
+ break;
+ }
+ case 2:
+ vnc_write(vs, &bg, 2);
+ vnc_write(vs, &fg, 2);
+ tight_encode_mono_rect16(vs->tight->tight.buffer, w, h, bg, fg);
+ break;
+ default:
+ vnc_write_u8(vs, bg);
+ vnc_write_u8(vs, fg);
+ tight_encode_mono_rect8(vs->tight->tight.buffer, w, h, bg, fg);
+ break;
+ }
+ vs->tight->tight.offset = bytes;
+
+ bytes = tight_compress_data(vs, stream, bytes, level, Z_DEFAULT_STRATEGY);
+ return (bytes >= 0);
+}
+
+struct palette_cb_priv {
+ VncState *vs;
+ uint8_t *header;
+#ifdef CONFIG_VNC_PNG
+ png_colorp png_palette;
+#endif
+};
+
+static void write_palette(int idx, uint32_t color, void *opaque)
+{
+ struct palette_cb_priv *priv = opaque;
+ VncState *vs = priv->vs;
+ uint32_t bytes = vs->client_pf.bytes_per_pixel;
+
+ if (bytes == 4) {
+ ((uint32_t*)priv->header)[idx] = color;
+ } else {
+ ((uint16_t*)priv->header)[idx] = color;
+ }
+}
+
+static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h)
+{
+ int stream = 3;
+ int level = tight_conf[vs->tight->compression].gradient_zlib_level;
+ ssize_t bytes;
+
+ if (vs->client_pf.bytes_per_pixel == 1) {
+ return send_full_color_rect(vs, x, y, w, h);
+ }
+
+ vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
+ vnc_write_u8(vs, VNC_TIGHT_FILTER_GRADIENT);
+
+ buffer_reserve(&vs->tight->gradient, w * 3 * sizeof(int));
+
+ if (vs->tight->pixel24) {
+ tight_filter_gradient24(vs, vs->tight->tight.buffer, w, h);
+ bytes = 3;
+ } else if (vs->client_pf.bytes_per_pixel == 4) {
+ tight_filter_gradient32(vs, (uint32_t *)vs->tight->tight.buffer, w, h);
+ bytes = 4;
+ } else {
+ tight_filter_gradient16(vs, (uint16_t *)vs->tight->tight.buffer, w, h);
+ bytes = 2;
+ }
+
+ buffer_reset(&vs->tight->gradient);
+
+ bytes = w * h * bytes;
+ vs->tight->tight.offset = bytes;
+
+ bytes = tight_compress_data(vs, stream, bytes,
+ level, Z_FILTERED);
+ return (bytes >= 0);
+}
+
+static int send_palette_rect(VncState *vs, int x, int y,
+ int w, int h, VncPalette *palette)
+{
+ int stream = 2;
+ int level = tight_conf[vs->tight->compression].idx_zlib_level;
+ int colors;
+ ssize_t bytes;
+
+#ifdef CONFIG_VNC_PNG
+ if (tight_can_send_png_rect(vs, w, h)) {
+ return send_png_rect(vs, x, y, w, h, palette);
+ }
+#endif
+
+ colors = palette_size(palette);
+
+ vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4);
+ vnc_write_u8(vs, VNC_TIGHT_FILTER_PALETTE);
+ vnc_write_u8(vs, colors - 1);
+
+ switch (vs->client_pf.bytes_per_pixel) {
+ case 4:
+ {
+ size_t old_offset, offset;
+ uint32_t header[palette_size(palette)];
+ struct palette_cb_priv priv = { vs, (uint8_t *)header };
+
+ old_offset = vs->output.offset;
+ palette_iter(palette, write_palette, &priv);
+ vnc_write(vs, header, sizeof(header));
+
+ if (vs->tight->pixel24) {
+ tight_pack24(vs, vs->output.buffer + old_offset, colors, &offset);
+ vs->output.offset = old_offset + offset;
+ }
+
+ tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h, palette);
+ break;
+ }
+ case 2:
+ {
+ uint16_t header[palette_size(palette)];
+ struct palette_cb_priv priv = { vs, (uint8_t *)header };
+
+ palette_iter(palette, write_palette, &priv);
+ vnc_write(vs, header, sizeof(header));
+ tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h, palette);
+ break;
+ }
+ default:
+ return -1; /* No palette for 8bits colors */
+ }
+ bytes = w * h;
+ vs->tight->tight.offset = bytes;
+
+ bytes = tight_compress_data(vs, stream, bytes,
+ level, Z_DEFAULT_STRATEGY);
+ return (bytes >= 0);
+}
+
+/*
+ * JPEG compression stuff.
+ */
+#ifdef CONFIG_VNC_JPEG
+/*
+ * Destination manager implementation for JPEG library.
+ */
+
+/* This is called once per encoding */
+static void jpeg_init_destination(j_compress_ptr cinfo)
+{
+ VncState *vs = cinfo->client_data;
+ Buffer *buffer = &vs->tight->jpeg;
+
+ cinfo->dest->next_output_byte = (JOCTET *)buffer->buffer + buffer->offset;
+ cinfo->dest->free_in_buffer = (size_t)(buffer->capacity - buffer->offset);
+}
+
+/* This is called when we ran out of buffer (shouldn't happen!) */
+static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo)
+{
+ VncState *vs = cinfo->client_data;
+ Buffer *buffer = &vs->tight->jpeg;
+
+ buffer->offset = buffer->capacity;
+ buffer_reserve(buffer, 2048);
+ jpeg_init_destination(cinfo);
+ return TRUE;
+}
+
+/* This is called when we are done processing data */
+static void jpeg_term_destination(j_compress_ptr cinfo)
+{
+ VncState *vs = cinfo->client_data;
+ Buffer *buffer = &vs->tight->jpeg;
+
+ buffer->offset = buffer->capacity - cinfo->dest->free_in_buffer;
+}
+
+static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ struct jpeg_destination_mgr manager;
+ pixman_image_t *linebuf;
+ JSAMPROW row[1];
+ uint8_t *buf;
+ int dy;
+
+ if (surface_bytes_per_pixel(vs->vd->ds) == 1) {
+ return send_full_color_rect(vs, x, y, w, h);
+ }
+
+ buffer_reserve(&vs->tight->jpeg, 2048);
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+
+ cinfo.client_data = vs;
+ cinfo.image_width = w;
+ cinfo.image_height = h;
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, true);
+
+ manager.init_destination = jpeg_init_destination;
+ manager.empty_output_buffer = jpeg_empty_output_buffer;
+ manager.term_destination = jpeg_term_destination;
+ cinfo.dest = &manager;
+
+ jpeg_start_compress(&cinfo, true);
+
+ linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w);
+ buf = (uint8_t *)pixman_image_get_data(linebuf);
+ row[0] = buf;
+ for (dy = 0; dy < h; dy++) {
+ qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy);
+ jpeg_write_scanlines(&cinfo, row, 1);
+ }
+ qemu_pixman_image_unref(linebuf);
+
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ vnc_write_u8(vs, VNC_TIGHT_JPEG << 4);
+
+ tight_send_compact_size(vs, vs->tight->jpeg.offset);
+ vnc_write(vs, vs->tight->jpeg.buffer, vs->tight->jpeg.offset);
+ buffer_reset(&vs->tight->jpeg);
+
+ return 1;
+}
+#endif /* CONFIG_VNC_JPEG */
+
+/*
+ * PNG compression stuff.
+ */
+#ifdef CONFIG_VNC_PNG
+static void write_png_palette(int idx, uint32_t pix, void *opaque)
+{
+ struct palette_cb_priv *priv = opaque;
+ VncState *vs = priv->vs;
+ png_colorp color = &priv->png_palette[idx];
+
+ if (vs->tight->pixel24)
+ {
+ color->red = (pix >> vs->client_pf.rshift) & vs->client_pf.rmax;
+ color->green = (pix >> vs->client_pf.gshift) & vs->client_pf.gmax;
+ color->blue = (pix >> vs->client_pf.bshift) & vs->client_pf.bmax;
+ }
+ else
+ {
+ int red, green, blue;
+
+ red = (pix >> vs->client_pf.rshift) & vs->client_pf.rmax;
+ green = (pix >> vs->client_pf.gshift) & vs->client_pf.gmax;
+ blue = (pix >> vs->client_pf.bshift) & vs->client_pf.bmax;
+ color->red = ((red * 255 + vs->client_pf.rmax / 2) /
+ vs->client_pf.rmax);
+ color->green = ((green * 255 + vs->client_pf.gmax / 2) /
+ vs->client_pf.gmax);
+ color->blue = ((blue * 255 + vs->client_pf.bmax / 2) /
+ vs->client_pf.bmax);
+ }
+}
+
+static void png_write_data(png_structp png_ptr, png_bytep data,
+ png_size_t length)
+{
+ VncState *vs = png_get_io_ptr(png_ptr);
+
+ buffer_reserve(&vs->tight->png, vs->tight->png.offset + length);
+ memcpy(vs->tight->png.buffer + vs->tight->png.offset, data, length);
+
+ vs->tight->png.offset += length;
+}
+
+static void png_flush_data(png_structp png_ptr)
+{
+}
+
+static void *vnc_png_malloc(png_structp png_ptr, png_size_t size)
+{
+ return g_malloc(size);
+}
+
+static void vnc_png_free(png_structp png_ptr, png_voidp ptr)
+{
+ g_free(ptr);
+}
+
+static int send_png_rect(VncState *vs, int x, int y, int w, int h,
+ VncPalette *palette)
+{
+ png_byte color_type;
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_colorp png_palette = NULL;
+ pixman_image_t *linebuf;
+ int level = tight_png_conf[vs->tight->compression].png_zlib_level;
+ int filters = tight_png_conf[vs->tight->compression].png_filters;
+ uint8_t *buf;
+ int dy;
+
+ png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL,
+ NULL, vnc_png_malloc, vnc_png_free);
+
+ if (png_ptr == NULL)
+ return -1;
+
+ info_ptr = png_create_info_struct(png_ptr);
+
+ if (info_ptr == NULL) {
+ png_destroy_write_struct(&png_ptr, NULL);
+ return -1;
+ }
+
+ png_set_write_fn(png_ptr, (void *) vs, png_write_data, png_flush_data);
+ png_set_compression_level(png_ptr, level);
+ png_set_filter(png_ptr, PNG_FILTER_TYPE_DEFAULT, filters);
+
+ if (palette) {
+ color_type = PNG_COLOR_TYPE_PALETTE;
+ } else {
+ color_type = PNG_COLOR_TYPE_RGB;
+ }
+
+ png_set_IHDR(png_ptr, info_ptr, w, h,
+ 8, color_type, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ struct palette_cb_priv priv;
+
+ png_palette = png_malloc(png_ptr, sizeof(*png_palette) *
+ palette_size(palette));
+
+ priv.vs = vs;
+ priv.png_palette = png_palette;
+ palette_iter(palette, write_png_palette, &priv);
+
+ png_set_PLTE(png_ptr, info_ptr, png_palette, palette_size(palette));
+
+ if (vs->client_pf.bytes_per_pixel == 4) {
+ tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h,
+ palette);
+ } else {
+ tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h,
+ palette);
+ }
+ }
+
+ png_write_info(png_ptr, info_ptr);
+
+ buffer_reserve(&vs->tight->png, 2048);
+ linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w);
+ buf = (uint8_t *)pixman_image_get_data(linebuf);
+ for (dy = 0; dy < h; dy++)
+ {
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ memcpy(buf, vs->tight->tight.buffer + (dy * w), w);
+ } else {
+ qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy);
+ }
+ png_write_row(png_ptr, buf);
+ }
+ qemu_pixman_image_unref(linebuf);
+
+ png_write_end(png_ptr, NULL);
+
+ if (color_type == PNG_COLOR_TYPE_PALETTE) {
+ png_free(png_ptr, png_palette);
+ }
+
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ vnc_write_u8(vs, VNC_TIGHT_PNG << 4);
+
+ tight_send_compact_size(vs, vs->tight->png.offset);
+ vnc_write(vs, vs->tight->png.buffer, vs->tight->png.offset);
+ buffer_reset(&vs->tight->png);
+ return 1;
+}
+#endif /* CONFIG_VNC_PNG */
+
+static void vnc_tight_start(VncState *vs)
+{
+ buffer_reset(&vs->tight->tight);
+
+ // make the output buffer be the zlib buffer, so we can compress it later
+ vs->tight->tmp = vs->output;
+ vs->output = vs->tight->tight;
+}
+
+static void vnc_tight_stop(VncState *vs)
+{
+ // switch back to normal output/zlib buffers
+ vs->tight->tight = vs->output;
+ vs->output = vs->tight->tmp;
+}
+
+static int send_sub_rect_nojpeg(VncState *vs, int x, int y, int w, int h,
+ int bg, int fg, int colors, VncPalette *palette)
+{
+ int ret;
+
+ if (colors == 0) {
+ if (tight_detect_smooth_image(vs, w, h)) {
+ ret = send_gradient_rect(vs, x, y, w, h);
+ } else {
+ ret = send_full_color_rect(vs, x, y, w, h);
+ }
+ } else if (colors == 1) {
+ ret = send_solid_rect(vs);
+ } else if (colors == 2) {
+ ret = send_mono_rect(vs, x, y, w, h, bg, fg);
+ } else if (colors <= 256) {
+ ret = send_palette_rect(vs, x, y, w, h, palette);
+ } else {
+ ret = 0;
+ }
+ return ret;
+}
+
+#ifdef CONFIG_VNC_JPEG
+static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h,
+ int bg, int fg, int colors,
+ VncPalette *palette, bool force)
+{
+ int ret;
+
+ if (colors == 0) {
+ if (force || (tight_jpeg_conf[vs->tight->quality].jpeg_full &&
+ tight_detect_smooth_image(vs, w, h))) {
+ int quality = tight_conf[vs->tight->quality].jpeg_quality;
+
+ ret = send_jpeg_rect(vs, x, y, w, h, quality);
+ } else {
+ ret = send_full_color_rect(vs, x, y, w, h);
+ }
+ } else if (colors == 1) {
+ ret = send_solid_rect(vs);
+ } else if (colors == 2) {
+ ret = send_mono_rect(vs, x, y, w, h, bg, fg);
+ } else if (colors <= 256) {
+ if (force || (colors > 96 &&
+ tight_jpeg_conf[vs->tight->quality].jpeg_idx &&
+ tight_detect_smooth_image(vs, w, h))) {
+ int quality = tight_conf[vs->tight->quality].jpeg_quality;
+
+ ret = send_jpeg_rect(vs, x, y, w, h, quality);
+ } else {
+ ret = send_palette_rect(vs, x, y, w, h, palette);
+ }
+ } else {
+ ret = 0;
+ }
+ return ret;
+}
+#endif
+
+static __thread VncPalette *color_count_palette;
+static __thread Notifier vnc_tight_cleanup_notifier;
+
+static void vnc_tight_cleanup(Notifier *n, void *value)
+{
+ g_free(color_count_palette);
+ color_count_palette = NULL;
+}
+
+static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
+{
+ uint32_t bg = 0, fg = 0;
+ int colors;
+ int ret = 0;
+#ifdef CONFIG_VNC_JPEG
+ bool force_jpeg = false;
+ bool allow_jpeg = true;
+#endif
+
+ if (!color_count_palette) {
+ color_count_palette = g_malloc(sizeof(VncPalette));
+ vnc_tight_cleanup_notifier.notify = vnc_tight_cleanup;
+ qemu_thread_atexit_add(&vnc_tight_cleanup_notifier);
+ }
+
+ vnc_framebuffer_update(vs, x, y, w, h, vs->tight->type);
+
+ vnc_tight_start(vs);
+ vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+ vnc_tight_stop(vs);
+
+#ifdef CONFIG_VNC_JPEG
+ if (!vs->vd->non_adaptive && vs->tight->quality != (uint8_t)-1) {
+ double freq = vnc_update_freq(vs, x, y, w, h);
+
+ if (freq < tight_jpeg_conf[vs->tight->quality].jpeg_freq_min) {
+ allow_jpeg = false;
+ }
+ if (freq >= tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) {
+ force_jpeg = true;
+ vnc_sent_lossy_rect(vs, x, y, w, h);
+ }
+ }
+#endif
+
+ colors = tight_fill_palette(vs, x, y, w * h, &bg, &fg, color_count_palette);
+
+#ifdef CONFIG_VNC_JPEG
+ if (allow_jpeg && vs->tight->quality != (uint8_t)-1) {
+ ret = send_sub_rect_jpeg(vs, x, y, w, h, bg, fg, colors,
+ color_count_palette, force_jpeg);
+ } else {
+ ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors,
+ color_count_palette);
+ }
+#else
+ ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors,
+ color_count_palette);
+#endif
+
+ return ret;
+}
+
+static int send_sub_rect_solid(VncState *vs, int x, int y, int w, int h)
+{
+ vnc_framebuffer_update(vs, x, y, w, h, vs->tight->type);
+
+ vnc_tight_start(vs);
+ vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+ vnc_tight_stop(vs);
+
+ return send_solid_rect(vs);
+}
+
+static int send_rect_simple(VncState *vs, int x, int y, int w, int h,
+ bool split)
+{
+ int max_size, max_width;
+ int max_sub_width, max_sub_height;
+ int dx, dy;
+ int rw, rh;
+ int n = 0;
+
+ max_size = tight_conf[vs->tight->compression].max_rect_size;
+ max_width = tight_conf[vs->tight->compression].max_rect_width;
+
+ if (split && (w > max_width || w * h > max_size)) {
+ max_sub_width = (w > max_width) ? max_width : w;
+ max_sub_height = max_size / max_sub_width;
+
+ for (dy = 0; dy < h; dy += max_sub_height) {
+ for (dx = 0; dx < w; dx += max_width) {
+ rw = MIN(max_sub_width, w - dx);
+ rh = MIN(max_sub_height, h - dy);
+ n += send_sub_rect(vs, x+dx, y+dy, rw, rh);
+ }
+ }
+ } else {
+ n += send_sub_rect(vs, x, y, w, h);
+ }
+
+ return n;
+}
+
+static int find_large_solid_color_rect(VncState *vs, int x, int y,
+ int w, int h, int max_rows)
+{
+ int dx, dy, dw, dh;
+ int n = 0;
+
+ /* Try to find large solid-color areas and send them separately. */
+
+ for (dy = y; dy < y + h; dy += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) {
+
+ /* If a rectangle becomes too large, send its upper part now. */
+
+ if (dy - y >= max_rows) {
+ n += send_rect_simple(vs, x, y, w, max_rows, true);
+ y += max_rows;
+ h -= max_rows;
+ }
+
+ dh = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, (y + h - dy));
+
+ for (dx = x; dx < x + w; dx += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) {
+ uint32_t color_value;
+ int x_best, y_best, w_best, h_best;
+
+ dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, (x + w - dx));
+
+ if (!check_solid_tile(vs, dx, dy, dw, dh, &color_value, false)) {
+ continue ;
+ }
+
+ /* Get dimensions of solid-color area. */
+
+ find_best_solid_area(vs, dx, dy, w - (dx - x), h - (dy - y),
+ color_value, &w_best, &h_best);
+
+ /* Make sure a solid rectangle is large enough
+ (or the whole rectangle is of the same color). */
+
+ if (w_best * h_best != w * h &&
+ w_best * h_best < VNC_TIGHT_MIN_SOLID_SUBRECT_SIZE) {
+ continue;
+ }
+
+ /* Try to extend solid rectangle to maximum size. */
+
+ x_best = dx; y_best = dy;
+ extend_solid_area(vs, x, y, w, h, color_value,
+ &x_best, &y_best, &w_best, &h_best);
+
+ /* Send rectangles at top and left to solid-color area. */
+
+ if (y_best != y) {
+ n += send_rect_simple(vs, x, y, w, y_best-y, true);
+ }
+ if (x_best != x) {
+ n += tight_send_framebuffer_update(vs, x, y_best,
+ x_best-x, h_best);
+ }
+
+ /* Send solid-color rectangle. */
+ n += send_sub_rect_solid(vs, x_best, y_best, w_best, h_best);
+
+ /* Send remaining rectangles (at right and bottom). */
+
+ if (x_best + w_best != x + w) {
+ n += tight_send_framebuffer_update(vs, x_best+w_best,
+ y_best,
+ w-(x_best-x)-w_best,
+ h_best);
+ }
+ if (y_best + h_best != y + h) {
+ n += tight_send_framebuffer_update(vs, x, y_best+h_best,
+ w, h-(y_best-y)-h_best);
+ }
+
+ /* Return after all recursive calls are done. */
+ return n;
+ }
+ }
+ return n + send_rect_simple(vs, x, y, w, h, true);
+}
+
+static int tight_send_framebuffer_update(VncState *vs, int x, int y,
+ int w, int h)
+{
+ int max_rows;
+
+ if (vs->client_pf.bytes_per_pixel == 4 && vs->client_pf.rmax == 0xFF &&
+ vs->client_pf.bmax == 0xFF && vs->client_pf.gmax == 0xFF) {
+ vs->tight->pixel24 = true;
+ } else {
+ vs->tight->pixel24 = false;
+ }
+
+#ifdef CONFIG_VNC_JPEG
+ if (vs->tight->quality != (uint8_t)-1) {
+ double freq = vnc_update_freq(vs, x, y, w, h);
+
+ if (freq > tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) {
+ return send_rect_simple(vs, x, y, w, h, false);
+ }
+ }
+#endif
+
+ if (w * h < VNC_TIGHT_MIN_SPLIT_RECT_SIZE) {
+ return send_rect_simple(vs, x, y, w, h, true);
+ }
+
+ /* Calculate maximum number of rows in one non-solid rectangle. */
+
+ max_rows = tight_conf[vs->tight->compression].max_rect_size;
+ max_rows /= MIN(tight_conf[vs->tight->compression].max_rect_width, w);
+
+ return find_large_solid_color_rect(vs, x, y, w, h, max_rows);
+}
+
+int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y,
+ int w, int h)
+{
+ vs->tight->type = VNC_ENCODING_TIGHT;
+ return tight_send_framebuffer_update(vs, x, y, w, h);
+}
+
+int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y,
+ int w, int h)
+{
+ vs->tight->type = VNC_ENCODING_TIGHT_PNG;
+ return tight_send_framebuffer_update(vs, x, y, w, h);
+}
+
+void vnc_tight_clear(VncState *vs)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(vs->tight->stream); i++) {
+ if (vs->tight->stream[i].opaque) {
+ deflateEnd(&vs->tight->stream[i]);
+ }
+ }
+
+ buffer_free(&vs->tight->tight);
+ buffer_free(&vs->tight->zlib);
+ buffer_free(&vs->tight->gradient);
+#ifdef CONFIG_VNC_JPEG
+ buffer_free(&vs->tight->jpeg);
+#endif
+#ifdef CONFIG_VNC_PNG
+ buffer_free(&vs->tight->png);
+#endif
+}
diff --git a/ui/vnc-enc-tight.h b/ui/vnc-enc-tight.h
new file mode 100644
index 000000000..95c3dac02
--- /dev/null
+++ b/ui/vnc-enc-tight.h
@@ -0,0 +1,183 @@
+/*
+ * QEMU VNC display driver: tight encoding
+ *
+ * From libvncserver/rfb/rfbproto.h
+ * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin
+ * Copyright (C) 2000-2002 Constantin Kaplinsky. All Rights Reserved.
+ * Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef VNC_ENC_TIGHT_H
+#define VNC_ENC_TIGHT_H
+
+/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * Tight Encoding.
+ *
+ *-- The first byte of each Tight-encoded rectangle is a "compression control
+ * byte". Its format is as follows (bit 0 is the least significant one):
+ *
+ * bit 0: if 1, then compression stream 0 should be reset;
+ * bit 1: if 1, then compression stream 1 should be reset;
+ * bit 2: if 1, then compression stream 2 should be reset;
+ * bit 3: if 1, then compression stream 3 should be reset;
+ * bits 7-4: if 1000 (0x08), then the compression type is "fill",
+ * if 1001 (0x09), then the compression type is "jpeg",
+ * if 1010 (0x0A), then the compression type is "png",
+ * if 0xxx, then the compression type is "basic",
+ * values greater than 1010 are not valid.
+ *
+ * If the compression type is "basic", then bits 6..4 of the
+ * compression control byte (those xxx in 0xxx) specify the following:
+ *
+ * bits 5-4: decimal representation is the index of a particular zlib
+ * stream which should be used for decompressing the data;
+ * bit 6: if 1, then a "filter id" byte is following this byte.
+ *
+ *-- The data that follows after the compression control byte described
+ * above depends on the compression type ("fill", "jpeg", "png" or "basic").
+ *
+ *-- If the compression type is "fill", then the only pixel value follows, in
+ * client pixel format (see NOTE 1). This value applies to all pixels of the
+ * rectangle.
+ *
+ *-- If the compression type is "jpeg" or "png", the following data stream
+ * looks like this:
+ *
+ * 1..3 bytes: data size (N) in compact representation;
+ * N bytes: JPEG or PNG image.
+ *
+ * Data size is compactly represented in one, two or three bytes, according
+ * to the following scheme:
+ *
+ * 0xxxxxxx (for values 0..127)
+ * 1xxxxxxx 0yyyyyyy (for values 128..16383)
+ * 1xxxxxxx 1yyyyyyy zzzzzzzz (for values 16384..4194303)
+ *
+ * Here each character denotes one bit, xxxxxxx are the least significant 7
+ * bits of the value (bits 0-6), yyyyyyy are bits 7-13, and zzzzzzzz are the
+ * most significant 8 bits (bits 14-21). For example, decimal value 10000
+ * should be represented as two bytes: binary 10010000 01001110, or
+ * hexadecimal 90 4E.
+ *
+ *-- If the compression type is "basic" and bit 6 of the compression control
+ * byte was set to 1, then the next (second) byte specifies "filter id" which
+ * tells the decoder what filter type was used by the encoder to pre-process
+ * pixel data before the compression. The "filter id" byte can be one of the
+ * following:
+ *
+ * 0: no filter ("copy" filter);
+ * 1: "palette" filter;
+ * 2: "gradient" filter.
+ *
+ *-- If bit 6 of the compression control byte is set to 0 (no "filter id"
+ * byte), or if the filter id is 0, then raw pixel values in the client
+ * format (see NOTE 1) will be compressed. See below details on the
+ * compression.
+ *
+ *-- The "gradient" filter pre-processes pixel data with a simple algorithm
+ * which converts each color component to a difference between a "predicted"
+ * intensity and the actual intensity. Such a technique does not affect
+ * uncompressed data size, but helps to compress photo-like images better.
+ * Pseudo-code for converting intensities to differences is the following:
+ *
+ * P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1];
+ * if (P[i,j] < 0) then P[i,j] := 0;
+ * if (P[i,j] > MAX) then P[i,j] := MAX;
+ * D[i,j] := V[i,j] - P[i,j];
+ *
+ * Here V[i,j] is the intensity of a color component for a pixel at
+ * coordinates (i,j). MAX is the maximum value of intensity for a color
+ * component.
+ *
+ *-- The "palette" filter converts true-color pixel data to indexed colors
+ * and a palette which can consist of 2..256 colors. If the number of colors
+ * is 2, then each pixel is encoded in 1 bit, otherwise 8 bits is used to
+ * encode one pixel. 1-bit encoding is performed such way that the most
+ * significant bits correspond to the leftmost pixels, and each raw of pixels
+ * is aligned to the byte boundary. When "palette" filter is used, the
+ * palette is sent before the pixel data. The palette begins with an unsigned
+ * byte which value is the number of colors in the palette minus 1 (i.e. 1
+ * means 2 colors, 255 means 256 colors in the palette). Then follows the
+ * palette itself which consist of pixel values in client pixel format (see
+ * NOTE 1).
+ *
+ *-- The pixel data is compressed using the zlib library. But if the data
+ * size after applying the filter but before the compression is less then 12,
+ * then the data is sent as is, uncompressed. Four separate zlib streams
+ * (0..3) can be used and the decoder should read the actual stream id from
+ * the compression control byte (see NOTE 2).
+ *
+ * If the compression is not used, then the pixel data is sent as is,
+ * otherwise the data stream looks like this:
+ *
+ * 1..3 bytes: data size (N) in compact representation;
+ * N bytes: zlib-compressed data.
+ *
+ * Data size is compactly represented in one, two or three bytes, just like
+ * in the "jpeg" compression method (see above).
+ *
+ *-- NOTE 1. If the color depth is 24, and all three color components are
+ * 8-bit wide, then one pixel in Tight encoding is always represented by
+ * three bytes, where the first byte is red component, the second byte is
+ * green component, and the third byte is blue component of the pixel color
+ * value. This applies to colors in palettes as well.
+ *
+ *-- NOTE 2. The decoder must reset compression streams' states before
+ * decoding the rectangle, if some of bits 0,1,2,3 in the compression control
+ * byte are set to 1. Note that the decoder must reset zlib streams even if
+ * the compression type is "fill", "jpeg" or "png".
+ *
+ *-- NOTE 3. The "gradient" filter and "jpeg" compression may be used only
+ * when bits-per-pixel value is either 16 or 32, not 8.
+ *
+ *-- NOTE 4. The width of any Tight-encoded rectangle cannot exceed 2048
+ * pixels. If a rectangle is wider, it must be split into several rectangles
+ * and each one should be encoded separately.
+ *
+ */
+
+#define VNC_TIGHT_EXPLICIT_FILTER 0x04
+#define VNC_TIGHT_FILL 0x08
+#define VNC_TIGHT_JPEG 0x09
+#define VNC_TIGHT_PNG 0x0A
+#define VNC_TIGHT_MAX_SUBENCODING 0x0A
+
+/* Filters to improve compression efficiency */
+#define VNC_TIGHT_FILTER_COPY 0x00
+#define VNC_TIGHT_FILTER_PALETTE 0x01
+#define VNC_TIGHT_FILTER_GRADIENT 0x02
+
+/* Note: The following constant should not be changed. */
+#define VNC_TIGHT_MIN_TO_COMPRESS 12
+
+/* The parameters below may be adjusted. */
+#define VNC_TIGHT_MIN_SPLIT_RECT_SIZE 4096
+#define VNC_TIGHT_MIN_SOLID_SUBRECT_SIZE 2048
+#define VNC_TIGHT_MAX_SPLIT_TILE_SIZE 16
+
+#define VNC_TIGHT_JPEG_MIN_RECT_SIZE 4096
+#define VNC_TIGHT_DETECT_SUBROW_WIDTH 7
+#define VNC_TIGHT_DETECT_MIN_WIDTH 8
+#define VNC_TIGHT_DETECT_MIN_HEIGHT 8
+
+#endif /* VNC_ENC_TIGHT_H */
diff --git a/ui/vnc-enc-zlib.c b/ui/vnc-enc-zlib.c
new file mode 100644
index 000000000..900ae5b30
--- /dev/null
+++ b/ui/vnc-enc-zlib.c
@@ -0,0 +1,154 @@
+/*
+ * QEMU VNC display driver: zlib encoding
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "vnc.h"
+
+#define ZALLOC_ALIGNMENT 16
+
+void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size)
+{
+ void *p;
+
+ size *= items;
+ size = (size + ZALLOC_ALIGNMENT - 1) & ~(ZALLOC_ALIGNMENT - 1);
+
+ p = g_malloc0(size);
+
+ return (p);
+}
+
+void vnc_zlib_zfree(void *x, void *addr)
+{
+ g_free(addr);
+}
+
+static void vnc_zlib_start(VncState *vs)
+{
+ buffer_reset(&vs->zlib.zlib);
+
+ // make the output buffer be the zlib buffer, so we can compress it later
+ vs->zlib.tmp = vs->output;
+ vs->output = vs->zlib.zlib;
+}
+
+static int vnc_zlib_stop(VncState *vs)
+{
+ z_streamp zstream = &vs->zlib.stream;
+ int previous_out;
+
+ // switch back to normal output/zlib buffers
+ vs->zlib.zlib = vs->output;
+ vs->output = vs->zlib.tmp;
+
+ // compress the zlib buffer
+
+ // initialize the stream
+ // XXX need one stream per session
+ if (zstream->opaque != vs) {
+ int err;
+
+ VNC_DEBUG("VNC: initializing zlib stream\n");
+ VNC_DEBUG("VNC: opaque = %p | vs = %p\n", zstream->opaque, vs);
+ zstream->zalloc = vnc_zlib_zalloc;
+ zstream->zfree = vnc_zlib_zfree;
+
+ err = deflateInit2(zstream, vs->tight->compression, Z_DEFLATED,
+ MAX_WBITS,
+ MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+
+ if (err != Z_OK) {
+ fprintf(stderr, "VNC: error initializing zlib\n");
+ return -1;
+ }
+
+ vs->zlib.level = vs->tight->compression;
+ zstream->opaque = vs;
+ }
+
+ if (vs->tight->compression != vs->zlib.level) {
+ if (deflateParams(zstream, vs->tight->compression,
+ Z_DEFAULT_STRATEGY) != Z_OK) {
+ return -1;
+ }
+ vs->zlib.level = vs->tight->compression;
+ }
+
+ // reserve memory in output buffer
+ buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64);
+
+ // set pointers
+ zstream->next_in = vs->zlib.zlib.buffer;
+ zstream->avail_in = vs->zlib.zlib.offset;
+ zstream->next_out = vs->output.buffer + vs->output.offset;
+ zstream->avail_out = vs->output.capacity - vs->output.offset;
+ previous_out = zstream->avail_out;
+ zstream->data_type = Z_BINARY;
+
+ // start encoding
+ if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) {
+ fprintf(stderr, "VNC: error during zlib compression\n");
+ return -1;
+ }
+
+ vs->output.offset = vs->output.capacity - zstream->avail_out;
+ return previous_out - zstream->avail_out;
+}
+
+int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
+{
+ int old_offset, new_offset, bytes_written;
+
+ vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_ZLIB);
+
+ // remember where we put in the follow-up size
+ old_offset = vs->output.offset;
+ vnc_write_s32(vs, 0);
+
+ // compress the stream
+ vnc_zlib_start(vs);
+ vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+ bytes_written = vnc_zlib_stop(vs);
+
+ if (bytes_written == -1)
+ return 0;
+
+ // hack in the size
+ new_offset = vs->output.offset;
+ vs->output.offset = old_offset;
+ vnc_write_u32(vs, bytes_written);
+ vs->output.offset = new_offset;
+
+ return 1;
+}
+
+void vnc_zlib_clear(VncState *vs)
+{
+ if (vs->zlib.stream.opaque) {
+ deflateEnd(&vs->zlib.stream);
+ }
+ buffer_free(&vs->zlib.zlib);
+}
diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c
new file mode 100644
index 000000000..bd33b8906
--- /dev/null
+++ b/ui/vnc-enc-zrle.c
@@ -0,0 +1,366 @@
+/*
+ * QEMU VNC display driver: Zlib Run-length Encoding (ZRLE)
+ *
+ * From libvncserver/libvncserver/zrle.c
+ * Copyright (C) 2002 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ *
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "vnc.h"
+#include "vnc-enc-zrle.h"
+
+static const int bits_per_packed_pixel[] = {
+ 0, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
+};
+
+
+static void vnc_zrle_start(VncState *vs)
+{
+ buffer_reset(&vs->zrle->zrle);
+
+ /* make the output buffer be the zlib buffer, so we can compress it later */
+ vs->zrle->tmp = vs->output;
+ vs->output = vs->zrle->zrle;
+}
+
+static void vnc_zrle_stop(VncState *vs)
+{
+ /* switch back to normal output/zlib buffers */
+ vs->zrle->zrle = vs->output;
+ vs->output = vs->zrle->tmp;
+}
+
+static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h,
+ int bpp)
+{
+ Buffer tmp;
+
+ buffer_reset(&vs->zrle->fb);
+ buffer_reserve(&vs->zrle->fb, w * h * bpp + bpp);
+
+ tmp = vs->output;
+ vs->output = vs->zrle->fb;
+
+ vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+
+ vs->zrle->fb = vs->output;
+ vs->output = tmp;
+ return vs->zrle->fb.buffer;
+}
+
+static int zrle_compress_data(VncState *vs, int level)
+{
+ z_streamp zstream = &vs->zrle->stream;
+
+ buffer_reset(&vs->zrle->zlib);
+
+ if (zstream->opaque != vs) {
+ int err;
+
+ zstream->zalloc = vnc_zlib_zalloc;
+ zstream->zfree = vnc_zlib_zfree;
+
+ err = deflateInit2(zstream, level, Z_DEFLATED, MAX_WBITS,
+ MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+
+ if (err != Z_OK) {
+ fprintf(stderr, "VNC: error initializing zlib\n");
+ return -1;
+ }
+
+ zstream->opaque = vs;
+ }
+
+ /* reserve memory in output buffer */
+ buffer_reserve(&vs->zrle->zlib, vs->zrle->zrle.offset + 64);
+
+ /* set pointers */
+ zstream->next_in = vs->zrle->zrle.buffer;
+ zstream->avail_in = vs->zrle->zrle.offset;
+ zstream->next_out = vs->zrle->zlib.buffer;
+ zstream->avail_out = vs->zrle->zlib.capacity;
+ zstream->data_type = Z_BINARY;
+
+ /* start encoding */
+ if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) {
+ fprintf(stderr, "VNC: error during zrle compression\n");
+ return -1;
+ }
+
+ vs->zrle->zlib.offset = vs->zrle->zlib.capacity - zstream->avail_out;
+ return vs->zrle->zlib.offset;
+}
+
+/* Try to work out whether to use RLE and/or a palette. We do this by
+ * estimating the number of bytes which will be generated and picking the
+ * method which results in the fewest bytes. Of course this may not result
+ * in the fewest bytes after compression... */
+static void zrle_choose_palette_rle(VncState *vs, int w, int h,
+ VncPalette *palette, int bpp_out,
+ int runs, int single_pixels,
+ int zywrle_level,
+ bool *use_rle, bool *use_palette)
+{
+ size_t estimated_bytes;
+ size_t plain_rle_bytes;
+
+ *use_palette = *use_rle = false;
+
+ estimated_bytes = w * h * (bpp_out / 8); /* start assuming raw */
+
+ if (bpp_out != 8) {
+ if (zywrle_level > 0 && !(zywrle_level & 0x80))
+ estimated_bytes >>= zywrle_level;
+ }
+
+ plain_rle_bytes = ((bpp_out / 8) + 1) * (runs + single_pixels);
+
+ if (plain_rle_bytes < estimated_bytes) {
+ *use_rle = true;
+ estimated_bytes = plain_rle_bytes;
+ }
+
+ if (palette_size(palette) < 128) {
+ int palette_rle_bytes;
+
+ palette_rle_bytes = (bpp_out / 8) * palette_size(palette);
+ palette_rle_bytes += 2 * runs + single_pixels;
+
+ if (palette_rle_bytes < estimated_bytes) {
+ *use_rle = true;
+ *use_palette = true;
+ estimated_bytes = palette_rle_bytes;
+ }
+
+ if (palette_size(palette) < 17) {
+ int packed_bytes;
+
+ packed_bytes = (bpp_out / 8) * palette_size(palette);
+ packed_bytes += w * h *
+ bits_per_packed_pixel[palette_size(palette)-1] / 8;
+
+ if (packed_bytes < estimated_bytes) {
+ *use_rle = false;
+ *use_palette = true;
+ }
+ }
+ }
+}
+
+static void zrle_write_u32(VncState *vs, uint32_t value)
+{
+ vnc_write(vs, (uint8_t *)&value, 4);
+}
+
+static void zrle_write_u24a(VncState *vs, uint32_t value)
+{
+ vnc_write(vs, (uint8_t *)&value, 3);
+}
+
+static void zrle_write_u24b(VncState *vs, uint32_t value)
+{
+ vnc_write(vs, ((uint8_t *)&value) + 1, 3);
+}
+
+static void zrle_write_u16(VncState *vs, uint16_t value)
+{
+ vnc_write(vs, (uint8_t *)&value, 2);
+}
+
+static void zrle_write_u8(VncState *vs, uint8_t value)
+{
+ vnc_write_u8(vs, value);
+}
+
+#define ENDIAN_LITTLE 0
+#define ENDIAN_BIG 1
+#define ENDIAN_NO 2
+
+#define ZRLE_BPP 8
+#define ZYWRLE_ENDIAN ENDIAN_NO
+#include "vnc-enc-zrle.c.inc"
+#undef ZRLE_BPP
+
+#define ZRLE_BPP 15
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_LITTLE
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_BIG
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZRLE_BPP
+#define ZRLE_BPP 16
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_LITTLE
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_BIG
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZRLE_BPP
+#define ZRLE_BPP 32
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_LITTLE
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_BIG
+#include "vnc-enc-zrle.c.inc"
+
+#define ZRLE_COMPACT_PIXEL 24a
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_LITTLE
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_BIG
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZRLE_COMPACT_PIXEL
+#define ZRLE_COMPACT_PIXEL 24b
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_LITTLE
+#include "vnc-enc-zrle.c.inc"
+
+#undef ZYWRLE_ENDIAN
+#define ZYWRLE_ENDIAN ENDIAN_BIG
+#include "vnc-enc-zrle.c.inc"
+#undef ZRLE_COMPACT_PIXEL
+#undef ZRLE_BPP
+
+static int zrle_send_framebuffer_update(VncState *vs, int x, int y,
+ int w, int h)
+{
+ bool be = vs->client_be;
+ size_t bytes;
+ int zywrle_level;
+
+ if (vs->zrle->type == VNC_ENCODING_ZYWRLE) {
+ if (!vs->vd->lossy || vs->tight->quality == (uint8_t)-1
+ || vs->tight->quality == 9) {
+ zywrle_level = 0;
+ vs->zrle->type = VNC_ENCODING_ZRLE;
+ } else if (vs->tight->quality < 3) {
+ zywrle_level = 3;
+ } else if (vs->tight->quality < 6) {
+ zywrle_level = 2;
+ } else {
+ zywrle_level = 1;
+ }
+ } else {
+ zywrle_level = 0;
+ }
+
+ vnc_zrle_start(vs);
+
+ switch (vs->client_pf.bytes_per_pixel) {
+ case 1:
+ zrle_encode_8ne(vs, x, y, w, h, zywrle_level);
+ break;
+
+ case 2:
+ if (vs->client_pf.gmax > 0x1F) {
+ if (be) {
+ zrle_encode_16be(vs, x, y, w, h, zywrle_level);
+ } else {
+ zrle_encode_16le(vs, x, y, w, h, zywrle_level);
+ }
+ } else {
+ if (be) {
+ zrle_encode_15be(vs, x, y, w, h, zywrle_level);
+ } else {
+ zrle_encode_15le(vs, x, y, w, h, zywrle_level);
+ }
+ }
+ break;
+
+ case 4:
+ {
+ bool fits_in_ls3bytes;
+ bool fits_in_ms3bytes;
+
+ fits_in_ls3bytes =
+ ((vs->client_pf.rmax << vs->client_pf.rshift) < (1 << 24) &&
+ (vs->client_pf.gmax << vs->client_pf.gshift) < (1 << 24) &&
+ (vs->client_pf.bmax << vs->client_pf.bshift) < (1 << 24));
+
+ fits_in_ms3bytes = (vs->client_pf.rshift > 7 &&
+ vs->client_pf.gshift > 7 &&
+ vs->client_pf.bshift > 7);
+
+ if ((fits_in_ls3bytes && !be) || (fits_in_ms3bytes && be)) {
+ if (be) {
+ zrle_encode_24abe(vs, x, y, w, h, zywrle_level);
+ } else {
+ zrle_encode_24ale(vs, x, y, w, h, zywrle_level);
+ }
+ } else if ((fits_in_ls3bytes && be) || (fits_in_ms3bytes && !be)) {
+ if (be) {
+ zrle_encode_24bbe(vs, x, y, w, h, zywrle_level);
+ } else {
+ zrle_encode_24ble(vs, x, y, w, h, zywrle_level);
+ }
+ } else {
+ if (be) {
+ zrle_encode_32be(vs, x, y, w, h, zywrle_level);
+ } else {
+ zrle_encode_32le(vs, x, y, w, h, zywrle_level);
+ }
+ }
+ }
+ break;
+ }
+
+ vnc_zrle_stop(vs);
+ bytes = zrle_compress_data(vs, Z_DEFAULT_COMPRESSION);
+ vnc_framebuffer_update(vs, x, y, w, h, vs->zrle->type);
+ vnc_write_u32(vs, bytes);
+ vnc_write(vs, vs->zrle->zlib.buffer, vs->zrle->zlib.offset);
+ return 1;
+}
+
+int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
+{
+ vs->zrle->type = VNC_ENCODING_ZRLE;
+ return zrle_send_framebuffer_update(vs, x, y, w, h);
+}
+
+int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
+{
+ vs->zrle->type = VNC_ENCODING_ZYWRLE;
+ return zrle_send_framebuffer_update(vs, x, y, w, h);
+}
+
+void vnc_zrle_clear(VncState *vs)
+{
+ if (vs->zrle->stream.opaque) {
+ deflateEnd(&vs->zrle->stream);
+ }
+ buffer_free(&vs->zrle->zrle);
+ buffer_free(&vs->zrle->fb);
+ buffer_free(&vs->zrle->zlib);
+}
diff --git a/ui/vnc-enc-zrle.c.inc b/ui/vnc-enc-zrle.c.inc
new file mode 100644
index 000000000..c107d8aff
--- /dev/null
+++ b/ui/vnc-enc-zrle.c.inc
@@ -0,0 +1,263 @@
+/*
+ * QEMU VNC display driver: Zlib Run-length Encoding (ZRLE)
+ *
+ * From libvncserver/libvncserver/zrleencodetemplate.c
+ * Copyright (C) 2002 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ *
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+/*
+ * Before including this file, you must define a number of CPP macros.
+ *
+ * ZRLE_BPP should be 8, 16 or 32 depending on the bits per pixel.
+ *
+ * Note that the buf argument to ZRLE_ENCODE needs to be at least one pixel
+ * bigger than the largest tile of pixel data, since the ZRLE encoding
+ * algorithm writes to the position one past the end of the pixel data.
+ */
+
+
+#include "qemu/osdep.h"
+
+#undef ZRLE_ENDIAN_SUFFIX
+
+#if ZYWRLE_ENDIAN == ENDIAN_LITTLE
+#define ZRLE_ENDIAN_SUFFIX le
+#elif ZYWRLE_ENDIAN == ENDIAN_BIG
+#define ZRLE_ENDIAN_SUFFIX be
+#else
+#define ZRLE_ENDIAN_SUFFIX ne
+#endif
+
+#ifndef ZRLE_CONCAT
+#define ZRLE_CONCAT_I(a, b) a##b
+#define ZRLE_CONCAT2(a, b) ZRLE_CONCAT_I(a, b)
+#define ZRLE_CONCAT3(a, b, c) ZRLE_CONCAT2(a, ZRLE_CONCAT2(b, c))
+#endif
+
+#ifdef ZRLE_COMPACT_PIXEL
+#define ZRLE_ENCODE_SUFFIX ZRLE_CONCAT2(ZRLE_COMPACT_PIXEL,ZRLE_ENDIAN_SUFFIX)
+#define ZRLE_WRITE_SUFFIX ZRLE_COMPACT_PIXEL
+#define ZRLE_PIXEL ZRLE_CONCAT3(uint,ZRLE_BPP,_t)
+#define ZRLE_BPP_OUT 24
+#elif ZRLE_BPP == 15
+#define ZRLE_ENCODE_SUFFIX ZRLE_CONCAT2(ZRLE_BPP,ZRLE_ENDIAN_SUFFIX)
+#define ZRLE_WRITE_SUFFIX 16
+#define ZRLE_PIXEL uint16_t
+#define ZRLE_BPP_OUT 16
+#else
+#define ZRLE_ENCODE_SUFFIX ZRLE_CONCAT2(ZRLE_BPP,ZRLE_ENDIAN_SUFFIX)
+#define ZRLE_WRITE_SUFFIX ZRLE_BPP
+#define ZRLE_BPP_OUT ZRLE_BPP
+#define ZRLE_PIXEL ZRLE_CONCAT3(uint,ZRLE_BPP,_t)
+#endif
+
+#define ZRLE_WRITE_PIXEL ZRLE_CONCAT2(zrle_write_u, ZRLE_WRITE_SUFFIX)
+#define ZRLE_ENCODE ZRLE_CONCAT2(zrle_encode_, ZRLE_ENCODE_SUFFIX)
+#define ZRLE_ENCODE_TILE ZRLE_CONCAT2(zrle_encode_tile, ZRLE_ENCODE_SUFFIX)
+#define ZRLE_WRITE_PALETTE ZRLE_CONCAT2(zrle_write_palette,ZRLE_ENCODE_SUFFIX)
+
+static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h,
+ int zywrle_level);
+
+#if ZRLE_BPP != 8
+#include "vnc-enc-zywrle-template.c"
+#endif
+
+
+static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h,
+ int zywrle_level)
+{
+ int ty;
+
+ for (ty = y; ty < y + h; ty += VNC_ZRLE_TILE_HEIGHT) {
+
+ int tx, th;
+
+ th = MIN(VNC_ZRLE_TILE_HEIGHT, y + h - ty);
+
+ for (tx = x; tx < x + w; tx += VNC_ZRLE_TILE_WIDTH) {
+ int tw;
+ ZRLE_PIXEL *buf;
+
+ tw = MIN(VNC_ZRLE_TILE_WIDTH, x + w - tx);
+
+ buf = zrle_convert_fb(vs, tx, ty, tw, th, ZRLE_BPP);
+ ZRLE_ENCODE_TILE(vs, buf, tw, th, zywrle_level);
+ }
+ }
+}
+
+static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h,
+ int zywrle_level)
+{
+ VncPalette *palette = &vs->zrle->palette;
+
+ int runs = 0;
+ int single_pixels = 0;
+
+ bool use_rle;
+ bool use_palette;
+
+ int i;
+
+ ZRLE_PIXEL *ptr = data;
+ ZRLE_PIXEL *end = ptr + h * w;
+ *end = ~*(end-1); /* one past the end is different so the while loop ends */
+
+ /* Real limit is 127 but we wan't a way to know if there is more than 127 */
+ palette_init(palette, 256, ZRLE_BPP);
+
+ while (ptr < end) {
+ ZRLE_PIXEL pix = *ptr;
+ if (*++ptr != pix) { /* FIXME */
+ single_pixels++;
+ } else {
+ while (*++ptr == pix) ;
+ runs++;
+ }
+ palette_put(palette, pix);
+ }
+
+ /* Solid tile is a special case */
+
+ if (palette_size(palette) == 1) {
+ bool found;
+
+ vnc_write_u8(vs, 1);
+ ZRLE_WRITE_PIXEL(vs, palette_color(palette, 0, &found));
+ return;
+ }
+
+ zrle_choose_palette_rle(vs, w, h, palette, ZRLE_BPP_OUT,
+ runs, single_pixels, zywrle_level,
+ &use_rle, &use_palette);
+
+ if (!use_palette) {
+ vnc_write_u8(vs, (use_rle ? 128 : 0));
+ } else {
+ uint32_t colors[VNC_PALETTE_MAX_SIZE];
+ size_t size = palette_size(palette);
+
+ vnc_write_u8(vs, (use_rle ? 128 : 0) | size);
+ palette_fill(palette, colors);
+
+ for (i = 0; i < size; i++) {
+ ZRLE_WRITE_PIXEL(vs, colors[i]);
+ }
+ }
+
+ if (use_rle) {
+ ZRLE_PIXEL *ptr = data;
+ ZRLE_PIXEL *end = ptr + w * h;
+ ZRLE_PIXEL *run_start;
+ ZRLE_PIXEL pix;
+
+ while (ptr < end) {
+ int len;
+ int index = 0;
+
+ run_start = ptr;
+ pix = *ptr++;
+
+ while (*ptr == pix && ptr < end) {
+ ptr++;
+ }
+
+ len = ptr - run_start;
+
+ if (use_palette)
+ index = palette_idx(palette, pix);
+
+ if (len <= 2 && use_palette) {
+ if (len == 2) {
+ vnc_write_u8(vs, index);
+ }
+ vnc_write_u8(vs, index);
+ continue;
+ }
+ if (use_palette) {
+ vnc_write_u8(vs, index | 128);
+ } else {
+ ZRLE_WRITE_PIXEL(vs, pix);
+ }
+
+ len -= 1;
+
+ while (len >= 255) {
+ vnc_write_u8(vs, 255);
+ len -= 255;
+ }
+
+ vnc_write_u8(vs, len);
+ }
+ } else if (use_palette) { /* no RLE */
+ int bppp;
+ ZRLE_PIXEL *ptr = data;
+
+ /* packed pixels */
+
+ assert (palette_size(palette) < 17);
+
+ bppp = bits_per_packed_pixel[palette_size(palette)-1];
+
+ for (i = 0; i < h; i++) {
+ uint8_t nbits = 0;
+ uint8_t byte = 0;
+
+ ZRLE_PIXEL *eol = ptr + w;
+
+ while (ptr < eol) {
+ ZRLE_PIXEL pix = *ptr++;
+ uint8_t index = palette_idx(palette, pix);
+
+ byte = (byte << bppp) | index;
+ nbits += bppp;
+ if (nbits >= 8) {
+ vnc_write_u8(vs, byte);
+ nbits = 0;
+ }
+ }
+ if (nbits > 0) {
+ byte <<= 8 - nbits;
+ vnc_write_u8(vs, byte);
+ }
+ }
+ } else {
+
+ /* raw */
+
+#if ZRLE_BPP != 8
+ if (zywrle_level > 0 && !(zywrle_level & 0x80)) {
+ ZYWRLE_ANALYZE(data, data, w, h, w, zywrle_level, vs->zywrle.buf);
+ ZRLE_ENCODE_TILE(vs, data, w, h, zywrle_level | 0x80);
+ }
+ else
+#endif
+ {
+#ifdef ZRLE_COMPACT_PIXEL
+ ZRLE_PIXEL *ptr;
+
+ for (ptr = data; ptr < data + w * h; ptr++) {
+ ZRLE_WRITE_PIXEL(vs, *ptr);
+ }
+#else
+ vnc_write(vs, data, w * h * (ZRLE_BPP / 8));
+#endif
+ }
+ }
+}
+
+#undef ZRLE_PIXEL
+#undef ZRLE_WRITE_PIXEL
+#undef ZRLE_ENCODE
+#undef ZRLE_ENCODE_TILE
+#undef ZYWRLE_ENCODE_TILE
+#undef ZRLE_BPP_OUT
+#undef ZRLE_WRITE_SUFFIX
+#undef ZRLE_ENCODE_SUFFIX
diff --git a/ui/vnc-enc-zrle.h b/ui/vnc-enc-zrle.h
new file mode 100644
index 000000000..6535cb2aa
--- /dev/null
+++ b/ui/vnc-enc-zrle.h
@@ -0,0 +1,40 @@
+/*
+ * QEMU VNC display driver: Zlib Run-length Encoding (ZRLE)
+ *
+ * From libvncserver/libvncserver/zrle.c
+ * Copyright (C) 2002 RealVNC Ltd. All Rights Reserved.
+ * Copyright (C) 2003 Sun Microsystems, Inc.
+ *
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef VNC_ENC_ZRLE_H
+#define VNC_ENC_ZRLE_H
+
+/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+ * ZRLE - encoding combining Zlib compression, tiling, palettisation and
+ * run-length encoding.
+ */
+
+#define VNC_ZRLE_TILE_WIDTH 64
+#define VNC_ZRLE_TILE_HEIGHT 64
+
+#endif
diff --git a/ui/vnc-enc-zywrle-template.c b/ui/vnc-enc-zywrle-template.c
new file mode 100644
index 000000000..e9be55966
--- /dev/null
+++ b/ui/vnc-enc-zywrle-template.c
@@ -0,0 +1,171 @@
+
+/********************************************************************
+ * *
+ * THIS FILE IS PART OF THE 'ZYWRLE' VNC CODEC SOURCE CODE. *
+ * *
+ * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
+ * GOVERNED BY A FOLLOWING BSD-STYLE SOURCE LICENSE. *
+ * PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
+ * *
+ * THE 'ZYWRLE' VNC CODEC SOURCE CODE IS (C) COPYRIGHT 2006 *
+ * BY Hitachi Systems & Services, Ltd. *
+ * (Noriaki Yamazaki, Research & Development Center) *
+ * *
+ * *
+ ********************************************************************
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Hitachi Systems & Services, Ltd. nor
+the names of its contributors may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ********************************************************************/
+
+/* Change Log:
+ V0.02 : 2008/02/04 : Fix mis encode/decode when width != scanline
+ (Thanks Johannes Schindelin, author of LibVNC
+ Server/Client)
+ V0.01 : 2007/02/06 : Initial release
+*/
+
+/*
+[References]
+ PLHarr:
+ Senecal, J. G., P. Lindstrom, M. A. Duchaineau, and K. I. Joy,
+ "An Improved N-Bit to N-Bit Reversible Haar-Like Transform,"
+ Pacific Graphics 2004, October 2004, pp. 371-380.
+ EZW:
+ Shapiro, JM: Embedded Image Coding Using Zerotrees of Wavelet Coefficients,
+ IEEE Trans. Signal. Process., Vol.41, pp.3445-3462 (1993).
+*/
+
+
+/* Template Macro stuffs. */
+#undef ZYWRLE_ANALYZE
+#undef ZYWRLE_SYNTHESIZE
+
+#define ZYWRLE_SUFFIX ZRLE_CONCAT2(ZRLE_BPP,ZRLE_ENDIAN_SUFFIX)
+
+#define ZYWRLE_ANALYZE ZRLE_CONCAT2(zywrle_analyze_, ZYWRLE_SUFFIX)
+#define ZYWRLE_SYNTHESIZE ZRLE_CONCAT2(zywrle_synthesize_,ZYWRLE_SUFFIX)
+
+#define ZYWRLE_RGBYUV ZRLE_CONCAT2(zywrle_rgbyuv_, ZYWRLE_SUFFIX)
+#define ZYWRLE_YUVRGB ZRLE_CONCAT2(zywrle_yuvrgb_, ZYWRLE_SUFFIX)
+#define ZYWRLE_YMASK ZRLE_CONCAT2(ZYWRLE_YMASK, ZRLE_BPP)
+#define ZYWRLE_UVMASK ZRLE_CONCAT2(ZYWRLE_UVMASK, ZRLE_BPP)
+#define ZYWRLE_LOAD_PIXEL ZRLE_CONCAT2(ZYWRLE_LOAD_PIXEL, ZRLE_BPP)
+#define ZYWRLE_SAVE_PIXEL ZRLE_CONCAT2(ZYWRLE_SAVE_PIXEL, ZRLE_BPP)
+
+/* Packing/Unpacking pixel stuffs.
+ Endian conversion stuffs. */
+#undef S_0
+#undef S_1
+#undef L_0
+#undef L_1
+#undef L_2
+
+#if ZYWRLE_ENDIAN == ENDIAN_BIG
+# define S_0 1
+# define S_1 0
+# define L_0 3
+# define L_1 2
+# define L_2 1
+#else
+# define S_0 0
+# define S_1 1
+# define L_0 0
+# define L_1 1
+# define L_2 2
+#endif
+
+#define ZYWRLE_QUANTIZE
+#include "qemu/osdep.h"
+#include "vnc-enc-zywrle.h"
+
+#ifndef ZRLE_COMPACT_PIXEL
+static inline void ZYWRLE_RGBYUV(int *buf, ZRLE_PIXEL *data,
+ int width, int height, int scanline)
+{
+ int r, g, b;
+ int y, u, v;
+ int *line;
+ int *end;
+
+ end = buf + height * width;
+ while (buf < end) {
+ line = buf + width;
+ while (buf < line) {
+ ZYWRLE_LOAD_PIXEL(data, r, g, b);
+ ZYWRLE_RGBYUV_(r, g, b, y, u, v, ZYWRLE_YMASK, ZYWRLE_UVMASK);
+ ZYWRLE_SAVE_COEFF(buf, v, y, u);
+ buf++;
+ data++;
+ }
+ data += scanline - width;
+ }
+}
+
+static ZRLE_PIXEL *ZYWRLE_ANALYZE(ZRLE_PIXEL *dst, ZRLE_PIXEL *src,
+ int w, int h, int scanline, int level,
+ int *buf) {
+ int l;
+ int uw = w;
+ int uh = h;
+ int *top;
+ int *end;
+ int *line;
+ ZRLE_PIXEL *p;
+ int r, g, b;
+ int s;
+ int *ph;
+
+ zywrle_calc_size(&w, &h, level);
+
+ if (w == 0 || h == 0) {
+ return NULL;
+ }
+ uw -= w;
+ uh -= h;
+
+ p = dst;
+ ZYWRLE_LOAD_UNALIGN(src,*(ZRLE_PIXEL*)top = *p;);
+ ZYWRLE_RGBYUV(buf, src, w, h, scanline);
+ wavelet(buf, w, h, level);
+ for (l = 0; l < level; l++) {
+ ZYWRLE_PACK_COEFF(buf, dst, 3, w, h, scanline, l);
+ ZYWRLE_PACK_COEFF(buf, dst, 2, w, h, scanline, l);
+ ZYWRLE_PACK_COEFF(buf, dst, 1, w, h, scanline, l);
+ if (l == level - 1) {
+ ZYWRLE_PACK_COEFF(buf, dst, 0, w, h, scanline, l);
+ }
+ }
+ ZYWRLE_SAVE_UNALIGN(dst,*dst = *(ZRLE_PIXEL*)top;);
+ return dst;
+}
+#endif /* ZRLE_COMPACT_PIXEL */
+
+#undef ZYWRLE_RGBYUV
+#undef ZYWRLE_YUVRGB
+#undef ZYWRLE_LOAD_PIXEL
+#undef ZYWRLE_SAVE_PIXEL
diff --git a/ui/vnc-enc-zywrle.h b/ui/vnc-enc-zywrle.h
new file mode 100644
index 000000000..9b7f69897
--- /dev/null
+++ b/ui/vnc-enc-zywrle.h
@@ -0,0 +1,659 @@
+/********************************************************************
+ * *
+ * THIS FILE IS PART OF THE 'ZYWRLE' VNC CODEC SOURCE CODE. *
+ * *
+ * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS *
+ * GOVERNED BY A FOLLOWING BSD-STYLE SOURCE LICENSE. *
+ * PLEASE READ THESE TERMS BEFORE DISTRIBUTING. *
+ * *
+ * THE 'ZYWRLE' VNC CODEC SOURCE CODE IS (C) COPYRIGHT 2006 *
+ * BY Hitachi Systems & Services, Ltd. *
+ * (Noriaki Yamazaki, Research & Development Center) *
+ * *
+ * *
+ ********************************************************************
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Hitachi Systems & Services, Ltd. nor
+the names of its contributors may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ********************************************************************/
+
+#ifndef VNC_ENC_ZYWRLE_H
+#define VNC_ENC_ZYWRLE_H
+
+/* Tables for Coefficients filtering. */
+#ifndef ZYWRLE_QUANTIZE
+/* Type A:lower bit omitting of EZW style. */
+static const unsigned int zywrle_param[3][3]={
+ {0x0000F000, 0x00000000, 0x00000000},
+ {0x0000C000, 0x00F0F0F0, 0x00000000},
+ {0x0000C000, 0x00C0C0C0, 0x00F0F0F0},
+/* {0x0000FF00, 0x00000000, 0x00000000},
+ {0x0000FF00, 0x00FFFFFF, 0x00000000},
+ {0x0000FF00, 0x00FFFFFF, 0x00FFFFFF}, */
+};
+#else
+/* Type B:Non liner quantization filter. */
+static const int8_t zywrle_conv[4][256]={
+{ /* bi=5, bo=5 r=0.0:PSNR=24.849 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+},
+{ /* bi=5, bo=5 r=2.0:PSNR=74.031 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32,
+ 32, 32, 32, 32, 32, 32, 32, 32,
+ 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 56, 56, 56, 56, 56,
+ 56, 56, 56, 56, 64, 64, 64, 64,
+ 64, 64, 64, 64, 72, 72, 72, 72,
+ 72, 72, 72, 72, 80, 80, 80, 80,
+ 80, 80, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 96, 96,
+ 96, 96, 96, 104, 104, 104, 104, 104,
+ 104, 104, 104, 104, 104, 112, 112, 112,
+ 112, 112, 112, 112, 112, 112, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 0, -120, -120, -120, -120, -120, -120, -120,
+ -120, -120, -120, -112, -112, -112, -112, -112,
+ -112, -112, -112, -112, -104, -104, -104, -104,
+ -104, -104, -104, -104, -104, -104, -96, -96,
+ -96, -96, -96, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -80,
+ -80, -80, -80, -80, -80, -72, -72, -72,
+ -72, -72, -72, -72, -72, -64, -64, -64,
+ -64, -64, -64, -64, -64, -56, -56, -56,
+ -56, -56, -56, -56, -56, -56, -48, -48,
+ -48, -48, -48, -48, -48, -48, -48, -48,
+ -48, -32, -32, -32, -32, -32, -32, -32,
+ -32, -32, -32, -32, -32, -32, -32, -32,
+ -32, -32, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+},
+{ /* bi=5, bo=4 r=2.0:PSNR=64.441 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48,
+ 48, 48, 48, 48, 48, 48, 48, 48,
+ 64, 64, 64, 64, 64, 64, 64, 64,
+ 64, 64, 64, 64, 64, 64, 64, 64,
+ 80, 80, 80, 80, 80, 80, 80, 80,
+ 80, 80, 80, 80, 80, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 104, 104, 104, 104, 104, 104, 104, 104,
+ 104, 104, 104, 112, 112, 112, 112, 112,
+ 112, 112, 112, 112, 120, 120, 120, 120,
+ 120, 120, 120, 120, 120, 120, 120, 120,
+ 0, -120, -120, -120, -120, -120, -120, -120,
+ -120, -120, -120, -120, -120, -112, -112, -112,
+ -112, -112, -112, -112, -112, -112, -104, -104,
+ -104, -104, -104, -104, -104, -104, -104, -104,
+ -104, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -80, -80, -80, -80,
+ -80, -80, -80, -80, -80, -80, -80, -80,
+ -80, -64, -64, -64, -64, -64, -64, -64,
+ -64, -64, -64, -64, -64, -64, -64, -64,
+ -64, -48, -48, -48, -48, -48, -48, -48,
+ -48, -48, -48, -48, -48, -48, -48, -48,
+ -48, -48, -48, -48, -48, -48, -48, -48,
+ -48, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+},
+{ /* bi=5, bo=2 r=2.0:PSNR=43.175 */
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 88, 88, 88, 88, 88, 88, 88, 88,
+ 0, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, -88, -88, -88, -88, -88, -88, -88,
+ -88, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+}
+};
+
+static const int8_t *zywrle_param[3][3][3]={
+ {{zywrle_conv[0], zywrle_conv[2], zywrle_conv[0]},
+ {zywrle_conv[0], zywrle_conv[0], zywrle_conv[0]},
+ {zywrle_conv[0], zywrle_conv[0], zywrle_conv[0]}},
+ {{zywrle_conv[0], zywrle_conv[3], zywrle_conv[0]},
+ {zywrle_conv[1], zywrle_conv[1], zywrle_conv[1]},
+ {zywrle_conv[0], zywrle_conv[0], zywrle_conv[0]}},
+ {{zywrle_conv[0], zywrle_conv[3], zywrle_conv[0]},
+ {zywrle_conv[2], zywrle_conv[2], zywrle_conv[2]},
+ {zywrle_conv[1], zywrle_conv[1], zywrle_conv[1]}},
+};
+#endif
+
+/* Load/Save pixel stuffs. */
+#define ZYWRLE_YMASK15 0xFFFFFFF8
+#define ZYWRLE_UVMASK15 0xFFFFFFF8
+#define ZYWRLE_LOAD_PIXEL15(src, r, g, b) \
+ do { \
+ r = (((uint8_t*)src)[S_1]<< 1)& 0xF8; \
+ g = (((uint8_t*)src)[S_1]<< 6) | (((uint8_t*)src)[S_0]>> 2); \
+ g &= 0xF8; \
+ b = (((uint8_t*)src)[S_0]<< 3)& 0xF8; \
+ } while (0)
+
+#define ZYWRLE_SAVE_PIXEL15(dst, r, g, b) \
+ do { \
+ r &= 0xF8; \
+ g &= 0xF8; \
+ b &= 0xF8; \
+ ((uint8_t*)dst)[S_1] = (uint8_t)((r >> 1)|(g >> 6)); \
+ ((uint8_t*)dst)[S_0] = (uint8_t)(((b >> 3)|(g << 2))& 0xFF); \
+ } while (0)
+
+#define ZYWRLE_YMASK16 0xFFFFFFFC
+#define ZYWRLE_UVMASK16 0xFFFFFFF8
+#define ZYWRLE_LOAD_PIXEL16(src, r, g, b) \
+ do { \
+ r = ((uint8_t*)src)[S_1] & 0xF8; \
+ g = (((uint8_t*)src)[S_1]<< 5) | (((uint8_t*)src)[S_0] >> 3); \
+ g &= 0xFC; \
+ b = (((uint8_t*)src)[S_0]<< 3) & 0xF8; \
+ } while (0)
+
+#define ZYWRLE_SAVE_PIXEL16(dst, r, g,b) \
+ do { \
+ r &= 0xF8; \
+ g &= 0xFC; \
+ b &= 0xF8; \
+ ((uint8_t*)dst)[S_1] = (uint8_t)(r | (g >> 5)); \
+ ((uint8_t*)dst)[S_0] = (uint8_t)(((b >> 3)|(g << 3)) & 0xFF); \
+ } while (0)
+
+#define ZYWRLE_YMASK32 0xFFFFFFFF
+#define ZYWRLE_UVMASK32 0xFFFFFFFF
+#define ZYWRLE_LOAD_PIXEL32(src, r, g, b) \
+ do { \
+ r = ((uint8_t*)src)[L_2]; \
+ g = ((uint8_t*)src)[L_1]; \
+ b = ((uint8_t*)src)[L_0]; \
+ } while (0)
+#define ZYWRLE_SAVE_PIXEL32(dst, r, g, b) \
+ do { \
+ ((uint8_t*)dst)[L_2] = (uint8_t)r; \
+ ((uint8_t*)dst)[L_1] = (uint8_t)g; \
+ ((uint8_t*)dst)[L_0] = (uint8_t)b; \
+ } while (0)
+
+static inline void harr(int8_t *px0, int8_t *px1)
+{
+ /* Piecewise-Linear Harr(PLHarr) */
+ int x0 = (int)*px0, x1 = (int)*px1;
+ int orgx0 = x0, orgx1 = x1;
+
+ if ((x0 ^ x1) & 0x80) {
+ /* differ sign */
+ x1 += x0;
+ if (((x1 ^ orgx1) & 0x80) == 0) {
+ /* |x1| > |x0| */
+ x0 -= x1; /* H = -B */
+ }
+ } else {
+ /* same sign */
+ x0 -= x1;
+ if (((x0 ^ orgx0) & 0x80) == 0) {
+ /* |x0| > |x1| */
+ x1 += x0; /* L = A */
+ }
+ }
+ *px0 = (int8_t)x1;
+ *px1 = (int8_t)x0;
+}
+
+/*
+ 1D-Wavelet transform.
+
+ In coefficients array, the famous 'pyramid' decomposition is well used.
+
+ 1D Model:
+ |L0L0L0L0|L0L0L0L0|H0H0H0H0|H0H0H0H0| : level 0
+ |L1L1L1L1|H1H1H1H1|H0H0H0H0|H0H0H0H0| : level 1
+
+ But this method needs line buffer because H/L is different position from X0/X1.
+ So, I used 'interleave' decomposition instead of it.
+
+ 1D Model:
+ |L0H0L0H0|L0H0L0H0|L0H0L0H0|L0H0L0H0| : level 0
+ |L1H0H1H0|L1H0H1H0|L1H0H1H0|L1H0H1H0| : level 1
+
+ In this method, H/L and X0/X1 is always same position.
+ This leads us to more speed and less memory.
+ Of cause, the result of both method is quite same
+ because it's only difference that coefficient position.
+*/
+static inline void wavelet_level(int *data, int size, int l, int skip_pixel)
+{
+ int s, ofs;
+ int8_t *px0;
+ int8_t *end;
+
+ px0 = (int8_t*)data;
+ s = (8 << l) * skip_pixel;
+ end = px0 + (size >> (l + 1)) * s;
+ s -= 2;
+ ofs = (4 << l) * skip_pixel;
+
+ while (px0 < end) {
+ harr(px0, px0 + ofs);
+ px0++;
+ harr(px0, px0 + ofs);
+ px0++;
+ harr(px0, px0 + ofs);
+ px0 += s;
+ }
+}
+
+#ifndef ZYWRLE_QUANTIZE
+/* Type A:lower bit omitting of EZW style. */
+static inline void filter_wavelet_square(int *buf, int width, int height,
+ int level, int l)
+{
+ int r, s;
+ int x, y;
+ int *h;
+ const unsigned int *m;
+
+ m = &(zywrle_param[level - 1][l]);
+ s = 2 << l;
+
+ for (r = 1; r < 4; r++) {
+ h = buf;
+ if (r & 0x01) {
+ h += s >> 1;
+ }
+ if (r & 0x02) {
+ h += (s >> 1) * width;
+ }
+ for (y = 0; y < height / s; y++) {
+ for (x = 0; x < width / s; x++) {
+ /*
+ these are same following code.
+ h[x] = h[x] / (~m[x]+1) * (~m[x]+1);
+ ( round h[x] with m[x] bit )
+ '&' operator isn't 'round' but is 'floor'.
+ So, we must offset when h[x] is negative.
+ */
+ if (((int8_t*)h)[0] & 0x80) {
+ ((int8_t*)h)[0] += ~((int8_t*)m)[0];
+ }
+ if (((int8_t*)h)[1] & 0x80) {
+ ((int8_t*)h)[1] += ~((int8_t*)m)[1];
+ }
+ if (((int8_t*)h)[2] & 0x80) {
+ ((int8_t*)h)[2] += ~((int8_t*)m)[2];
+ }
+ *h &= *m;
+ h += s;
+ }
+ h += (s-1)*width;
+ }
+ }
+}
+#else
+/*
+ Type B:Non liner quantization filter.
+
+ Coefficients have Gaussian curve and smaller value which is
+ large part of coefficients isn't more important than larger value.
+ So, I use filter of Non liner quantize/dequantize table.
+ In general, Non liner quantize formula is explained as following.
+
+ y=f(x) = sign(x)*round( ((abs(x)/(2^7))^ r )* 2^(bo-1) )*2^(8-bo)
+ x=f-1(y) = sign(y)*round( ((abs(y)/(2^7))^(1/r))* 2^(bi-1) )*2^(8-bi)
+ ( r:power coefficient bi:effective MSB in input bo:effective MSB in output )
+
+ r < 1.0 : Smaller value is more important than larger value.
+ r > 1.0 : Larger value is more important than smaller value.
+ r = 1.0 : Liner quantization which is same with EZW style.
+
+ r = 0.75 is famous non liner quantization used in MP3 audio codec.
+ In contrast to audio data, larger value is important in wavelet coefficients.
+ So, I select r = 2.0 table( quantize is x^2, dequantize sqrt(x) ).
+
+ As compared with EZW style liner quantization, this filter tended to be
+ more sharp edge and be more compression rate but be more blocking noise and be
+ less quality. Especially, the surface of graphic objects has distinguishable
+ noise in middle quality mode.
+
+ We need only quantized-dequantized(filtered) value rather than quantized value
+ itself because all values are packed or palette-lized in later ZRLE section.
+ This lead us not to need to modify client decoder when we change
+ the filtering procedure in future.
+ Client only decodes coefficients given by encoder.
+*/
+static inline void filter_wavelet_square(int *buf, int width, int height,
+ int level, int l)
+{
+ int r, s;
+ int x, y;
+ int *h;
+ const int8_t **m;
+
+ m = zywrle_param[level - 1][l];
+ s = 2 << l;
+
+ for (r = 1; r < 4; r++) {
+ h = buf;
+ if (r & 0x01) {
+ h += s >> 1;
+ }
+ if (r & 0x02) {
+ h += (s >> 1) * width;
+ }
+ for (y = 0; y < height / s; y++) {
+ for (x = 0; x < width / s; x++) {
+ ((int8_t*)h)[0] = m[0][((uint8_t*)h)[0]];
+ ((int8_t*)h)[1] = m[1][((uint8_t*)h)[1]];
+ ((int8_t*)h)[2] = m[2][((uint8_t*)h)[2]];
+ h += s;
+ }
+ h += (s - 1) * width;
+ }
+ }
+}
+#endif
+
+static inline void wavelet(int *buf, int width, int height, int level)
+{
+ int l, s;
+ int *top;
+ int *end;
+
+ for (l = 0; l < level; l++) {
+ top = buf;
+ end = buf + height * width;
+ s = width << l;
+ while (top < end) {
+ wavelet_level(top, width, l, 1);
+ top += s;
+ }
+ top = buf;
+ end = buf + width;
+ s = 1<<l;
+ while (top < end) {
+ wavelet_level(top, height, l, width);
+ top += s;
+ }
+ filter_wavelet_square(buf, width, height, level, l);
+ }
+}
+
+
+/* Load/Save coefficients stuffs.
+ Coefficients manages as 24 bits little-endian pixel. */
+#define ZYWRLE_LOAD_COEFF(src, r, g, b) \
+ do { \
+ r = ((int8_t*)src)[2]; \
+ g = ((int8_t*)src)[1]; \
+ b = ((int8_t*)src)[0]; \
+ } while (0)
+
+#define ZYWRLE_SAVE_COEFF(dst, r, g, b) \
+ do { \
+ ((int8_t*)dst)[2] = (int8_t)r; \
+ ((int8_t*)dst)[1] = (int8_t)g; \
+ ((int8_t*)dst)[0] = (int8_t)b; \
+ } while (0)
+
+/*
+ RGB <=> YUV conversion stuffs.
+ YUV coversion is explained as following formula in strict meaning:
+ Y = 0.299R + 0.587G + 0.114B ( 0<=Y<=255)
+ U = -0.169R - 0.331G + 0.500B (-128<=U<=127)
+ V = 0.500R - 0.419G - 0.081B (-128<=V<=127)
+
+ I use simple conversion RCT(reversible color transform) which is described
+ in JPEG-2000 specification.
+ Y = (R + 2G + B)/4 ( 0<=Y<=255)
+ U = B-G (-256<=U<=255)
+ V = R-G (-256<=V<=255)
+*/
+
+/* RCT is N-bit RGB to N-bit Y and N+1-bit UV.
+ For make Same N-bit, UV is lossy.
+ More exact PLHarr, we reduce to odd range(-127<=x<=127). */
+#define ZYWRLE_RGBYUV_(r, g, b, y, u, v, ymask, uvmask) \
+ do { \
+ y = (r + (g << 1) + b) >> 2; \
+ u = b - g; \
+ v = r - g; \
+ y -= 128; \
+ u >>= 1; \
+ v >>= 1; \
+ y &= ymask; \
+ u &= uvmask; \
+ v &= uvmask; \
+ if (y == -128) { \
+ y += (0xFFFFFFFF - ymask + 1); \
+ } \
+ if (u == -128) { \
+ u += (0xFFFFFFFF - uvmask + 1); \
+ } \
+ if (v == -128) { \
+ v += (0xFFFFFFFF - uvmask + 1); \
+ } \
+ } while (0)
+
+
+/*
+ coefficient packing/unpacking stuffs.
+ Wavelet transform makes 4 sub coefficient image from 1 original image.
+
+ model with pyramid decomposition:
+ +------+------+
+ | | |
+ | L | Hx |
+ | | |
+ +------+------+
+ | | |
+ | H | Hxy |
+ | | |
+ +------+------+
+
+ So, we must transfer each sub images individually in strict meaning.
+ But at least ZRLE meaning, following one decompositon image is same as
+ avobe individual sub image. I use this format.
+ (Strictly saying, transfer order is reverse(Hxy->Hy->Hx->L)
+ for simplified procedure for any wavelet level.)
+
+ +------+------+
+ | L |
+ +------+------+
+ | Hx |
+ +------+------+
+ | Hy |
+ +------+------+
+ | Hxy |
+ +------+------+
+*/
+#define ZYWRLE_INC_PTR(data) \
+ do { \
+ data++; \
+ if( data - p >= (w + uw) ) { \
+ data += scanline-(w + uw); \
+ p = data; \
+ } \
+ } while (0)
+
+#define ZYWRLE_TRANSFER_COEFF(buf, data, t, w, h, scanline, level, TRANS) \
+ do { \
+ ph = buf; \
+ s = 2 << level; \
+ if (t & 0x01) { \
+ ph += s >> 1; \
+ } \
+ if (t & 0x02) { \
+ ph += (s >> 1) * w; \
+ } \
+ end = ph + h * w; \
+ while (ph < end) { \
+ line = ph + w; \
+ while (ph < line) { \
+ TRANS \
+ ZYWRLE_INC_PTR(data); \
+ ph += s; \
+ } \
+ ph += (s - 1) * w; \
+ } \
+ } while (0)
+
+#define ZYWRLE_PACK_COEFF(buf, data, t, width, height, scanline, level) \
+ ZYWRLE_TRANSFER_COEFF(buf, data, t, width, height, scanline, level, \
+ ZYWRLE_LOAD_COEFF(ph, r, g, b); \
+ ZYWRLE_SAVE_PIXEL(data, r, g, b);)
+
+#define ZYWRLE_UNPACK_COEFF(buf, data, t, width, height, scanline, level) \
+ ZYWRLE_TRANSFER_COEFF(buf, data, t, width, height, scanline, level, \
+ ZYWRLE_LOAD_PIXEL(data, r, g, b); \
+ ZYWRLE_SAVE_COEFF(ph, r, g, b);)
+
+#define ZYWRLE_SAVE_UNALIGN(data, TRANS) \
+ do { \
+ top = buf + w * h; \
+ end = buf + (w + uw) * (h + uh); \
+ while (top < end) { \
+ TRANS \
+ ZYWRLE_INC_PTR(data); \
+ top++; \
+ } \
+ } while (0)
+
+#define ZYWRLE_LOAD_UNALIGN(data,TRANS) \
+ do { \
+ top = buf + w * h; \
+ if (uw) { \
+ p = data + w; \
+ end = (int*)(p + h * scanline); \
+ while (p < (ZRLE_PIXEL*)end) { \
+ line = (int*)(p + uw); \
+ while (p < (ZRLE_PIXEL*)line) { \
+ TRANS \
+ p++; \
+ top++; \
+ } \
+ p += scanline - uw; \
+ } \
+ } \
+ if (uh) { \
+ p = data + h * scanline; \
+ end = (int*)(p + uh * scanline); \
+ while (p < (ZRLE_PIXEL*)end) { \
+ line = (int*)(p + w); \
+ while (p < (ZRLE_PIXEL*)line) { \
+ TRANS \
+ p++; \
+ top++; \
+ } \
+ p += scanline - w; \
+ } \
+ } \
+ if (uw && uh) { \
+ p= data + w + h * scanline; \
+ end = (int*)(p + uh * scanline); \
+ while (p < (ZRLE_PIXEL*)end) { \
+ line = (int*)(p + uw); \
+ while (p < (ZRLE_PIXEL*)line) { \
+ TRANS \
+ p++; \
+ top++; \
+ } \
+ p += scanline-uw; \
+ } \
+ } \
+ } while (0)
+
+static inline void zywrle_calc_size(int *w, int *h, int level)
+{
+ *w &= ~((1 << level) - 1);
+ *h &= ~((1 << level) - 1);
+}
+
+#endif
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
new file mode 100644
index 000000000..4562bf892
--- /dev/null
+++ b/ui/vnc-jobs.c
@@ -0,0 +1,382 @@
+/*
+ * QEMU VNC display driver
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+
+#include "qemu/osdep.h"
+#include "vnc.h"
+#include "vnc-jobs.h"
+#include "qemu/sockets.h"
+#include "qemu/main-loop.h"
+#include "block/aio.h"
+#include "trace.h"
+
+/*
+ * Locking:
+ *
+ * There are three levels of locking:
+ * - jobs queue lock: for each operation on the queue (push, pop, isEmpty?)
+ * - VncDisplay global lock: mainly used for framebuffer updates to avoid
+ * screen corruption if the framebuffer is updated
+ * while the worker is doing something.
+ * - VncState::output lock: used to make sure the output buffer is not corrupted
+ * if two threads try to write on it at the same time
+ *
+ * While the VNC worker thread is working, the VncDisplay global lock is held
+ * to avoid screen corruption (this does not block vnc_refresh() because it
+ * uses trylock()) but the output lock is not held because the thread works on
+ * its own output buffer.
+ * When the encoding job is done, the worker thread will hold the output lock
+ * and copy its output buffer in vs->output.
+ */
+
+struct VncJobQueue {
+ QemuCond cond;
+ QemuMutex mutex;
+ QemuThread thread;
+ bool exit;
+ QTAILQ_HEAD(, VncJob) jobs;
+};
+
+typedef struct VncJobQueue VncJobQueue;
+
+/*
+ * We use a single global queue, but most of the functions are
+ * already reentrant, so we can easily add more than one encoding thread
+ */
+static VncJobQueue *queue;
+
+static void vnc_lock_queue(VncJobQueue *queue)
+{
+ qemu_mutex_lock(&queue->mutex);
+}
+
+static void vnc_unlock_queue(VncJobQueue *queue)
+{
+ qemu_mutex_unlock(&queue->mutex);
+}
+
+VncJob *vnc_job_new(VncState *vs)
+{
+ VncJob *job = g_new0(VncJob, 1);
+
+ assert(vs->magic == VNC_MAGIC);
+ job->vs = vs;
+ vnc_lock_queue(queue);
+ QLIST_INIT(&job->rectangles);
+ vnc_unlock_queue(queue);
+ return job;
+}
+
+int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h)
+{
+ VncRectEntry *entry = g_new0(VncRectEntry, 1);
+
+ trace_vnc_job_add_rect(job->vs, job, x, y, w, h);
+
+ entry->rect.x = x;
+ entry->rect.y = y;
+ entry->rect.w = w;
+ entry->rect.h = h;
+
+ vnc_lock_queue(queue);
+ QLIST_INSERT_HEAD(&job->rectangles, entry, next);
+ vnc_unlock_queue(queue);
+ return 1;
+}
+
+void vnc_job_push(VncJob *job)
+{
+ vnc_lock_queue(queue);
+ if (queue->exit || QLIST_EMPTY(&job->rectangles)) {
+ g_free(job);
+ } else {
+ QTAILQ_INSERT_TAIL(&queue->jobs, job, next);
+ qemu_cond_broadcast(&queue->cond);
+ }
+ vnc_unlock_queue(queue);
+}
+
+static bool vnc_has_job_locked(VncState *vs)
+{
+ VncJob *job;
+
+ QTAILQ_FOREACH(job, &queue->jobs, next) {
+ if (job->vs == vs || !vs) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void vnc_jobs_join(VncState *vs)
+{
+ vnc_lock_queue(queue);
+ while (vnc_has_job_locked(vs)) {
+ qemu_cond_wait(&queue->cond, &queue->mutex);
+ }
+ vnc_unlock_queue(queue);
+ vnc_jobs_consume_buffer(vs);
+}
+
+void vnc_jobs_consume_buffer(VncState *vs)
+{
+ bool flush;
+
+ vnc_lock_output(vs);
+ if (vs->jobs_buffer.offset) {
+ if (vs->ioc != NULL && buffer_empty(&vs->output)) {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ if (vs->disconnecting == FALSE) {
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_OUT,
+ vnc_client_io, vs, NULL);
+ }
+ }
+ buffer_move(&vs->output, &vs->jobs_buffer);
+
+ if (vs->job_update == VNC_STATE_UPDATE_FORCE) {
+ vs->force_update_offset = vs->output.offset;
+ }
+ vs->job_update = VNC_STATE_UPDATE_NONE;
+ }
+ flush = vs->ioc != NULL && vs->abort != true;
+ vnc_unlock_output(vs);
+
+ if (flush) {
+ vnc_flush(vs);
+ }
+}
+
+/*
+ * Copy data for local use
+ */
+static void vnc_async_encoding_start(VncState *orig, VncState *local)
+{
+ buffer_init(&local->output, "vnc-worker-output");
+ local->sioc = NULL; /* Don't do any network work on this thread */
+ local->ioc = NULL; /* Don't do any network work on this thread */
+
+ local->vnc_encoding = orig->vnc_encoding;
+ local->features = orig->features;
+ local->vd = orig->vd;
+ local->lossy_rect = orig->lossy_rect;
+ local->write_pixels = orig->write_pixels;
+ local->client_pf = orig->client_pf;
+ local->client_be = orig->client_be;
+ local->tight = orig->tight;
+ local->zlib = orig->zlib;
+ local->hextile = orig->hextile;
+ local->zrle = orig->zrle;
+ local->client_width = orig->client_width;
+ local->client_height = orig->client_height;
+}
+
+static void vnc_async_encoding_end(VncState *orig, VncState *local)
+{
+ buffer_free(&local->output);
+ orig->tight = local->tight;
+ orig->zlib = local->zlib;
+ orig->hextile = local->hextile;
+ orig->zrle = local->zrle;
+ orig->lossy_rect = local->lossy_rect;
+}
+
+static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect)
+{
+ trace_vnc_job_clamp_rect(vs, job, rect->x, rect->y, rect->w, rect->h);
+
+ if (rect->x >= vs->client_width) {
+ goto discard;
+ }
+ rect->w = MIN(vs->client_width - rect->x, rect->w);
+ if (rect->w == 0) {
+ goto discard;
+ }
+
+ if (rect->y >= vs->client_height) {
+ goto discard;
+ }
+ rect->h = MIN(vs->client_height - rect->y, rect->h);
+ if (rect->h == 0) {
+ goto discard;
+ }
+
+ trace_vnc_job_clamped_rect(vs, job, rect->x, rect->y, rect->w, rect->h);
+ return true;
+
+ discard:
+ trace_vnc_job_discard_rect(vs, job, rect->x, rect->y, rect->w, rect->h);
+ return false;
+}
+
+static int vnc_worker_thread_loop(VncJobQueue *queue)
+{
+ VncJob *job;
+ VncRectEntry *entry, *tmp;
+ VncState vs = {};
+ int n_rectangles;
+ int saved_offset;
+
+ vnc_lock_queue(queue);
+ while (QTAILQ_EMPTY(&queue->jobs) && !queue->exit) {
+ qemu_cond_wait(&queue->cond, &queue->mutex);
+ }
+ /* Here job can only be NULL if queue->exit is true */
+ job = QTAILQ_FIRST(&queue->jobs);
+ vnc_unlock_queue(queue);
+ assert(job->vs->magic == VNC_MAGIC);
+
+ if (queue->exit) {
+ return -1;
+ }
+
+ vnc_lock_output(job->vs);
+ if (job->vs->ioc == NULL || job->vs->abort == true) {
+ vnc_unlock_output(job->vs);
+ goto disconnected;
+ }
+ if (buffer_empty(&job->vs->output)) {
+ /*
+ * Looks like a NOP as it obviously moves no data. But it
+ * moves the empty buffer, so we don't have to malloc a new
+ * one for vs.output
+ */
+ buffer_move_empty(&vs.output, &job->vs->output);
+ }
+ vnc_unlock_output(job->vs);
+
+ /* Make a local copy of vs and switch output buffers */
+ vnc_async_encoding_start(job->vs, &vs);
+ vs.magic = VNC_MAGIC;
+
+ /* Start sending rectangles */
+ n_rectangles = 0;
+ vnc_write_u8(&vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(&vs, 0);
+ saved_offset = vs.output.offset;
+ vnc_write_u16(&vs, 0);
+
+ vnc_lock_display(job->vs->vd);
+ QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
+ int n;
+
+ if (job->vs->ioc == NULL) {
+ vnc_unlock_display(job->vs->vd);
+ /* Copy persistent encoding data */
+ vnc_async_encoding_end(job->vs, &vs);
+ goto disconnected;
+ }
+
+ if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) {
+ n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y,
+ entry->rect.w, entry->rect.h);
+
+ if (n >= 0) {
+ n_rectangles += n;
+ }
+ }
+ g_free(entry);
+ }
+ trace_vnc_job_nrects(&vs, job, n_rectangles);
+ vnc_unlock_display(job->vs->vd);
+
+ /* Put n_rectangles at the beginning of the message */
+ vs.output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF;
+ vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
+
+ vnc_lock_output(job->vs);
+ if (job->vs->ioc != NULL) {
+ buffer_move(&job->vs->jobs_buffer, &vs.output);
+ /* Copy persistent encoding data */
+ vnc_async_encoding_end(job->vs, &vs);
+
+ qemu_bh_schedule(job->vs->bh);
+ } else {
+ buffer_reset(&vs.output);
+ /* Copy persistent encoding data */
+ vnc_async_encoding_end(job->vs, &vs);
+ }
+ vnc_unlock_output(job->vs);
+
+disconnected:
+ vnc_lock_queue(queue);
+ QTAILQ_REMOVE(&queue->jobs, job, next);
+ vnc_unlock_queue(queue);
+ qemu_cond_broadcast(&queue->cond);
+ g_free(job);
+ vs.magic = 0;
+ return 0;
+}
+
+static VncJobQueue *vnc_queue_init(void)
+{
+ VncJobQueue *queue = g_new0(VncJobQueue, 1);
+
+ qemu_cond_init(&queue->cond);
+ qemu_mutex_init(&queue->mutex);
+ QTAILQ_INIT(&queue->jobs);
+ return queue;
+}
+
+static void vnc_queue_clear(VncJobQueue *q)
+{
+ qemu_cond_destroy(&queue->cond);
+ qemu_mutex_destroy(&queue->mutex);
+ g_free(q);
+ queue = NULL; /* Unset global queue */
+}
+
+static void *vnc_worker_thread(void *arg)
+{
+ VncJobQueue *queue = arg;
+
+ qemu_thread_get_self(&queue->thread);
+
+ while (!vnc_worker_thread_loop(queue)) ;
+ vnc_queue_clear(queue);
+ return NULL;
+}
+
+static bool vnc_worker_thread_running(void)
+{
+ return queue; /* Check global queue */
+}
+
+void vnc_start_worker_thread(void)
+{
+ VncJobQueue *q;
+
+ if (vnc_worker_thread_running())
+ return ;
+
+ q = vnc_queue_init();
+ qemu_thread_create(&q->thread, "vnc_worker", vnc_worker_thread, q,
+ QEMU_THREAD_DETACHED);
+ queue = q; /* Set global queue */
+}
diff --git a/ui/vnc-jobs.h b/ui/vnc-jobs.h
new file mode 100644
index 000000000..59f66bcc3
--- /dev/null
+++ b/ui/vnc-jobs.h
@@ -0,0 +1,68 @@
+/*
+ * QEMU VNC display driver
+ *
+ * From libvncserver/rfb/rfbproto.h
+ * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin
+ * Copyright (C) 2000-2002 Constantin Kaplinsky. All Rights Reserved.
+ * Copyright (C) 2000 Tridia Corporation. All Rights Reserved.
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef VNC_JOBS_H
+#define VNC_JOBS_H
+
+/* Jobs */
+VncJob *vnc_job_new(VncState *vs);
+int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h);
+void vnc_job_push(VncJob *job);
+void vnc_jobs_join(VncState *vs);
+
+void vnc_jobs_consume_buffer(VncState *vs);
+void vnc_start_worker_thread(void);
+
+/* Locks */
+static inline int vnc_trylock_display(VncDisplay *vd)
+{
+ return qemu_mutex_trylock(&vd->mutex);
+}
+
+static inline void vnc_lock_display(VncDisplay *vd)
+{
+ qemu_mutex_lock(&vd->mutex);
+}
+
+static inline void vnc_unlock_display(VncDisplay *vd)
+{
+ qemu_mutex_unlock(&vd->mutex);
+}
+
+static inline void vnc_lock_output(VncState *vs)
+{
+ qemu_mutex_lock(&vs->output_mutex);
+}
+
+static inline void vnc_unlock_output(VncState *vs)
+{
+ qemu_mutex_unlock(&vs->output_mutex);
+}
+
+#endif /* VNC_JOBS_H */
diff --git a/ui/vnc-palette.c b/ui/vnc-palette.c
new file mode 100644
index 000000000..dc7c0ba99
--- /dev/null
+++ b/ui/vnc-palette.c
@@ -0,0 +1,159 @@
+/*
+ * QEMU VNC display driver: palette hash table
+ *
+ * From libvncserver/libvncserver/tight.c
+ * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "vnc-palette.h"
+
+static VncPaletteEntry *palette_find(const VncPalette *palette,
+ uint32_t color, unsigned int hash)
+{
+ VncPaletteEntry *entry;
+
+ QLIST_FOREACH(entry, &palette->table[hash], next) {
+ if (entry->color == color) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+static unsigned int palette_hash(uint32_t rgb, int bpp)
+{
+ if (bpp == 16) {
+ return ((unsigned int)(((rgb >> 8) + rgb) & 0xFF));
+ } else {
+ return ((unsigned int)(((rgb >> 16) + (rgb >> 8)) & 0xFF));
+ }
+}
+
+VncPalette *palette_new(size_t max, int bpp)
+{
+ VncPalette *palette;
+
+ palette = g_malloc0(sizeof(*palette));
+ palette_init(palette, max, bpp);
+ return palette;
+}
+
+void palette_init(VncPalette *palette, size_t max, int bpp)
+{
+ memset(palette, 0, sizeof (*palette));
+ palette->max = max;
+ palette->bpp = bpp;
+}
+
+void palette_destroy(VncPalette *palette)
+{
+ g_free(palette);
+}
+
+int palette_put(VncPalette *palette, uint32_t color)
+{
+ unsigned int hash;
+ unsigned int idx = palette->size;
+ VncPaletteEntry *entry;
+
+ hash = palette_hash(color, palette->bpp) % VNC_PALETTE_HASH_SIZE;
+ entry = palette_find(palette, color, hash);
+
+ if (!entry && palette->size >= palette->max) {
+ return 0;
+ }
+ if (!entry) {
+ VncPaletteEntry *entry;
+
+ entry = &palette->pool[palette->size];
+ entry->color = color;
+ entry->idx = idx;
+ QLIST_INSERT_HEAD(&palette->table[hash], entry, next);
+ palette->size++;
+ }
+ return palette->size;
+}
+
+int palette_idx(const VncPalette *palette, uint32_t color)
+{
+ VncPaletteEntry *entry;
+ unsigned int hash;
+
+ hash = palette_hash(color, palette->bpp) % VNC_PALETTE_HASH_SIZE;
+ entry = palette_find(palette, color, hash);
+ return (entry == NULL ? -1 : entry->idx);
+}
+
+size_t palette_size(const VncPalette *palette)
+{
+ return palette->size;
+}
+
+void palette_iter(const VncPalette *palette,
+ void (*iter)(int idx, uint32_t color, void *opaque),
+ void *opaque)
+{
+ int i;
+ VncPaletteEntry *entry;
+
+ for (i = 0; i < VNC_PALETTE_HASH_SIZE; i++) {
+ QLIST_FOREACH(entry, &palette->table[i], next) {
+ iter(entry->idx, entry->color, opaque);
+ }
+ }
+}
+
+uint32_t palette_color(const VncPalette *palette, int idx, bool *found)
+{
+ int i;
+ VncPaletteEntry *entry;
+
+ for (i = 0; i < VNC_PALETTE_HASH_SIZE; i++) {
+ QLIST_FOREACH(entry, &palette->table[i], next) {
+ if (entry->idx == idx) {
+ *found = true;
+ return entry->color;
+ }
+ }
+ }
+
+ *found = false;
+ return -1;
+}
+
+static void palette_fill_cb(int idx, uint32_t color, void *opaque)
+{
+ uint32_t *colors = opaque;
+
+ colors[idx] = color;
+}
+
+size_t palette_fill(const VncPalette *palette,
+ uint32_t colors[VNC_PALETTE_MAX_SIZE])
+{
+ palette_iter(palette, palette_fill_cb, colors);
+ return palette_size(palette);
+}
diff --git a/ui/vnc-palette.h b/ui/vnc-palette.h
new file mode 100644
index 000000000..e9f0eaf73
--- /dev/null
+++ b/ui/vnc-palette.h
@@ -0,0 +1,66 @@
+/*
+ * QEMU VNC display driver: palette hash table
+ *
+ * From libvncserver/libvncserver/tight.c
+ * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
+ * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
+ *
+ * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef VNC_PALETTE_H
+#define VNC_PALETTE_H
+
+#include "qemu/queue.h"
+
+#define VNC_PALETTE_HASH_SIZE 256
+#define VNC_PALETTE_MAX_SIZE 256
+
+typedef struct VncPaletteEntry {
+ int idx;
+ uint32_t color;
+ QLIST_ENTRY(VncPaletteEntry) next;
+} VncPaletteEntry;
+
+typedef struct VncPalette {
+ VncPaletteEntry pool[VNC_PALETTE_MAX_SIZE];
+ size_t size;
+ size_t max;
+ int bpp;
+ QLIST_HEAD(,VncPaletteEntry) table[VNC_PALETTE_HASH_SIZE];
+} VncPalette;
+
+VncPalette *palette_new(size_t max, int bpp);
+void palette_init(VncPalette *palette, size_t max, int bpp);
+void palette_destroy(VncPalette *palette);
+
+int palette_put(VncPalette *palette, uint32_t color);
+int palette_idx(const VncPalette *palette, uint32_t color);
+size_t palette_size(const VncPalette *palette);
+
+void palette_iter(const VncPalette *palette,
+ void (*iter)(int idx, uint32_t color, void *opaque),
+ void *opaque);
+uint32_t palette_color(const VncPalette *palette, int idx, bool *found);
+size_t palette_fill(const VncPalette *palette,
+ uint32_t colors[VNC_PALETTE_MAX_SIZE]);
+
+#endif /* VNC_PALETTE_H */
diff --git a/ui/vnc-stubs.c b/ui/vnc-stubs.c
new file mode 100644
index 000000000..b4eb3ce71
--- /dev/null
+++ b/ui/vnc-stubs.c
@@ -0,0 +1,24 @@
+#include "qemu/osdep.h"
+#include "ui/console.h"
+#include "qapi/error.h"
+
+int vnc_display_password(const char *id, const char *password)
+{
+ return -ENODEV;
+}
+int vnc_display_pw_expire(const char *id, time_t expires)
+{
+ return -ENODEV;
+};
+void vnc_parse(const char *str)
+{
+ if (strcmp(str, "none") == 0) {
+ return;
+ }
+ error_setg(&error_fatal, "VNC support is disabled");
+}
+int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
+{
+ error_setg(errp, "VNC support is disabled");
+ return -1;
+}
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
new file mode 100644
index 000000000..6d79f3e5a
--- /dev/null
+++ b/ui/vnc-ws.c
@@ -0,0 +1,150 @@
+/*
+ * QEMU VNC display driver: Websockets support
+ *
+ * Copyright (C) 2010 Joel Martin
+ * Copyright (C) 2012 Tim Hardeck
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "vnc.h"
+#include "io/channel-websock.h"
+#include "qemu/bswap.h"
+#include "trace.h"
+
+static void vncws_tls_handshake_done(QIOTask *task,
+ gpointer user_data)
+{
+ VncState *vs = user_data;
+ Error *err = NULL;
+
+ if (qio_task_propagate_error(task, &err)) {
+ VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err));
+ vnc_client_error(vs);
+ error_free(err);
+ } else {
+ VNC_DEBUG("TLS handshake complete, starting websocket handshake\n");
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ QIO_CHANNEL(vs->ioc), G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vncws_handshake_io, vs, NULL);
+ }
+}
+
+
+gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
+ GIOCondition condition,
+ void *opaque)
+{
+ VncState *vs = opaque;
+ QIOChannelTLS *tls;
+ Error *err = NULL;
+
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
+ }
+
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ vnc_client_error(vs);
+ return TRUE;
+ }
+
+ tls = qio_channel_tls_new_server(
+ vs->ioc,
+ vs->vd->tlscreds,
+ vs->vd->tlsauthzid,
+ &err);
+ if (!tls) {
+ VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
+ error_free(err);
+ vnc_client_error(vs);
+ return TRUE;
+ }
+
+ qio_channel_set_name(QIO_CHANNEL(tls), "vnc-ws-server-tls");
+
+ object_unref(OBJECT(vs->ioc));
+ vs->ioc = QIO_CHANNEL(tls);
+ trace_vnc_client_io_wrap(vs, vs->ioc, "tls");
+ vs->tls = qio_channel_tls_get_session(tls);
+
+ qio_channel_tls_handshake(tls,
+ vncws_tls_handshake_done,
+ vs,
+ NULL,
+ NULL);
+
+ return TRUE;
+}
+
+
+static void vncws_handshake_done(QIOTask *task,
+ gpointer user_data)
+{
+ VncState *vs = user_data;
+ Error *err = NULL;
+
+ if (qio_task_propagate_error(task, &err)) {
+ VNC_DEBUG("Websock handshake failed %s\n", error_get_pretty(err));
+ vnc_client_error(vs);
+ error_free(err);
+ } else {
+ VNC_DEBUG("Websock handshake complete, starting VNC protocol\n");
+ vnc_start_protocol(vs);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vnc_client_io, vs, NULL);
+ }
+}
+
+
+gboolean vncws_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
+ GIOCondition condition,
+ void *opaque)
+{
+ VncState *vs = opaque;
+ QIOChannelWebsock *wioc;
+
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
+ }
+
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ vnc_client_error(vs);
+ return TRUE;
+ }
+
+ wioc = qio_channel_websock_new_server(vs->ioc);
+ qio_channel_set_name(QIO_CHANNEL(wioc), "vnc-ws-server-websock");
+
+ object_unref(OBJECT(vs->ioc));
+ vs->ioc = QIO_CHANNEL(wioc);
+ trace_vnc_client_io_wrap(vs, vs->ioc, "websock");
+
+ qio_channel_websock_handshake(wioc,
+ vncws_handshake_done,
+ vs,
+ NULL);
+
+ return TRUE;
+}
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
new file mode 100644
index 000000000..396cacfcb
--- /dev/null
+++ b/ui/vnc-ws.h
@@ -0,0 +1,31 @@
+/*
+ * QEMU VNC display driver: Websockets support
+ *
+ * Copyright (C) 2010 Joel Martin
+ * Copyright (C) 2012 Tim Hardeck
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef QEMU_UI_VNC_WS_H
+#define QEMU_UI_VNC_WS_H
+
+gboolean vncws_tls_handshake_io(QIOChannel *ioc,
+ GIOCondition condition,
+ void *opaque);
+gboolean vncws_handshake_io(QIOChannel *ioc,
+ GIOCondition condition,
+ void *opaque);
+
+#endif /* QEMU_UI_VNC_WS_H */
diff --git a/ui/vnc.c b/ui/vnc.c
new file mode 100644
index 000000000..af02522e8
--- /dev/null
+++ b/ui/vnc.c
@@ -0,0 +1,4312 @@
+/*
+ * QEMU VNC display driver
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "vnc.h"
+#include "vnc-jobs.h"
+#include "trace.h"
+#include "hw/qdev-core.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/sockets.h"
+#include "qemu/timer.h"
+#include "authz/list.h"
+#include "qemu/config-file.h"
+#include "qapi/qapi-emit-events.h"
+#include "qapi/qapi-events-ui.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-ui.h"
+#include "ui/input.h"
+#include "crypto/hash.h"
+#include "crypto/tlscreds.h"
+#include "crypto/tlscredsanon.h"
+#include "crypto/tlscredsx509.h"
+#include "crypto/random.h"
+#include "crypto/secret_common.h"
+#include "qom/object_interfaces.h"
+#include "qemu/cutils.h"
+#include "qemu/help_option.h"
+#include "io/dns-resolver.h"
+
+#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
+#define VNC_REFRESH_INTERVAL_INC 50
+#define VNC_REFRESH_INTERVAL_MAX GUI_REFRESH_INTERVAL_IDLE
+static const struct timeval VNC_REFRESH_STATS = { 0, 500000 };
+static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
+
+#include "vnc_keysym.h"
+#include "crypto/cipher.h"
+
+static QTAILQ_HEAD(, VncDisplay) vnc_displays =
+ QTAILQ_HEAD_INITIALIZER(vnc_displays);
+
+static int vnc_cursor_define(VncState *vs);
+static void vnc_update_throttle_offset(VncState *vs);
+
+static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
+{
+#ifdef _VNC_DEBUG
+ static const char *mn[] = {
+ [0] = "undefined",
+ [VNC_SHARE_MODE_CONNECTING] = "connecting",
+ [VNC_SHARE_MODE_SHARED] = "shared",
+ [VNC_SHARE_MODE_EXCLUSIVE] = "exclusive",
+ [VNC_SHARE_MODE_DISCONNECTED] = "disconnected",
+ };
+ fprintf(stderr, "%s/%p: %s -> %s\n", __func__,
+ vs->ioc, mn[vs->share_mode], mn[mode]);
+#endif
+
+ switch (vs->share_mode) {
+ case VNC_SHARE_MODE_CONNECTING:
+ vs->vd->num_connecting--;
+ break;
+ case VNC_SHARE_MODE_SHARED:
+ vs->vd->num_shared--;
+ break;
+ case VNC_SHARE_MODE_EXCLUSIVE:
+ vs->vd->num_exclusive--;
+ break;
+ default:
+ break;
+ }
+
+ vs->share_mode = mode;
+
+ switch (vs->share_mode) {
+ case VNC_SHARE_MODE_CONNECTING:
+ vs->vd->num_connecting++;
+ break;
+ case VNC_SHARE_MODE_SHARED:
+ vs->vd->num_shared++;
+ break;
+ case VNC_SHARE_MODE_EXCLUSIVE:
+ vs->vd->num_exclusive++;
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void vnc_init_basic_info(SocketAddress *addr,
+ VncBasicInfo *info,
+ Error **errp)
+{
+ switch (addr->type) {
+ case SOCKET_ADDRESS_TYPE_INET:
+ info->host = g_strdup(addr->u.inet.host);
+ info->service = g_strdup(addr->u.inet.port);
+ if (addr->u.inet.ipv6) {
+ info->family = NETWORK_ADDRESS_FAMILY_IPV6;
+ } else {
+ info->family = NETWORK_ADDRESS_FAMILY_IPV4;
+ }
+ break;
+
+ case SOCKET_ADDRESS_TYPE_UNIX:
+ info->host = g_strdup("");
+ info->service = g_strdup(addr->u.q_unix.path);
+ info->family = NETWORK_ADDRESS_FAMILY_UNIX;
+ break;
+
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ case SOCKET_ADDRESS_TYPE_FD:
+ error_setg(errp, "Unsupported socket address type %s",
+ SocketAddressType_str(addr->type));
+ break;
+ default:
+ abort();
+ }
+
+ return;
+}
+
+static void vnc_init_basic_info_from_server_addr(QIOChannelSocket *ioc,
+ VncBasicInfo *info,
+ Error **errp)
+{
+ SocketAddress *addr = NULL;
+
+ if (!ioc) {
+ error_setg(errp, "No listener socket available");
+ return;
+ }
+
+ addr = qio_channel_socket_get_local_address(ioc, errp);
+ if (!addr) {
+ return;
+ }
+
+ vnc_init_basic_info(addr, info, errp);
+ qapi_free_SocketAddress(addr);
+}
+
+static void vnc_init_basic_info_from_remote_addr(QIOChannelSocket *ioc,
+ VncBasicInfo *info,
+ Error **errp)
+{
+ SocketAddress *addr = NULL;
+
+ addr = qio_channel_socket_get_remote_address(ioc, errp);
+ if (!addr) {
+ return;
+ }
+
+ vnc_init_basic_info(addr, info, errp);
+ qapi_free_SocketAddress(addr);
+}
+
+static const char *vnc_auth_name(VncDisplay *vd) {
+ switch (vd->auth) {
+ case VNC_AUTH_INVALID:
+ return "invalid";
+ case VNC_AUTH_NONE:
+ return "none";
+ case VNC_AUTH_VNC:
+ return "vnc";
+ case VNC_AUTH_RA2:
+ return "ra2";
+ case VNC_AUTH_RA2NE:
+ return "ra2ne";
+ case VNC_AUTH_TIGHT:
+ return "tight";
+ case VNC_AUTH_ULTRA:
+ return "ultra";
+ case VNC_AUTH_TLS:
+ return "tls";
+ case VNC_AUTH_VENCRYPT:
+ switch (vd->subauth) {
+ case VNC_AUTH_VENCRYPT_PLAIN:
+ return "vencrypt+plain";
+ case VNC_AUTH_VENCRYPT_TLSNONE:
+ return "vencrypt+tls+none";
+ case VNC_AUTH_VENCRYPT_TLSVNC:
+ return "vencrypt+tls+vnc";
+ case VNC_AUTH_VENCRYPT_TLSPLAIN:
+ return "vencrypt+tls+plain";
+ case VNC_AUTH_VENCRYPT_X509NONE:
+ return "vencrypt+x509+none";
+ case VNC_AUTH_VENCRYPT_X509VNC:
+ return "vencrypt+x509+vnc";
+ case VNC_AUTH_VENCRYPT_X509PLAIN:
+ return "vencrypt+x509+plain";
+ case VNC_AUTH_VENCRYPT_TLSSASL:
+ return "vencrypt+tls+sasl";
+ case VNC_AUTH_VENCRYPT_X509SASL:
+ return "vencrypt+x509+sasl";
+ default:
+ return "vencrypt";
+ }
+ case VNC_AUTH_SASL:
+ return "sasl";
+ }
+ return "unknown";
+}
+
+static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
+{
+ VncServerInfo *info;
+ Error *err = NULL;
+
+ if (!vd->listener || !vd->listener->nsioc) {
+ return NULL;
+ }
+
+ info = g_malloc0(sizeof(*info));
+ vnc_init_basic_info_from_server_addr(vd->listener->sioc[0],
+ qapi_VncServerInfo_base(info), &err);
+ info->has_auth = true;
+ info->auth = g_strdup(vnc_auth_name(vd));
+ if (err) {
+ qapi_free_VncServerInfo(info);
+ info = NULL;
+ error_free(err);
+ }
+ return info;
+}
+
+static void vnc_client_cache_auth(VncState *client)
+{
+ if (!client->info) {
+ return;
+ }
+
+ if (client->tls) {
+ client->info->x509_dname =
+ qcrypto_tls_session_get_peer_name(client->tls);
+ client->info->has_x509_dname =
+ client->info->x509_dname != NULL;
+ }
+#ifdef CONFIG_VNC_SASL
+ if (client->sasl.conn &&
+ client->sasl.username) {
+ client->info->has_sasl_username = true;
+ client->info->sasl_username = g_strdup(client->sasl.username);
+ }
+#endif
+}
+
+static void vnc_client_cache_addr(VncState *client)
+{
+ Error *err = NULL;
+
+ client->info = g_malloc0(sizeof(*client->info));
+ vnc_init_basic_info_from_remote_addr(client->sioc,
+ qapi_VncClientInfo_base(client->info),
+ &err);
+ client->info->websocket = client->websocket;
+ if (err) {
+ qapi_free_VncClientInfo(client->info);
+ client->info = NULL;
+ error_free(err);
+ }
+}
+
+static void vnc_qmp_event(VncState *vs, QAPIEvent event)
+{
+ VncServerInfo *si;
+
+ if (!vs->info) {
+ return;
+ }
+
+ si = vnc_server_info_get(vs->vd);
+ if (!si) {
+ return;
+ }
+
+ switch (event) {
+ case QAPI_EVENT_VNC_CONNECTED:
+ qapi_event_send_vnc_connected(si, qapi_VncClientInfo_base(vs->info));
+ break;
+ case QAPI_EVENT_VNC_INITIALIZED:
+ qapi_event_send_vnc_initialized(si, vs->info);
+ break;
+ case QAPI_EVENT_VNC_DISCONNECTED:
+ qapi_event_send_vnc_disconnected(si, vs->info);
+ break;
+ default:
+ break;
+ }
+
+ qapi_free_VncServerInfo(si);
+}
+
+static VncClientInfo *qmp_query_vnc_client(const VncState *client)
+{
+ VncClientInfo *info;
+ Error *err = NULL;
+
+ info = g_malloc0(sizeof(*info));
+
+ vnc_init_basic_info_from_remote_addr(client->sioc,
+ qapi_VncClientInfo_base(info),
+ &err);
+ if (err) {
+ error_free(err);
+ qapi_free_VncClientInfo(info);
+ return NULL;
+ }
+
+ info->websocket = client->websocket;
+
+ if (client->tls) {
+ info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls);
+ info->has_x509_dname = info->x509_dname != NULL;
+ }
+#ifdef CONFIG_VNC_SASL
+ if (client->sasl.conn && client->sasl.username) {
+ info->has_sasl_username = true;
+ info->sasl_username = g_strdup(client->sasl.username);
+ }
+#endif
+
+ return info;
+}
+
+static VncDisplay *vnc_display_find(const char *id)
+{
+ VncDisplay *vd;
+
+ if (id == NULL) {
+ return QTAILQ_FIRST(&vnc_displays);
+ }
+ QTAILQ_FOREACH(vd, &vnc_displays, next) {
+ if (strcmp(id, vd->id) == 0) {
+ return vd;
+ }
+ }
+ return NULL;
+}
+
+static VncClientInfoList *qmp_query_client_list(VncDisplay *vd)
+{
+ VncClientInfoList *prev = NULL;
+ VncState *client;
+
+ QTAILQ_FOREACH(client, &vd->clients, next) {
+ QAPI_LIST_PREPEND(prev, qmp_query_vnc_client(client));
+ }
+ return prev;
+}
+
+VncInfo *qmp_query_vnc(Error **errp)
+{
+ VncInfo *info = g_malloc0(sizeof(*info));
+ VncDisplay *vd = vnc_display_find(NULL);
+ SocketAddress *addr = NULL;
+
+ if (vd == NULL || !vd->listener || !vd->listener->nsioc) {
+ info->enabled = false;
+ } else {
+ info->enabled = true;
+
+ /* for compatibility with the original command */
+ info->has_clients = true;
+ info->clients = qmp_query_client_list(vd);
+
+ addr = qio_channel_socket_get_local_address(vd->listener->sioc[0],
+ errp);
+ if (!addr) {
+ goto out_error;
+ }
+
+ switch (addr->type) {
+ case SOCKET_ADDRESS_TYPE_INET:
+ info->host = g_strdup(addr->u.inet.host);
+ info->service = g_strdup(addr->u.inet.port);
+ if (addr->u.inet.ipv6) {
+ info->family = NETWORK_ADDRESS_FAMILY_IPV6;
+ } else {
+ info->family = NETWORK_ADDRESS_FAMILY_IPV4;
+ }
+ break;
+
+ case SOCKET_ADDRESS_TYPE_UNIX:
+ info->host = g_strdup("");
+ info->service = g_strdup(addr->u.q_unix.path);
+ info->family = NETWORK_ADDRESS_FAMILY_UNIX;
+ break;
+
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ case SOCKET_ADDRESS_TYPE_FD:
+ error_setg(errp, "Unsupported socket address type %s",
+ SocketAddressType_str(addr->type));
+ goto out_error;
+ default:
+ abort();
+ }
+
+ info->has_host = true;
+ info->has_service = true;
+ info->has_family = true;
+
+ info->has_auth = true;
+ info->auth = g_strdup(vnc_auth_name(vd));
+ }
+
+ qapi_free_SocketAddress(addr);
+ return info;
+
+out_error:
+ qapi_free_SocketAddress(addr);
+ qapi_free_VncInfo(info);
+ return NULL;
+}
+
+
+static void qmp_query_auth(int auth, int subauth,
+ VncPrimaryAuth *qmp_auth,
+ VncVencryptSubAuth *qmp_vencrypt,
+ bool *qmp_has_vencrypt);
+
+static VncServerInfo2List *qmp_query_server_entry(QIOChannelSocket *ioc,
+ bool websocket,
+ int auth,
+ int subauth,
+ VncServerInfo2List *prev)
+{
+ VncServerInfo2 *info;
+ Error *err = NULL;
+ SocketAddress *addr;
+
+ addr = qio_channel_socket_get_local_address(ioc, NULL);
+ if (!addr) {
+ return prev;
+ }
+
+ info = g_new0(VncServerInfo2, 1);
+ vnc_init_basic_info(addr, qapi_VncServerInfo2_base(info), &err);
+ qapi_free_SocketAddress(addr);
+ if (err) {
+ qapi_free_VncServerInfo2(info);
+ error_free(err);
+ return prev;
+ }
+ info->websocket = websocket;
+
+ qmp_query_auth(auth, subauth, &info->auth,
+ &info->vencrypt, &info->has_vencrypt);
+
+ QAPI_LIST_PREPEND(prev, info);
+ return prev;
+}
+
+static void qmp_query_auth(int auth, int subauth,
+ VncPrimaryAuth *qmp_auth,
+ VncVencryptSubAuth *qmp_vencrypt,
+ bool *qmp_has_vencrypt)
+{
+ switch (auth) {
+ case VNC_AUTH_VNC:
+ *qmp_auth = VNC_PRIMARY_AUTH_VNC;
+ break;
+ case VNC_AUTH_RA2:
+ *qmp_auth = VNC_PRIMARY_AUTH_RA2;
+ break;
+ case VNC_AUTH_RA2NE:
+ *qmp_auth = VNC_PRIMARY_AUTH_RA2NE;
+ break;
+ case VNC_AUTH_TIGHT:
+ *qmp_auth = VNC_PRIMARY_AUTH_TIGHT;
+ break;
+ case VNC_AUTH_ULTRA:
+ *qmp_auth = VNC_PRIMARY_AUTH_ULTRA;
+ break;
+ case VNC_AUTH_TLS:
+ *qmp_auth = VNC_PRIMARY_AUTH_TLS;
+ break;
+ case VNC_AUTH_VENCRYPT:
+ *qmp_auth = VNC_PRIMARY_AUTH_VENCRYPT;
+ *qmp_has_vencrypt = true;
+ switch (subauth) {
+ case VNC_AUTH_VENCRYPT_PLAIN:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSNONE:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSVNC:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSPLAIN:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN;
+ break;
+ case VNC_AUTH_VENCRYPT_X509NONE:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE;
+ break;
+ case VNC_AUTH_VENCRYPT_X509VNC:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC;
+ break;
+ case VNC_AUTH_VENCRYPT_X509PLAIN:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN;
+ break;
+ case VNC_AUTH_VENCRYPT_TLSSASL:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL;
+ break;
+ case VNC_AUTH_VENCRYPT_X509SASL:
+ *qmp_vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL;
+ break;
+ default:
+ *qmp_has_vencrypt = false;
+ break;
+ }
+ break;
+ case VNC_AUTH_SASL:
+ *qmp_auth = VNC_PRIMARY_AUTH_SASL;
+ break;
+ case VNC_AUTH_NONE:
+ default:
+ *qmp_auth = VNC_PRIMARY_AUTH_NONE;
+ break;
+ }
+}
+
+VncInfo2List *qmp_query_vnc_servers(Error **errp)
+{
+ VncInfo2List *prev = NULL;
+ VncInfo2 *info;
+ VncDisplay *vd;
+ DeviceState *dev;
+ size_t i;
+
+ QTAILQ_FOREACH(vd, &vnc_displays, next) {
+ info = g_new0(VncInfo2, 1);
+ info->id = g_strdup(vd->id);
+ info->clients = qmp_query_client_list(vd);
+ qmp_query_auth(vd->auth, vd->subauth, &info->auth,
+ &info->vencrypt, &info->has_vencrypt);
+ if (vd->dcl.con) {
+ dev = DEVICE(object_property_get_link(OBJECT(vd->dcl.con),
+ "device", &error_abort));
+ info->has_display = true;
+ info->display = g_strdup(dev->id);
+ }
+ for (i = 0; vd->listener != NULL && i < vd->listener->nsioc; i++) {
+ info->server = qmp_query_server_entry(
+ vd->listener->sioc[i], false, vd->auth, vd->subauth,
+ info->server);
+ }
+ for (i = 0; vd->wslistener != NULL && i < vd->wslistener->nsioc; i++) {
+ info->server = qmp_query_server_entry(
+ vd->wslistener->sioc[i], true, vd->ws_auth,
+ vd->ws_subauth, info->server);
+ }
+
+ QAPI_LIST_PREPEND(prev, info);
+ }
+ return prev;
+}
+
+bool vnc_display_reload_certs(const char *id, Error **errp)
+{
+ VncDisplay *vd = vnc_display_find(id);
+ QCryptoTLSCredsClass *creds = NULL;
+
+ if (!vd) {
+ error_setg(errp, "Can not find vnc display");
+ return false;
+ }
+
+ if (!vd->tlscreds) {
+ error_setg(errp, "vnc tls is not enabled");
+ return false;
+ }
+
+ creds = QCRYPTO_TLS_CREDS_GET_CLASS(OBJECT(vd->tlscreds));
+ if (creds->reload == NULL) {
+ error_setg(errp, "%s doesn't support to reload TLS credential",
+ object_get_typename(OBJECT(vd->tlscreds)));
+ return false;
+ }
+ if (!creds->reload(vd->tlscreds, errp)) {
+ return false;
+ }
+
+ return true;
+}
+
+/* TODO
+ 1) Get the queue working for IO.
+ 2) there is some weirdness when using the -S option (the screen is grey
+ and not totally invalidated
+ 3) resolutions > 1024
+*/
+
+static int vnc_update_client(VncState *vs, int has_dirty);
+static void vnc_disconnect_start(VncState *vs);
+
+static void vnc_colordepth(VncState *vs);
+static void framebuffer_update_request(VncState *vs, int incremental,
+ int x_position, int y_position,
+ int w, int h);
+static void vnc_refresh(DisplayChangeListener *dcl);
+static int vnc_refresh_server_surface(VncDisplay *vd);
+
+static int vnc_width(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_WIDTH, ROUND_UP(surface_width(vd->ds),
+ VNC_DIRTY_PIXELS_PER_BIT));
+}
+
+static int vnc_true_width(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_WIDTH, surface_width(vd->ds));
+}
+
+static int vnc_height(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_HEIGHT, surface_height(vd->ds));
+}
+
+static void vnc_set_area_dirty(DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT],
+ VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT),
+ VncDisplay *vd,
+ int x, int y, int w, int h)
+{
+ int width = vnc_width(vd);
+ int height = vnc_height(vd);
+
+ /* this is needed this to ensure we updated all affected
+ * blocks if x % VNC_DIRTY_PIXELS_PER_BIT != 0 */
+ w += (x % VNC_DIRTY_PIXELS_PER_BIT);
+ x -= (x % VNC_DIRTY_PIXELS_PER_BIT);
+
+ x = MIN(x, width);
+ y = MIN(y, height);
+ w = MIN(x + w, width) - x;
+ h = MIN(y + h, height);
+
+ for (; y < h; y++) {
+ bitmap_set(dirty[y], x / VNC_DIRTY_PIXELS_PER_BIT,
+ DIV_ROUND_UP(w, VNC_DIRTY_PIXELS_PER_BIT));
+ }
+}
+
+static void vnc_dpy_update(DisplayChangeListener *dcl,
+ int x, int y, int w, int h)
+{
+ VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
+ struct VncSurface *s = &vd->guest;
+
+ vnc_set_area_dirty(s->dirty, vd, x, y, w, h);
+}
+
+void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
+ int32_t encoding)
+{
+ vnc_write_u16(vs, x);
+ vnc_write_u16(vs, y);
+ vnc_write_u16(vs, w);
+ vnc_write_u16(vs, h);
+
+ vnc_write_s32(vs, encoding);
+}
+
+static void vnc_desktop_resize_ext(VncState *vs, int reject_reason)
+{
+ trace_vnc_msg_server_ext_desktop_resize(
+ vs, vs->ioc, vs->client_width, vs->client_height, reject_reason);
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1); /* number of rects */
+ vnc_framebuffer_update(vs,
+ reject_reason ? 1 : 0,
+ reject_reason,
+ vs->client_width, vs->client_height,
+ VNC_ENCODING_DESKTOP_RESIZE_EXT);
+ vnc_write_u8(vs, 1); /* number of screens */
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u32(vs, 0); /* screen id */
+ vnc_write_u16(vs, 0); /* screen x-pos */
+ vnc_write_u16(vs, 0); /* screen y-pos */
+ vnc_write_u16(vs, vs->client_width);
+ vnc_write_u16(vs, vs->client_height);
+ vnc_write_u32(vs, 0); /* screen flags */
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void vnc_desktop_resize(VncState *vs)
+{
+ if (vs->ioc == NULL || (!vnc_has_feature(vs, VNC_FEATURE_RESIZE) &&
+ !vnc_has_feature(vs, VNC_FEATURE_RESIZE_EXT))) {
+ return;
+ }
+ if (vs->client_width == vs->vd->true_width &&
+ vs->client_height == pixman_image_get_height(vs->vd->server)) {
+ return;
+ }
+
+ assert(vs->vd->true_width < 65536 &&
+ vs->vd->true_width >= 0);
+ assert(pixman_image_get_height(vs->vd->server) < 65536 &&
+ pixman_image_get_height(vs->vd->server) >= 0);
+ vs->client_width = vs->vd->true_width;
+ vs->client_height = pixman_image_get_height(vs->vd->server);
+
+ if (vnc_has_feature(vs, VNC_FEATURE_RESIZE_EXT)) {
+ vnc_desktop_resize_ext(vs, 0);
+ return;
+ }
+
+ trace_vnc_msg_server_desktop_resize(
+ vs, vs->ioc, vs->client_width, vs->client_height);
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1); /* number of rects */
+ vnc_framebuffer_update(vs, 0, 0, vs->client_width, vs->client_height,
+ VNC_ENCODING_DESKTOPRESIZE);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void vnc_abort_display_jobs(VncDisplay *vd)
+{
+ VncState *vs;
+
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ vnc_lock_output(vs);
+ vs->abort = true;
+ vnc_unlock_output(vs);
+ }
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ vnc_jobs_join(vs);
+ }
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ vnc_lock_output(vs);
+ if (vs->update == VNC_STATE_UPDATE_NONE &&
+ vs->job_update != VNC_STATE_UPDATE_NONE) {
+ /* job aborted before completion */
+ vs->update = vs->job_update;
+ vs->job_update = VNC_STATE_UPDATE_NONE;
+ }
+ vs->abort = false;
+ vnc_unlock_output(vs);
+ }
+}
+
+int vnc_server_fb_stride(VncDisplay *vd)
+{
+ return pixman_image_get_stride(vd->server);
+}
+
+void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y)
+{
+ uint8_t *ptr;
+
+ ptr = (uint8_t *)pixman_image_get_data(vd->server);
+ ptr += y * vnc_server_fb_stride(vd);
+ ptr += x * VNC_SERVER_FB_BYTES;
+ return ptr;
+}
+
+static void vnc_update_server_surface(VncDisplay *vd)
+{
+ int width, height;
+
+ qemu_pixman_image_unref(vd->server);
+ vd->server = NULL;
+
+ if (QTAILQ_EMPTY(&vd->clients)) {
+ return;
+ }
+
+ width = vnc_width(vd);
+ height = vnc_height(vd);
+ vd->true_width = vnc_true_width(vd);
+ vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT,
+ width, height,
+ NULL, 0);
+
+ memset(vd->guest.dirty, 0x00, sizeof(vd->guest.dirty));
+ vnc_set_area_dirty(vd->guest.dirty, vd, 0, 0,
+ width, height);
+}
+
+static bool vnc_check_pageflip(DisplaySurface *s1,
+ DisplaySurface *s2)
+{
+ return (s1 != NULL &&
+ s2 != NULL &&
+ surface_width(s1) == surface_width(s2) &&
+ surface_height(s1) == surface_height(s2) &&
+ surface_format(s1) == surface_format(s2));
+
+}
+
+static void vnc_dpy_switch(DisplayChangeListener *dcl,
+ DisplaySurface *surface)
+{
+ VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
+ bool pageflip = vnc_check_pageflip(vd->ds, surface);
+ VncState *vs;
+
+ vnc_abort_display_jobs(vd);
+ vd->ds = surface;
+
+ /* guest surface */
+ qemu_pixman_image_unref(vd->guest.fb);
+ vd->guest.fb = pixman_image_ref(surface->image);
+ vd->guest.format = surface->format;
+
+
+ if (pageflip) {
+ trace_vnc_server_dpy_pageflip(vd,
+ surface_width(surface),
+ surface_height(surface),
+ surface_format(surface));
+ vnc_set_area_dirty(vd->guest.dirty, vd, 0, 0,
+ surface_width(surface),
+ surface_height(surface));
+ return;
+ }
+
+ trace_vnc_server_dpy_recreate(vd,
+ surface_width(surface),
+ surface_height(surface),
+ surface_format(surface));
+ /* server surface */
+ vnc_update_server_surface(vd);
+
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ vnc_colordepth(vs);
+ vnc_desktop_resize(vs);
+ vnc_cursor_define(vs);
+ memset(vs->dirty, 0x00, sizeof(vs->dirty));
+ vnc_set_area_dirty(vs->dirty, vd, 0, 0,
+ vnc_width(vd),
+ vnc_height(vd));
+ vnc_update_throttle_offset(vs);
+ }
+}
+
+/* fastest code */
+static void vnc_write_pixels_copy(VncState *vs,
+ void *pixels, int size)
+{
+ vnc_write(vs, pixels, size);
+}
+
+/* slowest but generic code. */
+void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v)
+{
+ uint8_t r, g, b;
+
+#if VNC_SERVER_FB_FORMAT == PIXMAN_FORMAT(32, PIXMAN_TYPE_ARGB, 0, 8, 8, 8)
+ r = (((v & 0x00ff0000) >> 16) << vs->client_pf.rbits) >> 8;
+ g = (((v & 0x0000ff00) >> 8) << vs->client_pf.gbits) >> 8;
+ b = (((v & 0x000000ff) >> 0) << vs->client_pf.bbits) >> 8;
+#else
+# error need some bits here if you change VNC_SERVER_FB_FORMAT
+#endif
+ v = (r << vs->client_pf.rshift) |
+ (g << vs->client_pf.gshift) |
+ (b << vs->client_pf.bshift);
+ switch (vs->client_pf.bytes_per_pixel) {
+ case 1:
+ buf[0] = v;
+ break;
+ case 2:
+ if (vs->client_be) {
+ buf[0] = v >> 8;
+ buf[1] = v;
+ } else {
+ buf[1] = v >> 8;
+ buf[0] = v;
+ }
+ break;
+ default:
+ case 4:
+ if (vs->client_be) {
+ buf[0] = v >> 24;
+ buf[1] = v >> 16;
+ buf[2] = v >> 8;
+ buf[3] = v;
+ } else {
+ buf[3] = v >> 24;
+ buf[2] = v >> 16;
+ buf[1] = v >> 8;
+ buf[0] = v;
+ }
+ break;
+ }
+}
+
+static void vnc_write_pixels_generic(VncState *vs,
+ void *pixels1, int size)
+{
+ uint8_t buf[4];
+
+ if (VNC_SERVER_FB_BYTES == 4) {
+ uint32_t *pixels = pixels1;
+ int n, i;
+ n = size >> 2;
+ for (i = 0; i < n; i++) {
+ vnc_convert_pixel(vs, buf, pixels[i]);
+ vnc_write(vs, buf, vs->client_pf.bytes_per_pixel);
+ }
+ }
+}
+
+int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
+{
+ int i;
+ uint8_t *row;
+ VncDisplay *vd = vs->vd;
+
+ row = vnc_server_fb_ptr(vd, x, y);
+ for (i = 0; i < h; i++) {
+ vs->write_pixels(vs, row, w * VNC_SERVER_FB_BYTES);
+ row += vnc_server_fb_stride(vd);
+ }
+ return 1;
+}
+
+int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
+{
+ int n = 0;
+
+ switch(vs->vnc_encoding) {
+ case VNC_ENCODING_ZLIB:
+ n = vnc_zlib_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ case VNC_ENCODING_HEXTILE:
+ vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE);
+ n = vnc_hextile_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ case VNC_ENCODING_TIGHT:
+ n = vnc_tight_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ case VNC_ENCODING_TIGHT_PNG:
+ n = vnc_tight_png_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ case VNC_ENCODING_ZRLE:
+ n = vnc_zrle_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ case VNC_ENCODING_ZYWRLE:
+ n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ default:
+ vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW);
+ n = vnc_raw_send_framebuffer_update(vs, x, y, w, h);
+ break;
+ }
+ return n;
+}
+
+static void vnc_mouse_set(DisplayChangeListener *dcl,
+ int x, int y, int visible)
+{
+ /* can we ask the client(s) to move the pointer ??? */
+}
+
+static int vnc_cursor_define(VncState *vs)
+{
+ QEMUCursor *c = vs->vd->cursor;
+ int isize;
+
+ if (!vs->vd->cursor) {
+ return -1;
+ }
+
+ if (vnc_has_feature(vs, VNC_FEATURE_ALPHA_CURSOR)) {
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u16(vs, 1); /* # of rects */
+ vnc_framebuffer_update(vs, c->hot_x, c->hot_y, c->width, c->height,
+ VNC_ENCODING_ALPHA_CURSOR);
+ vnc_write_s32(vs, VNC_ENCODING_RAW);
+ vnc_write(vs, c->data, c->width * c->height * 4);
+ vnc_unlock_output(vs);
+ return 0;
+ }
+ if (vnc_has_feature(vs, VNC_FEATURE_RICH_CURSOR)) {
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u16(vs, 1); /* # of rects */
+ vnc_framebuffer_update(vs, c->hot_x, c->hot_y, c->width, c->height,
+ VNC_ENCODING_RICH_CURSOR);
+ isize = c->width * c->height * vs->client_pf.bytes_per_pixel;
+ vnc_write_pixels_generic(vs, c->data, isize);
+ vnc_write(vs, vs->vd->cursor_mask, vs->vd->cursor_msize);
+ vnc_unlock_output(vs);
+ return 0;
+ }
+ return -1;
+}
+
+static void vnc_dpy_cursor_define(DisplayChangeListener *dcl,
+ QEMUCursor *c)
+{
+ VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
+ VncState *vs;
+
+ cursor_put(vd->cursor);
+ g_free(vd->cursor_mask);
+
+ vd->cursor = c;
+ cursor_get(vd->cursor);
+ vd->cursor_msize = cursor_get_mono_bpl(c) * c->height;
+ vd->cursor_mask = g_malloc0(vd->cursor_msize);
+ cursor_get_mono_mask(c, 0, vd->cursor_mask);
+
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ vnc_cursor_define(vs);
+ }
+}
+
+static int find_and_clear_dirty_height(VncState *vs,
+ int y, int last_x, int x, int height)
+{
+ int h;
+
+ for (h = 1; h < (height - y); h++) {
+ if (!test_bit(last_x, vs->dirty[y + h])) {
+ break;
+ }
+ bitmap_clear(vs->dirty[y + h], last_x, x - last_x);
+ }
+
+ return h;
+}
+
+/*
+ * Figure out how much pending data we should allow in the output
+ * buffer before we throttle incremental display updates, and/or
+ * drop audio samples.
+ *
+ * We allow for equiv of 1 full display's worth of FB updates,
+ * and 1 second of audio samples. If audio backlog was larger
+ * than that the client would already suffering awful audio
+ * glitches, so dropping samples is no worse really).
+ */
+static void vnc_update_throttle_offset(VncState *vs)
+{
+ size_t offset =
+ vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel;
+
+ if (vs->audio_cap) {
+ int bps;
+ switch (vs->as.fmt) {
+ default:
+ case AUDIO_FORMAT_U8:
+ case AUDIO_FORMAT_S8:
+ bps = 1;
+ break;
+ case AUDIO_FORMAT_U16:
+ case AUDIO_FORMAT_S16:
+ bps = 2;
+ break;
+ case AUDIO_FORMAT_U32:
+ case AUDIO_FORMAT_S32:
+ bps = 4;
+ break;
+ }
+ offset += vs->as.freq * bps * vs->as.nchannels;
+ }
+
+ /* Put a floor of 1MB on offset, so that if we have a large pending
+ * buffer and the display is resized to a small size & back again
+ * we don't suddenly apply a tiny send limit
+ */
+ offset = MAX(offset, 1024 * 1024);
+
+ if (vs->throttle_output_offset != offset) {
+ trace_vnc_client_throttle_threshold(
+ vs, vs->ioc, vs->throttle_output_offset, offset, vs->client_width,
+ vs->client_height, vs->client_pf.bytes_per_pixel, vs->audio_cap);
+ }
+
+ vs->throttle_output_offset = offset;
+}
+
+static bool vnc_should_update(VncState *vs)
+{
+ switch (vs->update) {
+ case VNC_STATE_UPDATE_NONE:
+ break;
+ case VNC_STATE_UPDATE_INCREMENTAL:
+ /* Only allow incremental updates if the pending send queue
+ * is less than the permitted threshold, and the job worker
+ * is completely idle.
+ */
+ if (vs->output.offset < vs->throttle_output_offset &&
+ vs->job_update == VNC_STATE_UPDATE_NONE) {
+ return true;
+ }
+ trace_vnc_client_throttle_incremental(
+ vs, vs->ioc, vs->job_update, vs->output.offset);
+ break;
+ case VNC_STATE_UPDATE_FORCE:
+ /* Only allow forced updates if the pending send queue
+ * does not contain a previous forced update, and the
+ * job worker is completely idle.
+ *
+ * Note this means we'll queue a forced update, even if
+ * the output buffer size is otherwise over the throttle
+ * output limit.
+ */
+ if (vs->force_update_offset == 0 &&
+ vs->job_update == VNC_STATE_UPDATE_NONE) {
+ return true;
+ }
+ trace_vnc_client_throttle_forced(
+ vs, vs->ioc, vs->job_update, vs->force_update_offset);
+ break;
+ }
+ return false;
+}
+
+static int vnc_update_client(VncState *vs, int has_dirty)
+{
+ VncDisplay *vd = vs->vd;
+ VncJob *job;
+ int y;
+ int height, width;
+ int n = 0;
+
+ if (vs->disconnecting) {
+ vnc_disconnect_finish(vs);
+ return 0;
+ }
+
+ vs->has_dirty += has_dirty;
+ if (!vnc_should_update(vs)) {
+ return 0;
+ }
+
+ if (!vs->has_dirty && vs->update != VNC_STATE_UPDATE_FORCE) {
+ return 0;
+ }
+
+ /*
+ * Send screen updates to the vnc client using the server
+ * surface and server dirty map. guest surface updates
+ * happening in parallel don't disturb us, the next pass will
+ * send them to the client.
+ */
+ job = vnc_job_new(vs);
+
+ height = pixman_image_get_height(vd->server);
+ width = pixman_image_get_width(vd->server);
+
+ y = 0;
+ for (;;) {
+ int x, h;
+ unsigned long x2;
+ unsigned long offset = find_next_bit((unsigned long *) &vs->dirty,
+ height * VNC_DIRTY_BPL(vs),
+ y * VNC_DIRTY_BPL(vs));
+ if (offset == height * VNC_DIRTY_BPL(vs)) {
+ /* no more dirty bits */
+ break;
+ }
+ y = offset / VNC_DIRTY_BPL(vs);
+ x = offset % VNC_DIRTY_BPL(vs);
+ x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y],
+ VNC_DIRTY_BPL(vs), x);
+ bitmap_clear(vs->dirty[y], x, x2 - x);
+ h = find_and_clear_dirty_height(vs, y, x, x2, height);
+ x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT);
+ if (x2 > x) {
+ n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y,
+ (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h);
+ }
+ if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) {
+ y += h;
+ if (y == height) {
+ break;
+ }
+ }
+ }
+
+ vs->job_update = vs->update;
+ vs->update = VNC_STATE_UPDATE_NONE;
+ vnc_job_push(job);
+ vs->has_dirty = 0;
+ return n;
+}
+
+/* audio */
+static void audio_capture_notify(void *opaque, audcnotification_e cmd)
+{
+ VncState *vs = opaque;
+
+ assert(vs->magic == VNC_MAGIC);
+ switch (cmd) {
+ case AUD_CNOTIFY_DISABLE:
+ trace_vnc_msg_server_audio_end(vs, vs->ioc);
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
+ vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_END);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+ break;
+
+ case AUD_CNOTIFY_ENABLE:
+ trace_vnc_msg_server_audio_begin(vs, vs->ioc);
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
+ vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_BEGIN);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+ break;
+ }
+}
+
+static void audio_capture_destroy(void *opaque)
+{
+}
+
+static void audio_capture(void *opaque, const void *buf, int size)
+{
+ VncState *vs = opaque;
+
+ assert(vs->magic == VNC_MAGIC);
+ trace_vnc_msg_server_audio_data(vs, vs->ioc, buf, size);
+ vnc_lock_output(vs);
+ if (vs->output.offset < vs->throttle_output_offset) {
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
+ vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
+ vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
+ vnc_write_u32(vs, size);
+ vnc_write(vs, buf, size);
+ } else {
+ trace_vnc_client_throttle_audio(vs, vs->ioc, vs->output.offset);
+ }
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void audio_add(VncState *vs)
+{
+ struct audio_capture_ops ops;
+
+ if (vs->audio_cap) {
+ error_report("audio already running");
+ return;
+ }
+
+ ops.notify = audio_capture_notify;
+ ops.destroy = audio_capture_destroy;
+ ops.capture = audio_capture;
+
+ vs->audio_cap = AUD_add_capture(vs->vd->audio_state, &vs->as, &ops, vs);
+ if (!vs->audio_cap) {
+ error_report("Failed to add audio capture");
+ }
+}
+
+static void audio_del(VncState *vs)
+{
+ if (vs->audio_cap) {
+ AUD_del_capture(vs->audio_cap, vs);
+ vs->audio_cap = NULL;
+ }
+}
+
+static void vnc_disconnect_start(VncState *vs)
+{
+ if (vs->disconnecting) {
+ return;
+ }
+ trace_vnc_client_disconnect_start(vs, vs->ioc);
+ vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
+ }
+ qio_channel_close(vs->ioc, NULL);
+ vs->disconnecting = TRUE;
+}
+
+void vnc_disconnect_finish(VncState *vs)
+{
+ int i;
+
+ trace_vnc_client_disconnect_finish(vs, vs->ioc);
+
+ vnc_jobs_join(vs); /* Wait encoding jobs */
+
+ vnc_lock_output(vs);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_DISCONNECTED);
+
+ buffer_free(&vs->input);
+ buffer_free(&vs->output);
+
+ qapi_free_VncClientInfo(vs->info);
+
+ vnc_zlib_clear(vs);
+ vnc_tight_clear(vs);
+ vnc_zrle_clear(vs);
+
+#ifdef CONFIG_VNC_SASL
+ vnc_sasl_client_cleanup(vs);
+#endif /* CONFIG_VNC_SASL */
+ audio_del(vs);
+ qkbd_state_lift_all_keys(vs->vd->kbd);
+
+ if (vs->mouse_mode_notifier.notify != NULL) {
+ qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
+ }
+ QTAILQ_REMOVE(&vs->vd->clients, vs, next);
+ if (QTAILQ_EMPTY(&vs->vd->clients)) {
+ /* last client gone */
+ vnc_update_server_surface(vs->vd);
+ }
+ if (vs->cbpeer.update.notify) {
+ qemu_clipboard_peer_unregister(&vs->cbpeer);
+ }
+
+ vnc_unlock_output(vs);
+
+ qemu_mutex_destroy(&vs->output_mutex);
+ if (vs->bh != NULL) {
+ qemu_bh_delete(vs->bh);
+ }
+ buffer_free(&vs->jobs_buffer);
+
+ for (i = 0; i < VNC_STAT_ROWS; ++i) {
+ g_free(vs->lossy_rect[i]);
+ }
+ g_free(vs->lossy_rect);
+
+ object_unref(OBJECT(vs->ioc));
+ vs->ioc = NULL;
+ object_unref(OBJECT(vs->sioc));
+ vs->sioc = NULL;
+ vs->magic = 0;
+ g_free(vs->zrle);
+ g_free(vs->tight);
+ g_free(vs);
+}
+
+size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err)
+{
+ if (ret <= 0) {
+ if (ret == 0) {
+ trace_vnc_client_eof(vs, vs->ioc);
+ vnc_disconnect_start(vs);
+ } else if (ret != QIO_CHANNEL_ERR_BLOCK) {
+ trace_vnc_client_io_error(vs, vs->ioc,
+ err ? error_get_pretty(err) : "Unknown");
+ vnc_disconnect_start(vs);
+ }
+
+ error_free(err);
+ return 0;
+ }
+ return ret;
+}
+
+
+void vnc_client_error(VncState *vs)
+{
+ VNC_DEBUG("Closing down client sock: protocol error\n");
+ vnc_disconnect_start(vs);
+}
+
+
+/*
+ * Called to write a chunk of data to the client socket. The data may
+ * be the raw data, or may have already been encoded by SASL.
+ * The data will be written either straight onto the socket, or
+ * written via the GNUTLS wrappers, if TLS/SSL encryption is enabled
+ *
+ * NB, it is theoretically possible to have 2 layers of encryption,
+ * both SASL, and this TLS layer. It is highly unlikely in practice
+ * though, since SASL encryption will typically be a no-op if TLS
+ * is active
+ *
+ * Returns the number of bytes written, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * 0 on I/O error, and disconnects the client socket.
+ */
+size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
+{
+ Error *err = NULL;
+ ssize_t ret;
+ ret = qio_channel_write(vs->ioc, (const char *)data, datalen, &err);
+ VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret);
+ return vnc_client_io_error(vs, ret, err);
+}
+
+
+/*
+ * Called to write buffered data to the client socket, when not
+ * using any SASL SSF encryption layers. Will write as much data
+ * as possible without blocking. If all buffered data is written,
+ * will switch the FD poll() handler back to read monitoring.
+ *
+ * Returns the number of bytes written, which may be less than
+ * the buffered output data if the socket would block. Returns
+ * 0 on I/O error, and disconnects the client socket.
+ */
+static size_t vnc_client_write_plain(VncState *vs)
+{
+ size_t offset;
+ size_t ret;
+
+#ifdef CONFIG_VNC_SASL
+ VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
+ vs->output.buffer, vs->output.capacity, vs->output.offset,
+ vs->sasl.waitWriteSSF);
+
+ if (vs->sasl.conn &&
+ vs->sasl.runSSF &&
+ vs->sasl.waitWriteSSF) {
+ ret = vnc_client_write_buf(vs, vs->output.buffer, vs->sasl.waitWriteSSF);
+ if (ret)
+ vs->sasl.waitWriteSSF -= ret;
+ } else
+#endif /* CONFIG_VNC_SASL */
+ ret = vnc_client_write_buf(vs, vs->output.buffer, vs->output.offset);
+ if (!ret)
+ return 0;
+
+ if (ret >= vs->force_update_offset) {
+ if (vs->force_update_offset != 0) {
+ trace_vnc_client_unthrottle_forced(vs, vs->ioc);
+ }
+ vs->force_update_offset = 0;
+ } else {
+ vs->force_update_offset -= ret;
+ }
+ offset = vs->output.offset;
+ buffer_advance(&vs->output, ret);
+ if (offset >= vs->throttle_output_offset &&
+ vs->output.offset < vs->throttle_output_offset) {
+ trace_vnc_client_unthrottle_incremental(vs, vs->ioc, vs->output.offset);
+ }
+
+ if (vs->output.offset == 0) {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vnc_client_io, vs, NULL);
+ }
+
+ return ret;
+}
+
+
+/*
+ * First function called whenever there is data to be written to
+ * the client socket. Will delegate actual work according to whether
+ * SASL SSF layers are enabled (thus requiring encryption calls)
+ */
+static void vnc_client_write_locked(VncState *vs)
+{
+#ifdef CONFIG_VNC_SASL
+ if (vs->sasl.conn &&
+ vs->sasl.runSSF &&
+ !vs->sasl.waitWriteSSF) {
+ vnc_client_write_sasl(vs);
+ } else
+#endif /* CONFIG_VNC_SASL */
+ {
+ vnc_client_write_plain(vs);
+ }
+}
+
+static void vnc_client_write(VncState *vs)
+{
+ assert(vs->magic == VNC_MAGIC);
+ vnc_lock_output(vs);
+ if (vs->output.offset) {
+ vnc_client_write_locked(vs);
+ } else if (vs->ioc != NULL) {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vnc_client_io, vs, NULL);
+ }
+ vnc_unlock_output(vs);
+}
+
+void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
+{
+ vs->read_handler = func;
+ vs->read_handler_expect = expecting;
+}
+
+
+/*
+ * Called to read a chunk of data from the client socket. The data may
+ * be the raw data, or may need to be further decoded by SASL.
+ * The data will be read either straight from to the socket, or
+ * read via the GNUTLS wrappers, if TLS/SSL encryption is enabled
+ *
+ * NB, it is theoretically possible to have 2 layers of encryption,
+ * both SASL, and this TLS layer. It is highly unlikely in practice
+ * though, since SASL encryption will typically be a no-op if TLS
+ * is active
+ *
+ * Returns the number of bytes read, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * 0 on I/O error or EOF, and disconnects the client socket.
+ */
+size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
+{
+ ssize_t ret;
+ Error *err = NULL;
+ ret = qio_channel_read(vs->ioc, (char *)data, datalen, &err);
+ VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret);
+ return vnc_client_io_error(vs, ret, err);
+}
+
+
+/*
+ * Called to read data from the client socket to the input buffer,
+ * when not using any SASL SSF encryption layers. Will read as much
+ * data as possible without blocking.
+ *
+ * Returns the number of bytes read, which may be less than
+ * the requested 'datalen' if the socket would block. Returns
+ * 0 on I/O error or EOF, and disconnects the client socket.
+ */
+static size_t vnc_client_read_plain(VncState *vs)
+{
+ size_t ret;
+ VNC_DEBUG("Read plain %p size %zd offset %zd\n",
+ vs->input.buffer, vs->input.capacity, vs->input.offset);
+ buffer_reserve(&vs->input, 4096);
+ ret = vnc_client_read_buf(vs, buffer_end(&vs->input), 4096);
+ if (!ret)
+ return 0;
+ vs->input.offset += ret;
+ return ret;
+}
+
+static void vnc_jobs_bh(void *opaque)
+{
+ VncState *vs = opaque;
+
+ assert(vs->magic == VNC_MAGIC);
+ vnc_jobs_consume_buffer(vs);
+}
+
+/*
+ * First function called whenever there is more data to be read from
+ * the client socket. Will delegate actual work according to whether
+ * SASL SSF layers are enabled (thus requiring decryption calls)
+ * Returns 0 on success, -1 if client disconnected
+ */
+static int vnc_client_read(VncState *vs)
+{
+ size_t ret;
+
+#ifdef CONFIG_VNC_SASL
+ if (vs->sasl.conn && vs->sasl.runSSF)
+ ret = vnc_client_read_sasl(vs);
+ else
+#endif /* CONFIG_VNC_SASL */
+ ret = vnc_client_read_plain(vs);
+ if (!ret) {
+ if (vs->disconnecting) {
+ vnc_disconnect_finish(vs);
+ return -1;
+ }
+ return 0;
+ }
+
+ while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) {
+ size_t len = vs->read_handler_expect;
+ int ret;
+
+ ret = vs->read_handler(vs, vs->input.buffer, len);
+ if (vs->disconnecting) {
+ vnc_disconnect_finish(vs);
+ return -1;
+ }
+
+ if (!ret) {
+ buffer_advance(&vs->input, len);
+ } else {
+ vs->read_handler_expect = ret;
+ }
+ }
+ return 0;
+}
+
+gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED,
+ GIOCondition condition, void *opaque)
+{
+ VncState *vs = opaque;
+
+ assert(vs->magic == VNC_MAGIC);
+
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ vnc_disconnect_start(vs);
+ return TRUE;
+ }
+
+ if (condition & G_IO_IN) {
+ if (vnc_client_read(vs) < 0) {
+ /* vs is free()ed here */
+ return TRUE;
+ }
+ }
+ if (condition & G_IO_OUT) {
+ vnc_client_write(vs);
+ }
+
+ if (vs->disconnecting) {
+ if (vs->ioc_tag != 0) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = 0;
+ }
+ return TRUE;
+}
+
+
+/*
+ * Scale factor to apply to vs->throttle_output_offset when checking for
+ * hard limit. Worst case normal usage could be x2, if we have a complete
+ * incremental update and complete forced update in the output buffer.
+ * So x3 should be good enough, but we pick x5 to be conservative and thus
+ * (hopefully) never trigger incorrectly.
+ */
+#define VNC_THROTTLE_OUTPUT_LIMIT_SCALE 5
+
+void vnc_write(VncState *vs, const void *data, size_t len)
+{
+ assert(vs->magic == VNC_MAGIC);
+ if (vs->disconnecting) {
+ return;
+ }
+ /* Protection against malicious client/guest to prevent our output
+ * buffer growing without bound if client stops reading data. This
+ * should rarely trigger, because we have earlier throttling code
+ * which stops issuing framebuffer updates and drops audio data
+ * if the throttle_output_offset value is exceeded. So we only reach
+ * this higher level if a huge number of pseudo-encodings get
+ * triggered while data can't be sent on the socket.
+ *
+ * NB throttle_output_offset can be zero during early protocol
+ * handshake, or from the job thread's VncState clone
+ */
+ if (vs->throttle_output_offset != 0 &&
+ (vs->output.offset / VNC_THROTTLE_OUTPUT_LIMIT_SCALE) >
+ vs->throttle_output_offset) {
+ trace_vnc_client_output_limit(vs, vs->ioc, vs->output.offset,
+ vs->throttle_output_offset);
+ vnc_disconnect_start(vs);
+ return;
+ }
+ buffer_reserve(&vs->output, len);
+
+ if (vs->ioc != NULL && buffer_empty(&vs->output)) {
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_OUT,
+ vnc_client_io, vs, NULL);
+ }
+
+ buffer_append(&vs->output, data, len);
+}
+
+void vnc_write_s32(VncState *vs, int32_t value)
+{
+ vnc_write_u32(vs, *(uint32_t *)&value);
+}
+
+void vnc_write_u32(VncState *vs, uint32_t value)
+{
+ uint8_t buf[4];
+
+ buf[0] = (value >> 24) & 0xFF;
+ buf[1] = (value >> 16) & 0xFF;
+ buf[2] = (value >> 8) & 0xFF;
+ buf[3] = value & 0xFF;
+
+ vnc_write(vs, buf, 4);
+}
+
+void vnc_write_u16(VncState *vs, uint16_t value)
+{
+ uint8_t buf[2];
+
+ buf[0] = (value >> 8) & 0xFF;
+ buf[1] = value & 0xFF;
+
+ vnc_write(vs, buf, 2);
+}
+
+void vnc_write_u8(VncState *vs, uint8_t value)
+{
+ vnc_write(vs, (char *)&value, 1);
+}
+
+void vnc_flush(VncState *vs)
+{
+ vnc_lock_output(vs);
+ if (vs->ioc != NULL && vs->output.offset) {
+ vnc_client_write_locked(vs);
+ }
+ if (vs->disconnecting) {
+ if (vs->ioc_tag != 0) {
+ g_source_remove(vs->ioc_tag);
+ }
+ vs->ioc_tag = 0;
+ }
+ vnc_unlock_output(vs);
+}
+
+static uint8_t read_u8(uint8_t *data, size_t offset)
+{
+ return data[offset];
+}
+
+static uint16_t read_u16(uint8_t *data, size_t offset)
+{
+ return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF);
+}
+
+static int32_t read_s32(uint8_t *data, size_t offset)
+{
+ return (int32_t)((data[offset] << 24) | (data[offset + 1] << 16) |
+ (data[offset + 2] << 8) | data[offset + 3]);
+}
+
+uint32_t read_u32(uint8_t *data, size_t offset)
+{
+ return ((data[offset] << 24) | (data[offset + 1] << 16) |
+ (data[offset + 2] << 8) | data[offset + 3]);
+}
+
+static void check_pointer_type_change(Notifier *notifier, void *data)
+{
+ VncState *vs = container_of(notifier, VncState, mouse_mode_notifier);
+ int absolute = qemu_input_is_absolute();
+
+ if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute != absolute) {
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1);
+ vnc_framebuffer_update(vs, absolute, 0,
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
+ VNC_ENCODING_POINTER_TYPE_CHANGE);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+ }
+ vs->absolute = absolute;
+}
+
+static void pointer_event(VncState *vs, int button_mask, int x, int y)
+{
+ static uint32_t bmap[INPUT_BUTTON__MAX] = {
+ [INPUT_BUTTON_LEFT] = 0x01,
+ [INPUT_BUTTON_MIDDLE] = 0x02,
+ [INPUT_BUTTON_RIGHT] = 0x04,
+ [INPUT_BUTTON_WHEEL_UP] = 0x08,
+ [INPUT_BUTTON_WHEEL_DOWN] = 0x10,
+ };
+ QemuConsole *con = vs->vd->dcl.con;
+ int width = pixman_image_get_width(vs->vd->server);
+ int height = pixman_image_get_height(vs->vd->server);
+
+ if (vs->last_bmask != button_mask) {
+ qemu_input_update_buttons(con, bmap, vs->last_bmask, button_mask);
+ vs->last_bmask = button_mask;
+ }
+
+ if (vs->absolute) {
+ qemu_input_queue_abs(con, INPUT_AXIS_X, x, 0, width);
+ qemu_input_queue_abs(con, INPUT_AXIS_Y, y, 0, height);
+ } else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) {
+ qemu_input_queue_rel(con, INPUT_AXIS_X, x - 0x7FFF);
+ qemu_input_queue_rel(con, INPUT_AXIS_Y, y - 0x7FFF);
+ } else {
+ if (vs->last_x != -1) {
+ qemu_input_queue_rel(con, INPUT_AXIS_X, x - vs->last_x);
+ qemu_input_queue_rel(con, INPUT_AXIS_Y, y - vs->last_y);
+ }
+ vs->last_x = x;
+ vs->last_y = y;
+ }
+ qemu_input_event_sync();
+}
+
+static void press_key(VncState *vs, QKeyCode qcode)
+{
+ qkbd_state_key_event(vs->vd->kbd, qcode, true);
+ qkbd_state_key_event(vs->vd->kbd, qcode, false);
+}
+
+static void vnc_led_state_change(VncState *vs)
+{
+ if (!vnc_has_feature(vs, VNC_FEATURE_LED_STATE)) {
+ return;
+ }
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1);
+ vnc_framebuffer_update(vs, 0, 0, 1, 1, VNC_ENCODING_LED_STATE);
+ vnc_write_u8(vs, vs->vd->ledstate);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void kbd_leds(void *opaque, int ledstate)
+{
+ VncDisplay *vd = opaque;
+ VncState *client;
+
+ trace_vnc_key_guest_leds((ledstate & QEMU_CAPS_LOCK_LED),
+ (ledstate & QEMU_NUM_LOCK_LED),
+ (ledstate & QEMU_SCROLL_LOCK_LED));
+
+ if (ledstate == vd->ledstate) {
+ return;
+ }
+
+ vd->ledstate = ledstate;
+
+ QTAILQ_FOREACH(client, &vd->clients, next) {
+ vnc_led_state_change(client);
+ }
+}
+
+static void do_key_event(VncState *vs, int down, int keycode, int sym)
+{
+ QKeyCode qcode = qemu_input_key_number_to_qcode(keycode);
+
+ /* QEMU console switch */
+ switch (qcode) {
+ case Q_KEY_CODE_1 ... Q_KEY_CODE_9: /* '1' to '9' keys */
+ if (vs->vd->dcl.con == NULL && down &&
+ qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_CTRL) &&
+ qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_ALT)) {
+ /* Reset the modifiers sent to the current console */
+ qkbd_state_lift_all_keys(vs->vd->kbd);
+ console_select(qcode - Q_KEY_CODE_1);
+ return;
+ }
+ default:
+ break;
+ }
+
+ /* Turn off the lock state sync logic if the client support the led
+ state extension.
+ */
+ if (down && vs->vd->lock_key_sync &&
+ !vnc_has_feature(vs, VNC_FEATURE_LED_STATE) &&
+ keycode_is_keypad(vs->vd->kbd_layout, keycode)) {
+ /* If the numlock state needs to change then simulate an additional
+ keypress before sending this one. This will happen if the user
+ toggles numlock away from the VNC window.
+ */
+ if (keysym_is_numlock(vs->vd->kbd_layout, sym & 0xFFFF)) {
+ if (!qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_NUMLOCK)) {
+ trace_vnc_key_sync_numlock(true);
+ press_key(vs, Q_KEY_CODE_NUM_LOCK);
+ }
+ } else {
+ if (qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_NUMLOCK)) {
+ trace_vnc_key_sync_numlock(false);
+ press_key(vs, Q_KEY_CODE_NUM_LOCK);
+ }
+ }
+ }
+
+ if (down && vs->vd->lock_key_sync &&
+ !vnc_has_feature(vs, VNC_FEATURE_LED_STATE) &&
+ ((sym >= 'A' && sym <= 'Z') || (sym >= 'a' && sym <= 'z'))) {
+ /* If the capslock state needs to change then simulate an additional
+ keypress before sending this one. This will happen if the user
+ toggles capslock away from the VNC window.
+ */
+ int uppercase = !!(sym >= 'A' && sym <= 'Z');
+ bool shift = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_SHIFT);
+ bool capslock = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_CAPSLOCK);
+ if (capslock) {
+ if (uppercase == shift) {
+ trace_vnc_key_sync_capslock(false);
+ press_key(vs, Q_KEY_CODE_CAPS_LOCK);
+ }
+ } else {
+ if (uppercase != shift) {
+ trace_vnc_key_sync_capslock(true);
+ press_key(vs, Q_KEY_CODE_CAPS_LOCK);
+ }
+ }
+ }
+
+ qkbd_state_key_event(vs->vd->kbd, qcode, down);
+ if (!qemu_console_is_graphic(NULL)) {
+ bool numlock = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_NUMLOCK);
+ bool control = qkbd_state_modifier_get(vs->vd->kbd, QKBD_MOD_CTRL);
+ /* QEMU console emulation */
+ if (down) {
+ switch (keycode) {
+ case 0x2a: /* Left Shift */
+ case 0x36: /* Right Shift */
+ case 0x1d: /* Left CTRL */
+ case 0x9d: /* Right CTRL */
+ case 0x38: /* Left ALT */
+ case 0xb8: /* Right ALT */
+ break;
+ case 0xc8:
+ kbd_put_keysym(QEMU_KEY_UP);
+ break;
+ case 0xd0:
+ kbd_put_keysym(QEMU_KEY_DOWN);
+ break;
+ case 0xcb:
+ kbd_put_keysym(QEMU_KEY_LEFT);
+ break;
+ case 0xcd:
+ kbd_put_keysym(QEMU_KEY_RIGHT);
+ break;
+ case 0xd3:
+ kbd_put_keysym(QEMU_KEY_DELETE);
+ break;
+ case 0xc7:
+ kbd_put_keysym(QEMU_KEY_HOME);
+ break;
+ case 0xcf:
+ kbd_put_keysym(QEMU_KEY_END);
+ break;
+ case 0xc9:
+ kbd_put_keysym(QEMU_KEY_PAGEUP);
+ break;
+ case 0xd1:
+ kbd_put_keysym(QEMU_KEY_PAGEDOWN);
+ break;
+
+ case 0x47:
+ kbd_put_keysym(numlock ? '7' : QEMU_KEY_HOME);
+ break;
+ case 0x48:
+ kbd_put_keysym(numlock ? '8' : QEMU_KEY_UP);
+ break;
+ case 0x49:
+ kbd_put_keysym(numlock ? '9' : QEMU_KEY_PAGEUP);
+ break;
+ case 0x4b:
+ kbd_put_keysym(numlock ? '4' : QEMU_KEY_LEFT);
+ break;
+ case 0x4c:
+ kbd_put_keysym('5');
+ break;
+ case 0x4d:
+ kbd_put_keysym(numlock ? '6' : QEMU_KEY_RIGHT);
+ break;
+ case 0x4f:
+ kbd_put_keysym(numlock ? '1' : QEMU_KEY_END);
+ break;
+ case 0x50:
+ kbd_put_keysym(numlock ? '2' : QEMU_KEY_DOWN);
+ break;
+ case 0x51:
+ kbd_put_keysym(numlock ? '3' : QEMU_KEY_PAGEDOWN);
+ break;
+ case 0x52:
+ kbd_put_keysym('0');
+ break;
+ case 0x53:
+ kbd_put_keysym(numlock ? '.' : QEMU_KEY_DELETE);
+ break;
+
+ case 0xb5:
+ kbd_put_keysym('/');
+ break;
+ case 0x37:
+ kbd_put_keysym('*');
+ break;
+ case 0x4a:
+ kbd_put_keysym('-');
+ break;
+ case 0x4e:
+ kbd_put_keysym('+');
+ break;
+ case 0x9c:
+ kbd_put_keysym('\n');
+ break;
+
+ default:
+ if (control) {
+ kbd_put_keysym(sym & 0x1f);
+ } else {
+ kbd_put_keysym(sym);
+ }
+ break;
+ }
+ }
+ }
+}
+
+static const char *code2name(int keycode)
+{
+ return QKeyCode_str(qemu_input_key_number_to_qcode(keycode));
+}
+
+static void key_event(VncState *vs, int down, uint32_t sym)
+{
+ int keycode;
+ int lsym = sym;
+
+ if (lsym >= 'A' && lsym <= 'Z' && qemu_console_is_graphic(NULL)) {
+ lsym = lsym - 'A' + 'a';
+ }
+
+ keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF,
+ vs->vd->kbd, down) & SCANCODE_KEYMASK;
+ trace_vnc_key_event_map(down, sym, keycode, code2name(keycode));
+ do_key_event(vs, down, keycode, sym);
+}
+
+static void ext_key_event(VncState *vs, int down,
+ uint32_t sym, uint16_t keycode)
+{
+ /* if the user specifies a keyboard layout, always use it */
+ if (keyboard_layout) {
+ key_event(vs, down, sym);
+ } else {
+ trace_vnc_key_event_ext(down, sym, keycode, code2name(keycode));
+ do_key_event(vs, down, keycode, sym);
+ }
+}
+
+static void framebuffer_update_request(VncState *vs, int incremental,
+ int x, int y, int w, int h)
+{
+ if (incremental) {
+ if (vs->update != VNC_STATE_UPDATE_FORCE) {
+ vs->update = VNC_STATE_UPDATE_INCREMENTAL;
+ }
+ } else {
+ vs->update = VNC_STATE_UPDATE_FORCE;
+ vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
+ if (vnc_has_feature(vs, VNC_FEATURE_RESIZE_EXT)) {
+ vnc_desktop_resize_ext(vs, 0);
+ }
+ }
+}
+
+static void send_ext_key_event_ack(VncState *vs)
+{
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1);
+ vnc_framebuffer_update(vs, 0, 0,
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
+ VNC_ENCODING_EXT_KEY_EVENT);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void send_ext_audio_ack(VncState *vs)
+{
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1);
+ vnc_framebuffer_update(vs, 0, 0,
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
+ VNC_ENCODING_AUDIO);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void send_xvp_message(VncState *vs, int code)
+{
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_XVP);
+ vnc_write_u8(vs, 0); /* pad */
+ vnc_write_u8(vs, 1); /* version */
+ vnc_write_u8(vs, code);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+}
+
+static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
+{
+ int i;
+ unsigned int enc = 0;
+
+ vs->features = 0;
+ vs->vnc_encoding = 0;
+ vs->tight->compression = 9;
+ vs->tight->quality = -1; /* Lossless by default */
+ vs->absolute = -1;
+
+ /*
+ * Start from the end because the encodings are sent in order of preference.
+ * This way the preferred encoding (first encoding defined in the array)
+ * will be set at the end of the loop.
+ */
+ for (i = n_encodings - 1; i >= 0; i--) {
+ enc = encodings[i];
+ switch (enc) {
+ case VNC_ENCODING_RAW:
+ vs->vnc_encoding = enc;
+ break;
+ case VNC_ENCODING_HEXTILE:
+ vs->features |= VNC_FEATURE_HEXTILE_MASK;
+ vs->vnc_encoding = enc;
+ break;
+ case VNC_ENCODING_TIGHT:
+ vs->features |= VNC_FEATURE_TIGHT_MASK;
+ vs->vnc_encoding = enc;
+ break;
+#ifdef CONFIG_VNC_PNG
+ case VNC_ENCODING_TIGHT_PNG:
+ vs->features |= VNC_FEATURE_TIGHT_PNG_MASK;
+ vs->vnc_encoding = enc;
+ break;
+#endif
+ case VNC_ENCODING_ZLIB:
+ /*
+ * VNC_ENCODING_ZRLE compresses better than VNC_ENCODING_ZLIB.
+ * So prioritize ZRLE, even if the client hints that it prefers
+ * ZLIB.
+ */
+ if ((vs->features & VNC_FEATURE_ZRLE_MASK) == 0) {
+ vs->features |= VNC_FEATURE_ZLIB_MASK;
+ vs->vnc_encoding = enc;
+ }
+ break;
+ case VNC_ENCODING_ZRLE:
+ vs->features |= VNC_FEATURE_ZRLE_MASK;
+ vs->vnc_encoding = enc;
+ break;
+ case VNC_ENCODING_ZYWRLE:
+ vs->features |= VNC_FEATURE_ZYWRLE_MASK;
+ vs->vnc_encoding = enc;
+ break;
+ case VNC_ENCODING_DESKTOPRESIZE:
+ vs->features |= VNC_FEATURE_RESIZE_MASK;
+ break;
+ case VNC_ENCODING_DESKTOP_RESIZE_EXT:
+ vs->features |= VNC_FEATURE_RESIZE_EXT_MASK;
+ break;
+ case VNC_ENCODING_POINTER_TYPE_CHANGE:
+ vs->features |= VNC_FEATURE_POINTER_TYPE_CHANGE_MASK;
+ break;
+ case VNC_ENCODING_RICH_CURSOR:
+ vs->features |= VNC_FEATURE_RICH_CURSOR_MASK;
+ break;
+ case VNC_ENCODING_ALPHA_CURSOR:
+ vs->features |= VNC_FEATURE_ALPHA_CURSOR_MASK;
+ break;
+ case VNC_ENCODING_EXT_KEY_EVENT:
+ send_ext_key_event_ack(vs);
+ break;
+ case VNC_ENCODING_AUDIO:
+ send_ext_audio_ack(vs);
+ break;
+ case VNC_ENCODING_WMVi:
+ vs->features |= VNC_FEATURE_WMVI_MASK;
+ break;
+ case VNC_ENCODING_LED_STATE:
+ vs->features |= VNC_FEATURE_LED_STATE_MASK;
+ break;
+ case VNC_ENCODING_XVP:
+ if (vs->vd->power_control) {
+ vs->features |= VNC_FEATURE_XVP;
+ send_xvp_message(vs, VNC_XVP_CODE_INIT);
+ }
+ break;
+ case VNC_ENCODING_CLIPBOARD_EXT:
+ vs->features |= VNC_FEATURE_CLIPBOARD_EXT_MASK;
+ vnc_server_cut_text_caps(vs);
+ break;
+ case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9:
+ vs->tight->compression = (enc & 0x0F);
+ break;
+ case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9:
+ if (vs->vd->lossy) {
+ vs->tight->quality = (enc & 0x0F);
+ }
+ break;
+ default:
+ VNC_DEBUG("Unknown encoding: %d (0x%.8x): %d\n", i, enc, enc);
+ break;
+ }
+ }
+ vnc_desktop_resize(vs);
+ check_pointer_type_change(&vs->mouse_mode_notifier, NULL);
+ vnc_led_state_change(vs);
+ vnc_cursor_define(vs);
+}
+
+static void set_pixel_conversion(VncState *vs)
+{
+ pixman_format_code_t fmt = qemu_pixman_get_format(&vs->client_pf);
+
+ if (fmt == VNC_SERVER_FB_FORMAT) {
+ vs->write_pixels = vnc_write_pixels_copy;
+ vnc_hextile_set_pixel_conversion(vs, 0);
+ } else {
+ vs->write_pixels = vnc_write_pixels_generic;
+ vnc_hextile_set_pixel_conversion(vs, 1);
+ }
+}
+
+static void send_color_map(VncState *vs)
+{
+ int i;
+
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_SET_COLOUR_MAP_ENTRIES);
+ vnc_write_u8(vs, 0); /* padding */
+ vnc_write_u16(vs, 0); /* first color */
+ vnc_write_u16(vs, 256); /* # of colors */
+
+ for (i = 0; i < 256; i++) {
+ PixelFormat *pf = &vs->client_pf;
+
+ vnc_write_u16(vs, (((i >> pf->rshift) & pf->rmax) << (16 - pf->rbits)));
+ vnc_write_u16(vs, (((i >> pf->gshift) & pf->gmax) << (16 - pf->gbits)));
+ vnc_write_u16(vs, (((i >> pf->bshift) & pf->bmax) << (16 - pf->bbits)));
+ }
+ vnc_unlock_output(vs);
+}
+
+static void set_pixel_format(VncState *vs, int bits_per_pixel,
+ int big_endian_flag, int true_color_flag,
+ int red_max, int green_max, int blue_max,
+ int red_shift, int green_shift, int blue_shift)
+{
+ if (!true_color_flag) {
+ /* Expose a reasonable default 256 color map */
+ bits_per_pixel = 8;
+ red_max = 7;
+ green_max = 7;
+ blue_max = 3;
+ red_shift = 0;
+ green_shift = 3;
+ blue_shift = 6;
+ }
+
+ switch (bits_per_pixel) {
+ case 8:
+ case 16:
+ case 32:
+ break;
+ default:
+ vnc_client_error(vs);
+ return;
+ }
+
+ vs->client_pf.rmax = red_max ? red_max : 0xFF;
+ vs->client_pf.rbits = ctpopl(red_max);
+ vs->client_pf.rshift = red_shift;
+ vs->client_pf.rmask = red_max << red_shift;
+ vs->client_pf.gmax = green_max ? green_max : 0xFF;
+ vs->client_pf.gbits = ctpopl(green_max);
+ vs->client_pf.gshift = green_shift;
+ vs->client_pf.gmask = green_max << green_shift;
+ vs->client_pf.bmax = blue_max ? blue_max : 0xFF;
+ vs->client_pf.bbits = ctpopl(blue_max);
+ vs->client_pf.bshift = blue_shift;
+ vs->client_pf.bmask = blue_max << blue_shift;
+ vs->client_pf.bits_per_pixel = bits_per_pixel;
+ vs->client_pf.bytes_per_pixel = bits_per_pixel / 8;
+ vs->client_pf.depth = bits_per_pixel == 32 ? 24 : bits_per_pixel;
+ vs->client_be = big_endian_flag;
+
+ if (!true_color_flag) {
+ send_color_map(vs);
+ }
+
+ set_pixel_conversion(vs);
+
+ graphic_hw_invalidate(vs->vd->dcl.con);
+ graphic_hw_update(vs->vd->dcl.con);
+}
+
+static void pixel_format_message (VncState *vs) {
+ char pad[3] = { 0, 0, 0 };
+
+ vs->client_pf = qemu_default_pixelformat(32);
+
+ vnc_write_u8(vs, vs->client_pf.bits_per_pixel); /* bits-per-pixel */
+ vnc_write_u8(vs, vs->client_pf.depth); /* depth */
+
+#ifdef HOST_WORDS_BIGENDIAN
+ vnc_write_u8(vs, 1); /* big-endian-flag */
+#else
+ vnc_write_u8(vs, 0); /* big-endian-flag */
+#endif
+ vnc_write_u8(vs, 1); /* true-color-flag */
+ vnc_write_u16(vs, vs->client_pf.rmax); /* red-max */
+ vnc_write_u16(vs, vs->client_pf.gmax); /* green-max */
+ vnc_write_u16(vs, vs->client_pf.bmax); /* blue-max */
+ vnc_write_u8(vs, vs->client_pf.rshift); /* red-shift */
+ vnc_write_u8(vs, vs->client_pf.gshift); /* green-shift */
+ vnc_write_u8(vs, vs->client_pf.bshift); /* blue-shift */
+ vnc_write(vs, pad, 3); /* padding */
+
+ vnc_hextile_set_pixel_conversion(vs, 0);
+ vs->write_pixels = vnc_write_pixels_copy;
+}
+
+static void vnc_colordepth(VncState *vs)
+{
+ if (vnc_has_feature(vs, VNC_FEATURE_WMVI)) {
+ /* Sending a WMVi message to notify the client*/
+ vnc_lock_output(vs);
+ vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
+ vnc_write_u8(vs, 0);
+ vnc_write_u16(vs, 1); /* number of rects */
+ vnc_framebuffer_update(vs, 0, 0,
+ vs->client_width,
+ vs->client_height,
+ VNC_ENCODING_WMVi);
+ pixel_format_message(vs);
+ vnc_unlock_output(vs);
+ vnc_flush(vs);
+ } else {
+ set_pixel_conversion(vs);
+ }
+}
+
+static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
+{
+ int i;
+ uint16_t limit;
+ uint32_t freq;
+ VncDisplay *vd = vs->vd;
+
+ if (data[0] > 3) {
+ update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
+ }
+
+ switch (data[0]) {
+ case VNC_MSG_CLIENT_SET_PIXEL_FORMAT:
+ if (len == 1)
+ return 20;
+
+ set_pixel_format(vs, read_u8(data, 4),
+ read_u8(data, 6), read_u8(data, 7),
+ read_u16(data, 8), read_u16(data, 10),
+ read_u16(data, 12), read_u8(data, 14),
+ read_u8(data, 15), read_u8(data, 16));
+ break;
+ case VNC_MSG_CLIENT_SET_ENCODINGS:
+ if (len == 1)
+ return 4;
+
+ if (len == 4) {
+ limit = read_u16(data, 2);
+ if (limit > 0)
+ return 4 + (limit * 4);
+ } else
+ limit = read_u16(data, 2);
+
+ for (i = 0; i < limit; i++) {
+ int32_t val = read_s32(data, 4 + (i * 4));
+ memcpy(data + 4 + (i * 4), &val, sizeof(val));
+ }
+
+ set_encodings(vs, (int32_t *)(data + 4), limit);
+ break;
+ case VNC_MSG_CLIENT_FRAMEBUFFER_UPDATE_REQUEST:
+ if (len == 1)
+ return 10;
+
+ framebuffer_update_request(vs,
+ read_u8(data, 1), read_u16(data, 2), read_u16(data, 4),
+ read_u16(data, 6), read_u16(data, 8));
+ break;
+ case VNC_MSG_CLIENT_KEY_EVENT:
+ if (len == 1)
+ return 8;
+
+ key_event(vs, read_u8(data, 1), read_u32(data, 4));
+ break;
+ case VNC_MSG_CLIENT_POINTER_EVENT:
+ if (len == 1)
+ return 6;
+
+ pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4));
+ break;
+ case VNC_MSG_CLIENT_CUT_TEXT:
+ if (len == 1) {
+ return 8;
+ }
+ if (len == 8) {
+ uint32_t dlen = abs(read_s32(data, 4));
+ if (dlen > (1 << 20)) {
+ error_report("vnc: client_cut_text msg payload has %u bytes"
+ " which exceeds our limit of 1MB.", dlen);
+ vnc_client_error(vs);
+ break;
+ }
+ if (dlen > 0) {
+ return 8 + dlen;
+ }
+ }
+
+ if (read_s32(data, 4) < 0) {
+ vnc_client_cut_text_ext(vs, abs(read_s32(data, 4)),
+ read_u32(data, 8), data + 12);
+ break;
+ }
+ vnc_client_cut_text(vs, read_u32(data, 4), data + 8);
+ break;
+ case VNC_MSG_CLIENT_XVP:
+ if (!(vs->features & VNC_FEATURE_XVP)) {
+ error_report("vnc: xvp client message while disabled");
+ vnc_client_error(vs);
+ break;
+ }
+ if (len == 1) {
+ return 4;
+ }
+ if (len == 4) {
+ uint8_t version = read_u8(data, 2);
+ uint8_t action = read_u8(data, 3);
+
+ if (version != 1) {
+ error_report("vnc: xvp client message version %d != 1",
+ version);
+ vnc_client_error(vs);
+ break;
+ }
+
+ switch (action) {
+ case VNC_XVP_ACTION_SHUTDOWN:
+ qemu_system_powerdown_request();
+ break;
+ case VNC_XVP_ACTION_REBOOT:
+ send_xvp_message(vs, VNC_XVP_CODE_FAIL);
+ break;
+ case VNC_XVP_ACTION_RESET:
+ qemu_system_reset_request(SHUTDOWN_CAUSE_HOST_QMP_SYSTEM_RESET);
+ break;
+ default:
+ send_xvp_message(vs, VNC_XVP_CODE_FAIL);
+ break;
+ }
+ }
+ break;
+ case VNC_MSG_CLIENT_QEMU:
+ if (len == 1)
+ return 2;
+
+ switch (read_u8(data, 1)) {
+ case VNC_MSG_CLIENT_QEMU_EXT_KEY_EVENT:
+ if (len == 2)
+ return 12;
+
+ ext_key_event(vs, read_u16(data, 2),
+ read_u32(data, 4), read_u32(data, 8));
+ break;
+ case VNC_MSG_CLIENT_QEMU_AUDIO:
+ if (len == 2)
+ return 4;
+
+ switch (read_u16 (data, 2)) {
+ case VNC_MSG_CLIENT_QEMU_AUDIO_ENABLE:
+ trace_vnc_msg_client_audio_enable(vs, vs->ioc);
+ audio_add(vs);
+ break;
+ case VNC_MSG_CLIENT_QEMU_AUDIO_DISABLE:
+ trace_vnc_msg_client_audio_disable(vs, vs->ioc);
+ audio_del(vs);
+ break;
+ case VNC_MSG_CLIENT_QEMU_AUDIO_SET_FORMAT:
+ if (len == 4)
+ return 10;
+ switch (read_u8(data, 4)) {
+ case 0: vs->as.fmt = AUDIO_FORMAT_U8; break;
+ case 1: vs->as.fmt = AUDIO_FORMAT_S8; break;
+ case 2: vs->as.fmt = AUDIO_FORMAT_U16; break;
+ case 3: vs->as.fmt = AUDIO_FORMAT_S16; break;
+ case 4: vs->as.fmt = AUDIO_FORMAT_U32; break;
+ case 5: vs->as.fmt = AUDIO_FORMAT_S32; break;
+ default:
+ VNC_DEBUG("Invalid audio format %d\n", read_u8(data, 4));
+ vnc_client_error(vs);
+ break;
+ }
+ vs->as.nchannels = read_u8(data, 5);
+ if (vs->as.nchannels != 1 && vs->as.nchannels != 2) {
+ VNC_DEBUG("Invalid audio channel count %d\n",
+ read_u8(data, 5));
+ vnc_client_error(vs);
+ break;
+ }
+ freq = read_u32(data, 6);
+ /* No official limit for protocol, but 48khz is a sensible
+ * upper bound for trustworthy clients, and this limit
+ * protects calculations involving 'vs->as.freq' later.
+ */
+ if (freq > 48000) {
+ VNC_DEBUG("Invalid audio frequency %u > 48000", freq);
+ vnc_client_error(vs);
+ break;
+ }
+ vs->as.freq = freq;
+ trace_vnc_msg_client_audio_format(
+ vs, vs->ioc, vs->as.fmt, vs->as.nchannels, vs->as.freq);
+ break;
+ default:
+ VNC_DEBUG("Invalid audio message %d\n", read_u8(data, 4));
+ vnc_client_error(vs);
+ break;
+ }
+ break;
+
+ default:
+ VNC_DEBUG("Msg: %d\n", read_u16(data, 0));
+ vnc_client_error(vs);
+ break;
+ }
+ break;
+ case VNC_MSG_CLIENT_SET_DESKTOP_SIZE:
+ {
+ size_t size;
+ uint8_t screens;
+ int w, h;
+
+ if (len < 8) {
+ return 8;
+ }
+
+ screens = read_u8(data, 6);
+ size = 8 + screens * 16;
+ if (len < size) {
+ return size;
+ }
+ w = read_u16(data, 2);
+ h = read_u16(data, 4);
+
+ trace_vnc_msg_client_set_desktop_size(vs, vs->ioc, w, h, screens);
+ if (dpy_ui_info_supported(vs->vd->dcl.con)) {
+ QemuUIInfo info;
+ memset(&info, 0, sizeof(info));
+ info.width = w;
+ info.height = h;
+ dpy_set_ui_info(vs->vd->dcl.con, &info);
+ vnc_desktop_resize_ext(vs, 4 /* Request forwarded */);
+ } else {
+ vnc_desktop_resize_ext(vs, 3 /* Invalid screen layout */);
+ }
+
+ break;
+ }
+ default:
+ VNC_DEBUG("Msg: %d\n", data[0]);
+ vnc_client_error(vs);
+ break;
+ }
+
+ vnc_update_throttle_offset(vs);
+ vnc_read_when(vs, protocol_client_msg, 1);
+ return 0;
+}
+
+static int protocol_client_init(VncState *vs, uint8_t *data, size_t len)
+{
+ char buf[1024];
+ VncShareMode mode;
+ int size;
+
+ mode = data[0] ? VNC_SHARE_MODE_SHARED : VNC_SHARE_MODE_EXCLUSIVE;
+ switch (vs->vd->share_policy) {
+ case VNC_SHARE_POLICY_IGNORE:
+ /*
+ * Ignore the shared flag. Nothing to do here.
+ *
+ * Doesn't conform to the rfb spec but is traditional qemu
+ * behavior, thus left here as option for compatibility
+ * reasons.
+ */
+ break;
+ case VNC_SHARE_POLICY_ALLOW_EXCLUSIVE:
+ /*
+ * Policy: Allow clients ask for exclusive access.
+ *
+ * Implementation: When a client asks for exclusive access,
+ * disconnect all others. Shared connects are allowed as long
+ * as no exclusive connection exists.
+ *
+ * This is how the rfb spec suggests to handle the shared flag.
+ */
+ if (mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ VncState *client;
+ QTAILQ_FOREACH(client, &vs->vd->clients, next) {
+ if (vs == client) {
+ continue;
+ }
+ if (client->share_mode != VNC_SHARE_MODE_EXCLUSIVE &&
+ client->share_mode != VNC_SHARE_MODE_SHARED) {
+ continue;
+ }
+ vnc_disconnect_start(client);
+ }
+ }
+ if (mode == VNC_SHARE_MODE_SHARED) {
+ if (vs->vd->num_exclusive > 0) {
+ vnc_disconnect_start(vs);
+ return 0;
+ }
+ }
+ break;
+ case VNC_SHARE_POLICY_FORCE_SHARED:
+ /*
+ * Policy: Shared connects only.
+ * Implementation: Disallow clients asking for exclusive access.
+ *
+ * Useful for shared desktop sessions where you don't want
+ * someone forgetting to say -shared when running the vnc
+ * client disconnect everybody else.
+ */
+ if (mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ vnc_disconnect_start(vs);
+ return 0;
+ }
+ break;
+ }
+ vnc_set_share_mode(vs, mode);
+
+ if (vs->vd->num_shared > vs->vd->connections_limit) {
+ vnc_disconnect_start(vs);
+ return 0;
+ }
+
+ assert(pixman_image_get_width(vs->vd->server) < 65536 &&
+ pixman_image_get_width(vs->vd->server) >= 0);
+ assert(pixman_image_get_height(vs->vd->server) < 65536 &&
+ pixman_image_get_height(vs->vd->server) >= 0);
+ vs->client_width = pixman_image_get_width(vs->vd->server);
+ vs->client_height = pixman_image_get_height(vs->vd->server);
+ vnc_write_u16(vs, vs->client_width);
+ vnc_write_u16(vs, vs->client_height);
+
+ pixel_format_message(vs);
+
+ if (qemu_name) {
+ size = snprintf(buf, sizeof(buf), "QEMU (%s)", qemu_name);
+ if (size > sizeof(buf)) {
+ size = sizeof(buf);
+ }
+ } else {
+ size = snprintf(buf, sizeof(buf), "QEMU");
+ }
+
+ vnc_write_u32(vs, size);
+ vnc_write(vs, buf, size);
+ vnc_flush(vs);
+
+ vnc_client_cache_auth(vs);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_INITIALIZED);
+
+ vnc_read_when(vs, protocol_client_msg, 1);
+
+ return 0;
+}
+
+void start_client_init(VncState *vs)
+{
+ vnc_read_when(vs, protocol_client_init, 1);
+}
+
+static void authentication_failed(VncState *vs)
+{
+ vnc_write_u32(vs, 1); /* Reject auth */
+ if (vs->minor >= 8) {
+ static const char err[] = "Authentication failed";
+ vnc_write_u32(vs, sizeof(err));
+ vnc_write(vs, err, sizeof(err));
+ }
+ vnc_flush(vs);
+ vnc_client_error(vs);
+}
+
+static void
+vnc_munge_des_rfb_key(unsigned char *key, size_t nkey)
+{
+ size_t i;
+ for (i = 0; i < nkey; i++) {
+ uint8_t r = key[i];
+ r = (r & 0xf0) >> 4 | (r & 0x0f) << 4;
+ r = (r & 0xcc) >> 2 | (r & 0x33) << 2;
+ r = (r & 0xaa) >> 1 | (r & 0x55) << 1;
+ key[i] = r;
+ }
+}
+
+static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
+{
+ unsigned char response[VNC_AUTH_CHALLENGE_SIZE];
+ size_t i, pwlen;
+ unsigned char key[8];
+ time_t now = time(NULL);
+ QCryptoCipher *cipher = NULL;
+ Error *err = NULL;
+
+ if (!vs->vd->password) {
+ trace_vnc_auth_fail(vs, vs->auth, "password is not set", "");
+ goto reject;
+ }
+ if (vs->vd->expires < now) {
+ trace_vnc_auth_fail(vs, vs->auth, "password is expired", "");
+ goto reject;
+ }
+
+ memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE);
+
+ /* Calculate the expected challenge response */
+ pwlen = strlen(vs->vd->password);
+ for (i=0; i<sizeof(key); i++)
+ key[i] = i<pwlen ? vs->vd->password[i] : 0;
+ vnc_munge_des_rfb_key(key, sizeof(key));
+
+ cipher = qcrypto_cipher_new(
+ QCRYPTO_CIPHER_ALG_DES,
+ QCRYPTO_CIPHER_MODE_ECB,
+ key, G_N_ELEMENTS(key),
+ &err);
+ if (!cipher) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot create cipher",
+ error_get_pretty(err));
+ error_free(err);
+ goto reject;
+ }
+
+ if (qcrypto_cipher_encrypt(cipher,
+ vs->challenge,
+ response,
+ VNC_AUTH_CHALLENGE_SIZE,
+ &err) < 0) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot encrypt challenge response",
+ error_get_pretty(err));
+ error_free(err);
+ goto reject;
+ }
+
+ /* Compare expected vs actual challenge response */
+ if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) {
+ trace_vnc_auth_fail(vs, vs->auth, "mis-matched challenge response", "");
+ goto reject;
+ } else {
+ trace_vnc_auth_pass(vs, vs->auth);
+ vnc_write_u32(vs, 0); /* Accept auth */
+ vnc_flush(vs);
+
+ start_client_init(vs);
+ }
+
+ qcrypto_cipher_free(cipher);
+ return 0;
+
+reject:
+ authentication_failed(vs);
+ qcrypto_cipher_free(cipher);
+ return 0;
+}
+
+void start_auth_vnc(VncState *vs)
+{
+ Error *err = NULL;
+
+ if (qcrypto_random_bytes(vs->challenge, sizeof(vs->challenge), &err)) {
+ trace_vnc_auth_fail(vs, vs->auth, "cannot get random bytes",
+ error_get_pretty(err));
+ error_free(err);
+ authentication_failed(vs);
+ return;
+ }
+
+ /* Send client a 'random' challenge */
+ vnc_write(vs, vs->challenge, sizeof(vs->challenge));
+ vnc_flush(vs);
+
+ vnc_read_when(vs, protocol_client_auth_vnc, sizeof(vs->challenge));
+}
+
+
+static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len)
+{
+ /* We only advertise 1 auth scheme at a time, so client
+ * must pick the one we sent. Verify this */
+ if (data[0] != vs->auth) { /* Reject auth */
+ trace_vnc_auth_reject(vs, vs->auth, (int)data[0]);
+ authentication_failed(vs);
+ } else { /* Accept requested auth */
+ trace_vnc_auth_start(vs, vs->auth);
+ switch (vs->auth) {
+ case VNC_AUTH_NONE:
+ if (vs->minor >= 8) {
+ vnc_write_u32(vs, 0); /* Accept auth completion */
+ vnc_flush(vs);
+ }
+ trace_vnc_auth_pass(vs, vs->auth);
+ start_client_init(vs);
+ break;
+
+ case VNC_AUTH_VNC:
+ start_auth_vnc(vs);
+ break;
+
+ case VNC_AUTH_VENCRYPT:
+ start_auth_vencrypt(vs);
+ break;
+
+#ifdef CONFIG_VNC_SASL
+ case VNC_AUTH_SASL:
+ start_auth_sasl(vs);
+ break;
+#endif /* CONFIG_VNC_SASL */
+
+ default: /* Should not be possible, but just in case */
+ trace_vnc_auth_fail(vs, vs->auth, "Unhandled auth method", "");
+ authentication_failed(vs);
+ }
+ }
+ return 0;
+}
+
+static int protocol_version(VncState *vs, uint8_t *version, size_t len)
+{
+ char local[13];
+
+ memcpy(local, version, 12);
+ local[12] = 0;
+
+ if (sscanf(local, "RFB %03d.%03d\n", &vs->major, &vs->minor) != 2) {
+ VNC_DEBUG("Malformed protocol version %s\n", local);
+ vnc_client_error(vs);
+ return 0;
+ }
+ VNC_DEBUG("Client request protocol version %d.%d\n", vs->major, vs->minor);
+ if (vs->major != 3 ||
+ (vs->minor != 3 &&
+ vs->minor != 4 &&
+ vs->minor != 5 &&
+ vs->minor != 7 &&
+ vs->minor != 8)) {
+ VNC_DEBUG("Unsupported client version\n");
+ vnc_write_u32(vs, VNC_AUTH_INVALID);
+ vnc_flush(vs);
+ vnc_client_error(vs);
+ return 0;
+ }
+ /* Some broken clients report v3.4 or v3.5, which spec requires to be treated
+ * as equivalent to v3.3 by servers
+ */
+ if (vs->minor == 4 || vs->minor == 5)
+ vs->minor = 3;
+
+ if (vs->minor == 3) {
+ trace_vnc_auth_start(vs, vs->auth);
+ if (vs->auth == VNC_AUTH_NONE) {
+ vnc_write_u32(vs, vs->auth);
+ vnc_flush(vs);
+ trace_vnc_auth_pass(vs, vs->auth);
+ start_client_init(vs);
+ } else if (vs->auth == VNC_AUTH_VNC) {
+ VNC_DEBUG("Tell client VNC auth\n");
+ vnc_write_u32(vs, vs->auth);
+ vnc_flush(vs);
+ start_auth_vnc(vs);
+ } else {
+ trace_vnc_auth_fail(vs, vs->auth,
+ "Unsupported auth method for v3.3", "");
+ vnc_write_u32(vs, VNC_AUTH_INVALID);
+ vnc_flush(vs);
+ vnc_client_error(vs);
+ }
+ } else {
+ vnc_write_u8(vs, 1); /* num auth */
+ vnc_write_u8(vs, vs->auth);
+ vnc_read_when(vs, protocol_client_auth, 1);
+ vnc_flush(vs);
+ }
+
+ return 0;
+}
+
+static VncRectStat *vnc_stat_rect(VncDisplay *vd, int x, int y)
+{
+ struct VncSurface *vs = &vd->guest;
+
+ return &vs->stats[y / VNC_STAT_RECT][x / VNC_STAT_RECT];
+}
+
+void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h)
+{
+ int i, j;
+
+ w = (x + w) / VNC_STAT_RECT;
+ h = (y + h) / VNC_STAT_RECT;
+ x /= VNC_STAT_RECT;
+ y /= VNC_STAT_RECT;
+
+ for (j = y; j <= h; j++) {
+ for (i = x; i <= w; i++) {
+ vs->lossy_rect[j][i] = 1;
+ }
+ }
+}
+
+static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y)
+{
+ VncState *vs;
+ int sty = y / VNC_STAT_RECT;
+ int stx = x / VNC_STAT_RECT;
+ int has_dirty = 0;
+
+ y = QEMU_ALIGN_DOWN(y, VNC_STAT_RECT);
+ x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT);
+
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ int j;
+
+ /* kernel send buffers are full -> refresh later */
+ if (vs->output.offset) {
+ continue;
+ }
+
+ if (!vs->lossy_rect[sty][stx]) {
+ continue;
+ }
+
+ vs->lossy_rect[sty][stx] = 0;
+ for (j = 0; j < VNC_STAT_RECT; ++j) {
+ bitmap_set(vs->dirty[y + j],
+ x / VNC_DIRTY_PIXELS_PER_BIT,
+ VNC_STAT_RECT / VNC_DIRTY_PIXELS_PER_BIT);
+ }
+ has_dirty++;
+ }
+
+ return has_dirty;
+}
+
+static int vnc_update_stats(VncDisplay *vd, struct timeval * tv)
+{
+ int width = MIN(pixman_image_get_width(vd->guest.fb),
+ pixman_image_get_width(vd->server));
+ int height = MIN(pixman_image_get_height(vd->guest.fb),
+ pixman_image_get_height(vd->server));
+ int x, y;
+ struct timeval res;
+ int has_dirty = 0;
+
+ for (y = 0; y < height; y += VNC_STAT_RECT) {
+ for (x = 0; x < width; x += VNC_STAT_RECT) {
+ VncRectStat *rect = vnc_stat_rect(vd, x, y);
+
+ rect->updated = false;
+ }
+ }
+
+ qemu_timersub(tv, &VNC_REFRESH_STATS, &res);
+
+ if (timercmp(&vd->guest.last_freq_check, &res, >)) {
+ return has_dirty;
+ }
+ vd->guest.last_freq_check = *tv;
+
+ for (y = 0; y < height; y += VNC_STAT_RECT) {
+ for (x = 0; x < width; x += VNC_STAT_RECT) {
+ VncRectStat *rect= vnc_stat_rect(vd, x, y);
+ int count = ARRAY_SIZE(rect->times);
+ struct timeval min, max;
+
+ if (!timerisset(&rect->times[count - 1])) {
+ continue ;
+ }
+
+ max = rect->times[(rect->idx + count - 1) % count];
+ qemu_timersub(tv, &max, &res);
+
+ if (timercmp(&res, &VNC_REFRESH_LOSSY, >)) {
+ rect->freq = 0;
+ has_dirty += vnc_refresh_lossy_rect(vd, x, y);
+ memset(rect->times, 0, sizeof (rect->times));
+ continue ;
+ }
+
+ min = rect->times[rect->idx];
+ max = rect->times[(rect->idx + count - 1) % count];
+ qemu_timersub(&max, &min, &res);
+
+ rect->freq = res.tv_sec + res.tv_usec / 1000000.;
+ rect->freq /= count;
+ rect->freq = 1. / rect->freq;
+ }
+ }
+ return has_dirty;
+}
+
+double vnc_update_freq(VncState *vs, int x, int y, int w, int h)
+{
+ int i, j;
+ double total = 0;
+ int num = 0;
+
+ x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT);
+ y = QEMU_ALIGN_DOWN(y, VNC_STAT_RECT);
+
+ for (j = y; j <= y + h; j += VNC_STAT_RECT) {
+ for (i = x; i <= x + w; i += VNC_STAT_RECT) {
+ total += vnc_stat_rect(vs->vd, i, j)->freq;
+ num++;
+ }
+ }
+
+ if (num) {
+ return total / num;
+ } else {
+ return 0;
+ }
+}
+
+static void vnc_rect_updated(VncDisplay *vd, int x, int y, struct timeval * tv)
+{
+ VncRectStat *rect;
+
+ rect = vnc_stat_rect(vd, x, y);
+ if (rect->updated) {
+ return ;
+ }
+ rect->times[rect->idx] = *tv;
+ rect->idx = (rect->idx + 1) % ARRAY_SIZE(rect->times);
+ rect->updated = true;
+}
+
+static int vnc_refresh_server_surface(VncDisplay *vd)
+{
+ int width = MIN(pixman_image_get_width(vd->guest.fb),
+ pixman_image_get_width(vd->server));
+ int height = MIN(pixman_image_get_height(vd->guest.fb),
+ pixman_image_get_height(vd->server));
+ int cmp_bytes, server_stride, line_bytes, guest_ll, guest_stride, y = 0;
+ uint8_t *guest_row0 = NULL, *server_row0;
+ VncState *vs;
+ int has_dirty = 0;
+ pixman_image_t *tmpbuf = NULL;
+
+ struct timeval tv = { 0, 0 };
+
+ if (!vd->non_adaptive) {
+ gettimeofday(&tv, NULL);
+ has_dirty = vnc_update_stats(vd, &tv);
+ }
+
+ /*
+ * Walk through the guest dirty map.
+ * Check and copy modified bits from guest to server surface.
+ * Update server dirty map.
+ */
+ server_row0 = (uint8_t *)pixman_image_get_data(vd->server);
+ server_stride = guest_stride = guest_ll =
+ pixman_image_get_stride(vd->server);
+ cmp_bytes = MIN(VNC_DIRTY_PIXELS_PER_BIT * VNC_SERVER_FB_BYTES,
+ server_stride);
+ if (vd->guest.format != VNC_SERVER_FB_FORMAT) {
+ int width = pixman_image_get_width(vd->server);
+ tmpbuf = qemu_pixman_linebuf_create(VNC_SERVER_FB_FORMAT, width);
+ } else {
+ int guest_bpp =
+ PIXMAN_FORMAT_BPP(pixman_image_get_format(vd->guest.fb));
+ guest_row0 = (uint8_t *)pixman_image_get_data(vd->guest.fb);
+ guest_stride = pixman_image_get_stride(vd->guest.fb);
+ guest_ll = pixman_image_get_width(vd->guest.fb)
+ * DIV_ROUND_UP(guest_bpp, 8);
+ }
+ line_bytes = MIN(server_stride, guest_ll);
+
+ for (;;) {
+ int x;
+ uint8_t *guest_ptr, *server_ptr;
+ unsigned long offset = find_next_bit((unsigned long *) &vd->guest.dirty,
+ height * VNC_DIRTY_BPL(&vd->guest),
+ y * VNC_DIRTY_BPL(&vd->guest));
+ if (offset == height * VNC_DIRTY_BPL(&vd->guest)) {
+ /* no more dirty bits */
+ break;
+ }
+ y = offset / VNC_DIRTY_BPL(&vd->guest);
+ x = offset % VNC_DIRTY_BPL(&vd->guest);
+
+ server_ptr = server_row0 + y * server_stride + x * cmp_bytes;
+
+ if (vd->guest.format != VNC_SERVER_FB_FORMAT) {
+ qemu_pixman_linebuf_fill(tmpbuf, vd->guest.fb, width, 0, y);
+ guest_ptr = (uint8_t *)pixman_image_get_data(tmpbuf);
+ } else {
+ guest_ptr = guest_row0 + y * guest_stride;
+ }
+ guest_ptr += x * cmp_bytes;
+
+ for (; x < DIV_ROUND_UP(width, VNC_DIRTY_PIXELS_PER_BIT);
+ x++, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) {
+ int _cmp_bytes = cmp_bytes;
+ if (!test_and_clear_bit(x, vd->guest.dirty[y])) {
+ continue;
+ }
+ if ((x + 1) * cmp_bytes > line_bytes) {
+ _cmp_bytes = line_bytes - x * cmp_bytes;
+ }
+ assert(_cmp_bytes >= 0);
+ if (memcmp(server_ptr, guest_ptr, _cmp_bytes) == 0) {
+ continue;
+ }
+ memcpy(server_ptr, guest_ptr, _cmp_bytes);
+ if (!vd->non_adaptive) {
+ vnc_rect_updated(vd, x * VNC_DIRTY_PIXELS_PER_BIT,
+ y, &tv);
+ }
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ set_bit(x, vs->dirty[y]);
+ }
+ has_dirty++;
+ }
+
+ y++;
+ }
+ qemu_pixman_image_unref(tmpbuf);
+ return has_dirty;
+}
+
+static void vnc_refresh(DisplayChangeListener *dcl)
+{
+ VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
+ VncState *vs, *vn;
+ int has_dirty, rects = 0;
+
+ if (QTAILQ_EMPTY(&vd->clients)) {
+ update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_MAX);
+ return;
+ }
+
+ graphic_hw_update(vd->dcl.con);
+
+ if (vnc_trylock_display(vd)) {
+ update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
+ return;
+ }
+
+ has_dirty = vnc_refresh_server_surface(vd);
+ vnc_unlock_display(vd);
+
+ QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
+ rects += vnc_update_client(vs, has_dirty);
+ /* vs might be free()ed here */
+ }
+
+ if (has_dirty && rects) {
+ vd->dcl.update_interval /= 2;
+ if (vd->dcl.update_interval < VNC_REFRESH_INTERVAL_BASE) {
+ vd->dcl.update_interval = VNC_REFRESH_INTERVAL_BASE;
+ }
+ } else {
+ vd->dcl.update_interval += VNC_REFRESH_INTERVAL_INC;
+ if (vd->dcl.update_interval > VNC_REFRESH_INTERVAL_MAX) {
+ vd->dcl.update_interval = VNC_REFRESH_INTERVAL_MAX;
+ }
+ }
+}
+
+static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
+ bool skipauth, bool websocket)
+{
+ VncState *vs = g_new0(VncState, 1);
+ bool first_client = QTAILQ_EMPTY(&vd->clients);
+ int i;
+
+ trace_vnc_client_connect(vs, sioc);
+ vs->zrle = g_new0(VncZrle, 1);
+ vs->tight = g_new0(VncTight, 1);
+ vs->magic = VNC_MAGIC;
+ vs->sioc = sioc;
+ object_ref(OBJECT(vs->sioc));
+ vs->ioc = QIO_CHANNEL(sioc);
+ object_ref(OBJECT(vs->ioc));
+ vs->vd = vd;
+
+ buffer_init(&vs->input, "vnc-input/%p", sioc);
+ buffer_init(&vs->output, "vnc-output/%p", sioc);
+ buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc);
+
+ buffer_init(&vs->tight->tight, "vnc-tight/%p", sioc);
+ buffer_init(&vs->tight->zlib, "vnc-tight-zlib/%p", sioc);
+ buffer_init(&vs->tight->gradient, "vnc-tight-gradient/%p", sioc);
+#ifdef CONFIG_VNC_JPEG
+ buffer_init(&vs->tight->jpeg, "vnc-tight-jpeg/%p", sioc);
+#endif
+#ifdef CONFIG_VNC_PNG
+ buffer_init(&vs->tight->png, "vnc-tight-png/%p", sioc);
+#endif
+ buffer_init(&vs->zlib.zlib, "vnc-zlib/%p", sioc);
+ buffer_init(&vs->zrle->zrle, "vnc-zrle/%p", sioc);
+ buffer_init(&vs->zrle->fb, "vnc-zrle-fb/%p", sioc);
+ buffer_init(&vs->zrle->zlib, "vnc-zrle-zlib/%p", sioc);
+
+ if (skipauth) {
+ vs->auth = VNC_AUTH_NONE;
+ vs->subauth = VNC_AUTH_INVALID;
+ } else {
+ if (websocket) {
+ vs->auth = vd->ws_auth;
+ vs->subauth = VNC_AUTH_INVALID;
+ } else {
+ vs->auth = vd->auth;
+ vs->subauth = vd->subauth;
+ }
+ }
+ VNC_DEBUG("Client sioc=%p ws=%d auth=%d subauth=%d\n",
+ sioc, websocket, vs->auth, vs->subauth);
+
+ vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect));
+ for (i = 0; i < VNC_STAT_ROWS; ++i) {
+ vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS);
+ }
+
+ VNC_DEBUG("New client on socket %p\n", vs->sioc);
+ update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
+ qio_channel_set_blocking(vs->ioc, false, NULL);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
+ if (websocket) {
+ vs->websocket = 1;
+ if (vd->tlscreds) {
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vncws_tls_handshake_io, vs, NULL);
+ } else {
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vncws_handshake_io, vs, NULL);
+ }
+ } else {
+ vs->ioc_tag = qio_channel_add_watch(
+ vs->ioc, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ vnc_client_io, vs, NULL);
+ }
+
+ vnc_client_cache_addr(vs);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED);
+ vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
+
+ vs->last_x = -1;
+ vs->last_y = -1;
+
+ vs->as.freq = 44100;
+ vs->as.nchannels = 2;
+ vs->as.fmt = AUDIO_FORMAT_S16;
+ vs->as.endianness = 0;
+
+ qemu_mutex_init(&vs->output_mutex);
+ vs->bh = qemu_bh_new(vnc_jobs_bh, vs);
+
+ QTAILQ_INSERT_TAIL(&vd->clients, vs, next);
+ if (first_client) {
+ vnc_update_server_surface(vd);
+ }
+
+ graphic_hw_update(vd->dcl.con);
+
+ if (!vs->websocket) {
+ vnc_start_protocol(vs);
+ }
+
+ if (vd->num_connecting > vd->connections_limit) {
+ QTAILQ_FOREACH(vs, &vd->clients, next) {
+ if (vs->share_mode == VNC_SHARE_MODE_CONNECTING) {
+ vnc_disconnect_start(vs);
+ return;
+ }
+ }
+ }
+}
+
+void vnc_start_protocol(VncState *vs)
+{
+ vnc_write(vs, "RFB 003.008\n", 12);
+ vnc_flush(vs);
+ vnc_read_when(vs, protocol_version, 12);
+
+ vs->mouse_mode_notifier.notify = check_pointer_type_change;
+ qemu_add_mouse_mode_change_notifier(&vs->mouse_mode_notifier);
+}
+
+static void vnc_listen_io(QIONetListener *listener,
+ QIOChannelSocket *cioc,
+ void *opaque)
+{
+ VncDisplay *vd = opaque;
+ bool isWebsock = listener == vd->wslistener;
+
+ qio_channel_set_name(QIO_CHANNEL(cioc),
+ isWebsock ? "vnc-ws-server" : "vnc-server");
+ qio_channel_set_delay(QIO_CHANNEL(cioc), false);
+ vnc_connect(vd, cioc, false, isWebsock);
+}
+
+static const DisplayChangeListenerOps dcl_ops = {
+ .dpy_name = "vnc",
+ .dpy_refresh = vnc_refresh,
+ .dpy_gfx_update = vnc_dpy_update,
+ .dpy_gfx_switch = vnc_dpy_switch,
+ .dpy_gfx_check_format = qemu_pixman_check_format,
+ .dpy_mouse_set = vnc_mouse_set,
+ .dpy_cursor_define = vnc_dpy_cursor_define,
+};
+
+void vnc_display_init(const char *id, Error **errp)
+{
+ VncDisplay *vd;
+
+ if (vnc_display_find(id) != NULL) {
+ return;
+ }
+ vd = g_malloc0(sizeof(*vd));
+
+ vd->id = strdup(id);
+ QTAILQ_INSERT_TAIL(&vnc_displays, vd, next);
+
+ QTAILQ_INIT(&vd->clients);
+ vd->expires = TIME_MAX;
+
+ if (keyboard_layout) {
+ trace_vnc_key_map_init(keyboard_layout);
+ vd->kbd_layout = init_keyboard_layout(name2keysym,
+ keyboard_layout, errp);
+ } else {
+ vd->kbd_layout = init_keyboard_layout(name2keysym, "en-us", errp);
+ }
+
+ if (!vd->kbd_layout) {
+ return;
+ }
+
+ vd->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
+ vd->connections_limit = 32;
+
+ qemu_mutex_init(&vd->mutex);
+ vnc_start_worker_thread();
+
+ vd->dcl.ops = &dcl_ops;
+ register_displaychangelistener(&vd->dcl);
+ vd->kbd = qkbd_state_init(vd->dcl.con);
+}
+
+
+static void vnc_display_close(VncDisplay *vd)
+{
+ if (!vd) {
+ return;
+ }
+ vd->is_unix = false;
+
+ if (vd->listener) {
+ qio_net_listener_disconnect(vd->listener);
+ object_unref(OBJECT(vd->listener));
+ }
+ vd->listener = NULL;
+
+ if (vd->wslistener) {
+ qio_net_listener_disconnect(vd->wslistener);
+ object_unref(OBJECT(vd->wslistener));
+ }
+ vd->wslistener = NULL;
+
+ vd->auth = VNC_AUTH_INVALID;
+ vd->subauth = VNC_AUTH_INVALID;
+ if (vd->tlscreds) {
+ object_unref(OBJECT(vd->tlscreds));
+ vd->tlscreds = NULL;
+ }
+ if (vd->tlsauthz) {
+ object_unparent(OBJECT(vd->tlsauthz));
+ vd->tlsauthz = NULL;
+ }
+ g_free(vd->tlsauthzid);
+ vd->tlsauthzid = NULL;
+ if (vd->lock_key_sync) {
+ qemu_remove_led_event_handler(vd->led);
+ vd->led = NULL;
+ }
+#ifdef CONFIG_VNC_SASL
+ if (vd->sasl.authz) {
+ object_unparent(OBJECT(vd->sasl.authz));
+ vd->sasl.authz = NULL;
+ }
+ g_free(vd->sasl.authzid);
+ vd->sasl.authzid = NULL;
+#endif
+}
+
+int vnc_display_password(const char *id, const char *password)
+{
+ VncDisplay *vd = vnc_display_find(id);
+
+ if (!vd) {
+ return -EINVAL;
+ }
+ if (vd->auth == VNC_AUTH_NONE) {
+ error_printf_unless_qmp("If you want use passwords please enable "
+ "password auth using '-vnc ${dpy},password'.\n");
+ return -EINVAL;
+ }
+
+ g_free(vd->password);
+ vd->password = g_strdup(password);
+
+ return 0;
+}
+
+int vnc_display_pw_expire(const char *id, time_t expires)
+{
+ VncDisplay *vd = vnc_display_find(id);
+
+ if (!vd) {
+ return -EINVAL;
+ }
+
+ vd->expires = expires;
+ return 0;
+}
+
+static void vnc_display_print_local_addr(VncDisplay *vd)
+{
+ SocketAddress *addr;
+
+ if (!vd->listener || !vd->listener->nsioc) {
+ return;
+ }
+
+ addr = qio_channel_socket_get_local_address(vd->listener->sioc[0], NULL);
+ if (!addr) {
+ return;
+ }
+
+ if (addr->type != SOCKET_ADDRESS_TYPE_INET) {
+ qapi_free_SocketAddress(addr);
+ return;
+ }
+ error_printf_unless_qmp("VNC server running on %s:%s\n",
+ addr->u.inet.host,
+ addr->u.inet.port);
+ qapi_free_SocketAddress(addr);
+}
+
+static QemuOptsList qemu_vnc_opts = {
+ .name = "vnc",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_vnc_opts.head),
+ .implied_opt_name = "vnc",
+ .desc = {
+ {
+ .name = "vnc",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "websocket",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "tls-creds",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "share",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "display",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "head",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "connections",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "to",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "ipv4",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "ipv6",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "password",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "password-secret",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "reverse",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "lock-key-sync",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "key-delay-ms",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "sasl",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "tls-authz",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "sasl-authz",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "lossy",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "non-adaptive",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "audiodev",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "power-control",
+ .type = QEMU_OPT_BOOL,
+ },
+ { /* end of list */ }
+ },
+};
+
+
+static int
+vnc_display_setup_auth(int *auth,
+ int *subauth,
+ QCryptoTLSCreds *tlscreds,
+ bool password,
+ bool sasl,
+ bool websocket,
+ Error **errp)
+{
+ /*
+ * We have a choice of 3 authentication options
+ *
+ * 1. none
+ * 2. vnc
+ * 3. sasl
+ *
+ * The channel can be run in 2 modes
+ *
+ * 1. clear
+ * 2. tls
+ *
+ * And TLS can use 2 types of credentials
+ *
+ * 1. anon
+ * 2. x509
+ *
+ * We thus have 9 possible logical combinations
+ *
+ * 1. clear + none
+ * 2. clear + vnc
+ * 3. clear + sasl
+ * 4. tls + anon + none
+ * 5. tls + anon + vnc
+ * 6. tls + anon + sasl
+ * 7. tls + x509 + none
+ * 8. tls + x509 + vnc
+ * 9. tls + x509 + sasl
+ *
+ * These need to be mapped into the VNC auth schemes
+ * in an appropriate manner. In regular VNC, all the
+ * TLS options get mapped into VNC_AUTH_VENCRYPT
+ * sub-auth types.
+ *
+ * In websockets, the https:// protocol already provides
+ * TLS support, so there is no need to make use of the
+ * VeNCrypt extension. Furthermore, websockets browser
+ * clients could not use VeNCrypt even if they wanted to,
+ * as they cannot control when the TLS handshake takes
+ * place. Thus there is no option but to rely on https://,
+ * meaning combinations 4->6 and 7->9 will be mapped to
+ * VNC auth schemes in the same way as combos 1->3.
+ *
+ * Regardless of fact that we have a different mapping to
+ * VNC auth mechs for plain VNC vs websockets VNC, the end
+ * result has the same security characteristics.
+ */
+ if (websocket || !tlscreds) {
+ if (password) {
+ VNC_DEBUG("Initializing VNC server with password auth\n");
+ *auth = VNC_AUTH_VNC;
+ } else if (sasl) {
+ VNC_DEBUG("Initializing VNC server with SASL auth\n");
+ *auth = VNC_AUTH_SASL;
+ } else {
+ VNC_DEBUG("Initializing VNC server with no auth\n");
+ *auth = VNC_AUTH_NONE;
+ }
+ *subauth = VNC_AUTH_INVALID;
+ } else {
+ bool is_x509 = object_dynamic_cast(OBJECT(tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_X509) != NULL;
+ bool is_anon = object_dynamic_cast(OBJECT(tlscreds),
+ TYPE_QCRYPTO_TLS_CREDS_ANON) != NULL;
+
+ if (!is_x509 && !is_anon) {
+ error_setg(errp,
+ "Unsupported TLS cred type %s",
+ object_get_typename(OBJECT(tlscreds)));
+ return -1;
+ }
+ *auth = VNC_AUTH_VENCRYPT;
+ if (password) {
+ if (is_x509) {
+ VNC_DEBUG("Initializing VNC server with x509 password auth\n");
+ *subauth = VNC_AUTH_VENCRYPT_X509VNC;
+ } else {
+ VNC_DEBUG("Initializing VNC server with TLS password auth\n");
+ *subauth = VNC_AUTH_VENCRYPT_TLSVNC;
+ }
+
+ } else if (sasl) {
+ if (is_x509) {
+ VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
+ *subauth = VNC_AUTH_VENCRYPT_X509SASL;
+ } else {
+ VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
+ *subauth = VNC_AUTH_VENCRYPT_TLSSASL;
+ }
+ } else {
+ if (is_x509) {
+ VNC_DEBUG("Initializing VNC server with x509 no auth\n");
+ *subauth = VNC_AUTH_VENCRYPT_X509NONE;
+ } else {
+ VNC_DEBUG("Initializing VNC server with TLS no auth\n");
+ *subauth = VNC_AUTH_VENCRYPT_TLSNONE;
+ }
+ }
+ }
+ return 0;
+}
+
+
+static int vnc_display_get_address(const char *addrstr,
+ bool websocket,
+ bool reverse,
+ int displaynum,
+ int to,
+ bool has_ipv4,
+ bool has_ipv6,
+ bool ipv4,
+ bool ipv6,
+ SocketAddress **retaddr,
+ Error **errp)
+{
+ int ret = -1;
+ SocketAddress *addr = NULL;
+
+ addr = g_new0(SocketAddress, 1);
+
+ if (strncmp(addrstr, "unix:", 5) == 0) {
+ addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ addr->u.q_unix.path = g_strdup(addrstr + 5);
+
+ if (websocket) {
+ error_setg(errp, "UNIX sockets not supported with websock");
+ goto cleanup;
+ }
+
+ if (to) {
+ error_setg(errp, "Port range not support with UNIX socket");
+ goto cleanup;
+ }
+ ret = 0;
+ } else {
+ const char *port;
+ size_t hostlen;
+ unsigned long long baseport = 0;
+ InetSocketAddress *inet;
+
+ port = strrchr(addrstr, ':');
+ if (!port) {
+ if (websocket) {
+ hostlen = 0;
+ port = addrstr;
+ } else {
+ error_setg(errp, "no vnc port specified");
+ goto cleanup;
+ }
+ } else {
+ hostlen = port - addrstr;
+ port++;
+ if (*port == '\0') {
+ error_setg(errp, "vnc port cannot be empty");
+ goto cleanup;
+ }
+ }
+
+ addr->type = SOCKET_ADDRESS_TYPE_INET;
+ inet = &addr->u.inet;
+ if (addrstr[0] == '[' && addrstr[hostlen - 1] == ']') {
+ inet->host = g_strndup(addrstr + 1, hostlen - 2);
+ } else {
+ inet->host = g_strndup(addrstr, hostlen);
+ }
+ /* plain VNC port is just an offset, for websocket
+ * port is absolute */
+ if (websocket) {
+ if (g_str_equal(addrstr, "") ||
+ g_str_equal(addrstr, "on")) {
+ if (displaynum == -1) {
+ error_setg(errp, "explicit websocket port is required");
+ goto cleanup;
+ }
+ inet->port = g_strdup_printf(
+ "%d", displaynum + 5700);
+ if (to) {
+ inet->has_to = true;
+ inet->to = to + 5700;
+ }
+ } else {
+ inet->port = g_strdup(port);
+ }
+ } else {
+ int offset = reverse ? 0 : 5900;
+ if (parse_uint_full(port, &baseport, 10) < 0) {
+ error_setg(errp, "can't convert to a number: %s", port);
+ goto cleanup;
+ }
+ if (baseport > 65535 ||
+ baseport + offset > 65535) {
+ error_setg(errp, "port %s out of range", port);
+ goto cleanup;
+ }
+ inet->port = g_strdup_printf(
+ "%d", (int)baseport + offset);
+
+ if (to) {
+ inet->has_to = true;
+ inet->to = to + offset;
+ }
+ }
+
+ inet->ipv4 = ipv4;
+ inet->has_ipv4 = has_ipv4;
+ inet->ipv6 = ipv6;
+ inet->has_ipv6 = has_ipv6;
+
+ ret = baseport;
+ }
+
+ *retaddr = addr;
+
+ cleanup:
+ if (ret < 0) {
+ qapi_free_SocketAddress(addr);
+ }
+ return ret;
+}
+
+static void vnc_free_addresses(SocketAddress ***retsaddr,
+ size_t *retnsaddr)
+{
+ size_t i;
+
+ for (i = 0; i < *retnsaddr; i++) {
+ qapi_free_SocketAddress((*retsaddr)[i]);
+ }
+ g_free(*retsaddr);
+
+ *retsaddr = NULL;
+ *retnsaddr = 0;
+}
+
+static int vnc_display_get_addresses(QemuOpts *opts,
+ bool reverse,
+ SocketAddress ***retsaddr,
+ size_t *retnsaddr,
+ SocketAddress ***retwsaddr,
+ size_t *retnwsaddr,
+ Error **errp)
+{
+ SocketAddress *saddr = NULL;
+ SocketAddress *wsaddr = NULL;
+ QemuOptsIter addriter;
+ const char *addr;
+ int to = qemu_opt_get_number(opts, "to", 0);
+ bool has_ipv4 = qemu_opt_get(opts, "ipv4");
+ bool has_ipv6 = qemu_opt_get(opts, "ipv6");
+ bool ipv4 = qemu_opt_get_bool(opts, "ipv4", false);
+ bool ipv6 = qemu_opt_get_bool(opts, "ipv6", false);
+ int displaynum = -1;
+ int ret = -1;
+
+ *retsaddr = NULL;
+ *retnsaddr = 0;
+ *retwsaddr = NULL;
+ *retnwsaddr = 0;
+
+ addr = qemu_opt_get(opts, "vnc");
+ if (addr == NULL || g_str_equal(addr, "none")) {
+ ret = 0;
+ goto cleanup;
+ }
+ if (qemu_opt_get(opts, "websocket") &&
+ !qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA1)) {
+ error_setg(errp,
+ "SHA1 hash support is required for websockets");
+ goto cleanup;
+ }
+
+ qemu_opt_iter_init(&addriter, opts, "vnc");
+ while ((addr = qemu_opt_iter_next(&addriter)) != NULL) {
+ int rv;
+ rv = vnc_display_get_address(addr, false, reverse, 0, to,
+ has_ipv4, has_ipv6,
+ ipv4, ipv6,
+ &saddr, errp);
+ if (rv < 0) {
+ goto cleanup;
+ }
+ /* Historical compat - first listen address can be used
+ * to set the default websocket port
+ */
+ if (displaynum == -1) {
+ displaynum = rv;
+ }
+ *retsaddr = g_renew(SocketAddress *, *retsaddr, *retnsaddr + 1);
+ (*retsaddr)[(*retnsaddr)++] = saddr;
+ }
+
+ /* If we had multiple primary displays, we don't do defaults
+ * for websocket, and require explicit config instead. */
+ if (*retnsaddr > 1) {
+ displaynum = -1;
+ }
+
+ qemu_opt_iter_init(&addriter, opts, "websocket");
+ while ((addr = qemu_opt_iter_next(&addriter)) != NULL) {
+ if (vnc_display_get_address(addr, true, reverse, displaynum, to,
+ has_ipv4, has_ipv6,
+ ipv4, ipv6,
+ &wsaddr, errp) < 0) {
+ goto cleanup;
+ }
+
+ /* Historical compat - if only a single listen address was
+ * provided, then this is used to set the default listen
+ * address for websocket too
+ */
+ if (*retnsaddr == 1 &&
+ (*retsaddr)[0]->type == SOCKET_ADDRESS_TYPE_INET &&
+ wsaddr->type == SOCKET_ADDRESS_TYPE_INET &&
+ g_str_equal(wsaddr->u.inet.host, "") &&
+ !g_str_equal((*retsaddr)[0]->u.inet.host, "")) {
+ g_free(wsaddr->u.inet.host);
+ wsaddr->u.inet.host = g_strdup((*retsaddr)[0]->u.inet.host);
+ }
+
+ *retwsaddr = g_renew(SocketAddress *, *retwsaddr, *retnwsaddr + 1);
+ (*retwsaddr)[(*retnwsaddr)++] = wsaddr;
+ }
+
+ ret = 0;
+ cleanup:
+ if (ret < 0) {
+ vnc_free_addresses(retsaddr, retnsaddr);
+ vnc_free_addresses(retwsaddr, retnwsaddr);
+ }
+ return ret;
+}
+
+static int vnc_display_connect(VncDisplay *vd,
+ SocketAddress **saddr,
+ size_t nsaddr,
+ SocketAddress **wsaddr,
+ size_t nwsaddr,
+ Error **errp)
+{
+ /* connect to viewer */
+ QIOChannelSocket *sioc = NULL;
+ if (nwsaddr != 0) {
+ error_setg(errp, "Cannot use websockets in reverse mode");
+ return -1;
+ }
+ if (nsaddr != 1) {
+ error_setg(errp, "Expected a single address in reverse mode");
+ return -1;
+ }
+ /* TODO SOCKET_ADDRESS_TYPE_FD when fd has AF_UNIX */
+ vd->is_unix = saddr[0]->type == SOCKET_ADDRESS_TYPE_UNIX;
+ sioc = qio_channel_socket_new();
+ qio_channel_set_name(QIO_CHANNEL(sioc), "vnc-reverse");
+ if (qio_channel_socket_connect_sync(sioc, saddr[0], errp) < 0) {
+ object_unref(OBJECT(sioc));
+ return -1;
+ }
+ vnc_connect(vd, sioc, false, false);
+ object_unref(OBJECT(sioc));
+ return 0;
+}
+
+
+static int vnc_display_listen(VncDisplay *vd,
+ SocketAddress **saddr,
+ size_t nsaddr,
+ SocketAddress **wsaddr,
+ size_t nwsaddr,
+ Error **errp)
+{
+ size_t i;
+
+ if (nsaddr) {
+ vd->listener = qio_net_listener_new();
+ qio_net_listener_set_name(vd->listener, "vnc-listen");
+ for (i = 0; i < nsaddr; i++) {
+ if (qio_net_listener_open_sync(vd->listener,
+ saddr[i], 1,
+ errp) < 0) {
+ return -1;
+ }
+ }
+
+ qio_net_listener_set_client_func(vd->listener,
+ vnc_listen_io, vd, NULL);
+ }
+
+ if (nwsaddr) {
+ vd->wslistener = qio_net_listener_new();
+ qio_net_listener_set_name(vd->wslistener, "vnc-ws-listen");
+ for (i = 0; i < nwsaddr; i++) {
+ if (qio_net_listener_open_sync(vd->wslistener,
+ wsaddr[i], 1,
+ errp) < 0) {
+ return -1;
+ }
+ }
+
+ qio_net_listener_set_client_func(vd->wslistener,
+ vnc_listen_io, vd, NULL);
+ }
+
+ return 0;
+}
+
+
+void vnc_display_open(const char *id, Error **errp)
+{
+ VncDisplay *vd = vnc_display_find(id);
+ QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id);
+ SocketAddress **saddr = NULL, **wsaddr = NULL;
+ size_t nsaddr, nwsaddr;
+ const char *share, *device_id;
+ QemuConsole *con;
+ bool password = false;
+ bool reverse = false;
+ const char *credid;
+ bool sasl = false;
+ const char *tlsauthz;
+ const char *saslauthz;
+ int lock_key_sync = 1;
+ int key_delay_ms;
+ const char *audiodev;
+ const char *passwordSecret;
+
+ if (!vd) {
+ error_setg(errp, "VNC display not active");
+ return;
+ }
+ vnc_display_close(vd);
+
+ if (!opts) {
+ return;
+ }
+
+ reverse = qemu_opt_get_bool(opts, "reverse", false);
+ if (vnc_display_get_addresses(opts, reverse, &saddr, &nsaddr,
+ &wsaddr, &nwsaddr, errp) < 0) {
+ goto fail;
+ }
+
+
+ passwordSecret = qemu_opt_get(opts, "password-secret");
+ if (passwordSecret) {
+ if (qemu_opt_get(opts, "password")) {
+ error_setg(errp,
+ "'password' flag is redundant with 'password-secret'");
+ goto fail;
+ }
+ vd->password = qcrypto_secret_lookup_as_utf8(passwordSecret,
+ errp);
+ if (!vd->password) {
+ goto fail;
+ }
+ password = true;
+ } else {
+ password = qemu_opt_get_bool(opts, "password", false);
+ }
+ if (password) {
+ if (fips_get_state()) {
+ error_setg(errp,
+ "VNC password auth disabled due to FIPS mode, "
+ "consider using the VeNCrypt or SASL authentication "
+ "methods as an alternative");
+ goto fail;
+ }
+ if (!qcrypto_cipher_supports(
+ QCRYPTO_CIPHER_ALG_DES, QCRYPTO_CIPHER_MODE_ECB)) {
+ error_setg(errp,
+ "Cipher backend does not support DES algorithm");
+ goto fail;
+ }
+ }
+
+ lock_key_sync = qemu_opt_get_bool(opts, "lock-key-sync", true);
+ key_delay_ms = qemu_opt_get_number(opts, "key-delay-ms", 10);
+ sasl = qemu_opt_get_bool(opts, "sasl", false);
+#ifndef CONFIG_VNC_SASL
+ if (sasl) {
+ error_setg(errp, "VNC SASL auth requires cyrus-sasl support");
+ goto fail;
+ }
+#endif /* CONFIG_VNC_SASL */
+ credid = qemu_opt_get(opts, "tls-creds");
+ if (credid) {
+ Object *creds;
+ creds = object_resolve_path_component(
+ object_get_objects_root(), credid);
+ if (!creds) {
+ error_setg(errp, "No TLS credentials with id '%s'",
+ credid);
+ goto fail;
+ }
+ vd->tlscreds = (QCryptoTLSCreds *)
+ object_dynamic_cast(creds,
+ TYPE_QCRYPTO_TLS_CREDS);
+ if (!vd->tlscreds) {
+ error_setg(errp, "Object with id '%s' is not TLS credentials",
+ credid);
+ goto fail;
+ }
+ object_ref(OBJECT(vd->tlscreds));
+
+ if (!qcrypto_tls_creds_check_endpoint(vd->tlscreds,
+ QCRYPTO_TLS_CREDS_ENDPOINT_SERVER,
+ errp)) {
+ goto fail;
+ }
+ }
+ tlsauthz = qemu_opt_get(opts, "tls-authz");
+ if (tlsauthz && !vd->tlscreds) {
+ error_setg(errp, "'tls-authz' provided but TLS is not enabled");
+ goto fail;
+ }
+
+ saslauthz = qemu_opt_get(opts, "sasl-authz");
+ if (saslauthz && !sasl) {
+ error_setg(errp, "'sasl-authz' provided but SASL auth is not enabled");
+ goto fail;
+ }
+
+ share = qemu_opt_get(opts, "share");
+ if (share) {
+ if (strcmp(share, "ignore") == 0) {
+ vd->share_policy = VNC_SHARE_POLICY_IGNORE;
+ } else if (strcmp(share, "allow-exclusive") == 0) {
+ vd->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
+ } else if (strcmp(share, "force-shared") == 0) {
+ vd->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
+ } else {
+ error_setg(errp, "unknown vnc share= option");
+ goto fail;
+ }
+ } else {
+ vd->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
+ }
+ vd->connections_limit = qemu_opt_get_number(opts, "connections", 32);
+
+#ifdef CONFIG_VNC_JPEG
+ vd->lossy = qemu_opt_get_bool(opts, "lossy", false);
+#endif
+ vd->non_adaptive = qemu_opt_get_bool(opts, "non-adaptive", false);
+ /* adaptive updates are only used with tight encoding and
+ * if lossy updates are enabled so we can disable all the
+ * calculations otherwise */
+ if (!vd->lossy) {
+ vd->non_adaptive = true;
+ }
+
+ vd->power_control = qemu_opt_get_bool(opts, "power-control", false);
+
+ if (tlsauthz) {
+ vd->tlsauthzid = g_strdup(tlsauthz);
+ }
+#ifdef CONFIG_VNC_SASL
+ if (sasl) {
+ if (saslauthz) {
+ vd->sasl.authzid = g_strdup(saslauthz);
+ }
+ }
+#endif
+
+ if (vnc_display_setup_auth(&vd->auth, &vd->subauth,
+ vd->tlscreds, password,
+ sasl, false, errp) < 0) {
+ goto fail;
+ }
+ trace_vnc_auth_init(vd, 0, vd->auth, vd->subauth);
+
+ if (vnc_display_setup_auth(&vd->ws_auth, &vd->ws_subauth,
+ vd->tlscreds, password,
+ sasl, true, errp) < 0) {
+ goto fail;
+ }
+ trace_vnc_auth_init(vd, 1, vd->ws_auth, vd->ws_subauth);
+
+#ifdef CONFIG_VNC_SASL
+ if (sasl && !vnc_sasl_server_init(errp)) {
+ goto fail;
+ }
+#endif
+ vd->lock_key_sync = lock_key_sync;
+ if (lock_key_sync) {
+ vd->led = qemu_add_led_event_handler(kbd_leds, vd);
+ }
+ vd->ledstate = 0;
+
+ audiodev = qemu_opt_get(opts, "audiodev");
+ if (audiodev) {
+ vd->audio_state = audio_state_by_name(audiodev);
+ if (!vd->audio_state) {
+ error_setg(errp, "Audiodev '%s' not found", audiodev);
+ goto fail;
+ }
+ }
+
+ device_id = qemu_opt_get(opts, "display");
+ if (device_id) {
+ int head = qemu_opt_get_number(opts, "head", 0);
+ Error *err = NULL;
+
+ con = qemu_console_lookup_by_device_name(device_id, head, &err);
+ if (err) {
+ error_propagate(errp, err);
+ goto fail;
+ }
+ } else {
+ con = NULL;
+ }
+
+ if (con != vd->dcl.con) {
+ qkbd_state_free(vd->kbd);
+ unregister_displaychangelistener(&vd->dcl);
+ vd->dcl.con = con;
+ register_displaychangelistener(&vd->dcl);
+ vd->kbd = qkbd_state_init(vd->dcl.con);
+ }
+ qkbd_state_set_delay(vd->kbd, key_delay_ms);
+
+ if (saddr == NULL) {
+ goto cleanup;
+ }
+
+ if (reverse) {
+ if (vnc_display_connect(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
+ goto fail;
+ }
+ } else {
+ if (vnc_display_listen(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
+ goto fail;
+ }
+ }
+
+ if (qemu_opt_get(opts, "to")) {
+ vnc_display_print_local_addr(vd);
+ }
+
+ cleanup:
+ vnc_free_addresses(&saddr, &nsaddr);
+ vnc_free_addresses(&wsaddr, &nwsaddr);
+ return;
+
+fail:
+ vnc_display_close(vd);
+ goto cleanup;
+}
+
+void vnc_display_add_client(const char *id, int csock, bool skipauth)
+{
+ VncDisplay *vd = vnc_display_find(id);
+ QIOChannelSocket *sioc;
+
+ if (!vd) {
+ return;
+ }
+
+ sioc = qio_channel_socket_new_fd(csock, NULL);
+ if (sioc) {
+ qio_channel_set_name(QIO_CHANNEL(sioc), "vnc-server");
+ vnc_connect(vd, sioc, skipauth, false);
+ object_unref(OBJECT(sioc));
+ }
+}
+
+static void vnc_auto_assign_id(QemuOptsList *olist, QemuOpts *opts)
+{
+ int i = 2;
+ char *id;
+
+ id = g_strdup("default");
+ while (qemu_opts_find(olist, id)) {
+ g_free(id);
+ id = g_strdup_printf("vnc%d", i++);
+ }
+ qemu_opts_set_id(opts, id);
+}
+
+void vnc_parse(const char *str)
+{
+ QemuOptsList *olist = qemu_find_opts("vnc");
+ QemuOpts *opts = qemu_opts_parse_noisily(olist, str, !is_help_option(str));
+ const char *id;
+
+ if (!opts) {
+ exit(1);
+ }
+
+ id = qemu_opts_id(opts);
+ if (!id) {
+ /* auto-assign id if not present */
+ vnc_auto_assign_id(olist, opts);
+ }
+}
+
+int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
+{
+ Error *local_err = NULL;
+ char *id = (char *)qemu_opts_id(opts);
+
+ assert(id);
+ vnc_display_init(id, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+ vnc_display_open(id, &local_err);
+ if (local_err != NULL) {
+ error_propagate(errp, local_err);
+ return -1;
+ }
+ return 0;
+}
+
+static void vnc_register_config(void)
+{
+ qemu_add_opts(&qemu_vnc_opts);
+}
+opts_init(vnc_register_config);
diff --git a/ui/vnc.h b/ui/vnc.h
new file mode 100644
index 000000000..a7149831f
--- /dev/null
+++ b/ui/vnc.h
@@ -0,0 +1,645 @@
+/*
+ * QEMU VNC display driver
+ *
+ * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws>
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2009 Red Hat, Inc
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_VNC_H
+#define QEMU_VNC_H
+
+#include "qemu/queue.h"
+#include "qemu/thread.h"
+#include "ui/clipboard.h"
+#include "ui/console.h"
+#include "audio/audio.h"
+#include "qemu/bitmap.h"
+#include "crypto/tlssession.h"
+#include "qemu/buffer.h"
+#include "io/channel-socket.h"
+#include "io/channel-tls.h"
+#include "io/net-listener.h"
+#include "authz/base.h"
+#include <zlib.h>
+
+#include "keymaps.h"
+#include "vnc-palette.h"
+#include "vnc-enc-zrle.h"
+#include "ui/kbd-state.h"
+
+// #define _VNC_DEBUG 1
+
+#ifdef _VNC_DEBUG
+#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define VNC_DEBUG(fmt, ...) do { } while (0)
+#endif
+
+/*****************************************************************************
+ *
+ * Core data structures
+ *
+ *****************************************************************************/
+
+typedef struct VncState VncState;
+typedef struct VncJob VncJob;
+typedef struct VncRect VncRect;
+typedef struct VncRectEntry VncRectEntry;
+
+typedef int VncReadEvent(VncState *vs, uint8_t *data, size_t len);
+
+typedef void VncWritePixels(VncState *vs, void *data, int size);
+
+typedef void VncSendHextileTile(VncState *vs,
+ int x, int y, int w, int h,
+ void *last_bg,
+ void *last_fg,
+ int *has_bg, int *has_fg);
+
+/* VNC_DIRTY_PIXELS_PER_BIT is the number of dirty pixels represented
+ * by one bit in the dirty bitmap, should be a power of 2 */
+#define VNC_DIRTY_PIXELS_PER_BIT 16
+
+/* VNC_MAX_WIDTH must be a multiple of VNC_DIRTY_PIXELS_PER_BIT. */
+
+#define VNC_MAX_WIDTH ROUND_UP(2560, VNC_DIRTY_PIXELS_PER_BIT)
+#define VNC_MAX_HEIGHT 2048
+
+/* VNC_DIRTY_BITS is the number of bits in the dirty bitmap. */
+#define VNC_DIRTY_BITS (VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT)
+
+/* VNC_DIRTY_BPL (BPL = bits per line) might be greater than
+ * VNC_DIRTY_BITS due to alignment */
+#define VNC_DIRTY_BPL(x) (sizeof((x)->dirty) / VNC_MAX_HEIGHT * BITS_PER_BYTE)
+
+#define VNC_STAT_RECT 64
+#define VNC_STAT_COLS (VNC_MAX_WIDTH / VNC_STAT_RECT)
+#define VNC_STAT_ROWS (VNC_MAX_HEIGHT / VNC_STAT_RECT)
+
+#define VNC_AUTH_CHALLENGE_SIZE 16
+
+typedef struct VncDisplay VncDisplay;
+
+#include "vnc-auth-vencrypt.h"
+#ifdef CONFIG_VNC_SASL
+#include "vnc-auth-sasl.h"
+#endif
+#include "vnc-ws.h"
+
+struct VncRectStat
+{
+ /* time of last 10 updates, to find update frequency */
+ struct timeval times[10];
+ int idx;
+
+ double freq; /* Update frequency (in Hz) */
+ bool updated; /* Already updated during this refresh */
+};
+
+typedef struct VncRectStat VncRectStat;
+
+struct VncSurface
+{
+ struct timeval last_freq_check;
+ DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT],
+ VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT);
+ VncRectStat stats[VNC_STAT_ROWS][VNC_STAT_COLS];
+ pixman_image_t *fb;
+ pixman_format_code_t format;
+};
+
+typedef enum VncShareMode {
+ VNC_SHARE_MODE_CONNECTING = 1,
+ VNC_SHARE_MODE_SHARED,
+ VNC_SHARE_MODE_EXCLUSIVE,
+ VNC_SHARE_MODE_DISCONNECTED,
+} VncShareMode;
+
+typedef enum VncSharePolicy {
+ VNC_SHARE_POLICY_IGNORE = 1,
+ VNC_SHARE_POLICY_ALLOW_EXCLUSIVE,
+ VNC_SHARE_POLICY_FORCE_SHARED,
+} VncSharePolicy;
+
+struct VncDisplay
+{
+ QTAILQ_HEAD(, VncState) clients;
+ int num_connecting;
+ int num_shared;
+ int num_exclusive;
+ int connections_limit;
+ VncSharePolicy share_policy;
+ QIONetListener *listener;
+ QIONetListener *wslistener;
+ DisplaySurface *ds;
+ DisplayChangeListener dcl;
+ kbd_layout_t *kbd_layout;
+ int lock_key_sync;
+ QEMUPutLEDEntry *led;
+ int ledstate;
+ QKbdState *kbd;
+ QemuMutex mutex;
+
+ QEMUCursor *cursor;
+ int cursor_msize;
+ uint8_t *cursor_mask;
+
+ struct VncSurface guest; /* guest visible surface (aka ds->surface) */
+ pixman_image_t *server; /* vnc server surface */
+ int true_width; /* server surface width before rounding up */
+
+ const char *id;
+ QTAILQ_ENTRY(VncDisplay) next;
+ bool is_unix;
+ char *password;
+ time_t expires;
+ int auth;
+ int subauth; /* Used by VeNCrypt */
+ int ws_auth; /* Used by websockets */
+ int ws_subauth; /* Used by websockets */
+ bool lossy;
+ bool non_adaptive;
+ bool power_control;
+ QCryptoTLSCreds *tlscreds;
+ QAuthZ *tlsauthz;
+ char *tlsauthzid;
+#ifdef CONFIG_VNC_SASL
+ VncDisplaySASL sasl;
+#endif
+
+ AudioState *audio_state;
+};
+
+typedef struct VncTight {
+ int type;
+ uint8_t quality;
+ uint8_t compression;
+ uint8_t pixel24;
+ Buffer tight;
+ Buffer tmp;
+ Buffer zlib;
+ Buffer gradient;
+#ifdef CONFIG_VNC_JPEG
+ Buffer jpeg;
+#endif
+#ifdef CONFIG_VNC_PNG
+ Buffer png;
+#endif
+ int levels[4];
+ z_stream stream[4];
+} VncTight;
+
+typedef struct VncHextile {
+ VncSendHextileTile *send_tile;
+} VncHextile;
+
+typedef struct VncZlib {
+ Buffer zlib;
+ Buffer tmp;
+ z_stream stream;
+ int level;
+} VncZlib;
+
+typedef struct VncZrle {
+ int type;
+ Buffer fb;
+ Buffer zrle;
+ Buffer tmp;
+ Buffer zlib;
+ z_stream stream;
+ VncPalette palette;
+} VncZrle;
+
+typedef struct VncZywrle {
+ int buf[VNC_ZRLE_TILE_WIDTH * VNC_ZRLE_TILE_HEIGHT];
+} VncZywrle;
+
+struct VncRect
+{
+ int x;
+ int y;
+ int w;
+ int h;
+};
+
+struct VncRectEntry
+{
+ struct VncRect rect;
+ QLIST_ENTRY(VncRectEntry) next;
+};
+
+struct VncJob
+{
+ VncState *vs;
+
+ QLIST_HEAD(, VncRectEntry) rectangles;
+ QTAILQ_ENTRY(VncJob) next;
+};
+
+typedef enum {
+ VNC_STATE_UPDATE_NONE,
+ VNC_STATE_UPDATE_INCREMENTAL,
+ VNC_STATE_UPDATE_FORCE,
+} VncStateUpdate;
+
+#define VNC_MAGIC ((uint64_t)0x05b3f069b3d204bb)
+
+struct VncState
+{
+ uint64_t magic;
+ QIOChannelSocket *sioc; /* The underlying socket */
+ QIOChannel *ioc; /* The channel currently used for I/O */
+ guint ioc_tag;
+ gboolean disconnecting;
+
+ DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS);
+ uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in
+ * vnc-jobs-async.c */
+
+ VncDisplay *vd;
+ VncStateUpdate update; /* Most recent pending request from client */
+ VncStateUpdate job_update; /* Currently processed by job thread */
+ int has_dirty;
+ uint32_t features;
+ int absolute;
+ int last_x;
+ int last_y;
+ uint32_t last_bmask;
+ size_t client_width; /* limited to u16 by RFB proto */
+ size_t client_height; /* limited to u16 by RFB proto */
+ VncShareMode share_mode;
+
+ uint32_t vnc_encoding;
+
+ int major;
+ int minor;
+
+ int auth;
+ int subauth; /* Used by VeNCrypt */
+ char challenge[VNC_AUTH_CHALLENGE_SIZE];
+ QCryptoTLSSession *tls; /* Borrowed pointer from channel, don't free */
+#ifdef CONFIG_VNC_SASL
+ VncStateSASL sasl;
+#endif
+ bool encode_ws;
+ bool websocket;
+
+#ifdef CONFIG_VNC
+ VncClientInfo *info;
+#endif
+
+ /* Job thread bottom half has put data for a forced update
+ * into the output buffer. This offset points to the end of
+ * the update data in the output buffer. This lets us determine
+ * when a force update is fully sent to the client, allowing
+ * us to process further forced updates. */
+ size_t force_update_offset;
+ /* We allow multiple incremental updates or audio capture
+ * samples to be queued in output buffer, provided the
+ * buffer size doesn't exceed this threshold. The value
+ * is calculating dynamically based on framebuffer size
+ * and audio sample settings in vnc_update_throttle_offset() */
+ size_t throttle_output_offset;
+ Buffer output;
+ Buffer input;
+ /* current output mode information */
+ VncWritePixels *write_pixels;
+ PixelFormat client_pf;
+ pixman_format_code_t client_format;
+ bool client_be;
+
+ CaptureVoiceOut *audio_cap;
+ struct audsettings as;
+
+ VncReadEvent *read_handler;
+ size_t read_handler_expect;
+
+ bool abort;
+ QemuMutex output_mutex;
+ QEMUBH *bh;
+ Buffer jobs_buffer;
+
+ /* Encoding specific, if you add something here, don't forget to
+ * update vnc_async_encoding_start()
+ */
+ VncTight *tight;
+ VncZlib zlib;
+ VncHextile hextile;
+ VncZrle *zrle;
+ VncZywrle zywrle;
+
+ Notifier mouse_mode_notifier;
+
+ QemuClipboardPeer cbpeer;
+ QemuClipboardInfo *cbinfo;
+ uint32_t cbpending;
+
+ QTAILQ_ENTRY(VncState) next;
+};
+
+
+/*****************************************************************************
+ *
+ * Authentication modes
+ *
+ *****************************************************************************/
+
+enum {
+ VNC_AUTH_INVALID = 0,
+ VNC_AUTH_NONE = 1,
+ VNC_AUTH_VNC = 2,
+ VNC_AUTH_RA2 = 5,
+ VNC_AUTH_RA2NE = 6,
+ VNC_AUTH_TIGHT = 16,
+ VNC_AUTH_ULTRA = 17,
+ VNC_AUTH_TLS = 18, /* Supported in GTK-VNC & VINO */
+ VNC_AUTH_VENCRYPT = 19, /* Supported in GTK-VNC & VeNCrypt */
+ VNC_AUTH_SASL = 20, /* Supported in GTK-VNC & VINO */
+};
+
+enum {
+ VNC_AUTH_VENCRYPT_PLAIN = 256,
+ VNC_AUTH_VENCRYPT_TLSNONE = 257,
+ VNC_AUTH_VENCRYPT_TLSVNC = 258,
+ VNC_AUTH_VENCRYPT_TLSPLAIN = 259,
+ VNC_AUTH_VENCRYPT_X509NONE = 260,
+ VNC_AUTH_VENCRYPT_X509VNC = 261,
+ VNC_AUTH_VENCRYPT_X509PLAIN = 262,
+ VNC_AUTH_VENCRYPT_X509SASL = 263,
+ VNC_AUTH_VENCRYPT_TLSSASL = 264,
+};
+
+
+/*****************************************************************************
+ *
+ * Encoding types
+ *
+ *****************************************************************************/
+
+#define VNC_ENCODING_RAW 0x00000000
+#define VNC_ENCODING_COPYRECT 0x00000001
+#define VNC_ENCODING_RRE 0x00000002
+#define VNC_ENCODING_CORRE 0x00000004
+#define VNC_ENCODING_HEXTILE 0x00000005
+#define VNC_ENCODING_ZLIB 0x00000006
+#define VNC_ENCODING_TIGHT 0x00000007
+#define VNC_ENCODING_ZLIBHEX 0x00000008
+#define VNC_ENCODING_TRLE 0x0000000f
+#define VNC_ENCODING_ZRLE 0x00000010
+#define VNC_ENCODING_ZYWRLE 0x00000011
+#define VNC_ENCODING_COMPRESSLEVEL0 0xFFFFFF00 /* -256 */
+#define VNC_ENCODING_QUALITYLEVEL0 0xFFFFFFE0 /* -32 */
+#define VNC_ENCODING_XCURSOR 0xFFFFFF10 /* -240 */
+#define VNC_ENCODING_RICH_CURSOR 0xFFFFFF11 /* -239 */
+#define VNC_ENCODING_POINTER_POS 0xFFFFFF18 /* -232 */
+#define VNC_ENCODING_LASTRECT 0xFFFFFF20 /* -224 */
+#define VNC_ENCODING_DESKTOPRESIZE 0xFFFFFF21 /* -223 */
+#define VNC_ENCODING_POINTER_TYPE_CHANGE 0XFFFFFEFF /* -257 */
+#define VNC_ENCODING_EXT_KEY_EVENT 0XFFFFFEFE /* -258 */
+#define VNC_ENCODING_AUDIO 0XFFFFFEFD /* -259 */
+#define VNC_ENCODING_TIGHT_PNG 0xFFFFFEFC /* -260 */
+#define VNC_ENCODING_LED_STATE 0XFFFFFEFB /* -261 */
+#define VNC_ENCODING_DESKTOP_RESIZE_EXT 0XFFFFFECC /* -308 */
+#define VNC_ENCODING_XVP 0XFFFFFECB /* -309 */
+#define VNC_ENCODING_ALPHA_CURSOR 0XFFFFFEC6 /* -314 */
+#define VNC_ENCODING_WMVi 0x574D5669
+#define VNC_ENCODING_CLIPBOARD_EXT 0xc0a1e5ce
+
+/*****************************************************************************
+ *
+ * Other tight constants
+ *
+ *****************************************************************************/
+
+/*
+ * Vendors known by TightVNC: standard VNC/RealVNC, TridiaVNC, and TightVNC.
+ */
+
+#define VNC_TIGHT_CCB_RESET_MASK (0x0f)
+#define VNC_TIGHT_CCB_TYPE_MASK (0x0f << 4)
+#define VNC_TIGHT_CCB_TYPE_FILL (0x08 << 4)
+#define VNC_TIGHT_CCB_TYPE_JPEG (0x09 << 4)
+#define VNC_TIGHT_CCB_TYPE_PNG (0x0A << 4)
+#define VNC_TIGHT_CCB_BASIC_MAX (0x07 << 4)
+#define VNC_TIGHT_CCB_BASIC_ZLIB (0x03 << 4)
+#define VNC_TIGHT_CCB_BASIC_FILTER (0x04 << 4)
+
+/*****************************************************************************
+ *
+ * Features
+ *
+ *****************************************************************************/
+
+enum VncFeatures {
+ VNC_FEATURE_RESIZE,
+ VNC_FEATURE_RESIZE_EXT,
+ VNC_FEATURE_HEXTILE,
+ VNC_FEATURE_POINTER_TYPE_CHANGE,
+ VNC_FEATURE_WMVI,
+ VNC_FEATURE_TIGHT,
+ VNC_FEATURE_ZLIB,
+ VNC_FEATURE_RICH_CURSOR,
+ VNC_FEATURE_ALPHA_CURSOR,
+ VNC_FEATURE_TIGHT_PNG,
+ VNC_FEATURE_ZRLE,
+ VNC_FEATURE_ZYWRLE,
+ VNC_FEATURE_LED_STATE,
+ VNC_FEATURE_XVP,
+ VNC_FEATURE_CLIPBOARD_EXT,
+};
+
+#define VNC_FEATURE_RESIZE_MASK (1 << VNC_FEATURE_RESIZE)
+#define VNC_FEATURE_RESIZE_EXT_MASK (1 << VNC_FEATURE_RESIZE_EXT)
+#define VNC_FEATURE_HEXTILE_MASK (1 << VNC_FEATURE_HEXTILE)
+#define VNC_FEATURE_POINTER_TYPE_CHANGE_MASK (1 << VNC_FEATURE_POINTER_TYPE_CHANGE)
+#define VNC_FEATURE_WMVI_MASK (1 << VNC_FEATURE_WMVI)
+#define VNC_FEATURE_TIGHT_MASK (1 << VNC_FEATURE_TIGHT)
+#define VNC_FEATURE_ZLIB_MASK (1 << VNC_FEATURE_ZLIB)
+#define VNC_FEATURE_RICH_CURSOR_MASK (1 << VNC_FEATURE_RICH_CURSOR)
+#define VNC_FEATURE_ALPHA_CURSOR_MASK (1 << VNC_FEATURE_ALPHA_CURSOR)
+#define VNC_FEATURE_TIGHT_PNG_MASK (1 << VNC_FEATURE_TIGHT_PNG)
+#define VNC_FEATURE_ZRLE_MASK (1 << VNC_FEATURE_ZRLE)
+#define VNC_FEATURE_ZYWRLE_MASK (1 << VNC_FEATURE_ZYWRLE)
+#define VNC_FEATURE_LED_STATE_MASK (1 << VNC_FEATURE_LED_STATE)
+#define VNC_FEATURE_XVP_MASK (1 << VNC_FEATURE_XVP)
+#define VNC_FEATURE_CLIPBOARD_EXT_MASK (1 << VNC_FEATURE_CLIPBOARD_EXT)
+
+
+/* Client -> Server message IDs */
+#define VNC_MSG_CLIENT_SET_PIXEL_FORMAT 0
+#define VNC_MSG_CLIENT_SET_ENCODINGS 2
+#define VNC_MSG_CLIENT_FRAMEBUFFER_UPDATE_REQUEST 3
+#define VNC_MSG_CLIENT_KEY_EVENT 4
+#define VNC_MSG_CLIENT_POINTER_EVENT 5
+#define VNC_MSG_CLIENT_CUT_TEXT 6
+#define VNC_MSG_CLIENT_VMWARE_0 127
+#define VNC_MSG_CLIENT_CALL_CONTROL 249
+#define VNC_MSG_CLIENT_XVP 250
+#define VNC_MSG_CLIENT_SET_DESKTOP_SIZE 251
+#define VNC_MSG_CLIENT_TIGHT 252
+#define VNC_MSG_CLIENT_GII 253
+#define VNC_MSG_CLIENT_VMWARE_1 254
+#define VNC_MSG_CLIENT_QEMU 255
+
+/* Server -> Client message IDs */
+#define VNC_MSG_SERVER_FRAMEBUFFER_UPDATE 0
+#define VNC_MSG_SERVER_SET_COLOUR_MAP_ENTRIES 1
+#define VNC_MSG_SERVER_BELL 2
+#define VNC_MSG_SERVER_CUT_TEXT 3
+#define VNC_MSG_SERVER_VMWARE_0 127
+#define VNC_MSG_SERVER_CALL_CONTROL 249
+#define VNC_MSG_SERVER_XVP 250
+#define VNC_MSG_SERVER_TIGHT 252
+#define VNC_MSG_SERVER_GII 253
+#define VNC_MSG_SERVER_VMWARE_1 254
+#define VNC_MSG_SERVER_QEMU 255
+
+
+
+/* QEMU client -> server message IDs */
+#define VNC_MSG_CLIENT_QEMU_EXT_KEY_EVENT 0
+#define VNC_MSG_CLIENT_QEMU_AUDIO 1
+
+/* QEMU server -> client message IDs */
+#define VNC_MSG_SERVER_QEMU_AUDIO 1
+
+
+
+/* QEMU client -> server audio message IDs */
+#define VNC_MSG_CLIENT_QEMU_AUDIO_ENABLE 0
+#define VNC_MSG_CLIENT_QEMU_AUDIO_DISABLE 1
+#define VNC_MSG_CLIENT_QEMU_AUDIO_SET_FORMAT 2
+
+/* QEMU server -> client audio message IDs */
+#define VNC_MSG_SERVER_QEMU_AUDIO_END 0
+#define VNC_MSG_SERVER_QEMU_AUDIO_BEGIN 1
+#define VNC_MSG_SERVER_QEMU_AUDIO_DATA 2
+
+/* XVP server -> client status code */
+#define VNC_XVP_CODE_FAIL 0
+#define VNC_XVP_CODE_INIT 1
+
+/* XVP client -> server action request */
+#define VNC_XVP_ACTION_SHUTDOWN 2
+#define VNC_XVP_ACTION_REBOOT 3
+#define VNC_XVP_ACTION_RESET 4
+
+/* extended clipboard flags */
+#define VNC_CLIPBOARD_TEXT (1 << 0)
+#define VNC_CLIPBOARD_RTF (1 << 1)
+#define VNC_CLIPBOARD_HTML (1 << 2)
+#define VNC_CLIPBOARD_DIB (1 << 3)
+#define VNC_CLIPBOARD_FILES (1 << 4)
+#define VNC_CLIPBOARD_CAPS (1 << 24)
+#define VNC_CLIPBOARD_REQUEST (1 << 25)
+#define VNC_CLIPBOARD_PEEK (1 << 26)
+#define VNC_CLIPBOARD_NOTIFY (1 << 27)
+#define VNC_CLIPBOARD_PROVIDE (1 << 28)
+
+/*****************************************************************************
+ *
+ * Internal APIs
+ *
+ *****************************************************************************/
+
+/* Event loop functions */
+gboolean vnc_client_io(QIOChannel *ioc,
+ GIOCondition condition,
+ void *opaque);
+
+size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
+size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
+
+/* Protocol I/O functions */
+void vnc_write(VncState *vs, const void *data, size_t len);
+void vnc_write_u32(VncState *vs, uint32_t value);
+void vnc_write_s32(VncState *vs, int32_t value);
+void vnc_write_u16(VncState *vs, uint16_t value);
+void vnc_write_u8(VncState *vs, uint8_t value);
+void vnc_flush(VncState *vs);
+void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting);
+void vnc_disconnect_finish(VncState *vs);
+void vnc_start_protocol(VncState *vs);
+
+
+/* Buffer I/O functions */
+uint32_t read_u32(uint8_t *data, size_t offset);
+
+/* Protocol stage functions */
+void vnc_client_error(VncState *vs);
+size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err);
+
+void start_client_init(VncState *vs);
+void start_auth_vnc(VncState *vs);
+
+
+/* Misc helpers */
+
+static inline uint32_t vnc_has_feature(VncState *vs, int feature) {
+ return (vs->features & (1 << feature));
+}
+
+/* Framebuffer */
+void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
+ int32_t encoding);
+
+/* server fb is in PIXMAN_x8r8g8b8 */
+#define VNC_SERVER_FB_FORMAT PIXMAN_FORMAT(32, PIXMAN_TYPE_ARGB, 0, 8, 8, 8)
+#define VNC_SERVER_FB_BITS (PIXMAN_FORMAT_BPP(VNC_SERVER_FB_FORMAT))
+#define VNC_SERVER_FB_BYTES ((VNC_SERVER_FB_BITS+7)/8)
+
+void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y);
+int vnc_server_fb_stride(VncDisplay *vd);
+
+void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v);
+double vnc_update_freq(VncState *vs, int x, int y, int w, int h);
+void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h);
+
+/* Encodings */
+int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+
+int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+
+int vnc_hextile_send_framebuffer_update(VncState *vs, int x,
+ int y, int w, int h);
+void vnc_hextile_set_pixel_conversion(VncState *vs, int generic);
+
+void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size);
+void vnc_zlib_zfree(void *x, void *addr);
+int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+void vnc_zlib_clear(VncState *vs);
+
+int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y,
+ int w, int h);
+void vnc_tight_clear(VncState *vs);
+
+int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h);
+void vnc_zrle_clear(VncState *vs);
+
+/* vnc-clipboard.c */
+void vnc_server_cut_text_caps(VncState *vs);
+void vnc_client_cut_text(VncState *vs, size_t len, uint8_t *text);
+void vnc_client_cut_text_ext(VncState *vs, int32_t len, uint32_t flags, uint8_t *data);
+
+#endif /* QEMU_VNC_H */
diff --git a/ui/vnc_keysym.h b/ui/vnc_keysym.h
new file mode 100644
index 000000000..e8a2ec73c
--- /dev/null
+++ b/ui/vnc_keysym.h
@@ -0,0 +1,722 @@
+
+#include "keymaps.h"
+
+static const name2keysym_t name2keysym[]={
+/* ascii */
+ { "space", 0x020},
+ { "exclam", 0x021},
+ { "quotedbl", 0x022},
+ { "numbersign", 0x023},
+ { "dollar", 0x024},
+ { "percent", 0x025},
+ { "ampersand", 0x026},
+ { "apostrophe", 0x027},
+ { "parenleft", 0x028},
+ { "parenright", 0x029},
+ { "asterisk", 0x02a},
+ { "plus", 0x02b},
+ { "comma", 0x02c},
+ { "minus", 0x02d},
+ { "period", 0x02e},
+ { "slash", 0x02f},
+ { "0", 0x030},
+ { "1", 0x031},
+ { "2", 0x032},
+ { "3", 0x033},
+ { "4", 0x034},
+ { "5", 0x035},
+ { "6", 0x036},
+ { "7", 0x037},
+ { "8", 0x038},
+ { "9", 0x039},
+ { "colon", 0x03a},
+ { "semicolon", 0x03b},
+ { "less", 0x03c},
+ { "equal", 0x03d},
+ { "greater", 0x03e},
+ { "question", 0x03f},
+ { "at", 0x040},
+ { "A", 0x041},
+ { "B", 0x042},
+ { "C", 0x043},
+ { "D", 0x044},
+ { "E", 0x045},
+ { "F", 0x046},
+ { "G", 0x047},
+ { "H", 0x048},
+ { "I", 0x049},
+ { "J", 0x04a},
+ { "K", 0x04b},
+ { "L", 0x04c},
+ { "M", 0x04d},
+ { "N", 0x04e},
+ { "O", 0x04f},
+ { "P", 0x050},
+ { "Q", 0x051},
+ { "R", 0x052},
+ { "S", 0x053},
+ { "T", 0x054},
+ { "U", 0x055},
+ { "V", 0x056},
+ { "W", 0x057},
+ { "X", 0x058},
+ { "Y", 0x059},
+ { "Z", 0x05a},
+ { "bracketleft", 0x05b},
+ { "backslash", 0x05c},
+ { "bracketright", 0x05d},
+ { "asciicircum", 0x05e},
+ { "underscore", 0x05f},
+ { "grave", 0x060},
+ { "a", 0x061},
+ { "b", 0x062},
+ { "c", 0x063},
+ { "d", 0x064},
+ { "e", 0x065},
+ { "f", 0x066},
+ { "g", 0x067},
+ { "h", 0x068},
+ { "i", 0x069},
+ { "j", 0x06a},
+ { "k", 0x06b},
+ { "l", 0x06c},
+ { "m", 0x06d},
+ { "n", 0x06e},
+ { "o", 0x06f},
+ { "p", 0x070},
+ { "q", 0x071},
+ { "r", 0x072},
+ { "s", 0x073},
+ { "t", 0x074},
+ { "u", 0x075},
+ { "v", 0x076},
+ { "w", 0x077},
+ { "x", 0x078},
+ { "y", 0x079},
+ { "z", 0x07a},
+ { "braceleft", 0x07b},
+ { "bar", 0x07c},
+ { "braceright", 0x07d},
+ { "asciitilde", 0x07e},
+
+/* latin 1 extensions */
+{ "nobreakspace", 0x0a0},
+{ "exclamdown", 0x0a1},
+{ "cent", 0x0a2},
+{ "sterling", 0x0a3},
+{ "currency", 0x0a4},
+{ "yen", 0x0a5},
+{ "brokenbar", 0x0a6},
+{ "section", 0x0a7},
+{ "diaeresis", 0x0a8},
+{ "copyright", 0x0a9},
+{ "ordfeminine", 0x0aa},
+{ "guillemotleft", 0x0ab},
+{ "notsign", 0x0ac},
+{ "hyphen", 0x0ad},
+{ "registered", 0x0ae},
+{ "macron", 0x0af},
+{ "degree", 0x0b0},
+{ "plusminus", 0x0b1},
+{ "twosuperior", 0x0b2},
+{ "threesuperior", 0x0b3},
+{ "acute", 0x0b4},
+{ "mu", 0x0b5},
+{ "paragraph", 0x0b6},
+{ "periodcentered", 0x0b7},
+{ "cedilla", 0x0b8},
+{ "onesuperior", 0x0b9},
+{ "masculine", 0x0ba},
+{ "guillemotright", 0x0bb},
+{ "onequarter", 0x0bc},
+{ "onehalf", 0x0bd},
+{ "threequarters", 0x0be},
+{ "questiondown", 0x0bf},
+{ "Agrave", 0x0c0},
+{ "Aacute", 0x0c1},
+{ "Acircumflex", 0x0c2},
+{ "Atilde", 0x0c3},
+{ "Adiaeresis", 0x0c4},
+{ "Aring", 0x0c5},
+{ "AE", 0x0c6},
+{ "Ccedilla", 0x0c7},
+{ "Egrave", 0x0c8},
+{ "Eacute", 0x0c9},
+{ "Ecircumflex", 0x0ca},
+{ "Ediaeresis", 0x0cb},
+{ "Igrave", 0x0cc},
+{ "Iacute", 0x0cd},
+{ "Icircumflex", 0x0ce},
+{ "Idiaeresis", 0x0cf},
+{ "ETH", 0x0d0},
+{ "Eth", 0x0d0},
+{ "Ntilde", 0x0d1},
+{ "Ograve", 0x0d2},
+{ "Oacute", 0x0d3},
+{ "Ocircumflex", 0x0d4},
+{ "Otilde", 0x0d5},
+{ "Odiaeresis", 0x0d6},
+{ "multiply", 0x0d7},
+{ "Ooblique", 0x0d8},
+{ "Oslash", 0x0d8},
+{ "Ugrave", 0x0d9},
+{ "Uacute", 0x0da},
+{ "Ucircumflex", 0x0db},
+{ "Udiaeresis", 0x0dc},
+{ "Yacute", 0x0dd},
+{ "THORN", 0x0de},
+{ "Thorn", 0x0de},
+{ "ssharp", 0x0df},
+{ "agrave", 0x0e0},
+{ "aacute", 0x0e1},
+{ "acircumflex", 0x0e2},
+{ "atilde", 0x0e3},
+{ "adiaeresis", 0x0e4},
+{ "aring", 0x0e5},
+{ "ae", 0x0e6},
+{ "ccedilla", 0x0e7},
+{ "egrave", 0x0e8},
+{ "eacute", 0x0e9},
+{ "ecircumflex", 0x0ea},
+{ "ediaeresis", 0x0eb},
+{ "igrave", 0x0ec},
+{ "iacute", 0x0ed},
+{ "icircumflex", 0x0ee},
+{ "idiaeresis", 0x0ef},
+{ "eth", 0x0f0},
+{ "ntilde", 0x0f1},
+{ "ograve", 0x0f2},
+{ "oacute", 0x0f3},
+{ "ocircumflex", 0x0f4},
+{ "otilde", 0x0f5},
+{ "odiaeresis", 0x0f6},
+{ "division", 0x0f7},
+{ "oslash", 0x0f8},
+{ "ooblique", 0x0f8},
+{ "ugrave", 0x0f9},
+{ "uacute", 0x0fa},
+{ "ucircumflex", 0x0fb},
+{ "udiaeresis", 0x0fc},
+{ "yacute", 0x0fd},
+{ "thorn", 0x0fe},
+{ "ydiaeresis", 0x0ff},
+{"EuroSign", 0x20ac}, /* XK_EuroSign */
+
+/* latin 2 - Polish national characters */
+{ "eogonek", 0x1ea},
+{ "Eogonek", 0x1ca},
+{ "aogonek", 0x1b1},
+{ "Aogonek", 0x1a1},
+{ "sacute", 0x1b6},
+{ "Sacute", 0x1a6},
+{ "lstroke", 0x1b3},
+{ "Lstroke", 0x1a3},
+{ "zabovedot", 0x1bf},
+{ "Zabovedot", 0x1af},
+{ "zacute", 0x1bc},
+{ "Zacute", 0x1ac},
+{ "Odoubleacute", 0x1d5},
+{ "Udoubleacute", 0x1db},
+{ "cacute", 0x1e6},
+{ "Cacute", 0x1c6},
+{ "nacute", 0x1f1},
+{ "Nacute", 0x1d1},
+{ "odoubleacute", 0x1f5},
+{ "udoubleacute", 0x1fb},
+
+/* Czech national characters */
+{ "ecaron", 0x1ec},
+{ "scaron", 0x1b9},
+{ "ccaron", 0x1e8},
+{ "rcaron", 0x1f8},
+{ "zcaron", 0x1be},
+{ "uring", 0x1f9},
+
+ /* modifiers */
+{"ISO_Level3_Shift", 0xfe03}, /* XK_ISO_Level3_Shift */
+{"Control_L", 0xffe3}, /* XK_Control_L */
+{"Control_R", 0xffe4}, /* XK_Control_R */
+{"Alt_L", 0xffe9}, /* XK_Alt_L */
+{"Alt_R", 0xffea}, /* XK_Alt_R */
+{"Caps_Lock", 0xffe5}, /* XK_Caps_Lock */
+{"Meta_L", 0xffe7}, /* XK_Meta_L */
+{"Meta_R", 0xffe8}, /* XK_Meta_R */
+{"Shift_L", 0xffe1}, /* XK_Shift_L */
+{"Shift_R", 0xffe2}, /* XK_Shift_R */
+{"Super_L", 0xffeb}, /* XK_Super_L */
+{"Super_R", 0xffec}, /* XK_Super_R */
+
+ /* special keys */
+{"BackSpace", 0xff08}, /* XK_BackSpace */
+{"Tab", 0xff09}, /* XK_Tab */
+{"Return", 0xff0d}, /* XK_Return */
+{"Right", 0xff53}, /* XK_Right */
+{"Left", 0xff51}, /* XK_Left */
+{"Up", 0xff52}, /* XK_Up */
+{"Down", 0xff54}, /* XK_Down */
+{"Next", 0xff56},
+{"Page_Down", 0xff56}, /* XK_Page_Down */
+{"Prior", 0xff55},
+{"Page_Up", 0xff55}, /* XK_Page_Up */
+{"Insert", 0xff63}, /* XK_Insert */
+{"Delete", 0xffff}, /* XK_Delete */
+{"Home", 0xff50}, /* XK_Home */
+{"End", 0xff57}, /* XK_End */
+{"Scroll_Lock", 0xff14}, /* XK_Scroll_Lock */
+{"KP_Home", 0xff95},
+{"KP_Left", 0xff96},
+{"KP_Up", 0xff97},
+{"KP_Right", 0xff98},
+{"KP_Down", 0xff99},
+{"KP_Prior", 0xff9a},
+{"KP_Page_Up", 0xff9a},
+{"KP_Next", 0xff9b},
+{"KP_Page_Down", 0xff9b},
+{"KP_End", 0xff9c},
+{"KP_Begin", 0xff9d},
+{"KP_Insert", 0xff9e},
+{"KP_Delete", 0xff9f},
+{"F1", 0xffbe}, /* XK_F1 */
+{"F2", 0xffbf}, /* XK_F2 */
+{"F3", 0xffc0}, /* XK_F3 */
+{"F4", 0xffc1}, /* XK_F4 */
+{"F5", 0xffc2}, /* XK_F5 */
+{"F6", 0xffc3}, /* XK_F6 */
+{"F7", 0xffc4}, /* XK_F7 */
+{"F8", 0xffc5}, /* XK_F8 */
+{"F9", 0xffc6}, /* XK_F9 */
+{"F10", 0xffc7}, /* XK_F10 */
+{"F11", 0xffc8}, /* XK_F11 */
+{"F12", 0xffc9}, /* XK_F12 */
+{"F13", 0xffca}, /* XK_F13 */
+{"F14", 0xffcb}, /* XK_F14 */
+{"F15", 0xffcc}, /* XK_F15 */
+{"Sys_Req", 0xff15}, /* XK_Sys_Req */
+{"KP_0", 0xffb0}, /* XK_KP_0 */
+{"KP_1", 0xffb1}, /* XK_KP_1 */
+{"KP_2", 0xffb2}, /* XK_KP_2 */
+{"KP_3", 0xffb3}, /* XK_KP_3 */
+{"KP_4", 0xffb4}, /* XK_KP_4 */
+{"KP_5", 0xffb5}, /* XK_KP_5 */
+{"KP_6", 0xffb6}, /* XK_KP_6 */
+{"KP_7", 0xffb7}, /* XK_KP_7 */
+{"KP_8", 0xffb8}, /* XK_KP_8 */
+{"KP_9", 0xffb9}, /* XK_KP_9 */
+{"KP_Add", 0xffab}, /* XK_KP_Add */
+{"KP_Separator", 0xffac},/* XK_KP_Separator */
+{"KP_Decimal", 0xffae}, /* XK_KP_Decimal */
+{"KP_Divide", 0xffaf}, /* XK_KP_Divide */
+{"KP_Enter", 0xff8d}, /* XK_KP_Enter */
+{"KP_Equal", 0xffbd}, /* XK_KP_Equal */
+{"KP_Multiply", 0xffaa}, /* XK_KP_Multiply */
+{"KP_Subtract", 0xffad}, /* XK_KP_Subtract */
+{"help", 0xff6a}, /* XK_Help */
+{"Menu", 0xff67}, /* XK_Menu */
+{"Print", 0xff61}, /* XK_Print */
+{"Mode_switch", 0xff7e}, /* XK_Mode_switch */
+{"Num_Lock", 0xff7f}, /* XK_Num_Lock */
+{"Pause", 0xff13}, /* XK_Pause */
+{"Escape", 0xff1b}, /* XK_Escape */
+
+/* dead keys */
+{"dead_grave", 0xfe50}, /* XK_dead_grave */
+{"dead_acute", 0xfe51}, /* XK_dead_acute */
+{"dead_circumflex", 0xfe52}, /* XK_dead_circumflex */
+{"dead_tilde", 0xfe53}, /* XK_dead_tilde */
+{"dead_macron", 0xfe54}, /* XK_dead_macron */
+{"dead_breve", 0xfe55}, /* XK_dead_breve */
+{"dead_abovedot", 0xfe56}, /* XK_dead_abovedot */
+{"dead_diaeresis", 0xfe57}, /* XK_dead_diaeresis */
+{"dead_abovering", 0xfe58}, /* XK_dead_abovering */
+{"dead_doubleacute", 0xfe59}, /* XK_dead_doubleacute */
+{"dead_caron", 0xfe5a}, /* XK_dead_caron */
+{"dead_cedilla", 0xfe5b}, /* XK_dead_cedilla */
+{"dead_ogonek", 0xfe5c}, /* XK_dead_ogonek */
+{"dead_iota", 0xfe5d}, /* XK_dead_iota */
+{"dead_voiced_sound", 0xfe5e}, /* XK_dead_voiced_sound */
+{"dead_semivoiced_sound", 0xfe5f}, /* XK_dead_semivoiced_sound */
+{"dead_belowdot", 0xfe60}, /* XK_dead_belowdot */
+{"dead_hook", 0xfe61}, /* XK_dead_hook */
+{"dead_horn", 0xfe62}, /* XK_dead_horn */
+
+
+ /* localized keys */
+{"BackApostrophe", 0xff21},
+{"Muhenkan", 0xff22},
+{"Katakana", 0xff27},
+{"Hankaku", 0xff29},
+{"Zenkaku_Hankaku", 0xff2a},
+{"Henkan_Mode_Real", 0xff23},
+{"Henkan_Mode_Ultra", 0xff3e},
+{"backslash_ja", 0xffa5},
+{"Katakana_Real", 0xff25},
+{"Eisu_toggle", 0xff30},
+
+{"abovedot", 0x01ff}, /* U+02D9 DOT ABOVE */
+{"amacron", 0x03e0}, /* U+0101 LATIN SMALL LETTER A WITH MACRON */
+{"Amacron", 0x03c0}, /* U+0100 LATIN CAPITAL LETTER A WITH MACRON */
+{"Arabic_ain", 0x05d9}, /* U+0639 ARABIC LETTER AIN */
+{"Arabic_alef", 0x05c7}, /* U+0627 ARABIC LETTER ALEF */
+{"Arabic_alefmaksura", 0x05e9}, /* U+0649 ARABIC LETTER ALEF MAKSURA */
+{"Arabic_beh", 0x05c8}, /* U+0628 ARABIC LETTER BEH */
+{"Arabic_comma", 0x05ac}, /* U+060C ARABIC COMMA */
+{"Arabic_dad", 0x05d6}, /* U+0636 ARABIC LETTER DAD */
+{"Arabic_dal", 0x05cf}, /* U+062F ARABIC LETTER DAL */
+{"Arabic_damma", 0x05ef}, /* U+064F ARABIC DAMMA */
+{"Arabic_dammatan", 0x05ec}, /* U+064C ARABIC DAMMATAN */
+{"Arabic_fatha", 0x05ee}, /* U+064E ARABIC FATHA */
+{"Arabic_fathatan", 0x05eb}, /* U+064B ARABIC FATHATAN */
+{"Arabic_feh", 0x05e1}, /* U+0641 ARABIC LETTER FEH */
+{"Arabic_ghain", 0x05da}, /* U+063A ARABIC LETTER GHAIN */
+{"Arabic_ha", 0x05e7}, /* U+0647 ARABIC LETTER HEH */
+{"Arabic_hah", 0x05cd}, /* U+062D ARABIC LETTER HAH */
+{"Arabic_hamza", 0x05c1}, /* U+0621 ARABIC LETTER HAMZA */
+{"Arabic_hamzaonalef", 0x05c3}, /* U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE */
+{"Arabic_hamzaonwaw", 0x05c4}, /* U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE */
+{"Arabic_hamzaonyeh", 0x05c6}, /* U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE */
+{"Arabic_hamzaunderalef", 0x05c5}, /* U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW */
+{"Arabic_jeem", 0x05cc}, /* U+062C ARABIC LETTER JEEM */
+{"Arabic_kaf", 0x05e3}, /* U+0643 ARABIC LETTER KAF */
+{"Arabic_kasra", 0x05f0}, /* U+0650 ARABIC KASRA */
+{"Arabic_kasratan", 0x05ed}, /* U+064D ARABIC KASRATAN */
+{"Arabic_khah", 0x05ce}, /* U+062E ARABIC LETTER KHAH */
+{"Arabic_lam", 0x05e4}, /* U+0644 ARABIC LETTER LAM */
+{"Arabic_maddaonalef", 0x05c2}, /* U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE */
+{"Arabic_meem", 0x05e5}, /* U+0645 ARABIC LETTER MEEM */
+{"Arabic_noon", 0x05e6}, /* U+0646 ARABIC LETTER NOON */
+{"Arabic_qaf", 0x05e2}, /* U+0642 ARABIC LETTER QAF */
+{"Arabic_question_mark", 0x05bf}, /* U+061F ARABIC QUESTION MARK */
+{"Arabic_ra", 0x05d1}, /* U+0631 ARABIC LETTER REH */
+{"Arabic_sad", 0x05d5}, /* U+0635 ARABIC LETTER SAD */
+{"Arabic_seen", 0x05d3}, /* U+0633 ARABIC LETTER SEEN */
+{"Arabic_semicolon", 0x05bb}, /* U+061B ARABIC SEMICOLON */
+{"Arabic_shadda", 0x05f1}, /* U+0651 ARABIC SHADDA */
+{"Arabic_sheen", 0x05d4}, /* U+0634 ARABIC LETTER SHEEN */
+{"Arabic_sukun", 0x05f2}, /* U+0652 ARABIC SUKUN */
+{"Arabic_tah", 0x05d7}, /* U+0637 ARABIC LETTER TAH */
+{"Arabic_tatweel", 0x05e0}, /* U+0640 ARABIC TATWEEL */
+{"Arabic_teh", 0x05ca}, /* U+062A ARABIC LETTER TEH */
+{"Arabic_tehmarbuta", 0x05c9}, /* U+0629 ARABIC LETTER TEH MARBUTA */
+{"Arabic_thal", 0x05d0}, /* U+0630 ARABIC LETTER THAL */
+{"Arabic_theh", 0x05cb}, /* U+062B ARABIC LETTER THEH */
+{"Arabic_waw", 0x05e8}, /* U+0648 ARABIC LETTER WAW */
+{"Arabic_yeh", 0x05ea}, /* U+064A ARABIC LETTER YEH */
+{"Arabic_zah", 0x05d8}, /* U+0638 ARABIC LETTER ZAH */
+{"Arabic_zain", 0x05d2}, /* U+0632 ARABIC LETTER ZAIN */
+{"breve", 0x01a2}, /* U+02D8 BREVE */
+{"caron", 0x01b7}, /* U+02C7 CARON */
+{"Ccaron", 0x01c8}, /* U+010C LATIN CAPITAL LETTER C WITH CARON */
+{"numerosign", 0x06b0}, /* U+2116 NUMERO SIGN */
+{"Cyrillic_a", 0x06c1}, /* U+0430 CYRILLIC SMALL LETTER A */
+{"Cyrillic_A", 0x06e1}, /* U+0410 CYRILLIC CAPITAL LETTER A */
+{"Cyrillic_be", 0x06c2}, /* U+0431 CYRILLIC SMALL LETTER BE */
+{"Cyrillic_BE", 0x06e2}, /* U+0411 CYRILLIC CAPITAL LETTER BE */
+{"Cyrillic_che", 0x06de}, /* U+0447 CYRILLIC SMALL LETTER CHE */
+{"Cyrillic_CHE", 0x06fe}, /* U+0427 CYRILLIC CAPITAL LETTER CHE */
+{"Cyrillic_de", 0x06c4}, /* U+0434 CYRILLIC SMALL LETTER DE */
+{"Cyrillic_DE", 0x06e4}, /* U+0414 CYRILLIC CAPITAL LETTER DE */
+{"Cyrillic_dzhe", 0x06af}, /* U+045F CYRILLIC SMALL LETTER DZHE */
+{"Cyrillic_DZHE", 0x06bf}, /* U+040F CYRILLIC CAPITAL LETTER DZHE */
+{"Cyrillic_e", 0x06dc}, /* U+044D CYRILLIC SMALL LETTER E */
+{"Cyrillic_E", 0x06fc}, /* U+042D CYRILLIC CAPITAL LETTER E */
+{"Cyrillic_ef", 0x06c6}, /* U+0444 CYRILLIC SMALL LETTER EF */
+{"Cyrillic_EF", 0x06e6}, /* U+0424 CYRILLIC CAPITAL LETTER EF */
+{"Cyrillic_el", 0x06cc}, /* U+043B CYRILLIC SMALL LETTER EL */
+{"Cyrillic_EL", 0x06ec}, /* U+041B CYRILLIC CAPITAL LETTER EL */
+{"Cyrillic_em", 0x06cd}, /* U+043C CYRILLIC SMALL LETTER EM */
+{"Cyrillic_EM", 0x06ed}, /* U+041C CYRILLIC CAPITAL LETTER EM */
+{"Cyrillic_en", 0x06ce}, /* U+043D CYRILLIC SMALL LETTER EN */
+{"Cyrillic_EN", 0x06ee}, /* U+041D CYRILLIC CAPITAL LETTER EN */
+{"Cyrillic_er", 0x06d2}, /* U+0440 CYRILLIC SMALL LETTER ER */
+{"Cyrillic_ER", 0x06f2}, /* U+0420 CYRILLIC CAPITAL LETTER ER */
+{"Cyrillic_es", 0x06d3}, /* U+0441 CYRILLIC SMALL LETTER ES */
+{"Cyrillic_ES", 0x06f3}, /* U+0421 CYRILLIC CAPITAL LETTER ES */
+{"Cyrillic_ghe", 0x06c7}, /* U+0433 CYRILLIC SMALL LETTER GHE */
+{"Cyrillic_GHE", 0x06e7}, /* U+0413 CYRILLIC CAPITAL LETTER GHE */
+{"Cyrillic_ha", 0x06c8}, /* U+0445 CYRILLIC SMALL LETTER HA */
+{"Cyrillic_HA", 0x06e8}, /* U+0425 CYRILLIC CAPITAL LETTER HA */
+{"Cyrillic_hardsign", 0x06df}, /* U+044A CYRILLIC SMALL LETTER HARD SIGN */
+{"Cyrillic_HARDSIGN", 0x06ff}, /* U+042A CYRILLIC CAPITAL LETTER HARD SIGN */
+{"Cyrillic_i", 0x06c9}, /* U+0438 CYRILLIC SMALL LETTER I */
+{"Cyrillic_I", 0x06e9}, /* U+0418 CYRILLIC CAPITAL LETTER I */
+{"Cyrillic_ie", 0x06c5}, /* U+0435 CYRILLIC SMALL LETTER IE */
+{"Cyrillic_IE", 0x06e5}, /* U+0415 CYRILLIC CAPITAL LETTER IE */
+{"Cyrillic_io", 0x06a3}, /* U+0451 CYRILLIC SMALL LETTER IO */
+{"Cyrillic_IO", 0x06b3}, /* U+0401 CYRILLIC CAPITAL LETTER IO */
+{"Cyrillic_je", 0x06a8}, /* U+0458 CYRILLIC SMALL LETTER JE */
+{"Cyrillic_JE", 0x06b8}, /* U+0408 CYRILLIC CAPITAL LETTER JE */
+{"Cyrillic_ka", 0x06cb}, /* U+043A CYRILLIC SMALL LETTER KA */
+{"Cyrillic_KA", 0x06eb}, /* U+041A CYRILLIC CAPITAL LETTER KA */
+{"Cyrillic_lje", 0x06a9}, /* U+0459 CYRILLIC SMALL LETTER LJE */
+{"Cyrillic_LJE", 0x06b9}, /* U+0409 CYRILLIC CAPITAL LETTER LJE */
+{"Cyrillic_nje", 0x06aa}, /* U+045A CYRILLIC SMALL LETTER NJE */
+{"Cyrillic_NJE", 0x06ba}, /* U+040A CYRILLIC CAPITAL LETTER NJE */
+{"Cyrillic_o", 0x06cf}, /* U+043E CYRILLIC SMALL LETTER O */
+{"Cyrillic_O", 0x06ef}, /* U+041E CYRILLIC CAPITAL LETTER O */
+{"Cyrillic_pe", 0x06d0}, /* U+043F CYRILLIC SMALL LETTER PE */
+{"Cyrillic_PE", 0x06f0}, /* U+041F CYRILLIC CAPITAL LETTER PE */
+{"Cyrillic_sha", 0x06db}, /* U+0448 CYRILLIC SMALL LETTER SHA */
+{"Cyrillic_SHA", 0x06fb}, /* U+0428 CYRILLIC CAPITAL LETTER SHA */
+{"Cyrillic_shcha", 0x06dd}, /* U+0449 CYRILLIC SMALL LETTER SHCHA */
+{"Cyrillic_SHCHA", 0x06fd}, /* U+0429 CYRILLIC CAPITAL LETTER SHCHA */
+{"Cyrillic_shorti", 0x06ca}, /* U+0439 CYRILLIC SMALL LETTER SHORT I */
+{"Cyrillic_SHORTI", 0x06ea}, /* U+0419 CYRILLIC CAPITAL LETTER SHORT I */
+{"Cyrillic_softsign", 0x06d8}, /* U+044C CYRILLIC SMALL LETTER SOFT SIGN */
+{"Cyrillic_SOFTSIGN", 0x06f8}, /* U+042C CYRILLIC CAPITAL LETTER SOFT SIGN */
+{"Cyrillic_te", 0x06d4}, /* U+0442 CYRILLIC SMALL LETTER TE */
+{"Cyrillic_TE", 0x06f4}, /* U+0422 CYRILLIC CAPITAL LETTER TE */
+{"Cyrillic_tse", 0x06c3}, /* U+0446 CYRILLIC SMALL LETTER TSE */
+{"Cyrillic_TSE", 0x06e3}, /* U+0426 CYRILLIC CAPITAL LETTER TSE */
+{"Cyrillic_u", 0x06d5}, /* U+0443 CYRILLIC SMALL LETTER U */
+{"Cyrillic_U", 0x06f5}, /* U+0423 CYRILLIC CAPITAL LETTER U */
+{"Cyrillic_ve", 0x06d7}, /* U+0432 CYRILLIC SMALL LETTER VE */
+{"Cyrillic_VE", 0x06f7}, /* U+0412 CYRILLIC CAPITAL LETTER VE */
+{"Cyrillic_ya", 0x06d1}, /* U+044F CYRILLIC SMALL LETTER YA */
+{"Cyrillic_YA", 0x06f1}, /* U+042F CYRILLIC CAPITAL LETTER YA */
+{"Cyrillic_yeru", 0x06d9}, /* U+044B CYRILLIC SMALL LETTER YERU */
+{"Cyrillic_YERU", 0x06f9}, /* U+042B CYRILLIC CAPITAL LETTER YERU */
+{"Cyrillic_yu", 0x06c0}, /* U+044E CYRILLIC SMALL LETTER YU */
+{"Cyrillic_YU", 0x06e0}, /* U+042E CYRILLIC CAPITAL LETTER YU */
+{"Cyrillic_ze", 0x06da}, /* U+0437 CYRILLIC SMALL LETTER ZE */
+{"Cyrillic_ZE", 0x06fa}, /* U+0417 CYRILLIC CAPITAL LETTER ZE */
+{"Cyrillic_zhe", 0x06d6}, /* U+0436 CYRILLIC SMALL LETTER ZHE */
+{"Cyrillic_ZHE", 0x06f6}, /* U+0416 CYRILLIC CAPITAL LETTER ZHE */
+{"doubleacute", 0x01bd}, /* U+02DD DOUBLE ACUTE ACCENT */
+{"doublelowquotemark", 0x0afe}, /* U+201E DOUBLE LOW-9 QUOTATION MARK */
+{"downarrow", 0x08fe}, /* U+2193 DOWNWARDS ARROW */
+{"dstroke", 0x01f0}, /* U+0111 LATIN SMALL LETTER D WITH STROKE */
+{"Dstroke", 0x01d0}, /* U+0110 LATIN CAPITAL LETTER D WITH STROKE */
+{"eabovedot", 0x03ec}, /* U+0117 LATIN SMALL LETTER E WITH DOT ABOVE */
+{"Eabovedot", 0x03cc}, /* U+0116 LATIN CAPITAL LETTER E WITH DOT ABOVE */
+{"emacron", 0x03ba}, /* U+0113 LATIN SMALL LETTER E WITH MACRON */
+{"Emacron", 0x03aa}, /* U+0112 LATIN CAPITAL LETTER E WITH MACRON */
+{"endash", 0x0aaa}, /* U+2013 EN DASH */
+{"eng", 0x03bf}, /* U+014B LATIN SMALL LETTER ENG */
+{"ENG", 0x03bd}, /* U+014A LATIN CAPITAL LETTER ENG */
+{"Execute", 0xff62}, /* Execute, run, do */
+{"F16", 0xffcd},
+{"F17", 0xffce},
+{"F18", 0xffcf},
+{"F19", 0xffd0},
+{"F20", 0xffd1},
+{"F21", 0xffd2},
+{"F22", 0xffd3},
+{"F23", 0xffd4},
+{"F24", 0xffd5},
+{"F25", 0xffd6},
+{"F26", 0xffd7},
+{"F27", 0xffd8},
+{"F28", 0xffd9},
+{"F29", 0xffda},
+{"F30", 0xffdb},
+{"F31", 0xffdc},
+{"F32", 0xffdd},
+{"F33", 0xffde},
+{"F34", 0xffdf},
+{"F35", 0xffe0},
+{"fiveeighths", 0x0ac5}, /* U+215D VULGAR FRACTION FIVE EIGHTHS */
+{"gbreve", 0x02bb}, /* U+011F LATIN SMALL LETTER G WITH BREVE */
+{"Gbreve", 0x02ab}, /* U+011E LATIN CAPITAL LETTER G WITH BREVE */
+{"gcedilla", 0x03bb}, /* U+0123 LATIN SMALL LETTER G WITH CEDILLA */
+{"Gcedilla", 0x03ab}, /* U+0122 LATIN CAPITAL LETTER G WITH CEDILLA */
+{"Greek_OMEGA", 0x07d9}, /* U+03A9 GREEK CAPITAL LETTER OMEGA */
+{"Henkan_Mode", 0xff23}, /* Start/Stop Conversion */
+{"horizconnector", 0x08a3}, /*(U+2500 BOX DRAWINGS LIGHT HORIZONTAL)*/
+{"hstroke", 0x02b1}, /* U+0127 LATIN SMALL LETTER H WITH STROKE */
+{"Hstroke", 0x02a1}, /* U+0126 LATIN CAPITAL LETTER H WITH STROKE */
+{"Iabovedot", 0x02a9}, /* U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE */
+{"idotless", 0x02b9}, /* U+0131 LATIN SMALL LETTER DOTLESS I */
+{"imacron", 0x03ef}, /* U+012B LATIN SMALL LETTER I WITH MACRON */
+{"Imacron", 0x03cf}, /* U+012A LATIN CAPITAL LETTER I WITH MACRON */
+{"iogonek", 0x03e7}, /* U+012F LATIN SMALL LETTER I WITH OGONEK */
+{"Iogonek", 0x03c7}, /* U+012E LATIN CAPITAL LETTER I WITH OGONEK */
+{"ISO_First_Group", 0xfe0c},
+{"ISO_Last_Group", 0xfe0e},
+{"ISO_Next_Group", 0xfe08},
+{"kana_a", 0x04a7}, /* U+30A1 KATAKANA LETTER SMALL A */
+{"kana_A", 0x04b1}, /* U+30A2 KATAKANA LETTER A */
+{"kana_CHI", 0x04c1}, /* U+30C1 KATAKANA LETTER TI */
+{"kana_closingbracket", 0x04a3}, /* U+300D RIGHT CORNER BRACKET */
+{"kana_comma", 0x04a4}, /* U+3001 IDEOGRAPHIC COMMA */
+{"kana_conjunctive", 0x04a5}, /* U+30FB KATAKANA MIDDLE DOT */
+{"kana_e", 0x04aa}, /* U+30A7 KATAKANA LETTER SMALL E */
+{"kana_E", 0x04b4}, /* U+30A8 KATAKANA LETTER E */
+{"kana_FU", 0x04cc}, /* U+30D5 KATAKANA LETTER HU */
+{"kana_fullstop", 0x04a1}, /* U+3002 IDEOGRAPHIC FULL STOP */
+{"kana_HA", 0x04ca}, /* U+30CF KATAKANA LETTER HA */
+{"kana_HE", 0x04cd}, /* U+30D8 KATAKANA LETTER HE */
+{"kana_HI", 0x04cb}, /* U+30D2 KATAKANA LETTER HI */
+{"kana_HO", 0x04ce}, /* U+30DB KATAKANA LETTER HO */
+{"kana_i", 0x04a8}, /* U+30A3 KATAKANA LETTER SMALL I */
+{"kana_I", 0x04b2}, /* U+30A4 KATAKANA LETTER I */
+{"kana_KA", 0x04b6}, /* U+30AB KATAKANA LETTER KA */
+{"kana_KE", 0x04b9}, /* U+30B1 KATAKANA LETTER KE */
+{"kana_KI", 0x04b7}, /* U+30AD KATAKANA LETTER KI */
+{"kana_KO", 0x04ba}, /* U+30B3 KATAKANA LETTER KO */
+{"kana_KU", 0x04b8}, /* U+30AF KATAKANA LETTER KU */
+{"kana_MA", 0x04cf}, /* U+30DE KATAKANA LETTER MA */
+{"kana_ME", 0x04d2}, /* U+30E1 KATAKANA LETTER ME */
+{"kana_MI", 0x04d0}, /* U+30DF KATAKANA LETTER MI */
+{"kana_MO", 0x04d3}, /* U+30E2 KATAKANA LETTER MO */
+{"kana_MU", 0x04d1}, /* U+30E0 KATAKANA LETTER MU */
+{"kana_N", 0x04dd}, /* U+30F3 KATAKANA LETTER N */
+{"kana_NA", 0x04c5}, /* U+30CA KATAKANA LETTER NA */
+{"kana_NE", 0x04c8}, /* U+30CD KATAKANA LETTER NE */
+{"kana_NI", 0x04c6}, /* U+30CB KATAKANA LETTER NI */
+{"kana_NO", 0x04c9}, /* U+30CE KATAKANA LETTER NO */
+{"kana_NU", 0x04c7}, /* U+30CC KATAKANA LETTER NU */
+{"kana_o", 0x04ab}, /* U+30A9 KATAKANA LETTER SMALL O */
+{"kana_O", 0x04b5}, /* U+30AA KATAKANA LETTER O */
+{"kana_openingbracket", 0x04a2}, /* U+300C LEFT CORNER BRACKET */
+{"kana_RA", 0x04d7}, /* U+30E9 KATAKANA LETTER RA */
+{"kana_RE", 0x04da}, /* U+30EC KATAKANA LETTER RE */
+{"kana_RI", 0x04d8}, /* U+30EA KATAKANA LETTER RI */
+{"kana_RU", 0x04d9}, /* U+30EB KATAKANA LETTER RU */
+{"kana_SA", 0x04bb}, /* U+30B5 KATAKANA LETTER SA */
+{"kana_SE", 0x04be}, /* U+30BB KATAKANA LETTER SE */
+{"kana_SHI", 0x04bc}, /* U+30B7 KATAKANA LETTER SI */
+{"kana_SO", 0x04bf}, /* U+30BD KATAKANA LETTER SO */
+{"kana_SU", 0x04bd}, /* U+30B9 KATAKANA LETTER SU */
+{"kana_TA", 0x04c0}, /* U+30BF KATAKANA LETTER TA */
+{"kana_TE", 0x04c3}, /* U+30C6 KATAKANA LETTER TE */
+{"kana_TO", 0x04c4}, /* U+30C8 KATAKANA LETTER TO */
+{"kana_tsu", 0x04af}, /* U+30C3 KATAKANA LETTER SMALL TU */
+{"kana_TSU", 0x04c2}, /* U+30C4 KATAKANA LETTER TU */
+{"kana_u", 0x04a9}, /* U+30A5 KATAKANA LETTER SMALL U */
+{"kana_U", 0x04b3}, /* U+30A6 KATAKANA LETTER U */
+{"kana_WA", 0x04dc}, /* U+30EF KATAKANA LETTER WA */
+{"kana_WO", 0x04a6}, /* U+30F2 KATAKANA LETTER WO */
+{"kana_ya", 0x04ac}, /* U+30E3 KATAKANA LETTER SMALL YA */
+{"kana_YA", 0x04d4}, /* U+30E4 KATAKANA LETTER YA */
+{"kana_yo", 0x04ae}, /* U+30E7 KATAKANA LETTER SMALL YO */
+{"kana_YO", 0x04d6}, /* U+30E8 KATAKANA LETTER YO */
+{"kana_yu", 0x04ad}, /* U+30E5 KATAKANA LETTER SMALL YU */
+{"kana_YU", 0x04d5}, /* U+30E6 KATAKANA LETTER YU */
+{"Kanji", 0xff21}, /* Kanji, Kanji convert */
+{"kcedilla", 0x03f3}, /* U+0137 LATIN SMALL LETTER K WITH CEDILLA */
+{"Kcedilla", 0x03d3}, /* U+0136 LATIN CAPITAL LETTER K WITH CEDILLA */
+{"kra", 0x03a2}, /* U+0138 LATIN SMALL LETTER KRA */
+{"lcedilla", 0x03b6}, /* U+013C LATIN SMALL LETTER L WITH CEDILLA */
+{"Lcedilla", 0x03a6}, /* U+013B LATIN CAPITAL LETTER L WITH CEDILLA */
+{"leftarrow", 0x08fb}, /* U+2190 LEFTWARDS ARROW */
+{"leftdoublequotemark", 0x0ad2}, /* U+201C LEFT DOUBLE QUOTATION MARK */
+{"Macedonia_dse", 0x06a5}, /* U+0455 CYRILLIC SMALL LETTER DZE */
+{"Macedonia_DSE", 0x06b5}, /* U+0405 CYRILLIC CAPITAL LETTER DZE */
+{"Macedonia_gje", 0x06a2}, /* U+0453 CYRILLIC SMALL LETTER GJE */
+{"Macedonia_GJE", 0x06b2}, /* U+0403 CYRILLIC CAPITAL LETTER GJE */
+{"Macedonia_kje", 0x06ac}, /* U+045C CYRILLIC SMALL LETTER KJE */
+{"Macedonia_KJE", 0x06bc}, /* U+040C CYRILLIC CAPITAL LETTER KJE */
+{"ncedilla", 0x03f1}, /* U+0146 LATIN SMALL LETTER N WITH CEDILLA */
+{"Ncedilla", 0x03d1}, /* U+0145 LATIN CAPITAL LETTER N WITH CEDILLA */
+{"oe", 0x13bd}, /* U+0153 LATIN SMALL LIGATURE OE */
+{"OE", 0x13bc}, /* U+0152 LATIN CAPITAL LIGATURE OE */
+{"ogonek", 0x01b2}, /* U+02DB OGONEK */
+{"omacron", 0x03f2}, /* U+014D LATIN SMALL LETTER O WITH MACRON */
+{"Omacron", 0x03d2}, /* U+014C LATIN CAPITAL LETTER O WITH MACRON */
+{"oneeighth", 0x0ac3}, /* U+215B VULGAR FRACTION ONE EIGHTH */
+{"rcedilla", 0x03b3}, /* U+0157 LATIN SMALL LETTER R WITH CEDILLA */
+{"Rcedilla", 0x03a3}, /* U+0156 LATIN CAPITAL LETTER R WITH CEDILLA */
+{"rightarrow", 0x08fd}, /* U+2192 RIGHTWARDS ARROW */
+{"rightdoublequotemark", 0x0ad3}, /* U+201D RIGHT DOUBLE QUOTATION MARK */
+{"Scaron", 0x01a9}, /* U+0160 LATIN CAPITAL LETTER S WITH CARON */
+{"scedilla", 0x01ba}, /* U+015F LATIN SMALL LETTER S WITH CEDILLA */
+{"Scedilla", 0x01aa}, /* U+015E LATIN CAPITAL LETTER S WITH CEDILLA */
+{"semivoicedsound", 0x04df}, /* U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */
+{"seveneighths", 0x0ac6}, /* U+215E VULGAR FRACTION SEVEN EIGHTHS */
+{"Thai_baht", 0x0ddf}, /* U+0E3F THAI CURRENCY SYMBOL BAHT */
+{"Thai_bobaimai", 0x0dba}, /* U+0E1A THAI CHARACTER BO BAIMAI */
+{"Thai_chochan", 0x0da8}, /* U+0E08 THAI CHARACTER CHO CHAN */
+{"Thai_chochang", 0x0daa}, /* U+0E0A THAI CHARACTER CHO CHANG */
+{"Thai_choching", 0x0da9}, /* U+0E09 THAI CHARACTER CHO CHING */
+{"Thai_chochoe", 0x0dac}, /* U+0E0C THAI CHARACTER CHO CHOE */
+{"Thai_dochada", 0x0dae}, /* U+0E0E THAI CHARACTER DO CHADA */
+{"Thai_dodek", 0x0db4}, /* U+0E14 THAI CHARACTER DO DEK */
+{"Thai_fofa", 0x0dbd}, /* U+0E1D THAI CHARACTER FO FA */
+{"Thai_fofan", 0x0dbf}, /* U+0E1F THAI CHARACTER FO FAN */
+{"Thai_hohip", 0x0dcb}, /* U+0E2B THAI CHARACTER HO HIP */
+{"Thai_honokhuk", 0x0dce}, /* U+0E2E THAI CHARACTER HO NOKHUK */
+{"Thai_khokhai", 0x0da2}, /* U+0E02 THAI CHARACTER KHO KHAI */
+{"Thai_khokhon", 0x0da5}, /* U+0E05 THAI CHARACTER KHO KHON */
+{"Thai_khokhuat", 0x0da3}, /* U+0E03 THAI CHARACTER KHO KHUAT */
+{"Thai_khokhwai", 0x0da4}, /* U+0E04 THAI CHARACTER KHO KHWAI */
+{"Thai_khorakhang", 0x0da6}, /* U+0E06 THAI CHARACTER KHO RAKHANG */
+{"Thai_kokai", 0x0da1}, /* U+0E01 THAI CHARACTER KO KAI */
+{"Thai_lakkhangyao", 0x0de5}, /* U+0E45 THAI CHARACTER LAKKHANGYAO */
+{"Thai_lekchet", 0x0df7}, /* U+0E57 THAI DIGIT SEVEN */
+{"Thai_lekha", 0x0df5}, /* U+0E55 THAI DIGIT FIVE */
+{"Thai_lekhok", 0x0df6}, /* U+0E56 THAI DIGIT SIX */
+{"Thai_lekkao", 0x0df9}, /* U+0E59 THAI DIGIT NINE */
+{"Thai_leknung", 0x0df1}, /* U+0E51 THAI DIGIT ONE */
+{"Thai_lekpaet", 0x0df8}, /* U+0E58 THAI DIGIT EIGHT */
+{"Thai_leksam", 0x0df3}, /* U+0E53 THAI DIGIT THREE */
+{"Thai_leksi", 0x0df4}, /* U+0E54 THAI DIGIT FOUR */
+{"Thai_leksong", 0x0df2}, /* U+0E52 THAI DIGIT TWO */
+{"Thai_leksun", 0x0df0}, /* U+0E50 THAI DIGIT ZERO */
+{"Thai_lochula", 0x0dcc}, /* U+0E2C THAI CHARACTER LO CHULA */
+{"Thai_loling", 0x0dc5}, /* U+0E25 THAI CHARACTER LO LING */
+{"Thai_lu", 0x0dc6}, /* U+0E26 THAI CHARACTER LU */
+{"Thai_maichattawa", 0x0deb}, /* U+0E4B THAI CHARACTER MAI CHATTAWA */
+{"Thai_maiek", 0x0de8}, /* U+0E48 THAI CHARACTER MAI EK */
+{"Thai_maihanakat", 0x0dd1}, /* U+0E31 THAI CHARACTER MAI HAN-AKAT */
+{"Thai_maitaikhu", 0x0de7}, /* U+0E47 THAI CHARACTER MAITAIKHU */
+{"Thai_maitho", 0x0de9}, /* U+0E49 THAI CHARACTER MAI THO */
+{"Thai_maitri", 0x0dea}, /* U+0E4A THAI CHARACTER MAI TRI */
+{"Thai_maiyamok", 0x0de6}, /* U+0E46 THAI CHARACTER MAIYAMOK */
+{"Thai_moma", 0x0dc1}, /* U+0E21 THAI CHARACTER MO MA */
+{"Thai_ngongu", 0x0da7}, /* U+0E07 THAI CHARACTER NGO NGU */
+{"Thai_nikhahit", 0x0ded}, /* U+0E4D THAI CHARACTER NIKHAHIT */
+{"Thai_nonen", 0x0db3}, /* U+0E13 THAI CHARACTER NO NEN */
+{"Thai_nonu", 0x0db9}, /* U+0E19 THAI CHARACTER NO NU */
+{"Thai_oang", 0x0dcd}, /* U+0E2D THAI CHARACTER O ANG */
+{"Thai_paiyannoi", 0x0dcf}, /* U+0E2F THAI CHARACTER PAIYANNOI */
+{"Thai_phinthu", 0x0dda}, /* U+0E3A THAI CHARACTER PHINTHU */
+{"Thai_phophan", 0x0dbe}, /* U+0E1E THAI CHARACTER PHO PHAN */
+{"Thai_phophung", 0x0dbc}, /* U+0E1C THAI CHARACTER PHO PHUNG */
+{"Thai_phosamphao", 0x0dc0}, /* U+0E20 THAI CHARACTER PHO SAMPHAO */
+{"Thai_popla", 0x0dbb}, /* U+0E1B THAI CHARACTER PO PLA */
+{"Thai_rorua", 0x0dc3}, /* U+0E23 THAI CHARACTER RO RUA */
+{"Thai_ru", 0x0dc4}, /* U+0E24 THAI CHARACTER RU */
+{"Thai_saraa", 0x0dd0}, /* U+0E30 THAI CHARACTER SARA A */
+{"Thai_saraaa", 0x0dd2}, /* U+0E32 THAI CHARACTER SARA AA */
+{"Thai_saraae", 0x0de1}, /* U+0E41 THAI CHARACTER SARA AE */
+{"Thai_saraaimaimalai", 0x0de4}, /* U+0E44 THAI CHARACTER SARA AI MAIMALAI */
+{"Thai_saraaimaimuan", 0x0de3}, /* U+0E43 THAI CHARACTER SARA AI MAIMUAN */
+{"Thai_saraam", 0x0dd3}, /* U+0E33 THAI CHARACTER SARA AM */
+{"Thai_sarae", 0x0de0}, /* U+0E40 THAI CHARACTER SARA E */
+{"Thai_sarai", 0x0dd4}, /* U+0E34 THAI CHARACTER SARA I */
+{"Thai_saraii", 0x0dd5}, /* U+0E35 THAI CHARACTER SARA II */
+{"Thai_sarao", 0x0de2}, /* U+0E42 THAI CHARACTER SARA O */
+{"Thai_sarau", 0x0dd8}, /* U+0E38 THAI CHARACTER SARA U */
+{"Thai_saraue", 0x0dd6}, /* U+0E36 THAI CHARACTER SARA UE */
+{"Thai_sarauee", 0x0dd7}, /* U+0E37 THAI CHARACTER SARA UEE */
+{"Thai_sarauu", 0x0dd9}, /* U+0E39 THAI CHARACTER SARA UU */
+{"Thai_sorusi", 0x0dc9}, /* U+0E29 THAI CHARACTER SO RUSI */
+{"Thai_sosala", 0x0dc8}, /* U+0E28 THAI CHARACTER SO SALA */
+{"Thai_soso", 0x0dab}, /* U+0E0B THAI CHARACTER SO SO */
+{"Thai_sosua", 0x0dca}, /* U+0E2A THAI CHARACTER SO SUA */
+{"Thai_thanthakhat", 0x0dec}, /* U+0E4C THAI CHARACTER THANTHAKHAT */
+{"Thai_thonangmontho", 0x0db1}, /* U+0E11 THAI CHARACTER THO NANGMONTHO */
+{"Thai_thophuthao", 0x0db2}, /* U+0E12 THAI CHARACTER THO PHUTHAO */
+{"Thai_thothahan", 0x0db7}, /* U+0E17 THAI CHARACTER THO THAHAN */
+{"Thai_thothan", 0x0db0}, /* U+0E10 THAI CHARACTER THO THAN */
+{"Thai_thothong", 0x0db8}, /* U+0E18 THAI CHARACTER THO THONG */
+{"Thai_thothung", 0x0db6}, /* U+0E16 THAI CHARACTER THO THUNG */
+{"Thai_topatak", 0x0daf}, /* U+0E0F THAI CHARACTER TO PATAK */
+{"Thai_totao", 0x0db5}, /* U+0E15 THAI CHARACTER TO TAO */
+{"Thai_wowaen", 0x0dc7}, /* U+0E27 THAI CHARACTER WO WAEN */
+{"Thai_yoyak", 0x0dc2}, /* U+0E22 THAI CHARACTER YO YAK */
+{"Thai_yoying", 0x0dad}, /* U+0E0D THAI CHARACTER YO YING */
+{"threeeighths", 0x0ac4}, /* U+215C VULGAR FRACTION THREE EIGHTHS */
+{"trademark", 0x0ac9}, /* U+2122 TRADE MARK SIGN */
+{"tslash", 0x03bc}, /* U+0167 LATIN SMALL LETTER T WITH STROKE */
+{"Tslash", 0x03ac}, /* U+0166 LATIN CAPITAL LETTER T WITH STROKE */
+{"umacron", 0x03fe}, /* U+016B LATIN SMALL LETTER U WITH MACRON */
+{"Umacron", 0x03de}, /* U+016A LATIN CAPITAL LETTER U WITH MACRON */
+{"uogonek", 0x03f9}, /* U+0173 LATIN SMALL LETTER U WITH OGONEK */
+{"Uogonek", 0x03d9}, /* U+0172 LATIN CAPITAL LETTER U WITH OGONEK */
+{"uparrow", 0x08fc}, /* U+2191 UPWARDS ARROW */
+{"voicedsound", 0x04de}, /* U+309B KATAKANA-HIRAGANA VOICED SOUND MARK */
+{"Zcaron", 0x01ae}, /* U+017D LATIN CAPITAL LETTER Z WITH CARON */
+
+{NULL,0},
+};
diff --git a/ui/win32-kbd-hook.c b/ui/win32-kbd-hook.c
new file mode 100644
index 000000000..1ac237db9
--- /dev/null
+++ b/ui/win32-kbd-hook.c
@@ -0,0 +1,102 @@
+/*
+ * This work is licensed under the terms of the GNU GPL, version 2 or
+ * (at your option) any later version. See the COPYING file in the
+ * top-level directory.
+ *
+ * The win32 keyboard hooking code was imported from project spice-gtk.
+ */
+
+#include "qemu/osdep.h"
+#include "sysemu/sysemu.h"
+#include "ui/win32-kbd-hook.h"
+
+static Notifier win32_unhook_notifier;
+static HHOOK win32_keyboard_hook;
+static HWND win32_window;
+static DWORD win32_grab;
+
+static LRESULT CALLBACK keyboard_hook_cb(int code, WPARAM wparam, LPARAM lparam)
+{
+ if (win32_window && code == HC_ACTION && win32_window == GetFocus()) {
+ KBDLLHOOKSTRUCT *hooked = (KBDLLHOOKSTRUCT *)lparam;
+
+ if (wparam != WM_KEYUP) {
+ DWORD dwmsg = (hooked->flags << 24) |
+ ((hooked->scanCode & 0xff) << 16) | 1;
+
+ switch (hooked->vkCode) {
+ case VK_CAPITAL:
+ /* fall through */
+ case VK_SCROLL:
+ /* fall through */
+ case VK_NUMLOCK:
+ /* fall through */
+ case VK_LSHIFT:
+ /* fall through */
+ case VK_RSHIFT:
+ /* fall through */
+ case VK_RCONTROL:
+ /* fall through */
+ case VK_LMENU:
+ /* fall through */
+ case VK_RMENU:
+ break;
+
+ case VK_LCONTROL:
+ /*
+ * When pressing AltGr, an extra VK_LCONTROL with a special
+ * scancode with bit 9 set is sent. Let's ignore the extra
+ * VK_LCONTROL, as that will make AltGr misbehave.
+ */
+ if (hooked->scanCode & 0x200) {
+ return 1;
+ }
+ break;
+
+ default:
+ if (win32_grab) {
+ SendMessage(win32_window, wparam, hooked->vkCode, dwmsg);
+ return 1;
+ }
+ break;
+ }
+
+ } else {
+ switch (hooked->vkCode) {
+ case VK_LCONTROL:
+ if (hooked->scanCode & 0x200) {
+ return 1;
+ }
+ break;
+ }
+ }
+ }
+
+ return CallNextHookEx(NULL, code, wparam, lparam);
+}
+
+static void keyboard_hook_unhook(Notifier *n, void *data)
+{
+ UnhookWindowsHookEx(win32_keyboard_hook);
+ win32_keyboard_hook = NULL;
+}
+
+void win32_kbd_set_window(void *hwnd)
+{
+ if (hwnd && !win32_keyboard_hook) {
+ /* note: the installing thread must have a message loop */
+ win32_keyboard_hook = SetWindowsHookEx(WH_KEYBOARD_LL, keyboard_hook_cb,
+ GetModuleHandle(NULL), 0);
+ if (win32_keyboard_hook) {
+ win32_unhook_notifier.notify = keyboard_hook_unhook;
+ qemu_add_exit_notifier(&win32_unhook_notifier);
+ }
+ }
+
+ win32_window = hwnd;
+}
+
+void win32_kbd_set_grab(bool grab)
+{
+ win32_grab = grab;
+}
diff --git a/ui/x_keymap.c b/ui/x_keymap.c
new file mode 100644
index 000000000..2ce7b8996
--- /dev/null
+++ b/ui/x_keymap.c
@@ -0,0 +1,119 @@
+/*
+ * QEMU X11 keymaps
+ *
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ * Copyright (C) 2017 Red Hat, Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1 as
+ * published by the Free Software Foundation.
+ */
+
+#include "qemu/osdep.h"
+
+#include "x_keymap.h"
+#include "trace.h"
+#include "qemu/notify.h"
+#include "ui/input.h"
+
+#include <X11/XKBlib.h>
+#include <X11/Xutil.h>
+
+static gboolean check_for_xwin(Display *dpy)
+{
+ const char *vendor = ServerVendor(dpy);
+
+ trace_xkeymap_vendor(vendor);
+
+ if (strstr(vendor, "Cygwin/X")) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean check_for_xquartz(Display *dpy)
+{
+ int nextensions;
+ int i;
+ gboolean match = FALSE;
+ char **extensions = XListExtensions(dpy, &nextensions);
+ for (i = 0 ; extensions != NULL && i < nextensions ; i++) {
+ trace_xkeymap_extension(extensions[i]);
+ if (strcmp(extensions[i], "Apple-WM") == 0 ||
+ strcmp(extensions[i], "Apple-DRI") == 0) {
+ match = TRUE;
+ }
+ }
+ if (extensions) {
+ XFreeExtensionList(extensions);
+ }
+
+ return match;
+}
+
+const guint16 *qemu_xkeymap_mapping_table(Display *dpy, size_t *maplen)
+{
+ XkbDescPtr desc;
+ const gchar *keycodes = NULL;
+ const guint16 *map;
+
+ /* There is no easy way to determine what X11 server
+ * and platform & keyboard driver is in use. Thus we
+ * do best guess heuristics.
+ *
+ * This will need more work for people with other
+ * X servers..... patches welcomed.
+ */
+
+ desc = XkbGetMap(dpy,
+ XkbGBN_AllComponentsMask,
+ XkbUseCoreKbd);
+ if (desc) {
+ if (XkbGetNames(dpy, XkbKeycodesNameMask, desc) == Success) {
+ keycodes = XGetAtomName (dpy, desc->names->keycodes);
+ if (!keycodes) {
+ g_warning("could not lookup keycode name");
+ } else {
+ trace_xkeymap_keycodes(keycodes);
+ }
+ }
+ XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True);
+ }
+
+ if (check_for_xwin(dpy)) {
+ trace_xkeymap_keymap("xwin");
+ *maplen = qemu_input_map_xorgxwin_to_qcode_len;
+ map = qemu_input_map_xorgxwin_to_qcode;
+ } else if (check_for_xquartz(dpy)) {
+ trace_xkeymap_keymap("xquartz");
+ *maplen = qemu_input_map_xorgxquartz_to_qcode_len;
+ map = qemu_input_map_xorgxquartz_to_qcode;
+ } else if ((keycodes && g_str_has_prefix(keycodes, "evdev")) ||
+ (XKeysymToKeycode(dpy, XK_Page_Up) == 0x70)) {
+ trace_xkeymap_keymap("evdev");
+ *maplen = qemu_input_map_xorgevdev_to_qcode_len;
+ map = qemu_input_map_xorgevdev_to_qcode;
+ } else if ((keycodes && g_str_has_prefix(keycodes, "xfree86")) ||
+ (XKeysymToKeycode(dpy, XK_Page_Up) == 0x63)) {
+ trace_xkeymap_keymap("kbd");
+ *maplen = qemu_input_map_xorgkbd_to_qcode_len;
+ map = qemu_input_map_xorgkbd_to_qcode;
+ } else {
+ trace_xkeymap_keymap("NULL");
+ g_warning("Unknown X11 keycode mapping '%s'.\n"
+ "Please report to qemu-devel@nongnu.org\n"
+ "including the following information:\n"
+ "\n"
+ " - Operating system\n"
+ " - X11 Server\n"
+ " - xprop -root\n"
+ " - xdpyinfo\n",
+ keycodes ? keycodes : "<null>");
+ map = NULL;
+ }
+ if (keycodes) {
+ XFree((void *)keycodes);
+ }
+ return map;
+}
diff --git a/ui/x_keymap.h b/ui/x_keymap.h
new file mode 100644
index 000000000..0395e335f
--- /dev/null
+++ b/ui/x_keymap.h
@@ -0,0 +1,32 @@
+/*
+ * QEMU X11 keymaps
+ *
+ * Copyright (c) 2017 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_X_KEYMAP_H
+#define QEMU_X_KEYMAP_H
+
+#include <X11/Xlib.h>
+
+const guint16 *qemu_xkeymap_mapping_table(Display *dpy, size_t *maplen);
+
+#endif